From 357c7a525d13376f8032a040570f523d7f82949f Mon Sep 17 00:00:00 2001 From: "Werner, Stefan" Date: Mon, 6 Jul 2026 16:16:15 +0200 Subject: [PATCH 1/7] Clamp Morton branching factor and add regression test Clamp BVHBuilderMorton settings to MAX_BRANCHING_FACTOR when an oversized maxBranchingFactor is provided via RTCBuildArguments.\n\nAdd an integration test that exercises rtcBuildBVH with RTC_BUILD_QUALITY_LOW and maxBranchingFactor=64 to verify the Morton build path returns a valid root. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- kernels/builders/bvh_builder_morton.h | 5 + .../integration/test_embree_release/test.cpp | 100 ++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/kernels/builders/bvh_builder_morton.h b/kernels/builders/bvh_builder_morton.h index 87d4786810..d05031c0a2 100644 --- a/kernels/builders/bvh_builder_morton.h +++ b/kernels/builders/bvh_builder_morton.h @@ -32,12 +32,17 @@ namespace embree if (RTC_BUILD_ARGUMENTS_HAS(settings,minLeafSize )) minLeafSize = settings.minLeafSize; if (RTC_BUILD_ARGUMENTS_HAS(settings,maxLeafSize )) maxLeafSize = settings.maxLeafSize; + if (branchingFactor > MAX_BRANCHING_FACTOR) + branchingFactor = MAX_BRANCHING_FACTOR; + minLeafSize = min(minLeafSize,maxLeafSize); } Settings (size_t branchingFactor, size_t maxDepth, size_t minLeafSize, size_t maxLeafSize, size_t singleThreadThreshold) : branchingFactor(branchingFactor), maxDepth(maxDepth), minLeafSize(minLeafSize), maxLeafSize(maxLeafSize), singleThreadThreshold(singleThreadThreshold) { + if (branchingFactor > MAX_BRANCHING_FACTOR) + branchingFactor = MAX_BRANCHING_FACTOR; minLeafSize = min(minLeafSize,maxLeafSize); } diff --git a/tests/integration/test_embree_release/test.cpp b/tests/integration/test_embree_release/test.cpp index 3585e5c3a2..e729ce663d 100644 --- a/tests/integration/test_embree_release/test.cpp +++ b/tests/integration/test_embree_release/test.cpp @@ -4,10 +4,13 @@ #include #include +#include #include #include +#include #include +#include struct Hit @@ -18,6 +21,56 @@ struct Hit float tfar = std::numeric_limits::infinity(); }; +struct BuildTestNodeHeader +{ + unsigned int childCount; +}; + +static bool buildProgress(void* /*userPtr*/, double /*f*/) +{ + return true; +} + +static void* createNode(RTCThreadLocalAllocator alloc, unsigned int childCount, void* /*userPtr*/) +{ + const size_t bytes = + sizeof(BuildTestNodeHeader) + + sizeof(void*) * childCount + + sizeof(RTCBounds) * childCount; + + char* p = (char*) rtcThreadLocalAlloc(alloc, bytes, 16); + std::memset(p, 0, bytes); + ((BuildTestNodeHeader*)p)->childCount = childCount; + return p; +} + +static void setNodeChildren(void* nodePtr, void** children, unsigned int childCount, void* /*userPtr*/) +{ + BuildTestNodeHeader* h = (BuildTestNodeHeader*) nodePtr; + void** out = (void**) (h + 1); + for (unsigned int i = 0; i < childCount; ++i) out[i] = children[i]; +} + +static void setNodeBounds(void* nodePtr, const RTCBounds** bounds, unsigned int childCount, void* /*userPtr*/) +{ + BuildTestNodeHeader* h = (BuildTestNodeHeader*) nodePtr; + void** childBase = (void**) (h + 1); + RTCBounds* out = (RTCBounds*) (childBase + h->childCount); + for (unsigned int i = 0; i < childCount; ++i) out[i] = *bounds[i]; +} + +static void* createLeaf(RTCThreadLocalAllocator alloc, + const RTCBuildPrimitive* prims, + size_t primCount, + void* /*userPtr*/) +{ + const size_t bytes = sizeof(size_t) + primCount * sizeof(RTCBuildPrimitive); + char* p = (char*) rtcThreadLocalAlloc(alloc, bytes, 16); + *((size_t*)p) = primCount; + std::memcpy(p + sizeof(size_t), prims, primCount * sizeof(RTCBuildPrimitive)); + return p; +} + inline Hit castRay(RTCScene scene, float ox, float oy, float oz, float dx, float dy, float dz) @@ -106,3 +159,50 @@ TEST_CASE("Minimal test", "[minimal]") REQUIRE(true); } +TEST_CASE("Morton builder clamps oversized branching factor", "[bvh-builder]") +{ + RTCDevice device = rtcNewDevice(nullptr); + RTCBVH bvh = rtcNewBVH(device); + + const size_t primitiveCount = 1024; + std::vector prims(primitiveCount); + for (size_t i = 0; i < primitiveCount; ++i) + { + const float x = float(i % 32); + const float y = float((i / 32) % 32); + + RTCBuildPrimitive p{}; + p.lower_x = x * 2.0f; + p.lower_y = y * 2.0f; + p.lower_z = 0.0f; + p.upper_x = p.lower_x + 0.5f; + p.upper_y = p.lower_y + 0.5f; + p.upper_z = 0.5f; + p.geomID = 0; + p.primID = (unsigned int)i; + prims[i] = p; + } + + RTCBuildArguments args = rtcDefaultBuildArguments(); + args.byteSize = sizeof(args); + args.buildQuality = RTC_BUILD_QUALITY_LOW; + args.maxBranchingFactor = 64; + args.maxDepth = 1024; + args.minLeafSize = 1; + args.maxLeafSize = 1; + args.bvh = bvh; + args.primitives = prims.data(); + args.primitiveCount = prims.size(); + args.primitiveArrayCapacity = prims.size(); + args.createNode = createNode; + args.setNodeChildren = setNodeChildren; + args.setNodeBounds = setNodeBounds; + args.createLeaf = createLeaf; + args.buildProgress = buildProgress; + + void* root = rtcBuildBVH(&args); + REQUIRE(root != nullptr); + + rtcReleaseBVH(bvh); + rtcReleaseDevice(device); +} From 55ca27c5fa93ce3c73ba680e8d3256e502c76354 Mon Sep 17 00:00:00 2001 From: "Werner, Stefan" Date: Thu, 9 Jul 2026 12:26:08 +0200 Subject: [PATCH 2/7] Created regression test for morton builder clamp --- tests/CMakeLists.txt | 2 + .../integration/test_embree_release/test.cpp | 101 ------------- tests/regression/CMakeLists.txt | 10 ++ .../morton_builder_clamp_regression.cpp | 134 ++++++++++++++++++ 4 files changed, 146 insertions(+), 101 deletions(-) create mode 100644 tests/regression/CMakeLists.txt create mode 100644 tests/regression/morton_builder_clamp_regression.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e2828acb07..54004a79a7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,6 +39,8 @@ FOREACH(xml ${PRIMITIVE_TESTS}) ENDFOREACH() ENDFOREACH() + ADD_SUBDIRECTORY(regression) + IF (EMBREE_TESTING_INSTALL_TESTS) # test resources diff --git a/tests/integration/test_embree_release/test.cpp b/tests/integration/test_embree_release/test.cpp index e729ce663d..ea5520676b 100644 --- a/tests/integration/test_embree_release/test.cpp +++ b/tests/integration/test_embree_release/test.cpp @@ -4,13 +4,10 @@ #include #include -#include #include #include -#include #include -#include struct Hit @@ -21,56 +18,6 @@ struct Hit float tfar = std::numeric_limits::infinity(); }; -struct BuildTestNodeHeader -{ - unsigned int childCount; -}; - -static bool buildProgress(void* /*userPtr*/, double /*f*/) -{ - return true; -} - -static void* createNode(RTCThreadLocalAllocator alloc, unsigned int childCount, void* /*userPtr*/) -{ - const size_t bytes = - sizeof(BuildTestNodeHeader) + - sizeof(void*) * childCount + - sizeof(RTCBounds) * childCount; - - char* p = (char*) rtcThreadLocalAlloc(alloc, bytes, 16); - std::memset(p, 0, bytes); - ((BuildTestNodeHeader*)p)->childCount = childCount; - return p; -} - -static void setNodeChildren(void* nodePtr, void** children, unsigned int childCount, void* /*userPtr*/) -{ - BuildTestNodeHeader* h = (BuildTestNodeHeader*) nodePtr; - void** out = (void**) (h + 1); - for (unsigned int i = 0; i < childCount; ++i) out[i] = children[i]; -} - -static void setNodeBounds(void* nodePtr, const RTCBounds** bounds, unsigned int childCount, void* /*userPtr*/) -{ - BuildTestNodeHeader* h = (BuildTestNodeHeader*) nodePtr; - void** childBase = (void**) (h + 1); - RTCBounds* out = (RTCBounds*) (childBase + h->childCount); - for (unsigned int i = 0; i < childCount; ++i) out[i] = *bounds[i]; -} - -static void* createLeaf(RTCThreadLocalAllocator alloc, - const RTCBuildPrimitive* prims, - size_t primCount, - void* /*userPtr*/) -{ - const size_t bytes = sizeof(size_t) + primCount * sizeof(RTCBuildPrimitive); - char* p = (char*) rtcThreadLocalAlloc(alloc, bytes, 16); - *((size_t*)p) = primCount; - std::memcpy(p + sizeof(size_t), prims, primCount * sizeof(RTCBuildPrimitive)); - return p; -} - inline Hit castRay(RTCScene scene, float ox, float oy, float oz, float dx, float dy, float dz) @@ -158,51 +105,3 @@ TEST_CASE("Minimal test", "[minimal]") REQUIRE(true); } - -TEST_CASE("Morton builder clamps oversized branching factor", "[bvh-builder]") -{ - RTCDevice device = rtcNewDevice(nullptr); - RTCBVH bvh = rtcNewBVH(device); - - const size_t primitiveCount = 1024; - std::vector prims(primitiveCount); - for (size_t i = 0; i < primitiveCount; ++i) - { - const float x = float(i % 32); - const float y = float((i / 32) % 32); - - RTCBuildPrimitive p{}; - p.lower_x = x * 2.0f; - p.lower_y = y * 2.0f; - p.lower_z = 0.0f; - p.upper_x = p.lower_x + 0.5f; - p.upper_y = p.lower_y + 0.5f; - p.upper_z = 0.5f; - p.geomID = 0; - p.primID = (unsigned int)i; - prims[i] = p; - } - - RTCBuildArguments args = rtcDefaultBuildArguments(); - args.byteSize = sizeof(args); - args.buildQuality = RTC_BUILD_QUALITY_LOW; - args.maxBranchingFactor = 64; - args.maxDepth = 1024; - args.minLeafSize = 1; - args.maxLeafSize = 1; - args.bvh = bvh; - args.primitives = prims.data(); - args.primitiveCount = prims.size(); - args.primitiveArrayCapacity = prims.size(); - args.createNode = createNode; - args.setNodeChildren = setNodeChildren; - args.setNodeBounds = setNodeBounds; - args.createLeaf = createLeaf; - args.buildProgress = buildProgress; - - void* root = rtcBuildBVH(&args); - REQUIRE(root != nullptr); - - rtcReleaseBVH(bvh); - rtcReleaseDevice(device); -} diff --git a/tests/regression/CMakeLists.txt b/tests/regression/CMakeLists.txt new file mode 100644 index 0000000000..80b0a23e80 --- /dev/null +++ b/tests/regression/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright 2009-2021 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +add_executable(embree_regression_morton_builder_clamp morton_builder_clamp_regression.cpp) +target_link_libraries(embree_regression_morton_builder_clamp PRIVATE embree) +set_property(TARGET embree_regression_morton_builder_clamp PROPERTY FOLDER tests/regression) + +if (BUILD_TESTING) + add_test(NAME regression_morton_builder_clamp COMMAND embree_regression_morton_builder_clamp) +endif() diff --git a/tests/regression/morton_builder_clamp_regression.cpp b/tests/regression/morton_builder_clamp_regression.cpp new file mode 100644 index 0000000000..2c1ebac40a --- /dev/null +++ b/tests/regression/morton_builder_clamp_regression.cpp @@ -0,0 +1,134 @@ +// Copyright 2009-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include +#include +#include +#include + +struct BuildTestNodeHeader +{ + unsigned int childCount; +}; + +static bool buildProgress(void* /*userPtr*/, double /*f*/) +{ + return true; +} + +static void* createNode(RTCThreadLocalAllocator alloc, unsigned int childCount, void* /*userPtr*/) +{ + const size_t bytes = + sizeof(BuildTestNodeHeader) + + sizeof(void*) * childCount + + sizeof(RTCBounds) * childCount; + + char* p = (char*) rtcThreadLocalAlloc(alloc, bytes, 16); + std::memset(p, 0, bytes); + ((BuildTestNodeHeader*)p)->childCount = childCount; + return p; +} + +static void setNodeChildren(void* nodePtr, void** children, unsigned int childCount, void* /*userPtr*/) +{ + BuildTestNodeHeader* h = (BuildTestNodeHeader*) nodePtr; + void** out = (void**) (h + 1); + for (unsigned int i = 0; i < childCount; ++i) out[i] = children[i]; +} + +static void setNodeBounds(void* nodePtr, const RTCBounds** bounds, unsigned int childCount, void* /*userPtr*/) +{ + BuildTestNodeHeader* h = (BuildTestNodeHeader*) nodePtr; + void** childBase = (void**) (h + 1); + RTCBounds* out = (RTCBounds*) (childBase + h->childCount); + for (unsigned int i = 0; i < childCount; ++i) out[i] = *bounds[i]; +} + +static void* createLeaf(RTCThreadLocalAllocator alloc, + const RTCBuildPrimitive* prims, + size_t primCount, + void* /*userPtr*/) +{ + const size_t bytes = sizeof(size_t) + primCount * sizeof(RTCBuildPrimitive); + char* p = (char*) rtcThreadLocalAlloc(alloc, bytes, 16); + *((size_t*)p) = primCount; + std::memcpy(p + sizeof(size_t), prims, primCount * sizeof(RTCBuildPrimitive)); + return p; +} + +static std::vector makeGridPrimitives(size_t primitiveCount) +{ + std::vector prims(primitiveCount); + for (size_t i = 0; i < primitiveCount; ++i) + { + const float x = float(i % 32); + const float y = float((i / 32) % 32); + + RTCBuildPrimitive p{}; + p.lower_x = x * 2.0f; + p.lower_y = y * 2.0f; + p.lower_z = 0.0f; + p.upper_x = p.lower_x + 0.5f; + p.upper_y = p.lower_y + 0.5f; + p.upper_z = 0.5f; + p.geomID = 0; + p.primID = (unsigned int)i; + prims[i] = p; + } + return prims; +} + +static bool runCase(unsigned int maxBranchingFactor) +{ + RTCDevice device = rtcNewDevice(nullptr); + if (device == nullptr) + return false; + + RTCBVH bvh = rtcNewBVH(device); + if (bvh == nullptr) + { + rtcReleaseDevice(device); + return false; + } + + std::vector prims = makeGridPrimitives(1024); + + RTCBuildArguments args = rtcDefaultBuildArguments(); + args.byteSize = sizeof(args); + args.buildQuality = RTC_BUILD_QUALITY_LOW; + args.maxBranchingFactor = maxBranchingFactor; + args.maxDepth = 1024; + args.minLeafSize = 1; + args.maxLeafSize = 1; + args.bvh = bvh; + args.primitives = prims.data(); + args.primitiveCount = prims.size(); + args.primitiveArrayCapacity = prims.size(); + args.createNode = createNode; + args.setNodeChildren = setNodeChildren; + args.setNodeBounds = setNodeBounds; + args.createLeaf = createLeaf; + args.buildProgress = buildProgress; + + void* root = rtcBuildBVH(&args); + + rtcReleaseBVH(bvh); + rtcReleaseDevice(device); + return root != nullptr; +} + +int main() +{ + bool okOversized = runCase(64); + bool okExtreme = runCase(std::numeric_limits::max()); + + if (!okOversized) + std::cerr << "Morton clamp regression failed for maxBranchingFactor=64\n"; + if (!okExtreme) + std::cerr << "Morton clamp regression failed for maxBranchingFactor=UINT_MAX\n"; + + return (okOversized && okExtreme) ? 0 : 1; +} From 9d504af21e6bcc939b5d9981e52c0d2ebf722855 Mon Sep 17 00:00:00 2001 From: "Werner, Stefan" Date: Wed, 15 Jul 2026 23:26:29 +0200 Subject: [PATCH 3/7] Fixed regression test --- .../morton_builder_clamp_regression.cpp | 79 +++++++++++-------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/tests/regression/morton_builder_clamp_regression.cpp b/tests/regression/morton_builder_clamp_regression.cpp index 2c1ebac40a..f4d4705d49 100644 --- a/tests/regression/morton_builder_clamp_regression.cpp +++ b/tests/regression/morton_builder_clamp_regression.cpp @@ -1,62 +1,74 @@ -// Copyright 2009-2021 Intel Corporation +// Copyright 2026 Intel Corporation // SPDX-License-Identifier: Apache-2.0 #include #include +#include #include #include #include #include -struct BuildTestNodeHeader +constexpr unsigned int max_branching_factor = 8; + +struct Node { - unsigned int childCount; + Node() + { + for (unsigned int i = 0; i < max_branching_factor; ++i) + children[i] = nullptr; + } + virtual ~Node() = default; + Node *children[max_branching_factor]; }; -static bool buildProgress(void* /*userPtr*/, double /*f*/) +static bool buildProgress(void * /*userPtr*/, double /*f*/) +{ + return true; +} + +bool memoryMonitor(void * /*userPtr*/, ssize_t /*bytes*/, bool /*post*/) { return true; } -static void* createNode(RTCThreadLocalAllocator alloc, unsigned int childCount, void* /*userPtr*/) +static void *createNode(RTCThreadLocalAllocator alloc, unsigned int childCount, void * /*userPtr*/) { - const size_t bytes = - sizeof(BuildTestNodeHeader) + - sizeof(void*) * childCount + - sizeof(RTCBounds) * childCount; - - char* p = (char*) rtcThreadLocalAlloc(alloc, bytes, 16); - std::memset(p, 0, bytes); - ((BuildTestNodeHeader*)p)->childCount = childCount; - return p; + assert(childCount <= max_branching_factor); + if (childCount > max_branching_factor) + return nullptr; + + Node *node = (Node *)rtcThreadLocalAlloc(alloc, sizeof(Node), 16); + new (node) Node(); + return node; } -static void setNodeChildren(void* nodePtr, void** children, unsigned int childCount, void* /*userPtr*/) +static void setNodeChildren(void *nodePtr, void **children, unsigned int childCount, void * /*userPtr*/) { - BuildTestNodeHeader* h = (BuildTestNodeHeader*) nodePtr; - void** out = (void**) (h + 1); - for (unsigned int i = 0; i < childCount; ++i) out[i] = children[i]; + assert(childCount <= max_branching_factor); + if (childCount > max_branching_factor) + return; + Node *node = (Node *)nodePtr; + for (unsigned int i = 0; i < childCount; ++i) + node->children[i] = (Node *)children[i]; } -static void setNodeBounds(void* nodePtr, const RTCBounds** bounds, unsigned int childCount, void* /*userPtr*/) +static void setNodeBounds(void *nodePtr, const RTCBounds **bounds, unsigned int childCount, void * /*userPtr*/) { - BuildTestNodeHeader* h = (BuildTestNodeHeader*) nodePtr; - void** childBase = (void**) (h + 1); - RTCBounds* out = (RTCBounds*) (childBase + h->childCount); - for (unsigned int i = 0; i < childCount; ++i) out[i] = *bounds[i]; + assert(childCount <= max_branching_factor); + /* deliberately empty in regression test */ } -static void* createLeaf(RTCThreadLocalAllocator alloc, - const RTCBuildPrimitive* prims, +static void *createLeaf(RTCThreadLocalAllocator alloc, + const RTCBuildPrimitive *prims, size_t primCount, - void* /*userPtr*/) + void * /*userPtr*/) { - const size_t bytes = sizeof(size_t) + primCount * sizeof(RTCBuildPrimitive); - char* p = (char*) rtcThreadLocalAlloc(alloc, bytes, 16); - *((size_t*)p) = primCount; - std::memcpy(p + sizeof(size_t), prims, primCount * sizeof(RTCBuildPrimitive)); - return p; + + Node *node = (Node *)rtcThreadLocalAlloc(alloc, sizeof(Node), 16); + new (node) Node(); + return node; } static std::vector makeGridPrimitives(size_t primitiveCount) @@ -113,7 +125,7 @@ static bool runCase(unsigned int maxBranchingFactor) args.createLeaf = createLeaf; args.buildProgress = buildProgress; - void* root = rtcBuildBVH(&args); + void *root = rtcBuildBVH(&args); rtcReleaseBVH(bvh); rtcReleaseDevice(device); @@ -122,6 +134,8 @@ static bool runCase(unsigned int maxBranchingFactor) int main() { + /* In the failure case, this test should assert or result in a segfault from stack overflow. */ + bool okOversized = runCase(64); bool okExtreme = runCase(std::numeric_limits::max()); @@ -130,5 +144,6 @@ int main() if (!okExtreme) std::cerr << "Morton clamp regression failed for maxBranchingFactor=UINT_MAX\n"; + std::cout << "Morton clamp regression test completed.\n"; return (okOversized && okExtreme) ? 0 : 1; } From f591cf303e5111cb25f679410feb8cefffa91311 Mon Sep 17 00:00:00 2001 From: "Werner, Stefan" Date: Wed, 15 Jul 2026 23:27:21 +0200 Subject: [PATCH 4/7] Renamed paramters so they wouldn't shadow members --- kernels/builders/bvh_builder_morton.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernels/builders/bvh_builder_morton.h b/kernels/builders/bvh_builder_morton.h index d05031c0a2..de0bbebaf7 100644 --- a/kernels/builders/bvh_builder_morton.h +++ b/kernels/builders/bvh_builder_morton.h @@ -38,8 +38,8 @@ namespace embree minLeafSize = min(minLeafSize,maxLeafSize); } - Settings (size_t branchingFactor, size_t maxDepth, size_t minLeafSize, size_t maxLeafSize, size_t singleThreadThreshold) - : branchingFactor(branchingFactor), maxDepth(maxDepth), minLeafSize(minLeafSize), maxLeafSize(maxLeafSize), singleThreadThreshold(singleThreadThreshold) + Settings (size_t branchingFactor_, size_t maxDepth_, size_t minLeafSize_, size_t maxLeafSize_, size_t singleThreadThreshold_) + : branchingFactor(branchingFactor_), maxDepth(maxDepth_), minLeafSize(minLeafSize_), maxLeafSize(maxLeafSize_), singleThreadThreshold(singleThreadThreshold_) { if (branchingFactor > MAX_BRANCHING_FACTOR) branchingFactor = MAX_BRANCHING_FACTOR; From cbe7df2927b7ce109124d817c7e82ce7670bf564 Mon Sep 17 00:00:00 2001 From: "Werner, Stefan" Date: Thu, 16 Jul 2026 00:18:10 +0200 Subject: [PATCH 5/7] Fixed indentation --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 54004a79a7..5f99ac211d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,7 +39,7 @@ FOREACH(xml ${PRIMITIVE_TESTS}) ENDFOREACH() ENDFOREACH() - ADD_SUBDIRECTORY(regression) +ADD_SUBDIRECTORY(regression) IF (EMBREE_TESTING_INSTALL_TESTS) From 5011f85cff807cb5b188cf62050bb30711f4073c Mon Sep 17 00:00:00 2001 From: "Werner, Stefan" Date: Mon, 3 Aug 2026 14:01:31 +0200 Subject: [PATCH 6/7] Branching factor is limited in multiple places: * User facing API will return an error * Internal API will thrown an exception --- kernels/builders/bvh_builder_hair.h | 7 +++++- kernels/builders/bvh_builder_morton.h | 16 ++++++++---- kernels/builders/bvh_builder_msmblur_hair.h | 7 +++++- kernels/common/rtcore_builder.cpp | 25 +++++++++++++++++++ .../morton_builder_clamp_regression.cpp | 18 +++++++------ 5 files changed, 58 insertions(+), 15 deletions(-) diff --git a/kernels/builders/bvh_builder_hair.h b/kernels/builders/bvh_builder_hair.h index d83e8918a1..e4544dd0a3 100644 --- a/kernels/builders/bvh_builder_hair.h +++ b/kernels/builders/bvh_builder_hair.h @@ -84,7 +84,12 @@ namespace embree createLeaf(createLeaf), progressMonitor(progressMonitor), reportFinishedRange(reportFinishedRange), - alignedHeuristic(prims), unalignedHeuristic(scene,prims), strandHeuristic(scene,prims) {} + alignedHeuristic(prims), unalignedHeuristic(scene,prims), strandHeuristic(scene,prims) + { + if (cfg.branchingFactor > MAX_BRANCHING_FACTOR) { + throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large"); + } + } /*! checks if all primitives are from the same geometry */ __forceinline bool sameGeometry(const PrimInfoRange& range) diff --git a/kernels/builders/bvh_builder_morton.h b/kernels/builders/bvh_builder_morton.h index de0bbebaf7..318001837a 100644 --- a/kernels/builders/bvh_builder_morton.h +++ b/kernels/builders/bvh_builder_morton.h @@ -32,8 +32,9 @@ namespace embree if (RTC_BUILD_ARGUMENTS_HAS(settings,minLeafSize )) minLeafSize = settings.minLeafSize; if (RTC_BUILD_ARGUMENTS_HAS(settings,maxLeafSize )) maxLeafSize = settings.maxLeafSize; - if (branchingFactor > MAX_BRANCHING_FACTOR) - branchingFactor = MAX_BRANCHING_FACTOR; + if (branchingFactor > MAX_BRANCHING_FACTOR) { + throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large"); + } minLeafSize = min(minLeafSize,maxLeafSize); } @@ -41,8 +42,9 @@ namespace embree Settings (size_t branchingFactor_, size_t maxDepth_, size_t minLeafSize_, size_t maxLeafSize_, size_t singleThreadThreshold_) : branchingFactor(branchingFactor_), maxDepth(maxDepth_), minLeafSize(minLeafSize_), maxLeafSize(maxLeafSize_), singleThreadThreshold(singleThreadThreshold_) { - if (branchingFactor > MAX_BRANCHING_FACTOR) - branchingFactor = MAX_BRANCHING_FACTOR; + if (branchingFactor > MAX_BRANCHING_FACTOR) { + throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large"); + } minLeafSize = min(minLeafSize,maxLeafSize); } @@ -208,7 +210,11 @@ namespace embree createLeaf(createLeaf), calculateBounds(calculateBounds), progressMonitor(progressMonitor), - morton(nullptr) {} + morton(nullptr) + { + if (branchingFactor > MAX_BRANCHING_FACTOR) + throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large"); + } ReductionTy createLargeLeaf(size_t depth, const range& current, Allocator alloc) { diff --git a/kernels/builders/bvh_builder_msmblur_hair.h b/kernels/builders/bvh_builder_msmblur_hair.h index 397e8636b1..0d3e487535 100644 --- a/kernels/builders/bvh_builder_msmblur_hair.h +++ b/kernels/builders/bvh_builder_msmblur_hair.h @@ -101,7 +101,12 @@ namespace embree createLeaf(createLeaf), progressMonitor(progressMonitor), unalignedHeuristic(scene), - temporalSplitHeuristic(scene->device,recalculatePrimRef) {} + temporalSplitHeuristic(scene->device,recalculatePrimRef) + { + if (cfg.branchingFactor > MAX_BRANCHING_FACTOR) { + throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large"); + } + } private: diff --git a/kernels/common/rtcore_builder.cpp b/kernels/common/rtcore_builder.cpp index 29e3bdca20..cc064d3b2a 100644 --- a/kernels/common/rtcore_builder.cpp +++ b/kernels/common/rtcore_builder.cpp @@ -367,6 +367,31 @@ RTC_NAMESPACE_BEGIN if (arguments->primitiveArrayCapacity < arguments->primitiveCount) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"primitiveArrayCapacity must be greater or equal to primitiveCount") + if (RTC_BUILD_ARGUMENTS_HAS((*arguments),maxLeafSize) && arguments->maxLeafSize > RTC_BUILD_MAX_PRIMITIVES_PER_LEAF) { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"maxLeafSize must be smaller or equal to RTC_BUILD_MAX_PRIMITIVES_PER_LEAF") + } + + if (RTC_BUILD_ARGUMENTS_HAS((*arguments),maxBranchingFactor)) + { + const unsigned int branchingFactor = arguments->maxBranchingFactor; + if (branchingFactor < 2) { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"maxBranchingFactor must be greater or equal to 2"); + } + + if (arguments->buildQuality == RTC_BUILD_QUALITY_LOW) + { + if (branchingFactor > BVHBuilderMorton::MAX_BRANCHING_FACTOR) { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"maxBranchingFactor too large for RTC_BUILD_QUALITY_LOW (maximum is 8)") + } + } + else if (arguments->buildQuality == RTC_BUILD_QUALITY_MEDIUM || arguments->buildQuality == RTC_BUILD_QUALITY_HIGH) + { + if (branchingFactor > GeneralBVHBuilder::MAX_BRANCHING_FACTOR) { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"maxBranchingFactor too large for this build quality (maximum is 16)") + } + } + } + /* initialize the allocator */ bvh->allocator.init_estimate(arguments->primitiveCount*sizeof(BBox3fa)); bvh->allocator.reset(); diff --git a/tests/regression/morton_builder_clamp_regression.cpp b/tests/regression/morton_builder_clamp_regression.cpp index f4d4705d49..d258c514bd 100644 --- a/tests/regression/morton_builder_clamp_regression.cpp +++ b/tests/regression/morton_builder_clamp_regression.cpp @@ -125,25 +125,27 @@ static bool runCase(unsigned int maxBranchingFactor) args.createLeaf = createLeaf; args.buildProgress = buildProgress; + rtcGetDeviceError(device); void *root = rtcBuildBVH(&args); + RTCError error = rtcGetDeviceError(device); rtcReleaseBVH(bvh); rtcReleaseDevice(device); - return root != nullptr; + return root == nullptr && error == RTC_ERROR_INVALID_ARGUMENT; } int main() { - /* In the failure case, this test should assert or result in a segfault from stack overflow. */ - bool okOversized = runCase(64); bool okExtreme = runCase(std::numeric_limits::max()); - if (!okOversized) - std::cerr << "Morton clamp regression failed for maxBranchingFactor=64\n"; - if (!okExtreme) - std::cerr << "Morton clamp regression failed for maxBranchingFactor=UINT_MAX\n"; + if (!okOversized) { + std::cerr << "Morton oversized maxBranchingFactor regression failed for maxBranchingFactor=64\n"; + } + if (!okExtreme) { + std::cerr << "Morton oversized maxBranchingFactor regression failed for maxBranchingFactor=UINT_MAX\n"; + } - std::cout << "Morton clamp regression test completed.\n"; + std::cout << "Morton oversized branching factor regression test completed.\n"; return (okOversized && okExtreme) ? 0 : 1; } From 985b6dbfa77d1abf1574cda06228fa7a1c401cf3 Mon Sep 17 00:00:00 2001 From: "Werner, Stefan" Date: Tue, 18 Aug 2026 12:46:02 +0200 Subject: [PATCH 7/7] Use unified regression test binary Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/CMakeLists.txt | 3 - tests/regression/CMakeLists.txt | 10 - .../morton_builder_clamp_regression.cpp | 151 ------------- tutorials/CMakeLists.txt | 1 + .../embree_regression_tests/CMakeLists.txt | 12 ++ .../embree_regression_tests.cpp | 198 ++++++++++++++++++ 6 files changed, 211 insertions(+), 164 deletions(-) delete mode 100644 tests/regression/CMakeLists.txt delete mode 100644 tests/regression/morton_builder_clamp_regression.cpp create mode 100644 tutorials/embree_regression_tests/CMakeLists.txt create mode 100644 tutorials/embree_regression_tests/embree_regression_tests.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5f99ac211d..17b58bbb72 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,9 +39,6 @@ FOREACH(xml ${PRIMITIVE_TESTS}) ENDFOREACH() ENDFOREACH() -ADD_SUBDIRECTORY(regression) - - IF (EMBREE_TESTING_INSTALL_TESTS) # test resources INSTALL(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/primitives" DESTINATION "${CMAKE_INSTALL_TESTDIR}/tests" COMPONENT testing PATTERN "*.py" EXCLUDE) diff --git a/tests/regression/CMakeLists.txt b/tests/regression/CMakeLists.txt deleted file mode 100644 index 80b0a23e80..0000000000 --- a/tests/regression/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright 2009-2021 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -add_executable(embree_regression_morton_builder_clamp morton_builder_clamp_regression.cpp) -target_link_libraries(embree_regression_morton_builder_clamp PRIVATE embree) -set_property(TARGET embree_regression_morton_builder_clamp PROPERTY FOLDER tests/regression) - -if (BUILD_TESTING) - add_test(NAME regression_morton_builder_clamp COMMAND embree_regression_morton_builder_clamp) -endif() diff --git a/tests/regression/morton_builder_clamp_regression.cpp b/tests/regression/morton_builder_clamp_regression.cpp deleted file mode 100644 index d258c514bd..0000000000 --- a/tests/regression/morton_builder_clamp_regression.cpp +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2026 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 - -#include -#include - -#include -#include -#include -#include -#include - -constexpr unsigned int max_branching_factor = 8; - -struct Node -{ - Node() - { - for (unsigned int i = 0; i < max_branching_factor; ++i) - children[i] = nullptr; - } - virtual ~Node() = default; - Node *children[max_branching_factor]; -}; - -static bool buildProgress(void * /*userPtr*/, double /*f*/) -{ - return true; -} - -bool memoryMonitor(void * /*userPtr*/, ssize_t /*bytes*/, bool /*post*/) -{ - return true; -} - -static void *createNode(RTCThreadLocalAllocator alloc, unsigned int childCount, void * /*userPtr*/) -{ - assert(childCount <= max_branching_factor); - if (childCount > max_branching_factor) - return nullptr; - - Node *node = (Node *)rtcThreadLocalAlloc(alloc, sizeof(Node), 16); - new (node) Node(); - return node; -} - -static void setNodeChildren(void *nodePtr, void **children, unsigned int childCount, void * /*userPtr*/) -{ - assert(childCount <= max_branching_factor); - if (childCount > max_branching_factor) - return; - Node *node = (Node *)nodePtr; - for (unsigned int i = 0; i < childCount; ++i) - node->children[i] = (Node *)children[i]; -} - -static void setNodeBounds(void *nodePtr, const RTCBounds **bounds, unsigned int childCount, void * /*userPtr*/) -{ - assert(childCount <= max_branching_factor); - /* deliberately empty in regression test */ -} - -static void *createLeaf(RTCThreadLocalAllocator alloc, - const RTCBuildPrimitive *prims, - size_t primCount, - void * /*userPtr*/) -{ - - Node *node = (Node *)rtcThreadLocalAlloc(alloc, sizeof(Node), 16); - new (node) Node(); - return node; -} - -static std::vector makeGridPrimitives(size_t primitiveCount) -{ - std::vector prims(primitiveCount); - for (size_t i = 0; i < primitiveCount; ++i) - { - const float x = float(i % 32); - const float y = float((i / 32) % 32); - - RTCBuildPrimitive p{}; - p.lower_x = x * 2.0f; - p.lower_y = y * 2.0f; - p.lower_z = 0.0f; - p.upper_x = p.lower_x + 0.5f; - p.upper_y = p.lower_y + 0.5f; - p.upper_z = 0.5f; - p.geomID = 0; - p.primID = (unsigned int)i; - prims[i] = p; - } - return prims; -} - -static bool runCase(unsigned int maxBranchingFactor) -{ - RTCDevice device = rtcNewDevice(nullptr); - if (device == nullptr) - return false; - - RTCBVH bvh = rtcNewBVH(device); - if (bvh == nullptr) - { - rtcReleaseDevice(device); - return false; - } - - std::vector prims = makeGridPrimitives(1024); - - RTCBuildArguments args = rtcDefaultBuildArguments(); - args.byteSize = sizeof(args); - args.buildQuality = RTC_BUILD_QUALITY_LOW; - args.maxBranchingFactor = maxBranchingFactor; - args.maxDepth = 1024; - args.minLeafSize = 1; - args.maxLeafSize = 1; - args.bvh = bvh; - args.primitives = prims.data(); - args.primitiveCount = prims.size(); - args.primitiveArrayCapacity = prims.size(); - args.createNode = createNode; - args.setNodeChildren = setNodeChildren; - args.setNodeBounds = setNodeBounds; - args.createLeaf = createLeaf; - args.buildProgress = buildProgress; - - rtcGetDeviceError(device); - void *root = rtcBuildBVH(&args); - RTCError error = rtcGetDeviceError(device); - - rtcReleaseBVH(bvh); - rtcReleaseDevice(device); - return root == nullptr && error == RTC_ERROR_INVALID_ARGUMENT; -} - -int main() -{ - bool okOversized = runCase(64); - bool okExtreme = runCase(std::numeric_limits::max()); - - if (!okOversized) { - std::cerr << "Morton oversized maxBranchingFactor regression failed for maxBranchingFactor=64\n"; - } - if (!okExtreme) { - std::cerr << "Morton oversized maxBranchingFactor regression failed for maxBranchingFactor=UINT_MAX\n"; - } - - std::cout << "Morton oversized branching factor regression test completed.\n"; - return (okOversized && okExtreme) ? 0 : 1; -} diff --git a/tutorials/CMakeLists.txt b/tutorials/CMakeLists.txt index dce001eaa4..10486521e8 100644 --- a/tutorials/CMakeLists.txt +++ b/tutorials/CMakeLists.txt @@ -113,6 +113,7 @@ ADD_SUBDIRECTORY(ray_mask) ADD_SUBDIRECTORY(forest) ADD_SUBDIRECTORY(host_device_memory) ADD_SUBDIRECTORY(embree_tests) +ADD_SUBDIRECTORY(embree_regression_tests) ENDIF() diff --git a/tutorials/embree_regression_tests/CMakeLists.txt b/tutorials/embree_regression_tests/CMakeLists.txt new file mode 100644 index 0000000000..355bccbf6c --- /dev/null +++ b/tutorials/embree_regression_tests/CMakeLists.txt @@ -0,0 +1,12 @@ +## Copyright 2009-2021 Intel Corporation +## SPDX-License-Identifier: Apache-2.0 + +ADD_EXECUTABLE(embree_regression_tests ../../kernels/embree.rc embree_regression_tests.cpp) +TARGET_LINK_LIBRARIES(embree_regression_tests embree) +SET_PROPERTY(TARGET embree_regression_tests PROPERTY FOLDER tutorials) +SET_PROPERTY(TARGET embree_regression_tests APPEND PROPERTY COMPILE_FLAGS " ${FLAGS_LOWEST}") +INSTALL(TARGETS embree_regression_tests DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT examples) +SIGN_TARGET(embree_regression_tests) + +ADD_EMBREE_TEST_ECS(embree_regression_tests embree_regression_tests NO_REFERENCE NO_ISPC NO_SYCL) +SET_EMBREE_TEST_PROPERTIES(embree_regression_tests PROPERTIES TIMEOUT 7000) diff --git a/tutorials/embree_regression_tests/embree_regression_tests.cpp b/tutorials/embree_regression_tests/embree_regression_tests.cpp new file mode 100644 index 0000000000..00adaf857b --- /dev/null +++ b/tutorials/embree_regression_tests/embree_regression_tests.cpp @@ -0,0 +1,198 @@ +// Copyright 2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include +#include +#include +#include +#include + +#if defined(RTC_NAMESPACE_USE) +RTC_NAMESPACE_USE +#endif + +namespace +{ + constexpr unsigned int max_branching_factor = 8; + + struct CaseResult + { + bool pass; + bool skip; + std::string message; + }; + + struct Node + { + Node() + { + for (unsigned int i = 0; i < max_branching_factor; ++i) + children[i] = nullptr; + } + + virtual ~Node() = default; + Node* children[max_branching_factor]; + }; + + static CaseResult passResult(const char* msg) + { + return { true, false, msg }; + } + + static CaseResult failResult(const char* msg) + { + return { false, false, msg }; + } + + static bool buildProgress(void* /*userPtr*/, double /*f*/) + { + return true; + } + + static void* createNode(RTCThreadLocalAllocator alloc, unsigned int childCount, void* /*userPtr*/) + { + assert(childCount <= max_branching_factor); + if (childCount > max_branching_factor) + return nullptr; + + Node* node = (Node*)rtcThreadLocalAlloc(alloc, sizeof(Node), 16); + new (node) Node(); + return node; + } + + static void setNodeChildren(void* nodePtr, void** children, unsigned int childCount, void* /*userPtr*/) + { + assert(childCount <= max_branching_factor); + if (childCount > max_branching_factor) + return; + + Node* node = (Node*)nodePtr; + for (unsigned int i = 0; i < childCount; ++i) + node->children[i] = (Node*)children[i]; + } + + static void setNodeBounds(void* /*nodePtr*/, const RTCBounds** /*bounds*/, unsigned int childCount, void* /*userPtr*/) + { + assert(childCount <= max_branching_factor); + } + + static void* createLeaf(RTCThreadLocalAllocator alloc, + const RTCBuildPrimitive* /*prims*/, + size_t /*primCount*/, + void* /*userPtr*/) + { + Node* node = (Node*)rtcThreadLocalAlloc(alloc, sizeof(Node), 16); + new (node) Node(); + return node; + } + + static std::vector makeGridPrimitives(size_t primitiveCount) + { + std::vector prims(primitiveCount); + for (size_t i = 0; i < primitiveCount; ++i) + { + const float x = float(i % 32); + const float y = float((i / 32) % 32); + + RTCBuildPrimitive& p = prims[i]; + p = {}; + p.lower_x = x * 2.0f; + p.lower_y = y * 2.0f; + p.lower_z = 0.0f; + p.upper_x = p.lower_x + 0.5f; + p.upper_y = p.lower_y + 0.5f; + p.upper_z = 0.5f; + p.geomID = 0; + p.primID = (unsigned int)i; + } + return prims; + } + + static bool morton_builder_rejects_oversized_branching_factor(RTCDevice device, unsigned int maxBranchingFactor) + { + RTCBVH bvh = rtcNewBVH(device); + if (!bvh) + return false; + + std::vector prims = makeGridPrimitives(1024); + + RTCBuildArguments args = rtcDefaultBuildArguments(); + args.byteSize = sizeof(args); + args.buildQuality = RTC_BUILD_QUALITY_LOW; + args.maxBranchingFactor = maxBranchingFactor; + args.maxDepth = 1024; + args.minLeafSize = 1; + args.maxLeafSize = 1; + args.bvh = bvh; + args.primitives = prims.data(); + args.primitiveCount = prims.size(); + args.primitiveArrayCapacity = prims.size(); + args.createNode = createNode; + args.setNodeChildren = setNodeChildren; + args.setNodeBounds = setNodeBounds; + args.createLeaf = createLeaf; + args.buildProgress = buildProgress; + + rtcGetDeviceError(device); + void* root = rtcBuildBVH(&args); + const RTCError error = rtcGetDeviceError(device); + + rtcReleaseBVH(bvh); + return root == nullptr && error == RTC_ERROR_INVALID_ARGUMENT; + } + + static CaseResult morton_builder_clamp(RTCDevice device) + { + if (!morton_builder_rejects_oversized_branching_factor(device, 64)) + return failResult("maxBranchingFactor=64 was not rejected"); + + if (!morton_builder_rejects_oversized_branching_factor(device, std::numeric_limits::max())) + return failResult("maxBranchingFactor=UINT_MAX was not rejected"); + + return passResult("oversized maxBranchingFactor values are rejected"); + } + + struct TestCase + { + const char* name; + CaseResult (*fn)(RTCDevice); + }; +} + +int main() +{ + RTCDevice device = rtcNewDevice(nullptr); + if (!device) + { + std::printf("FAIL create_device\n"); + return 1; + } + + const TestCase tests[] = { + { "Morton-builder-clamp", morton_builder_clamp } + }; + + int failed = 0; + for (const TestCase& tc : tests) + { + const CaseResult result = tc.fn(device); + if (result.pass) + std::printf("PASS %s: %s\n", tc.name, result.message.c_str()); + else + { + ++failed; + std::printf("FAIL %s: %s\n", tc.name, result.message.c_str()); + } + } + + rtcReleaseDevice(device); + + std::printf("SUMMARY total=%u passed=%u failed=%d\n", + (unsigned)(sizeof(tests) / sizeof(tests[0])), + (unsigned)(sizeof(tests) / sizeof(tests[0])) - (unsigned)failed, + failed); + return failed == 0 ? 0 : 1; +}