/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #ifndef OD_SR_TRIANGLE_RENDERER_H #define OD_SR_TRIANGLE_RENDERER_H #include "Gs/GsBaseInclude.h" #include "ExGsHelpers.h" #include "SrTriangleRendererData.h" #include #include class OdSrFrameContext; class OdSrTransformationHolder; namespace TriangleRendering { // =========== Main Renderer Class =========== /** * Textured triangle renderer with named parameters */ class OdSrTriangleRenderer { private: // Constants for color handling static const int BYTES_PER_PIXEL_TEXTURE = 4; // BGRA static const int BYTES_PER_PIXEL_FRAME = 3; // BGR OdSrTriangleRendererData::TriangleVertex m_triangleBuffer[3]; OdSrTriangleRendererData& m_rendererData; OdSrFrameContext& m_frameContext; typedef OdSrTriangleRendererData::TriangleVertex TriangleVertex; typedef OdSrTriangleRendererData::RenderSettings RenderSettings; typedef OdSrTriangleRendererData::RenderQuality RenderQuality; void renderTrianglePerspective(const TriangleVertex vertices[3], const RenderSettings& settings, int pixelStep); void renderTriangleOrtho(const TriangleVertex vertices[3], const RenderSettings& settings, int pixelStep); void renderTriangleScanline(OdInt32 x1, OdInt32 y1, OdInt32 x2, OdInt32 y2, OdInt32 x3, OdInt32 y3, ODCOLORREF col); // High-performance pixel access methods inline OdUInt8* getPixelPtr(OdUInt8* pBuffer, OdUInt32 scanLineLength, OdInt32 x, OdInt32 y) const { return pBuffer + scanLineLength * y + x * 3; } // Fast check for pixel visibility (only rectangle clipping) inline bool isPixelInClipRect(OdInt32 x, OdInt32 y) const { const auto& clipRegion = m_rendererData.clipRegion(); // Fast path: no custom clip region if (clipRegion.clipRect.m_min.x == 0 && clipRegion.clipRect.m_max.x == 0 && clipRegion.clipRect.m_min.y == 0 && clipRegion.clipRect.m_max.y == 0) return true; return (x >= clipRegion.clipRect.m_min.x && x <= clipRegion.clipRect.m_max.x && y >= clipRegion.clipRect.m_min.y && y <= clipRegion.clipRect.m_max.y); } // Ultra-fast block fill inline void fillPixelBlock( OdInt32 x, OdInt32 y, OdInt32 maxX, OdInt32 maxY, int pixelStep, OdUInt8 r, OdUInt8 g, OdUInt8 b, OdUInt8 a) const { auto& frameBuffer = m_rendererData.frameBuffer(); for (int fillY = 0; fillY < pixelStep && (y + fillY) <= maxY; fillY++) { for (int fillX = 0; fillX < pixelStep && (x + fillX) <= maxX; fillX++) { int pixelX = x + fillX; int pixelY = y + fillY; auto* pPixel = frameBuffer.m_pData + frameBuffer.m_scanLineLength * pixelY + pixelX * 3; if (a != 255) OdMergeRGBAlpha(pPixel[2], pPixel[1], pPixel[0], r, g, b, a, pPixel[2], pPixel[1], pPixel[0]); else { *(pPixel++) = b; *(pPixel++) = g; *pPixel = r; } } } } // Mask-aware block fill inline void fillPixelBlockMasked( OdInt32 x, OdInt32 y, OdInt32 maxX, OdInt32 maxY, int pixelStep, OdUInt8 r, OdUInt8 g, OdUInt8 b, OdUInt8 a, OdUInt32 visibilityMask) const { auto& frameBuffer = m_rendererData.frameBuffer(); OdUInt32 bit = 1; for (int fillY = 0; fillY < pixelStep && (y + fillY) <= maxY; fillY++) { for (int fillX = 0; fillX < pixelStep && (x + fillX) <= maxX; fillX++) { if (visibilityMask & bit) { int pixelX = x + fillX; int pixelY = y + fillY; auto* pPixel = frameBuffer.m_pData + frameBuffer.m_scanLineLength * pixelY + pixelX * 3; if (a != 255) OdMergeRGBAlpha(pPixel[2], pPixel[1], pPixel[0], r, g, b, a, pPixel[2], pPixel[1], pPixel[0]); else { *(pPixel++) = b; *(pPixel++) = g; *pPixel = r; } bit <<= 1; if (bit == 0) return; // Overflow protection } } } } // Smart pixel block filling that chooses optimal strategy inline void fillPixelBlockSmart( OdInt32 x, OdInt32 y, OdInt32 maxX, OdInt32 maxY, int pixelStep, OdUInt8 r, OdUInt8 g, OdUInt8 b, OdUInt8 a) const { const auto& clipRegion = m_rendererData.clipRegion(); bool hasMask = (clipRegion.clipMask != nullptr); if (!hasMask) fillPixelBlock(x, y, maxX, maxY, pixelStep, r, g, b, a); else { // Mask-aware path OdUInt32 visibilityMask = getBlockVisibility(x, y, pixelStep); if (visibilityMask == 0) return; // Entire block is masked out // Entire small block is visible - use fast fill if (visibilityMask == 0xFFFFFFFF && pixelStep <= 4) fillPixelBlock(x, y, maxX, maxY, pixelStep, r, g, b, a); else fillPixelBlockMasked(x, y, maxX, maxY, pixelStep, r, g, b, a, visibilityMask); } } // Check mask visibility for a block of pixels (returns bitmask) inline OdUInt32 getBlockVisibility(OdInt32 x, OdInt32 y, int blockSize) const { const auto& clipRegion = m_rendererData.clipRegion(); if (clipRegion.clipMask == nullptr) return 0xFFFFFFFF; // All pixels visible OdUInt32 visibilityMask = 0; OdUInt32 bit = 1; for (int dy = 0; dy < blockSize && dy < 32; dy++) { for (int dx = 0; dx < blockSize && (dy * blockSize + dx) < 32; dx++) { OdInt32 maskX = (x + dx) - clipRegion.offsetX; OdInt32 maskY = (y + dy) - clipRegion.offsetY; if (maskX >= 0 && maskX < clipRegion.width && maskY >= 0 && maskY < clipRegion.height) { int maskIndex = maskY * clipRegion.width + maskX; if (clipRegion.clipMask[maskIndex] != 0) visibilityMask |= bit; } bit <<= 1; } } return visibilityMask; } // Expensive but precise pixel check (use sparingly!) inline bool isPixelVisible(OdInt32 x, OdInt32 y) const { if (!isPixelInClipRect(x, y)) return false; const auto& clipRegion = m_rendererData.clipRegion(); if (clipRegion.clipMask == nullptr) return true; OdInt32 maskX = x - clipRegion.offsetX; OdInt32 maskY = y - clipRegion.offsetY; if (maskX >= 0 && maskX < clipRegion.width && maskY >= 0 && maskY < clipRegion.height) { OdInt32 maskIndex = maskY * clipRegion.width + maskX; return clipRegion.clipMask[maskIndex] != 0; } return false; } inline void drawScanline(OdInt32 y, OdInt32 x1, OdInt32 x2, ODCOLORREF color) { const auto& clipRect = m_rendererData.clipRegion().clipRect; const auto& clipMask = m_rendererData.clipRegion().clipMask; const auto& frameBuffer = m_rendererData.frameBuffer(); const auto& clipOffsetX = m_rendererData.clipRegion().offsetX; const auto& clipOffsetY = m_rendererData.clipRegion().offsetY; const auto& clipWidth = m_rendererData.clipRegion().width; if (y <= (int)clipRect.m_max.y && y >= clipRect.m_min.y) { int i; if (x1 > x2) { i = x1; x1 = x2; x2 = i; } if (x1 < clipRect.m_min.x) x1 = clipRect.m_min.x; if (x2 > (int)clipRect.m_max.x) x2 = clipRect.m_max.x; if (x1 <= x2) { OdUInt8 blue = ODGETBLUE(color); OdUInt8 green = ODGETGREEN(color); OdUInt8 red = ODGETRED(color); OdUInt8 alpha = ODGETALPHA(color); OdUInt8* pPtr = getPixelPtr(frameBuffer.m_pData, frameBuffer.m_scanLineLength, x1, y); if (!clipMask) { if (alpha == 255) { for (i = 0; i < x2 - x1 + 1; i++) { *(pPtr++) = blue; *(pPtr++) = green; *(pPtr++) = red; } } else if (alpha != 0) { for (i = 0; i < x2 - x1 + 1; i++) { OdMergeRGBAlpha(pPtr[2], pPtr[1], pPtr[0], red, green, blue, alpha, pPtr[2], pPtr[1], pPtr[0]); pPtr += 3; } } } else { const OdUInt8* pStencil = clipMask + (y - clipOffsetY) * clipWidth + (x1 - clipOffsetX); if (alpha == 255) { for (i = 0; i < x2 - x1 + 1; i++) { if (*pStencil) { *(pPtr++) = blue; *(pPtr++) = green; *(pPtr++) = red; } else { pPtr += 3; } pStencil++; } } else if (alpha != 0) { for (i = 0; i < x2 - x1 + 1; i++) { if (*pStencil) { OdMergeRGBAlpha(pPtr[2], pPtr[1], pPtr[0], red, green, blue, alpha, pPtr[2], pPtr[1], pPtr[0]); } pPtr += 3; pStencil++; } } } } } } bool clipTriangle(TriangleVertex vertices[3]); static void getBoundingBoxWithClipping(const TriangleVertex vertices[3], const OdSrTriangleRendererData& rendererData, OdInt32& minX, OdInt32& minY, OdInt32& maxX, OdInt32& maxY); public: explicit OdSrTriangleRenderer(OdSrTriangleRendererData& data, OdSrFrameContext& frameContext); // Main method for rendering a triangle with settings void renderTriangle( const OdGePoint3d& pt1, const OdGePoint3d& pt2, const OdGePoint3d& pt3, const OdSrTransformationHolder& transformationHolder, const RenderSettings& settings = RenderSettings()); void renderClippedTriangle(const OdGePoint3d screenVertices[3], const OdGePoint3d textureVertices[3], const RenderSettings& settings); void renderTriangleNoClip( const OdGePoint3d& pt1, const OdGePoint3d& pt2, const OdGePoint3d& pt3, const OdSrTransformationHolder& transformationHolder, const RenderSettings& settings); }; }; #endif // OD_SR_TRIANGLE_RENDERER_H