diff --git a/Source/engine/render/dun_render.cpp b/Source/engine/render/dun_render.cpp index cf7ccbc46cd..3baf8dc8668 100644 --- a/Source/engine/render/dun_render.cpp +++ b/Source/engine/render/dun_render.cpp @@ -17,11 +17,10 @@ #include #include #include +#include #include "engine/render/blit_impl.hpp" #include "levels/dun_tile.hpp" -#include "lighting.h" -#include "options.h" #include "utils/attributes.h" #ifdef DEBUG_STR #include "engine/render/text_render.hpp" @@ -275,16 +274,6 @@ DVL_ALWAYS_INLINE Clip CalculateClip(int_fast16_t x, int_fast16_t y, int_fast16_ return clip; } -DVL_ALWAYS_INLINE bool IsFullyDark(const uint8_t *DVL_RESTRICT tbl) -{ - return tbl == FullyDarkLightTable; -} - -DVL_ALWAYS_INLINE bool IsFullyLit(const uint8_t *DVL_RESTRICT tbl) -{ - return tbl == FullyLitLightTable; -} - template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderSquareFull(uint8_t *DVL_RESTRICT dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { @@ -1138,4 +1127,9 @@ void world_draw_black_tile(const Surface &out, int sx, int sy) } } +void DunTriangleTileApplyTrans(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, const uint8_t *tbl) +{ + BlitPixelsWithMap(dst, src, ReencodedTriangleFrameSize, tbl); +} + } // namespace devilution diff --git a/Source/engine/render/dun_render.hpp b/Source/engine/render/dun_render.hpp index 98ab8c101db..1fa984955ae 100644 --- a/Source/engine/render/dun_render.hpp +++ b/Source/engine/render/dun_render.hpp @@ -11,6 +11,7 @@ #include "engine/surface.hpp" #include "levels/dun_tile.hpp" #include "levels/gendung.h" +#include "lighting.h" #include "utils/attributes.h" // #define DUN_RENDER_STATS @@ -145,11 +146,16 @@ DVL_ALWAYS_INLINE void RenderTileFoliage(const Surface &out, const Point &positi } /** - * @brief Render a black 64x31 tile ◆ + * @brief Renders a black 64x31 tile ◆ * @param out Target buffer * @param sx Target buffer coordinate (left corner of the tile) * @param sy Target buffer coordinate (bottom corner of the tile) */ void world_draw_black_tile(const Surface &out, int sx, int sy); +/** + * @brief Writes a tile with the color swaps from `tbl` to `dst`. + */ +void DunTriangleTileApplyTrans(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, const uint8_t *tbl); + } // namespace devilution diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index aca653cf287..a2012c0cfec 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/Source/engine/render/scrollrt.cpp @@ -5,6 +5,8 @@ */ #include "engine/render/scrollrt.h" +#include +#include #include #include @@ -548,59 +550,17 @@ void DrawCell(const Surface &out, Point tilePosition, Point targetBufferPosition targetBufferPosition.y -= TILE_HEIGHT; for (uint_fast8_t i = 2, n = MicroTileLen; i < n; i += 2) { - { - const LevelCelBlock levelCelBlock { pMap->mt[i] }; - if (levelCelBlock.hasValue()) { - RenderTile(out, targetBufferPosition, - levelCelBlock, - transparency ? MaskType::Transparent : MaskType::Solid, tbl); - } + if (const LevelCelBlock levelCelBlock { pMap->mt[i] }; levelCelBlock.hasValue()) { + RenderTile(out, targetBufferPosition, levelCelBlock, transparency ? MaskType::Transparent : MaskType::Solid, tbl); } - { - const LevelCelBlock levelCelBlock { pMap->mt[i + 1] }; - if (levelCelBlock.hasValue()) { - RenderTile(out, targetBufferPosition + RightFrameDisplacement, - levelCelBlock, - transparency ? MaskType::Transparent : MaskType::Solid, tbl); - } + if (const LevelCelBlock levelCelBlock { pMap->mt[i + 1] }; levelCelBlock.hasValue()) { + RenderTile(out, targetBufferPosition + RightFrameDisplacement, + levelCelBlock, transparency ? MaskType::Transparent : MaskType::Solid, tbl); } targetBufferPosition.y -= TILE_HEIGHT; } } -/** - * @brief Render a floor tile. - * @param out Target buffer - * @param tilePosition dPiece coordinates - * @param targetBufferPosition Target buffer coordinate - */ -void DrawFloorTile(const Surface &out, Point tilePosition, Point targetBufferPosition) -{ - const int lightTableIndex = dLight[tilePosition.x][tilePosition.y]; - - const uint8_t *tbl = LightTables[lightTableIndex].data(); -#ifdef _DEBUG - if (DebugPath && MyPlayer->IsPositionInPath(tilePosition)) - tbl = GetPauseTRN(); -#endif - - const uint16_t levelPieceId = dPiece[tilePosition.x][tilePosition.y]; - { - const LevelCelBlock levelCelBlock { DPieceMicros[levelPieceId].mt[0] }; - if (levelCelBlock.hasValue()) { - RenderTileFrame(out, targetBufferPosition, TileType::LeftTriangle, - GetDunFrame(levelCelBlock.frame()), DunFrameTriangleHeight, MaskType::Solid, tbl); - } - } - { - const LevelCelBlock levelCelBlock { DPieceMicros[levelPieceId].mt[1] }; - if (levelCelBlock.hasValue()) { - RenderTileFrame(out, targetBufferPosition + RightFrameDisplacement, TileType::RightTriangle, - GetDunFrame(levelCelBlock.frame()), DunFrameTriangleHeight, MaskType::Solid, tbl); - } - } -} - /** * @brief Draw item for a given tile * @param out Output buffer @@ -829,8 +789,103 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit } } +constexpr int MinFloorTileToBakeLight = 2; +constexpr size_t FloorTilesPerLightBufferSize = 96; +constexpr size_t FloorBufferNumLightingLevels = NumLightingLevels +#ifdef DEBUG_ + + 1 +#endif + ; + +struct FloorTilesBufferEntry { + PointOf targetBufferPosition; + uint16_t levelPieceId; + + bool operator<(const FloorTilesBufferEntry &other) const + { + return levelPieceId < other.levelPieceId; + } +}; + +struct FloorTilesBuffer { + using BufferForLightLevel = StaticVector; + + std::array perLightLevel; +}; + +const uint8_t *GetLightTableFromIndex(uint8_t lightTableIndex) +{ + return +#ifdef _DEBUG + lightTableIndex == NumLightingLevels + ? GetPauseTRN() + : +#endif + LightTables[lightTableIndex].data(); +} + +void DrawIdenticalFloorTiles(const Surface &out, const uint8_t *lightTable, FloorTilesBufferEntry *begin, FloorTilesBufferEntry *end) +{ + if (begin == end) return; + const uint16_t levelPieceId = begin->levelPieceId; + if (begin + MinFloorTileToBakeLight >= end) { + { + const uint8_t *src = GetDunFrame(DPieceMicros[levelPieceId].mt[0].frame()); + for (FloorTilesBufferEntry *it = begin; it != end; ++it) { + RenderTileFrame(out, it->targetBufferPosition, TileType::LeftTriangle, + src, DunFrameTriangleHeight, MaskType::Solid, lightTable); + } + } + { + const uint8_t *src = GetDunFrame(DPieceMicros[levelPieceId].mt[1].frame()); + for (FloorTilesBufferEntry *it = begin; it != end; ++it) { + RenderTileFrame(out, it->targetBufferPosition + RightFrameDisplacement, TileType::RightTriangle, + src, DunFrameTriangleHeight, MaskType::Solid, lightTable); + } + } + } else { + // Doesn't matter what `FullyLitLightTable` light table points to, as long as it's not `nullptr`. + uint8_t *fullyLitBefore = FullyLitLightTable; + FullyLitLightTable = LightTables[0].data(); + + uint8_t bakedLightTile[ReencodedTriangleFrameSize]; + + DunTriangleTileApplyTrans(bakedLightTile, GetDunFrame(DPieceMicros[levelPieceId].mt[0].frame()), lightTable); + for (FloorTilesBufferEntry *it = begin; it != end; ++it) { + RenderTileFrame(out, it->targetBufferPosition, TileType::LeftTriangle, + bakedLightTile, DunFrameTriangleHeight, MaskType::Solid, FullyLitLightTable); + } + DunTriangleTileApplyTrans(bakedLightTile, GetDunFrame(DPieceMicros[levelPieceId].mt[1].frame()), lightTable); + for (FloorTilesBufferEntry *it = begin; it != end; ++it) { + RenderTileFrame(out, it->targetBufferPosition + RightFrameDisplacement, TileType::RightTriangle, + bakedLightTile, DunFrameTriangleHeight, MaskType::Solid, FullyLitLightTable); + } + + FullyLitLightTable = fullyLitBefore; + } +} + +void DrawFloorBuffer(const Surface &out, const uint8_t *lightTable, FloorTilesBuffer::BufferForLightLevel &buffer) +{ + if (buffer.size() > 2) std::sort(buffer.begin(), buffer.end()); + uint16_t prevLevelPieceId = buffer[0].levelPieceId; + size_t prevBegin = 0; + FloorTilesBufferEntry *arr = buffer.begin(); + for (size_t i = 1, n = buffer.size(); i < n; ++i) { + const uint16_t frame = buffer[i].levelPieceId; + if (prevLevelPieceId != frame) { + DrawIdenticalFloorTiles(out, lightTable, &arr[prevBegin], &arr[i]); + prevLevelPieceId = frame; + prevBegin = i; + } + } + if (prevBegin != buffer.size()) { + DrawIdenticalFloorTiles(out, lightTable, &arr[prevBegin], buffer.end()); + } +} + /** - * @brief Render a row of tiles + * @brief Renders the floor tiles * @param out Buffer to render to * @param tilePosition dPiece coordinates * @param targetBufferPosition Target buffer coordinates @@ -839,32 +894,61 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit */ void DrawFloor(const Surface &out, Point tilePosition, Point targetBufferPosition, int rows, int columns) { + FloorTilesBuffer tilesBuffer; + PointOf position = targetBufferPosition; for (int i = 0; i < rows; i++) { - for (int j = 0; j < columns; j++, tilePosition += Direction::East, targetBufferPosition.x += TILE_WIDTH) { + for (int j = 0; j < columns; ++j, tilePosition += Direction::East, position.x += TILE_WIDTH) { if (!InDungeonBounds(tilePosition)) { - world_draw_black_tile(out, targetBufferPosition.x, targetBufferPosition.y); + world_draw_black_tile(out, position.x, position.y); continue; } - if (IsFloor(tilePosition)) { - DrawFloorTile(out, tilePosition, targetBufferPosition); + const uint16_t levelPieceId = dPiece[tilePosition.x][tilePosition.y]; + assert(DPieceMicros[levelPieceId].mt[0].hasValue() == DPieceMicros[levelPieceId].mt[1].hasValue()); + if (!DPieceMicros[levelPieceId].mt[0].hasValue()) continue; + + const uint8_t lightTableIndex = +#ifdef _DEBUG + DebugPath && MyPlayer->IsPositionInPath(tilePosition) + ? NumLightingLevels + : +#endif + dLight[tilePosition.x][tilePosition.y]; + const auto *lightTable = GetLightTableFromIndex(lightTableIndex); + if (IsFullyLit(lightTable) || IsFullyDark(lightTable)) { + RenderTileFrame(out, position, TileType::LeftTriangle, + GetDunFrame(DPieceMicros[levelPieceId].mt[0].frame()), DunFrameTriangleHeight, MaskType::Solid, lightTable); + RenderTileFrame(out, position + RightFrameDisplacement, TileType::RightTriangle, + GetDunFrame(DPieceMicros[levelPieceId].mt[1].frame()), DunFrameTriangleHeight, MaskType::Solid, lightTable); + } else { + FloorTilesBuffer::BufferForLightLevel &buffer = tilesBuffer.perLightLevel[lightTableIndex]; + if (buffer.size() == FloorTilesPerLightBufferSize) { + DrawFloorBuffer(out, lightTable, buffer); + buffer.clear(); + } + buffer.emplace_back(FloorTilesBufferEntry { position, levelPieceId }); } } + // Return to start of row tilePosition += Displacement(Direction::West) * columns; - targetBufferPosition.x -= columns * TILE_WIDTH; + position.x -= columns * TILE_WIDTH; // Jump to next row - targetBufferPosition.y += TILE_HEIGHT / 2; + position.y += TILE_HEIGHT / 2; if ((i & 1) != 0) { tilePosition.x++; columns--; - targetBufferPosition.x += TILE_WIDTH / 2; + position.x += TILE_WIDTH / 2; } else { tilePosition.y++; columns++; - targetBufferPosition.x -= TILE_WIDTH / 2; + position.x -= TILE_WIDTH / 2; } } + for (uint8_t lightTableIndex = 0; lightTableIndex < FloorBufferNumLightingLevels; ++lightTableIndex) { + FloorTilesBuffer::BufferForLightLevel &buffer = tilesBuffer.perLightLevel[lightTableIndex]; + if (!buffer.empty()) DrawFloorBuffer(out, GetLightTableFromIndex(lightTableIndex), buffer); + } } /** diff --git a/Source/lighting.h b/Source/lighting.h index fbef5c92330..b78a567ae9a 100644 --- a/Source/lighting.h +++ b/Source/lighting.h @@ -87,4 +87,7 @@ void lighting_color_cycling(); constexpr int MaxCrawlRadius = 18; +DVL_ALWAYS_INLINE bool IsFullyDark(const uint8_t *tbl) { return tbl == FullyDarkLightTable; } +DVL_ALWAYS_INLINE bool IsFullyLit(const uint8_t *tbl) { return tbl == FullyLitLightTable; } + } // namespace devilution