/* ** gl_texture.cpp ** high level GL texture interface ** **--------------------------------------------------------------------------- ** Copyright 2019 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** ** */ #include "palette.h" #include "build.h" #include "hightile.h" #include "polymost.h" #include "textures.h" #include "bitmap.h" #include "v_font.h" #include "../../glbackend/glbackend.h" // Test CVARs. CVAR(Int, fixpalette, -1, 0) CVAR(Int, fixpalswap, -1, 0) template void FlipNonSquareBlock(T* dst, const T* src, int x, int y, int srcpitch) { for (int i = 0; i < x; ++i) { for (int j = 0; j < y; ++j) { dst[i * y + j] = src[i + j * srcpitch]; } } } //=========================================================================== // // Create an indexed version of the requested texture // //=========================================================================== FHardwareTexture* GLInstance::CreateIndexedTexture(FTexture* tex) { auto siz = tex->GetSize(); const uint8_t* p = tex->Get8BitPixels(); TArray store(siz.x * siz.y, true); if (!p) { tex->Create8BitPixels(store.Data()); p = store.Data(); } auto glpic = GLInterface.NewTexture(); glpic->CreateTexture(siz.x, siz.y, FHardwareTexture::Indexed, false); TArray flipped(siz.x * siz.y, true); FlipNonSquareBlock(flipped.Data(), p, siz.y, siz.x, siz.y); glpic->LoadTexture(flipped.Data()); return glpic; } //=========================================================================== // // Create a true color version of the requested tile // //=========================================================================== FHardwareTexture* GLInstance::CreateTrueColorTexture(FTexture* tex, int palid, bool checkfulltransparency, bool rgb8bit) { auto palette = palid < 0? nullptr : palmanager.GetPaletteData(palid); if (palid >= 0 && palette == nullptr) return nullptr; auto texbuffer = tex->CreateTexBuffer(palette, CTF_ProcessData); // Check if the texture is fully transparent. When creating a brightmap such textures can be discarded. if (checkfulltransparency) { int siz = texbuffer.mWidth * texbuffer.mHeight * 4; bool found = false; for (int i = 3; i < siz; i+=4) { if (texbuffer.mBuffer[i] > 0) { found = true; break; } } if (!found) return nullptr; } auto glpic = GLInterface.NewTexture(); if (!rgb8bit) glpic->CreateTexture(texbuffer.mWidth, texbuffer.mHeight, FHardwareTexture::TrueColor, true); else glpic->CreateTexture(texbuffer.mWidth, texbuffer.mHeight, FHardwareTexture::Brightmap, false); // Use a more memory friendly format for simple brightmaps. glpic->LoadTexture(texbuffer.mBuffer); return glpic; } //=========================================================================== // // Retrieve the texture to be used. // //=========================================================================== FHardwareTexture* GLInstance::LoadTexture(FTexture* tex, int textype, int palid) { if (textype == TT_INDEXED) palid = -1; auto phwtex = tex->GetHardwareTexture(palid); if (phwtex) return *phwtex; FHardwareTexture *hwtex; if (textype == TT_INDEXED) hwtex = CreateIndexedTexture(tex); else hwtex = CreateTrueColorTexture(tex, textype == TT_HICREPLACE? -1 : palid, textype == TT_BRIGHTMAP, textype == TT_BRIGHTMAP); if (hwtex) tex->SetHardwareTexture(palid, hwtex); return hwtex; } //=========================================================================== // // Sets a texture for rendering. This should be the ONLY place to bind in-game textures // //=========================================================================== bool GLInstance::SetTextureInternal(int picnum, FTexture* tex, int palette, int method, int sampleroverride, FTexture *det, float detscale, FTexture *glow) { if (tex->GetWidth() <= 0 || tex->GetHeight() <= 0) return false; int usepalette = fixpalette >= 0 ? fixpalette : curbasepal; int usepalswap = fixpalswap >= 0 ? fixpalswap : palette; GLInterface.SetPalette(usepalette); GLInterface.SetPalswap(usepalswap); bool texbound[3] = {}; int MatrixChange = 0; TextureType = hw_int_useindexedcolortextures? TT_INDEXED : TT_TRUECOLOR; int lookuppal = 0; VSMatrix texmat; auto& h = hictinting[palette]; bool applytint = false; auto rep = (hw_hightile && !(h.f & HICTINT_ALWAYSUSEART)) ? tex->FindReplacement(palette) : nullptr; if (rep) { // Hightile replacements have only one texture representation and it is always the base. tex = rep->faces[0]; TextureType = TT_HICREPLACE; if (rep->palnum != palette || (h.f & HICTINT_APPLYOVERALTPAL)) applytint = true; } else { // Only look up the palette if we really want to use it (i.e. when creating a true color texture of an ART tile.) if (TextureType == TT_TRUECOLOR) { // Tinting is not used on indexed textures if (h.f & (HICTINT_ALWAYSUSEART | HICTINT_USEONART)) { applytint = true; if (!(h.f & HICTINT_APPLYOVERPALSWAP)) usepalswap = 0; } lookuppal = palmanager.LookupPalette(usepalette, usepalswap, false,fixpalette < 0? !!(curpaletteflags & Pal_Fullscreen) : 0); } } // This is intentionally the same value for both parameters. The shader does not use the same uniform for modulation and overlay colors. if (applytint && h.f) GLInterface.SetTinting(h.f, h.tint, h.tint); else GLInterface.SetTinting(-1, 0xffffff, 0xffffff); // Load the main texture auto mtex = LoadTexture(tex, TextureType, lookuppal); if (mtex) { auto sampler = (method & DAMETH_CLAMPED) ? (sampleroverride != -1 ? sampleroverride : SamplerClampXY) : SamplerRepeat; if (TextureType == TT_INDEXED) { sampler = sampler + SamplerNoFilterRepeat - SamplerRepeat; } UseDetailMapping(false); UseGlowMapping(false); UseBrightmaps(false); BindTexture(0, mtex, sampler); // Needs a) testing and b) verification for correctness. This doesn't look like it makes sense. if (rep && (rep->scale.x != 1.0f || rep->scale.y != 1.0f)) { //texmat.loadIdentity(); //texmat.scale(rep->scale.x, rep->scale.y, 1.0f); //GLInterface.SetMatrix(Matrix_Texture, &texmat); } // Also load additional layers needed for this texture. if (hw_detailmapping && hw_hightile) { float detscalex = detscale, detscaley = detscale; if (!(method & DAMETH_MODEL)) { auto drep = tex->FindReplacement(DETAILPAL); if (drep) { det = drep->faces[0]; detscalex = drep->scale.x; detscaley = drep->scale.y; } } if (det) { auto htex = LoadTexture(det, TT_HICREPLACE, 0); UseDetailMapping(true); BindTexture(3, htex, SamplerRepeat); texbound[0] = true; /* todo: instead of a matrix, just pass a two-component uniform. Using a full matrix here is problematic. if (MatrixChange & 1) MatrixChange |= 2; else texmat.loadIdentity(); if ((detscalex != 1.0f) || (detscaley != 1.0f)) { texmat.scale(detscalex, detscaley, 1.0f); MatrixChange |= 2; } if (MatrixChange & 2) GLInterface.SetMatrix(Matrix_Detail, &texmat); */ } } if (hw_glowmapping && hw_hightile) { if (!(method & DAMETH_MODEL)) { auto drep = tex->FindReplacement(GLOWPAL); if (drep) { glow = drep->faces[0]; } } if (glow) { auto htex = LoadTexture(glow, TT_HICREPLACE, 0); UseGlowMapping(true); BindTexture(4, htex, SamplerRepeat); texbound[1] = true; } } #if 1 if (!(tex->PicAnim.sf & PICANM_NOFULLBRIGHT_BIT) && !(globalflags & GLOBAL_NO_GL_FULLBRIGHT) && !tex->NoBrightmapFlag[usepalswap]) { if (TextureType == TT_HICREPLACE) { auto brep = tex->FindReplacement(BRIGHTPAL); if (brep) { LoadTexture(brep->faces[0], TT_HICREPLACE, 0); UseBrightmaps(true); BindTexture(5, mtex, sampler); texbound[2] = true; } else { tex->PicAnim.sf |= PICANM_NOFULLBRIGHT_BIT; } } else if (TextureType == TT_TRUECOLOR) { lookuppal = palmanager.LookupPalette(usepalette, usepalswap, true); if (lookuppal >= 0) { auto htex = LoadTexture(tex, TT_BRIGHTMAP, lookuppal); if (htex == nullptr) { // Flag the texture as not being brightmapped for the given palette tex->NoBrightmapFlag.Set(usepalswap); } else { UseBrightmaps(true); BindTexture(5, htex, sampler); texbound[2] = true; } } } } if (!texbound[0]) UnbindTexture(3); if (!texbound[1]) UnbindTexture(4); if (!texbound[2]) UnbindTexture(5); #endif } else return false; float al = 0.5f; if (TextureType == TT_HICREPLACE) { al = ((unsigned)picnum < MAXTILES && alphahackarray[picnum] != 0) ? alphahackarray[picnum] * (1.f / 255.f) : (tex->alphaThreshold >= 0 ? tex->alphaThreshold * (1.f / 255.f) : 0.f); } GLInterface.SetAlphaThreshold(al); return true; } //=========================================================================== // // Sets a named texture for 2D rendering. In this case the palette is // a direct index into the palette map. // //=========================================================================== bool GLInstance::SetNamedTexture(FTexture* tex, int palette, int sampler) { auto mtex = LoadTexture(tex, palette>= 0? TT_TRUECOLOR : TT_HICREPLACE, palette); if (!mtex) return false; BindTexture(0, mtex, sampler); GLInterface.SetAlphaThreshold(tex->isTranslucent()? 0.f : 0.5f); return true; }