diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 33c070cc1..3c4bd4a65 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -778,6 +778,7 @@ set (PCH_SOURCES glbackend/gl_samplers.cpp glbackend/gl_shader.cpp glbackend/glbackend.cpp + glbackend/gl_palmanager.cpp mact/src/animlib.cpp mact/src/control.cpp diff --git a/source/build/src/polymost.cpp b/source/build/src/polymost.cpp index 08a68d16c..3f5b97243 100644 --- a/source/build/src/polymost.cpp +++ b/source/build/src/polymost.cpp @@ -136,8 +136,6 @@ static GLuint quadVertsID = 0; #define PALSWAP_TEXTURE_SIZE 2048 int32_t r_useindexedcolortextures = -1; -static int32_t lastbasepal = -1; -static FHardwareTexture *paletteTextureIDs[MAXBASEPALS]; static FHardwareTexture *palswapTextureID = nullptr; static inline float float_trans(uint32_t maskprops, uint8_t blend) @@ -373,10 +371,8 @@ void polymost_glinit() { globalflags |= GLOBAL_NO_GL_TILESHADES; // This re-enables the old fading logic without re-adding the r_usetileshades variable. The entire thing will have to be done on a more abstract level anyway. - lastbasepal = -1; - for (int basepalnum = 0; basepalnum < MAXBASEPALS; ++basepalnum) + for (int basepalnum = 0; basepalnum < MAXBASEPALS; ++basepalnum) { - paletteTextureIDs[basepalnum] = 0; uploadbasepalette(basepalnum); } palswapTextureID = 0; @@ -705,14 +701,7 @@ void uploadbasepalette(int32_t basepalnum, bool transient) // transient palettes basepalWFullBrightInfo[i*4+3] = 0-(IsPaletteIndexFullbright(i) != 0); } - if (!paletteTextureIDs[basepalnum]) - { - auto &p = paletteTextureIDs[basepalnum]; - p = GLInterface.NewTexture(); - p->CreateTexture(256, 1, false, false); - p->SetSampler(Sampler2DNoFilter); - } - paletteTextureIDs[basepalnum]->LoadTexture(basepalWFullBrightInfo); // RGBA + GLInterface.SetPaletteData(basepalnum, basepalWFullBrightInfo, transient); } // Used by RRRA fog hackery - the only place changing the palswaps at run time. @@ -1162,13 +1151,7 @@ static void polymost_updatePalette() GLInterface.SetPalswap(globalpal); GLInterface.SetShade(globalshade, numshades); - - //POGO: only bind the base pal once when it's swapped - if (curbasepal != lastbasepal) - { - GLInterface.BindTexture(2, paletteTextureIDs[curbasepal], Sampler2DNoFilter); - lastbasepal = curbasepal; - } + GLInterface.SetPalette(curbasepal); } diff --git a/source/glbackend/gl_palmanager.cpp b/source/glbackend/gl_palmanager.cpp new file mode 100644 index 000000000..c195d5289 --- /dev/null +++ b/source/glbackend/gl_palmanager.cpp @@ -0,0 +1,211 @@ +/* +** gl_palmanager.cpp +** Palette management +** +**--------------------------------------------------------------------------- +** 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 +#include "m_crc32.h" +#include "glad/glad.h" +#include "glbackend.h" +#include "gl_samplers.h" +#include "gl_shader.h" + +#include "baselayer.h" +#include "resourcefile.h" + + +//=========================================================================== +// +// The palette manager will contain all palettes being used for texture +// creation. It is also responsible for creating palette textures for indexed +// rendering. +// +//=========================================================================== + +PaletteManager::~PaletteManager() +{ + DeleteAll(); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void PaletteManager::DeleteAll() +{ + for (auto& pal : palettes) + { + if (pal.paltexture) delete pal.paltexture; + } + if (transientpalette.paltexture) delete transientpalette.paltexture; + transientpalette.paltexture = nullptr; + transientpalette.crc32 = -1; + palettes.Reset(); + palswaps.Reset(); + lastindex = -1; + memset(palettemap, 0, sizeof(palettemap)); + memset(palswapmap, 0, sizeof(palswapmap)); +} + +//=========================================================================== +// +// Adds a new palette while looking for duplicates. +// +//=========================================================================== + +unsigned PaletteManager::FindPalette(const uint8_t *paldata) +{ + auto crc32 = CalcCRC32(paldata, 1024); + for (unsigned int i = 0; i< palettes.Size(); i++) + { + if (crc32 == palettes[i].crc32) + { + if (!memcmp(paldata, palettes[i].colors, 1024)) + { + return i; + } + } + } + PaletteData pd; + memcpy(pd.colors, paldata, 1024); + pd.crc32 = crc32; + pd.paltexture = nullptr; + return palettes.Push(pd); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void PaletteManager::SetPalette(int index, const uint8_t* data, bool transient) +{ + // New palettes may only be added if declared transient or on startup. + // Otherwise this would require a renderer reset to flush out the textures affected by the change. + + if (index < 0 || index > 255) return; // invalid index - ignore. + if (transient) + { + // Transient palettes do not get stored in the list because it is assumed that they won't be needed for long. + // Only clear the texture if the palette is different. + if (memcmp(data, transientpalette.colors, 1024)) + { + memcpy(transientpalette.colors, data, 1024); + if (transientpalette.paltexture) delete transientpalette.paltexture; + transientpalette.paltexture = nullptr; + } + transientpalette.crc32 = index; + palettemap[index] = 0; + return; + } + palettemap[index] = FindPalette(data); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void PaletteManager::BindPalette(int index) +{ + if (index == transientpalette.crc32) + { + if (transientpalette.paltexture == nullptr) + { + auto p = GLInterface.NewTexture(); + p->CreateTexture(256, 1, false, false); + p->LoadTexture((uint8_t*)transientpalette.colors); + p->SetSampler(Sampler2DNoFilter); + transientpalette.paltexture = p; + } + inst->BindTexture(2, transientpalette.paltexture); + } + else if (palettemap[index] < palettes.Size()) + { + auto uindex = palettemap[index]; + if (uindex != lastindex) + { + lastindex = uindex; + if (palettes[uindex].paltexture == nullptr) + { + auto p = GLInterface.NewTexture(); + p->CreateTexture(256, 1, false, false); + p->LoadTexture((uint8_t*)palettes[uindex].colors); + p->SetSampler(Sampler2DNoFilter); + palettes[uindex].paltexture = p; + } + inst->BindTexture(2, palettes[uindex].paltexture); + } + } + +} + + + +#if 0 + +static void polymost_setPalswap(uint32_t index) +{ + static uint32_t lastPalswapIndex; + + if (currentShaderProgramID != polymost1CurrentShaderProgramID) + return; + + lastPalswapIndex = index; + polymost1PalswapPos.x = index * polymost1PalswapSize.x; + polymost1PalswapPos.y = floorf(polymost1PalswapPos.x); + polymost1PalswapPos = { polymost1PalswapPos.x - polymost1PalswapPos.y + (0.5f / PALSWAP_TEXTURE_SIZE), + polymost1PalswapPos.y * polymost1PalswapSize.y + (0.5f / PALSWAP_TEXTURE_SIZE) }; + glUniform2f(polymost1PalswapPosLoc, polymost1PalswapPos.x, polymost1PalswapPos.y); +} + +static void polymost_setPalswapSize(uint32_t width, uint32_t height) +{ + if (currentShaderProgramID != polymost1CurrentShaderProgramID) + return; + + polymost1PalswapSize = { width * (1.f / PALSWAP_TEXTURE_SIZE), + height * (1.f / PALSWAP_TEXTURE_SIZE) }; + + polymost1PalswapInnerSize = { (width - 1) * (1.f / PALSWAP_TEXTURE_SIZE), + (height - 1) * (1.f / PALSWAP_TEXTURE_SIZE) }; + + glUniform2f(polymost1PalswapSizeLoc, polymost1PalswapInnerSize.x, polymost1PalswapInnerSize.y); +} + + +#endif \ No newline at end of file diff --git a/source/glbackend/glbackend.cpp b/source/glbackend/glbackend.cpp index 208678e91..b7c94480c 100644 --- a/source/glbackend/glbackend.cpp +++ b/source/glbackend/glbackend.cpp @@ -41,6 +41,12 @@ FileReader GetBaseResource(const char* fn) GLInstance GLInterface; +GLInstance::GLInstance() + :palmanager(this) +{ + +} + void GLInstance::Init() { InitBaseRes(); @@ -146,8 +152,12 @@ void GLInstance::Deinit() mSamplers = nullptr; if (polymostShader) delete polymostShader; polymostShader = nullptr; - + if (surfaceShader) delete surfaceShader; + surfaceShader = nullptr; + if (vpxShader) delete vpxShader; + vpxShader = nullptr; activeShader = nullptr; + palmanager.DeleteAll(); } std::pair GLInstance::AllocVertices(size_t num) @@ -402,9 +412,6 @@ void GLInstance::SetPolymostShader() polymostShader->Bind(); activeShader = polymostShader; } - //GLInterface.BindTexture(1, palswapTextureID); - //GLInterface.BindTexture(2, paletteTextureIDs[curbasepal]); - } void GLInstance::SetSurfaceShader() @@ -425,6 +432,10 @@ void GLInstance::SetVPXShader() } } +void GLInstance::SetPalette(int index) +{ + palmanager.BindPalette(index); +} void PolymostRenderState::Apply(PolymostShader* shader) { @@ -446,36 +457,3 @@ void PolymostRenderState::Apply(PolymostShader* shader) shader->FogColor.Set(FogColor); } -#if 0 - -static void polymost_setPalswap(uint32_t index) -{ - static uint32_t lastPalswapIndex; - - if (currentShaderProgramID != polymost1CurrentShaderProgramID) - return; - - lastPalswapIndex = index; - polymost1PalswapPos.x = index * polymost1PalswapSize.x; - polymost1PalswapPos.y = floorf(polymost1PalswapPos.x); - polymost1PalswapPos = { polymost1PalswapPos.x - polymost1PalswapPos.y + (0.5f / PALSWAP_TEXTURE_SIZE), - polymost1PalswapPos.y * polymost1PalswapSize.y + (0.5f / PALSWAP_TEXTURE_SIZE) }; - glUniform2f(polymost1PalswapPosLoc, polymost1PalswapPos.x, polymost1PalswapPos.y); -} - -static void polymost_setPalswapSize(uint32_t width, uint32_t height) -{ - if (currentShaderProgramID != polymost1CurrentShaderProgramID) - return; - - polymost1PalswapSize = { width * (1.f / PALSWAP_TEXTURE_SIZE), - height * (1.f / PALSWAP_TEXTURE_SIZE) }; - - polymost1PalswapInnerSize = { (width - 1) * (1.f / PALSWAP_TEXTURE_SIZE), - (height - 1) * (1.f / PALSWAP_TEXTURE_SIZE) }; - - glUniform2f(polymost1PalswapSizeLoc, polymost1PalswapInnerSize.x, polymost1PalswapInnerSize.y); -} - - -#endif \ No newline at end of file diff --git a/source/glbackend/glbackend.h b/source/glbackend/glbackend.h index 6a259687a..e8631c27f 100644 --- a/source/glbackend/glbackend.h +++ b/source/glbackend/glbackend.h @@ -14,6 +14,46 @@ class FShader; class PolymostShader; class SurfaceShader; class FTexture; +class GLInstance; + +struct PaletteData +{ + int32_t crc32; + PalEntry colors[256]; + FHardwareTexture* paltexture; +}; + +class PaletteManager +{ + // The current engine limit is 256 palettes and 256 palswaps. + uint32_t palettemap[256] = {}; + uint32_t palswapmap[256] = {}; + uint32_t lastindex = ~0u; + + // Keep the short lived movie palettes out of the palette list for ease of maintenance. + // Since it is transient this doesn't need to be preserved if it changes, unlike the other palettes which need to be preserved as references for the texture management. + PaletteData transientpalette = { -1 }; + + // All data is being stored in contiguous blocks that can be used as uniform buffers as-is. + TArray palettes; + TArray palswaps; + GLInstance* const inst; + + //OpenGLRenderer::GLDataBuffer* palswapBuffer = nullptr; + + unsigned FindPalette(const uint8_t* paldata); + +public: + PaletteManager(GLInstance *inst_) : inst(inst_) + {} + ~PaletteManager(); + void DeleteAll(); + void SetPalette(int index, const uint8_t *data, bool transient); + void AddPalswap(const uint8_t* data); + + void BindPalette(int index); +}; + struct glinfo_t { const char* vendor; @@ -128,6 +168,7 @@ class GLInstance unsigned TextureHandleCache[THCACHESIZE]; int currentindex = THCACHESIZE; int maxTextureSize; + PaletteManager palmanager; VSMatrix matrices[NUMMATRICES]; PolymostRenderState renderState; @@ -155,6 +196,7 @@ public: return value; } + GLInstance(); std::pair AllocVertices(size_t num); void Draw(EDrawType type, size_t start, size_t count); @@ -203,9 +245,15 @@ public: void SetPolymostShader(); void SetSurfaceShader(); void SetVPXShader(); + void SetPalette(int palette); void ReadPixels(int w, int h, uint8_t* buffer); + void SetPaletteData(int index, const uint8_t* data, bool transient) + { + palmanager.SetPalette(index, data, transient); + } + void SetPalswap(uint32_t index) {