From be7880c4db6cb87de97ec168d28d16555ec28561 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 8 May 2021 14:39:27 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20cel=5Frender.cpp:=20Clip=20outli?= =?UTF-8?q?ne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clips `CelBlitOutlineTo` to the output buffer. The templated implementation means that this compiles to many case-specific functions with near-branchless inner loops. Increases binary size by 92 KiB. --- Source/engine/render/cel_render.cpp | 408 ++++++++++++++++++++++++---- 1 file changed, 359 insertions(+), 49 deletions(-) diff --git a/Source/engine/render/cel_render.cpp b/Source/engine/render/cel_render.cpp index 5792ecc91..d79ecb293 100644 --- a/Source/engine/render/cel_render.cpp +++ b/Source/engine/render/cel_render.cpp @@ -179,6 +179,359 @@ constexpr auto RenderLineMemcpy = [](std::uint8_t *dst, const std::uint8_t *src, std::memcpy(dst, src, w); }; +template +void RenderOutlineForPixels(std::uint8_t *dst, int dstPitch, const std::uint8_t *src, int width, std::uint8_t color) +{ + for (; width-- > 0; ++src, ++dst) { + if (SkipColorIndexZero && *src == 0) + continue; + if (North) + dst[-dstPitch] = color; + if (West) + dst[-1] = color; + if (East) + dst[1] = color; + if (South) + dst[dstPitch] = color; + } +} + +template +void RenderOutlineForPixel(std::uint8_t *dst, int dstPitch, std::uint8_t srcColor, std::uint8_t color) +{ + if (SkipColorIndexZero && srcColor == 0) + return; + if (North) + dst[-dstPitch] = color; + if (West) + dst[-1] = color; + if (East) + dst[1] = color; + if (South) + dst[dstPitch] = color; +} + +template +const byte *RenderCelOutlineRowClipped( // NOLINT(readability-function-cognitive-complexity,misc-no-recursion) + const CelOutputBuffer &out, Point position, const byte *src, int srcWidth, + ClipX clipX, std::uint8_t color, std::uint8_t continueRleRun = 0) +{ + std::int_fast16_t remainingWidth = clipX.width; + std::uint8_t v; + + if (ClipWidth && !ContinueRleRun) { + auto remainingLeftClip = clipX.left; + while (remainingLeftClip > 0) { + v = static_cast(*src++); + if (!IsCelTransparent(v)) { + if (v > remainingLeftClip) { + return RenderCelOutlineRowClipped(out, position, + src + remainingLeftClip, srcWidth - clipX.left, { 0, 0, clipX.width }, color, v - remainingLeftClip); + } + src += v; + } else { + v = GetCelTransparentWidth(v); + if (v > remainingLeftClip) { + const std::uint8_t overshoot = v - remainingLeftClip; + position.x += overshoot; + remainingWidth -= overshoot; + if (remainingWidth <= 0) + return src; + break; + } + } + remainingLeftClip -= v; + } + } + + auto *dst = &out[position]; + const auto dstPitch = out.pitch(); + + const auto handleEdgePixels = [&](std::uint8_t width) -> bool { + if (CheckFirstColumn && position.x <= 0) { + if (position.x == -1) { + RenderOutlineForPixel( + dst++, dstPitch, static_cast(*src++), color); + --width; + } + if (width > 0) { + RenderOutlineForPixel( + dst++, dstPitch, static_cast(*src++), color); + --width; + } + if (width > 0) { + RenderOutlineForPixels( + dst, dstPitch, reinterpret_cast(src), width, color); + src += width, dst += width; + } + return true; + } + if (CheckLastColumn && position.x + width >= out.w()) { + const bool lastPixel = position.x < out.w() && width >= 1; + const bool oobPixel = position.x + width > out.w(); + const int numSpecialPixels = (lastPixel ? 1 : 0) + (oobPixel ? 1 : 0); + if (width > numSpecialPixels) { + width -= numSpecialPixels; + RenderOutlineForPixels( + dst, dstPitch, reinterpret_cast(src), width, color); + src += width, dst += width; + } + if (lastPixel) { + RenderOutlineForPixel( + dst++, dstPitch, static_cast(*src++), color); + } + if (oobPixel) { + RenderOutlineForPixel( + dst++, dstPitch, static_cast(*src++), color); + } + return true; + } + return false; + }; + + const auto handleOvershoot = [&]() -> bool { + if (!((ClipWidth || CheckLastColumn || CheckFirstColumn) && v >= remainingWidth)) + return false; + if (handleEdgePixels(remainingWidth)) { + src += v - remainingWidth; + } else { + RenderOutlineForPixels( + dst, dstPitch, reinterpret_cast(src), remainingWidth, color); + src += v; + } + if (ClipWidth && clipX.right != 0) + src = SkipRestOfCelLine(src, srcWidth - clipX.width - (v - remainingWidth)); + return true; + }; + + if (ClipWidth || CheckFirstColumn || ContinueRleRun) { + v = ContinueRleRun ? continueRleRun : static_cast(*src++); + if (ContinueRleRun || !IsCelTransparent(v)) { + if (handleOvershoot()) + return src; + if (!handleEdgePixels(v)) { + RenderOutlineForPixels( + dst, dstPitch, reinterpret_cast(src), v, color); + src += v, dst += v; + } + } else { + v = GetCelTransparentWidth(v); + if ((ClipWidth || CheckLastColumn) && v >= remainingWidth) { + if (clipX.right != 0) + src = SkipRestOfCelLine(src, srcWidth - clipX.width - (v - remainingWidth)); + return src; + } + dst += v; + } + position.x += v; + remainingWidth -= v; + if (!(ClipWidth || CheckLastColumn) && remainingWidth == 0) { + if (ClipWidth && clipX.right != 0) + src = SkipRestOfCelLine(src, srcWidth - clipX.width); + return src; + } + } + + while (ClipWidth || CheckLastColumn || remainingWidth > 0) { + v = static_cast(*src++); + if (!IsCelTransparent(v)) { + if (handleOvershoot()) + return src; + if (!handleEdgePixels(v)) { + RenderOutlineForPixels( + dst, dstPitch, reinterpret_cast(src), v, color); + src += v, dst += v; + } + } else { + v = GetCelTransparentWidth(v); + dst += v; + if ((ClipWidth || CheckLastColumn) && v >= remainingWidth) { + if (clipX.right != 0) + src = SkipRestOfCelLine(src, srcWidth - clipX.width - (v - remainingWidth)); + return src; + } + } + remainingWidth -= v; + position.x += v; + } + + return src; +} + +template +void RenderCelOutlineClippedY(const CelOutputBuffer &out, Point position, const byte *src, std::size_t srcSize, // NOLINT(readability-function-cognitive-complexity) + std::size_t srcWidth, std::uint8_t color) +{ + const auto *srcEnd = src + srcSize; + + // Skip the bottom clipped lines. + const auto dstHeight = out.h(); + while (position.y > dstHeight && src != srcEnd) { + src = SkipRestOfCelLine(src, static_cast(srcWidth)); + --position.y; + } + if (src == srcEnd) + return; + + const ClipX clipX = { 0, 0, static_cast(srcWidth) }; + + if (position.y == dstHeight) { + // After-bottom line - can only draw north. + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + --position.y; + } + if (src == srcEnd) + return; + + if (position.y + 1 == dstHeight) { + // Bottom line - cannot draw south. + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + --position.y; + } + + while (position.y > 0 && src != srcEnd) { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + --position.y; + } + if (src == srcEnd) + return; + + if (position.y == 0) { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + --position.y; + } + if (src == srcEnd) + return; + + if (position.y == -1) { + // Special case: the top of the sprite is 1px below the last line, render just the outline above. + RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + } +} + +template +void RenderCelOutlineClippedXY(const CelOutputBuffer &out, Point position, const byte *src, std::size_t srcSize, // NOLINT(readability-function-cognitive-complexity) + std::size_t srcWidth, std::uint8_t color) +{ + const auto *srcEnd = src + srcSize; + + // Skip the bottom clipped lines. + const auto dstHeight = out.h(); + while (position.y > dstHeight && src != srcEnd) { + src = SkipRestOfCelLine(src, static_cast(srcWidth)); + --position.y; + } + if (src == srcEnd) + return; + + ClipX clipX = CalculateClipX(position.x, srcWidth, out); + if (clipX.width < 0) + return; + if (clipX.left > 0) { + --clipX.left, ++clipX.width; + } else if (clipX.right > 0) { + --clipX.right, ++clipX.width; + } + position.x += static_cast(clipX.left); + + if (position.y == dstHeight) { + // After-bottom line - can only draw north. + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + --position.y; + } + if (src == srcEnd) + return; + + if (position.y + 1 == dstHeight) { + // Bottom line - cannot draw south. + if (position.x <= 0) { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + } else if (position.x + clipX.width >= out.w()) { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + } else { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + } + --position.y; + } + + if (position.x <= 0) { + while (position.y > 0 && src != srcEnd) { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + --position.y; + } + } else if (position.x + clipX.width >= out.w()) { + while (position.y > 0 && src != srcEnd) { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + --position.y; + } + } else { + while (position.y > 0 && src != srcEnd) { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + --position.y; + } + } + if (src == srcEnd) + return; + + if (position.y == 0) { + if (position.x <= 0) { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + } else if (position.x + clipX.width >= out.w()) { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + } else { + src = RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + } + --position.y; + } + if (src == srcEnd) + return; + + if (position.y == -1) { + // Special case: the top of the sprite is 1px below the last line, render just the outline above. + RenderCelOutlineRowClipped( + out, position, src, srcWidth, clipX, color); + } +} + +template +void RenderCelOutline(const CelOutputBuffer &out, Point position, const byte *src, std::size_t srcSize, // NOLINT(readability-function-cognitive-complexity) + std::size_t srcWidth, std::uint8_t color) +{ + if (position.x > 0 && position.x + static_cast(srcWidth) < static_cast(out.w())) { + RenderCelOutlineClippedY(out, position, src, srcSize, srcWidth, color); + } else { + RenderCelOutlineClippedXY(out, position, src, srcSize, srcWidth, color); + } +} + } // namespace void CelDrawTo(const CelOutputBuffer &out, int sx, int sy, const CelSprite &cel, int frame) @@ -306,10 +659,9 @@ void CelBlitLightTransSafeTo(const CelOutputBuffer &out, int sx, int sy, const b } } } - } else { - src += width; - dst += width; } + src += width; + dst += width; } else { width = -static_cast(width); dst += width; @@ -374,53 +726,11 @@ void CelDrawUnsafeTo(const CelOutputBuffer &out, int x, int y, const CelSprite & void CelBlitOutlineTo(const CelOutputBuffer &out, uint8_t col, int sx, int sy, const CelSprite &cel, int frame, bool skipColorIndexZero) { int nDataSize; - const byte *src = CelGetFrameClipped(cel.Data(), frame, &nDataSize); - const auto *end = &src[nDataSize]; - uint8_t *dst = out.at(sx, sy); - const int celWidth = static_cast(cel.Width(frame)); - - for (; src != end; dst -= out.pitch() + celWidth) { - for (int w = celWidth; w > 0;) { - auto width = static_cast(*src++); - if (!IsCelTransparent(width)) { - w -= width; - if (dst < out.end() && dst > out.begin()) { - if (dst >= out.end() - out.pitch()) { - while (width > 0) { - if (!skipColorIndexZero || static_cast(*src) > 0) { - dst[-out.pitch()] = col; - dst[-1] = col; - dst[1] = col; - } - src++; - dst++; - width--; - } - } else { - while (width > 0) { - if (!skipColorIndexZero || static_cast(*src) > 0) { - dst[-out.pitch()] = col; - dst[-1] = col; - dst[1] = col; - dst[out.pitch()] = col; - } - src++; - dst++; - width--; - } - } - } else { - src += width; - dst += width; - } - } else { - width = GetCelTransparentWidth(width); - dst += width; - w -= width; - } - } - } + if (skipColorIndexZero) + RenderCelOutline(out, { sx, sy }, src, nDataSize, cel.Width(frame), col); + else + RenderCelOutline(out, { sx, sy }, src, nDataSize, cel.Width(frame), col); } std::pair MeasureSolidHorizontalBounds(const CelSprite &cel, int frame)