/** * @file dun_render.cpp * * Implementation of functionality for rendering the level tiles. */ // Debugging variables // #define DEBUG_STR // #define DEBUG_RENDER_COLOR // #define DEBUG_RENDER_OFFSET_X 5 // #define DEBUG_RENDER_OFFSET_Y 5 #include "engine/render/dun_render.hpp" #include #include #include #include #include "lighting.h" #include "utils/stdcompat/algorithm.hpp" #ifdef _DEBUG #include "miniwin/misc_msg.h" #endif #include "options.h" #include "utils/attributes.h" #ifdef DEBUG_STR #include "engine/render/text_render.hpp" #endif #if defined(DEBUG_STR) || defined(DUN_RENDER_STATS) #include "utils/str_cat.hpp" #endif namespace devilution { namespace { /** Width of a tile rendering primitive. */ constexpr int_fast16_t Width = TILE_WIDTH / 2; /** Height of a tile rendering primitive (except triangles). */ constexpr int_fast16_t Height = TILE_HEIGHT; /** Height of the lower triangle of a triangular or a trapezoid tile. */ constexpr int_fast16_t LowerHeight = TILE_HEIGHT / 2; /** Height of the upper triangle of a triangular tile. */ constexpr int_fast16_t TriangleUpperHeight = TILE_HEIGHT / 2 - 1; /** Height of the upper rectangle of a trapezoid tile. */ constexpr int_fast16_t TrapezoidUpperHeight = TILE_HEIGHT / 2; constexpr int_fast16_t TriangleHeight = LowerHeight + TriangleUpperHeight; /** For triangles, for each pixel drawn vertically, this many pixels are drawn horizontally. */ constexpr int_fast16_t XStep = 2; int_fast16_t GetTileHeight(TileType tile) { if (tile == TileType::LeftTriangle || tile == TileType::RightTriangle) return TriangleHeight; return Height; } #ifdef DEBUG_STR std::pair GetTileDebugStr(TileType tile) { // clang-format off switch (tile) { case TileType::Square: return {"S", UiFlags::AlignCenter | UiFlags::VerticalCenter}; case TileType::TransparentSquare: return {"T", UiFlags::AlignCenter | UiFlags::VerticalCenter}; case TileType::LeftTriangle: return {"<", UiFlags::AlignRight | UiFlags::VerticalCenter}; case TileType::RightTriangle: return {">", UiFlags::VerticalCenter}; case TileType::LeftTrapezoid: return {"\\", UiFlags::AlignCenter}; case TileType::RightTrapezoid: return {"/", UiFlags::AlignCenter}; default: return {"", {}}; } // clang-format on } #endif #ifdef DEBUG_RENDER_COLOR int DBGCOLOR = 0; int GetTileDebugColor(TileType tile) { // clang-format off switch (tile) { case TileType::Square: return PAL16_YELLOW + 5; case TileType::TransparentSquare: return PAL16_ORANGE + 5; case TileType::LeftTriangle: return PAL16_GRAY + 5; case TileType::RightTriangle: return PAL16_BEIGE; case TileType::LeftTrapezoid: return PAL16_RED + 5; case TileType::RightTrapezoid: return PAL16_BLUE + 5; default: return 0; } } #endif // DEBUG_RENDER_COLOR // Masks are defined by 2 template variables: // // 1. `OpaquePrefix`: Whether the line starts with opaque pixels // followed by blended pixels or the other way around. // 2. `PrefixIncrement`: The change to the prefix when going // up 1 line. // // The Left mask can only be applied to LeftTrapezoid and TransparentSquare. // The Right mask can only be applied to RightTrapezoid and TransparentSquare. // The Left/RightFoliage masks can only be applied to TransparentSquare. // True if the given OpaquePrefix and PrefixIncrement represent foliage. // For foliage, we skip transparent pixels instead of blending them. template constexpr bool IsFoliage = PrefixIncrement != 0 && (OpaquePrefix == (PrefixIncrement > 0)); // True for foliage: template constexpr bool SkipTransparentPixels = IsFoliage; // True if the entire lower half of the mask is transparent. // True for Transparent, LeftFoliage, and RightFoliage. template constexpr bool LowerHalfTransparent = (OpaquePrefix == (PrefixIncrement >= 0)); // The initial value for the prefix: template DVL_ALWAYS_INLINE int8_t InitPrefix() { return PrefixIncrement >= 0 ? -32 : 64; } // The initial value for the prefix at y-th line (counting from the bottom). template DVL_ALWAYS_INLINE int8_t InitPrefix(int8_t y) { return InitPrefix() + PrefixIncrement * y; } #ifdef DEBUG_STR template std::string prefixDebugString(int8_t prefix) { std::string out(32, OpaquePrefix ? '0' : '1'); const uint8_t clamped = clamp(prefix, 0, 32); out.replace(0, clamped, clamped, OpaquePrefix ? '1' : '0'); StrAppend(out, " prefix=", prefix, " OpaquePrefix=", OpaquePrefix, " PrefixIncrement=", PrefixIncrement); return out; } #endif enum class LightType : uint8_t { FullyDark, PartiallyLit, FullyLit, }; template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineOpaque(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t n, const uint8_t *DVL_RESTRICT tbl) { if (Light == LightType::FullyDark) { memset(dst, 0, n); } else if (Light == LightType::FullyLit) { #ifndef DEBUG_RENDER_COLOR memcpy(dst, src, n); #else memset(dst, DBGCOLOR, n); #endif } else { // Partially lit #ifndef DEBUG_RENDER_COLOR while (n-- != 0) { *dst++ = tbl[*src++]; } #else memset(dst, tbl[DBGCOLOR], n); #endif } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparent(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t n, const uint8_t *DVL_RESTRICT tbl) { #ifndef DEBUG_RENDER_COLOR if (Light == LightType::FullyDark) { while (n-- != 0) { *dst = paletteTransparencyLookup[0][*dst]; ++dst; } } else if (Light == LightType::FullyLit) { while (n-- != 0) { *dst = paletteTransparencyLookup[*dst][*src]; ++dst; ++src; } } else { // Partially lit while (n-- != 0) { *dst = paletteTransparencyLookup[*dst][tbl[*src]]; ++dst; ++src; } } #else for (size_t i = 0; i < n; i++) { dst[i] = paletteTransparencyLookup[dst[i]][tbl[DBGCOLOR + 4]]; } #endif } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparentOrOpaque(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t width, const uint8_t *DVL_RESTRICT tbl) { if (Transparent) { RenderLineTransparent(dst, src, width, tbl); } else { RenderLineOpaque(dst, src, width, tbl); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparentAndOpaque(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t prefixWidth, uint_fast8_t width, const uint8_t *DVL_RESTRICT tbl) { if (OpaquePrefix) { RenderLineOpaque(dst, src, prefixWidth, tbl); if (!SkipTransparentPixels) RenderLineTransparent(dst + prefixWidth, src + prefixWidth, width - prefixWidth, tbl); } else { if (!SkipTransparentPixels) RenderLineTransparent(dst, src, prefixWidth, tbl); RenderLineOpaque(dst + prefixWidth, src + prefixWidth, width - prefixWidth, tbl); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLine(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t n, const uint8_t *DVL_RESTRICT tbl, int8_t prefix) { if (PrefixIncrement == 0) { RenderLineTransparentOrOpaque(dst, src, n, tbl); } else { RenderLineTransparentAndOpaque(dst, src, clamp(prefix, 0, n), n, tbl); } } struct Clip { int_fast16_t top; int_fast16_t bottom; int_fast16_t left; int_fast16_t right; int_fast16_t width; int_fast16_t height; }; DVL_ALWAYS_INLINE Clip CalculateClip(int_fast16_t x, int_fast16_t y, int_fast16_t w, int_fast16_t h, const Surface &out) { Clip clip; clip.top = y + 1 < h ? h - (y + 1) : 0; clip.bottom = y + 1 > out.h() ? (y + 1) - out.h() : 0; clip.left = x < 0 ? -x : 0; clip.right = x + w > out.w() ? x + w - out.w() : 0; clip.width = w - clip.left - clip.right; clip.height = h - clip.top - clip.bottom; return clip; } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderSquareFull(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { for (auto i = 0; i < Height; ++i, dst -= dstPitch) { RenderLineTransparentOrOpaque(dst, src, Width, tbl); src += Width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderSquareClipped(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { src += clip.bottom * Height + clip.left; for (auto i = 0; i < clip.height; ++i, dst -= dstPitch) { RenderLineTransparentOrOpaque(dst, src, clip.width, tbl); src += Width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderSquare(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (clip.width == Width && clip.height == Height) { RenderSquareFull(dst, dstPitch, src, tbl); } else { RenderSquareClipped(dst, dstPitch, src, tbl, clip); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTransparentSquareFull(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { int8_t prefix = InitPrefix(); for (auto i = 0; i < Height; ++i, dst -= dstPitch + Width) { uint_fast8_t drawWidth = Width; while (drawWidth > 0) { auto v = static_cast(*src++); if (v > 0) { RenderLine(dst, src, v, tbl, prefix - (Width - drawWidth)); src += v; } else { v = -v; } dst += v; drawWidth -= v; } prefix += PrefixIncrement; } } template // NOLINTNEXTLINE(readability-function-cognitive-complexity): Actually complex and has to be fast. DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTransparentSquareClipped(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const auto skipRestOfTheLine = [&src](int_fast16_t remainingWidth) { while (remainingWidth > 0) { const auto v = static_cast(*src++); if (v > 0) { src += v; remainingWidth -= v; } else { remainingWidth -= -v; } } assert(remainingWidth == 0); }; // Skip the bottom clipped lines. for (auto i = 0; i < clip.bottom; ++i) { skipRestOfTheLine(Width); } int8_t prefix = InitPrefix(clip.bottom); for (auto i = 0; i < clip.height; ++i, dst -= dstPitch + clip.width) { auto drawWidth = clip.width; // Skip initial src if clipping on the left. // Handles overshoot, i.e. when the RLE segment goes into the unclipped area. auto remainingLeftClip = clip.left; while (remainingLeftClip > 0) { auto v = static_cast(*src++); if (v > 0) { if (v > remainingLeftClip) { const auto overshoot = v - remainingLeftClip; RenderLine(dst, src + remainingLeftClip, overshoot, tbl, prefix - (Width - remainingLeftClip)); dst += overshoot; drawWidth -= overshoot; } src += v; } else { v = -v; if (v > remainingLeftClip) { const auto overshoot = v - remainingLeftClip; dst += overshoot; drawWidth -= overshoot; } } remainingLeftClip -= v; } // Draw the non-clipped segment while (drawWidth > 0) { auto v = static_cast(*src++); if (v > 0) { if (v > drawWidth) { RenderLine(dst, src, drawWidth, tbl, prefix - (Width - drawWidth)); src += v; dst += drawWidth; drawWidth -= v; break; } RenderLine(dst, src, v, tbl, prefix - (Width - drawWidth)); src += v; } else { v = -v; if (v > drawWidth) { dst += drawWidth; drawWidth -= v; break; } } dst += v; drawWidth -= v; } // Skip the rest of src line if clipping on the right assert(drawWidth <= 0); skipRestOfTheLine(clip.right + drawWidth); prefix += PrefixIncrement; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTransparentSquare(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (clip.width == Width && clip.height == Height) { RenderTransparentSquareFull(dst, dstPitch, src, tbl); } else { RenderTransparentSquareClipped(dst, dstPitch, src, tbl, clip); } } /** Vertical clip for the lower and upper triangles of a diamond tile (L/RTRIANGLE).*/ struct DiamondClipY { int_fast16_t lowerBottom; int_fast16_t lowerTop; int_fast16_t upperBottom; int_fast16_t upperTop; }; template DVL_ALWAYS_INLINE DiamondClipY CalculateDiamondClipY(const Clip &clip) { DiamondClipY result; if (clip.bottom > LowerHeight) { result.lowerBottom = LowerHeight; result.upperBottom = clip.bottom - LowerHeight; result.lowerTop = result.upperTop = 0; } else if (clip.top > UpperHeight) { result.upperTop = UpperHeight; result.lowerTop = clip.top - UpperHeight; result.upperBottom = result.lowerBottom = 0; } else { result.upperTop = clip.top; result.lowerBottom = clip.bottom; result.lowerTop = result.upperBottom = 0; } return result; } DVL_ALWAYS_INLINE std::size_t CalculateTriangleSourceSkipLowerBottom(int_fast16_t numLines) { return XStep * numLines * (numLines + 1) / 2 + 2 * ((numLines + 1) / 2); } DVL_ALWAYS_INLINE std::size_t CalculateTriangleSourceSkipUpperBottom(int_fast16_t numLines) { return 2 * TriangleUpperHeight * numLines - numLines * (numLines - 1) + 2 * ((numLines + 1) / 2); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleLower(uint8_t *DVL_RESTRICT &dst, int dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { dst += XStep * (LowerHeight - 1); for (auto i = 1; i <= LowerHeight; ++i, dst -= dstPitch + XStep) { src += 2 * (i % 2); const auto width = XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleLowerClipVertical(const DiamondClipY &clipY, uint8_t *DVL_RESTRICT &dst, int dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom); dst += XStep * (LowerHeight - clipY.lowerBottom - 1); const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep) { src += 2 * (i % 2); const auto width = XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleLowerClipLeftAndVertical(int_fast16_t clipLeft, const DiamondClipY &clipY, uint8_t *DVL_RESTRICT &dst, int dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom); dst += XStep * (LowerHeight - clipY.lowerBottom - 1) - clipLeft; const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep) { src += 2 * (i % 2); const auto width = XStep * i; const auto startX = Width - XStep * i; const auto skip = startX < clipLeft ? clipLeft - startX : 0; if (width > skip) RenderLineTransparentOrOpaque(dst + skip, src + skip, width - skip, tbl); src += width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleLowerClipRightAndVertical(int_fast16_t clipRight, const DiamondClipY &clipY, uint8_t *DVL_RESTRICT &dst, int dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom); dst += XStep * (LowerHeight - clipY.lowerBottom - 1); const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep) { src += 2 * (i % 2); const auto width = XStep * i; if (width > clipRight) RenderLineTransparentOrOpaque(dst, src, width - clipRight, tbl); src += width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleFull(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { RenderLeftTriangleLower(dst, dstPitch, src, tbl); dst += 2 * XStep; for (auto i = 1; i <= TriangleUpperHeight; ++i, dst -= dstPitch - XStep) { src += 2 * (i % 2); const auto width = Width - XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleClipVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); RenderLeftTriangleLowerClipVertical(clipY, dst, dstPitch, src, tbl); src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom); dst += 2 * XStep + XStep * clipY.upperBottom; const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) { src += 2 * (i % 2); const auto width = Width - XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleClipLeftAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); const int_fast16_t clipLeft = clip.left; RenderLeftTriangleLowerClipLeftAndVertical(clipLeft, clipY, dst, dstPitch, src, tbl); src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom); dst += 2 * XStep + XStep * clipY.upperBottom; const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) { src += 2 * (i % 2); const auto width = Width - XStep * i; const auto startX = XStep * i; const auto skip = startX < clipLeft ? clipLeft - startX : 0; RenderLineTransparentOrOpaque(dst + skip, src + skip, width > skip ? width - skip : 0, tbl); src += width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangleClipRightAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); const int_fast16_t clipRight = clip.right; RenderLeftTriangleLowerClipRightAndVertical(clipRight, clipY, dst, dstPitch, src, tbl); src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom); dst += 2 * XStep + XStep * clipY.upperBottom; const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) { src += 2 * (i % 2); const auto width = Width - XStep * i; if (width <= clipRight) break; RenderLineTransparentOrOpaque(dst, src, width - clipRight, tbl); src += width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTriangle(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (clip.width == Width) { if (clip.height == TriangleHeight) { RenderLeftTriangleFull(dst, dstPitch, src, tbl); } else { RenderLeftTriangleClipVertical(dst, dstPitch, src, tbl, clip); } } else if (clip.right == 0) { RenderLeftTriangleClipLeftAndVertical(dst, dstPitch, src, tbl, clip); } else { RenderLeftTriangleClipRightAndVertical(dst, dstPitch, src, tbl, clip); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleLower(uint8_t *DVL_RESTRICT &dst, int dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { for (auto i = 1; i <= LowerHeight; ++i, dst -= dstPitch) { const auto width = XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width + 2 * (i % 2); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleLowerClipVertical(const DiamondClipY &clipY, uint8_t *DVL_RESTRICT &dst, int dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom); const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch) { const auto width = XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width + 2 * (i % 2); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleLowerClipLeftAndVertical(int_fast16_t clipLeft, const DiamondClipY &clipY, uint8_t *DVL_RESTRICT &dst, int dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom); const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch) { const auto width = XStep * i; if (width > clipLeft) RenderLineTransparentOrOpaque(dst, src + clipLeft, width - clipLeft, tbl); src += width + 2 * (i % 2); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleLowerClipRightAndVertical(int_fast16_t clipRight, const DiamondClipY &clipY, uint8_t *DVL_RESTRICT &dst, int dstPitch, const uint8_t *DVL_RESTRICT &src, const uint8_t *DVL_RESTRICT tbl) { src += CalculateTriangleSourceSkipLowerBottom(clipY.lowerBottom); const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch) { const auto width = XStep * i; const auto skip = Width - width < clipRight ? clipRight - (Width - width) : 0; if (width > skip) RenderLineTransparentOrOpaque(dst, src, width - skip, tbl); src += width + 2 * (i % 2); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleFull(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { RenderRightTriangleLower(dst, dstPitch, src, tbl); for (auto i = 1; i <= TriangleUpperHeight; ++i, dst -= dstPitch) { const auto width = Width - XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width + 2 * (i % 2); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleClipVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); RenderRightTriangleLowerClipVertical(clipY, dst, dstPitch, src, tbl); src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom); const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch) { const auto width = Width - XStep * i; RenderLineTransparentOrOpaque(dst, src, width, tbl); src += width + 2 * (i % 2); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleClipLeftAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); const int_fast16_t clipLeft = clip.left; RenderRightTriangleLowerClipLeftAndVertical(clipLeft, clipY, dst, dstPitch, src, tbl); src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom); const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch) { const auto width = Width - XStep * i; if (width <= clipLeft) break; RenderLineTransparentOrOpaque(dst, src + clipLeft, width - clipLeft, tbl); src += width + 2 * (i % 2); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangleClipRightAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); const int_fast16_t clipRight = clip.right; RenderRightTriangleLowerClipRightAndVertical(clipRight, clipY, dst, dstPitch, src, tbl); src += CalculateTriangleSourceSkipUpperBottom(clipY.upperBottom); const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch) { const auto width = Width - XStep * i; const auto skip = Width - width < clipRight ? clipRight - (Width - width) : 0; RenderLineTransparentOrOpaque(dst, src, width > skip ? width - skip : 0, tbl); src += width + 2 * (i % 2); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangle(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (clip.width == Width) { if (clip.height == TriangleHeight) { RenderRightTriangleFull(dst, dstPitch, src, tbl); } else { RenderRightTriangleClipVertical(dst, dstPitch, src, tbl, clip); } } else if (clip.right == 0) { RenderRightTriangleClipLeftAndVertical(dst, dstPitch, src, tbl, clip); } else { RenderRightTriangleClipRightAndVertical(dst, dstPitch, src, tbl, clip); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTrapezoidUpperHalf(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { uint_fast8_t prefixWidth = PrefixIncrement < 0 ? 32 : 0; for (auto i = 0; i < TrapezoidUpperHeight; ++i, dst -= dstPitch) { RenderLineTransparentAndOpaque(dst, src, prefixWidth, Width, tbl); if (PrefixIncrement != 0) prefixWidth += PrefixIncrement; src += Width; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTrapezoidUpperHalfClipVertical(const Clip &clip, const DiamondClipY &clipY, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { const auto upperMax = TrapezoidUpperHeight - clipY.upperTop; int8_t prefix = InitPrefix(clip.bottom); for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch) { RenderLine(dst, src, Width, tbl, prefix); src += Width; prefix += PrefixIncrement; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTrapezoidUpperHalfClipLeftAndVertical(const Clip &clip, const DiamondClipY &clipY, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { const auto upperMax = TrapezoidUpperHeight - clipY.upperTop; int8_t prefix = InitPrefix(clip.bottom); for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch) { RenderLine(dst, src, clip.width, tbl, prefix - clip.left); src += Width; prefix += PrefixIncrement; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTrapezoidUpperHalfClipRightAndVertical(const Clip &clip, const DiamondClipY &clipY, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { const auto upperMax = TrapezoidUpperHeight - clipY.upperTop; int8_t prefix = InitPrefix(clip.bottom); for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch) { RenderLine(dst, src, clip.width, tbl, prefix); src += Width; prefix += PrefixIncrement; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTrapezoidFull(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { RenderLeftTriangleLower>(dst, dstPitch, src, tbl); dst += XStep; RenderTrapezoidUpperHalf(dst, dstPitch, src, tbl); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTrapezoidClipVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); RenderLeftTriangleLowerClipVertical>(clipY, dst, dstPitch, src, tbl); src += clipY.upperBottom * Width; dst += XStep; RenderTrapezoidUpperHalfClipVertical(clip, clipY, dst, dstPitch, src, tbl); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTrapezoidClipLeftAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); RenderLeftTriangleLowerClipLeftAndVertical>(clip.left, clipY, dst, dstPitch, src, tbl); src += clipY.upperBottom * Width + clip.left; dst += XStep + clip.left; RenderTrapezoidUpperHalfClipLeftAndVertical(clip, clipY, dst, dstPitch, src, tbl); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTrapezoidClipRightAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); RenderLeftTriangleLowerClipRightAndVertical>(clip.right, clipY, dst, dstPitch, src, tbl); src += clipY.upperBottom * Width; dst += XStep; RenderTrapezoidUpperHalfClipRightAndVertical(clip, clipY, dst, dstPitch, src, tbl); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTrapezoid(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (clip.width == Width) { if (clip.height == Height) { RenderLeftTrapezoidFull(dst, dstPitch, src, tbl); } else { RenderLeftTrapezoidClipVertical(dst, dstPitch, src, tbl, clip); } } else if (clip.right == 0) { RenderLeftTrapezoidClipLeftAndVertical(dst, dstPitch, src, tbl, clip); } else { RenderLeftTrapezoidClipRightAndVertical(dst, dstPitch, src, tbl, clip); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTrapezoidFull(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { RenderRightTriangleLower>(dst, dstPitch, src, tbl); RenderTrapezoidUpperHalf(dst, dstPitch, src, tbl); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTrapezoidClipVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); RenderRightTriangleLowerClipVertical>(clipY, dst, dstPitch, src, tbl); src += clipY.upperBottom * Width; RenderTrapezoidUpperHalfClipVertical(clip, clipY, dst, dstPitch, src, tbl); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTrapezoidClipLeftAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); RenderRightTriangleLowerClipLeftAndVertical>(clip.left, clipY, dst, dstPitch, src, tbl); src += clipY.upperBottom * Width + clip.left; RenderTrapezoidUpperHalfClipLeftAndVertical(clip, clipY, dst, dstPitch, src, tbl); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTrapezoidClipRightAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { const DiamondClipY clipY = CalculateDiamondClipY(clip); RenderRightTriangleLowerClipRightAndVertical>(clip.right, clipY, dst, dstPitch, src, tbl); src += clipY.upperBottom * Width; RenderTrapezoidUpperHalfClipRightAndVertical(clip, clipY, dst, dstPitch, src, tbl); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTrapezoid(uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (clip.width == Width) { if (clip.height == Height) { RenderRightTrapezoidFull(dst, dstPitch, src, tbl); } else { RenderRightTrapezoidClipVertical(dst, dstPitch, src, tbl, clip); } } else if (clip.right == 0) { RenderRightTrapezoidClipLeftAndVertical(dst, dstPitch, src, tbl, clip); } else { RenderRightTrapezoidClipRightAndVertical(dst, dstPitch, src, tbl, clip); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTileType(TileType tile, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { switch (tile) { case TileType::Square: RenderSquare(dst, dstPitch, src, tbl, clip); break; case TileType::TransparentSquare: RenderTransparentSquare(dst, dstPitch, src, tbl, clip); break; case TileType::LeftTriangle: RenderLeftTriangle(dst, dstPitch, src, tbl, clip); break; case TileType::RightTriangle: RenderRightTriangle(dst, dstPitch, src, tbl, clip); break; case TileType::LeftTrapezoid: RenderLeftTrapezoid(dst, dstPitch, src, tbl, clip); break; case TileType::RightTrapezoid: RenderRightTrapezoid(dst, dstPitch, src, tbl, clip); break; } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTransparentSquareDispatch(uint8_t lightTableIndex, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (lightTableIndex == LightsMax) { RenderTransparentSquare(dst, dstPitch, src, tbl, clip); } else if (lightTableIndex == 0) { RenderTransparentSquare(dst, dstPitch, src, tbl, clip); } else { RenderTransparentSquare(dst, dstPitch, src, tbl, clip); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTrapezoidOrTransparentSquare(TileType tile, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { switch (tile) { case TileType::TransparentSquare: RenderTransparentSquare(dst, dstPitch, src, tbl, clip); break; case TileType::LeftTrapezoid: RenderLeftTrapezoid(dst, dstPitch, src, tbl, clip); break; default: app_fatal("Given mask can only be applied to TransparentSquare or LeftTrapezoid tiles"); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTrapezoidOrTransparentSquare(TileType tile, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { switch (tile) { case TileType::TransparentSquare: RenderTransparentSquare(dst, dstPitch, src, tbl, clip); break; case TileType::RightTrapezoid: RenderRightTrapezoid(dst, dstPitch, src, tbl, clip); break; default: app_fatal("Given mask can only be applied to TransparentSquare or LeftTrapezoid tiles"); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLeftTrapezoidOrTransparentSquareDispatch(uint8_t lightTableIndex, TileType tile, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (lightTableIndex == LightsMax) { RenderLeftTrapezoidOrTransparentSquare(tile, dst, dstPitch, src, tbl, clip); } else if (lightTableIndex == 0) { RenderLeftTrapezoidOrTransparentSquare(tile, dst, dstPitch, src, tbl, clip); } else { RenderLeftTrapezoidOrTransparentSquare(tile, dst, dstPitch, src, tbl, clip); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTrapezoidOrTransparentSquareDispatch(uint8_t lightTableIndex, TileType tile, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (lightTableIndex == LightsMax) { RenderRightTrapezoidOrTransparentSquare(tile, dst, dstPitch, src, tbl, clip); } else if (lightTableIndex == 0) { RenderRightTrapezoidOrTransparentSquare(tile, dst, dstPitch, src, tbl, clip); } else { RenderRightTrapezoidOrTransparentSquare(tile, dst, dstPitch, src, tbl, clip); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTileDispatch(uint8_t lightTableIndex, TileType tile, uint8_t *DVL_RESTRICT dst, int dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl, Clip clip) { if (lightTableIndex == LightsMax) { RenderTileType(tile, dst, dstPitch, src, tbl, clip); } else if (lightTableIndex == 0) { RenderTileType(tile, dst, dstPitch, src, tbl, clip); } else { RenderTileType(tile, dst, dstPitch, src, tbl, clip); } } // Blit with left and vertical clipping. void RenderBlackTileClipLeftAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, int sx, DiamondClipY clipY) { dst += XStep * (LowerHeight - clipY.lowerBottom - 1); // Lower triangle (drawn bottom to top): const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = clipY.lowerBottom + 1; i <= lowerMax; ++i, dst -= dstPitch + XStep) { const auto w = 2 * XStep * i; const auto curX = sx + TILE_WIDTH / 2 - XStep * i; if (curX >= 0) { memset(dst, 0, w); } else if (-curX <= w) { memset(dst - curX, 0, w + curX); } } dst += 2 * XStep + XStep * clipY.upperBottom; // Upper triangle (drawn bottom to top): const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = clipY.upperBottom; i < upperMax; ++i, dst -= dstPitch - XStep) { const auto w = 2 * XStep * (TriangleUpperHeight - i); const auto curX = sx + TILE_WIDTH / 2 - XStep * (TriangleUpperHeight - i); if (curX >= 0) { memset(dst, 0, w); } else if (-curX <= w) { memset(dst - curX, 0, w + curX); } else { break; } } } // Blit with right and vertical clipping. void RenderBlackTileClipRightAndVertical(uint8_t *DVL_RESTRICT dst, int dstPitch, int_fast16_t maxWidth, DiamondClipY clipY) { dst += XStep * (LowerHeight - clipY.lowerBottom - 1); // Lower triangle (drawn bottom to top): const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = clipY.lowerBottom + 1; i <= lowerMax; ++i, dst -= dstPitch + XStep) { const auto width = 2 * XStep * i; const auto endX = TILE_WIDTH / 2 + XStep * i; const auto skip = endX > maxWidth ? endX - maxWidth : 0; if (width > skip) memset(dst, 0, width - skip); } dst += 2 * XStep + XStep * clipY.upperBottom; // Upper triangle (drawn bottom to top): const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) { const auto width = TILE_WIDTH - 2 * XStep * i; const auto endX = TILE_WIDTH / 2 + XStep * (TriangleUpperHeight - i + 1); const auto skip = endX > maxWidth ? endX - maxWidth : 0; if (width <= skip) break; memset(dst, 0, width - skip); } } // Blit with vertical clipping only. void RenderBlackTileClipY(uint8_t *DVL_RESTRICT dst, int dstPitch, DiamondClipY clipY) { dst += XStep * (LowerHeight - clipY.lowerBottom - 1); // Lower triangle (drawn bottom to top): const auto lowerMax = LowerHeight - clipY.lowerTop; for (auto i = 1 + clipY.lowerBottom; i <= lowerMax; ++i, dst -= dstPitch + XStep) { memset(dst, 0, 2 * XStep * i); } dst += 2 * XStep + XStep * clipY.upperBottom; // Upper triangle (drawn bottom to top): const auto upperMax = TriangleUpperHeight - clipY.upperTop; for (auto i = 1 + clipY.upperBottom; i <= upperMax; ++i, dst -= dstPitch - XStep) { memset(dst, 0, TILE_WIDTH - 2 * XStep * i); } } // Blit a black tile without clipping (must be fully in bounds). void RenderBlackTileFull(uint8_t *DVL_RESTRICT dst, int dstPitch) { dst += XStep * (LowerHeight - 1); // Tile is fully in bounds, can use constant loop boundaries. // Lower triangle (drawn bottom to top): for (unsigned i = 1; i <= LowerHeight; ++i, dst -= dstPitch + XStep) { memset(dst, 0, 2 * XStep * i); } dst += 2 * XStep; // Upper triangle (drawn bottom to to top): for (unsigned i = 1; i <= TriangleUpperHeight; ++i, dst -= dstPitch - XStep) { memset(dst, 0, TILE_WIDTH - 2 * XStep * i); } } } // namespace #ifdef DUN_RENDER_STATS std::unordered_map DunRenderStats; string_view TileTypeToString(TileType tileType) { // clang-format off switch (tileType) { case TileType::Square: return "Square"; case TileType::TransparentSquare: return "TransparentSquare"; case TileType::LeftTriangle: return "LeftTriangle"; case TileType::RightTriangle: return "RightTriangle"; case TileType::LeftTrapezoid: return "LeftTrapezoid"; case TileType::RightTrapezoid: return "RightTrapezoid"; default: return "???"; } // clang-format on } string_view MaskTypeToString(MaskType maskType) { // clang-format off switch (maskType) { case MaskType::Solid: return "Solid"; case MaskType::Transparent: return "Transparent"; case MaskType::Right: return "Right"; case MaskType::Left: return "Left"; case MaskType::RightFoliage: return "RightFoliage"; case MaskType::LeftFoliage: return "LeftFoliage"; default: return "???"; } // clang-format on } #endif void RenderTile(const Surface &out, Point position, LevelCelBlock levelCelBlock, MaskType maskType, uint8_t lightTableIndex) { const TileType tile = levelCelBlock.type(); #ifdef DEBUG_RENDER_OFFSET_X position.x += DEBUG_RENDER_OFFSET_X; #endif #ifdef DEBUG_RENDER_OFFSET_Y position.y += DEBUG_RENDER_OFFSET_Y; #endif #ifdef DEBUG_RENDER_COLOR DBGCOLOR = GetTileDebugColor(tile); #endif Clip clip = CalculateClip(position.x, position.y, Width, GetTileHeight(tile), out); if (clip.width <= 0 || clip.height <= 0) return; const uint8_t *tbl = &LightTables[256 * lightTableIndex]; const auto *pFrameTable = reinterpret_cast(pDungeonCels.get()); const auto *src = reinterpret_cast(&pDungeonCels[SDL_SwapLE32(pFrameTable[levelCelBlock.frame()])]); uint8_t *dst = out.at(static_cast(position.x + clip.left), static_cast(position.y - clip.bottom)); const auto dstPitch = out.pitch(); #ifdef DUN_RENDER_STATS ++DunRenderStats[DunRenderType { tile, maskType }]; #endif switch (maskType) { case MaskType::Solid: RenderTileDispatch(lightTableIndex, tile, dst, dstPitch, src, tbl, clip); break; case MaskType::Transparent: RenderTileDispatch(lightTableIndex, tile, dst, dstPitch, src, tbl, clip); break; case MaskType::Left: RenderLeftTrapezoidOrTransparentSquareDispatch(lightTableIndex, tile, dst, dstPitch, src, tbl, clip); break; case MaskType::Right: RenderRightTrapezoidOrTransparentSquareDispatch(lightTableIndex, tile, dst, dstPitch, src, tbl, clip); break; case MaskType::LeftFoliage: RenderTransparentSquareDispatch(lightTableIndex, dst, dstPitch, src, tbl, clip); break; case MaskType::RightFoliage: RenderTransparentSquareDispatch(lightTableIndex, dst, dstPitch, src, tbl, clip); break; } #ifdef DEBUG_STR const std::pair debugStr = GetTileDebugStr(tile); DrawString(out, debugStr.first, Rectangle { Point { position.x + 2, position.y - 29 }, Size { 28, 28 } }, debugStr.second); #endif } void world_draw_black_tile(const Surface &out, int sx, int sy) { #ifdef DEBUG_RENDER_OFFSET_X sx += DEBUG_RENDER_OFFSET_X; #endif #ifdef DEBUG_RENDER_OFFSET_Y sy += DEBUG_RENDER_OFFSET_Y; #endif auto clip = CalculateClip(sx, sy, TILE_WIDTH, TriangleHeight, out); if (clip.width <= 0 || clip.height <= 0) return; auto clipY = CalculateDiamondClipY(clip); uint8_t *dst = out.at(sx, static_cast(sy - clip.bottom)); if (clip.width == TILE_WIDTH) { if (clip.height == TriangleHeight) { RenderBlackTileFull(dst, out.pitch()); } else { RenderBlackTileClipY(dst, out.pitch(), clipY); } } else { if (clip.right == 0) { RenderBlackTileClipLeftAndVertical(dst, out.pitch(), sx, clipY); } else { RenderBlackTileClipRightAndVertical(dst, out.pitch(), clip.width, clipY); } } } } // namespace devilution