From 64ad9aeacf2dc2c6e40566cc366ea66b610718aa Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Tue, 11 May 2021 06:11:31 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20cl2=5Frender.cpp:=20Clip=20the?= =?UTF-8?q?=20outline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/engine/render/cel_render.cpp | 2 +- Source/engine/render/cl2_render.cpp | 444 +++++++++++++++++++++------- 2 files changed, 338 insertions(+), 108 deletions(-) diff --git a/Source/engine/render/cel_render.cpp b/Source/engine/render/cel_render.cpp index 520ca657c..77b33b3af 100644 --- a/Source/engine/render/cel_render.cpp +++ b/Source/engine/render/cel_render.cpp @@ -533,7 +533,7 @@ void RenderCelOutlineClippedXY(const CelOutputBuffer &out, Point position, const } template -void RenderCelOutline(const CelOutputBuffer &out, Point position, const byte *src, std::size_t srcSize, // NOLINT(readability-function-cognitive-complexity) +void RenderCelOutline(const CelOutputBuffer &out, Point position, const byte *src, std::size_t srcSize, std::size_t srcWidth, std::uint8_t color) { if (position.x > 0 && position.x + static_cast(srcWidth) < static_cast(out.w())) { diff --git a/Source/engine/render/cl2_render.cpp b/Source/engine/render/cl2_render.cpp index e26da509a..187ba3f90 100644 --- a/Source/engine/render/cl2_render.cpp +++ b/Source/engine/render/cl2_render.cpp @@ -21,15 +21,13 @@ namespace { * indicates a fill-N command. */ -constexpr std::uint8_t MaxCl2Width = 65; - constexpr bool IsCl2Opaque(std::uint8_t control) { constexpr std::uint8_t Cl2OpaqueMin = 0x80; return control >= Cl2OpaqueMin; } -constexpr std::uint8_t GetCl2Cl2OpaquePixelsWidth(std::uint8_t control) +constexpr std::uint8_t GetCl2OpaquePixelsWidth(std::uint8_t control) { return -static_cast(control); } @@ -72,7 +70,7 @@ const byte *SkipRestOfCl2Line( remainingWidth -= GetCl2OpaqueFillWidth(v); ++src; } else { - v = GetCl2Cl2OpaquePixelsWidth(v); + v = GetCl2OpaquePixelsWidth(v); src += v; remainingWidth -= v; } @@ -123,7 +121,7 @@ void RenderCl2ClipY(const CelOutputBuffer &out, Point position, const byte *src, v = GetCl2OpaqueFillWidth(v); renderFill(dst, static_cast(*src++), v); } else { - v = GetCl2Cl2OpaquePixelsWidth(v); + v = GetCl2OpaquePixelsWidth(v); renderPixels(dst, reinterpret_cast(src), v); src += v; } @@ -187,7 +185,7 @@ void RenderCl2ClipXY( // NOLINT(readability-function-cognitive-complexity) } ++src; } else { - v = GetCl2Cl2OpaquePixelsWidth(v); + v = GetCl2OpaquePixelsWidth(v); if (v > remainingLeftClip) { const auto overshoot = v - remainingLeftClip; renderPixels(dst, reinterpret_cast(src + remainingLeftClip), overshoot); @@ -213,7 +211,7 @@ void RenderCl2ClipXY( // NOLINT(readability-function-cognitive-complexity) v = GetCl2OpaqueFillWidth(v); renderFill(dst, static_cast(*src++), std::min(remainingWidth, static_cast(v))); } else { - v = GetCl2Cl2OpaquePixelsWidth(v); + v = GetCl2OpaquePixelsWidth(v); renderPixels(dst, reinterpret_cast(src), std::min(remainingWidth, static_cast(v))); src += v; } @@ -291,89 +289,6 @@ void Cl2BlitSafe(const CelOutputBuffer &out, int sx, int sy, const byte *pRLEByt ); } -/** - * @brief Blit a solid colder shape one pixel larger then the given sprite shape, to the given buffer - * @param out Target buffer - * @param sx Target buffer coordinate - * @param sy Target buffer coordinate - * @param pRLEBytes CL2 pixel stream (run-length encoded) - * @param nDataSize Size of CL2 in bytes - * @param nWidth Width of sprite - * @param col Color index from current palette - */ -void Cl2BlitOutlineSafe(const CelOutputBuffer &out, int sx, int sy, const byte *pRLEBytes, int nDataSize, int nWidth, uint8_t col) -{ - const byte *src = pRLEBytes; - BYTE *dst = out.at(sx, sy); - int w = nWidth; - - while (nDataSize > 0) { - auto width = static_cast(*src++); - nDataSize--; - if (width < 0) { - width = -width; - if (width > MaxCl2Width) { - width -= MaxCl2Width; - nDataSize--; - if (static_cast(*src++) != 0 && dst < out.end() && dst > out.begin()) { - w -= width; - dst[-1] = col; - dst[width] = col; - while (width > 0) { - dst[-out.pitch()] = col; - dst[out.pitch()] = col; - dst++; - width--; - } - if (w == 0) { - w = nWidth; - dst -= out.pitch() + w; - } - continue; - } - } else { - nDataSize -= width; - if (dst < out.end() && dst > out.begin()) { - w -= width; - while (width > 0) { - if (static_cast(*src) != 0) { - dst[-1] = col; - dst[1] = col; - dst[-out.pitch()] = col; - // BUGFIX: only set `if (dst+out.pitch() < out.end())` - dst[out.pitch()] = col; - } - src++; - dst++; - width--; - } - if (w == 0) { - w = nWidth; - dst -= out.pitch() + w; - } - continue; - } - src += width; - } - } - while (width > 0) { - if (width > w) { - dst += w; - width -= w; - w = 0; - } else { - dst += width; - w -= width; - width = 0; - } - if (w == 0) { - w = nWidth; - dst -= out.pitch() + w; - } - } - } -} - /** * @brief Blit CL2 sprite, and apply lighting, to the given buffer * @param out Target buffer @@ -407,6 +322,321 @@ void Cl2BlitLightSafe(const CelOutputBuffer &out, int sx, int sy, const byte *pR ); } +template +void RenderOutlineForPixel(std::uint8_t *dst, int dstPitch, std::uint8_t color) +{ + if (North) + dst[-dstPitch] = color; + if (West) + dst[-1] = color; + if (East) + dst[1] = color; + if (South) + dst[dstPitch] = color; +} + +template +void RenderOutlineForPixels(std::uint8_t *dst, int dstPitch, int width, std::uint8_t color) +{ + while (width-- > 0) + RenderOutlineForPixel(dst++, dstPitch, color); +} + +template +const byte *RenderCl2OutlineRowClipped( // NOLINT(readability-function-cognitive-complexity,misc-no-recursion) + const CelOutputBuffer &out, Point position, const byte *src, std::size_t srcWidth, + ClipX clipX, std::uint8_t color, SkipSize &skipSize) +{ + std::int_fast16_t remainingWidth = clipX.width; + std::uint8_t v; + + auto *dst = &out[position]; + const auto dstPitch = out.pitch(); + + const auto renderPixels = [&](std::uint8_t width) { // NOLINT(readability-function-cognitive-complexity) + if (CheckFirstColumn && position.x <= 0) { + if (position.x == -1) { + RenderOutlineForPixel(dst++, dstPitch, color); + --width; + } + if (width > 0) { + RenderOutlineForPixel(dst++, dstPitch, color); + --width; + } + if (width > 0) { + RenderOutlineForPixels(dst, dstPitch, width, color); + dst += width; + } + } else 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, width, color); + dst += width; + } + if (lastPixel) { + RenderOutlineForPixel(dst++, dstPitch, color); + } + if (oobPixel) { + RenderOutlineForPixel(dst++, dstPitch, color); + } + } else { + RenderOutlineForPixels(dst, dstPitch, width, color); + dst += width; + } + }; + + if (ClipWidth) { + auto remainingLeftClip = clipX.left - skipSize.xOffset; + if (skipSize.xOffset > clipX.left) { + position.x += static_cast(skipSize.xOffset - clipX.left); + dst += skipSize.xOffset - clipX.left; + } + while (remainingLeftClip > 0) { + v = static_cast(*src++); + if (IsCl2Opaque(v)) { + if (IsCl2OpaqueFill(v)) { + v = GetCl2OpaqueFillWidth(v); + ++src; + } else { + v = GetCl2OpaquePixelsWidth(v); + src += v; + } + if (v > remainingLeftClip) { + const std::uint8_t overshoot = v - remainingLeftClip; + renderPixels(overshoot); + position.x += overshoot; + } + } else { + if (v > remainingLeftClip) { + const std::uint8_t overshoot = v - remainingLeftClip; + dst += overshoot; + position.x += overshoot; + } + } + remainingLeftClip -= v; + } + remainingWidth += remainingLeftClip; + } else { + position.x += static_cast(skipSize.xOffset); + dst += skipSize.xOffset; + remainingWidth -= skipSize.xOffset; + } + + while (remainingWidth > 0) { + v = static_cast(*src++); + if (IsCl2Opaque(v)) { + if (IsCl2OpaqueFill(v)) { + v = GetCl2OpaqueFillWidth(v); + ++src; + } else { + v = GetCl2OpaquePixelsWidth(v); + src += v; + } + renderPixels(ClipWidth ? std::min(remainingWidth, static_cast(v)) : v); + } else { + dst += v; + } + remainingWidth -= v; + position.x += v; + } + + if (ClipWidth) { + remainingWidth += clipX.right; + if (remainingWidth > 0) { + src = SkipRestOfCl2Line(src, static_cast(srcWidth), + remainingWidth, skipSize); + if (skipSize.wholeLines > 1) + dst -= dstPitch * (skipSize.wholeLines - 1); + remainingWidth = -skipSize.xOffset; + } + } + if (remainingWidth < 0) { + skipSize = GetSkipSize(-remainingWidth, static_cast(srcWidth)); + ++skipSize.wholeLines; + } else { + skipSize.xOffset = 0; + skipSize.wholeLines = 1; + } + + return src; +} + +void RenderCl2OutlineClippedY(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(); + SkipSize skipSize = { 0, 0 }; + while (position.y >= dstHeight && src != srcEnd) { + src = SkipRestOfCl2Line(src, static_cast(srcWidth), + static_cast(srcWidth - skipSize.xOffset), skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + if (src == srcEnd) + return; + + const ClipX clipX = { 0, 0, static_cast(srcWidth) }; + + if (position.y == dstHeight) { + // After-bottom line - can only draw north. + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + if (src == srcEnd) + return; + + if (position.y + 1 == dstHeight) { + // Bottom line - cannot draw south. + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + + while (position.y > 0 && src != srcEnd) { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + if (src == srcEnd) + return; + + if (position.y == 0) { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + 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. + RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + } +} + +void RenderCl2OutlineClippedXY(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(); + SkipSize skipSize = { 0, 0 }; + while (position.y >= dstHeight && src != srcEnd) { + src = SkipRestOfCl2Line(src, static_cast(srcWidth), + static_cast(srcWidth - skipSize.xOffset), skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + 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 = RenderCl2OutlineRowClipped(out, position, src, srcWidth, clipX, color, skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + if (src == srcEnd) + return; + + if (position.y + 1 == dstHeight) { + // Bottom line - cannot draw south. + if (position.x <= 0) { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + } else if (position.x + clipX.width >= out.w()) { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + } else { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + } + position.y -= static_cast(skipSize.wholeLines); + } + + if (position.x <= 0) { + while (position.y > 0 && src != srcEnd) { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + } else if (position.x + clipX.width >= out.w()) { + while (position.y > 0 && src != srcEnd) { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + } else { + while (position.y > 0 && src != srcEnd) { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + position.y -= static_cast(skipSize.wholeLines); + } + } + if (src == srcEnd) + return; + + if (position.y == 0) { + if (position.x <= 0) { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + } else if (position.x + clipX.width >= out.w()) { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + } else { + src = RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + } + position.y -= static_cast(skipSize.wholeLines); + } + 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. + RenderCl2OutlineRowClipped( + out, position, src, srcWidth, clipX, color, skipSize); + } +} + +void RenderCl2Outline(const CelOutputBuffer &out, Point position, const byte *src, std::size_t srcSize, + std::size_t srcWidth, std::uint8_t color) +{ + if (position.x > 0 && position.x + static_cast(srcWidth) < static_cast(out.w())) { + RenderCl2OutlineClippedY(out, position, src, srcSize, srcWidth, color); + } else { + RenderCl2OutlineClippedXY(out, position, src, srcSize, srcWidth, color); + } +} + } // namespace void Cl2ApplyTrans(byte *p, const std::array &ttbl, int nCel) @@ -414,27 +644,28 @@ void Cl2ApplyTrans(byte *p, const std::array &ttbl, int nCel) assert(p != nullptr); for (int i = 1; i <= nCel; i++) { + constexpr int FrameHeaderSize = 10; int nDataSize; - byte *dst = CelGetFrame(p, i, &nDataSize) + 10; - nDataSize -= 10; + byte *dst = CelGetFrame(p, i, &nDataSize) + FrameHeaderSize; + nDataSize -= FrameHeaderSize; while (nDataSize > 0) { - auto width = static_cast(*dst++); + auto v = static_cast(*dst++); nDataSize--; assert(nDataSize >= 0); - if (width < 0) { - width = -width; - if (width > MaxCl2Width) { - nDataSize--; - assert(nDataSize >= 0); + if (!IsCl2Opaque(v)) + continue; + if (IsCl2OpaqueFill(v)) { + nDataSize--; + assert(nDataSize >= 0); + *dst = static_cast(ttbl[static_cast(*dst)]); + dst++; + } else { + v = GetCl2OpaquePixelsWidth(v); + nDataSize -= v; + assert(nDataSize >= 0); + while (v-- > 0) { *dst = static_cast(ttbl[static_cast(*dst)]); dst++; - } else { - nDataSize -= width; - assert(nDataSize >= 0); - for (; width > 0; width--) { - *dst = static_cast(ttbl[static_cast(*dst)]); - dst++; - } } } } @@ -458,8 +689,7 @@ void Cl2DrawOutline(const CelOutputBuffer &out, uint8_t col, int sx, int sy, con int nDataSize; const byte *pRLEBytes = CelGetFrameClipped(cel.Data(), frame, &nDataSize); - const CelOutputBuffer &sub = out.subregionY(0, out.h() - 1); - Cl2BlitOutlineSafe(sub, sx, sy, pRLEBytes, nDataSize, cel.Width(frame), col); + RenderCl2Outline(out, { sx, sy }, pRLEBytes, nDataSize, cel.Width(frame), col); } void Cl2DrawLightTbl(const CelOutputBuffer &out, int sx, int sy, const CelSprite &cel, int frame, char light)