Skip to content

Commit b83f006

Browse files
StephenCWillsAJenbo
authored andcommitted
Bleed per-pixel light up when rendering walls
1 parent e06d88a commit b83f006

File tree

4 files changed

+121
-18
lines changed

4 files changed

+121
-18
lines changed

Source/engine/render/light_render.cpp

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
#include "engine/render/light_render.hpp"
22

3+
#include <cassert>
4+
#include <span>
35
#include <vector>
46

57
#include "engine/displacement.hpp"
68
#include "engine/point.hpp"
79
#include "levels/dun_tile.hpp"
10+
#include "levels/gendung.h"
811
#include "lighting.h"
912
#include "options.h"
1013

@@ -367,7 +370,12 @@ void BuildLightmap(Point tilePosition, Point targetBufferPosition, uint16_t view
367370
if (!*GetOptions().Graphics.perPixelLighting)
368371
return;
369372

370-
const size_t totalPixels = static_cast<size_t>(viewportWidth) * viewportHeight;
373+
// Since light may need to bleed up to the top of wall tiles,
374+
// expand the buffer space to include the full base diamond of the tallest tile graphics
375+
const uint16_t bufferHeight = viewportHeight + TILE_HEIGHT * (MicroTileLen / 2 + 1);
376+
rows += MicroTileLen + 2;
377+
378+
const size_t totalPixels = static_cast<size_t>(viewportWidth) * bufferHeight;
371379
LightmapBuffer.resize(totalPixels);
372380

373381
// Since rendering occurs in cells between quads,
@@ -404,7 +412,7 @@ void BuildLightmap(Point tilePosition, Point targetBufferPosition, uint16_t view
404412
continue;
405413
if (lightLevel < minLight)
406414
break;
407-
RenderCell(quad, center0, lightLevel, lightmap, viewportWidth, viewportHeight);
415+
RenderCell(quad, center0, lightLevel, lightmap, viewportWidth, bufferHeight);
408416
}
409417
}
410418

@@ -428,9 +436,13 @@ void BuildLightmap(Point tilePosition, Point targetBufferPosition, uint16_t view
428436

429437
} // namespace
430438

431-
Lightmap::Lightmap(const uint8_t *outBuffer, const uint8_t *lightmapBuffer, const uint8_t *lightTables, size_t lightTableSize)
439+
Lightmap::Lightmap(const uint8_t *outBuffer, uint16_t outPitch,
440+
std::span<const uint8_t> lightmapBuffer, uint16_t lightmapPitch,
441+
const uint8_t *lightTables, size_t lightTableSize)
432442
: outBuffer(outBuffer)
443+
, outPitch(outPitch)
433444
, lightmapBuffer(lightmapBuffer)
445+
, lightmapPitch(lightmapPitch)
434446
, lightTables(lightTables)
435447
, lightTableSize(lightTableSize)
436448
{
@@ -441,7 +453,63 @@ Lightmap Lightmap::build(Point tilePosition, Point targetBufferPosition,
441453
const uint8_t *outBuffer, const uint8_t *lightTables, size_t lightTableSize)
442454
{
443455
BuildLightmap(tilePosition, targetBufferPosition, viewportWidth, viewportHeight, rows, columns);
444-
return Lightmap(outBuffer, LightmapBuffer.data(), lightTables, lightTableSize);
456+
return Lightmap(outBuffer, LightmapBuffer, gnScreenWidth, lightTables, lightTableSize);
457+
}
458+
459+
Lightmap Lightmap::bleedUp(const Lightmap &source, Point targetBufferPosition, std::span<uint8_t> lightmapBuffer)
460+
{
461+
assert(lightmapBuffer.size() >= TILE_WIDTH * TILE_HEIGHT);
462+
463+
if (!*GetOptions().Graphics.perPixelLighting)
464+
return source;
465+
466+
const int sourceHeight = static_cast<int>(source.lightmapBuffer.size() / source.lightmapPitch);
467+
const int clipLeft = std::max(0, -targetBufferPosition.x);
468+
const int clipTop = std::max(0, -(targetBufferPosition.y - TILE_HEIGHT + 1));
469+
const int clipRight = std::max(0, targetBufferPosition.x + TILE_WIDTH - source.outPitch);
470+
const int clipBottom = std::max(0, targetBufferPosition.y - sourceHeight + 1);
471+
472+
// Nothing we can do if the tile is completely outside the bounds of the lightmap
473+
if (clipLeft + clipRight >= TILE_WIDTH)
474+
return source;
475+
if (clipTop + clipBottom >= TILE_HEIGHT)
476+
return source;
477+
478+
const uint16_t lightmapPitch = std::max(0, TILE_WIDTH - clipLeft - clipRight);
479+
const uint16_t lightmapHeight = TILE_HEIGHT - clipTop - clipBottom;
480+
481+
// Find the left edge of the last row in the tile
482+
const int outOffset = std::max(0, (targetBufferPosition.y - clipBottom) * source.outPitch + targetBufferPosition.x + clipLeft);
483+
const uint8_t *outLoc = source.outBuffer + outOffset;
484+
const uint8_t *outBuffer = outLoc - (lightmapHeight - 1) * source.outPitch;
485+
486+
// Start copying bytes from the bottom row of the tile
487+
const uint8_t *src = source.getLightingAt(outLoc);
488+
uint8_t *dst = lightmapBuffer.data() + (lightmapHeight - 1) * lightmapPitch;
489+
490+
int rowCount = clipBottom;
491+
while (src >= source.lightmapBuffer.data() && dst >= lightmapBuffer.data()) {
492+
const int bleed = std::max(0, (rowCount - TILE_HEIGHT / 2) * 2);
493+
const int lightOffset = std::max(bleed, clipLeft) - clipLeft;
494+
const int lightLength = std::max(0, TILE_WIDTH - clipLeft - std::max(bleed, clipRight) - lightOffset);
495+
496+
// Bleed pixels up by copying data from the row below this one
497+
if (rowCount > clipBottom && lightLength < lightmapPitch)
498+
memcpy(dst, dst + lightmapPitch, lightmapPitch);
499+
500+
// Copy data from the source lightmap between the top edge of the base diamond
501+
assert(dst + lightOffset + lightLength <= lightmapBuffer.data() + TILE_WIDTH * TILE_HEIGHT);
502+
assert(src + lightOffset + lightLength <= LightmapBuffer.data() + LightmapBuffer.size());
503+
memcpy(dst + lightOffset, src + lightOffset, lightLength);
504+
505+
src -= source.lightmapPitch;
506+
dst -= lightmapPitch;
507+
rowCount++;
508+
}
509+
510+
return Lightmap(outBuffer, source.outPitch,
511+
lightmapBuffer, lightmapPitch,
512+
source.lightTables, source.lightTableSize);
445513
}
446514

447515
} // namespace devilution

