From 451f1fd70a7e2538391229e41e0233186e470daf Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 18 Aug 2024 07:39:19 +0100 Subject: [PATCH] Re-encode dungeon tiles to remove bloat Triangles in the dungeon CEL data have two redundant 0x00 pixels every other row. Re-encodes the dungeon CEL data to remove these pixels in order to save RAM and simplify the rendering code. Example RAM savings: ``` VERBOSE: Re-encoding dungeon CELs: 1,119 frames, 738,836 bytes VERBOSE: Re-encoded dungeon CELs: 1,119 frames, 722,552 bytes ``` Performance remains the same. The rendering code is now a bit simpler. --- Source/CMakeLists.txt | 1 + Source/diablo.cpp | 2 +- Source/engine/render/dun_render.cpp | 70 ++++------ Source/levels/dun_tile.hpp | 6 +- Source/levels/gendung.cpp | 22 +++- Source/levels/reencode_dun_cels.cpp | 194 ++++++++++++++++++++++++++++ Source/levels/reencode_dun_cels.hpp | 20 +++ 7 files changed, 263 insertions(+), 52 deletions(-) create mode 100644 Source/levels/reencode_dun_cels.cpp create mode 100644 Source/levels/reencode_dun_cels.hpp diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 3e1579b8d2c..f291841044c 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -126,6 +126,7 @@ set(libdevilutionx_SRCS levels/drlg_l3.cpp levels/drlg_l4.cpp levels/gendung.cpp + levels/reencode_dun_cels.cpp levels/setmaps.cpp levels/themes.cpp levels/town.cpp diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 7c51b6f00ad..f93ff6aa99c 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -2856,8 +2856,8 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) SetRndSeed(DungeonSeeds[currlevel]); IncProgress(); MakeLightTable(); - SetDungeonMicros(); LoadLvlGFX(); + SetDungeonMicros(); ClearClxDrawCache(); IncProgress(); diff --git a/Source/engine/render/dun_render.cpp b/Source/engine/render/dun_render.cpp index e22205581c2..9788872bcef 100644 --- a/Source/engine/render/dun_render.cpp +++ b/Source/engine/render/dun_render.cpp @@ -186,19 +186,19 @@ template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparent(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t n, const uint8_t *DVL_RESTRICT tbl); template <> -void RenderLineTransparent(uint8_t *DVL_RESTRICT dst, [[maybe_unused]] const uint8_t *DVL_RESTRICT src, uint_fast8_t n, [[maybe_unused]] const uint8_t *DVL_RESTRICT tbl) +DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparent(uint8_t *DVL_RESTRICT dst, [[maybe_unused]] const uint8_t *DVL_RESTRICT src, uint_fast8_t n, [[maybe_unused]] const uint8_t *DVL_RESTRICT tbl) { BlitFillBlended(dst, n, 0); } template <> -void RenderLineTransparent(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t n, [[maybe_unused]] const uint8_t *DVL_RESTRICT tbl) +DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparent(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t n, [[maybe_unused]] const uint8_t *DVL_RESTRICT tbl) { BlitPixelsBlended(dst, src, n); } template <> -void RenderLineTransparent(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t n, const uint8_t *DVL_RESTRICT tbl) +DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparent(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t n, const uint8_t *DVL_RESTRICT tbl) { BlitPixelsBlendedWithMap(dst, src, n, tbl); } @@ -464,29 +464,24 @@ DVL_ALWAYS_INLINE DiamondClipY CalculateDiamondClipY(const Clip &clip) DVL_ALWAYS_INLINE std::size_t CalculateTriangleSourceSkipLowerBottom(int_fast16_t numLines) { - return XStep * numLines * (numLines + 1) / 2 + 2 * ((numLines + 1) / 2); + return XStep * numLines * (numLines + 1) / 2; } DVL_ALWAYS_INLINE std::size_t CalculateTriangleSourceSkipUpperBottom(int_fast16_t numLines) { - return 2 * TriangleUpperHeight * numLines - numLines * (numLines - 1) + 2 * ((numLines + 1) / 2); + return 2 * TriangleUpperHeight * numLines - numLines * (numLines - 1); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleLower(uint8_t *DVL_RESTRICT &dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { dst += XStep * (LowerHeight - 1); - unsigned width = 0; - for (unsigned i = 0; i < LowerHeight; i += 2) { - src += 2; - width += XStep; + unsigned width = XStep; + for (unsigned i = 0; i < LowerHeight; ++i) { RenderLineTransparentOrOpaque(dst, src, width, tbl); - dst -= dstPitch + XStep; src += width; - width += XStep; - RenderLineTransparentOrOpaque(dst, src, width, tbl); dst -= dstPitch + XStep; - src += width; + width += XStep; } } @@ -497,7 +492,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleLowerClipVertical(con dst += XStep * (LowerHeight - clipY.lowerBottom - 1); const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep) { - src += 2 * (i % 2); const auto width = XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width; @@ -511,7 +505,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleLowerClipLeftAndVerti dst += XStep * (LowerHeight - clipY.lowerBottom - 1) - clipLeft; const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep) { - src += 2 * (i % 2); const auto width = XStep * i; const auto startX = Width - XStep * i; const auto skip = startX < clipLeft ? clipLeft - startX : 0; @@ -528,7 +521,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleLowerClipRightAndVert dst += XStep * (LowerHeight - clipY.lowerBottom - 1); const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep) { - src += 2 * (i % 2); const auto width = XStep * i; if (width > clipRight) RenderLineTransparentOrOpaque(dst, src, width - clipRight, tbl); @@ -541,19 +533,13 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleFull(uint8_t *DVL_RES { RenderLeftTriangleLower(dst, dstPitch, src, tbl); dst += 2 * XStep; - unsigned width = Width; - for (unsigned i = 0; i < TriangleUpperHeight - 1; i += 2) { - src += 2; - width -= XStep; + unsigned width = Width - XStep; + for (unsigned i = 0; i < TriangleUpperHeight; ++i) { RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width; dst -= dstPitch - XStep; width -= XStep; - RenderLineTransparentOrOpaque(dst, src, width, tbl); - src += width; - dst -= dstPitch - XStep; } - RenderLineTransparentOrOpaque(dst, src, width, tbl); } template @@ -565,7 +551,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleClipVertical(uint8_t dst += 2 * XStep + XStep * clipY.upperBottom; const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) { - src += 2 * (i % 2); const auto width = Width - XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width; @@ -582,7 +567,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleClipLeftAndVertical(u dst += 2 * XStep + XStep * clipY.upperBottom; const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) { - src += 2 * (i % 2); const auto width = Width - XStep * i; const auto startX = XStep * i; const auto skip = startX < clipLeft ? clipLeft - startX : 0; @@ -601,7 +585,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleClipRightAndVertical( dst += 2 * XStep + XStep * clipY.upperBottom; const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) { - src += 2 * (i % 2); const auto width = Width - XStep * i; if (width <= clipRight) break; @@ -629,16 +612,12 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangle(uint8_t *DVL_RESTRIC template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleLower(uint8_t *DVL_RESTRICT &dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { - unsigned width = 0; - for (unsigned i = 0; i < LowerHeight; i += 2) { - width += XStep; - RenderLineTransparentOrOpaque(dst, src, width, tbl); - src += width + 2; - width += XStep; - dst -= dstPitch; + unsigned width = XStep; + for (unsigned i = 0; i < LowerHeight; ++i) { RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width; dst -= dstPitch; + width += XStep; } } @@ -650,7 +629,7 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleLowerClipVertical(co for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch) { const auto width = XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); - src += width + 2 * (i % 2); + src += width; } } @@ -663,7 +642,7 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleLowerClipLeftAndVert const auto width = XStep * i; if (width > clipLeft) RenderLineTransparentOrOpaque(dst, src + clipLeft, width - clipLeft, tbl); - src += width + 2 * (i % 2); + src += width; } } @@ -677,7 +656,7 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleLowerClipRightAndVer const auto skip = Width - width < clipRight ? clipRight - (Width - width) : 0; if (width > skip) RenderLineTransparentOrOpaque(dst, src, width - skip, tbl); - src += width + 2 * (i % 2); + src += width; } } @@ -685,18 +664,13 @@ template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleFull(uint8_t *DVL_RESTRICT dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { RenderRightTriangleLower(dst, dstPitch, src, tbl); - unsigned width = Width; - for (unsigned i = 0; i < TriangleUpperHeight - 1; i += 2) { - width -= XStep; - RenderLineTransparentOrOpaque(dst, src, width, tbl); - src += width + 2; - dst -= dstPitch; - width -= XStep; + unsigned width = Width - XStep; + for (unsigned i = 0; i < TriangleUpperHeight; ++i) { RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width; dst -= dstPitch; + width -= XStep; } - RenderLineTransparentOrOpaque(dst, src, width, tbl); } template @@ -709,7 +683,7 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleClipVertical(uint8_t for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch) { const auto width = Width - XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); - src += width + 2 * (i % 2); + src += width; } } @@ -726,7 +700,7 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleClipLeftAndVertical( if (width <= clipLeft) break; RenderLineTransparentOrOpaque(dst, src + clipLeft, width - clipLeft, tbl); - src += width + 2 * (i % 2); + src += width; } } @@ -742,7 +716,7 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleClipRightAndVertical const auto width = Width - XStep * i; const auto skip = Width - width < clipRight ? clipRight - (Width - width) : 0; RenderLineTransparentOrOpaque(dst, src, width > skip ? width - skip : 0, tbl); - src += width + 2 * (i % 2); + src += width; } } diff --git a/Source/levels/dun_tile.hpp b/Source/levels/dun_tile.hpp index 04983058420..0509dd574a2 100644 --- a/Source/levels/dun_tile.hpp +++ b/Source/levels/dun_tile.hpp @@ -34,8 +34,9 @@ enum class TileType : uint8_t { /** *🭮 Left-pointing 32x31 triangle. Encoded as 31 varying-width rows with 2 padding bytes before every even row. + * We remove the padding bytes in `ReencodeDungeonCels`. * - * The smallest rows (bottom and top) are 2px wide, the largest row is 16px wide (middle row). + * The smallest rows (bottom and top) are 2px wide, the largest row is 32px wide (middle row). * * Encoding: * for i in [0, 30]: @@ -46,8 +47,9 @@ enum class TileType : uint8_t { /** * 🭬Right-pointing 32x31 triangle. Encoded as 31 varying-width rows with 2 padding bytes after every even row. + * We remove the padding bytes in `ReencodeDungeonCels`. * - * The smallest rows (bottom and top) are 2px wide, the largest row is 16px wide (middle row). + * The smallest rows (bottom and top) are 2px wide, the largest row is 32px wide (middle row). * * Encoding: * for i in [0, 30]: diff --git a/Source/levels/gendung.cpp b/Source/levels/gendung.cpp index 7142828f60f..d894a7ff817 100644 --- a/Source/levels/gendung.cpp +++ b/Source/levels/gendung.cpp @@ -1,19 +1,28 @@ #include "levels/gendung.h" +#include #include +#include #include +#include #include +#include + +#include "engine/clx_sprite.hpp" #include "engine/load_file.hpp" #include "engine/random.hpp" +#include "engine/world_tile.hpp" #include "init.h" #include "levels/drlg_l1.h" #include "levels/drlg_l2.h" #include "levels/drlg_l3.h" #include "levels/drlg_l4.h" +#include "levels/reencode_dun_cels.hpp" #include "levels/town.h" #include "lighting.h" #include "options.h" +#include "utils/bitset2d.hpp" namespace devilution { @@ -490,12 +499,23 @@ void SetDungeonMicros() size_t tileCount; std::unique_ptr levelPieces = LoadMinData(tileCount); + ankerl::unordered_dense::map frameToTypeMap; + frameToTypeMap.reserve(4096); for (size_t i = 0; i < tileCount / blocks; i++) { uint16_t *pieces = &levelPieces[blocks * i]; for (size_t block = 0; block < blocks; block++) { - DPieceMicros[i].mt[block] = LevelCelBlock { SDL_SwapLE16(pieces[blocks - 2 + (block & 1) - (block & 0xE)]) }; + const LevelCelBlock levelCelBlock { SDL_SwapLE16(pieces[blocks - 2 + (block & 1) - (block & 0xE)]) }; + DPieceMicros[i].mt[block] = levelCelBlock; + if (levelCelBlock.hasValue()) { + if (const auto it = frameToTypeMap.find(levelCelBlock.frame()); it == frameToTypeMap.end()) { + frameToTypeMap.emplace_hint(it, levelCelBlock.frame(), levelCelBlock.type()); + } + } } } + std::vector> frameToTypeList = std::move(frameToTypeMap).extract(); + c_sort(frameToTypeList); + ReencodeDungeonCels(pDungeonCels, frameToTypeList); } void DRLG_InitTrans() diff --git a/Source/levels/reencode_dun_cels.cpp b/Source/levels/reencode_dun_cels.cpp new file mode 100644 index 00000000000..a50475a8255 --- /dev/null +++ b/Source/levels/reencode_dun_cels.cpp @@ -0,0 +1,194 @@ +#include "levels/reencode_dun_cels.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include "levels/dun_tile.hpp" +#include "utils/attributes.h" +#include "utils/endian.hpp" +#include "utils/format_int.hpp" +#include "utils/log.hpp" + +namespace devilution { +namespace { + +constexpr size_t LowerTriangleBloat = 16; +constexpr size_t TriangleBloat = LowerTriangleBloat + 14; + +DVL_ALWAYS_INLINE void ReencodeDungeonCelsLeftTriangleLower(uint8_t *&dst, const uint8_t *&src) +{ + unsigned width = 0; + for (unsigned i = 0; i < 8; ++i) { + src += 2; // Skips the two zero bytes (aka bloat). + width += 2; + std::memcpy(dst, src, width); + src += width; + dst += width; + width += 2; + std::memcpy(dst, src, width); + src += width; + dst += width; + } +} + +DVL_ALWAYS_INLINE void ReencodeDungeonCelsLeftTriangle(uint8_t *&dst, const uint8_t *&src) +{ + ReencodeDungeonCelsLeftTriangleLower(dst, src); + unsigned width = DunFrameWidth; + for (unsigned i = 0; i < 7; ++i) { + src += 2; // Skips the two zero bytes (aka bloat). + width -= 2; + std::memcpy(dst, src, width); + src += width; + dst += width; + width -= 2; + std::memcpy(dst, src, width); + src += width; + dst += width; + } + std::memcpy(dst, src, width); + src += width; + dst += width; +} + +DVL_ALWAYS_INLINE void ReencodeDungeonCelsRightTriangleLower(uint8_t *&dst, const uint8_t *&src) +{ + unsigned width = 0; + for (unsigned i = 0; i < 8; ++i) { + width += 2; + std::memcpy(dst, src, width); + src += width + 2; // Skips the two zero bytes (aka bloat). + dst += width; + width += 2; + std::memcpy(dst, src, width); + src += width; + dst += width; + } +} + +DVL_ALWAYS_INLINE void ReencodeDungeonCelsRightTriangle(uint8_t *&dst, const uint8_t *&src) +{ + ReencodeDungeonCelsRightTriangleLower(dst, src); + unsigned width = DunFrameWidth; + for (unsigned i = 0; i < 7; ++i) { + width -= 2; + std::memcpy(dst, src, width); + src += width + 2; // Skips the two zero bytes (aka bloat). + dst += width; + width -= 2; + std::memcpy(dst, src, width); + src += width; + dst += width; + } + std::memcpy(dst, src, width); + src += width; + dst += width; +} + +DVL_ALWAYS_INLINE void ReencodeDungeonCelsLeftTrapezoid(uint8_t *&dst, const uint8_t *&src) +{ + ReencodeDungeonCelsLeftTriangleLower(dst, src); + std::memcpy(dst, src, DunFrameWidth * 16); + src += DunFrameWidth * 16; + dst += DunFrameWidth * 16; +} + +DVL_ALWAYS_INLINE void ReencodeDungeonCelsRightTrapezoid(uint8_t *&dst, const uint8_t *&src) +{ + ReencodeDungeonCelsRightTriangleLower(dst, src); + std::memcpy(dst, src, DunFrameWidth * 16); + src += DunFrameWidth * 16; + dst += DunFrameWidth * 16; +} + +size_t GetReencodedSize(const uint8_t *dungeonCels, std::span> frames) +{ + size_t result = (2 + frames.size()) * 4; + const auto *srcOffsets = reinterpret_cast(dungeonCels); + for (const auto &[frame, type] : frames) { + size_t frameSize; + switch (type) { + case TileType::TransparentSquare: { + const uint32_t srcFrameBegin = SDL_SwapLE32(srcOffsets[frame]); + const uint32_t srcFrameEnd = SDL_SwapLE32(srcOffsets[frame + 1]); + frameSize = srcFrameEnd - srcFrameBegin; + } break; + case TileType::Square: { + frameSize = DunFrameWidth * DunFrameHeight; + } break; + case TileType::LeftTriangle: + case TileType::RightTriangle: + frameSize = 544 - TriangleBloat; + break; + case TileType::LeftTrapezoid: + case TileType::RightTrapezoid: + frameSize = 800 - LowerTriangleBloat; + break; + } + result += frameSize; + } + return result; +} + +} // namespace + +void ReencodeDungeonCels(std::unique_ptr &dungeonCels, std::span> frames) +{ + const auto *srcData = reinterpret_cast(dungeonCels.get()); + const auto *srcOffsets = reinterpret_cast(srcData); + + LogVerbose("Re-encoding dungeon CELs: {} frames, {} bytes", + FormatInteger(SDL_SwapLE32(srcOffsets[0])), + FormatInteger(SDL_SwapLE32(srcOffsets[SDL_SwapLE32(srcOffsets[0]) + 1]))); + + const size_t outSize = GetReencodedSize(srcData, frames); + std::unique_ptr result { new std::byte[outSize] }; + auto *const resultPtr = reinterpret_cast(result.get()); + WriteLE32(resultPtr, frames.size()); + uint8_t *out = resultPtr + (2 + frames.size()) * 4; // number of frames, frame offsets, file size + for (const auto &[frame, type] : frames) { + WriteLE32(&resultPtr[static_cast(frame * 4)], out - resultPtr); + const uint32_t srcFrameBegin = SDL_SwapLE32(srcOffsets[frame]); + const uint8_t *src = &srcData[srcFrameBegin]; + switch (type) { + case TileType::TransparentSquare: { + const uint32_t srcFrameEnd = SDL_SwapLE32(srcOffsets[frame + 1]); + const uint32_t size = srcFrameEnd - srcFrameBegin; + std::memcpy(out, src, size); + out += size; + } break; + case TileType::Square: + std::memcpy(out, src, DunFrameWidth * DunFrameHeight); + out += DunFrameWidth * DunFrameHeight; + break; + case TileType::LeftTriangle: + ReencodeDungeonCelsLeftTriangle(out, src); + break; + case TileType::RightTriangle: + ReencodeDungeonCelsRightTriangle(out, src); + break; + case TileType::LeftTrapezoid: + ReencodeDungeonCelsLeftTrapezoid(out, src); + break; + case TileType::RightTrapezoid: + ReencodeDungeonCelsRightTrapezoid(out, src); + break; + } + } + WriteLE32(&resultPtr[(1 + frames.size()) * 4], outSize); + + const auto *dstOffsets = reinterpret_cast(resultPtr); + LogVerbose(" Re-encoded dungeon CELs: {} frames, {} bytes", + FormatInteger(SDL_SwapLE32(dstOffsets[0])), + FormatInteger(SDL_SwapLE32(dstOffsets[SDL_SwapLE32(dstOffsets[0]) + 1]))); + + dungeonCels = std::move(result); +} + +} // namespace devilution diff --git a/Source/levels/reencode_dun_cels.hpp b/Source/levels/reencode_dun_cels.hpp new file mode 100644 index 00000000000..dae08fd0194 --- /dev/null +++ b/Source/levels/reencode_dun_cels.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "levels/dun_tile.hpp" + +namespace devilution { + +/** + * @brief Re-encodes dungeon cels, removing redundant padding bytes from triangles and trapezoids. + * + * This reduces memory usage and simplifies the rendering. + */ +void ReencodeDungeonCels(std::unique_ptr &dungeonCels, std::span> frames); + +} // namespace devilution