Browse Source

Simplify foliage rendering

During `ReencodeDungeonCels`, extracts floor tile foliage into
a triangle with the floor frame and a separate 16-px tall `TransparentSquare`.

This means that the floor frames are now always triangles and
the foliage can be rendered directly without masking.

Dungeon graphics sizes:

Map  | Frames | Foliage frames | Byte size  | Before PR | After PR
-----|--------|---------------:|-----------:|----------:|----------:
Town |  3,803 |             41 | 2,317,832  | 2,242,056 | 2,242,190
L1   |  1,119 |             11 |   738,836  |   721,604 |   721,110
L4   |  1,091 |              6 |   603,140  |   584,500 |   584,242

RG99 binary size reduced by ~4 KiB: 2,426,380 bytes -> 2,421,388 bytes
pull/7386/head
Gleb Mazovetskiy 2 years ago
parent
commit
2580cd7b68
  1. 5
      Source/diablo.cpp
  2. 54
      Source/engine/render/dun_render.cpp
  3. 86
      Source/engine/render/dun_render.hpp
  4. 57
      Source/engine/render/scrollrt.cpp
  5. 25
      Source/levels/dun_tile.hpp
  6. 20
      Source/levels/gendung.cpp
  7. 29
      Source/levels/gendung.h
  8. 152
      Source/levels/reencode_dun_cels.cpp
  9. 22
      Source/levels/reencode_dun_cels.hpp

5
Source/diablo.cpp

@ -2857,6 +2857,8 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir)
IncProgress();
LoadTrns();
MakeLightTable();
LoadLevelSOLData();
IncProgress();
LoadLvlGFX();
SetDungeonMicros();
ClearClxDrawCache();
@ -2911,7 +2913,6 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir)
if (!setlevel) {
CreateLevel(lvldir);
IncProgress();
LoadLevelSOLData();
SetRndSeed(DungeonSeeds[currlevel]);
if (leveltype != DTYPE_TOWN) {
@ -3039,8 +3040,6 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir)
}
InitCorpses();
IncProgress();
LoadLevelSOLData();
IncProgress();
if (lvldir == ENTRY_WARPLVL)
GetPortalLvlPos();

54
Source/engine/render/dun_render.cpp