Source/engine/render/light_render.hpp

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
#pragma once
22

3+
#include <span>
4+
35
#include "engine/point.hpp"
46

57
namespace devilution {
68

79
class Lightmap {
810
public:
9-
explicit Lightmap(const uint8_t *outBuffer, const uint8_t *lightmapBuffer, const uint8_t *lightTables, size_t lightTableSize);
11+
explicit Lightmap(const uint8_t *outBuffer, std::span<const uint8_t> lightmapBuffer, uint16_t pitch, const uint8_t *lightTables, size_t lightTableSize)
12+
: Lightmap(outBuffer, pitch, lightmapBuffer, pitch, lightTables, lightTableSize)
13+
{
14+
}
15+
16+
explicit Lightmap(const uint8_t *outBuffer, uint16_t outPitch,
17+
std::span<const uint8_t> lightmapBuffer, uint16_t lightmapPitch,
18+
const uint8_t *lightTables, size_t lightTableSize);
1019

1120
uint8_t adjustColor(uint8_t color, uint8_t lightLevel) const
1221
{
@@ -16,16 +25,33 @@ class Lightmap {
1625

1726
const uint8_t *getLightingAt(const uint8_t *outLoc) const
1827
{
19-
return lightmapBuffer + (outLoc - outBuffer);
28+
const ptrdiff_t outDist = outLoc - outBuffer;
29+
const ptrdiff_t rowOffset = outDist % outPitch;
30+
31+
if (outDist < 0) {
32+
// In order to support "bleed up" for wall tiles,
33+
// reuse the first row whenever outLoc is out of bounds
34+
const int modOffset = rowOffset < 0 ? outPitch : 0;
35+
return lightmapBuffer.data() + rowOffset + modOffset;
36+
}
37+
38+
const ptrdiff_t row = outDist / outPitch;
39+
return lightmapBuffer.data() + row * lightmapPitch + rowOffset;
2040
}
2141

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

46+
static Lightmap bleedUp(const Lightmap &source, Point targetBufferPosition, std::span<uint8_t> lightmapBuffer);
47+
2648
private:
2749
const uint8_t *outBuffer;
28-
const uint8_t *lightmapBuffer;
50+
const uint16_t outPitch;
51+
52+
std::span<const uint8_t> lightmapBuffer;
53+
const uint16_t lightmapPitch;
54+
2955
const uint8_t *lightTables;
3056
const size_t lightTableSize;
3157
};

Source/engine/render/scrollrt.cpp

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -536,26 +536,30 @@ void DrawCell(const Surface &out, const Lightmap lightmap, Point tilePosition, P
536536
return MaskType::Solid;
537537
};
538538

539+
// Create a special lightmap buffer to bleed light up walls
540+
uint8_t lightmapBuffer[TILE_WIDTH * TILE_HEIGHT];
541+
Lightmap bleedLightmap = Lightmap::bleedUp(lightmap, targetBufferPosition, lightmapBuffer);
542+
539543
// If the first micro tile is a floor tile, it may be followed
540544
// by foliage which should be rendered now.
541545
const bool isFloor = IsFloor(tilePosition);
542546
if (const LevelCelBlock levelCelBlock { pMap->mt[0] }; levelCelBlock.hasValue()) {
543547
const TileType tileType = levelCelBlock.type();
544548
if (!isFloor || tileType == TileType::TransparentSquare) {
545549
if (isFloor && tileType == TileType::TransparentSquare) {
546-
RenderTileFoliage(out, lightmap, targetBufferPosition, levelCelBlock, foliageTbl);
550+
RenderTileFoliage(out, bleedLightmap, targetBufferPosition, levelCelBlock, foliageTbl);
547551
} else {
548-
RenderTile(out, lightmap, targetBufferPosition, levelCelBlock, getFirstTileMaskLeft(tileType), tbl);
552+
RenderTile(out, bleedLightmap, targetBufferPosition, levelCelBlock, getFirstTileMaskLeft(tileType), tbl);
549553
}
550554
}
551555
}
552556
if (const LevelCelBlock levelCelBlock { pMap->mt[1] }; levelCelBlock.hasValue()) {
553557
const TileType tileType = levelCelBlock.type();
554558
if (!isFloor || tileType == TileType::TransparentSquare) {
555559
if (isFloor && tileType == TileType::TransparentSquare) {
556-
RenderTileFoliage(out, lightmap, targetBufferPosition + RightFrameDisplacement, levelCelBlock, foliageTbl);
560+
RenderTileFoliage(out, bleedLightmap, targetBufferPosition + RightFrameDisplacement, levelCelBlock, foliageTbl);
557561
} else {
558-
RenderTile(out, lightmap, targetBufferPosition + RightFrameDisplacement,
562+
RenderTile(out, bleedLightmap, targetBufferPosition + RightFrameDisplacement,
559563
levelCelBlock, getFirstTileMaskRight(tileType), tbl);
560564
}
561565
}
@@ -566,15 +570,15 @@ void DrawCell(const Surface &out, const Lightmap lightmap, Point tilePosition, P
566570
{
567571
const LevelCelBlock levelCelBlock { pMap->mt[i] };
568572
if (levelCelBlock.hasValue()) {
569-
RenderTile(out, lightmap, targetBufferPosition,
573+
RenderTile(out, bleedLightmap, targetBufferPosition,
570574
levelCelBlock,
571575
transparency ? MaskType::Transparent : MaskType::Solid, foliageTbl);
572576
}
573577
}
574578
{
575579
const LevelCelBlock levelCelBlock { pMap->mt[i + 1] };
576580
if (levelCelBlock.hasValue()) {
577-
RenderTile(out, lightmap, targetBufferPosition + RightFrameDisplacement,
581+
RenderTile(out, bleedLightmap, targetBufferPosition + RightFrameDisplacement,
578582
levelCelBlock,
579583
transparency ? MaskType::Transparent : MaskType::Solid, foliageTbl);
580584
}
@@ -839,10 +843,15 @@ void DrawDungeon(const Surface &out, const Lightmap &lightmap, Point tilePositio
839843
// Turn transparency off here for debugging
840844
transparency = transparency && (SDL_GetModState() & KMOD_ALT) == 0;
841845
#endif
842-
if (perPixelLighting && transparency) {
843-
ClxDrawBlendedWithLightmap(out, targetBufferPosition, (*pSpecialCels)[bArch], lightmap);
844-
} else if (perPixelLighting) {
845-
ClxDrawWithLightmap(out, targetBufferPosition, (*pSpecialCels)[bArch], lightmap);
846+
if (perPixelLighting) {
847+
// Create a special lightmap buffer to bleed light up walls
848+
uint8_t lightmapBuffer[TILE_WIDTH * TILE_HEIGHT];
849+
Lightmap bleedLightmap = Lightmap::bleedUp(lightmap, targetBufferPosition, lightmapBuffer);
850+
851+
if (transparency)
852+
ClxDrawBlendedWithLightmap(out, targetBufferPosition, (*pSpecialCels)[bArch], bleedLightmap);
853+
else
854+
ClxDrawWithLightmap(out, targetBufferPosition, (*pSpecialCels)[bArch], bleedLightmap);
846855
} else if (transparency) {
847856
ClxDrawLightBlended(out, targetBufferPosition, (*pSpecialCels)[bArch], lightTableIndex);
848857
} else {

test/dun_render_benchmark.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ void InitOnce()
6565
void RunForTileMaskLight(benchmark::State &state, TileType tileType, MaskType maskType, const uint8_t *lightTable)
6666
{
6767
Surface out = Surface(SdlSurface.get());
68-
Lightmap lightmap(nullptr, nullptr, nullptr, 0);
68+
Lightmap lightmap(nullptr, {}, 1, nullptr, 0);
6969
size_t numItemsProcessed = 0;
7070
const std::span<const LevelCelBlock> tiles = Tiles[tileType];
7171
for (auto _ : state) {

0 commit comments

Comments
 (0)