Skip to content

Commit

Permalink
Optimize floor drawing
Browse files Browse the repository at this point in the history
  • Loading branch information
glebm committed Aug 26, 2024
1 parent f60659b commit 9233786
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 81 deletions.
24 changes: 9 additions & 15 deletions Source/engine/render/dun_render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstring>

#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"
Expand Down Expand Up @@ -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 <LightType Light, bool Transparent>
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)
{
Expand Down Expand Up @@ -438,7 +427,7 @@ struct DiamondClipY {
};

template <int_fast16_t UpperHeight = TriangleUpperHeight>
DVL_ALWAYS_INLINE DiamondClipY CalculateDiamondClipY(const Clip &clip)
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT DiamondClipY CalculateDiamondClipY(const Clip &clip)
{
DiamondClipY result;
if (clip.bottom > LowerHeight) {
Expand All @@ -457,12 +446,12 @@ DVL_ALWAYS_INLINE DiamondClipY CalculateDiamondClipY(const Clip &clip)
return result;
}

DVL_ALWAYS_INLINE std::size_t CalculateTriangleSourceSkipLowerBottom(int_fast16_t numLines)
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT std::size_t CalculateTriangleSourceSkipLowerBottom(int_fast16_t numLines)
{
return XStep * numLines * (numLines + 1) / 2;
}

DVL_ALWAYS_INLINE std::size_t CalculateTriangleSourceSkipUpperBottom(int_fast16_t numLines)
DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT std::size_t CalculateTriangleSourceSkipUpperBottom(int_fast16_t numLines)
{
return 2 * TriangleUpperHeight * numLines - numLines * (numLines - 1);
}
Expand Down Expand Up @@ -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
8 changes: 7 additions & 1 deletion Source/engine/render/dun_render.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
215 changes: 150 additions & 65 deletions Source/engine/render/scrollrt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*/
#include "engine/render/scrollrt.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>

Expand All @@ -19,6 +21,7 @@
#include "doom.h"
#include "engine/backbuffer_state.hpp"
#include "engine/dx.h"
#include "engine/point.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/dun_render.hpp"
#include "engine/render/text_render.hpp"
Expand Down Expand Up @@ -77,6 +80,8 @@ bool frameflag;

namespace {

constexpr auto RightFrameDisplacement = Displacement { DunFrameWidth, 0 };

[[nodiscard]] DVL_ALWAYS_INLINE bool IsFloor(Point tilePosition)
{
return !TileHasAny(tilePosition, TileProperties::Solid);
Expand Down Expand Up @@ -534,69 +539,27 @@ void DrawCell(const Surface &out, Point tilePosition, Point targetBufferPosition
const TileType tileType = levelCelBlock.type();
if (!isFloor || tileType == TileType::TransparentSquare) {
if (isFloor && tileType == TileType::TransparentSquare) {
RenderTileFoliage(out, targetBufferPosition + Displacement { TILE_WIDTH / 2, 0 }, levelCelBlock, tbl);
RenderTileFoliage(out, targetBufferPosition + RightFrameDisplacement, levelCelBlock, tbl);
} else {
RenderTile(out, targetBufferPosition + Displacement { TILE_WIDTH / 2, 0 },
RenderTile(out, targetBufferPosition + RightFrameDisplacement,
levelCelBlock, getFirstTileMaskRight(tileType), tbl);
}
}
}
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 + Displacement { TILE_WIDTH / 2, 0 },
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 + Displacement { TILE_WIDTH / 2, 0 }, TileType::RightTriangle,
GetDunFrame(levelCelBlock.frame()), DunFrameTriangleHeight, MaskType::Solid, tbl);
}
}
}

/**
* @brief Draw item for a given tile
* @param out Output buffer
Expand Down Expand Up @@ -825,8 +788,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<int16_t> targetBufferPosition;
uint16_t levelPieceId;

bool operator<(const FloorTilesBufferEntry &other) const
{
return levelPieceId < other.levelPieceId;
}
};

struct FloorTilesBuffer {
using BufferForLightLevel = StaticVector<FloorTilesBufferEntry, FloorTilesPerLightBufferSize>;

std::array<BufferForLightLevel, FloorBufferNumLightingLevels> 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
Expand All @@ -835,34 +893,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<int16_t> position = targetBufferPosition;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
if (InDungeonBounds(tilePosition)) {
if (IsFloor(tilePosition)) {
DrawFloorTile(out, tilePosition, targetBufferPosition);
}
for (int j = 0; j < columns; ++j, tilePosition += Direction::East, position.x += TILE_WIDTH) {
if (!InDungeonBounds(tilePosition)) {
world_draw_black_tile(out, position.x, position.y);
continue;
}
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 {
world_draw_black_tile(out, targetBufferPosition.x, targetBufferPosition.y);
FloorTilesBuffer::BufferForLightLevel &buffer = tilesBuffer.perLightLevel[lightTableIndex];
if (buffer.size() == FloorTilesPerLightBufferSize) {
DrawFloorBuffer(out, lightTable, buffer);
buffer.clear();
}
buffer.emplace_back(FloorTilesBufferEntry { position, levelPieceId });
}
tilePosition += Direction::East;
targetBufferPosition.x += TILE_WIDTH;
}

// 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);
}
}

/**
Expand Down Expand Up @@ -985,7 +1070,7 @@ void Zoom(const Surface &out)

Displacement tileOffset;
Displacement tileShift;
int tileColums;
int tileColumns;
int tileRows;

void CalcFirstTilePosition(Point &position, Displacement &offset)
Expand Down Expand Up @@ -1046,7 +1131,7 @@ void DrawGame(const Surface &fullOut, Point position, Displacement offset)
? fullOut.subregionY(0, gnViewportHeight)
: fullOut.subregionY(0, (gnViewportHeight + 1) / 2);

int columns = tileColums;
int columns = tileColumns;
int rows = tileRows;

// Skip rendering parts covered by the panels
Expand Down Expand Up @@ -1509,7 +1594,7 @@ void CalcViewportGeometry()
const int viewportHeight = GetViewportHeight() / zoomFactor;
const Point renderStart = startPosition - Displacement { TILE_WIDTH / 2, TILE_HEIGHT / 2 };
tileRows = (viewportHeight - renderStart.y + TILE_HEIGHT / 2 - 1) / (TILE_HEIGHT / 2);
tileColums = (screenWidth - renderStart.x + TILE_WIDTH - 1) / TILE_WIDTH;
tileColumns = (screenWidth - renderStart.x + TILE_WIDTH - 1) / TILE_WIDTH;
}

Point GetScreenPosition(Point tile)
Expand Down
Loading

0 comments on commit 9233786

Please sign in to comment.