Skip to content

Commit

Permalink
Bleed per-pixel light up when rendering walls
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenCWills authored and AJenbo committed Mar 7, 2025
1 parent e06d88a commit b83f006
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 18 deletions.
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 @@ -367,7 +370,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 @@ -404,7 +412,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 @@ -428,9 +436,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 @@ -441,7 +453,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

0 comments on commit b83f006

Please sign in to comment.