Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 72 additions & 4 deletions Source/engine/render/light_render.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#include "engine/render/light_render.hpp"

#include <cassert>
#include <span>
#include <vector>

#include "engine/displacement.hpp"
#include "engine/point.hpp"
#include "levels/dun_tile.hpp"
#include "levels/gendung.h"
#include "lighting.h"
#include "options.h"

Expand Down Expand Up @@ -365,7 +368,12 @@ void BuildLightmap(Point tilePosition, Point targetBufferPosition, uint16_t view
if (!*GetOptions().Graphics.perPixelLighting)
return;

const size_t totalPixels = static_cast<size_t>(viewportWidth) * viewportHeight;
// Since light may need to bleed up to the top of wall tiles,
// expand the buffer space to include the full base diamond of the tallest tile graphics
const uint16_t bufferHeight = viewportHeight + TILE_HEIGHT * (MicroTileLen / 2 + 1);
rows += MicroTileLen + 2;

const size_t totalPixels = static_cast<size_t>(viewportWidth) * bufferHeight;
LightmapBuffer.resize(totalPixels);

// Since rendering occurs in cells between quads,
Expand Down Expand Up @@ -402,7 +410,7 @@ void BuildLightmap(Point tilePosition, Point targetBufferPosition, uint16_t view
continue;
if (lightLevel < minLight)
break;
RenderCell(quad, center0, lightLevel, lightmap, viewportWidth, viewportHeight);
RenderCell(quad, center0, lightLevel, lightmap, viewportWidth, bufferHeight);
}
}

Expand All @@ -426,9 +434,13 @@ void BuildLightmap(Point tilePosition, Point targetBufferPosition, uint16_t view

} // namespace

Lightmap::Lightmap(const uint8_t *outBuffer, const uint8_t *lightmapBuffer, const uint8_t *lightTables, size_t lightTableSize)
Lightmap::Lightmap(const uint8_t *outBuffer, uint16_t outPitch,
std::span<const uint8_t> lightmapBuffer, uint16_t lightmapPitch,
const uint8_t *lightTables, size_t lightTableSize)
: outBuffer(outBuffer)
, outPitch(outPitch)
, lightmapBuffer(lightmapBuffer)
, lightmapPitch(lightmapPitch)
, lightTables(lightTables)
, lightTableSize(lightTableSize)
{
Expand All @@ -439,7 +451,63 @@ Lightmap Lightmap::build(Point tilePosition, Point targetBufferPosition,
const uint8_t *outBuffer, const uint8_t *lightTables, size_t lightTableSize)
{
BuildLightmap(tilePosition, targetBufferPosition, viewportWidth, viewportHeight, rows, columns);
return Lightmap(outBuffer, LightmapBuffer.data(), lightTables, lightTableSize);
return Lightmap(outBuffer, LightmapBuffer, gnScreenWidth, lightTables, lightTableSize);
}

Lightmap Lightmap::bleedUp(const Lightmap &source, Point targetBufferPosition, std::span<uint8_t> lightmapBuffer)
{
assert(lightmapBuffer.size() >= TILE_WIDTH * TILE_HEIGHT);

if (!*GetOptions().Graphics.perPixelLighting)
return source;

const int sourceHeight = static_cast<int>(source.lightmapBuffer.size() / source.lightmapPitch);
const int clipLeft = std::max(0, -targetBufferPosition.x);
const int clipTop = std::max(0, -(targetBufferPosition.y - TILE_HEIGHT + 1));
const int clipRight = std::max(0, targetBufferPosition.x + TILE_WIDTH - source.outPitch);
const int clipBottom = std::max(0, targetBufferPosition.y - sourceHeight + 1);

// Nothing we can do if the tile is completely outside the bounds of the lightmap
if (clipLeft + clipRight >= TILE_WIDTH)
return source;
if (clipTop + clipBottom >= TILE_HEIGHT)
return source;

const uint16_t lightmapPitch = std::max(0, TILE_WIDTH - clipLeft - clipRight);
const uint16_t lightmapHeight = TILE_HEIGHT - clipTop - clipBottom;

// Find the left edge of the last row in the tile
const int outOffset = std::max(0, (targetBufferPosition.y - clipBottom) * source.outPitch + targetBufferPosition.x + clipLeft);
const uint8_t *outLoc = source.outBuffer + outOffset;
const uint8_t *outBuffer = outLoc - (lightmapHeight - 1) * source.outPitch;

// Start copying bytes from the bottom row of the tile
const uint8_t *src = source.getLightingAt(outLoc);
uint8_t *dst = lightmapBuffer.data() + (lightmapHeight - 1) * lightmapPitch;

int rowCount = clipBottom;
while (src >= source.lightmapBuffer.data() && dst >= lightmapBuffer.data()) {
const int bleed = std::max(0, (rowCount - TILE_HEIGHT / 2) * 2);
const int lightOffset = std::max(bleed, clipLeft) - clipLeft;
const int lightLength = std::max(0, TILE_WIDTH - clipLeft - std::max(bleed, clipRight) - lightOffset);

// Bleed pixels up by copying data from the row below this one
if (rowCount > clipBottom && lightLength < lightmapPitch)
memcpy(dst, dst + lightmapPitch, lightmapPitch);

// Copy data from the source lightmap between the top edge of the base diamond
assert(dst + lightOffset + lightLength <= lightmapBuffer.data() + TILE_WIDTH * TILE_HEIGHT);
assert(src + lightOffset + lightLength <= LightmapBuffer.data() + LightmapBuffer.size());
memcpy(dst + lightOffset, src + lightOffset, lightLength);

src -= source.lightmapPitch;
dst -= lightmapPitch;
rowCount++;
}

return Lightmap(outBuffer, source.outPitch,
lightmapBuffer, lightmapPitch,
source.lightTables, source.lightTableSize);
}

} // namespace devilution
32 changes: 29 additions & 3 deletions Source/engine/render/light_render.hpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
#pragma once

