diff --git a/Source/engine/render/light_render.cpp b/Source/engine/render/light_render.cpp index 9c993df97..e0659b77a 100644 --- a/Source/engine/render/light_render.cpp +++ b/Source/engine/render/light_render.cpp @@ -1,10 +1,13 @@ #include "engine/render/light_render.hpp" +#include +#include #include #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(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(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 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 lightmapBuffer) +{ + assert(lightmapBuffer.size() >= TILE_WIDTH * TILE_HEIGHT); + + if (!*GetOptions().Graphics.perPixelLighting) + return source; + + const int sourceHeight = static_cast(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 diff --git a/Source/engine/render/light_render.hpp b/Source/engine/render/light_render.hpp index cfd6e5ea5..ba71d8cfb 100644 --- a/Source/engine/render/light_render.hpp +++ b/Source/engine/render/light_render.hpp @@ -1,12 +1,21 @@ #pragma once +#include + #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 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 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 lightmapBuffer); + private: const uint8_t *outBuffer; - const uint8_t *lightmapBuffer; + const uint16_t outPitch; + + std::span lightmapBuffer; + const uint16_t lightmapPitch; + const uint8_t *lightTables; const size_t lightTableSize; }; diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index 5a3d41a68..fbd307837 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/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 { diff --git a/test/dun_render_benchmark.cpp b/test/dun_render_benchmark.cpp index cce1dee70..6d22e3a51 100644 --- a/test/dun_render_benchmark.cpp +++ b/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 tiles = Tiles[tileType]; for (auto _ : state) {