/** * @file common_impl.h * * Common code for implementing various renderers. */ #pragma once #include #include #include "engine.h" #include "engine/point.hpp" #include "engine/render/blit_impl.hpp" #include "lighting.h" #include "utils/attributes.h" namespace devilution { struct ClipX { int_fast16_t left; int_fast16_t right; int_fast16_t width; }; DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT ClipX CalculateClipX(int_fast16_t x, std::size_t w, const Surface &out) { ClipX clip; clip.left = static_cast(x < 0 ? -x : 0); clip.right = static_cast(static_cast(x + w) > out.w() ? x + w - out.w() : 0); clip.width = static_cast(w - clip.left - clip.right); return clip; } // Source data for rendering backwards: first line of input -> last line of output. struct RenderSrcBackwards { const uint8_t *begin; const uint8_t *end; uint_fast16_t width; }; // Source data for rendering forwards: first line of input -> first line of output. struct RenderSrcForwards { const uint8_t *begin; uint_fast16_t width; uint_fast16_t height; }; struct SkipSize { int_fast16_t wholeLines; int_fast16_t xOffset; }; DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT SkipSize GetSkipSize(int_fast16_t overrun, int_fast16_t srcWidth) { SkipSize result; result.wholeLines = overrun / srcWidth; result.xOffset = overrun - srcWidth * result.wholeLines; return result; } using GetBlitCommandFn = BlitCommand (*)(const uint8_t *src); template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT const uint8_t *SkipRestOfLine(const uint8_t *src, unsigned remainingWidth) { while (remainingWidth > 0) { const BlitCommand cmd = GetBlitCommand(src); src = cmd.srcEnd; remainingWidth -= cmd.length; } return src; } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT const uint8_t *SkipRestOfLineWithOverrun( const uint8_t *src, int_fast16_t srcWidth, SkipSize &skipSize) { int_fast16_t remainingWidth = srcWidth - skipSize.xOffset; while (remainingWidth > 0) { const BlitCommand cmd = GetBlitCommand(src); src = cmd.srcEnd; remainingWidth -= cmd.length; } if (remainingWidth < 0) { skipSize = GetSkipSize(-remainingWidth, srcWidth); ++skipSize.wholeLines; } else { skipSize.wholeLines = 1; skipSize.xOffset = 0; } return src; } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void SkipLinesForRenderBackwards( Point &position, RenderSrcBackwards &src, int_fast16_t dstHeight) { while (position.y >= dstHeight && src.begin != src.end) { src.begin = SkipRestOfLine(src.begin, src.width); --position.y; } } // Returns the horizontal overrun. template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT int_fast16_t SkipLinesForRenderBackwardsWithOverrun( Point &position, RenderSrcBackwards &src, int_fast16_t dstHeight) { SkipSize skipSize = { 0, 0 }; while (position.y >= dstHeight && src.begin != src.end) { src.begin = SkipRestOfLineWithOverrun( src.begin, static_cast(src.width), skipSize); position.y -= static_cast(skipSize.wholeLines); } return skipSize.xOffset; } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void SkipLinesForRenderForwards(Point &position, RenderSrcForwards &src, unsigned lineEndPadding) { while (position.y < 0 && src.height != 0) { src.begin = SkipRestOfLine(src.begin, src.width) + lineEndPadding; ++position.y; --src.height; } } template < bool TransparentCommandCanCrossLines, GetBlitCommandFn GetBlitCommand, typename BlitFn> void DoRenderBackwardsClipY( const Surface &out, Point position, RenderSrcBackwards src, BlitFn &&blitFn) { // Skip the bottom clipped lines. int_fast16_t xOffset; if (TransparentCommandCanCrossLines) { xOffset = SkipLinesForRenderBackwardsWithOverrun(position, src, out.h()); } else { SkipLinesForRenderBackwards(position, src, out.h()); } if (src.begin >= src.end) return; auto *dst = &out[position]; const auto *dstBegin = out.begin(); const int dstPitch = out.pitch(); while (src.begin != src.end && dst >= dstBegin) { auto remainingWidth = static_cast(src.width); if (TransparentCommandCanCrossLines) { remainingWidth -= xOffset; dst += xOffset; } while (remainingWidth > 0) { BlitCommand cmd = GetBlitCommand(src.begin); blitFn(cmd, dst, src.begin + 1); src.begin = cmd.srcEnd; dst += cmd.length; remainingWidth -= cmd.length; } dst -= dstPitch + src.width - remainingWidth; if (TransparentCommandCanCrossLines) { if (remainingWidth < 0) { const auto skipSize = GetSkipSize(-remainingWidth, static_cast(src.width)); xOffset = skipSize.xOffset; dst -= skipSize.wholeLines * dstPitch; } else { xOffset = 0; } } } } template < bool TransparentCommandCanCrossLines, GetBlitCommandFn GetBlitCommand, typename BlitFn> void DoRenderBackwardsClipXY( const Surface &out, Point position, RenderSrcBackwards src, ClipX clipX, BlitFn &&blitFn) { // Skip the bottom clipped lines. int_fast16_t xOffset; if (TransparentCommandCanCrossLines) { xOffset = SkipLinesForRenderBackwardsWithOverrun(position, src, out.h()); } else { SkipLinesForRenderBackwards(position, src, out.h()); } if (src.begin >= src.end) return; position.x += static_cast(clipX.left); auto *dst = &out[position]; const auto *dstBegin = out.begin(); const int dstPitch = out.pitch(); while (src.begin != src.end && dst >= dstBegin) { // Skip initial src if clipping on the left. // Handles overshoot, i.e. when the RLE segment goes into the unclipped area. int_fast16_t remainingWidth = clipX.width; int_fast16_t remainingLeftClip = clipX.left; if (TransparentCommandCanCrossLines) { remainingLeftClip -= xOffset; if (remainingLeftClip < 0) { dst += std::min(remainingWidth, -remainingLeftClip); remainingWidth += remainingLeftClip; } } while (remainingLeftClip > 0) { BlitCommand cmd = GetBlitCommand(src.begin); if (static_cast(cmd.length) > remainingLeftClip) { const auto overshoot = static_cast(cmd.length - remainingLeftClip); cmd.length = std::min(remainingWidth, overshoot); blitFn(cmd, dst, src.begin + 1 + remainingLeftClip); dst += cmd.length; remainingWidth -= overshoot; src.begin = cmd.srcEnd; break; } src.begin = cmd.srcEnd; remainingLeftClip -= cmd.length; } while (remainingWidth > 0) { BlitCommand cmd = GetBlitCommand(src.begin); const unsigned unclippedLength = cmd.length; cmd.length = std::min(remainingWidth, cmd.length); blitFn(cmd, dst, src.begin + 1); src.begin = cmd.srcEnd; dst += cmd.length; remainingWidth -= unclippedLength; // result can be negative } // We've advanced `dst` by `clipX.width`. // // Set dst to (position.y - 1, position.x) dst -= dstPitch + clipX.width; if (TransparentCommandCanCrossLines) { // `remainingWidth` can be negative, in which case it is the amount of pixels // that the source has overran the line. remainingWidth += clipX.right; SkipSize skipSize { 0, 0 }; if (remainingWidth > 0) { skipSize.xOffset = static_cast(src.width) - remainingWidth; src.begin = SkipRestOfLineWithOverrun( src.begin, static_cast(src.width), skipSize); --skipSize.wholeLines; } else if (remainingWidth < 0) { skipSize = GetSkipSize(-remainingWidth, static_cast(src.width)); } xOffset = skipSize.xOffset; dst -= dstPitch * skipSize.wholeLines; } else { src.begin = SkipRestOfLine(src.begin, clipX.right + remainingWidth); } } } template < bool TransparentCommandCanCrossLines, GetBlitCommandFn GetBlitCommand, typename BlitFn> void DoRenderBackwards( const Surface &out, Point position, const uint8_t *src, size_t srcSize, unsigned srcWidth, BlitFn &&blitFn) { const ClipX clipX = CalculateClipX(position.x, srcWidth, out); if (clipX.width <= 0) return; RenderSrcBackwards srcForBackwards { src, src + srcSize, static_cast(srcWidth) }; if (static_cast(clipX.width) == srcWidth) { DoRenderBackwardsClipY( out, position, srcForBackwards, std::forward(blitFn)); } else { DoRenderBackwardsClipXY( out, position, srcForBackwards, clipX, std::forward(blitFn)); } } template < GetBlitCommandFn GetBlitCommand, typename BlitFn, typename TransformBlitCommandFn> void DoRenderForwardsClipY( const Surface &out, Point position, RenderSrcForwards src, BlitFn &&blitFn, TransformBlitCommandFn &&transformBlitCommandFn) { // Even padding byte is specific to PCX which is the only renderer that uses this function currently. const unsigned srcLineEndPadding = src.width % 2; SkipLinesForRenderForwards(position, src, srcLineEndPadding); src.height = static_cast(std::min(out.h() - position.y, src.height)); const auto dstSkip = static_cast(out.pitch()); uint8_t *dst = &out[position]; for (unsigned y = 0; y < src.height; y++) { for (unsigned x = 0; x < src.width;) { BlitCommand cmd = GetBlitCommand(src.begin); cmd = transformBlitCommandFn(cmd); blitFn(cmd, dst + x, src.begin + 1); src.begin = cmd.srcEnd; x += cmd.length; } src.begin += srcLineEndPadding; dst += dstSkip; } } template < GetBlitCommandFn GetBlitCommand, typename BlitFn, typename TransformBlitCommandFn> void DoRenderForwardsClipXY( const Surface &out, Point position, RenderSrcForwards src, ClipX clipX, BlitFn &&blitFn, TransformBlitCommandFn &&transformBlitCommandFn) { const unsigned srcLineEndPadding = src.width % 2; SkipLinesForRenderForwards(position, src, srcLineEndPadding); src.height = static_cast(std::min(out.h() - position.y, src.height)); position.x += static_cast(clipX.left); const auto dstSkip = static_cast(out.pitch() - clipX.width); uint8_t *dst = &out[position]; for (unsigned y = 0; y < src.height; y++) { // Skip initial src if clipping on the left. // Handles overshoot, i.e. when the RLE segment goes into the unclipped area. int_fast16_t remainingWidth = clipX.width; auto remainingLeftClip = clipX.left; while (remainingLeftClip > 0) { BlitCommand cmd = GetBlitCommand(src.begin); if (static_cast(cmd.length) > remainingLeftClip) { const uint_fast16_t overshoot = cmd.length - remainingLeftClip; cmd.length = std::min(overshoot, remainingWidth); cmd = transformBlitCommandFn(cmd); blitFn(cmd, dst, src.begin + 1 + remainingLeftClip); src.begin = cmd.srcEnd; dst += cmd.length; remainingWidth -= static_cast(overshoot); break; } src.begin = cmd.srcEnd; remainingLeftClip -= cmd.length; } while (remainingWidth > 0) { BlitCommand cmd = GetBlitCommand(src.begin); const unsigned unclippedLength = cmd.length; cmd = transformBlitCommandFn(cmd); cmd.length = std::min(cmd.length, remainingWidth); blitFn(cmd, dst, src.begin + 1); src.begin = cmd.srcEnd; dst += cmd.length; remainingWidth -= unclippedLength; // result can be negative } src.begin = SkipRestOfLine(src.begin, clipX.right + remainingWidth) + srcLineEndPadding; dst += dstSkip; } } template < GetBlitCommandFn GetBlitCommand, typename BlitFn, typename TransformBlitCommandFn> void DoRenderForwards( const Surface &out, Point position, const uint8_t *src, unsigned srcWidth, unsigned srcHeight, BlitFn &&blitFn, TransformBlitCommandFn &&transformBlitCommandFn) { if (position.y >= out.h() || position.y + srcHeight <= 0) return; const ClipX clipX = CalculateClipX(position.x, srcWidth, out); if (clipX.width <= 0) return; RenderSrcForwards srcForForwards { src, static_cast(srcWidth), static_cast(srcHeight) }; if (static_cast(clipX.width) == srcWidth) { DoRenderForwardsClipY( out, position, srcForForwards, std::forward(blitFn), std::forward(transformBlitCommandFn)); } else { DoRenderForwardsClipXY( out, position, srcForForwards, clipX, std::forward(blitFn), std::forward(transformBlitCommandFn)); } } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixel(uint8_t *dst, int dstPitch, uint8_t color) { if (North) dst[-dstPitch] = color; if (West) dst[-1] = color; if (East) dst[1] = color; if (South) dst[dstPitch] = color; } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixel(uint8_t *dst, int dstPitch, uint8_t srcColor, uint8_t color) { if (SkipColorIndexZero && srcColor == 0) return; RenderOutlineForPixel(dst, dstPitch, color); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixels(uint8_t *dst, int dstPitch, int width, uint8_t color) { if (North) std::memset(dst - dstPitch, color, width); if (West && East) std::memset(dst - 1, color, width + 2); else if (West) std::memset(dst - 1, color, width); else if (East) std::memset(dst + 1, color, width); if (South) std::memset(dst + dstPitch, color, width); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixels(uint8_t *dst, int dstPitch, int width, const uint8_t *src, uint8_t color) { if (SkipColorIndexZero) { while (width-- > 0) RenderOutlineForPixel(dst++, dstPitch, *src++, color); } else { RenderOutlineForPixels(dst, dstPitch, width, color); } } } // namespace devilution