@ -49,18 +49,11 @@ constexpr int_fast16_t TriangleUpperHeight = DunFrameHeight / 2 - 1;
/** Height of the upper rectangle of a trapezoid tile. */
constexpr int_fast16_t TrapezoidUpperHeight = DunFrameHeight / 2;
constexpr int_fast16_t TriangleHeight = LowerHeight + TriangleUpperHeight;
constexpr int_fast16_t TriangleHeight = DunFrameTriangleHeight;
/** For triangles, for each pixel drawn vertically, this many pixels are drawn horizontally. */
constexpr int_fast16_t XStep = 2;
int_fast16_t GetTileHeight(TileType tile)
{
if (tile == TileType::LeftTriangle || tile == TileType::RightTriangle)
return TriangleHeight;
return Height;
}
#ifdef DEBUG_STR
std::pair<std::string_view, UiFlags> GetTileDebugStr(TileType tile)
{
@ -322,10 +315,12 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderSquare(uint8_t *DVL_RESTRICT dst,
}
template <LightType Light, bool OpaquePrefix, int8_t PrefixIncrement>
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTransparentSquareFull(uint8_t *DVL_RESTRICT dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl)
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTransparentSquareFull(uint8_t *DVL_RESTRICT dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, unsigned height)
{
int8_t prefix = InitPrefix<PrefixIncrement>();
for (auto i = 0; i < Height; ++i, dst -= dstPitch + Width) {
DVL_ASSUME(height >= 16);
DVL_ASSUME(height <= 32);
for (unsigned i = 0; i < height; ++i, dst -= dstPitch + Width) {
uint_fast8_t drawWidth = Width;
while (drawWidth > 0) {
auto v = static_cast<int8_t>(*src++);
@ -427,8 +422,8 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTransparentSquareClipped(uint8_t
template <LightType Light, bool OpaquePrefix, int8_t PrefixIncrement = 0>
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTransparentSquare(uint8_t *DVL_RESTRICT dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip)
{
if (clip.width == Width && clip.height == Height) {
RenderTransparentSquareFull<Light, OpaquePrefix, PrefixIncrement>(dst, dstPitch, src, tbl);
if (clip.width == Width && clip.bottom == 0 && clip.top == 0) {
RenderTransparentSquareFull<Light, OpaquePrefix, PrefixIncrement>(dst, dstPitch, src, tbl, clip.height);
} else {
RenderTransparentSquareClipped<Light, OpaquePrefix, PrefixIncrement>(dst, dstPitch, src, tbl, clip);
}
@ -972,18 +967,6 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTileType(TileType tile, uint8_t *
}
}
template <bool OpaquePrefix, int8_t PrefixIncrement>
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTransparentSquareDispatch(uint8_t *DVL_RESTRICT dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip)
{
if (IsFullyDark(tbl)) {
RenderTransparentSquare<LightType::FullyDark, OpaquePrefix, PrefixIncrement>(dst, dstPitch, src, tbl, clip);
} else if (IsFullyLit(tbl)) {
RenderTransparentSquare<LightType::FullyLit, OpaquePrefix, PrefixIncrement>(dst, dstPitch, src, tbl, clip);
} else {
RenderTransparentSquare<LightType::PartiallyLit, OpaquePrefix, PrefixIncrement>(dst, dstPitch, src, tbl, clip);
}
}
template <LightType Light, bool OpaquePrefix, int8_t PrefixIncrement>
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTrapezoidOrTransparentSquare(TileType tile, uint8_t *DVL_RESTRICT dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip)
{
@ -1086,27 +1069,18 @@ std::string_view MaskTypeToString(MaskType maskType)
}
#endif
void RenderTile(const Surface &out, Point position,
LevelCelBlock levelCelBlock, MaskType maskType, const uint8_t *tbl)
DVL_ATTRIBUTE_HOT void RenderTileFrame(const Surface &out, const Point &position, TileType tile, const uint8_t *src, int_fast16_t height,
MaskType maskType, const uint8_t *tbl)
{
const TileType tile = levelCelBlock.type();
#ifdef DEBUG_RENDER_OFFSET_X
position.x += DEBUG_RENDER_OFFSET_X;
#endif
#ifdef DEBUG_RENDER_OFFSET_Y
position.y += DEBUG_RENDER_OFFSET_Y;
#endif
#ifdef DEBUG_RENDER_COLOR
DBGCOLOR = GetTileDebugColor(tile);
#endif
const Clip clip = CalculateClip(position.x, position.y, Width, GetTileHeight(tile), out);
if (clip.width <= 0 || clip.height <= 0)
return;
const Clip clip = CalculateClip(position.x, position.y, DunFrameWidth, height, out);
if (clip.width <= 0 || clip.height <= 0) return;
const auto *pFrameTable = reinterpret_cast<const uint32_t *>(pDungeonCels.get());
const auto *src = reinterpret_cast<const uint8_t *>(&pDungeonCels[SDL_SwapLE32(pFrameTable[levelCelBlock.frame()])]);
uint8_t *dst = out.at(static_cast<int>(position.x + clip.left), static_cast<int>(position.y - clip.bottom));
const uint16_t dstPitch = out.pitch();
@ -1127,12 +1101,6 @@ void RenderTile(const Surface &out, Point position,
case MaskType::Right:
RenderRightTrapezoidOrTransparentSquareDispatch</*OpaquePrefix=*/true, /*PrefixIncrement=*/-2>(tile, dst, dstPitch, src, tbl, clip);
break;
case MaskType::LeftFoliage:
RenderTransparentSquareDispatch</*OpaquePrefix=*/true, /*PrefixIncrement=*/2>(dst, dstPitch, src, tbl, clip);
break;
case MaskType::RightFoliage:
RenderTransparentSquareDispatch</*OpaquePrefix=*/false, /*PrefixIncrement=*/-2>(dst, dstPitch, src, tbl, clip);
break;
}
#ifdef DEBUG_STR

86
Source/engine/render/dun_render.hpp

@ -7,11 +7,11 @@
#include <cstdint>
#include <SDL_endian.h>
#include "engine/point.hpp"
#include "engine/surface.hpp"
#include "levels/dun_tile.hpp"
#include "levels/gendung.h"
#include "utils/attributes.h"
// #define DUN_RENDER_STATS
#ifdef DUN_RENDER_STATS
@ -85,60 +85,6 @@ enum class MaskType : uint8_t {
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
*/
Left,
/**
* @brief Only the upper-left triangle is rendered.
*
* Can only be used with `TileType::TransparentSquare`.
*
* The lower 16 rows are skipped.
* The upper 16 rows are arranged like this (🮆 = opaque, 🮐 = not rendered):
*
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆🮆🮆
* 🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮆🮆
*/
RightFoliage,
/**
* @brief Only the upper right triangle is rendered.
*
* Can only be used with `TileType::TransparentSquare`.
*
* The lower 16 rows are skipped.
* The upper 16 rows are arranged like this (🮆 = opaque, 🮐 = not rendered):
*
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
* 🮆🮆🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐🮐
*/
LeftFoliage,
};
#ifdef DUN_RENDER_STATS
@ -163,6 +109,12 @@ std::string_view TileTypeToString(TileType tileType);
std::string_view MaskTypeToString(MaskType maskType);
#endif
/**
* @brief Low-level tile rendering function.
*/
void RenderTileFrame(const Surface &out, const Point &position, TileType tile, const uint8_t *src, int_fast16_t height,
MaskType maskType, const uint8_t *tbl);
/**
* @brief Blit current world CEL to the given buffer
* @param out Target buffer
@ -171,8 +123,26 @@ std::string_view MaskTypeToString(MaskType maskType);
* @param maskType The mask to use,
* @param tbl LightTable or TRN for a tile.
*/
void RenderTile(const Surface &out, Point position,
LevelCelBlock levelCelBlock, MaskType maskType, const uint8_t *tbl);
DVL_ALWAYS_INLINE void RenderTile(const Surface &out, const Point &position,
LevelCelBlock levelCelBlock, MaskType maskType, const uint8_t *tbl)
{
const TileType tileType = levelCelBlock.type();
RenderTileFrame(out, position, tileType,
GetDunFrame(levelCelBlock.frame()),
(tileType == TileType::LeftTriangle || tileType == TileType::RightTriangle)
? DunFrameTriangleHeight
: DunFrameHeight,
maskType, tbl);
}
/**
* @brief Renders a floor foliage tile.
*/
DVL_ALWAYS_INLINE void RenderTileFoliage(const Surface &out, const Point &position, LevelCelBlock levelCelBlock, const uint8_t *tbl)
{
RenderTileFrame(out, Point { position.x, position.y - 16 }, TileType::TransparentSquare,
GetDunFrameFoliage(levelCelBlock.frame()), /*height=*/16, MaskType::Solid, tbl);
}
/**
* @brief Render a black 64x31 tile

57
Source/engine/render/scrollrt.cpp

@ -486,7 +486,6 @@ void DrawCell(const Surface &out, Point tilePosition, Point targetBufferPosition
if ((SDL_GetModState() & KMOD_ALT) != 0)
transparency = false;
#endif
const bool foliage = IsFloor(tilePosition);
const auto getFirstTileMaskLeft = [=](TileType tile) -> MaskType {
if (transparency) {
@ -502,8 +501,6 @@ void DrawCell(const Surface &out, Point tilePosition, Point targetBufferPosition
return MaskType::Transparent;
}
}
if (foliage)
return MaskType::LeftFoliage;
return MaskType::Solid;
};
@ -521,40 +518,34 @@ void DrawCell(const Surface &out, Point tilePosition, Point targetBufferPosition
return MaskType::Transparent;
}
}
if (foliage)
return MaskType::RightFoliage;
return MaskType::Solid;
};
// The first micro tile may be rendered with a foliage mask.
// Only `TransparentSquare` tiles are rendered when `foliage` is true.
{
{
const LevelCelBlock levelCelBlock { pMap->mt[0] };
const TileType tileType = levelCelBlock.type();
const MaskType maskType = getFirstTileMaskLeft(tileType);
if (levelCelBlock.hasValue()) {
if (maskType != MaskType::LeftFoliage || tileType == TileType::TransparentSquare) {
RenderTile(out, targetBufferPosition,
levelCelBlock, maskType, tbl);
}
// If the first micro tile is a floor tile, it may be followed
// by foliage which should be rendered now.
const bool isFloor = IsFloor(tilePosition);
if (const LevelCelBlock levelCelBlock { pMap->mt[0] }; levelCelBlock.hasValue()) {
const TileType tileType = levelCelBlock.type();
if (!isFloor || tileType == TileType::TransparentSquare) {
if (isFloor && tileType == TileType::TransparentSquare) {
RenderTileFoliage(out, targetBufferPosition, levelCelBlock, tbl);
} else {
RenderTile(out, targetBufferPosition, levelCelBlock, getFirstTileMaskLeft(tileType), tbl);
}
}
{
const LevelCelBlock levelCelBlock { pMap->mt[1] };
const TileType tileType = levelCelBlock.type();
const MaskType maskType = getFirstTileMaskRight(tileType);
if (levelCelBlock.hasValue()) {
if (transparency || !foliage || levelCelBlock.type() == TileType::TransparentSquare) {
if (maskType != MaskType::RightFoliage || tileType == TileType::TransparentSquare) {
RenderTile(out, targetBufferPosition + RightFrameDisplacement,
levelCelBlock, maskType, tbl);
}
}
}
if (const LevelCelBlock levelCelBlock { pMap->mt[1] }; levelCelBlock.hasValue()) {
const TileType tileType = levelCelBlock.type();
if (!isFloor || tileType == TileType::TransparentSquare) {
if (isFloor && tileType == TileType::TransparentSquare) {
RenderTileFoliage(out, targetBufferPosition + RightFrameDisplacement, levelCelBlock, tbl);
} else {
RenderTile(out, targetBufferPosition + RightFrameDisplacement,
levelCelBlock, getFirstTileMaskRight(tileType), tbl);
}
}
targetBufferPosition.y -= TILE_HEIGHT;
}
targetBufferPosition.y -= TILE_HEIGHT;
for (uint_fast8_t i = 2, n = MicroTileLen; i < n; i += 2) {
{
@ -597,15 +588,15 @@ void DrawFloorTile(const Surface &out, Point tilePosition, Point targetBufferPos
{
const LevelCelBlock levelCelBlock { DPieceMicros[levelPieceId].mt[0] };
if (levelCelBlock.hasValue()) {
RenderTile(out, targetBufferPosition,
levelCelBlock, MaskType::Solid, tbl);
RenderTileFrame(out, targetBufferPosition, TileType::LeftTriangle,
GetDunFrame(levelCelBlock.frame()), DunFrameTriangleHeight, MaskType::Solid, tbl);
}
}
{
const LevelCelBlock levelCelBlock { DPieceMicros[levelPieceId].mt[1] };
if (levelCelBlock.hasValue()) {
RenderTile(out, targetBufferPosition + RightFrameDisplacement,
levelCelBlock, MaskType::Solid, tbl);
RenderTileFrame(out, targetBufferPosition + RightFrameDisplacement, TileType::RightTriangle,
GetDunFrame(levelCelBlock.frame()), DunFrameTriangleHeight, MaskType::Solid, tbl);
}
}
}

25
Source/levels/dun_tile.hpp

@ -2,6 +2,8 @@
#include <cstdint>
#include "utils/enum_traits.h"
#define TILE_WIDTH 64
#define TILE_HEIGHT 32
@ -100,10 +102,33 @@ struct LevelCelBlock {
}
};
enum class TileProperties : uint8_t {
// clang-format off
None = 0,
Solid = 1 << 0,
BlockLight = 1 << 1,
BlockMissile = 1 << 2,
Transparent = 1 << 3,
TransparentLeft = 1 << 4,
TransparentRight = 1 << 5,
Trap = 1 << 7,
// clang-format on
};
use_enum_as_flags(TileProperties);
struct MICROS {
LevelCelBlock mt[16];
};
/** Width of a tile rendering primitive. */
constexpr int_fast16_t DunFrameWidth = TILE_WIDTH / 2;
/** Height of a tile rendering primitive (except triangles). */
constexpr int_fast16_t DunFrameHeight = TILE_HEIGHT;
constexpr int_fast16_t DunFrameTriangleHeight = 31;
constexpr size_t ReencodedTriangleFrameSize = 544 - 32;
constexpr size_t ReencodedTrapezoidFrameSize = 800 - 16;
} // namespace devilution

20
Source/levels/gendung.cpp

@ -23,6 +23,7 @@
#include "lighting.h"
#include "options.h"
#include "utils/bitset2d.hpp"
#include "utils/log.hpp"
namespace devilution {
@ -499,22 +500,25 @@ void SetDungeonMicros()
size_t tileCount;
std::unique_ptr<uint16_t[]> levelPieces = LoadMinData(tileCount);
ankerl::unordered_dense::map<uint16_t, TileType> frameToTypeMap;
ankerl::unordered_dense::map<uint16_t, DunFrameInfo> 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++) {
for (size_t levelPieceId = 0; levelPieceId < tileCount / blocks; levelPieceId++) {
uint16_t *pieces = &levelPieces[blocks * levelPieceId];
for (uint32_t block = 0; block < blocks; block++) {
const LevelCelBlock levelCelBlock { SDL_SwapLE16(pieces[blocks - 2 + (block & 1) - (block & 0xE)]) };
DPieceMicros[i].mt[block] = levelCelBlock;
DPieceMicros[levelPieceId].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());
frameToTypeMap.emplace_hint(it, levelCelBlock.frame(),
DunFrameInfo { static_cast<uint8_t>(block), levelCelBlock.type(), SOLData[levelPieceId] });
}
}
}
}
std::vector<std::pair<uint16_t, TileType>> frameToTypeList = std::move(frameToTypeMap).extract();
c_sort(frameToTypeList);
std::vector<std::pair<uint16_t, DunFrameInfo>> frameToTypeList = std::move(frameToTypeMap).extract();
c_sort(frameToTypeList, [](const std::pair<uint16_t, DunFrameInfo> &a, const std::pair<uint16_t, DunFrameInfo> &b) {
return a.first < b.first;
});
ReencodeDungeonCels(pDungeonCels, frameToTypeList);
}

29
Source/levels/gendung.h

@ -105,20 +105,6 @@ enum class DungeonFlag : uint8_t {
};
use_enum_as_flags(DungeonFlag);
enum class TileProperties : uint8_t {
// clang-format off
None = 0,
Solid = 1 << 0,
BlockLight = 1 << 1,
BlockMissile = 1 << 2,
Transparent = 1 << 3,
TransparentLeft = 1 << 4,
TransparentRight = 1 << 5,
Trap = 1 << 7,
// clang-format on
};
use_enum_as_flags(TileProperties);
enum _difficulty : uint8_t {
DIFF_NORMAL,
DIFF_NIGHTMARE,
@ -139,10 +125,6 @@ struct MegaTile {
uint16_t micro4;
};
struct MICROS {
LevelCelBlock mt[16];
};
struct ShadowStruct {
uint8_t strig;
uint8_t s1;
@ -377,4 +359,15 @@ bool IsNearThemeRoom(WorldTilePosition position);
void InitLevels();
void FloodTransparencyValues(uint8_t floorID);
DVL_ALWAYS_INLINE const uint8_t *GetDunFrame(uint32_t frame)
{
const auto *pFrameTable = reinterpret_cast<const uint32_t *>(pDungeonCels.get());
return reinterpret_cast<const uint8_t *>(&pDungeonCels[SDL_SwapLE32(pFrameTable[frame])]);
}
DVL_ALWAYS_INLINE const uint8_t *GetDunFrameFoliage(uint32_t frame)
{
return GetDunFrame(frame) + ReencodedTriangleFrameSize;
}
} // namespace devilution

152
Source/levels/reencode_dun_cels.cpp

@ -18,9 +18,6 @@
namespace devilution {
namespace {
constexpr size_t LowerTriangleBloat = 16;
constexpr size_t TriangleBloat = 32;
DVL_ALWAYS_INLINE void ReencodeDungeonCelsLeftTriangleLower(uint8_t *&dst, const uint8_t *&src)
{
unsigned width = 0;
@ -106,28 +103,131 @@ DVL_ALWAYS_INLINE void ReencodeDungeonCelsRightTrapezoid(uint8_t *&dst, const ui
dst += DunFrameWidth * 16;
}
size_t GetReencodedSize(const uint8_t *dungeonCels, std::span<std::pair<uint16_t, TileType>> frames)
DVL_ALWAYS_INLINE void RenderTransparentSquare(uint8_t *dst, const uint8_t *src)
{
for (unsigned i = 0; i < DunFrameHeight; ++i, dst -= 2 * DunFrameWidth) {
uint_fast8_t drawWidth = DunFrameWidth;
while (drawWidth > 0) {
auto v = static_cast<int8_t>(*src++);
if (v > 0) {
std::memcpy(dst, src, v);
src += v;
} else {
v = static_cast<int8_t>(-v);
}
dst += v;
drawWidth -= v;
}
}
}
DVL_ALWAYS_INLINE void ExtractFoliageLeftTriangle(uint8_t *&dst, uint8_t *src)
{
for (int w = 2, y = 31; y >= 16; --y, w += 2, src -= DunFrameWidth) {
std::memcpy(dst, src + (DunFrameWidth - w), w);
std::memset(src + (DunFrameWidth - w), 0, w);
dst += w;
}
for (int w = 30, y = 15; y > 0; --y, w -= 2, src -= DunFrameWidth) {
std::memcpy(dst, src + (DunFrameWidth - w), w);
std::memset(src + (DunFrameWidth - w), 0, w);
dst += w;
}
}
DVL_ALWAYS_INLINE void ExtractFoliageRightTriangle(uint8_t *&dst, uint8_t *src)
{
for (int w = 2, y = 31; y >= 16; --y, w += 2, src -= DunFrameWidth) {
std::memcpy(dst, src, w);
std::memset(src, 0, w);
dst += w;
}
for (int w = 30, y = 15; y > 0; --y, w -= 2, src -= DunFrameWidth) {
std::memcpy(dst, src, w);
std::memset(src, 0, w);
dst += w;
}
}
DVL_ALWAYS_INLINE void ExtractFoliageTransparentSquare(uint8_t *&dst, const uint8_t *src)
{
// The bottom 16 lines are always transparent, foliage only
// applies to the upper half of the tile.
src -= DunFrameHeight * 16;
for (int y = 16; y > 0; --y, src -= 2 * DunFrameWidth) {
unsigned transparentRun = 0;
unsigned solidRun = 0;
for (int x = 0; x < DunFrameWidth; ++x) {
if (*src++ != 0) {
if (transparentRun != 0) {
*dst++ = static_cast<uint8_t>(-static_cast<int8_t>(transparentRun));
transparentRun = 0;
}
++solidRun;
} else {
if (solidRun != 0) {
*dst++ = solidRun;
std::memcpy(dst, src - solidRun, solidRun);
dst += solidRun;
solidRun = 0;
}
++transparentRun;
}
}
if (transparentRun != 0) {
*dst++ = static_cast<uint8_t>(-static_cast<int8_t>(transparentRun));
} else if (solidRun != 0) {
*dst++ = solidRun;
std::memcpy(dst, src - solidRun, solidRun);
dst += solidRun;
}
}
}
DVL_ALWAYS_INLINE void ReencodeFloorWithFoliage(uint8_t *&dst, const uint8_t *&src, TileType tileType)
{
uint8_t surface[DunFrameWidth * DunFrameHeight] {};
uint8_t *surfaceLastLine = &surface[DunFrameWidth * (DunFrameHeight - 1)];
RenderTransparentSquare(surfaceLastLine, src);
if (tileType == TileType::LeftTriangle) {
ExtractFoliageLeftTriangle(dst, surfaceLastLine);
} else {
ExtractFoliageRightTriangle(dst, surfaceLastLine);
}
ExtractFoliageTransparentSquare(dst, surfaceLastLine);
}
size_t GetReencodedSize(const uint8_t *dungeonCels, std::span<std::pair<uint16_t, DunFrameInfo>> frames)
{
size_t result = (2 + frames.size()) * 4;
const auto *srcOffsets = reinterpret_cast<const uint32_t *>(dungeonCels);
for (const auto &[frame, type] : frames) {
for (const auto &[frame, info] : frames) {
size_t frameSize;
switch (type) {
switch (info.type) {
case TileType::TransparentSquare: {
const uint32_t srcFrameBegin = SDL_SwapLE32(srcOffsets[frame]);
const uint32_t srcFrameEnd = SDL_SwapLE32(srcOffsets[frame + 1]);
frameSize = srcFrameEnd - srcFrameBegin;
if (info.isFloor()) {
uint8_t out[1024];
uint8_t *outIt = out;
const uint8_t *src = &dungeonCels[srcFrameBegin];
const TileType newType = info.isFloorLeft() ? TileType::LeftTriangle : TileType::RightTriangle;
ReencodeFloorWithFoliage(outIt, src, newType);
frameSize = outIt - out;
} else {
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;
frameSize = ReencodedTriangleFrameSize;
break;
case TileType::LeftTrapezoid:
case TileType::RightTrapezoid:
frameSize = 800 - LowerTriangleBloat;
frameSize = ReencodedTrapezoidFrameSize;
break;
}
result += frameSize;
@ -137,11 +237,12 @@ size_t GetReencodedSize(const uint8_t *dungeonCels, std::span<std::pair<uint16_t
} // namespace
void ReencodeDungeonCels(std::unique_ptr<std::byte[]> &dungeonCels, std::span<std::pair<uint16_t, TileType>> frames)
void ReencodeDungeonCels(std::unique_ptr<std::byte[]> &dungeonCels, std::span<std::pair<uint16_t, DunFrameInfo>> frames)
{
const auto *srcData = reinterpret_cast<const uint8_t *>(dungeonCels.get());
const auto *srcOffsets = reinterpret_cast<const uint32_t *>(srcData);
int numFoliage = 0;
LogVerbose("Re-encoding dungeon CELs: {} frames, {} bytes",
FormatInteger(SDL_SwapLE32(srcOffsets[0])),
FormatInteger(SDL_SwapLE32(srcOffsets[SDL_SwapLE32(srcOffsets[0]) + 1])));
@ -149,18 +250,24 @@ void ReencodeDungeonCels(std::unique_ptr<std::byte[]> &dungeonCels, std::span<st
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());
WriteLE32(resultPtr, static_cast<uint32_t>(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);
for (const auto &[frame, info] : frames) {
WriteLE32(&resultPtr[static_cast<size_t>(frame * 4)], static_cast<uint32_t>(out - resultPtr));
const uint32_t srcFrameBegin = SDL_SwapLE32(srcOffsets[frame]);
const uint8_t *src = &srcData[srcFrameBegin];
switch (type) {
switch (info.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;
if (info.isFloor()) {
const TileType newType = info.isFloorLeft() ? TileType::LeftTriangle : TileType::RightTriangle;
ReencodeFloorWithFoliage(out, src, newType);
++numFoliage;
} else {
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);
@ -180,12 +287,13 @@ void ReencodeDungeonCels(std::unique_ptr<std::byte[]> &dungeonCels, std::span<st
break;
}
}
WriteLE32(&resultPtr[(1 + frames.size()) * 4], outSize);
WriteLE32(&resultPtr[(1 + frames.size()) * 4], static_cast<uint32_t>(outSize));
const auto *dstOffsets = reinterpret_cast<const uint32_t *>(resultPtr);
LogVerbose(" Re-encoded dungeon CELs: {} frames, {} bytes",
LogVerbose(" Re-encoded dungeon CELs: {} frames, {} bytes. Extracted {} foliage tiles.",
FormatInteger(SDL_SwapLE32(dstOffsets[0])),
FormatInteger(SDL_SwapLE32(dstOffsets[SDL_SwapLE32(dstOffsets[0]) + 1])));
FormatInteger(SDL_SwapLE32(dstOffsets[SDL_SwapLE32(dstOffsets[0]) + 1])),
FormatInteger(numFoliage));
dungeonCels = std::move(result);
}

22
Source/levels/reencode_dun_cels.hpp

@ -10,11 +10,29 @@
namespace devilution {
struct DunFrameInfo {
// Only floor tiles have this.
uint8_t microTileIndex;
TileType type;
TileProperties properties;
[[nodiscard]] bool isFloor() const
{
return !HasAnyOf(properties, TileProperties::Solid)
&& (microTileIndex == 0 || microTileIndex == 1);
}
[[nodiscard]] bool isFloorLeft() const { return microTileIndex == 0; }
};
/**
* @brief Re-encodes dungeon cels, removing redundant padding bytes from triangles and trapezoids.
* @brief Re-encodes dungeon cels.
*
* 1. Removing redundant padding bytes from triangles and trapezoids.
* 2. Extracts floor tile foliage into a triangle with the floor frame and a separate 16-px tall `TransparentSquare`.
*
* This reduces memory usage and simplifies the rendering.
*/
void ReencodeDungeonCels(std::unique_ptr<std::byte[]> &dungeonCels, std::span<std::pair<uint16_t, TileType>> frames);
void ReencodeDungeonCels(std::unique_ptr<std::byte[]> &dungeonCels, std::span<std::pair<uint16_t, DunFrameInfo>> frames);
} // namespace devilution

Loading…
Cancel
Save