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 87d4786810..318001837a 100644 --- a/kernels/builders/bvh_builder_morton.h +++ b/kernels/builders/bvh_builder_morton.h @@ -32,12 +32,19 @@ 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) { + throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large"); + } + 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) { + throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large"); + } minLeafSize = min(minLeafSize,maxLeafSize); } @@ -203,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/CMakeLists.txt b/tests/CMakeLists.txt index e2828acb07..17b58bbb72 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,7 +39,6 @@ FOREACH(xml ${PRIMITIVE_TESTS}) ENDFOREACH() ENDFOREACH() - 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/integration/test_embree_release/test.cpp b/tests/integration/test_embree_release/test.cpp index 3585e5c3a2..ea5520676b 100644 --- a/tests/integration/test_embree_release/test.cpp +++ b/tests/integration/test_embree_release/test.cpp @@ -105,4 +105,3 @@ TEST_CASE("Minimal test", "[minimal]") REQUIRE(true); } - 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; +}