diff --git a/src/openrct2-ui/drawing/engines/opengl/TextureCache.cpp b/src/openrct2-ui/drawing/engines/opengl/TextureCache.cpp index feba41b77f..53dfab65f0 100644 --- a/src/openrct2-ui/drawing/engines/opengl/TextureCache.cpp +++ b/src/openrct2-ui/drawing/engines/opengl/TextureCache.cpp @@ -21,6 +21,7 @@ #include using namespace OpenRCT2::Ui; +using namespace OpenRCT2::Drawing; constexpr uint32_t kUnusedIndex = 0xFFFFFFFF; @@ -206,7 +207,7 @@ void TextureCache::CreateTextures() glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D( - GL_TEXTURE_2D, 0, GL_R8UI, PALETTE_SIZE, PALETTE_SIZE, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, blendArray); + GL_TEXTURE_2D, 0, GL_R8UI, kGamePaletteSize, kGamePaletteSize, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, blendArray); } _initialized = true; diff --git a/src/openrct2/CommandLineSprite.cpp b/src/openrct2/CommandLineSprite.cpp index 233e21b883..d65583e0ef 100644 --- a/src/openrct2/CommandLineSprite.cpp +++ b/src/openrct2/CommandLineSprite.cpp @@ -214,7 +214,7 @@ static bool SpriteImageExport(const G1Element& spriteElement, u8string_view outP image.Height = dpi.height; image.Depth = 8; image.Stride = dpi.LineStride(); - image.Palette = std::make_unique(StandardPalette); + image.Palette = StandardPalette; image.Pixels = std::vector(pixels8, pixels8 + pixelsLen); Imaging::WriteToFile(outPath, image, IMAGE_FORMAT::PNG); return true; diff --git a/src/openrct2/core/Imaging.cpp b/src/openrct2/core/Imaging.cpp index 4b2b5f0df6..363a6baac3 100644 --- a/src/openrct2/core/Imaging.cpp +++ b/src/openrct2/core/Imaging.cpp @@ -189,7 +189,7 @@ namespace OpenRCT2::Imaging if (image.Depth == 8) { - if (image.Palette == nullptr) + if (!image.Palette.has_value()) { throw std::runtime_error("Expected a palette for 8-bit image."); } diff --git a/src/openrct2/core/Imaging.h b/src/openrct2/core/Imaging.h index 854c7698c9..6515bcda01 100644 --- a/src/openrct2/core/Imaging.h +++ b/src/openrct2/core/Imaging.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -37,7 +38,7 @@ struct Image // Data std::vector Pixels; - std::unique_ptr Palette; + std::optional Palette; uint32_t Stride{}; }; diff --git a/src/openrct2/drawing/ColourPalette.h b/src/openrct2/drawing/ColourPalette.h new file mode 100644 index 0000000000..ee15a1b63c --- /dev/null +++ b/src/openrct2/drawing/ColourPalette.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace OpenRCT2::Drawing +{ + + struct PaletteBGRA + { + uint8_t Blue{}; + uint8_t Green{}; + uint8_t Red{}; + uint8_t Alpha{}; + }; + + constexpr auto kGamePaletteSize = 256u; + + using GamePalette = std::array; + +} // namespace OpenRCT2::Drawing diff --git a/src/openrct2/drawing/Drawing.cpp b/src/openrct2/drawing/Drawing.cpp index 37b9f4d918..a98290bebf 100644 --- a/src/openrct2/drawing/Drawing.cpp +++ b/src/openrct2/drawing/Drawing.cpp @@ -26,78 +26,55 @@ #include "../world/Location.hpp" #include "LightFX.h" +#include #include #include +#include using namespace OpenRCT2; -const PaletteMap& PaletteMap::GetDefault() -{ - static bool initialised = false; - static uint8_t data[256]; - static PaletteMap defaultMap(data); - if (!initialised) - { - for (size_t i = 0; i < sizeof(data); i++) - { - data[i] = static_cast(i); - } - initialised = true; - } - return defaultMap; -} +static auto _defaultPaletteMapping = []() { + std::array res; + std::iota(res.begin(), res.end(), 0); + return res; +}(); -bool PaletteMap::operator==(const PaletteMap& lhs) const +PaletteMap PaletteMap::GetDefault() { - return _data == lhs._data && _dataLength == lhs._dataLength && _numMaps == lhs._numMaps && _mapLength == lhs._mapLength; + return PaletteMap(_defaultPaletteMapping); } uint8_t& PaletteMap::operator[](size_t index) { - assert(index < _dataLength); - - // Provide safety in release builds - if (index >= _dataLength) - { - static uint8_t dummy; - return dummy; - } - return _data[index]; } uint8_t PaletteMap::operator[](size_t index) const { - assert(index < _dataLength); - - // Provide safety in release builds - if (index >= _dataLength) - { - return 0; - } - return _data[index]; } uint8_t PaletteMap::Blend(uint8_t src, uint8_t dst) const { +#ifdef _DEBUG // src = 0 would be transparent so there is no blend palette for that, hence (src - 1) assert(src != 0 && (src - 1) < _numMaps); assert(dst < _mapLength); +#endif auto idx = ((src - 1) * 256) + dst; - return (*this)[idx]; + return _data[idx]; } void PaletteMap::Copy(size_t dstIndex, const PaletteMap& src, size_t srcIndex, size_t length) { - auto maxLength = std::min(_mapLength - srcIndex, _mapLength - dstIndex); + auto maxLength = std::min(_data.size() - srcIndex, _data.size() - dstIndex); assert(length <= maxLength); auto copyLength = std::min(length, maxLength); - std::memcpy(&_data[dstIndex], &src._data[srcIndex], copyLength); + std::copy(src._data.begin() + srcIndex, src._data.begin() + srcIndex + copyLength, _data.begin() + dstIndex); } -GamePalette gPalette; -uint8_t gGamePalette[256 * 4]; +OpenRCT2::Drawing::GamePalette gPalette; +OpenRCT2::Drawing::GamePalette gGamePalette; uint32_t gPaletteEffectFrame; ImageId gPickupPeepImage; @@ -718,16 +695,17 @@ void GfxTransposePalette(int32_t pal, uint8_t product) { int32_t width = g1->width; int32_t x = g1->x_offset; - uint8_t* dest_pointer = &gGamePalette[x * 4]; uint8_t* source_pointer = g1->offset; for (; width > 0; width--) { - dest_pointer[0] = (source_pointer[0] * product) >> 8; - dest_pointer[1] = (source_pointer[1] * product) >> 8; - dest_pointer[2] = (source_pointer[2] * product) >> 8; + auto& dest_pointer = gGamePalette[x]; + dest_pointer.Blue = (source_pointer[0] * product) >> 8; + dest_pointer.Green = (source_pointer[1] * product) >> 8; + dest_pointer.Red = (source_pointer[2] * product) >> 8; source_pointer += 3; - dest_pointer += 4; + + x++; } UpdatePalette(gGamePalette, 10, 236); } @@ -759,14 +737,14 @@ void LoadPalette() int32_t width = g1->width; int32_t x = g1->x_offset; uint8_t* src = g1->offset; - uint8_t* dst = &gGamePalette[x * 4]; for (; width > 0; width--) { - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; + auto& dst = gGamePalette[x]; + dst.Blue = src[0]; + dst.Green = src[1]; + dst.Red = src[2]; src += 3; - dst += 4; + x++; } } UpdatePalette(gGamePalette, 10, 236); @@ -911,15 +889,14 @@ FilterPaletteID GetGlassPaletteId(colour_t c) return GlassPaletteIds[c]; } -void UpdatePalette(const uint8_t* colours, int32_t start_index, int32_t num_colours) +void UpdatePalette(std::span palette, int32_t start_index, int32_t num_colours) { - colours += start_index * 4; - for (int32_t i = start_index; i < num_colours + start_index; i++) { - uint8_t r = colours[2]; - uint8_t g = colours[1]; - uint8_t b = colours[0]; + const auto& colour = palette[i]; + uint8_t b = colour.Blue; + uint8_t g = colour.Green; + uint8_t r = colour.Red; if (LightFXIsAvailable()) { @@ -936,18 +913,17 @@ void UpdatePalette(const uint8_t* colours, int32_t start_index, int32_t num_colo } } - gPalette[i].Red = r; - gPalette[i].Green = g; gPalette[i].Blue = b; + gPalette[i].Green = g; + gPalette[i].Red = r; gPalette[i].Alpha = 0; - colours += 4; } // Fix #1749 and #6535: rainbow path, donut shop and pause button contain black spots that should be white. - gPalette[255].Alpha = 0; - gPalette[255].Red = 255; - gPalette[255].Green = 255; gPalette[255].Blue = 255; + gPalette[255].Green = 255; + gPalette[255].Red = 255; + gPalette[255].Alpha = 0; if (!gOpenRCT2Headless) { @@ -990,14 +966,15 @@ void UpdatePaletteEffects() if (g1 != nullptr) { int32_t xoffset = g1->x_offset; - xoffset = xoffset * 4; - uint8_t* paletteOffset = gGamePalette + xoffset; + for (int32_t i = 0; i < g1->width; i++) { - paletteOffset[(i * 4) + 0] = -((0xFF - g1->offset[(i * 3) + 0]) / 2) - 1; - paletteOffset[(i * 4) + 1] = -((0xFF - g1->offset[(i * 3) + 1]) / 2) - 1; - paletteOffset[(i * 4) + 2] = -((0xFF - g1->offset[(i * 3) + 2]) / 2) - 1; + auto& paletteOffset = gGamePalette[xoffset + i]; + paletteOffset.Blue = -((0xFF - g1->offset[(i * 3) + 0]) / 2) - 1; + paletteOffset.Green = -((0xFF - g1->offset[(i * 3) + 1]) / 2) - 1; + paletteOffset.Red = -((0xFF - g1->offset[(i * 3) + 2]) / 2) - 1; } + UpdatePalette(gGamePalette, kPaletteOffsetDynamic, kPaletteLengthDynamic); } gClimateLightningFlash++; @@ -1018,13 +995,13 @@ void UpdatePaletteEffects() if (g1 != nullptr) { int32_t xoffset = g1->x_offset; - xoffset = xoffset * 4; - uint8_t* paletteOffset = gGamePalette + xoffset; + for (int32_t i = 0; i < g1->width; i++) { - paletteOffset[(i * 4) + 0] = g1->offset[(i * 3) + 0]; - paletteOffset[(i * 4) + 1] = g1->offset[(i * 3) + 1]; - paletteOffset[(i * 4) + 2] = g1->offset[(i * 3) + 2]; + auto& paletteOffset = gGamePalette[xoffset + i]; + paletteOffset.Blue = g1->offset[(i * 3) + 0]; + paletteOffset.Green = g1->offset[(i * 3) + 1]; + paletteOffset.Red = g1->offset[(i * 3) + 2]; } } } @@ -1054,19 +1031,18 @@ void UpdatePaletteEffects() if (g1 != nullptr) { uint8_t* vs = &g1->offset[j * 3]; - uint8_t* vd = &gGamePalette[kPaletteOffsetWaterWaves * 4]; int32_t n = kPaletteLengthWaterWaves; for (int32_t i = 0; i < n; i++) { - vd[0] = vs[0]; - vd[1] = vs[1]; - vd[2] = vs[2]; + auto& vd = gGamePalette[kPaletteOffsetWaterWaves + i]; + vd.Blue = vs[0]; + vd.Green = vs[1]; + vd.Red = vs[2]; vs += 9; if (vs >= &g1->offset[9 * n]) { vs -= 9 * n; } - vd += 4; } } @@ -1075,23 +1051,23 @@ void UpdatePaletteEffects() { waterId = water_type->palette_index_2; } + g1 = GfxGetG1Element(shade + waterId); if (g1 != nullptr) { uint8_t* vs = &g1->offset[j * 3]; - uint8_t* vd = &gGamePalette[kPaletteOffsetWaterSparkles * 4]; int32_t n = kPaletteLengthWaterSparkles; for (int32_t i = 0; i < n; i++) { - vd[0] = vs[0]; - vd[1] = vs[1]; - vd[2] = vs[2]; + auto& vd = gGamePalette[kPaletteOffsetWaterSparkles]; + vd.Blue = vs[0]; + vd.Green = vs[1]; + vd.Red = vs[2]; vs += 9; if (vs >= &g1->offset[9 * n]) { vs -= 9 * n; } - vd += 4; } } @@ -1101,19 +1077,18 @@ void UpdatePaletteEffects() if (g1 != nullptr) { uint8_t* vs = &g1->offset[j * 3]; - uint8_t* vd = &gGamePalette[PALETTE_INDEX_243 * 4]; int32_t n = 3; for (int32_t i = 0; i < n; i++) { - vd[0] = vs[0]; - vd[1] = vs[1]; - vd[2] = vs[2]; + auto& vd = gGamePalette[PALETTE_INDEX_243 + i]; + vd.Blue = vs[0]; + vd.Green = vs[1]; + vd.Red = vs[2]; vs += 3; if (vs >= &g1->offset[3 * n]) { vs -= 3 * n; } - vd += 4; } } diff --git a/src/openrct2/drawing/Drawing.h b/src/openrct2/drawing/Drawing.h index 5a69037d41..0598f3b854 100644 --- a/src/openrct2/drawing/Drawing.h +++ b/src/openrct2/drawing/Drawing.h @@ -14,18 +14,22 @@ #include "../interface/Colour.h" #include "../interface/ZoomLevel.h" #include "../world/Location.hpp" +#include "ColourPalette.h" #include "Font.h" #include "ImageId.hpp" #include "Text.h" +#include #include #include #include +#include #include struct ScreenCoordsXY; struct ScreenLine; struct ScreenRect; + namespace OpenRCT2 { struct IPlatformEnvironment; @@ -37,47 +41,6 @@ namespace OpenRCT2::Drawing struct IDrawingEngine; } -struct PaletteBGRA -{ - uint8_t Blue{}; - uint8_t Green{}; - uint8_t Red{}; - uint8_t Alpha{}; -}; - -constexpr auto PALETTE_SIZE = 256u; - -struct GamePalette -{ - PaletteBGRA Colour[PALETTE_SIZE]{}; - - PaletteBGRA& operator[](size_t idx) - { - assert(idx < PALETTE_SIZE); - if (idx >= PALETTE_SIZE) - { - static PaletteBGRA dummy; - return dummy; - } - - return Colour[idx]; - } - - const PaletteBGRA operator[](size_t idx) const - { - assert(idx < PALETTE_SIZE); - if (idx >= PALETTE_SIZE) - return {}; - - return Colour[idx]; - } - - explicit operator uint8_t*() - { - return reinterpret_cast(Colour); - } -}; - struct G1Element { uint8_t* offset = nullptr; // 0x00 @@ -388,39 +351,39 @@ struct TranslucentWindowPalette struct PaletteMap { private: - uint8_t* _data{}; - uint32_t _dataLength{}; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-private-field" - uint16_t _numMaps; -#pragma clang diagnostic pop - uint16_t _mapLength; + std::span _data{}; +#ifdef _DEBUG + // We only require those fields for the asserts in debug builds. + size_t _numMaps{}; + size_t _mapLength{}; +#endif public: - static const PaletteMap& GetDefault(); + static PaletteMap GetDefault(); - PaletteMap() = default; + constexpr PaletteMap() = default; - PaletteMap(uint8_t* data, uint16_t numMaps, uint16_t mapLength) - : _data(data) - , _dataLength(numMaps * mapLength) + constexpr PaletteMap(uint8_t* data, size_t numMaps, size_t mapLength) + : _data{ data, numMaps * mapLength } +#ifdef _DEBUG , _numMaps(numMaps) , _mapLength(mapLength) +#endif { } - template - PaletteMap(uint8_t (&map)[TSize]) + constexpr PaletteMap(std::span map) : _data(map) - , _dataLength(static_cast(std::size(map))) +#ifdef _DEBUG , _numMaps(1) - , _mapLength(static_cast(std::size(map))) + , _mapLength(map.size()) +#endif { } - bool operator==(const PaletteMap& lhs) const; uint8_t& operator[](size_t index); uint8_t operator[](size_t index) const; + uint8_t Blend(uint8_t src, uint8_t dst) const; void Copy(size_t dstIndex, const PaletteMap& src, size_t srcIndex, size_t length); }; @@ -428,7 +391,7 @@ public: struct DrawSpriteArgs { ImageId Image; - const PaletteMap& PalMap; + PaletteMap PalMap; const G1Element& SourceImage; int32_t SrcX; int32_t SrcY; @@ -528,8 +491,8 @@ constexpr uint8_t kPaletteTotalOffsets = 192; constexpr int8_t kMaxScrollingTextModes = 38; -extern GamePalette gPalette; -extern uint8_t gGamePalette[256 * 4]; +extern OpenRCT2::Drawing::GamePalette gPalette; +extern OpenRCT2::Drawing::GamePalette gGamePalette; extern uint32_t gPaletteEffectFrame; extern uint8_t gTextPalette[]; @@ -642,7 +605,7 @@ void MaskFn( std::optional GetPaletteG1Index(colour_t paletteId); std::optional GetPaletteMapForColour(colour_t paletteId); -void UpdatePalette(const uint8_t* colours, int32_t start_index, int32_t num_colours); +void UpdatePalette(std::span palette, int32_t start_index, int32_t num_colours); void UpdatePaletteEffects(); void RefreshVideo(); diff --git a/src/openrct2/drawing/IDrawingEngine.h b/src/openrct2/drawing/IDrawingEngine.h index 3f8751af56..20d8d0d288 100644 --- a/src/openrct2/drawing/IDrawingEngine.h +++ b/src/openrct2/drawing/IDrawingEngine.h @@ -10,6 +10,7 @@ #pragma once #include "./Weather.h" +#include "ColourPalette.h" #include #include @@ -39,7 +40,6 @@ enum DRAWING_ENGINE_FLAGS }; struct DrawPixelInfo; -struct GamePalette; namespace OpenRCT2::Ui { diff --git a/src/openrct2/drawing/ImageImporter.cpp b/src/openrct2/drawing/ImageImporter.cpp index e4573630e3..a71fbeefa3 100644 --- a/src/openrct2/drawing/ImageImporter.cpp +++ b/src/openrct2/drawing/ImageImporter.cpp @@ -306,7 +306,7 @@ namespace OpenRCT2::Drawing { if (!IsTransparentPixel(colour)) { - for (uint32_t i = 0; i < PALETTE_SIZE; i++) + for (uint32_t i = 0; i < kGamePaletteSize; i++) { if (static_cast(palette[i].Red) == colour[0] && static_cast(palette[i].Green) == colour[1] && static_cast(palette[i].Blue) == colour[2]) @@ -364,7 +364,7 @@ namespace OpenRCT2::Drawing { auto smallestError = static_cast(-1); auto bestMatch = PALETTE_TRANSPARENT; - for (uint32_t x = 0; x < PALETTE_SIZE; x++) + for (uint32_t x = 0; x < kGamePaletteSize; x++) { if (IsChangablePixel(x)) { diff --git a/src/openrct2/drawing/ImageImporter.h b/src/openrct2/drawing/ImageImporter.h index 7a825177f9..9c56c380b5 100644 --- a/src/openrct2/drawing/ImageImporter.h +++ b/src/openrct2/drawing/ImageImporter.h @@ -93,7 +93,7 @@ namespace OpenRCT2::Drawing ImageImportMeta createImageImportMetaFromJson(json_t& input); } // namespace OpenRCT2::Drawing -constexpr GamePalette StandardPalette = { { +constexpr OpenRCT2::Drawing::GamePalette StandardPalette = { { // 0 (Unused/Transparent) { 0, 0, 0, 255 }, diff --git a/src/openrct2/drawing/LightFX.cpp b/src/openrct2/drawing/LightFX.cpp index c0135874b4..b8689cca36 100644 --- a/src/openrct2/drawing/LightFX.cpp +++ b/src/openrct2/drawing/LightFX.cpp @@ -30,6 +30,7 @@ #include using namespace OpenRCT2; +using namespace OpenRCT2::Drawing; static uint8_t _bakedLightTexture_lantern_0[32 * 32]; static uint8_t _bakedLightTexture_lantern_1[64 * 64]; diff --git a/src/openrct2/drawing/LightFX.h b/src/openrct2/drawing/LightFX.h index 1c869f59ff..1d42ff4729 100644 --- a/src/openrct2/drawing/LightFX.h +++ b/src/openrct2/drawing/LightFX.h @@ -9,12 +9,13 @@ #pragma once +#include "ColourPalette.h" + #include struct CoordsXY; struct Vehicle; struct DrawPixelInfo; -struct GamePalette; struct CoordsXYZ; struct EntityBase; @@ -56,7 +57,7 @@ void LightFXRenderLightsToFrontBuffer(); void LightFXUpdateViewportSettings(); void* LightFXGetFrontBuffer(); -const GamePalette& LightFXGetPalette(); +const OpenRCT2::Drawing::GamePalette& LightFXGetPalette(); void LightFXAdd3DLight(const EntityBase& entity, const uint8_t id, const CoordsXYZ& loc, const LightType lightType); diff --git a/src/openrct2/drawing/NewDrawing.h b/src/openrct2/drawing/NewDrawing.h index cd71c7b3bf..0e1c9dc760 100644 --- a/src/openrct2/drawing/NewDrawing.h +++ b/src/openrct2/drawing/NewDrawing.h @@ -10,9 +10,10 @@ #pragma once #include "../localisation/StringIdType.h" +#include "ColourPalette.h" struct DrawPixelInfo; -struct GamePalette; + enum class DrawingEngine : int32_t; extern StringId DrawingEngineStringIds[3]; @@ -21,7 +22,7 @@ DrawingEngine drawing_engine_get_type(); bool DrawingEngineRequiresNewWindow(DrawingEngine srcEngine, DrawingEngine dstEngine); void DrawingEngineInit(); void DrawingEngineResize(); -void DrawingEngineSetPalette(const GamePalette& colours); +void DrawingEngineSetPalette(const OpenRCT2::Drawing::GamePalette& colours); void DrawingEngineCopyRect(int32_t x, int32_t y, int32_t width, int32_t height, int32_t dx, int32_t dy); void DrawingEngineDispose(); diff --git a/src/openrct2/interface/Colour.cpp b/src/openrct2/interface/Colour.cpp index dc8b9ae179..292503f505 100644 --- a/src/openrct2/interface/Colour.cpp +++ b/src/openrct2/interface/Colour.cpp @@ -16,6 +16,8 @@ #include +using namespace OpenRCT2::Drawing; + ColourShadeMap ColourMapA[COLOUR_COUNT] = {}; static constexpr uint8_t kLegacyColourMaskBase = 0x1F; @@ -169,9 +171,9 @@ static uint8_t FindClosestPaletteIndex(uint8_t red, uint8_t green, uint8_t blue) static void InitBlendColourMap() { - for (size_t i = 0; i < PALETTE_SIZE; i++) + for (size_t i = 0; i < kGamePaletteSize; i++) { - for (size_t j = i; j < PALETTE_SIZE; j++) + for (size_t j = i; j < kGamePaletteSize; j++) { uint8_t red = (gPalette[i].Red + gPalette[j].Red) / 2; uint8_t green = (gPalette[i].Green + gPalette[j].Green) / 2; diff --git a/src/openrct2/interface/Screenshot.cpp b/src/openrct2/interface/Screenshot.cpp index cbdbea5c31..c5e7d4336e 100644 --- a/src/openrct2/interface/Screenshot.cpp +++ b/src/openrct2/interface/Screenshot.cpp @@ -63,7 +63,7 @@ static bool WriteDpiToFile(std::string_view path, const DrawPixelInfo& dpi, cons image.Height = dpi.height; image.Depth = 8; image.Stride = dpi.LineStride(); - image.Palette = std::make_unique(palette); + image.Palette = palette; image.Pixels = std::vector(pixels8, pixels8 + pixelsLen); Imaging::WriteToFile(path, image, IMAGE_FORMAT::PNG); return true; diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 6527d249f1..56e4f4bcf4 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -238,6 +238,7 @@ +