qzdoom/src/hwrenderer/textures/hw_material.cpp
Christoph Oelckers 08fe9c375b - use the same formula for calculating 3DMidTex offsets as the renderer when per-sidedef scaling is used.
This reuses the FTexCoordInfo class the hardware renderer had been using to calculate wall texture offsetting.
The software renderers still need this sorted out to bring them in line with the rest of the code, though, but they do not have this code sufficiently well organized to make this a straightforward task.
2018-11-17 18:24:14 +01:00

594 lines
15 KiB
C++

//
//---------------------------------------------------------------------------
//
// Copyright(C) 2004-2016 Christoph Oelckers
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
//
#include "w_wad.h"
#include "m_png.h"
#include "sbar.h"
#include "stats.h"
#include "r_utility.h"
#include "c_dispatch.h"
#include "hw_ihwtexture.h"
#include "hw_material.h"
EXTERN_CVAR(Bool, gl_texture_usehires)
//===========================================================================
//
// Quick'n dirty image rescaling.
//
// This will only be used when the source texture is larger than
// what the hardware can manage (extremely rare in Doom)
//
// Code taken from wxWidgets
//
//===========================================================================
struct BoxPrecalc
{
int boxStart;
int boxEnd;
};
static void ResampleBoxPrecalc(TArray<BoxPrecalc>& boxes, int oldDim)
{
int newDim = boxes.Size();
const double scale_factor_1 = double(oldDim) / newDim;
const int scale_factor_2 = (int)(scale_factor_1 / 2);
for (int dst = 0; dst < newDim; ++dst)
{
// Source pixel in the Y direction
const int src_p = int(dst * scale_factor_1);
BoxPrecalc& precalc = boxes[dst];
precalc.boxStart = clamp<int>(int(src_p - scale_factor_1 / 2.0 + 1), 0, oldDim - 1);
precalc.boxEnd = clamp<int>(MAX<int>(precalc.boxStart + 1, int(src_p + scale_factor_2)), 0, oldDim - 1);
}
}
void IHardwareTexture::Resize(int swidth, int sheight, int width, int height, unsigned char *src_data, unsigned char *dst_data)
{
// This function implements a simple pre-blur/box averaging method for
// downsampling that gives reasonably smooth results To scale the image
// down we will need to gather a grid of pixels of the size of the scale
// factor in each direction and then do an averaging of the pixels.
TArray<BoxPrecalc> vPrecalcs(height, true);
TArray<BoxPrecalc> hPrecalcs(width, true);
ResampleBoxPrecalc(vPrecalcs, sheight);
ResampleBoxPrecalc(hPrecalcs, swidth);
int averaged_pixels, averaged_alpha, src_pixel_index;
double sum_r, sum_g, sum_b, sum_a;
for (int y = 0; y < height; y++) // Destination image - Y direction
{
// Source pixel in the Y direction
const BoxPrecalc& vPrecalc = vPrecalcs[y];
for (int x = 0; x < width; x++) // Destination image - X direction
{
// Source pixel in the X direction
const BoxPrecalc& hPrecalc = hPrecalcs[x];
// Box of pixels to average
averaged_pixels = 0;
averaged_alpha = 0;
sum_r = sum_g = sum_b = sum_a = 0.0;
for (int j = vPrecalc.boxStart; j <= vPrecalc.boxEnd; ++j)
{
for (int i = hPrecalc.boxStart; i <= hPrecalc.boxEnd; ++i)
{
// Calculate the actual index in our source pixels
src_pixel_index = j * swidth + i;
int a = src_data[src_pixel_index * 4 + 3];
if (a > 0) // do not use color from fully transparent pixels
{
sum_r += src_data[src_pixel_index * 4 + 0];
sum_g += src_data[src_pixel_index * 4 + 1];
sum_b += src_data[src_pixel_index * 4 + 2];
sum_a += a;
averaged_pixels++;
}
averaged_alpha++;
}
}
// Calculate the average from the sum and number of averaged pixels
dst_data[0] = (unsigned char)xs_CRoundToInt(sum_r / averaged_pixels);
dst_data[1] = (unsigned char)xs_CRoundToInt(sum_g / averaged_pixels);
dst_data[2] = (unsigned char)xs_CRoundToInt(sum_b / averaged_pixels);
dst_data[3] = (unsigned char)xs_CRoundToInt(sum_a / averaged_alpha);
dst_data += 4;
}
}
}
//===========================================================================
//
//
//
//===========================================================================
IHardwareTexture * FMaterial::ValidateSysTexture(FTexture * tex, bool expand)
{
if (tex && tex->UseType!=ETextureType::Null)
{
IHardwareTexture *gltex = tex->SystemTexture[expand];
if (gltex == nullptr)
{
gltex = tex->SystemTexture[expand] = screen->CreateHardwareTexture(tex);
}
return gltex;
}
return nullptr;
}
//===========================================================================
//
// Constructor
//
//===========================================================================
TArray<FMaterial *> FMaterial::mMaterials;
int FMaterial::mMaxBound;
FMaterial::FMaterial(FTexture * tx, bool expanded)
{
mShaderIndex = SHADER_Default;
sourcetex = tex = tx;
if (tx->UseType == ETextureType::SWCanvas && tx->WidthBits == 0)
{
mShaderIndex = SHADER_Paletted;
}
else if (tx->bWarped)
{
mShaderIndex = tx->bWarped; // This picks SHADER_Warp1 or SHADER_Warp2
tx->shaderspeed = static_cast<FWarpTexture*>(tx)->GetSpeed();
}
else if (tx->bHasCanvas)
{
if (tx->shaderindex >= FIRST_USER_SHADER)
{
mShaderIndex = tx->shaderindex;
}
// no brightmap for cameratexture
}
else
{
if (tx->Normal && tx->Specular)
{
for (auto &texture : { tx->Normal, tx->Specular })
{
ValidateSysTexture(texture, expanded);
mTextureLayers.Push(texture);
}
mShaderIndex = SHADER_Specular;
}
else if (tx->Normal && tx->Metallic && tx->Roughness && tx->AmbientOcclusion)
{
for (auto &texture : { tx->Normal, tx->Metallic, tx->Roughness, tx->AmbientOcclusion })
{
ValidateSysTexture(texture, expanded);
mTextureLayers.Push(texture);
}
mShaderIndex = SHADER_PBR;
}
tx->CreateDefaultBrightmap();
if (tx->Brightmap)
{
ValidateSysTexture(tx->Brightmap, expanded);
mTextureLayers.Push(tx->Brightmap);
if (mShaderIndex == SHADER_Specular)
mShaderIndex = SHADER_SpecularBrightmap;
else if (mShaderIndex == SHADER_PBR)
mShaderIndex = SHADER_PBRBrightmap;
else
mShaderIndex = SHADER_Brightmap;
}
if (tx->shaderindex >= FIRST_USER_SHADER)
{
const UserShaderDesc &usershader = usershaders[tx->shaderindex - FIRST_USER_SHADER];
if (usershader.shaderType == mShaderIndex) // Only apply user shader if it matches the expected material
{
for (auto &texture : tx->CustomShaderTextures)
{
if (texture == nullptr) continue;
ValidateSysTexture(texture, expanded);
mTextureLayers.Push(texture);
}
mShaderIndex = tx->shaderindex;
}
}
}
mBaseLayer = ValidateSysTexture(tx, expanded);
mWidth = tx->GetWidth();
mHeight = tx->GetHeight();
mLeftOffset = tx->GetLeftOffset(0); // These only get used by decals and decals should not use renderer-specific offsets.
mTopOffset = tx->GetTopOffset(0);
mRenderWidth = tx->GetScaledWidth();
mRenderHeight = tx->GetScaledHeight();
mSpriteU[0] = mSpriteV[0] = 0.f;
mSpriteU[1] = mSpriteV[1] = 1.f;
FTexture *basetex = tx->GetRedirect();
// allow the redirect only if the texture is not expanded or the scale matches.
if (!expanded || (tx->Scale.X == basetex->Scale.X && tx->Scale.Y == basetex->Scale.Y))
{
sourcetex = basetex;
mBaseLayer = ValidateSysTexture(basetex, expanded);
}
mExpanded = expanded;
if (expanded)
{
int oldwidth = mWidth;
int oldheight = mHeight;
mTrimResult = TrimBorders(trim); // get the trim size before adding the empty frame
mWidth += 2;
mHeight += 2;
mRenderWidth = mRenderWidth * mWidth / oldwidth;
mRenderHeight = mRenderHeight * mHeight / oldheight;
}
SetSpriteRect();
mTextureLayers.ShrinkToFit();
mMaxBound = -1;
mMaterials.Push(this);
tx->Material[expanded] = this;
if (tx->bHasCanvas) tx->bTranslucent = 0;
}
//===========================================================================
//
// Destructor
//
//===========================================================================
FMaterial::~FMaterial()
{
for(unsigned i=0;i<mMaterials.Size();i++)
{
if (mMaterials[i]==this)
{
mMaterials.Delete(i);
break;
}
}
}
//===========================================================================
//
// Set the sprite rectangle
//
//===========================================================================
void FMaterial::SetSpriteRect()
{
auto leftOffset = tex->GetLeftOffsetHW();
auto topOffset = tex->GetTopOffsetHW();
float fxScale = (float)tex->Scale.X;
float fyScale = (float)tex->Scale.Y;
// mSpriteRect is for positioning the sprite in the scene.
mSpriteRect.left = -leftOffset / fxScale;
mSpriteRect.top = -topOffset / fyScale;
mSpriteRect.width = mWidth / fxScale;
mSpriteRect.height = mHeight / fyScale;
if (mExpanded)
{
// a little adjustment to make sprites look better with texture filtering:
// create a 1 pixel wide empty frame around them.
int oldwidth = mWidth - 2;
int oldheight = mHeight - 2;
leftOffset += 1;
topOffset += 1;
// Reposition the sprite with the frame considered
mSpriteRect.left = -leftOffset / fxScale;
mSpriteRect.top = -topOffset / fyScale;
mSpriteRect.width = mWidth / fxScale;
mSpriteRect.height = mHeight / fyScale;
if (mTrimResult)
{
mSpriteRect.left += trim[0] / fxScale;
mSpriteRect.top += trim[1] / fyScale;
mSpriteRect.width -= (oldwidth - trim[2]) / fxScale;
mSpriteRect.height -= (oldheight - trim[3]) / fyScale;
mSpriteU[0] = trim[0] / (float)mWidth;
mSpriteV[0] = trim[1] / (float)mHeight;
mSpriteU[1] -= (oldwidth - trim[0] - trim[2]) / (float)mWidth;
mSpriteV[1] -= (oldheight - trim[1] - trim[3]) / (float)mHeight;
}
}
}
//===========================================================================
//
// Finds empty space around the texture.
// Used for sprites that got placed into a huge empty frame.
//
//===========================================================================
bool FMaterial::TrimBorders(uint16_t *rect)
{
int w;
int h;
unsigned char *buffer = sourcetex->CreateTexBuffer(0, w, h);
if (buffer == NULL)
{
return false;
}
if (w != mWidth || h != mHeight)
{
// external Hires replacements cannot be trimmed.
delete [] buffer;
return false;
}
int size = w*h;
if (size == 1)
{
// nothing to be done here.
rect[0] = 0;
rect[1] = 0;
rect[2] = 1;
rect[3] = 1;
delete[] buffer;
return true;
}
int first, last;
for(first = 0; first < size; first++)
{
if (buffer[first*4+3] != 0) break;
}
if (first >= size)
{
// completely empty
rect[0] = 0;
rect[1] = 0;
rect[2] = 1;
rect[3] = 1;
delete [] buffer;
return true;
}
for(last = size-1; last >= first; last--)
{
if (buffer[last*4+3] != 0) break;
}
rect[1] = first / w;
rect[3] = 1 + last/w - rect[1];
rect[0] = 0;
rect[2] = w;
unsigned char *bufferoff = buffer + (rect[1] * w * 4);
h = rect[3];
for(int x = 0; x < w; x++)
{
for(int y = 0; y < h; y++)
{
if (bufferoff[(x+y*w)*4+3] != 0) goto outl;
}
rect[0]++;
}
outl:
rect[2] -= rect[0];
for(int x = w-1; rect[2] > 1; x--)
{
for(int y = 0; y < h; y++)
{
if (bufferoff[(x+y*w)*4+3] != 0)
{
delete [] buffer;
return true;
}
}
rect[2]--;
}
delete [] buffer;
return true;
}
//===========================================================================
//
//
//
//===========================================================================
void FMaterial::Precache()
{
screen->PrecacheMaterial(this, 0);
}
//===========================================================================
//
//
//
//===========================================================================
void FMaterial::PrecacheList(SpriteHits &translations)
{
if (mBaseLayer != nullptr) mBaseLayer->CleanUnused(translations);
SpriteHits::Iterator it(translations);
SpriteHits::Pair *pair;
while(it.NextPair(pair)) screen->PrecacheMaterial(this, pair->Key);
}
//===========================================================================
//
//
//
//===========================================================================
int FMaterial::GetAreas(FloatRect **pAreas) const
{
if (mShaderIndex == SHADER_Default) // texture splitting can only be done if there's no attached effects
{
*pAreas = sourcetex->areas;
return sourcetex->areacount;
}
else
{
return 0;
}
}
//==========================================================================
//
// Gets a texture from the texture manager and checks its validity for
// GL rendering.
//
//==========================================================================
FMaterial * FMaterial::ValidateTexture(FTexture * tex, bool expand)
{
again:
if (tex && tex->UseType!=ETextureType::Null)
{
if (tex->bNoExpand) expand = false;
FMaterial *gltex = tex->Material[expand];
if (gltex == NULL)
{
if (expand)
{
if (tex->bWarped || tex->bHasCanvas || tex->shaderindex >= FIRST_USER_SHADER || (tex->shaderindex >= SHADER_Specular && tex->shaderindex <= SHADER_PBRBrightmap))
{
tex->bNoExpand = true;
goto again;
}
if (tex->Brightmap != NULL &&
(tex->GetWidth() != tex->Brightmap->GetWidth() ||
tex->GetHeight() != tex->Brightmap->GetHeight())
)
{
// do not expand if the brightmap's size differs.
tex->bNoExpand = true;
goto again;
}
}
gltex = new FMaterial(tex, expand);
}
return gltex;
}
return NULL;
}
FMaterial * FMaterial::ValidateTexture(FTextureID no, bool expand, bool translate)
{
return ValidateTexture(translate? TexMan(no) : TexMan[no], expand);
}
//==========================================================================
//
// Flushes all hardware dependent data
//
//==========================================================================
void FMaterial::FlushAll()
{
for(int i=mMaterials.Size()-1;i>=0;i--)
{
mMaterials[i]->mBaseLayer->Clean(true);
}
// This is for shader layers. All shader layers must be managed by the texture manager
// so this will catch everything.
for(int i=TexMan.NumTextures()-1;i>=0;i--)
{
for (int j = 0; j < 2; j++)
{
auto gltex = TexMan.ByIndex(i)->SystemTexture[j];
if (gltex != nullptr) gltex->Clean(true);
}
}
}
void FMaterial::Clean(bool f)
{
// This somehow needs to deal with the other layers as well, but they probably need some form of reference counting to work properly...
mBaseLayer->Clean(f);
}
//==========================================================================
//
// Prints some texture info
//
//==========================================================================
CCMD(textureinfo)
{
int cntt = 0;
for (int i = 0; i < TexMan.NumTextures(); i++)
{
FTexture *tex = TexMan.ByIndex(i);
if (tex->SystemTexture[0] || tex->SystemTexture[1] || tex->Material[0] || tex->Material[1])
{
int lump = tex->GetSourceLump();
Printf(PRINT_LOG, "Texture '%s' (Index %d, Lump %d, Name '%s'):\n", tex->Name.GetChars(), i, lump, Wads.GetLumpFullName(lump));
if (tex->Material[0])
{
Printf(PRINT_LOG, "in use (normal)\n");
}
else if (tex->SystemTexture[0])
{
Printf(PRINT_LOG, "referenced (normal)\n");
}
if (tex->Material[1])
{
Printf(PRINT_LOG, "in use (expanded)\n");
}
else if (tex->SystemTexture[1])
{
Printf(PRINT_LOG, "referenced (normal)\n");
}
cntt++;
}
}
Printf(PRINT_LOG, "%d system textures\n", cntt);
}