Browse Source

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.
pull/7352/head
Gleb Mazovetskiy 2 years ago
parent
commit
451f1fd70a
  1. 1
      Source/CMakeLists.txt
  2. 2
      Source/diablo.cpp
  3. 70
      Source/engine/render/dun_render.cpp
  4. 6
      Source/levels/dun_tile.hpp
  5. 22
      Source/levels/gendung.cpp
  6. 194
      Source/levels/reencode_dun_cels.cpp
  7. 20
      Source/levels/reencode_dun_cels.hpp

1
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

2
Source/diablo.cpp

@ -2856,8 +2856,8 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir)
SetRndSeed(DungeonSeeds[currlevel]);
IncProgress();
MakeLightTable();
SetDungeonMicros();
LoadLvlGFX();
SetDungeonMicros();
ClearClxDrawCache();
IncProgress();

70
Source/engine/render/dun_render.cpp

@ -186,19 +186,19 @@ template <LightType Light>
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<LightType::FullyDark>(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<LightType::FullyDark>(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<LightType::FullyLit>(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<LightType::FullyLit>(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<LightType::PartiallyLit>(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<LightType::PartiallyLit>(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 <LightType Light, bool Transparent>
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<Light, Transparent>(dst, src, width, tbl);
dst -= dstPitch + XStep;
src += width;
width += XStep;
RenderLineTransparentOrOpaque<Light, Transparent>(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<Light, Transparent>(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<Light, Transparent>(dst, src, width - clipRight, tbl);
@ -541,19 +533,13 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleFull(uint8_t *DVL_RES
{
RenderLeftTriangleLower<Light, Transparent>(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<Light, Transparent>(dst, src, width, tbl);
src += width;
dst -= dstPitch - XStep;
width -= XStep;
RenderLineTransparentOrOpaque<Light, Transparent>(dst, src, width, tbl);
src += width;
dst -= dstPitch - XStep;
}
RenderLineTransparentOrOpaque<Light, Transparent>(dst, src, width, tbl);
}
template <LightType Light, bool Transparent>
@ -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<Light, Transparent>(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 <LightType Light, bool Transparent>
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<Light, Transparent>(dst, src, width, tbl);
src += width + 2;
width += XStep;
dst -= dstPitch;
unsigned width = XStep;
for (unsigned i = 0; i < LowerHeight; ++i) {
RenderLineTransparentOrOpaque<Light, Transparent>(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<Light, Transparent>(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<Light, Transparent>(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<Light, Transparent>(dst, src, width - skip, tbl);
src += width + 2 * (i % 2);
src += width;
}
}
@ -685,18 +664,13 @@ template <LightType Light, bool Transparent>
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<Light, Transparent>(dst, dstPitch, src, tbl);
unsigned width = Width;
for (unsigned i = 0; i < TriangleUpperHeight - 1; i += 2) {
width -= XStep;
RenderLineTransparentOrOpaque<Light, Transparent>(dst, src, width, tbl);
src += width + 2;
dst -= dstPitch;
width -= XStep;
unsigned width = Width - XStep;
for (unsigned i = 0; i < TriangleUpperHeight; ++i) {
RenderLineTransparentOrOpaque<Light, Transparent>(dst, src, width, tbl);
src += width;
dst -= dstPitch;
width -= XStep;
}
RenderLineTransparentOrOpaque<Light, Transparent>(dst, src, width, tbl);
}
template <LightType Light, bool Transparent>
@ -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<Light, Transparent>(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<Light, Transparent>(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<Light, Transparent>(dst, src, width > skip ? width - skip : 0, tbl);
src += width + 2 * (i % 2);
src += width;
}
}

6
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]:

22
Source/levels/gendung.cpp

@ -1,19 +1,28 @@
#include "levels/gendung.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <stack>
#include <utility>
#include <vector>
#include <ankerl/unordered_dense.h>
#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<uint16_t[]> levelPieces = LoadMinData(tileCount);
ankerl::unordered_dense::map<uint16_t, TileType> 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<std::pair<uint16_t, TileType>> frameToTypeList = std::move(frameToTypeMap).extract();
c_sort(frameToTypeList);
ReencodeDungeonCels(pDungeonCels, frameToTypeList);
}
void DRLG_InitTrans()

194
Source/levels/reencode_dun_cels.cpp

@ -0,0 +1,194 @@
#include "levels/reencode_dun_cels.hpp"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <span>
#include <utility>
#include <SDL_endian.h>
#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<std::pair<uint16_t, TileType>> frames)
{
size_t result = (2 + frames.size()) * 4;
const auto *srcOffsets = reinterpret_cast<const uint32_t *>(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<std::byte[]> &dungeonCels, std::span<std::pair<uint16_t, TileType>> frames)
{
const auto *srcData = reinterpret_cast<const uint8_t *>(dungeonCels.get());
const auto *srcOffsets = reinterpret_cast<const uint32_t *>(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<std::byte[]> result { new std::byte[outSize] };
auto *const resultPtr = reinterpret_cast<uint8_t *>(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<size_t>(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<const uint32_t *>(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

20
Source/levels/reencode_dun_cels.hpp

@ -0,0 +1,20 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <utility>
#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<std::byte[]> &dungeonCels, std::span<std::pair<uint16_t, TileType>> frames);
} // namespace devilution
Loading…
Cancel
Save