#include <span>

#include "engine/point.hpp"

namespace devilution {

class Lightmap {
public:
explicit Lightmap(const uint8_t *outBuffer, const uint8_t *lightmapBuffer, const uint8_t *lightTables, size_t lightTableSize);
explicit Lightmap(const uint8_t *outBuffer, std::span<const uint8_t> lightmapBuffer, uint16_t pitch, const uint8_t *lightTables, size_t lightTableSize)
: Lightmap(outBuffer, pitch, lightmapBuffer, pitch, lightTables, lightTableSize)
{
}

explicit Lightmap(const uint8_t *outBuffer, uint16_t outPitch,
std::span<const uint8_t> lightmapBuffer, uint16_t lightmapPitch,
const uint8_t *lightTables, size_t lightTableSize);

uint8_t adjustColor(uint8_t color, uint8_t lightLevel) const
{
Expand All @@ -16,16 +25,33 @@ class Lightmap {

const uint8_t *getLightingAt(const uint8_t *outLoc) const
{
return lightmapBuffer + (outLoc - outBuffer);
const ptrdiff_t outDist = outLoc - outBuffer;
const ptrdiff_t rowOffset = outDist % outPitch;

if (outDist < 0) {
// In order to support "bleed up" for wall tiles,
// reuse the first row whenever outLoc is out of bounds
const int modOffset = rowOffset < 0 ? outPitch : 0;
return lightmapBuffer.data() + rowOffset + modOffset;
}

const ptrdiff_t row = outDist / outPitch;
return lightmapBuffer.data() + row * lightmapPitch + rowOffset;
}

static Lightmap build(Point tilePosition, Point targetBufferPosition,
int viewportWidth, int viewportHeight, int rows, int columns,
const uint8_t *outBuffer, const uint8_t *lightTables, size_t lightTableSize);

static Lightmap bleedUp(const Lightmap &source, Point targetBufferPosition, std::span<uint8_t> lightmapBuffer);

private:
const uint8_t *outBuffer;
const uint8_t *lightmapBuffer;
const uint16_t outPitch;

std::span<const uint8_t> lightmapBuffer;
const uint16_t lightmapPitch;

const uint8_t *lightTables;
const size_t lightTableSize;
};
Expand Down
29 changes: 19 additions & 10 deletions Source/engine/render/scrollrt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,26 +536,30 @@ void DrawCell(const Surface &out, const Lightmap lightmap, Point tilePosition, P
return MaskType::Solid;
};

// Create a special lightmap buffer to bleed light up walls
uint8_t lightmapBuffer[TILE_WIDTH * TILE_HEIGHT];
Lightmap bleedLightmap = Lightmap::bleedUp(lightmap, targetBufferPosition, lightmapBuffer);

// 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, lightmap, targetBufferPosition, levelCelBlock, foliageTbl);
RenderTileFoliage(out, bleedLightmap, targetBufferPosition, levelCelBlock, foliageTbl);
} else {
RenderTile(out, lightmap, targetBufferPosition, levelCelBlock, getFirstTileMaskLeft(tileType), tbl);
RenderTile(out, bleedLightmap, targetBufferPosition, levelCelBlock, getFirstTileMaskLeft(tileType), 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, lightmap, targetBufferPosition + RightFrameDisplacement, levelCelBlock, foliageTbl);
RenderTileFoliage(out, bleedLightmap, targetBufferPosition + RightFrameDisplacement, levelCelBlock, foliageTbl);
} else {
RenderTile(out, lightmap, targetBufferPosition + RightFrameDisplacement,
RenderTile(out, bleedLightmap, targetBufferPosition + RightFrameDisplacement,
levelCelBlock, getFirstTileMaskRight(tileType), tbl);
}
}
Expand All @@ -566,15 +570,15 @@ void DrawCell(const Surface &out, const Lightmap lightmap, Point tilePosition, P
{
const LevelCelBlock levelCelBlock { pMap->mt[i] };
if (levelCelBlock.hasValue()) {
RenderTile(out, lightmap, targetBufferPosition,
RenderTile(out, bleedLightmap, targetBufferPosition,
levelCelBlock,
transparency ? MaskType::Transparent : MaskType::Solid, foliageTbl);
}
}
{
const LevelCelBlock levelCelBlock { pMap->mt[i + 1] };
if (levelCelBlock.hasValue()) {
RenderTile(out, lightmap, targetBufferPosition + RightFrameDisplacement,
RenderTile(out, bleedLightmap, targetBufferPosition + RightFrameDisplacement,
levelCelBlock,
transparency ? MaskType::Transparent : MaskType::Solid, foliageTbl);
}
Expand Down Expand Up @@ -839,10 +843,15 @@ void DrawDungeon(const Surface &out, const Lightmap &lightmap, Point tilePositio
// Turn transparency off here for debugging
transparency = transparency && (SDL_GetModState() & KMOD_ALT) == 0;
#endif
if (perPixelLighting && transparency) {
ClxDrawBlendedWithLightmap(out, targetBufferPosition, (*pSpecialCels)[bArch], lightmap);
} else if (perPixelLighting) {
ClxDrawWithLightmap(out, targetBufferPosition, (*pSpecialCels)[bArch], lightmap);
if (perPixelLighting) {
// Create a special lightmap buffer to bleed light up walls
uint8_t lightmapBuffer[TILE_WIDTH * TILE_HEIGHT];
Lightmap bleedLightmap = Lightmap::bleedUp(lightmap, targetBufferPosition, lightmapBuffer);

if (transparency)
ClxDrawBlendedWithLightmap(out, targetBufferPosition, (*pSpecialCels)[bArch], bleedLightmap);
else
ClxDrawWithLightmap(out, targetBufferPosition, (*pSpecialCels)[bArch], bleedLightmap);
} else if (transparency) {
ClxDrawLightBlended(out, targetBufferPosition, (*pSpecialCels)[bArch], lightTableIndex);
} else {
Expand Down
2 changes: 1 addition & 1 deletion test/dun_render_benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ void InitOnce()
void RunForTileMaskLight(benchmark::State &state, TileType tileType, MaskType maskType, const uint8_t *lightTable)
{
Surface out = Surface(SdlSurface.get());
Lightmap lightmap(nullptr, nullptr, nullptr, 0);
Lightmap lightmap(nullptr, {}, 1, nullptr, 0);
size_t numItemsProcessed = 0;
const std::span<const LevelCelBlock> tiles = Tiles[tileType];
for (auto _ : state) {
Expand Down