diff --git a/omm-sdk/include/omm.h b/omm-sdk/include/omm.h index ba1dadb..e73517f 100644 --- a/omm-sdk/include/omm.h +++ b/omm-sdk/include/omm.h @@ -15,8 +15,8 @@ license agreement from NVIDIA CORPORATION is strictly prohibited. #include #define OMM_VERSION_MAJOR 1 -#define OMM_VERSION_MINOR 5 -#define OMM_VERSION_BUILD 1 +#define OMM_VERSION_MINOR 6 +#define OMM_VERSION_BUILD 0 #define OMM_MAX_TRANSIENT_POOL_BUFFERS 8 @@ -420,8 +420,13 @@ typedef struct ommCpuBakeInputDesc const ommFormat* formats; // Determines how to promote mixed states ommUnknownStatePromotion unknownStatePromotion; - // Determines the state of unresolvable/degenerate triangles (nan/inf or zeroa area UV-triangles) - ommSpecialIndex degenTriState; + // Determines the state of unresolvable(nan/inf UV-triangles) and disabled triangles. Note that degenerate triangles (points/lines) will be resolved correctly. + union + { + OMM_DEPRECATED_MSG("unresolvedTriState has been deprecated, please use unresolvedTriState instead") + ommSpecialIndex degenTriState; + ommSpecialIndex unresolvedTriState; + }; // Micro triangle count is 4^N, where N is the subdivision level. // maxSubdivisionLevel level must be in range [0, 12]. // When dynamicSubdivisionScale is enabled maxSubdivisionLevel is the max subdivision level allowed. @@ -466,7 +471,7 @@ inline ommCpuBakeInputDesc ommCpuBakeInputDescDefault() v.format = ommFormat_OC1_4_State; v.formats = NULL; v.unknownStatePromotion = ommUnknownStatePromotion_ForceOpaque; - v.degenTriState = ommSpecialIndex_FullyUnknownOpaque; + v.unresolvedTriState = ommSpecialIndex_FullyUnknownOpaque; v.maxSubdivisionLevel = 8; v.subdivisionLevels = NULL; v.maxWorkloadSize = 0xFFFFFFFFFFFFFFFF; diff --git a/omm-sdk/include/omm.hpp b/omm-sdk/include/omm.hpp index 0cecc0d..3d635f4 100644 --- a/omm-sdk/include/omm.hpp +++ b/omm-sdk/include/omm.hpp @@ -316,8 +316,13 @@ namespace omm const Format* formats = nullptr; // Determines how to promote mixed states UnknownStatePromotion unknownStatePromotion = UnknownStatePromotion::ForceOpaque; - // Determines the state of unresolvable/degenerate triangles (nan/inf or zeroa area UV-triangles) - SpecialIndex degenTriState = SpecialIndex::FullyUnknownOpaque; + // Determines the state of unresolvable(nan/inf UV-triangles) and disabled triangles. Note that degenerate triangles (points/lines) will be resolved correctly. + union + { + OMM_DEPRECATED_MSG("degenTriState has been deprecated, please use unresolvedTriState instead") + omm::SpecialIndex degenTriState; + omm::SpecialIndex unresolvedTriState = SpecialIndex::FullyUnknownOpaque; + }; // Micro triangle count is 4^N, where N is the subdivision level. // maxSubdivisionLevel level must be in range [0, 12]. // When dynamicSubdivisionScale is enabled maxSubdivisionLevel is the max subdivision level allowed. diff --git a/omm-sdk/src/bake_cpu_impl.cpp b/omm-sdk/src/bake_cpu_impl.cpp index 61094f9..71b73bf 100644 --- a/omm-sdk/src/bake_cpu_impl.cpp +++ b/omm-sdk/src/bake_cpu_impl.cpp @@ -46,6 +46,7 @@ namespace Cpu DisableLevelLineIntersection = 1u << 8, DisableFineClassification = 1u << 9, EnableNearDuplicateDetectionBruteForce = 1u << 10, + EnableEdgeHeuristic = 1u << 11, }; constexpr void ValidateInternalBakeFlags() @@ -70,7 +71,8 @@ namespace Cpu enableAABBTesting(((uint32_t)flags& (uint32_t)BakeFlagsInternal::EnableAABBTesting) == (uint32_t)BakeFlagsInternal::EnableAABBTesting), disableRemovePoorQualityOMM(((uint32_t)flags& (uint32_t)BakeFlagsInternal::DisableRemovePoorQualityOMM) == (uint32_t)BakeFlagsInternal::DisableRemovePoorQualityOMM), disableLevelLineIntersection(((uint32_t)flags& (uint32_t)BakeFlagsInternal::DisableLevelLineIntersection) == (uint32_t)BakeFlagsInternal::DisableLevelLineIntersection), - disableFineClassification(((uint32_t)flags& (uint32_t)BakeFlagsInternal::DisableFineClassification) == (uint32_t)BakeFlagsInternal::DisableFineClassification) + disableFineClassification(((uint32_t)flags& (uint32_t)BakeFlagsInternal::DisableFineClassification) == (uint32_t)BakeFlagsInternal::DisableFineClassification), + enableEdgeHeuristic(((uint32_t)flags& (uint32_t)BakeFlagsInternal::EnableEdgeHeuristic) == (uint32_t)BakeFlagsInternal::EnableEdgeHeuristic) { } const bool enableInternalThreads; const bool disableSpecialIndices; @@ -82,6 +84,7 @@ namespace Cpu const bool disableRemovePoorQualityOMM; const bool disableLevelLineIntersection; const bool disableFineClassification; + const bool enableEdgeHeuristic; }; BakerImpl::~BakerImpl() @@ -411,7 +414,7 @@ namespace Cpu return GetArea2D(uvTri.p0, uvTri.p1, uvTri.p2); }; - static const uint32_t CalculateSuitableSubdivisionLevel(const ommCpuBakeInputDesc& desc, const Triangle& uvTri, uint2 texSize) + static const uint32_t ComputeAreaHeuristic(const ommCpuBakeInputDesc& desc, const Triangle& uvTri, uint2 texSize) { auto GetNextPow2 = [](uint v)->uint { @@ -442,7 +445,6 @@ namespace Cpu // Solves the following eqn: // targetPixelArea / (4^N) = pixelUvArea - // Questionable heuristic... micro-triangle should cover 8x8 pixel region? const float targetPixelArea = desc.dynamicSubdivisionScale * desc.dynamicSubdivisionScale; const uint ratio = uint(pixelUvArea / targetPixelArea); const uint ratioNextPow2 = GetNextPow2(ratio); @@ -453,19 +455,38 @@ namespace Cpu return std::min(SubdivisionLevel, desc.maxSubdivisionLevel); } - static bool IsDegenerate(const Triangle& t) + static const uint32_t ComputeEdgeHeuristic(const ommCpuBakeInputDesc& desc, const Triangle& uvTri, uint2 texSize) { - const bool anyNan = glm::any(glm::isnan(t.p0)) || glm::any(glm::isnan(t.p1)) || glm::any(glm::isnan(t.p2)); - const bool anyInf = glm::any(glm::isinf(t.p0)) || glm::any(glm::isinf(t.p1)) || glm::any(glm::isinf(t.p2)); - - const float3 N = glm::cross(float3(t.p2 - t.p0, 0), float3(t.p1 - t.p0, 0)); - const float N2 = N.z * N.z; - const bool bIsZeroArea = N2 < 1e-9; - - return anyNan || anyInf || bIsZeroArea; + // Adapted from 3.1.1 https://fileadmin.cs.lth.se/graphics/research/papers/2024/succinct_opacity_micromaps/paper-author-version.pdf + const float2 ve0 = (float2)texSize * (uvTri.p1 - uvTri.p0); + const float2 ve1 = (float2)texSize * (uvTri.p2 - uvTri.p0); + const float2 ve2 = (float2)texSize * (uvTri.p2 - uvTri.p1); + + const float le0 = glm::dot(ve0, ve0); + const float le1 = glm::dot(ve1, ve1); + const float le2 = glm::dot(ve2, ve2); + + const float eMax = std::max({ le0, le1, le2 }); + + const float n = eMax < 1e-6 ? 0 : std::log2(eMax) / 2.f - std::log2(desc.dynamicSubdivisionScale); + + const int SubdivisionLevel = (int)std::ceil(n); + return std::clamp(SubdivisionLevel, 0, desc.maxSubdivisionLevel); + } + + static const uint32_t CalculateSuitableSubdivisionLevel(const ommCpuBakeInputDesc& desc, const Options& options, const Triangle& uvTri, uint2 texSize) + { + if (uvTri.GetIsDegenerate() || options.enableEdgeHeuristic) + { + return ComputeEdgeHeuristic(desc, uvTri, texSize); + } + else + { + return ComputeAreaHeuristic(desc, uvTri, texSize); + } } - static int32_t GetSubdivisionLevelForPrimitive(const ommCpuBakeInputDesc& desc, uint32_t i, const Triangle& uvTri, uint2 texSize) + static int32_t GetSubdivisionLevelForPrimitive(const ommCpuBakeInputDesc& desc, const Options& options, uint32_t i, const Triangle& uvTri, uint2 texSize) { if (desc.subdivisionLevels && desc.subdivisionLevels[i] <= 12) { @@ -477,7 +498,7 @@ namespace Cpu if (enableDynamicSubdivisionLevel) { - return CalculateSuitableSubdivisionLevel(desc, uvTri, texSize); + return CalculateSuitableSubdivisionLevel(desc, options, uvTri, texSize); } else { @@ -485,6 +506,21 @@ namespace Cpu } } + static bool GetIsInvalid(const Options& options, const Triangle& uvTriangle) + { + if (uvTriangle.GetIsInvalid()) + { + return true; + } + + if (options.disableLevelLineIntersection && uvTriangle.GetIsDegenerate()) + { + return true; // we only support degen triangles in level line intersection mode. + } + + return false; + } + namespace impl { static ommResult SetupWorkItems( @@ -506,7 +542,7 @@ namespace Cpu { const uint32_t texCoordStrideInBytes = desc.texCoordStrideInBytes == 0 ? GetTexCoordFormatSize(desc.texCoordFormat) : desc.texCoordStrideInBytes; - uint32_t numDegenTri = 0; + uint32_t numDisabledTri = 0; for (int32_t i = 0; i < triangleCount; ++i) { @@ -515,14 +551,13 @@ namespace Cpu const Triangle uvTri = FetchUVTriangle(desc.texCoords, texCoordStrideInBytes, desc.texCoordFormat, triangleIndices); - const int32_t subdivisionLevel = GetSubdivisionLevelForPrimitive(desc, i, uvTri, texture->GetSize(0 /*always based on mip 0*/)); + const int32_t subdivisionLevel = GetSubdivisionLevelForPrimitive(desc, options, i, uvTri, texture->GetSize(0 /*always based on mip 0*/)); const bool bIsDisabled = subdivisionLevel == kDisabledPrimitive; - const bool bIsDegenerate = IsDegenerate(uvTri); - - if (bIsDisabled || bIsDegenerate) + + if (bIsDisabled || GetIsInvalid(options, uvTri)) { - numDegenTri++; + numDisabledTri++; continue; // These indices will be set to special index unknown later. } @@ -556,11 +591,11 @@ namespace Cpu } } - if (options.enableValidation && numDegenTri != 0) + if (options.enableValidation && numDisabledTri != 0) { - const char* specialIndex = ToString(desc.degenTriState); - log.Infof("[Info] - The workload consists of %d degenerate triangles, these will be classified as Fully Unknown Opaque (this behaviour can be changed by degenTriState).", - numDegenTri, specialIndex); + const char* specialIndex = ToString(desc.unresolvedTriState); + log.Infof("[Info] - The workload consists of %d unclassifiable triangles, these will be classified as unresolvedTriState = %s.", + numDisabledTri, specialIndex); } } return ommResult_SUCCESS; @@ -714,7 +749,13 @@ namespace Cpu return ommResult_SUCCESS; } - template + enum TriangleClass + { + Normal, + Degenerate + }; + + template static ommResult ResampleFine(const ommCpuBakeInputDesc& desc, const Logger& log, const Options& options, vector& vmWorkItems) { if (options.enableAABBTesting && !options.disableLevelLineIntersection) @@ -738,6 +779,17 @@ namespace Cpu { // Subdivide the input triangle in to smaller triangles. They will be "bird-curve" ordered. OmmWorkItem& workItem = vmWorkItems[workItemIt]; + const bool isDegenerate = workItem.uvTri.GetIsDegenerate(); + + if (eTriangleClass == TriangleClass::Normal && isDegenerate) + { + continue; + } + + if (eTriangleClass == TriangleClass::Degenerate && !isDegenerate) + { + continue; + } const uint32_t numMicroTriangles = omm::bird::GetNumMicroTriangles(workItem.subdivisionLevel); @@ -779,8 +831,18 @@ namespace Cpu else vmCoverage.numBelowAlpha++; - auto kernel = &LevelLineIntersectionKernel::run; - RasterizeConservativeSerialWithOffsetCoverage(subTri, rasterSize, pixelOffset, kernel, ¶ms); + + if constexpr (eTriangleClass == TriangleClass::Normal) + { + auto kernel = &LevelLineIntersectionKernel::run; + RasterizeConservativeSerialWithOffsetCoverage(subTri, rasterSize, pixelOffset, kernel, ¶ms); + } + else + { + auto kernel = &LevelLineIntersectionKernel::run; + Line l(subTri.aabb_s, subTri.aabb_e); + RasterizeConservativeLineWithOffset(l, rasterSize, pixelOffset, kernel, ¶ms); + } OMM_ASSERT(vmCoverage.numAboveAlpha != 0 || vmCoverage.numBelowAlpha != 0); const ommOpacityState state = GetStateFromCoverage(desc.format, desc.unknownStatePromotion, desc.alphaCutoffGreater, desc.alphaCutoffLessEqual, vmCoverage); @@ -867,7 +929,7 @@ namespace Cpu params.vmState = &vmCoverage; - auto kernel = [](int2 pixel, float3* bc, void* ctx) + auto kernel = [](int2 pixel, void* ctx) { KernelParams* p = (KernelParams*)ctx; @@ -1513,7 +1575,7 @@ namespace Cpu // Set special indices... { res.ommIndexBuffer.resize(triangleCount); - std::fill(res.ommIndexBuffer.begin(), res.ommIndexBuffer.end(), (int32_t)desc.degenTriState); + std::fill(res.ommIndexBuffer.begin(), res.ommIndexBuffer.end(), (int32_t)desc.unresolvedTriState); for (const OmmWorkItem& vm : vmWorkItems) { for (uint32_t primitiveIndex : vm.primitiveIndices) @@ -1564,8 +1626,12 @@ namespace Cpu return impl::ResampleCoarse(desc, log, options, vmWorkItems); }; - auto impl__ResampleFine = [](const ommCpuBakeInputDesc& desc, const Logger& log, const Options& options, vector& vmWorkItems) { - return impl::ResampleFine(desc, log, options, vmWorkItems); + auto impl__ResampleFineNormal = [](const ommCpuBakeInputDesc& desc, const Logger& log, const Options& options, vector& vmWorkItems) { + return impl::ResampleFine(desc, log, options, vmWorkItems); + }; + + auto impl__ResampleFineDegen = [](const ommCpuBakeInputDesc& desc, const Logger& log, const Options& options, vector& vmWorkItems) { + return impl::ResampleFine(desc, log, options, vmWorkItems); }; { @@ -1577,7 +1643,9 @@ namespace Cpu RETURN_STATUS_IF_FAILED(impl__ResampleCoarse(desc, m_log, options, vmWorkItems)); - RETURN_STATUS_IF_FAILED(impl__ResampleFine(desc, m_log, options, vmWorkItems)); + RETURN_STATUS_IF_FAILED(impl__ResampleFineNormal(desc, m_log, options, vmWorkItems)); + + RETURN_STATUS_IF_FAILED(impl__ResampleFineDegen(desc, m_log, options, vmWorkItems)); RETURN_STATUS_IF_FAILED(impl::PromoteToSpecialIndices(desc, options, vmWorkItems)); @@ -1604,4 +1672,4 @@ namespace Cpu } } // namespace Cpu -} // namespace omm \ No newline at end of file +} // namespace omm diff --git a/omm-sdk/src/bake_kernels_cpu.h b/omm-sdk/src/bake_kernels_cpu.h index 3894832..31a6454 100644 --- a/omm-sdk/src/bake_kernels_cpu.h +++ b/omm-sdk/src/bake_kernels_cpu.h @@ -80,17 +80,17 @@ struct LevelLineIntersectionKernel struct Triangle { // private - float _Sign(float2 p1, float2 p2, float2 p3) + float _Sign(const float2& p1, const float2& p2, const float2& p3) { return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); } // public - void Init(float2 v0, float2 v1, float2 v2) + void Init(const float2& v0, const float2& v1, const float2& v2) { _v0 = v0; _v1 = v1; _v2 = v2; } - bool PointInTriangle(float2 pt) + bool PointInTriangle(const float2& pt) { float d1, d2, d3; bool has_neg, has_pos; @@ -236,8 +236,8 @@ struct LevelLineIntersectionKernel } public: - template - static void run(int2 pixel, float3* bc, Coverage coverage, void* ctx) + template + static void run(int2 pixel, void* ctx) { // We add +0.5 here in order to compensate for the raster offset. const float2 pixelf = (float2)pixel + 0.5f; @@ -258,12 +258,14 @@ struct LevelLineIntersectionKernel // ~~~ Look for internal extremes ~~~ + if (!bIsDegenerate) { const bool IsOpaque0 = p->alphaCutoff < gatherRed.x; const bool IsOpaque1 = p->alphaCutoff < gatherRed.y; const bool IsOpaque2 = p->alphaCutoff < gatherRed.z; const bool IsOpaque3 = p->alphaCutoff < gatherRed.w; + Triangle t; t.Init(p->triangle->p0, p->triangle->p1, p->triangle->p2); @@ -333,11 +335,11 @@ struct LevelLineIntersectionKernel } else { - for (uint32_t edge = 0; edge < 3; ++edge) { - + if (bIsDegenerate) + { // Transform the edge to the local coordinate system of the texel. - const float2 p0 = (float2)p->size * p->triangle->getP(edge % 3) - pixelf; - const float2 p1 = (float2)p->size * p->triangle->getP((edge + 1) % 3) - pixelf; + const float2 p0 = (float2)p->size * p->triangle->aabb_s - pixelf; + const float2 p1 = (float2)p->size * p->triangle->aabb_e - pixelf; // Hyperbolic paraboloid (3D surface) => Hyperbola (2D line) // f(x, y) = a + b * x + c * y + d * x * y where f(x, y) = p->alphaCutoff => @@ -348,9 +350,30 @@ struct LevelLineIntersectionKernel { p->vmCoverage->numAboveAlpha += 1; p->vmCoverage->numBelowAlpha += 1; - break; } } + else + { + for (uint32_t edge = 0; edge < 3; ++edge) + { + // Transform the edge to the local coordinate system of the texel. + const float2 p0 = (float2)p->size * p->triangle->getP(edge % 3) - pixelf; + const float2 p1 = (float2)p->size * p->triangle->getP((edge + 1) % 3) - pixelf; + + // Hyperbolic paraboloid (3D surface) => Hyperbola (2D line) + // f(x, y) = a + b * x + c * y + d * x * y where f(x, y) = p->alphaCutoff => + // a - alpha + b * x + c * y + d * x * y = 0 + const float4 h(a - p->alphaCutoff, b, c, d); + + if (TestEdgeHyperbolaIntersection(p0, p1, h)) + { + p->vmCoverage->numAboveAlpha += 1; + p->vmCoverage->numBelowAlpha += 1; + break; + } + } + } + } } } @@ -371,7 +394,7 @@ struct ConservativeBilinearKernel }; template - static void run(int2 pixel, float3* bc, Coverage coverage, void* ctx) + static void run(int2 pixel, void* ctx) { // We add +0.5 here in order to compensate for the raster offset. const float2 pixelf = (float2)pixel + 0.5f; diff --git a/omm-sdk/src/debug_impl.cpp b/omm-sdk/src/debug_impl.cpp index 59b2c6f..2879589 100644 --- a/omm-sdk/src/debug_impl.cpp +++ b/omm-sdk/src/debug_impl.cpp @@ -14,7 +14,7 @@ license agreement from NVIDIA CORPORATION is strictly prohibited. #include #include -#include +#include #include #include #include @@ -189,7 +189,38 @@ namespace omm uint32_t triangleIndices[3]; GetUInt32Indices(desc.indexFormat, desc.indexBuffer, 3ull * primIt, triangleIndices); - omm::Triangle macroTriangle = FetchUVTriangle(desc.texCoords, texCoordStrideInBytes, desc.texCoordFormat, triangleIndices); + omm::Triangle t = FetchUVTriangle(desc.texCoords, texCoordStrideInBytes, desc.texCoordFormat, triangleIndices); + + if (t.GetIsDegenerate()) + { + // Takes a degenerate triangle and turns it to a generate(?) one by extruding the center point a bit perpendicular. + auto DeDegenerateTriangle = [](const float2& p0, const float2& pMiddle, const float2& p2)->float2 { + const float2 pDist = p2 - p0; + const float2 offsetVec = float2(pDist.y, -pDist.x); + return 0.5f * offsetVec + pMiddle; + }; + + const float d01 = glm::distance(t.p0, t.p1); + const float d02 = glm::distance(t.p0, t.p2); + const float d12 = glm::distance(t.p1, t.p2); + + if (d01 > d02 && d01 > d12) { + // p2 is between p0 and p1 + t.p2 = DeDegenerateTriangle(t.aabb_s, t.p2, t.aabb_e); + } + else if (d02 > d01 && d02 > d12) { + // p1 is between p0 and p2 + t.p1 = DeDegenerateTriangle(t.aabb_s, t.p1, t.aabb_e); + } + else { + // p0 is between p1 and p2 + t.p0 = DeDegenerateTriangle(t.aabb_s, t.p0, t.aabb_e); + } + + t = omm::Triangle(t.p0, t.p1, t.p2); + } + + omm::Triangle macroTriangle = t; const bool ClippedViewport = dumpDesc.detailedCutout; @@ -254,11 +285,10 @@ namespace omm uint32_t mipCount = 0; }; - auto Kernel = [&stateColorLUT](int2 pixel, float3* bc, void* context) { + auto Kernel = [&stateColorLUT](int2 pixel, const float3* bc, void* context) { RasterParams* p = (RasterParams*)context; - int2 dst = (pixel - p->offset); if (dst.x < 0 || dst.y < 0) return; @@ -307,9 +337,12 @@ namespace omm float2 bc2 = p->macroTriangleIsBackfacing ? float2(bc->x, bc->y) : float2(bc->z, bc->x); float2 bc2Clamp = glm::saturate(bc2); + uint32_t numTris = omm::bird::GetNumMicroTriangles(p->subdivisionLevel); + uint32_t vmIdx = omm::bird::bary2index(bc2Clamp, p->subdivisionLevel, isUpright); - vmIdx = std::clamp(vmIdx, 0u, omm::bird::GetNumMicroTriangles(p->subdivisionLevel) - 1); + vmIdx = std::clamp(vmIdx, 0u, numTris - 1); float3 vmColor = stateColorLUT[(uint32_t)p->states[vmIdx]]; + //float3 vmColor = float3(vmIdx, vmIdx, vmIdx) / float3(numTris, numTris, numTris); if (isUpright) vmColor = vmColor * 0.9f; @@ -386,7 +419,7 @@ namespace omm params.scale = scale; params.mode = Mode::FillBackground; params.highlightReuse = highlightReuse; - params.macroTriangleIsBackfacing = macroTriangle._winding == omm::WindingOrder::CW; + params.macroTriangleIsBackfacing = !macroTriangle.GetIsCCW(); params.mipCount = (uint32_t)alphaFps.size(); // Clone the source texture and render each individual VM on top of it. @@ -417,14 +450,14 @@ namespace omm } omm::Triangle t0(p00, p11, p01); - omm::RasterizeConservativeParallel(t0, srcSize, Kernel, ¶ms); + omm::RasterizeConservativeParallelBarycentrics(t0, srcSize, Kernel, ¶ms); omm::Triangle t1(p00, p10, p11); - omm::RasterizeConservativeParallel(t1, srcSize, Kernel, ¶ms); + omm::RasterizeConservativeParallelBarycentrics(t1, srcSize, Kernel, ¶ms); } { // Fill in each VM-substate color with a blend color params.mode = Mode::FillOMMStates; - omm::RasterizeConservativeParallel(macroTriangle, srcSize, Kernel, ¶ms); + omm::RasterizeConservativeParallelBarycentrics(macroTriangle, srcSize, Kernel, ¶ms); } if (!dumpDesc.oneFile || primitiveCount == primIt + 1) @@ -454,9 +487,9 @@ namespace omm } omm::Triangle t0(p00, p11, p01); - omm::RasterizeConservativeParallel(t0, srcSize, Kernel, ¶ms); + omm::RasterizeConservativeParallelBarycentrics(t0, srcSize, Kernel, ¶ms); omm::Triangle t1(p00, p10, p11); - omm::RasterizeConservativeParallel(t1, srcSize, Kernel, ¶ms); + omm::RasterizeConservativeParallelBarycentrics(t1, srcSize, Kernel, ¶ms); } } diff --git a/omm-sdk/src/serialize_impl.cpp b/omm-sdk/src/serialize_impl.cpp index 07fb999..561410d 100644 --- a/omm-sdk/src/serialize_impl.cpp +++ b/omm-sdk/src/serialize_impl.cpp @@ -127,7 +127,7 @@ namespace Cpu } os.write(reinterpret_cast(&inputDesc.unknownStatePromotion), sizeof(inputDesc.unknownStatePromotion)); - os.write(reinterpret_cast(&inputDesc.degenTriState), sizeof(inputDesc.degenTriState)); + os.write(reinterpret_cast(&inputDesc.unresolvedTriState), sizeof(inputDesc.unresolvedTriState)); os.write(reinterpret_cast(&inputDesc.maxSubdivisionLevel), sizeof(inputDesc.maxSubdivisionLevel)); size_t numSubdivLvls = inputDesc.subdivisionLevels == nullptr ? 0 : inputDesc.indexCount; @@ -418,7 +418,7 @@ namespace Cpu os.read(reinterpret_cast(&inputDesc.unknownStatePromotion), sizeof(inputDesc.unknownStatePromotion)); if (header.inputDescVersion >= 2) { - os.read(reinterpret_cast(&inputDesc.degenTriState), sizeof(inputDesc.degenTriState)); + os.read(reinterpret_cast(&inputDesc.unresolvedTriState), sizeof(inputDesc.unresolvedTriState)); } os.read(reinterpret_cast(&inputDesc.maxSubdivisionLevel), sizeof(inputDesc.maxSubdivisionLevel)); diff --git a/omm-sdk/src/version.h b/omm-sdk/src/version.h index d6a3f6b..0953ae1 100644 --- a/omm-sdk/src/version.h +++ b/omm-sdk/src/version.h @@ -12,8 +12,8 @@ license agreement from NVIDIA CORPORATION is strictly prohibited. #define STR(x) STR_HELPER(x) #define VERSION_MAJOR 1 -#define VERSION_MINOR 5 -#define VERSION_BUILD 1 +#define VERSION_MINOR 6 +#define VERSION_BUILD 0 #define VERSION_REVISION 0 #define VERSION_STRING STR(VERSION_MAJOR.VERSION_MINOR.VERSION_BUILD.VERSION_REVISION) diff --git a/shared/shared/bird.h b/shared/shared/bird.h index bd38b33..5f25718 100644 --- a/shared/shared/bird.h +++ b/shared/shared/bird.h @@ -12,7 +12,7 @@ license agreement from NVIDIA CORPORATION is strictly prohibited. #include "assert.h" #include "math.h" -#include "triangle.h" +#include "geometry.h" #include "bit_tricks.h" namespace omm diff --git a/shared/shared/cpu_raster.h b/shared/shared/cpu_raster.h index 9b4d03c..432e7c5 100644 --- a/shared/shared/cpu_raster.h +++ b/shared/shared/cpu_raster.h @@ -11,7 +11,7 @@ license agreement from NVIDIA CORPORATION is strictly prohibited. #pragma once #include "math.h" -#include "triangle.h" +#include "geometry.h" namespace omm { @@ -274,24 +274,26 @@ namespace omm // t - the triangle to rasterize // r - the pixel resolution to rasterize at. // f - the function callback, _should_ be inlined when using lambdas. - template - inline void Rasterize(const Triangle& _t, int2 r, const float2& offset, F f, void* context = nullptr) { + template + inline void RasterizeTriImpl(const Triangle& _t, int2 r, const float2& offset, F f, void* context = nullptr) { // Obvious optimizations this rasterizer does _not_ do: // No coarse raster step - might be useful for large triangles, // Tight triangle traversal, right now it searches row wise and terminates on first exit. + + // constexpr bool EnableBarycentrics = false; - // Scanline approaches could be investigated as well. - - omm::WindingOrder winding = _t._winding; + OMM_ASSERT(!_t.GetIsDegenerate()); - // Rasterizer expects CCW triangles. - const bool isBackfacing = winding == omm::WindingOrder::CW; + // Scanline approaches could be investigated as well. + const bool isCCW = _t.GetIsCCW(); const float2 rf = float2(r); const float2 invSize = 1.f / rf; - Triangle t = isBackfacing ? Triangle(_t.p2 * rf + offset, _t.p1 * rf + offset, _t.p0 * rf + offset) : Triangle(_t.p0 * rf + offset, _t.p1 * rf + offset, _t.p2 * rf + offset); - OMM_ASSERT(t._winding == omm::WindingOrder::CCW); + // Rasterizer expects CCW triangles. + Triangle t = isCCW ? Triangle(_t.p0 * rf + offset, _t.p1 * rf + offset, _t.p2 * rf + offset) + : Triangle(_t.p2 * rf + offset, _t.p1 * rf + offset, _t.p0 * rf + offset); + OMM_ASSERT(t.GetIsCCW()); const int2 min = int2{ glm::floor(t.aabb_s) }; const int2 max = int2{ glm::ceil(t.aabb_e) }; @@ -315,47 +317,62 @@ namespace omm if (_tix.SquareInTriangleSkipAABBTest(s, pixelSize)) { const float2 s_c = (s + 0.5f); - float3 bc = _tix.GetBarycentrics(s_c); - if (isBackfacing) - bc = { bc.z, bc.y, bc.x }; - if constexpr (TestCoverage) + if constexpr (EnableBarycentrics) { - Coverage coverage = _tix.SquareEntierlyInTriangleSkipAABBTest(s, pixelSize) ? FullyCovered : PartiallyCovered; - f(int2({ x, y }), &bc, coverage, context); - } - else { + float3 bc = _tix.GetBarycentrics(s_c); + if (!isCCW) + bc = { bc.z, bc.y, bc.x }; + f(int2({ x, y }), &bc, context); } + else + { + f(int2({ x, y }), context); + } wasInside = true; } else if (wasInside) break; } else if constexpr (eRasterMode == RasterMode::UnderConservative) { - static_assert(!TestCoverage); + const float2 s = float2(x, y); if (_tix.SquareEntierlyInTriangleSkipAABBTest(s, pixelSize)) { const float2 s_c = (s + 0.5f); - float3 bc = _tix.GetBarycentrics(s_c); - if (isBackfacing) - bc = { bc.z, bc.y, bc.x }; + if constexpr (EnableBarycentrics) + { + float3 bc = _tix.GetBarycentrics(s_c); + if (!isCCW) + bc = { bc.z, bc.y, bc.x }; - f(int2({ x, y }), &bc, context); + f(int2({ x, y }), &bc, context); + } + else + { + f(int2({ x, y }), context); + } wasInside = true; } else if (wasInside) break; } else if constexpr (eRasterMode == RasterMode::Default) { - static_assert(!TestCoverage); - const float2 s = (float2(x, y) + 0.5f); - if (_tix.PointInTriangle(s, pixelSize)) { - float3 bc = _tix.GetBarycentrics(s); - if (isBackfacing) - bc = { bc.z, bc.y, bc.x }; - f(int2({ x, y }), &bc, context); + const float2 s = (float2(x, y) + 0.5f); + if (_tix.PointInTriangle(s, pixelSize)) + { + if constexpr (EnableBarycentrics) + { + float3 bc = _tix.GetBarycentrics(s); + if (!isCCW) + bc = { bc.z, bc.y, bc.x }; + f(int2({ x, y }), &bc, context); + } + else + { + f(int2({ x, y }), context); + } wasInside = true; } else if (wasInside) @@ -365,25 +382,218 @@ namespace omm } } + template + inline void RasterizeLineImpl(const Line& _l, int2 r, const float2& offset, F f, void* context = nullptr) + { + Line l = _l.p0.x > _l.p1.x ? Line(_l.p1, _l.p0) : _l; + + // Bresenham's line algorithm + // src: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm + + int x0 = (int)(l.p0.x * r.x); + int x1 = (int)(l.p1.x * r.x); + int y0 = (int)(l.p0.y * r.y); + int y1 = (int)(l.p1.y * r.y); + + auto plotLineLow = [&f, context](int x0, int y0, int x1, int y1) { + int dx = x1 - x0; + int dy = y1 - y0; + int yi = 1; + if (dy < 0) + { + yi = -1; + dy = -dy; + } + int D = (2 * dy) - dx; + int y = y0; + + for (int x = x0; x <= x1; ++x) + { + if constexpr (EnableBarycentrics) + { + const float3 zero(0, 0, 0); + f(int2({ x, y }), &zero, context); + } + else + { + f(int2({ x, y }), context); + } + + if (D > 0) + { + y = y + 1; + D = D + (2 * (dy - dx)); + } + else + { + D = D + 2 * dy; + } + } + }; + + auto plotLineHigh = [&f, context](int x0, int y0, int x1, int y1) { + int dx = x1 - x0; + int dy = y1 - y0; + int xi = 1; + if (dx < 0) + { + xi = -1; + dx = -dx; + } + int D = (2 * dx) - dy; + int x = x0; + + for (int y = y0; y <= y1; ++y) + { + if constexpr (EnableBarycentrics) + { + const float3 zero(0, 0, 0); + f(int2({ x, y }), &zero, context); + } + else + { + f(int2({ x, y }), context); + } + + if (D > 0) + { + x = x + xi; + D = D + (2 * (dx - dy)); + } + else + { + D = D + 2 * dx; + } + } + }; + + if (abs(y1 - y0) < abs(x1 - x0)) + { + if (x0 > x1) + plotLineLow(x1, y1, x0, y0); + else + plotLineLow(x0, y0, x1, y1); + } + else + { + if (y0 > y1) + plotLineHigh(x1, y1, x0, y0); + else + plotLineHigh(x0, y0, x1, y1); + } + } + + template + inline void RasterizeLineConservativeImpl(const Line& _l, int2 r, const float2& offset, F f, void* context = nullptr) + { + const float2 rf(r); + Line l = Line(_l.p0 * rf + offset, _l.p1 * rf + offset); + + if (l.p0.x > l.p1.x) + { + l = Line(l.p1, l.p0); + } + + const int2 gridSize = r; + const float2 rayDirection = l.p1 - l.p0; + const float2 rayOrigin = l.p0; + const float tEnd = glm::length(rayDirection); + + int x = static_cast(glm::floor(l.p0.x)); + int y = static_cast(glm::floor(l.p0.y)); + + int stepX = (rayDirection.x > 0) ? 1 : ((rayDirection.x < 0) ? -1 : 0); + int stepY = (rayDirection.y > 0) ? 1 : ((rayDirection.y < 0) ? -1 : 0); + + float tDeltaX = (stepX != 0) ? 1.f / std::abs(rayDirection.x) : std::numeric_limits::infinity(); + float tDeltaY = (stepY != 0) ? 1.f / std::abs(rayDirection.y) : std::numeric_limits::infinity(); + + // Calculate tMax (distance to the next cell boundary) + float tMaxX, tMaxY; + + if (stepX != 0) { + float nextCellBoundary = (x + (stepX > 0 ? 1.f : 0.f)); + tMaxX = (nextCellBoundary - rayOrigin.x) / rayDirection.x; + } + else { + tMaxX = std::numeric_limits::infinity(); + } + + if (stepY != 0) { + float nextCellBoundary = (y + (stepY > 0 ? 1.f : 0.f)); + tMaxY = (nextCellBoundary - rayOrigin.y) / rayDirection.y; + } + else { + tMaxY = std::numeric_limits::infinity(); + } + + if (stepX == 0 && stepY == 0) + { + f(int2(x, y), context); + return; + } + + const int yMin = (int)std::min(glm::floor(l.p0.y), glm::floor(l.p1.y)); + const int yMax = (int)std::max(glm::ceil(l.p0.y), glm::ceil(l.p1.y)); + + const int xMin = (int)std::min(glm::floor(l.p0.x), glm::floor(l.p1.x)); + const int xMax = (int)std::max(glm::ceil(l.p0.x), glm::ceil(l.p1.x)); + + while (x >= xMin && x <= xMax && y >= yMin && y <= yMax) + { + f(int2(x, y), context); + + if (tMaxX < tMaxY) { + x += stepX; + tMaxX += tDeltaX; + } + else { + y += stepY; + tMaxY += tDeltaY; + } + } + } + + template + inline void RasterizeConservativeSerial(const Triangle& t, int2 r, F f, void* context = nullptr) { RasterizeTriImpl(t, r, float2{0,0}, f, context); }; + + template + inline void RasterizeConservativeSerialBarycentrics(const Triangle& t, int2 r, F f, void* context = nullptr) { RasterizeTriImpl(t, r, float2{ 0,0 }, f, context); }; + + template + inline void RasterizeConservativeSerialWithOffset(const Triangle& t, int2 r, float2 offset, F f, void* context = nullptr) { RasterizeTriImpl(t, r, offset, f, context); }; + + template + inline void RasterizeConservativeSerialWithOffsetCoverage(const Triangle& t, int2 r, float2 offset, F f, void* context = nullptr) { RasterizeTriImpl(t, r, offset, f, context); }; + + template + inline void RasterizeConservativeParallel(const Triangle& t, int2 r, F f, void* context = nullptr) { RasterizeTriImpl(t, r, float2{ 0,0 }, f, context); }; + + template + inline void RasterizeConservativeParallelBarycentrics(const Triangle& t, int2 r, F f, void* context = nullptr) { RasterizeTriImpl(t, r, float2{ 0,0 }, f, context); }; + + template + inline void RasterizeUnderConservative(const Triangle& t, int2 r, F f, void* context = nullptr) { RasterizeTriImpl(t, r, float2{ 0,0 }, f, context); }; + template - inline void RasterizeConservativeSerial(const Triangle& t, int2 r, F f, void* context = nullptr) { Rasterize(t, r, float2{0,0}, f, context); }; + inline void RasterizeUnderConservativeBarycentrics(const Triangle& t, int2 r, F f, void* context = nullptr) { RasterizeTriImpl(t, r, float2{ 0,0 }, f, context); }; template - inline void RasterizeConservativeSerialWithOffset(const Triangle& t, int2 r, float2 offset, F f, void* context = nullptr) { Rasterize(t, r, offset, f, context); }; + inline void RasterizeSerial(const Triangle& t, int2 r, F f, void* context = nullptr) { RasterizeTriImpl(t, r, float2{ 0,0 }, f, context); }; template - inline void RasterizeConservativeSerialWithOffsetCoverage(const Triangle& t, int2 r, float2 offset, F f, void* context = nullptr) { Rasterize(t, r, offset, f, context); }; + inline void RasterizeParallel(const Triangle& t, int2 r, F f, void* context = nullptr) { RasterizeTriImpl(t, r, float2{ 0,0 }, f, context); }; template - inline void RasterizeConservativeParallel(const Triangle& t, int2 r, F f, void* context = nullptr) { Rasterize(t, r, float2{ 0,0 }, f, context); }; + inline void RasterizeParallelBarycentrics(const Triangle& t, int2 r, F f, void* context = nullptr) { RasterizeTriImpl(t, r, float2{ 0,0 }, f, context); }; template - inline void RasterizeUnderConservative(const Triangle& t, int2 r, F f, void* context = nullptr) { Rasterize(t, r, float2{ 0,0 }, f, context); }; + inline void RasterizeLine(const Line& l, int2 r, F f, void* context = nullptr) { RasterizeLineImpl(l, r, float2{ 0,0 }, f, context); }; template - inline void RasterizeSerial(const Triangle& t, int2 r, F f, void* context = nullptr) { Rasterize(t, r, float2{ 0,0 }, f, context); }; + inline void RasterizeConservativeLine(const Line& l, int2 r, F f, void* context = nullptr) { RasterizeLineConservativeImpl(l, r, float2{ 0,0 }, f, context); }; template - inline void RasterizeParallel(const Triangle& t, int2 r, F f, void* context = nullptr) { Rasterize(t, r, float2{ 0,0 }, f, context); }; + inline void RasterizeConservativeLineWithOffset(const Line& l, int2 r, float2 offset, F f, void* context = nullptr) { RasterizeLineConservativeImpl(l, r, offset, f, context); }; } // namespace omm \ No newline at end of file diff --git a/shared/shared/triangle.h b/shared/shared/geometry.h similarity index 74% rename from shared/shared/triangle.h rename to shared/shared/geometry.h index 93a0e83..c19a07b 100644 --- a/shared/shared/triangle.h +++ b/shared/shared/geometry.h @@ -16,17 +16,42 @@ license agreement from NVIDIA CORPORATION is strictly prohibited. namespace omm { - enum class WindingOrder : uint8_t { - CW, - CCW, + struct Line + { + Line() = default; + Line& operator=(const Line&) = default; + Line(const float2& _p0, const float2& _p1) : + p0(_p0), + p1(_p1) + { + aabb_s = { std::min(p0.x, p1.x), std::min(p0.y, p1.y) }; + aabb_e = { std::max(p0.x, p1.x), std::max(p0.y, p1.y) }; + } + + float2 p0; + float2 p1; + float2 aabb_s; //< Start point of the aabb + float2 aabb_e; //< End point of the aabb }; - static inline WindingOrder GetWinding(const float2 _p0, const float2 _p1, const float2 _p2) { - double3 a = double3(_p2 - _p0, 0); - double3 b = double3(_p1 - _p0, 0); + static bool IsInvalid(const float2& p0, const float2& p1, const float2& p2) + { + const bool anyNan = glm::any(glm::isnan(p0)) || glm::any(glm::isnan(p1)) || glm::any(glm::isnan(p2)); + const bool anyInf = glm::any(glm::isinf(p0)) || glm::any(glm::isinf(p1)) || glm::any(glm::isinf(p2)); + return anyNan || anyInf; + } + + static inline bool IsDegenerate(const float2& p0, const float2& p1, const float2& p2) { + float area = 0.5f * std::abs(p0.x * (p1.y - p2.y) + p1.x * (p2.y - p0.y) + p2.x * (p0.y - p1.y)); + return area < 1e-9; + } + + static inline bool IsCCW(const float2& p0, const float2& p1, const float2& p2) { + double3 a = double3(p2 - p0, 0); + double3 b = double3(p1 - p0, 0); const double3 N = glm::cross(a, b); const double Nz = N.z; - return Nz < 0 ? WindingOrder::CCW : WindingOrder::CW; + return Nz < 0; } struct Triangle { @@ -36,14 +61,13 @@ namespace omm Triangle(const float2& _p0, const float2& _p1, const float2& _p2) : p0(_p0), p1(_p1), - p2(_p2), - _winding(GetWinding(p0, p1, p2)) + p2(_p2) { aabb_s = { std::min(std::min(p0.x, p1.x), p2.x), std::min(std::min(p0.y, p1.y), p2.y) }; aabb_e = { std::max(std::max(p0.x, p1.x), p2.x), std::max(std::max(p0.y, p1.y), p2.y) }; } - float2 getP(uint32_t index) const { + inline float2 getP(uint32_t index) const { if (index == 0) return p0; else if (index == 1) @@ -52,13 +76,26 @@ namespace omm return p2; } + inline bool GetIsInvalid() const { + return IsInvalid(p0, p1, p2); + } + + inline bool GetIsDegenerate() const { + return IsDegenerate(p0, p1, p2); + } + + inline bool GetIsCCW() const { + OMM_ASSERT(!GetIsInvalid()); + OMM_ASSERT(!GetIsDegenerate()); + return IsCCW(p0, p1, p2); + } + float2 p0; float2 p1; float2 p2; - float2 aabb_s; //< Start point of the aabb - float2 aabb_e; //< End point of the aabb - WindingOrder _winding; //< This matters when calculating barycentrics during rasterization. + float2 aabb_s; //< Start point of the aabb + float2 aabb_e; //< End point of the aabb }; template @@ -76,13 +113,6 @@ namespace omm return seed; } - static inline WindingOrder GetWinding(const Triangle& t) { - float2 a = t.p2 - t.p0; - float2 b = t.p1 - t.p0; - const float Nz = a.x * b.y - a.y * b.x; - return Nz < 0 ? WindingOrder::CCW : WindingOrder::CW; - } - static float float16ToFloat32(uint16_t fp16) { union { uint32_t ui32; float fp32; } output; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 50eeb1a..5f89967 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,7 +32,7 @@ if (OMM_ENABLE_GPU_TESTS) endif() endif() -set(omm_tests_src_cpu util/stb_lib.cpp util/image.h util/omm.h util/omm_histogram.h util/omm_histogram.cpp test_basic.cpp test_texture.cpp test_raster.cpp test_minimal_sample.cpp test_util.cpp test_tesselator.cpp test_omm_bake_cpu.cpp test_subdiv.cpp test_omm_indexing.cpp test_omm_log.cpp ) +set(omm_tests_src_cpu util/stb_lib.cpp util/image.h util/omm.h util/omm_histogram.h util/omm_histogram.cpp test_basic.cpp test_texture.cpp test_raster_tri.cpp test_raster_line.cpp test_minimal_sample.cpp test_util.cpp test_tesselator.cpp test_omm_bake_cpu.cpp test_subdiv.cpp test_omm_indexing.cpp test_omm_log.cpp ) add_executable(tests main.cpp ${omm_tests_src_cpu} ${omm_tests_src_gpu}) if (OMM_ENABLE_GPU_TESTS) set(OMM_ENABLE_GPU_TESTS_VALUE 1) diff --git a/tests/nvrhi/nvrhi_wrapper.cpp b/tests/nvrhi/nvrhi_wrapper.cpp index efcaf71..9e2d95a 100644 --- a/tests/nvrhi/nvrhi_wrapper.cpp +++ b/tests/nvrhi/nvrhi_wrapper.cpp @@ -16,12 +16,14 @@ license agreement from NVIDIA CORPORATION is strictly prohibited. #include #include -#include +#include #include #include #include +using namespace Microsoft::WRL; + namespace { struct DefaultMessageCallback : public nvrhi::IMessageCallback @@ -38,17 +40,17 @@ namespace { } }; - static CComPtr FindAdapter(const std::wstring& targetName) + static ComPtr FindAdapter(const std::wstring& targetName) { - CComPtr targetAdapter; - CComPtr DXGIFactory; + ComPtr targetAdapter; + ComPtr DXGIFactory; HRESULT hres = CreateDXGIFactory1(IID_PPV_ARGS(&DXGIFactory)); assert(hres == S_OK); unsigned int adapterNo = 0; while (SUCCEEDED(hres)) { - CComPtr pAdapter; + ComPtr pAdapter; hres = DXGIFactory->EnumAdapters(adapterNo, &pAdapter); if (SUCCEEDED(hres)) @@ -86,8 +88,8 @@ namespace { { nvrhi::d3d12::DeviceDesc deviceDesc; deviceDesc.errorCB = &DefaultMessageCallback::GetInstance(); - deviceDesc.pDevice = device12; - deviceDesc.pGraphicsCommandQueue = graphicsQueue; + deviceDesc.pDevice = device12.Get(); + deviceDesc.pGraphicsCommandQueue = graphicsQueue.Get(); deviceDesc.pComputeCommandQueue = nullptr; deviceDesc.pCopyCommandQueue = nullptr; @@ -105,7 +107,7 @@ namespace { { if (desc.enableDebugRuntime) { - CComPtr debugController; + ComPtr debugController; if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) { debugController->EnableDebugLayer(); @@ -115,15 +117,15 @@ namespace { targetAdapter = FindAdapter(desc.adapterNameSubstring); HRESULT hr = D3D12CreateDevice( - targetAdapter, + targetAdapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device12)); assert(hr == S_OK); if (desc.enableDebugRuntime) { - CComPtr pInfoQueue; - device12->QueryInterface(&pInfoQueue); + ComPtr pInfoQueue; + device12->QueryInterface(pInfoQueue.GetAddressOf()); if (pInfoQueue) { @@ -150,9 +152,9 @@ namespace { private: const NVRHIContext::InitParams desc; nvrhi::DeviceHandle nvrhiDevice; - CComPtr targetAdapter; - CComPtr device12; - CComPtr graphicsQueue; + ComPtr targetAdapter; + ComPtr device12; + ComPtr graphicsQueue; }; } // namespace diff --git a/tests/test_omm_bake_cpu.cpp b/tests/test_omm_bake_cpu.cpp index 295562e..0ff0c4a 100644 --- a/tests/test_omm_bake_cpu.cpp +++ b/tests/test_omm_bake_cpu.cpp @@ -42,6 +42,7 @@ namespace { struct Options { omm::Format format = omm::Format::OC1_4_State; + omm::TextureAddressMode addressingMode = omm::TextureAddressMode::Clamp; omm::UnknownStatePromotion unknownStatePromotion = omm::UnknownStatePromotion::Nearest; bool mergeSimilar = false; uint32_t mipCount = 1; @@ -56,7 +57,8 @@ namespace { bool forceCorruptedBlob = false; bool forceSerializedOutput = false; bool serializeCompress = false; - omm::SpecialIndex degenTriState = omm::SpecialIndex::FullyUnknownOpaque; + omm::SpecialIndex unresolvedTriState = omm::SpecialIndex::FullyUnknownOpaque; + float dynamicSubdivisionScale = 0.f; }; static float StandardCircle(int i, int j, int w, int h, int mip) @@ -180,7 +182,7 @@ namespace { desc.texture = tex; desc.format = opt.format; desc.alphaMode = omm::AlphaMode::Test; - desc.runtimeSamplerDesc.addressingMode = omm::TextureAddressMode::Clamp; + desc.runtimeSamplerDesc.addressingMode = opt.addressingMode; desc.runtimeSamplerDesc.filter = omm::TextureFilterMode::Linear; desc.indexFormat = omm::IndexFormat::UINT_32; desc.indexBuffer = triangleIndices; @@ -194,7 +196,7 @@ namespace { desc.unknownStatePromotion = opt.unknownStatePromotion; desc.bakeFlags = (omm::Cpu::BakeFlags)((uint32_t)omm::Cpu::BakeFlags::EnableInternalThreads); desc.maxWorkloadSize = opt.maxWorkloadSize; - desc.degenTriState = opt.degenTriState; + desc.unresolvedTriState = opt.unresolvedTriState; if (opt.mergeSimilar) desc.bakeFlags = (omm::Cpu::BakeFlags)((uint32_t)desc.bakeFlags | (uint32_t)omm::Cpu::BakeFlags::EnableNearDuplicateDetection); if (Force32BitIndices()) @@ -202,7 +204,7 @@ namespace { if (!opt.enableSpecialIndices) desc.bakeFlags = (omm::Cpu::BakeFlags)((uint32_t)desc.bakeFlags | (uint32_t)omm::Cpu::BakeFlags::DisableSpecialIndices); - desc.dynamicSubdivisionScale = 0.f; + desc.dynamicSubdivisionScale = opt.dynamicSubdivisionScale; omm::Cpu::BakeResult res = nullptr; const omm::Cpu::BakeResultDesc* resDesc = nullptr; @@ -2243,29 +2245,245 @@ namespace { }); } - TEST_P(OMMBakeTestCPU, Degen_Default) { + TEST_P(OMMBakeTestCPU, Degen_Default_lvl1) { uint32_t triangleIndices[3] = { 0, 1, 2, }; - float texCoords[8] = { 0.f, 0.f, - 0.f, 0.437582970f, - 0.f, 0.221271083f }; + float texCoords[8] = { 0.2f, 0.f, + 0.2f, 0.437582970f, + 0.2f, /*0.221271083f*/ 0.218791485f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 1, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle); + + ExpectEqual(stats, { + .totalOpaque = 1, + .totalUnknownTransparent = 1, + .totalUnknownOpaque = 2, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_lvl2) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.f, + 0.2f, 0.437582970f, + 0.2f, /*0.221271083f*/ 0.218791485f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 2, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle); + + ExpectEqual(stats, { + .totalOpaque = 6, + .totalTransparent = 3, + .totalUnknownTransparent = 3, + .totalUnknownOpaque = 4, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_Horizontal) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.2f, + 0.3f, 0.2f, + 0.41f, 0.2f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 1, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle); + + ExpectEqual(stats, { + .totalOpaque = 0, + .totalTransparent = 3, + .totalUnknownTransparent = 1, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_Diagonal) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.2f, + 0.3f, 0.2f, + 0.4f, 0.2f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 2, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle); + + ExpectEqual(stats, { + .totalTransparent = 13, + .totalUnknownTransparent = 2, + .totalUnknownOpaque = 1, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_lvl3) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.f, + 0.2f, 0.437582970f, + 0.2f, /*0.221271083f*/ 0.218791485f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 3, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle); + + ExpectEqual(stats, { + .totalOpaque = 28, + .totalTransparent = 21, + .totalUnknownTransparent = 7, + .totalUnknownOpaque = 8, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_lvl4) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.f, + 0.2f, 0.437582970f, + 0.2f, /*0.221271083f*/ 0.218791485f }; omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 4, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle); ExpectEqual(stats, { - .totalFullyUnknownOpaque = 1, - }); + .totalOpaque = 136, + .totalTransparent = 91, + .totalUnknownTransparent = 14, + .totalUnknownOpaque = 15, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_lvl4_wrap) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { -0.8f, 0.f, + -0.8f, 0.437582970f, + -0.8f, /*0.221271083f*/ 0.218791485f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 4, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle, + { .addressingMode = omm::TextureAddressMode::Wrap, .oneFile = false, .detailedCutout = true }); + + ExpectEqual(stats, { + .totalOpaque = 136, + .totalTransparent = 91, + .totalUnknownTransparent = 14, + .totalUnknownOpaque = 15, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_dyn_lvl_0_1) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.f, + 0.2f, 0.437582970f, + 0.2f, /*0.221271083f*/ 0.218791485f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 12, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle, { .dynamicSubdivisionScale = 0.1f }); + + ExpectEqual(stats, { + .totalOpaque = 9642463, + .totalTransparent = 7108335, + .totalUnknownTransparent = 3771, + .totalUnknownOpaque = 22647, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_dyn_lvl_0_5) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.f, + 0.2f, 0.437582970f, + 0.2f, /*0.221271083f*/ 0.218791485f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 12, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle, { .dynamicSubdivisionScale = 0.5f }); + + ExpectEqual(stats, { + .totalOpaque = 601591, + .totalTransparent = 443211, + .totalUnknownTransparent = 942, + .totalUnknownOpaque = 2832, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_dyn_lvl_2) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.f, + 0.2f, 0.437582970f, + 0.2f, /*0.221271083f*/ 0.218791485f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 12, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle, {.dynamicSubdivisionScale = 2.f}); + + ExpectEqual(stats, { + .totalOpaque = 37333, + .totalTransparent = 27495, + .totalUnknownTransparent = 353, + .totalUnknownOpaque = 355, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_dyn_lvl_3) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.f, + 0.2f, 0.437582970f, + 0.2f, /*0.221271083f*/ 0.218791485f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 12, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle, { .dynamicSubdivisionScale = 3.f }); + + ExpectEqual(stats, { + .totalOpaque = 37333, + .totalTransparent = 27495, + .totalUnknownTransparent = 353, + .totalUnknownOpaque = 355, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Default_dyn_lvl_10) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.f, + 0.2f, 0.437582970f, + 0.2f, /*0.221271083f*/ 0.218791485f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 12, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle, { .dynamicSubdivisionScale = 10.f }); + + ExpectEqual(stats, { + .totalOpaque = 2266, + .totalTransparent = 1653, + .totalUnknownTransparent = 87, + .totalUnknownOpaque = 90, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Point_Transparent) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.437582970f, + 0.2f, 0.437582970f, + 0.2f, 0.437582970f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 12, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle, { .dynamicSubdivisionScale = 2.f }); + + ExpectEqual(stats, { + .totalFullyTransparent = 1, + }); + } + + TEST_P(OMMBakeTestCPU, Degen_Point_Opaque) { + + uint32_t triangleIndices[3] = { 0, 1, 2, }; + float texCoords[8] = { 0.2f, 0.1f, + 0.2f, 0.1f, + 0.2f, 0.1f }; + + omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 12, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, &StandardCircle, { .dynamicSubdivisionScale = 2.f }); + + ExpectEqual(stats, { + .totalFullyOpaque = 1, + }); } - TEST_P(OMMBakeTestCPU, Degen_FullyUnknownTransparent) { + TEST_P(OMMBakeTestCPU, Invalid_FullyUnknownTransparent) { uint32_t triangleIndices[3] = { 0, 1, 2, }; float texCoords[8] = { 0.f, 0.f, - 0.f, 0.437582970f, + 0.f, std::numeric_limits::quiet_NaN(), 0.f, 0.221271083f }; omm::Debug::Stats stats = GetOmmBakeStatsFP32(0.5f, 4, { 1024, 1024 }, 3, triangleIndices, omm::TexCoordFormat::UV32_FLOAT, texCoords, - &StandardCircle, { .degenTriState = omm::SpecialIndex::FullyUnknownTransparent }); + &StandardCircle, { .unresolvedTriState = omm::SpecialIndex::FullyUnknownTransparent }); ExpectEqual(stats, { .totalFullyUnknownTransparent = 1, diff --git a/tests/test_omm_log.cpp b/tests/test_omm_log.cpp index 48b4290..6ebdeda 100644 --- a/tests/test_omm_log.cpp +++ b/tests/test_omm_log.cpp @@ -54,7 +54,7 @@ namespace { return tex; } - omm::Cpu::BakeInputDesc CreateDefaultBakeInputDesc(uint32_t triangleCount = 256, const float alphaCutoff = 0.3f, bool forceDegenTris = false) { + omm::Cpu::BakeInputDesc CreateDefaultBakeInputDesc(uint32_t triangleCount = 256, const float alphaCutoff = 0.3f, bool forceInvalidTris = false) { vmtest::TextureFP32 texture(1024, 1024, 1, false, alphaCutoff, [](int i, int j, int w, int h, int mip) { if ((i) % 2 != (j) % 2) @@ -76,8 +76,8 @@ namespace { for (uint32_t j = 0; j < 3; ++j) { _indices[3 * i + j] = 3 * i + j; _texCoords[3 * i + j] = float2(distr(eng), distr(eng)); - if (forceDegenTris) - _texCoords[3 * i + j].x = 0.f; + if (forceInvalidTris) + _texCoords[3 * i + j].x = std::numeric_limits::infinity(); } } @@ -194,11 +194,11 @@ namespace { " This is unusually large and may result in long bake times." }, omm::Result::SUCCESS); } - TEST_F(LogTest, Validation_DegenerateTriangles) + TEST_F(LogTest, Validation_InvalidTriangles) { InitBaker(true /*set callback*/); omm::Cpu::BakeInputDesc desc = CreateDefaultBakeInputDesc(256, 0.5f, true /*forceDegen*/); - Bake(desc, { "[Info] - The workload consists of 256 degenerate triangles, these will be classified as Fully Unknown Opaque (this behaviour can be changed by degenTriState)." }, omm::Result::SUCCESS); + Bake(desc, { "[Info] - The workload consists of 256 unclassifiable triangles, these will be classified as unresolvedTriState = Fully Unknown Opaque." }, omm::Result::SUCCESS); } TEST_F(LogTest, InvalidParameter_ValidationWithoutLog) diff --git a/tests/test_raster_line.cpp b/tests/test_raster_line.cpp new file mode 100644 index 0000000..7fad63c --- /dev/null +++ b/tests/test_raster_line.cpp @@ -0,0 +1,258 @@ +/* +Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + +NVIDIA CORPORATION and its licensors retain all intellectual property +and proprietary rights in and to this software, related documentation +and any modifications thereto. Any use, reproduction, disclosure or +distribution of this software and related documentation without an express +license agreement from NVIDIA CORPORATION is strictly prohibited. +*/ + +#include + +#include "util/image.h" +#include +#include +#include +#include + +namespace { + +class RasterLineTest : public ::testing::TestWithParam> { +protected: + omm::Line _line; + int2 _size; + void SetUp() override { + _line = std::get<0>(GetParam()); + _size = std::get<1>(GetParam()); + + // EXPECT_EQ(omm::GetWinding(_line), omm::WindingOrder::CCW); + } + + void TearDown() override { + } + + const char* GetRasterModeName(omm::RasterMode mode) { + if (omm::RasterMode::UnderConservative == mode) + { + return "UnderConservative"; + } + if (omm::RasterMode::OverConservative == mode) + { + return "OverConservative"; + } + else // if (omm::RasterMode::Default == mode) + { + return "Default"; + } + } + + void Run(int2 size, omm::RasterMode mode) { + + ImageRGB image(size, { 1, 128, 5 }); + + int one = 1; + int checkerSize = 64; + + auto KernelCBFill = [&image, checkerSize](int2 idx, void* ) { + if (idx.x >= image.GetSize().x) + return; + if (idx.y >= image.GetSize().y) + return; + if (idx.x < 0) + return; + if (idx.y < 0) + return; + + float2 p = { checkerSize * float(idx.x / checkerSize + 0.5) / image.GetWidth() , checkerSize * float(idx.y / checkerSize + 0.5) / image.GetHeight() }; + uchar3 val; + if ((idx.x / checkerSize) % 2 != (idx.y / checkerSize) % 2) + val = { 0, 0, 0 }; + else + val = { 64, 64, 64 }; + image.Store(idx, val); + }; + + struct Params { + int checkerSize = 1; + uchar3 fillColor = { 0,0 ,0 }; + }; + + auto LineFill = [&image, size](int2 idx, void* ctx) { + Params* p = (Params*)ctx; + + auto IsInRange = [&image](int2 idx)->bool { + if (idx.x >= image.GetSize().x) + return false; + if (idx.y >= image.GetSize().y) + return false; + if (idx.x < 0) + return false; + if (idx.y < 0) + return false; + + return true; + }; + + for (int y = 0; y < p->checkerSize; ++y) { + for (int x = 0; x < p->checkerSize; ++x) { + + int2 dst = p->checkerSize * idx + int2{ x, y }; + if (!IsInRange(dst)) + continue; + + uchar3 val = image.Load(dst); + val += p->fillColor; + image.Store(dst, p->fillColor); + } + } + }; + + // "Fullscreen" pass. + omm::RasterizeParallel(omm::Triangle({ 0.f, -1.f }, { 0.f, 1.f }, { 2.f, 1.f }), size, KernelCBFill); + + // "Triangle pass. + if (omm::RasterMode::OverConservative == mode) + { + { + Params p; + p.checkerSize = checkerSize; + p.fillColor = uchar3(128, 0, 0); + omm::RasterizeConservativeLine(_line, size / checkerSize, LineFill, & p); + } + + { + Params p; + p.checkerSize = 1; + p.fillColor = uchar3(0, 128, 0); + omm::RasterizeLine(_line, size, LineFill, &p); + } + } + else if (omm::RasterMode::Default == mode) + { + { + Params p; + p.checkerSize = checkerSize; + p.fillColor = uchar3(128, 0, 0); + omm::RasterizeLine(_line, size / checkerSize, LineFill, &p); + } + { + Params p; + p.checkerSize = 1; + p.fillColor = uchar3(0, 128, 0); + omm::RasterizeLine(_line, size, LineFill, &p); + } + } + + std::string name = ::testing::UnitTest::GetInstance()->current_test_suite()->name(); + std::replace(name.begin(), name.end(), '/', '_'); + + std::string fileName = name + GetRasterModeName(mode) + std::to_string(size.x) + "x" + std::to_string(size.y) + ".png"; + SaveImageToFile("RasterTestOutput", fileName.c_str(), image); + } +}; + +#if 0 +TEST_P(RasterLineTest, Rasterize) { + Run(_size, omm::RasterMode::Default); +} +#endif + +#if 0 +TEST_P(RasterLineTest, RasterizeConservative) { + Run(_size, omm::RasterMode::OverConservative); +} +#endif + +#if 0 +TEST_P(RasterLineTest, RasterizeSmall) { + Run({ _size.x / 2, _size.y / 2, }, omm::RasterMode::Default); +} +#endif + +TEST_P(RasterLineTest, RasterizeConservativeSmall) { + Run({ _size.x / 2, _size.y / 2, }, omm::RasterMode::OverConservative); +} + +#if 0 +TEST_P(RasterLineTest, RasterizeLarge) { + Run({ _size.x * 2, _size.y * 2, }, omm::RasterMode::Default); +} +#endif + +TEST_P(RasterLineTest, RasterizeConservativeLarge) { + Run({ _size.x * 2, _size.y * 2, }, omm::RasterMode::OverConservative); +} + +#if 1 +TEST_P(RasterLineTest, RasterizeConservativeSuperLarge) { + Run({ _size.x * 4, _size.y * 4, }, omm::RasterMode::OverConservative); +} +#endif + +#if 1 +INSTANTIATE_TEST_SUITE_P( + RasterLine_Low, + RasterLineTest, + ::testing::Values( + std::make_tuple(omm::Line({0.2f, 0.2f}, {0.7f, 0.5f}), {1024, 1024}) + )); +#endif + +#if 1 +INSTANTIATE_TEST_SUITE_P( + RasterLine_Diagonal, + RasterLineTest, + ::testing::Values( + std::make_tuple(omm::Line({ 0.01f, 0.01f }, { 0.99f, 0.9f }), { 1024, 1024 }) + )); +#endif + +#if 1 +INSTANTIATE_TEST_SUITE_P( + RasterLine_LowCenter, + RasterLineTest, + ::testing::Values( + std::make_tuple(omm::Line({ 0.2f, 0.2f }, { 0.5f, 0.5f }), { 1024, 1024 }) + )); + +INSTANTIATE_TEST_SUITE_P( + RasterLine_High, + RasterLineTest, + ::testing::Values( + std::make_tuple(omm::Line({ 0.1f, 0.9f }, { 0.7f, 0.2f }), { 1024, 1024 }) + )); + +INSTANTIATE_TEST_SUITE_P( + RasterLine_HighCenter, + RasterLineTest, + ::testing::Values( + std::make_tuple(omm::Line({ 0.1f, 0.9f }, { 0.5f, 0.2f }), { 1024, 1024 }) + )); + +INSTANTIATE_TEST_SUITE_P( + RasterLine_Horizontal, + RasterLineTest, + ::testing::Values( + std::make_tuple(omm::Line({ 0.2f, 0.5f }, { 0.5f, 0.5f }), { 1024, 1024 }) + )); +#endif + +#if 1 +INSTANTIATE_TEST_SUITE_P( + RasterLine_Vertical0, + RasterLineTest, + ::testing::Values( + std::make_tuple(omm::Line({ 0.5f, 0.5f }, { 0.5f, 0.1f }), { 1024, 1024 }) + )); +#endif + +#if 1 +INSTANTIATE_TEST_SUITE_P( + RasterLine_Vertical1, + RasterLineTest, + ::testing::Values( + std::make_tuple(omm::Line({ 0.5f, 0.1f }, { 0.5f, 0.5f }), { 1024, 1024 }) + )); +#endif +} // namespace \ No newline at end of file diff --git a/tests/test_raster.cpp b/tests/test_raster_tri.cpp similarity index 91% rename from tests/test_raster.cpp rename to tests/test_raster_tri.cpp index 4687d63..c5acaa7 100644 --- a/tests/test_raster.cpp +++ b/tests/test_raster_tri.cpp @@ -26,7 +26,7 @@ class RasterTest : public ::testing::TestWithParam(GetParam()); _size = std::get<1>(GetParam()); - EXPECT_EQ(omm::GetWinding(_triangle), omm::WindingOrder::CCW); + EXPECT_EQ(_triangle.GetIsCCW(), true); } void TearDown() override { @@ -57,7 +57,7 @@ class RasterTest : public ::testing::TestWithParam= image.GetSize().x) return; if (idx.y >= image.GetSize().y) @@ -82,7 +82,7 @@ class RasterTest : public ::testing::TestWithParambool { @@ -126,14 +126,14 @@ class RasterTest : public ::testing::TestWithParam(omm::Triangle({ 0.809000027f, 0.332400024f }, { 0.332400024f, 0.502599990f }, { 0.402599990f, 0.332400024f }), { 1024, 1024 }) )); - - } // namespace \ No newline at end of file diff --git a/tests/test_subdiv.cpp b/tests/test_subdiv.cpp index 72e11bf..8c142e2 100644 --- a/tests/test_subdiv.cpp +++ b/tests/test_subdiv.cpp @@ -368,7 +368,7 @@ namespace { // Raster macro triangle with colors ImageRGB imageA(size, { 0, 0, 0 }); - omm::RasterizeConservativeSerial(t, size, [subdivLvl, numSubTri, IndexToColor, &imageA](int2 pixel, float3* bc, void* ctc) { + omm::RasterizeConservativeSerialBarycentrics(t, size, [subdivLvl, numSubTri, IndexToColor, &imageA](int2 pixel, const float3* bc, void* ctc) { bool isUpright; uint32_t idx = omm::bird::bary2index(float2(bc->z, bc->x), subdivLvl, isUpright); @@ -384,7 +384,7 @@ namespace { omm::Triangle subTri = omm::bird::GetMicroTriangle(t, idx, subdivLvl); - omm::RasterizeConservativeSerial(subTri, int2(1024, 1024), [subdivLvl, numSubTri, IndexToColor, idx, &imageB](int2 pixel, float3* bc, void*) { + omm::RasterizeConservativeSerial(subTri, int2(1024, 1024), [subdivLvl, numSubTri, IndexToColor, idx, &imageB](int2 pixel, void*) { float3 color = IndexToColor(idx, subdivLvl); imageB.Store(pixel, uchar4(color.x * 255, color.y * 255, color.z * 255, 255)); diff --git a/tests/util/image.h b/tests/util/image.h index 8a84dd4..acb2740 100644 --- a/tests/util/image.h +++ b/tests/util/image.h @@ -1,7 +1,7 @@ #pragma once #include #include "omm.h" -#include +#include #include #include #include @@ -98,10 +98,24 @@ using ImageRGBA = Image; using ImageAlpha = Image; static inline bool SaveImageToFile(const std::string& folder, const std::string& fileName, const ImageRGB& image) { - const uint CHANNEL_NUM = 3; - std::string dst = folder + "/" + fileName; - int res = stbi_write_png(dst.c_str(), image.GetWidth(), image.GetHeight(), CHANNEL_NUM, (unsigned char*)image.GetData(), 0 /*stride in bytes*/); - return res == 1; + +#if OMM_TEST_ENABLE_IMAGE_DUMP + constexpr bool kDumpDebug = true; +#else + constexpr bool kDumpDebug = false; +#endif + + if (kDumpDebug) + { + if (!folder.empty()) + std::filesystem::create_directory(folder); + + const uint CHANNEL_NUM = 3; + std::string dst = folder + "/" + fileName; + int res = stbi_write_png(dst.c_str(), image.GetWidth(), image.GetHeight(), CHANNEL_NUM, (unsigned char*)image.GetData(), 0 /*stride in bytes*/); + return res == 1; + } + return false; } static inline void FillWithCheckerboardRGB(ImageRGB& image, int checkerSize) { @@ -133,7 +147,7 @@ static inline void FillWithCheckerboardRGBA(ImageRGBA& image, int checkerSize) { template static inline void Rasterize(Image& image, const omm::Triangle& t, bool conservative, T color) { - auto kernel = [&image, color](int2 idx, float3* bc, void*) { + auto kernel = [&image, color](int2 idx, void*) { if (!image.IsInsideImage(idx)) return;