Browse Source

Bleed per-pixel light up when rendering walls

pull/7817/head
staphen 1 year ago committed by Anders Jenbo
parent
commit
b83f006671
  1. 76
      Source/engine/render/light_render.cpp
  2. 32
      Source/engine/render/light_render.hpp
  3. 29
      Source/engine/render/scrollrt.cpp
  4. 2
      test/dun_render_benchmark.cpp

76
Source/engine/render/light_render.cpp

@ -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"
@ -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,
@ -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);
}
}
@ -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)
{
@ -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
Source/engine/render/light_render.hpp

@ -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
{
@ -16,16 +25,33 @@ public:
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;
};

29
Source/engine/render/scrollrt.cpp

@ -536,6 +536,10 @@ 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);
@ -543,9 +547,9 @@ void DrawCell(const Surface &out, const Lightmap lightmap, Point tilePosition, P
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);
}
}
}
@ -553,9 +557,9 @@ void DrawCell(const Surface &out, const Lightmap lightmap, Point tilePosition, P
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);
}
}
@ -566,7 +570,7 @@ 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);
}
@ -574,7 +578,7 @@ void DrawCell(const Surface &out, const Lightmap lightmap, Point tilePosition, P
{
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);
}
@ -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 {

2
test/dun_render_benchmark.cpp

@ -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) {

Loading…
Cancel
Save