// //--------------------------------------------------------------------------- // // 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 "filesystem.h" #include "m_png.h" #include "sbar.h" #include "stats.h" #include "r_utility.h" #include "c_dispatch.h" #include "v_video.h" #include "hw_ihwtexture.h" #include "hw_material.h" #include "texturemanager.h" //=========================================================================== // // 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& 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(src_p - scale_factor_1 / 2.0 + 1), 0, oldDim - 1); precalc.boxEnd = clamp(MAX(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 vPrecalcs(height, true); TArray 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; } } } //=========================================================================== // // Constructor // //=========================================================================== FMaterial::FMaterial(FTexture * tx, bool expanded) { mShaderIndex = SHADER_Default; sourcetex = tex = tx; if (tx->UseType == ETextureType::SWCanvas && static_cast(tx)->GetColorFormat() == 0) { mShaderIndex = SHADER_Paletted; } else if (tx->isWarped()) { mShaderIndex = tx->isWarped(); // This picks SHADER_Warp1 or SHADER_Warp2 } else if (tx->isHardwareCanvas()) { 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 }) { 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 }) { mTextureLayers.Push(texture); } mShaderIndex = SHADER_PBR; } tx->CreateDefaultBrightmap(); if (tx->Brightmap) { 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; mTextureLayers.Push(texture); } mShaderIndex = tx->shaderindex; } } } mWidth = tx->GetTexelWidth(); mHeight = tx->GetTexelHeight(); 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; 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(); tx->Material[expanded] = this; if (tx->isHardwareCanvas()) tx->bTranslucent = 0; } //=========================================================================== // // Destructor // //=========================================================================== FMaterial::~FMaterial() { } //=========================================================================== // // 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) { auto texbuffer = sourcetex->CreateTexBuffer(0); int w = texbuffer.mWidth; int h = texbuffer.mHeight; auto Buffer = texbuffer.mBuffer; if (texbuffer.mBuffer == nullptr) { return false; } if (w != mWidth || h != mHeight) { // external Hires replacements cannot be trimmed. 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; 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; 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) { return true; } } rect[2]--; } return true; } //=========================================================================== // // // //=========================================================================== IHardwareTexture *FMaterial::GetLayer(int i, int translation, FTexture **pLayer) { FTexture *layer = i == 0 ? tex : mTextureLayers[i - 1]; if (pLayer) *pLayer = layer; if (layer && layer->UseType!=ETextureType::Null) { IHardwareTexture *hwtex = layer->SystemTextures.GetHardwareTexture(translation, mExpanded); if (hwtex == nullptr) { hwtex = screen->CreateHardwareTexture(); layer->SystemTextures.AddHardwareTexture(translation, mExpanded, hwtex); } return hwtex; } return nullptr; } //=========================================================================== // // // //=========================================================================== void FMaterial::Precache() { screen->PrecacheMaterial(this, 0); } //=========================================================================== // // // //=========================================================================== void FMaterial::PrecacheList(SpriteHits &translations) { tex->SystemTextures.CleanUnused(translations, mExpanded); 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, bool create) { again: if (tex && tex->isValid()) { if (tex->bNoExpand) expand = false; FMaterial *hwtex = tex->Material[expand]; if (hwtex == NULL && create) { if (expand) { if (tex->isWarped() || tex->isHardwareCanvas() || tex->shaderindex >= FIRST_USER_SHADER || (tex->shaderindex >= SHADER_Specular && tex->shaderindex <= SHADER_PBRBrightmap)) { tex->bNoExpand = true; goto again; } if (tex->Brightmap != NULL && (tex->GetTexelWidth() != tex->Brightmap->GetTexelWidth() || tex->GetTexelHeight() != tex->Brightmap->GetTexelHeight()) ) { // do not expand if the brightmap's size differs. tex->bNoExpand = true; goto again; } } hwtex = new FMaterial(tex, expand); } return hwtex; } return NULL; } FMaterial * FMaterial::ValidateTexture(FTextureID no, bool expand, bool translate, bool create) { return ValidateTexture(TexMan.GetTexture(no, translate), expand, create); } //----------------------------------------------------------------------------- // // Make sprite offset adjustment user-configurable per renderer. // //----------------------------------------------------------------------------- extern int r_spriteadjustSW, r_spriteadjustHW; CUSTOM_CVAR(Int, r_spriteadjust, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) { r_spriteadjustHW = !!(self & 2); r_spriteadjustSW = !!(self & 1); for (int i = 0; i < TexMan.NumTextures(); i++) { auto tex = TexMan.GetTexture(FSetTextureID(i)); if (tex->GetTexelLeftOffset(0) != tex->GetTexelLeftOffset(1) || tex->GetTexelTopOffset(0) != tex->GetTexelTopOffset(1)) { for (int i = 0; i < 2; i++) { auto mat = tex->GetMaterial(i); if (mat != nullptr) mat->SetSpriteRect(); } } } } //========================================================================== // // this must be copied back to textures.cpp later. // //========================================================================== FWrapperTexture::FWrapperTexture(int w, int h, int bits) { Width = w; Height = h; Format = bits; UseType = ETextureType::SWCanvas; bNoCompress = true; auto hwtex = screen->CreateHardwareTexture(); // todo: Initialize here. SystemTextures.AddHardwareTexture(0, false, hwtex); } void DeleteMaterial(FMaterial* mat) { delete mat; }