diff --git a/common/math/lbbox.h b/common/math/lbbox.h index 7619199780..9fa43e30f8 100644 --- a/common/math/lbbox.h +++ b/common/math/lbbox.h @@ -25,22 +25,22 @@ namespace embree template __forceinline LBBox ( const LBBox& other ) - : bounds0(other.bounds0), bounds1(other.bounds1) {} + : bounds0(other.bounds0), bounds1(other.bounds1) {} - __forceinline LBBox& operator= ( const LBBox& other ) { - bounds0 = other.bounds0; bounds1 = other.bounds1; return *this; + __forceinline LBBox& operator= ( const LBBox& other ) { + bounds0 = other.bounds0; bounds1 = other.bounds1; return *this; } - __forceinline LBBox (EmptyTy) + __forceinline LBBox (EmptyTy) : bounds0(EmptyTy()), bounds1(EmptyTy()) {} - - __forceinline explicit LBBox ( const BBox& bounds) + + __forceinline explicit LBBox ( const BBox& bounds) : bounds0(bounds), bounds1(bounds) { } - - __forceinline LBBox ( const BBox& bounds0, const BBox& bounds1) + + __forceinline LBBox ( const BBox& bounds0, const BBox& bounds1) : bounds0(bounds0), bounds1(bounds1) { } - LBBox ( const avector>& bounds ) + LBBox ( const avector>& bounds ) { assert(bounds.size()); BBox b0 = bounds.front(); @@ -61,12 +61,29 @@ namespace embree template __forceinline LBBox(const BoundsFunc& bounds, const BBox1f& time_range, float numTimeSegments) { + if (!(numTimeSegments > 0.0f)) { + bounds0 = EmptyTy(); + bounds1 = EmptyTy(); + return; + } + const float lower = time_range.lower*numTimeSegments; const float upper = time_range.upper*numTimeSegments; const float ilowerf = floor(lower); const float iupperf = ceil(upper); - const int ilower = (int)ilowerf; - const int iupper = (int)iupperf; + if (!(ilowerf == ilowerf) || !(iupperf == iupperf)) { + bounds0 = EmptyTy(); + bounds1 = EmptyTy(); + return; + } + + const int ilower = (int)clamp(ilowerf, 0.0f, numTimeSegments); + const int iupper = (int)clamp(iupperf, 0.0f, numTimeSegments); + if (iupper <= ilower) { + bounds0 = EmptyTy(); + bounds1 = EmptyTy(); + return; + } const BBox blower0 = bounds(ilower); const BBox bupper1 = bounds(iupper); @@ -101,24 +118,46 @@ namespace embree template __forceinline LBBox(const BoundsFunc& bounds, const BBox1f& time_range_in, const BBox1f& geom_time_range, float geom_time_segments) { + if (!(geom_time_segments > 0.0f) || !(geom_time_range.size() > 0.0f)) { + bounds0 = EmptyTy(); + bounds1 = EmptyTy(); + return; + } + /* normalize global time_range_in to local geom_time_range */ const BBox1f time_range((time_range_in.lower-geom_time_range.lower)/geom_time_range.size(), (time_range_in.upper-geom_time_range.lower)/geom_time_range.size()); - + const float lower = time_range.lower*geom_time_segments; const float upper = time_range.upper*geom_time_segments; const float ilowerf = floor(lower); const float iupperf = ceil(upper); - const float ilowerfc = max(0.0f,ilowerf); - const float iupperfc = min(iupperf,geom_time_segments); + if (!(ilowerf == ilowerf) || !(iupperf == iupperf)) { + bounds0 = EmptyTy(); + bounds1 = EmptyTy(); + return; + } + + const float ilowerfc = clamp(ilowerf, 0.0f, geom_time_segments); + const float iupperfc = clamp(iupperf, 0.0f, geom_time_segments); const int ilowerc = (int)ilowerfc; const int iupperc = (int)iupperfc; - assert(iupperc-ilowerc > 0); + if (iupperc <= ilowerc) { + bounds0 = EmptyTy(); + bounds1 = EmptyTy(); + return; + } /* this larger iteration range guarantees that we process borders of geom_time_range is (partially) inside time_range_in */ - const int ilower_iter = max(-1,(int)ilowerf); - const int iupper_iter = min((int)iupperf,(int)geom_time_segments+1); - + const float iter_max = geom_time_segments + 1.0f; + const int ilower_iter = (int)clamp(ilowerf, -1.0f, iter_max); + const int iupper_iter = (int)clamp(iupperf, -1.0f, iter_max); + if (iupper_iter <= ilower_iter) { + bounds0 = EmptyTy(); + bounds1 = EmptyTy(); + return; + } + const BBox blower0 = bounds(ilowerc); const BBox bupper1 = bounds(iupperc); if (iupper_iter-ilower_iter == 1) { @@ -163,7 +202,7 @@ namespace embree bounds1 = b1; return; } - + for (int i = ilower+1; ibounds0 = b0; this->bounds1 = b1; } @@ -255,7 +294,7 @@ namespace embree } /* calculates bounds for [0,1] time range from bounds in dt time range */ - __forceinline LBBox global(const BBox1f& dt) const + __forceinline LBBox global(const BBox1f& dt) const { const float rcp_dt_size = 1.0f/dt.size(); const BBox b0 = interpolate(-dt.lower*rcp_dt_size); @@ -268,7 +307,7 @@ namespace embree //template friend __forceinline bool operator!=( const LBBox& a, const LBBox& b ) { return a.bounds0 != b.bounds0 || a.bounds1 != b.bounds1; } friend __forceinline bool operator==( const LBBox& a, const LBBox& b ) { return a.bounds0 == b.bounds0 && a.bounds1 == b.bounds1; } friend __forceinline bool operator!=( const LBBox& a, const LBBox& b ) { return a.bounds0 != b.bounds0 || a.bounds1 != b.bounds1; } - + /*! output operator */ friend __forceinline embree_ostream operator<<(embree_ostream cout, const LBBox& box) { return cout << "LBBox { " << box.bounds0 << "; " << box.bounds1 << " }"; @@ -288,7 +327,7 @@ namespace embree __forceinline bool isvalid_non_empty( const LBBox& v ) { return isvalid_non_empty(v.bounds0) && isvalid_non_empty(v.bounds1); } - + template __forceinline T expectedArea(const T& a0, const T& a1, const T& b0, const T& b1) { @@ -296,8 +335,8 @@ namespace embree const T db = b1-b0; return a0*b0+(a0*db+da*b0)*T(0.5f) + da*db*T(1.0f/3.0f); } - - template<> __forceinline float LBBox::expectedHalfArea() const + + template<> __forceinline float LBBox::expectedHalfArea() const { const Vec3fa d0 = bounds0.size(); const Vec3fa d1 = bounds1.size(); @@ -309,7 +348,7 @@ namespace embree template __forceinline float expectedApproxHalfArea(const LBBox& box) { - return box.expectedApproxHalfArea(); + return box.expectedApproxHalfArea(); } template diff --git a/include/embree4/rtcore_common.h b/include/embree4/rtcore_common.h index 57448ddaea..4cac024a20 100644 --- a/include/embree4/rtcore_common.h +++ b/include/embree4/rtcore_common.h @@ -35,7 +35,7 @@ typedef int ssize_t; #endif #endif -#if defined(_WIN32) +#if defined(_WIN32) # define RTC_FORCEINLINE __forceinline #else # define RTC_FORCEINLINE inline __attribute__((always_inline)) @@ -223,25 +223,25 @@ enum RTCFeatureFlags RTC_FEATURE_FLAG_ROUND_BSPLINE_CURVE | RTC_FEATURE_FLAG_ROUND_HERMITE_CURVE | RTC_FEATURE_FLAG_ROUND_CATMULL_ROM_CURVE, - + RTC_FEATURE_FLAG_FLAT_CURVES = RTC_FEATURE_FLAG_FLAT_LINEAR_CURVE | RTC_FEATURE_FLAG_FLAT_BEZIER_CURVE | RTC_FEATURE_FLAG_FLAT_BSPLINE_CURVE | RTC_FEATURE_FLAG_FLAT_HERMITE_CURVE | RTC_FEATURE_FLAG_FLAT_CATMULL_ROM_CURVE, - + RTC_FEATURE_FLAG_NORMAL_ORIENTED_CURVES = RTC_FEATURE_FLAG_NORMAL_ORIENTED_BEZIER_CURVE | RTC_FEATURE_FLAG_NORMAL_ORIENTED_BSPLINE_CURVE | RTC_FEATURE_FLAG_NORMAL_ORIENTED_HERMITE_CURVE | RTC_FEATURE_FLAG_NORMAL_ORIENTED_CATMULL_ROM_CURVE, - + RTC_FEATURE_FLAG_LINEAR_CURVES = RTC_FEATURE_FLAG_CONE_LINEAR_CURVE | RTC_FEATURE_FLAG_ROUND_LINEAR_CURVE | RTC_FEATURE_FLAG_FLAT_LINEAR_CURVE, - + RTC_FEATURE_FLAG_BEZIER_CURVES = RTC_FEATURE_FLAG_ROUND_BEZIER_CURVE | RTC_FEATURE_FLAG_FLAT_BEZIER_CURVE | @@ -256,7 +256,7 @@ enum RTCFeatureFlags RTC_FEATURE_FLAG_ROUND_HERMITE_CURVE | RTC_FEATURE_FLAG_FLAT_HERMITE_CURVE | RTC_FEATURE_FLAG_NORMAL_ORIENTED_HERMITE_CURVE, - + RTC_FEATURE_FLAG_CURVES = RTC_FEATURE_FLAG_CONE_LINEAR_CURVE | RTC_FEATURE_FLAG_ROUND_LINEAR_CURVE | @@ -273,7 +273,7 @@ enum RTCFeatureFlags RTC_FEATURE_FLAG_ROUND_CATMULL_ROM_CURVE | RTC_FEATURE_FLAG_FLAT_CATMULL_ROM_CURVE | RTC_FEATURE_FLAG_NORMAL_ORIENTED_CATMULL_ROM_CURVE, - + RTC_FEATURE_FLAG_INSTANCE = 1 << 23, RTC_FEATURE_FLAG_FILTER_FUNCTION_IN_ARGUMENTS = 1 << 24, @@ -361,13 +361,13 @@ RTC_FORCEINLINE void rtcInitRayQueryContext(struct RTCRayQueryContext* context) } /* Point query structure for closest point query */ -struct RTC_ALIGN(16) RTCPointQuery +struct RTC_ALIGN(16) RTCPointQuery { float x; // x coordinate of the query point float y; // y coordinate of the query point float z; // z coordinate of the query point float time; // time of the point query - float radius; // radius of the point query + float radius; // radius of the point query }; /* Structure of a packet of 4 query points */ @@ -387,7 +387,7 @@ struct RTC_ALIGN(32) RTCPointQuery8 float y[8]; // y coordinate of the query point float z[8]; // z coordinate of the query point float time[8]; // time of the point query - float radius[8]; // radius ofr the point query + float radius[8]; // radius ofr the point query }; /* Structure of a packet of 16 query points */ @@ -406,11 +406,11 @@ struct RTC_ALIGN(16) RTCPointQueryContext { // accumulated 4x4 column major matrices from world space to instance space. // undefined if size == 0. - float world2inst[RTC_MAX_INSTANCE_LEVEL_COUNT][16]; + float world2inst[RTC_MAX_INSTANCE_LEVEL_COUNT][16]; // accumulated 4x4 column major matrices from instance space to world space. // undefined if size == 0. - float inst2world[RTC_MAX_INSTANCE_LEVEL_COUNT][16]; + float inst2world[RTC_MAX_INSTANCE_LEVEL_COUNT][16]; // instance ids. unsigned int instID[RTC_MAX_INSTANCE_LEVEL_COUNT]; @@ -451,13 +451,13 @@ struct RTC_ALIGN(16) RTCPointQueryFunctionArguments void* userPtr; // primitive and geometry ID of primitive - unsigned int primID; - unsigned int geomID; + unsigned int primID; + unsigned int geomID; // the context with transformation and instance ID stack struct RTCPointQueryContext* context; - // If the current instance transform M (= context->world2inst[context->instStackSize]) + // If the current instance transform M (= context->world2inst[context->instStackSize]) // is a similarity matrix, i.e there is a constant factor similarityScale such that // for all x,y: dist(Mx, My) = similarityScale * dist(x, y), // The similarity scale is 0, if the current instance transform is not a 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.h b/kernels/builders/bvh_builder_msmblur.h index d4e3388db5..20de5c47a6 100644 --- a/kernels/builders/bvh_builder_msmblur.h +++ b/kernels/builders/bvh_builder_msmblur.h @@ -431,6 +431,14 @@ namespace embree if (in.depth > cfg.maxDepth) throw_RTCError(RTC_ERROR_UNKNOWN,"depth limit reached"); + if (in.prims.prims) + { + const mvector& prims = *in.prims.prims; + if (in.prims.begin() > in.prims.end() || in.prims.end() > prims.size()) { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid motion-blur primitive range"); + } + } + /* replace already found split by fallback split */ const BuildRecordSplit current(BuildRecord(in.prims,in.depth),findFallback(in.prims)); 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/accelset.h b/kernels/common/accelset.h index f78830e397..e3ea0ebd3b 100644 --- a/kernels/common/accelset.h +++ b/kernels/common/accelset.h @@ -13,7 +13,7 @@ namespace embree { struct IntersectFunctionNArguments; struct OccludedFunctionNArguments; - + struct IntersectFunctionNArguments : public RTCIntersectFunctionNArguments { Geometry* geometry; @@ -32,7 +32,7 @@ namespace embree class AccelSet : public Geometry { public: - typedef RTCIntersectFunctionN IntersectFuncN; + typedef RTCIntersectFunctionN IntersectFuncN; typedef RTCOccludedFunctionN OccludedFuncN; typedef void (*ErrorFunc) (); @@ -40,33 +40,39 @@ namespace embree { IntersectorN (ErrorFunc error = nullptr) ; IntersectorN (IntersectFuncN intersect, OccludedFuncN occluded, const char* name); - + operator bool() const { return name; } - + public: static const char* type; IntersectFuncN intersect; - OccludedFuncN occluded; + OccludedFuncN occluded; const char* name; }; - + public: - + /*! construction */ AccelSet (Device* device, Geometry::GType gtype, size_t items, size_t numTimeSteps); - + /*! makes the acceleration structure immutable */ virtual void immutable () {} - + /*! build accel */ virtual void build () = 0; /*! check if the i'th primitive is valid between the specified time range */ __forceinline bool valid(size_t i, const range& itime_range) const { - for (size_t itime = itime_range.begin(); itime <= itime_range.end(); itime++) + const size_t begin = itime_range.begin(); + const size_t end = itime_range.end(); + if (begin > end || end > fnumTimeSegments) { + return false; + } + + for (size_t itime = begin; itime <= end; itime++) if (!isvalid_non_empty(bounds(i,itime))) return false; - + return true; } @@ -121,7 +127,7 @@ namespace embree __forceinline LBBox3fa linearBounds(size_t primID, const BBox1f& dt) const { return LBBox3fa([&] (size_t itime) { return bounds(primID, itime); }, dt, time_range, fnumTimeSegments); } - + /*! calculates the linear bounds of the i'th primitive for the specified time range */ __forceinline bool linearBounds(size_t i, const BBox1f& time_range, LBBox3fa& bbox) const { if (!valid(i, timeSegmentRange(time_range))) return false; @@ -133,7 +139,7 @@ namespace embree unsigned int getTopologyVersion() const { return numPrimitives; } - + /* returns true if topology changed */ bool topologyChanged(unsigned int otherVersion) const { return numPrimitives != otherVersion; @@ -142,10 +148,10 @@ namespace embree public: /*! Intersects a single ray with the scene. */ - __forceinline bool intersect (RayHit& ray, unsigned int geomID, unsigned int primID, RayQueryContext* context) + __forceinline bool intersect (RayHit& ray, unsigned int geomID, unsigned int primID, RayQueryContext* context) { assert(primID < size()); - + int mask = -1; IntersectFunctionNArguments args; args.valid = &mask; @@ -161,7 +167,7 @@ namespace embree IntersectFuncN intersectFunc = nullptr; intersectFunc = intersectorN.intersect; - + if (context->getIntersectFunction()) intersectFunc = context->getIntersectFunction(); @@ -202,10 +208,10 @@ namespace embree } /*! Intersects a single ray with the scene. */ - __forceinline bool intersect (RayHit& ray, unsigned int geomID, unsigned int primID, RayQueryContext* context, RTCScene& forward_scene) + __forceinline bool intersect (RayHit& ray, unsigned int geomID, unsigned int primID, RayQueryContext* context, RTCScene& forward_scene) { assert(primID < size()); - + int mask = -1; IntersectFunctionNArguments args; args.valid = &mask; @@ -221,19 +227,19 @@ namespace embree typedef void (*RTCIntersectFunctionSYCL)(const void* args); RTCIntersectFunctionSYCL intersectFunc = nullptr; - + #if EMBREE_SYCL_GEOMETRY_CALLBACK if (context->args->feature_mask & RTC_FEATURE_FLAG_USER_GEOMETRY_CALLBACK_IN_GEOMETRY) intersectFunc = (RTCIntersectFunctionSYCL) intersectorN.intersect; #endif - + if (context->args->feature_mask & RTC_FEATURE_FLAG_USER_GEOMETRY_CALLBACK_IN_ARGUMENTS) if (context->getIntersectFunction()) intersectFunc = (RTCIntersectFunctionSYCL) context->getIntersectFunction(); if (intersectFunc) intersectFunc(&args); - + forward_scene = args.forward_scene; return mask != 0; } @@ -263,24 +269,24 @@ namespace embree if (context->args->feature_mask & RTC_FEATURE_FLAG_USER_GEOMETRY_CALLBACK_IN_GEOMETRY) occludedFunc = (RTCOccludedFunctionSYCL) intersectorN.occluded; #endif - + if (context->args->feature_mask & RTC_FEATURE_FLAG_USER_GEOMETRY_CALLBACK_IN_ARGUMENTS) if (context->getOccludedFunction()) occludedFunc = (RTCOccludedFunctionSYCL) context->getOccludedFunction(); if (occludedFunc) occludedFunc(&args); - + forward_scene = args.forward_scene; return mask != 0; } /*! Intersects a packet of K rays with the scene. */ template - __forceinline void intersect (const vbool& valid, RayHitK& ray, unsigned int geomID, unsigned int primID, RayQueryContext* context) + __forceinline void intersect (const vbool& valid, RayHitK& ray, unsigned int geomID, unsigned int primID, RayQueryContext* context) { assert(primID < size()); - + vint mask = valid.mask32(); IntersectFunctionNArguments args; args.valid = (int*)&mask; @@ -296,7 +302,7 @@ namespace embree IntersectFuncN intersectFunc = nullptr; intersectFunc = intersectorN.intersect; - + if (context->getIntersectFunction()) intersectFunc = context->getIntersectFunction(); @@ -309,7 +315,7 @@ namespace embree __forceinline void occluded (const vbool& valid, RayK& ray, unsigned int geomID, unsigned int primID, RayQueryContext* context) { assert(primID < size()); - + vint mask = valid.mask32(); OccludedFunctionNArguments args; args.valid = (int*)&mask; @@ -325,7 +331,7 @@ namespace embree OccludedFuncN occludedFunc = nullptr; occludedFunc = intersectorN.occluded; - + if (context->getOccludedFunction()) occludedFunc = context->getOccludedFunction(); @@ -337,7 +343,7 @@ namespace embree RTCBoundsFunction boundsFunc; IntersectorN intersectorN; }; - + #define DEFINE_SET_INTERSECTORN(symbol,intersector) \ AccelSet::IntersectorN symbol() { \ return AccelSet::IntersectorN(intersector::intersect, \ diff --git a/kernels/common/default.h b/kernels/common/default.h index e53e60a7b2..838d36cded 100644 --- a/kernels/common/default.h +++ b/kernels/common/default.h @@ -251,8 +251,13 @@ namespace embree { const float round_up = 1.0f+2.0f*float(ulp); // corrects inaccuracies to precisely match time step const float round_down = 1.0f-2.0f*float(ulp); - const int itime_lower = (int)max(floor(round_up *time_range.lower*numTimeSegments), 0.0f); - const int itime_upper = (int)min(ceil (round_down*time_range.upper*numTimeSegments), numTimeSegments); + const float lowerf = floor(round_up * time_range.lower * numTimeSegments); + const float upperf = ceil (round_down * time_range.upper * numTimeSegments); + const int itime_lower = (int)clamp(lowerf, 0.0f, numTimeSegments); + const int itime_upper = (int)clamp(upperf, 0.0f, numTimeSegments); + if (itime_upper < itime_lower) { + return make_range(itime_lower, itime_lower); + } return make_range(itime_lower, itime_upper); } diff --git a/kernels/common/motion_derivative.h b/kernels/common/motion_derivative.h index c619d6a675..bc1eb97db5 100644 --- a/kernels/common/motion_derivative.h +++ b/kernels/common/motion_derivative.h @@ -111,8 +111,17 @@ struct MotionDerivative Interval1f const& interval, unsigned int& numRoots, float* roots, - unsigned int maxNumRoots) + unsigned int maxNumRoots, + unsigned int depth = 0) { + if (numRoots >= maxNumRoots) { + return; + } + + if (depth > 64) { + return; + } + Interval1f range = eval(interval); if (range.lower > 0 || range.upper < 0 || range.lower >= range.upper) return; @@ -134,8 +143,11 @@ struct MotionDerivative return; } - findRoots(eval, Interval1f(interval.lower, split), numRoots, roots, maxNumRoots); - findRoots(eval, Interval1f(split, interval.upper), numRoots, roots, maxNumRoots); + findRoots(eval, Interval1f(interval.lower, split), numRoots, roots, maxNumRoots, depth + 1); + if (numRoots >= maxNumRoots) { + return; + } + findRoots(eval, Interval1f(split, interval.upper), numRoots, roots, maxNumRoots, depth + 1); } }; 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/kernels/common/scene_curves.h b/kernels/common/scene_curves.h index 7350a20ecd..53f10e78f1 100644 --- a/kernels/common/scene_curves.h +++ b/kernels/common/scene_curves.h @@ -497,8 +497,9 @@ namespace embree /*! check if the i'th primitive is valid at the itime'th time step */ __forceinline bool valid(Geometry::GType ctype, size_t i, const range& itime_range) const { - const unsigned int index = curve(i); - if (index+3 >= numVertices()) return false; + const size_t index = size_t(curve(i)); + const size_t vertices = numVertices(); + if (index > vertices || vertices - index < 4) { return false; } for (size_t itime = itime_range.begin(); itime <= itime_range.end(); itime++) { diff --git a/kernels/common/scene_instance.cpp b/kernels/common/scene_instance.cpp index 7ec470ef66..c6ed0abac3 100644 --- a/kernels/common/scene_instance.cpp +++ b/kernels/common/scene_instance.cpp @@ -80,7 +80,7 @@ namespace embree } #endif - void Instance::addElementsToCount (GeometryCounts & counts) const + void Instance::addElementsToCount (GeometryCounts & counts) const { if (Geometry::GTY_INSTANCE_CHEAP == this->gtype) { if (1 == numTimeSteps) { @@ -176,7 +176,7 @@ namespace embree instance->local2world = (AffineSpace3ff*)(data_device + offsetInstance + sizeof(Instance)); } - /* + /* This function calculates the correction for the linear bounds bbox0/bbox1 to properly bound the motion obtained when linearly @@ -250,7 +250,7 @@ namespace embree return delta; } - /* + /* This function calculates the correction for the linear bounds bbox0/bbox1 to properly bound the motion obtained by linearly blending the quaternion transformations and applying the @@ -259,9 +259,9 @@ namespace embree calclated, the the linear bounds get corrected at the extremal points. In difference to the previous function the extremal points cannot get calculated analytically, thus we fall back to - some root solver. + some root solver. */ - + BBox3fa boundSegmentNonlinear(MotionDerivativeCoefficients const& motionDerivCoeffs, AffineSpace3fa const& xfm0, AffineSpace3fa const& xfm1, @@ -321,6 +321,10 @@ namespace embree BBox3fa const& bbox0, BBox3fa const& bbox1, float tmin, float tmax) const { + if (unlikely(itime + 1 >= numTimeSteps)) { + return empty; + } + if (unlikely(gsubtype == GTY_SUBTYPE_INSTANCE_QUATERNION)) { auto const& xfm0 = local2world[itime]; auto const& xfm1 = local2world[itime+1]; @@ -338,6 +342,10 @@ namespace embree float geom_time_segments) const { LBBox3fa lbbox = empty; + if (!(geom_time_segments > 0.0f) || !(geom_time_range.size() > 0.0f)) { + return lbbox; + } + /* normalize global time_range_in to local geom_time_range */ const BBox1f time_range((time_range_in.lower-geom_time_range.lower)/geom_time_range.size(), (time_range_in.upper-geom_time_range.lower)/geom_time_range.size()); @@ -346,15 +354,25 @@ namespace embree const float upper = time_range.upper*geom_time_segments; const float ilowerf = floor(lower); const float iupperf = ceil(upper); - const float ilowerfc = max(0.0f,ilowerf); - const float iupperfc = min(iupperf,geom_time_segments); + if (!(ilowerf == ilowerf) || !(iupperf == iupperf)) { + return lbbox; + } + + const float ilowerfc = clamp(ilowerf, 0.0f, geom_time_segments); + const float iupperfc = clamp(iupperf, 0.0f, geom_time_segments); const int ilowerc = (int)ilowerfc; const int iupperc = (int)iupperfc; - assert(iupperc-ilowerc > 0); + if (iupperc <= ilowerc) { + return lbbox; + } /* this larger iteration range guarantees that we process borders of geom_time_range is (partially) inside time_range_in */ - const int ilower_iter = max(-1,(int)ilowerf); - const int iupper_iter = min((int)iupperf,(int)geom_time_segments+1); + const float iter_max = geom_time_segments + 1.0f; + const int ilower_iter = (int)clamp(ilowerf, -1.0f, iter_max); + const int iupper_iter = (int)clamp(iupperf, -1.0f, iter_max); + if (iupper_iter <= ilower_iter) { + return lbbox; + } if (iupper_iter-ilower_iter == 1) { diff --git a/kernels/common/scene_instance_array.cpp b/kernels/common/scene_instance_array.cpp index cc00b81c50..f49c686539 100644 --- a/kernels/common/scene_instance_array.cpp +++ b/kernels/common/scene_instance_array.cpp @@ -68,7 +68,7 @@ namespace embree Geometry::update(); } - void InstanceArray::addElementsToCount (GeometryCounts & counts) const + void InstanceArray::addElementsToCount (GeometryCounts & counts) const { if (1 == numTimeSteps) { counts.numInstanceArrays += numPrimitives; @@ -79,6 +79,10 @@ namespace embree AffineSpace3fa InstanceArray::getTransform(size_t i, float time) { + if (unlikely(i >= numPrimitives)) { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid instance primitive id"); + } + if (likely(numTimeSteps <= 1)) return getLocal2World(i); else @@ -182,14 +186,29 @@ namespace embree throw_RTCError(RTC_ERROR_INVALID_OPERATION, "if scene index buffer is set, it has to have the same size as the transform buffer."); } } - if (!object && objects && this->numPrimitives == 1) { - object = objects[0]; - if (object) object->refInc(); + if (!object && objects) + { + if (object_ids.size() != numPrimitives) { + throw_RTCError(RTC_ERROR_INVALID_OPERATION, "instance index buffer size must match transform buffer size."); + } + + for (size_t i = 0; i < numPrimitives; ++i) + { + const uint32_t id = object_ids[i]; + if (id != (unsigned int)(-1) && id >= numObjects) { + throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid instance array object id"); + } + } + + if (this->numPrimitives == 1) { + object = objects[0]; + if (object) { object->refInc(); } + } } Geometry::commit(); } - + size_t InstanceArray::getGeometryDataDeviceByteSize() const { size_t byte_size = sizeof(InstanceArray); byte_size += numObjects * sizeof(Accel*); @@ -297,7 +316,7 @@ namespace embree return delta; } - /* + /* This function calculates the correction for the linear bounds bbox0/bbox1 to properly bound the motion obtained by linearly blending the quaternion transformations and applying the @@ -306,9 +325,9 @@ namespace embree calclated, the the linear bounds get corrected at the extremal points. In difference to the previous function the extremal points cannot get calculated analytically, thus we fall back to - some root solver. + some root solver. */ - + BBox3fa boundSegmentNonlinear(MotionDerivativeCoefficients const& motionDerivCoeffs, AffineSpace3fa const& xfm0, AffineSpace3fa const& xfm1, @@ -370,6 +389,10 @@ namespace embree BBox3fa const& bbox0, BBox3fa const& bbox1, float tmin, float tmax) const { + if (unlikely(itime + 1 >= numTimeSteps)) { + return empty; + } + if (unlikely(gsubtype == GTY_SUBTYPE_INSTANCE_QUATERNION)) { auto const& xfm0 = l2w(i, itime); auto const& xfm1 = l2w(i, itime+1); @@ -388,6 +411,10 @@ namespace embree float geom_time_segments) const { LBBox3fa lbbox = empty; + if (!(geom_time_segments > 0.0f) || !(geom_time_range.size() > 0.0f)) { + return lbbox; + } + /* normalize global time_range_in to local geom_time_range */ const BBox1f time_range((time_range_in.lower-geom_time_range.lower)/geom_time_range.size(), (time_range_in.upper-geom_time_range.lower)/geom_time_range.size()); @@ -396,15 +423,25 @@ namespace embree const float upper = time_range.upper*geom_time_segments; const float ilowerf = floor(lower); const float iupperf = ceil(upper); - const float ilowerfc = max(0.0f,ilowerf); - const float iupperfc = min(iupperf,geom_time_segments); + if (!(ilowerf == ilowerf) || !(iupperf == iupperf)) { + return lbbox; + } + + const float ilowerfc = clamp(ilowerf, 0.0f, geom_time_segments); + const float iupperfc = clamp(iupperf, 0.0f, geom_time_segments); const int ilowerc = (int)ilowerfc; const int iupperc = (int)iupperfc; - assert(iupperc-ilowerc > 0); + if (iupperc <= ilowerc) { + return lbbox; + } /* this larger iteration range guarantees that we process borders of geom_time_range is (partially) inside time_range_in */ - const int ilower_iter = max(-1,(int)ilowerf); - const int iupper_iter = min((int)iupperf,(int)geom_time_segments+1); + const float iter_max = geom_time_segments + 1.0f; + const int ilower_iter = (int)clamp(ilowerf, -1.0f, iter_max); + const int iupper_iter = (int)clamp(iupperf, -1.0f, iter_max); + if (iupper_iter <= ilower_iter) { + return lbbox; + } if (iupper_iter-ilower_iter == 1) { diff --git a/kernels/common/scene_instance_array.h b/kernels/common/scene_instance_array.h index f3caa06e87..ebf9463270 100644 --- a/kernels/common/scene_instance_array.h +++ b/kernels/common/scene_instance_array.h @@ -126,7 +126,7 @@ namespace embree unsigned int getTopologyVersion() const { return numPrimitives; } - + /* returns true if topology changed */ bool topologyChanged(unsigned int otherVersion) const { return numPrimitives != otherVersion; @@ -191,12 +191,17 @@ namespace embree return object; } - assert(objects); - assert(i < numPrimitives); + if (unlikely(objects == nullptr || i >= numPrimitives)) { + return nullptr; + } + if (object_ids[i] == (unsigned int)(-1)) return nullptr; - assert(object_ids[i] < numObjects); + if (unlikely(object_ids[i] >= numObjects)) { + return nullptr; + } + return objects[object_ids[i]]; } diff --git a/kernels/common/scene_line_segments.h b/kernels/common/scene_line_segments.h index a672abd8d2..28491cea5d 100644 --- a/kernels/common/scene_line_segments.h +++ b/kernels/common/scene_line_segments.h @@ -70,7 +70,7 @@ namespace embree const vfloat p1 = mem>::loadu(valid,(float*)&src[(segment+1)*stride+ofs]); if (P ) mem>::storeu(valid,P+i,lerp(p0,p1,u)); if (dPdu ) mem>::storeu(valid,dPdu+i,p1-p0); - if (ddPdudu) mem>::storeu(valid,dPdu+i,vfloat(zero)); + if (ddPdudu) mem>::storeu(valid,ddPdudu+i,vfloat(zero)); } } diff --git a/kernels/common/scene_subdiv_mesh.cpp b/kernels/common/scene_subdiv_mesh.cpp index 4dc2080d36..3b3f4cb59b 100644 --- a/kernels/common/scene_subdiv_mesh.cpp +++ b/kernels/common/scene_subdiv_mesh.cpp @@ -30,7 +30,7 @@ namespace embree }; SubdivMesh::SubdivMesh (Device* device) - : Geometry(device,GTY_SUBDIV_MESH,0,1), + : Geometry(device,GTY_SUBDIV_MESH,0,1), displFunc(nullptr), tessellationRate(2.0f), numHalfEdges(0), @@ -42,7 +42,7 @@ namespace embree edgeCreaseMap(new EdgeCreaseMap), commitCounter(0) { - + vertices.resize(numTimeSteps); vertex_buffer_tags.resize(numTimeSteps); topology.resize(1); @@ -57,9 +57,9 @@ namespace embree else counts.numMBSubdivPatches += numPrimitives; } - void SubdivMesh::setMask (unsigned mask) + void SubdivMesh::setMask (unsigned mask) { - this->mask = mask; + this->mask = mask; Geometry::update(); } @@ -105,15 +105,15 @@ namespace embree { if (N == 0) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"at least one topology has to exist") - + size_t begin = topology.size(); topology.resize(N); for (size_t i = begin; i < topology.size(); i++) topology[i] = Topology(this); } - + void SubdivMesh::setBuffer(RTCBufferType type, unsigned int slot, RTCFormat format, const Ref& buffer, size_t offset, size_t stride, unsigned int num) - { + { /* verify that all accesses are 4 bytes aligned */ if (((size_t(buffer->getHostPtr()) + offset) & 0x3) || (stride & 0x3)) throw_RTCError(RTC_ERROR_INVALID_OPERATION, "data must be 4 bytes aligned"); @@ -139,7 +139,7 @@ namespace embree if (slot >= vertexAttribs.size()) throw_RTCError(RTC_ERROR_INVALID_OPERATION, "invalid vertex attribute buffer slot"); - + vertexAttribs[slot].set(buffer, offset, stride, num, format); vertexAttribs[slot].checkPadding16(); } @@ -365,7 +365,7 @@ namespace embree Geometry::update(); } - void SubdivMesh::setDisplacementFunction (RTCDisplacementFunctionN func) + void SubdivMesh::setDisplacementFunction (RTCDisplacementFunctionN func) { this->displFunc = func; } @@ -376,7 +376,7 @@ namespace embree levels.setModified(); } - __forceinline uint64_t pair64(unsigned int x, unsigned int y) + __forceinline uint64_t pair64(unsigned int x, unsigned int y) { if (xdevice,0) { } - + void SubdivMesh::Topology::setSubdivisionMode (RTCSubdivisionMode mode) { if (subdiv_mode == mode) return; subdiv_mode = mode; mesh->updateBuffer(RTC_BUFFER_TYPE_VERTEX_CREASE_WEIGHT, 0); } - + void SubdivMesh::Topology::update () { vertexIndices.setModified(); } - bool SubdivMesh::Topology::verify (size_t numVertices) + bool SubdivMesh::Topology::verify (size_t numVertices) { size_t ofs = 0; - for (size_t i=0; isize(); i++) + for (size_t i=0; isize(); i++) { int valence = mesh->faceVertices[i]; - for (size_t j=ofs; j= vertexIndices.size()) return false; - + if (vertexIndices[j] >= numVertices) - return false; + return false; } ofs += valence; } @@ -429,9 +429,9 @@ namespace embree halfEdges1.resize(numEdges); /* create all half edges */ - parallel_for( size_t(0), numFaces, blockSize, [&](const range& r) + parallel_for( size_t(0), numFaces, blockSize, [&](const range& r) { - for (size_t f=r.begin(); ffaceVertices[f]; const unsigned e = mesh->faceStartEdge[f]; @@ -441,16 +441,16 @@ namespace embree HalfEdge* edge = &halfEdges[e+de]; int nextOfs = (de == (N-1)) ? -int(N-1) : +1; int prevOfs = (de == 0) ? +int(N-1) : -1; - + const unsigned int startVertex = vertexIndices[e+de]; - const unsigned int endVertex = vertexIndices[e+de+nextOfs]; + const unsigned int endVertex = vertexIndices[e+de+nextOfs]; const uint64_t key = SubdivMesh::Edge(startVertex,endVertex); /* we always have to use the geometry topology to lookup creases */ const unsigned int startVertex0 = mesh->topology[0].vertexIndices[e+de]; - const unsigned int endVertex0 = mesh->topology[0].vertexIndices[e+de+nextOfs]; + const unsigned int endVertex0 = mesh->topology[0].vertexIndices[e+de+nextOfs]; const uint64_t key0 = SubdivMesh::Edge(startVertex0,endVertex0); - + edge->vtx_index = startVertex; edge->next_half_edge_ofs = nextOfs; edge->prev_half_edge_ofs = prevOfs; @@ -461,7 +461,7 @@ namespace embree edge->patch_type = HalfEdge::COMPLEX_PATCH; // type gets updated below edge->vertex_type = HalfEdge::REGULAR_VERTEX; - if (unlikely(mesh->holeSet->holeSet.lookup(unsigned(f)))) + if (unlikely(mesh->holeSet->holeSet.lookup(unsigned(f)))) halfEdges1[e+de] = SubdivMesh::KeyHalfEdge(std::numeric_limits::max(),edge); else halfEdges1[e+de] = SubdivMesh::KeyHalfEdge(key,edge); @@ -473,7 +473,7 @@ namespace embree radix_sort_u64(halfEdges1.data(),halfEdges0.data(),numHalfEdges); /* link all adjacent pairs of edges */ - parallel_for( size_t(0), numHalfEdges, blockSize, [&](const range& r) + parallel_for( size_t(0), numHalfEdges, blockSize, [&](const range& r) { /* skip if start of adjacent edges was not in our range */ size_t e=r.begin(); @@ -527,9 +527,9 @@ namespace embree }); /* set subdivision mode and calculate patch types */ - parallel_for( size_t(0), numFaces, blockSize, [&](const range& r) + parallel_for( size_t(0), numFaces, blockSize, [&](const range& r) { - for (size_t f=r.begin(); ffaceStartEdge[f]]; @@ -542,14 +542,14 @@ namespace embree } /* pin some edges and vertices */ - for (size_t i=0; ifaceVertices[f]; i++) + for (size_t i=0; ifaceVertices[f]; i++) { /* pin corner vertices when requested by user */ if (subdiv_mode == RTC_SUBDIVISION_MODE_PIN_CORNERS && edge[i].isCorner()) edge[i].vertex_crease_weight = float(inf); - + /* pin all border vertices when requested by user */ - else if (subdiv_mode == RTC_SUBDIVISION_MODE_PIN_BOUNDARY && edge[i].vertexHasBorder()) + else if (subdiv_mode == RTC_SUBDIVISION_MODE_PIN_BOUNDARY && edge[i].vertexHasBorder()) edge[i].vertex_crease_weight = float(inf); /* pin all edges and vertices when requested by user */ @@ -561,7 +561,7 @@ namespace embree /* we have to calculate patch_type last! */ HalfEdge::PatchType patch_type = edge->patchType(); - for (size_t i=0; ifaceVertices[f]; i++) + for (size_t i=0; ifaceVertices[f]; i++) edge[i].patch_type = patch_type; } }); @@ -578,35 +578,35 @@ namespace embree /* calculate which data to update */ const bool updateEdgeCreases = mesh->topology[0].vertexIndices.isLocalModified() || mesh->edge_creases.isLocalModified() || mesh->edge_crease_weights.isLocalModified(); - const bool updateVertexCreases = mesh->topology[0].vertexIndices.isLocalModified() || mesh->vertex_creases.isLocalModified() || mesh->vertex_crease_weights.isLocalModified(); + const bool updateVertexCreases = mesh->topology[0].vertexIndices.isLocalModified() || mesh->vertex_creases.isLocalModified() || mesh->vertex_crease_weights.isLocalModified(); const bool updateLevels = mesh->levels.isLocalModified(); /* parallel loop over all half edges */ - parallel_for( size_t(0), mesh->numHalfEdges, size_t(4096), [&](const range& r) + parallel_for( size_t(0), mesh->numHalfEdges, size_t(4096), [&](const range& r) { for (size_t i=r.begin(); i!=r.end(); i++) { HalfEdge& edge = halfEdges[i]; if (updateLevels) - edge.edge_level = mesh->getEdgeLevel(i); - + edge.edge_level = mesh->getEdgeLevel(i); + if (updateEdgeCreases) { if (edge.hasOpposite()) // leave weight at inf for borders edge.edge_crease_weight = mesh->edgeCreaseMap->edgeCreaseMap.lookup((uint64_t)halfEdgesGeom[i].getEdge(),0.0f); } - + /* we only use user specified vertex_crease_weight if the vertex is manifold */ - if (updateVertexCreases && edge.vertex_type != HalfEdge::NON_MANIFOLD_EDGE_VERTEX) + if (updateVertexCreases && edge.vertex_type != HalfEdge::NON_MANIFOLD_EDGE_VERTEX) { edge.vertex_crease_weight = mesh->vertexCreaseMap->vertexCreaseMap.lookup(halfEdgesGeom[i].vtx_index,0.0f); /* pin corner vertices when requested by user */ if (subdiv_mode == RTC_SUBDIVISION_MODE_PIN_CORNERS && edge.isCorner()) edge.vertex_crease_weight = float(inf); - + /* pin all border vertices when requested by user */ - else if (subdiv_mode == RTC_SUBDIVISION_MODE_PIN_BOUNDARY && edge.vertexHasBorder()) + else if (subdiv_mode == RTC_SUBDIVISION_MODE_PIN_BOUNDARY && edge.vertexHasBorder()) edge.vertex_crease_weight = float(inf); /* pin every vertex when requested by user */ @@ -635,7 +635,7 @@ namespace embree /* check if we have to recalculate the half edges */ bool recalculate = false; - recalculate |= vertexIndices.isLocalModified(); + recalculate |= vertexIndices.isLocalModified(); recalculate |= mesh->faceVertices.isLocalModified(); recalculate |= mesh->holes.isLocalModified(); @@ -645,22 +645,22 @@ namespace embree update |= mesh->edge_creases.isLocalModified(); update |= mesh->edge_crease_weights.isLocalModified(); update |= mesh->vertex_creases.isLocalModified(); - update |= mesh->vertex_crease_weights.isLocalModified(); + update |= mesh->vertex_crease_weights.isLocalModified(); update |= mesh->levels.isLocalModified(); /* now either recalculate or update the half edges */ if (recalculate) calculateHalfEdges(); else if (update) updateHalfEdges(); - + /* cleanup some state for static scenes */ - /* if (mesh->scene_ == nullptr || mesh->scene_->isStaticAccel()) + /* if (mesh->scene_ == nullptr || mesh->scene_->isStaticAccel()) { halfEdges0.clear(); halfEdges1.clear(); } */ /* clear modified state of all buffers */ - vertexIndices.clearLocalModified(); + vertexIndices.clearLocalModified(); } void SubdivMesh::printStatistics() @@ -669,8 +669,8 @@ namespace embree size_t numRegularQuadFaces = 0; size_t numIrregularQuadFaces = 0; size_t numComplexFaces = 0; - - for (size_t e=0, f=0; f()); @@ -707,11 +707,11 @@ namespace embree for (size_t e=0; evertexCreaseMap.init(vertex_creases,vertex_crease_weights); - + /* create map with all edge creases */ if (edge_creases.isLocalModified() || edge_crease_weights.isLocalModified()) edgeCreaseMap->edgeCreaseMap.init(edge_creases,edge_crease_weights); @@ -731,7 +731,7 @@ namespace embree if (vertexAttribs[i]) vertex_attrib_buffer_tags[i].resize(numFaces()*numInterpolationSlots4(vertexAttribs[i].getStride())); /* cleanup some state for static scenes */ - /* if (scene_ == nullptr || scene_->isStaticAccel()) + /* if (scene_ == nullptr || scene_->isStaticAccel()) { vertexCreaseMap->vertexCreaseMap.clear(); edgeCreaseMap->edgeCreaseMap.clear(); @@ -740,7 +740,7 @@ namespace embree /* clear modified state of all buffers */ faceVertices.clearLocalModified(); holes.clearLocalModified(); - for (auto& buffer : vertices) buffer.clearLocalModified(); + for (auto& buffer : vertices) buffer.clearLocalModified(); levels.clearLocalModified(); edge_creases.clearLocalModified(); edge_crease_weights.clearLocalModified(); @@ -756,7 +756,7 @@ namespace embree } } - bool SubdivMesh::verify () + bool SubdivMesh::verify () { /*! verify consistent size of vertex arrays */ if (vertices.size() == 0) return false; @@ -775,14 +775,19 @@ namespace embree /*! verify vertices */ for (const auto& buffer : vertices) for (size_t i=0; i= numHalfEdges) @@ -823,13 +828,13 @@ namespace embree { if (topologyID >= topology.size()) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid topology"); - + if (edgeID >= numHalfEdges) throw_RTCError(RTC_ERROR_INVALID_ARGUMENT, "invalid half edge"); return edgeID + topology[topologyID].halfEdges[edgeID].opposite_half_edge_ofs; } - + #endif namespace isa @@ -837,7 +842,7 @@ namespace embree SubdivMesh* createSubdivMesh(Device* device) { return new SubdivMeshISA(device); } - + void SubdivMeshISA::interpolate(const RTCInterpolateArguments* const args) { unsigned int primID = args->primID; @@ -852,11 +857,11 @@ namespace embree float* ddPdvdv = args->ddPdvdv; float* ddPdudv = args->ddPdudv; unsigned int valueCount = args->valueCount; - + /* calculate base pointer and stride */ assert((bufferType == RTC_BUFFER_TYPE_VERTEX && bufferSlot < RTC_MAX_TIME_STEP_COUNT) || (bufferType == RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE && bufferSlot < RTC_MAX_USER_VERTEX_BUFFERS)); - const char* src = nullptr; + const char* src = nullptr; size_t stride = 0; std::vector* baseEntry = nullptr; Topology* topo = nullptr; @@ -874,35 +879,35 @@ namespace embree baseEntry = &vertex_buffer_tags[bufferSlot]; topo = &topology[0]; } - + bool has_P = P; bool has_dP = dPdu; assert(!has_dP || dPdv); bool has_ddP = ddPdudu; assert(!has_ddP || (ddPdvdv && ddPdudu)); - + for (unsigned int i=0; i(baseEntry->at(interpolationSlot(primID,i/4,stride)),commitCounter, topo->getHalfEdge(primID),src+i*sizeof(float),stride,u,v, - has_P ? &Pt : nullptr, - has_dP ? &dPdut : nullptr, + has_P ? &Pt : nullptr, + has_dP ? &dPdut : nullptr, has_dP ? &dPdvt : nullptr, - has_ddP ? &ddPdudut : nullptr, - has_ddP ? &ddPdvdvt : nullptr, + has_ddP ? &ddPdudut : nullptr, + has_ddP ? &ddPdvdvt : nullptr, has_ddP ? &ddPdudvt : nullptr); - + if (has_P) { - for (size_t j=i; jvalid; @@ -929,11 +934,11 @@ namespace embree float* ddPdvdv = args->ddPdvdv; float* ddPdudv = args->ddPdudv; unsigned int valueCount = args->valueCount; - + /* calculate base pointer and stride */ assert((bufferType == RTC_BUFFER_TYPE_VERTEX && bufferSlot < RTC_MAX_TIME_STEP_COUNT) || (bufferType == RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE && bufferSlot < RTC_MAX_USER_VERTEX_BUFFERS)); - const char* src = nullptr; + const char* src = nullptr; size_t stride = 0; std::vector* baseEntry = nullptr; Topology* topo = nullptr; @@ -951,22 +956,22 @@ namespace embree baseEntry = &vertex_buffer_tags[bufferSlot]; topo = &topology[0]; } - + const int* valid = (const int*) valid_i; - - for (size_t i=0; i(baseEntry->at(interpolationSlot(primID,j/4,stride)),commitCounter, diff --git a/kernels/geometry/grid_soa.h b/kernels/geometry/grid_soa.h index 9126f95f7b..61d922f849 100644 --- a/kernels/geometry/grid_soa.h +++ b/kernels/geometry/grid_soa.h @@ -26,20 +26,20 @@ namespace embree /*! Subgrid creation */ template static GridSOA* create(const SubdivPatch1Base* patches, const unsigned time_steps, - unsigned x0, unsigned x1, unsigned y0, unsigned y1, + unsigned x0, unsigned x1, unsigned y0, unsigned y1, const Scene* scene, Allocator& alloc, BBox3fa* bounds_o = nullptr) { - const unsigned width = x1-x0+1; - const unsigned height = y1-y0+1; + const unsigned width = x1-x0+1; + const unsigned height = y1-y0+1; const GridRange range(0,width-1,0,height-1); size_t bvhBytes = 0; - if (time_steps == 1) + if (time_steps == 1) bvhBytes = getBVHBytes(range,sizeof(BVH4::AABBNode),0); else { bvhBytes = (time_steps-1)*getBVHBytes(range,sizeof(BVH4::AABBNodeMB),0); bvhBytes += getTemporalBVHBytes(make_range(0,int(time_steps-1)),sizeof(BVH4::AABBNodeMB4D)); } - const size_t gridBytes = 4*size_t(width)*size_t(height)*sizeof(float); + const size_t gridBytes = 4*size_t(width)*size_t(height)*sizeof(float); size_t rootBytes = time_steps*sizeof(BVH4::NodeRef); #if !defined(__64BIT__) rootBytes += 4; // We read 2 elements behind the grid. As we store at least 8 root bytes after the grid we are fine in 64 bit mode. But in 32 bit mode we have to do additional padding. @@ -52,7 +52,7 @@ namespace embree /*! Grid creation */ template static GridSOA* create(const SubdivPatch1Base* const patches, const unsigned time_steps, - const Scene* scene, const Allocator& alloc, BBox3fa* bounds_o = nullptr) + const Scene* scene, const Allocator& alloc, BBox3fa* bounds_o = nullptr) { return create(patches,time_steps,0,patches->grid_u_res-1,0,patches->grid_v_res-1,scene,alloc,bounds_o); } @@ -68,11 +68,19 @@ namespace embree /*! returns pointer to Grid array */ __forceinline float* gridData(size_t t = 0) { return (float*) &data[gridOffset + t*gridBytes]; } __forceinline const float* gridData(size_t t = 0) const { return (float*) &data[gridOffset + t*gridBytes]; } - + __forceinline void* encodeLeaf(size_t u, size_t v) { return (void*) (16*(v * width + u + 1)); // +1 to not create empty leaf } + + static __forceinline bool validEncodedLeaf(const void* ptr) { + return (size_t)ptr >= 16; + } + __forceinline float* decodeLeaf(size_t t, const void* ptr) { + if (unlikely(!validEncodedLeaf(ptr))) { + return nullptr; + } return gridData(t) + (((size_t) (ptr) >> 4) - 1); } @@ -89,10 +97,10 @@ namespace embree const float* const grid_x_array = grid_array + 0 * dim_offset; const float* const grid_y_array = grid_array + 1 * dim_offset; const float* const grid_z_array = grid_array + 2 * dim_offset; - + /* compute the bounds just for the range! */ BBox3fa bounds( empty ); - for (unsigned v = range.v_start; v<=range.v_end; v++) + for (unsigned v = range.v_start; v<=range.v_end; v++) { for (unsigned u = range.u_start; u<=range.u_end; u++) { @@ -108,13 +116,13 @@ namespace embree /*! Evaluates grid over patch and builds BVH4 tree over the grid. */ std::pair buildBVH(BBox3fa* bounds_o); - + /*! Create BVH4 tree over grid. */ std::pair buildBVH(const GridRange& range, size_t& allocator); /*! Evaluates grid over patch and builds MSMBlur BVH4 tree over the grid. */ std::pair buildMSMBlurBVH(const range time_range, BBox3fa* bounds_o); - + /*! Create MBlur BVH4 tree over grid. */ std::pair buildMBlurBVH(size_t time, const GridRange& range, size_t& allocator); @@ -133,12 +141,12 @@ namespace embree : grid_uv(grid_uv), line_offset(line_offset), lines(lines) {} __forceinline void operator() (vfloat& u, vfloat& v, Vec3& Ng) const { - const Vec3 tri_v012_uv = Loader::gather(grid_uv,line_offset,lines); + const Vec3 tri_v012_uv = Loader::gather(grid_uv,line_offset,lines); const Vec2 uv0 = GridSOA::decodeUV(tri_v012_uv[0]); const Vec2 uv1 = GridSOA::decodeUV(tri_v012_uv[1]); - const Vec2 uv2 = GridSOA::decodeUV(tri_v012_uv[2]); - const Vec2 uv = u * uv1 + v * uv2 + (1.0f-u-v) * uv0; - u = uv[0];v = uv[1]; + const Vec2 uv2 = GridSOA::decodeUV(tri_v012_uv[2]); + const Vec2 uv = u * uv1 + v * uv2 + (1.0f-u-v) * uv0; + u = uv[0];v = uv[1]; } }; @@ -148,7 +156,7 @@ namespace embree typedef vbool4 vbool; typedef vint4 vint; typedef vfloat4 vfloat; - + static __forceinline const Vec3vf4 gather(const float* const grid, const size_t line_offset, const size_t lines) { vfloat4 r0 = vfloat4::loadu(grid + 0*line_offset); @@ -163,9 +171,9 @@ namespace embree shuffle<0,1,1,2>(r1)); // r10, r11, r11, r12 } - static __forceinline void gather(const float* const grid_x, - const float* const grid_y, - const float* const grid_z, + static __forceinline void gather(const float* const grid_x, + const float* const grid_y, + const float* const grid_z, const size_t line_offset, const size_t lines, Vec3vf4& v0_o, @@ -180,7 +188,7 @@ namespace embree v2_o = Vec3vf4(tri_v012_x[2],tri_v012_y[2],tri_v012_z[2]); } }; - + #if defined (__AVX__) struct Gather3x3 { @@ -188,15 +196,15 @@ namespace embree typedef vbool8 vbool; typedef vint8 vint; typedef vfloat8 vfloat; - + static __forceinline const Vec3vf8 gather(const float* const grid, const size_t line_offset, const size_t lines) { vfloat4 ra = vfloat4::loadu(grid + 0*line_offset); vfloat4 rb = vfloat4::loadu(grid + 1*line_offset); // this accesses 2 elements too much in case of 2x2 grid, but this is ok as we ensure enough padding after the grid vfloat4 rc; - if (likely(lines > 2)) + if (likely(lines > 2)) rc = vfloat4::loadu(grid + 2*line_offset); - else + else rc = rb; if (unlikely(line_offset == 2)) @@ -205,7 +213,7 @@ namespace embree rb = shuffle<0,1,1,1>(rb); rc = shuffle<0,1,1,1>(rc); } - + const vfloat8 r0 = vfloat8(ra,rb); const vfloat8 r1 = vfloat8(rb,rc); return Vec3vf8(unpacklo(r0,r1), // r00, r10, r01, r11, r10, r20, r11, r21 @@ -213,9 +221,9 @@ namespace embree shuffle<0,1,1,2>(r1)); // r10, r11, r11, r12, r20, r21, r21, r22 } - static __forceinline void gather(const float* const grid_x, - const float* const grid_y, - const float* const grid_z, + static __forceinline void gather(const float* const grid_x, + const float* const grid_y, + const float* const grid_z, const size_t line_offset, const size_t lines, Vec3vf8& v0_o, @@ -242,14 +250,14 @@ namespace embree const vfloat v = toFloat(iv) * vfloat(8.0f/0x10000); return Vec2(u,v); } - + __forceinline unsigned int geomID() const { return _geomID; - } - + } + __forceinline unsigned int primID() const { return _primID; - } + } public: BVH4::NodeRef troot; diff --git a/kernels/geometry/grid_soa_intersector1.h b/kernels/geometry/grid_soa_intersector1.h index 6d56bd0404..2a1c5137ee 100644 --- a/kernels/geometry/grid_soa_intersector1.h +++ b/kernels/geometry/grid_soa_intersector1.h @@ -15,22 +15,22 @@ namespace embree { public: typedef void Primitive; - + class Precalculations - { + { public: __forceinline Precalculations (const Ray& ray, const void* ptr) : grid(nullptr) {} - + public: GridSOA* grid; int itime; float ftime; }; - + template static __forceinline void intersect(RayHit& ray, - RayQueryContext* context, + RayQueryContext* context, const float* const grid_x, const size_t line_offset, const size_t lines, @@ -42,15 +42,15 @@ namespace embree const float* const grid_z = grid_x + 2 * dim_offset; const float* const grid_uv = grid_x + 3 * dim_offset; Vec3 v0, v1, v2; - Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,v0,v1,v2); + Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,v0,v1,v2); GridSOA::MapUV mapUV(grid_uv,line_offset,lines); PlueckerIntersector1 intersector(ray,nullptr); intersector.intersect(ray,v0,v1,v2,mapUV,Intersect1EpilogMU(ray,context,pre.grid->geomID(),pre.grid->primID())); }; - + template static __forceinline bool occluded(Ray& ray, - RayQueryContext* context, + RayQueryContext* context, const float* const grid_x, const size_t line_offset, const size_t lines, @@ -64,19 +64,23 @@ namespace embree Vec3 v0, v1, v2; Loader::gather(grid_x,grid_y,grid_z,line_offset,lines,v0,v1,v2); - + GridSOA::MapUV mapUV(grid_uv,line_offset,lines); PlueckerIntersector1 intersector(ray,nullptr); return intersector.intersect(ray,v0,v1,v2,mapUV,Occluded1EpilogMU(ray,context,pre.grid->geomID(),pre.grid->primID())); } - + /*! Intersect a ray with the primitive. */ - static __forceinline void intersect(Precalculations& pre, RayHit& ray, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) + static __forceinline void intersect(Precalculations& pre, RayHit& ray, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return; + } + const size_t line_offset = pre.grid->width; const size_t lines = pre.grid->height; const float* const grid_x = pre.grid->decodeLeaf(0,prim); - + #if defined(__AVX__) intersect( ray, context, grid_x, line_offset, lines, pre); #else @@ -85,14 +89,18 @@ namespace embree intersect(ray, context, grid_x+line_offset, line_offset, lines, pre); #endif } - + /*! Test if the ray is occluded by the primitive */ static __forceinline bool occluded(Precalculations& pre, Ray& ray, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return false; + } + const size_t line_offset = pre.grid->width; const size_t lines = pre.grid->height; const float* const grid_x = pre.grid->decodeLeaf(0,prim); - + #if defined(__AVX__) return occluded( ray, context, grid_x, line_offset, lines, pre); #else @@ -101,7 +109,7 @@ namespace embree if (occluded(ray, context, grid_x+line_offset, line_offset, lines, pre)) return true; #endif return false; - } + } }; class GridSOAMBIntersector1 @@ -109,10 +117,10 @@ namespace embree public: typedef void Primitive; typedef GridSOAIntersector1::Precalculations Precalculations; - + template static __forceinline void intersect(RayHit& ray, const float ftime, - RayQueryContext* context, + RayQueryContext* context, const float* const grid_x, const size_t line_offset, const size_t lines, @@ -139,10 +147,10 @@ namespace embree PlueckerIntersector1 intersector(ray,nullptr); intersector.intersect(ray,v0,v1,v2,mapUV,Intersect1EpilogMU(ray,context,pre.grid->geomID(),pre.grid->primID())); }; - + template static __forceinline bool occluded(Ray& ray, const float ftime, - RayQueryContext* context, + RayQueryContext* context, const float* const grid_x, const size_t line_offset, const size_t lines, @@ -160,23 +168,27 @@ namespace embree Vec3 b0, b1, b2; Loader::gather(grid_x+grid_offset,grid_y+grid_offset,grid_z+grid_offset,line_offset,lines,b0,b1,b2); - + Vec3 v0 = lerp(a0,b0,vfloat(ftime)); Vec3 v1 = lerp(a1,b1,vfloat(ftime)); Vec3 v2 = lerp(a2,b2,vfloat(ftime)); - + GridSOA::MapUV mapUV(grid_uv,line_offset,lines); PlueckerIntersector1 intersector(ray,nullptr); return intersector.intersect(ray,v0,v1,v2,mapUV,Occluded1EpilogMU(ray,context,pre.grid->geomID(),pre.grid->primID())); } - + /*! Intersect a ray with the primitive. */ - static __forceinline void intersect(Precalculations& pre, RayHit& ray, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) - { + static __forceinline void intersect(Precalculations& pre, RayHit& ray, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) + { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return; + } + const size_t line_offset = pre.grid->width; const size_t lines = pre.grid->height; const float* const grid_x = pre.grid->decodeLeaf(pre.itime,prim); - + #if defined(__AVX__) intersect( ray, pre.ftime, context, grid_x, line_offset, lines, pre); #else @@ -185,14 +197,17 @@ namespace embree intersect(ray, pre.ftime, context, grid_x+line_offset, line_offset, lines, pre); #endif } - + /*! Test if the ray is occluded by the primitive */ static __forceinline bool occluded(Precalculations& pre, Ray& ray, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return false; + } const size_t line_offset = pre.grid->width; const size_t lines = pre.grid->height; const float* const grid_x = pre.grid->decodeLeaf(pre.itime,prim); - + #if defined(__AVX__) return occluded( ray, pre.ftime, context, grid_x, line_offset, lines, pre); #else @@ -201,7 +216,7 @@ namespace embree if (occluded(ray, pre.ftime, context, grid_x+line_offset, line_offset, lines, pre)) return true; #endif return false; - } + } }; } } diff --git a/kernels/geometry/grid_soa_intersector_packet.h b/kernels/geometry/grid_soa_intersector_packet.h index 5e5a24b7dd..eb4df26c4e 100644 --- a/kernels/geometry/grid_soa_intersector_packet.h +++ b/kernels/geometry/grid_soa_intersector_packet.h @@ -81,6 +81,10 @@ namespace embree /*! Intersect a ray with the primitive. */ static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return; + } + const size_t dim_offset = pre.grid->dim_offset; const size_t line_offset = pre.grid->width; const float* const grid_x = pre.grid->decodeLeaf(0,prim); @@ -112,6 +116,10 @@ namespace embree /*! Test if the ray is occluded by the primitive */ static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return vbool(false); + } + const size_t dim_offset = pre.grid->dim_offset; const size_t line_offset = pre.grid->width; const float* const grid_x = pre.grid->decodeLeaf(0,prim); @@ -181,6 +189,10 @@ namespace embree /*! Intersect a ray with the primitive. */ static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return; + } + const size_t line_offset = pre.grid->width; const size_t lines = pre.grid->height; const float* const grid_x = pre.grid->decodeLeaf(0,prim); @@ -196,6 +208,10 @@ namespace embree /*! Test if the ray is occluded by the primitive */ static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return false; + } + const size_t line_offset = pre.grid->width; const size_t lines = pre.grid->height; const float* const grid_x = pre.grid->decodeLeaf(0,prim); @@ -237,6 +253,10 @@ namespace embree /*! Intersect a ray with the primitive. */ static __forceinline void intersect(const vbool& valid_i, Precalculations& pre, RayHitK& ray, const vfloat& ftime, int itime, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return; + } + const size_t grid_offset = pre.grid->gridBytes >> 2; const size_t dim_offset = pre.grid->dim_offset; const size_t line_offset = pre.grid->width; @@ -299,6 +319,10 @@ namespace embree /*! Test if the ray is occluded by the primitive */ static __forceinline vbool occluded(const vbool& valid_i, Precalculations& pre, RayK& ray, const vfloat& ftime, int itime, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return vbool(false); + } + const size_t grid_offset = pre.grid->gridBytes >> 2; const size_t dim_offset = pre.grid->dim_offset; const size_t line_offset = pre.grid->width; @@ -405,6 +429,10 @@ namespace embree /*! Intersect a ray with the primitive. */ static __forceinline void intersect(Precalculations& pre, RayHitK& ray, size_t k, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) { + return; + } + float ftime; int itime = getTimeSegment(ray.time()[k], float(pre.grid->time_steps-1), ftime); @@ -424,6 +452,9 @@ namespace embree /*! Test if the ray is occluded by the primitive */ static __forceinline bool occluded(Precalculations& pre, RayK& ray, size_t k, RayQueryContext* context, const Primitive* prim, size_t& lazy_node) { + if (unlikely(!GridSOA::validEncodedLeaf(prim))) + return false; + float ftime; int itime = getTimeSegment(ray.time()[k], float(pre.grid->time_steps-1), ftime); 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..4dabefc24c --- /dev/null +++ b/tutorials/embree_regression_tests/embree_regression_tests.cpp @@ -0,0 +1,989 @@ +// Copyright 2009-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(RTC_NAMESPACE_USE) +RTC_NAMESPACE_USE +#endif + +namespace +{ + struct CaseResult + { + bool pass; + bool skip; + std::string message; + }; + + static RTCError consumeDeviceError(RTCDevice device) + { + return rtcGetDeviceError(device); + } + + static bool isFiniteBounds(const RTCBounds& b) + { + const float v[6] = { b.lower_x, b.lower_y, b.lower_z, b.upper_x, b.upper_y, b.upper_z }; + for (size_t i = 0; i < 6; ++i) { + if (!std::isfinite(v[i])) + return false; + } + return true; + } + + /* empty bounds (lower > upper) are a valid defensive response; only NaN is unsafe */ + static bool hasNaNBounds(const RTCBounds& b) + { + const float v[6] = { b.lower_x, b.lower_y, b.lower_z, b.upper_x, b.upper_y, b.upper_z }; + for (size_t i = 0; i < 6; ++i) { + if (std::isnan(v[i])) + return true; + } + return false; + } + + static bool errorIsAccepted(RTCError err) + { + return err == RTC_ERROR_NONE || err == RTC_ERROR_INVALID_ARGUMENT || err == RTC_ERROR_INVALID_OPERATION; + } + + static float* setTransformBuffer1(RTCGeometry geom, float* xfm) + { + rtcSetSharedGeometryBuffer( + geom, + RTC_BUFFER_TYPE_TRANSFORM, + 0, + RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, + xfm, + 0, + 16 * sizeof(float), + 1); + return xfm; + } + + static void setIdentityXfm(float* m) + { + for (int i = 0; i < 16; ++i) + m[i] = 0.0f; + m[0] = 1.0f; + m[5] = 1.0f; + m[10] = 1.0f; + m[15] = 1.0f; + } + + static RTCScene createTriangleScene(RTCDevice device) + { + RTCScene scene = rtcNewScene(device); + RTCGeometry geom = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE); + + float* vertices = (float*)rtcSetNewGeometryBuffer( + geom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 3 * sizeof(float), 3); + unsigned* indices = (unsigned*)rtcSetNewGeometryBuffer( + geom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, 3 * sizeof(unsigned), 1); + + vertices[0] = 0.0f; vertices[1] = 0.0f; vertices[2] = 0.0f; + vertices[3] = 1.0f; vertices[4] = 0.0f; vertices[5] = 0.0f; + vertices[6] = 0.0f; vertices[7] = 1.0f; vertices[8] = 0.0f; + + indices[0] = 0; + indices[1] = 1; + indices[2] = 2; + + rtcCommitGeometry(geom); + rtcAttachGeometry(scene, geom); + rtcReleaseGeometry(geom); + rtcCommitScene(scene); + return scene; + } + + static CaseResult passResult(const char* msg) + { + CaseResult r; + r.pass = true; + r.skip = false; + r.message = msg; + return r; + } + + static CaseResult failResult(const char* msg) + { + CaseResult r; + r.pass = false; + r.skip = false; + r.message = msg; + return r; + } + + static CaseResult skipResult(const char* msg) + { + CaseResult r; + r.pass = true; + r.skip = true; + r.message = msg; + return r; + } + + static CaseResult issue01_time_segment_range_clamp(RTCDevice device) + { + RTCScene child = createTriangleScene(device); + if (consumeDeviceError(device) != RTC_ERROR_NONE) { + rtcReleaseScene(child); + return failResult("failed to build child scene"); + } + + RTCScene top = rtcNewScene(device); + RTCGeometry inst = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_INSTANCE); + if (!inst) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return skipResult("instance geometry unsupported"); + } + + rtcSetGeometryInstancedScene(inst, child); + rtcSetGeometryTimeStepCount(inst, 2); + rtcSetGeometryTimeRange(inst, 1.0f, 0.0f); + float xfm0[16], xfm1[16]; + setIdentityXfm(xfm0); + setIdentityXfm(xfm1); + rtcSetGeometryTransform(inst, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm0); + rtcSetGeometryTransform(inst, 1, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm1); + + rtcCommitGeometry(inst); + rtcAttachGeometry(top, inst); + rtcReleaseGeometry(inst); + rtcCommitScene(top); + + RTCError err = consumeDeviceError(device); + if (!errorIsAccepted(err)) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("unexpected API error"); + } + + if (err == RTC_ERROR_NONE) { + RTCBounds b; + rtcGetSceneBounds(top, &b); + if (!isFiniteBounds(b)) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("non-finite bounds"); + } + } + + rtcReleaseScene(top); + rtcReleaseScene(child); + return passResult("safe handling for inverted time range"); + } + + static CaseResult issue02_lbbox_nan_range(RTCDevice device) + { + RTCScene child = createTriangleScene(device); + if (consumeDeviceError(device) != RTC_ERROR_NONE) { + rtcReleaseScene(child); + return failResult("failed to build child scene"); + } + + RTCScene top = rtcNewScene(device); + RTCGeometry inst = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_INSTANCE); + if (!inst) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return skipResult("instance geometry unsupported"); + } + + rtcSetGeometryInstancedScene(inst, child); + rtcSetGeometryTimeStepCount(inst, 2); + rtcSetGeometryTimeRange(inst, std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); + float xfm0[16], xfm1[16]; + setIdentityXfm(xfm0); + setIdentityXfm(xfm1); + rtcSetGeometryTransform(inst, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm0); + rtcSetGeometryTransform(inst, 1, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm1); + + rtcCommitGeometry(inst); + rtcAttachGeometry(top, inst); + rtcReleaseGeometry(inst); + rtcCommitScene(top); + + RTCError err = consumeDeviceError(device); + if (!errorIsAccepted(err)) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("unexpected API error"); + } + + if (err == RTC_ERROR_NONE) { + RTCBounds b; + rtcGetSceneBounds(top, &b); + if (hasNaNBounds(b)) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("NaN bounds"); + } + } + + rtcReleaseScene(top); + rtcReleaseScene(child); + return passResult("safe handling for NaN range"); + } + + static CaseResult issue03_lbbox_extreme_range(RTCDevice device) + { + RTCScene child = createTriangleScene(device); + RTCScene top = rtcNewScene(device); + RTCGeometry inst = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_INSTANCE); + if (!inst) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return skipResult("instance geometry unsupported"); + } + + rtcSetGeometryInstancedScene(inst, child); + rtcSetGeometryTimeStepCount(inst, 2); + rtcSetGeometryTimeRange(inst, -1.0e30f, 1.0e30f); + float xfm0[16], xfm1[16]; + setIdentityXfm(xfm0); + setIdentityXfm(xfm1); + rtcSetGeometryTransform(inst, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm0); + rtcSetGeometryTransform(inst, 1, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm1); + + rtcCommitGeometry(inst); + rtcAttachGeometry(top, inst); + rtcReleaseGeometry(inst); + rtcCommitScene(top); + + RTCError err = consumeDeviceError(device); + if (!errorIsAccepted(err)) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("unexpected API error"); + } + + if (err == RTC_ERROR_NONE) { + RTCBounds b; + rtcGetSceneBounds(top, &b); + if (!isFiniteBounds(b)) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("non-finite bounds under extreme range"); + } + } + + rtcReleaseScene(top); + rtcReleaseScene(child); + return passResult("extreme range handled safely"); + } + + static CaseResult issue04_instance_bound_segment_guard(RTCDevice device) + { + RTCScene child = createTriangleScene(device); + RTCScene top = rtcNewScene(device); + RTCGeometry inst = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_INSTANCE); + if (!inst) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return skipResult("instance geometry unsupported"); + } + + rtcSetGeometryInstancedScene(inst, child); + rtcSetGeometryTimeStepCount(inst, 2); + float xfm0[16], xfm1[16]; + setIdentityXfm(xfm0); + setIdentityXfm(xfm1); + xfm1[12] = 0.1f; + rtcSetGeometryTransform(inst, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm0); + rtcSetGeometryTransform(inst, 1, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm1); + rtcCommitGeometry(inst); + rtcAttachGeometry(top, inst); + rtcReleaseGeometry(inst); + rtcCommitScene(top); + + RTCRayHit rayhit; + std::memset(&rayhit, 0, sizeof(rayhit)); + rayhit.ray.org_x = 0.2f; + rayhit.ray.org_y = 0.2f; + rayhit.ray.org_z = -1.0f; + rayhit.ray.dir_x = 0.0f; + rayhit.ray.dir_y = 0.0f; + rayhit.ray.dir_z = 1.0f; + rayhit.ray.tnear = 0.0f; + rayhit.ray.tfar = std::numeric_limits::infinity(); + rayhit.ray.time = 2.0f; + rayhit.ray.mask = 0xFFFFFFFFu; + rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID; + rayhit.hit.instID[0] = RTC_INVALID_GEOMETRY_ID; + + RTCIntersectArguments args; + rtcInitIntersectArguments(&args); + rtcIntersect1(top, &rayhit, &args); + + RTCError err = consumeDeviceError(device); + rtcReleaseScene(top); + rtcReleaseScene(child); + if (err != RTC_ERROR_NONE) + return failResult("intersection returned API error"); + return passResult("out-of-range ray time handled safely"); + } + + static CaseResult issue05_instance_nonlinear_bounds(RTCDevice device) + { + RTCScene child = createTriangleScene(device); + RTCScene top = rtcNewScene(device); + RTCGeometry inst = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_INSTANCE); + if (!inst) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return skipResult("instance geometry unsupported"); + } + + rtcSetGeometryInstancedScene(inst, child); + rtcSetGeometryTimeStepCount(inst, 2); + rtcSetGeometryTimeRange(inst, 0.9f, 0.1f); + + RTCQuaternionDecomposition q0; + RTCQuaternionDecomposition q1; + rtcInitQuaternionDecomposition(&q0); + rtcInitQuaternionDecomposition(&q1); + rtcQuaternionDecompositionSetTranslation(&q0, 0.0f, 0.0f, 0.0f); + rtcQuaternionDecompositionSetTranslation(&q1, 1.0f, 0.0f, 0.0f); + rtcQuaternionDecompositionSetQuaternion(&q1, 0.9238795f, 0.0f, 0.3826834f, 0.0f); + rtcSetGeometryTransformQuaternion(inst, 0, &q0); + rtcSetGeometryTransformQuaternion(inst, 1, &q1); + + rtcCommitGeometry(inst); + rtcAttachGeometry(top, inst); + rtcReleaseGeometry(inst); + rtcCommitScene(top); + + RTCError err = consumeDeviceError(device); + if (!errorIsAccepted(err)) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("unexpected API error"); + } + + if (err == RTC_ERROR_NONE) { + RTCBounds b; + rtcGetSceneBounds(top, &b); + if (!isFiniteBounds(b)) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("non-finite bounds"); + } + } + + rtcReleaseScene(top); + rtcReleaseScene(child); + return passResult("nonlinear bounds path handled safely"); + } + + static CaseResult issue06_mb_builder_range_check(RTCDevice device) + { + RTCScene scene = rtcNewScene(device); + RTCGeometry geom = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE); + if (!geom) { + rtcReleaseScene(scene); + return skipResult("triangle geometry unsupported"); + } + + const unsigned int numTri = 2048; + const unsigned int numVertices = numTri * 3; + rtcSetGeometryTimeStepCount(geom, 2); + + float* v0 = (float*)rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 3 * sizeof(float), numVertices); + float* v1 = (float*)rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX, 1, RTC_FORMAT_FLOAT3, 3 * sizeof(float), numVertices); + unsigned* idx = (unsigned*)rtcSetNewGeometryBuffer(geom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, 3 * sizeof(unsigned), numTri); + + for (unsigned int i = 0; i < numTri; ++i) { + const float x = float(i % 64) * 0.05f; + const float y = float(i / 64) * 0.05f; + const unsigned int base = 3 * i; + v0[3 * base + 0] = x; v0[3 * base + 1] = y; v0[3 * base + 2] = 0.0f; + v0[3 * base + 3] = x + 0.01f;v0[3 * base + 4] = y; v0[3 * base + 5] = 0.0f; + v0[3 * base + 6] = x; v0[3 * base + 7] = y + 0.01f;v0[3 * base + 8] = 0.0f; + + v1[3 * base + 0] = x; v1[3 * base + 1] = y; v1[3 * base + 2] = 0.02f; + v1[3 * base + 3] = x + 0.01f; v1[3 * base + 4] = y; v1[3 * base + 5] = 0.02f; + v1[3 * base + 6] = x; v1[3 * base + 7] = y + 0.01f; v1[3 * base + 8] = 0.02f; + + idx[3 * i + 0] = base + 0; + idx[3 * i + 1] = base + 1; + idx[3 * i + 2] = base + 2; + } + + rtcCommitGeometry(geom); + rtcAttachGeometry(scene, geom); + rtcReleaseGeometry(geom); + rtcCommitScene(scene); + + RTCError err = consumeDeviceError(device); + rtcReleaseScene(scene); + if (err != RTC_ERROR_NONE) + return failResult("motion-blur scene build failed"); + return passResult("motion-blur builder completed safely"); + } + + static void simpleBoundsFunc(const RTCBoundsFunctionArguments* args) + { + args->bounds_o->lower_x = -1.0f; + args->bounds_o->lower_y = -1.0f; + args->bounds_o->lower_z = -1.0f; + args->bounds_o->upper_x = +1.0f; + args->bounds_o->upper_y = +1.0f; + args->bounds_o->upper_z = +1.0f; + } + + static CaseResult issue07_user_bounds_time_range_validation(RTCDevice device) + { + RTCScene scene = rtcNewScene(device); + RTCGeometry user = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_USER); + if (!user) { + rtcReleaseScene(scene); + return skipResult("user geometry unsupported"); + } + + rtcSetGeometryUserPrimitiveCount(user, 1); + rtcSetGeometryBoundsFunction(user, simpleBoundsFunc, nullptr); + rtcSetGeometryTimeStepCount(user, 2); + rtcSetGeometryTimeRange(user, 1.0f, 0.0f); + rtcCommitGeometry(user); + rtcAttachGeometry(scene, user); + rtcReleaseGeometry(user); + rtcCommitScene(scene); + + RTCError err = consumeDeviceError(device); + rtcReleaseScene(scene); + if (!errorIsAccepted(err)) + return failResult("unexpected API error"); + return passResult("invalid user-geometry range handled safely"); + } + + static CaseResult issue08_instance_array_object_id_validation(RTCDevice device) + { + RTCScene child = createTriangleScene(device); + RTCScene top = rtcNewScene(device); + RTCGeometry iarr = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_INSTANCE_ARRAY); + if (!iarr) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return skipResult("instance array unsupported"); + } + + float xfm[16]; + setIdentityXfm(xfm); + unsigned int objectIDs[1] = { 1u }; + RTCScene scenes[1] = { child }; + + setTransformBuffer1(iarr, xfm); + rtcSetSharedGeometryBuffer(iarr, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT, objectIDs, 0, sizeof(unsigned int), 1); + rtcSetGeometryInstancedScenes(iarr, scenes, 1); + rtcCommitGeometry(iarr); + + RTCError err = consumeDeviceError(device); + rtcReleaseGeometry(iarr); + rtcReleaseScene(top); + rtcReleaseScene(child); + if (err != RTC_ERROR_INVALID_ARGUMENT) + return failResult("expected invalid object id failure"); + return passResult("invalid object id rejected"); + } + + static CaseResult issue09_instance_array_transform_oob(RTCDevice device) + { + RTCScene child = createTriangleScene(device); + RTCScene top = rtcNewScene(device); + RTCGeometry iarr = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_INSTANCE_ARRAY); + if (!iarr) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return skipResult("instance array unsupported"); + } + + float xfm[16]; + setIdentityXfm(xfm); + unsigned int objectIDs[1] = { 0u }; + RTCScene scenes[1] = { child }; + + setTransformBuffer1(iarr, xfm); + rtcSetSharedGeometryBuffer(iarr, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT, objectIDs, 0, sizeof(unsigned int), 1); + rtcSetGeometryInstancedScenes(iarr, scenes, 1); + rtcCommitGeometry(iarr); + if (consumeDeviceError(device) != RTC_ERROR_NONE) { + rtcReleaseGeometry(iarr); + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("failed to commit valid instance array"); + } + + float outXfm[12] = {}; + rtcGetGeometryTransformEx(iarr, 3, 0.5f, RTC_FORMAT_FLOAT3X4_COLUMN_MAJOR, outXfm); + RTCError err = consumeDeviceError(device); + + rtcReleaseGeometry(iarr); + rtcReleaseScene(top); + rtcReleaseScene(child); + if (err != RTC_ERROR_INVALID_ARGUMENT) + return failResult("expected out-of-range instPrimID failure"); + return passResult("out-of-range instPrimID rejected"); + } + + static CaseResult issue10_line_segments_second_derivative_output(RTCDevice device) + { + RTCGeometry curve = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_FLAT_LINEAR_CURVE); + if (!curve) + return skipResult("flat linear curve unsupported"); + + float* vertices = (float*)rtcSetNewGeometryBuffer(curve, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT4, 4 * sizeof(float), 2); + unsigned int* indices = (unsigned int*)rtcSetNewGeometryBuffer(curve, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT, sizeof(unsigned int), 1); + + vertices[0] = 0.0f; vertices[1] = 0.0f; vertices[2] = 0.0f; vertices[3] = 0.1f; + vertices[4] = 2.0f; vertices[5] = 0.0f; vertices[6] = 0.0f; vertices[7] = 0.1f; + indices[0] = 0; + + rtcCommitGeometry(curve); + RTCError err = consumeDeviceError(device); + if (err != RTC_ERROR_NONE) { + rtcReleaseGeometry(curve); + return failResult("failed to commit test curve"); + } + + float P[4] = {}; + float dPdu[4] = { -777.0f, -777.0f, -777.0f, -777.0f }; + float dPdv[4] = { -777.0f, -777.0f, -777.0f, -777.0f }; + float ddPdudu[4] = { 999.0f, 999.0f, 999.0f, 999.0f }; + float ddPdvdv[4] = { 999.0f, 999.0f, 999.0f, 999.0f }; + float ddPdudv[4] = { 999.0f, 999.0f, 999.0f, 999.0f }; + + rtcInterpolate2(curve, 0, 0.5f, 0.0f, RTC_BUFFER_TYPE_VERTEX, 0, + P, dPdu, dPdv, ddPdudu, ddPdvdv, ddPdudv, 3); + err = consumeDeviceError(device); + rtcReleaseGeometry(curve); + + if (err != RTC_ERROR_NONE) + return failResult("interpolation call failed"); + + if (!(std::fabs(dPdu[0] - 2.0f) < 1.0e-4f && std::fabs(dPdu[1]) < 1.0e-4f && std::fabs(dPdu[2]) < 1.0e-4f)) + return failResult("dPdu was overwritten unexpectedly"); + + if (!(std::fabs(ddPdudu[0]) < 1.0e-4f && std::fabs(ddPdudu[1]) < 1.0e-4f && std::fabs(ddPdudu[2]) < 1.0e-4f)) + return failResult("ddPdudu not populated as expected"); + + return passResult("second derivative output uses correct pointer"); + } + + static CaseResult issue11_subdiv_verify_before_halfedge(RTCDevice device) + { + RTCGeometry subdiv = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_SUBDIVISION); + if (!subdiv) + return skipResult("subdivision geometry unsupported"); + + float vertices[12] = { + 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f + }; + unsigned int indices[3] = { 0, 1, 2 }; + unsigned int faces[1] = { 4 }; + + rtcSetSharedGeometryBuffer(subdiv, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, vertices, 0, 3 * sizeof(float), 4); + rtcSetSharedGeometryBuffer(subdiv, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT, indices, 0, sizeof(unsigned int), 3); + rtcSetSharedGeometryBuffer(subdiv, RTC_BUFFER_TYPE_FACE, 0, RTC_FORMAT_UINT, faces, 0, sizeof(unsigned int), 1); + + rtcCommitGeometry(subdiv); + RTCError err = consumeDeviceError(device); + rtcReleaseGeometry(subdiv); + + if (err == RTC_ERROR_NONE) + return failResult("invalid subdiv topology unexpectedly committed"); + return passResult("invalid subdiv topology rejected without crash"); + } + + static CaseResult issue12_curve_index_overflow_validation(RTCDevice device) + { + RTCGeometry curve = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_ROUND_BSPLINE_CURVE); + if (!curve) + return skipResult("round bspline curve unsupported"); + + float vertices[32] = {}; + for (int i = 0; i < 8; ++i) { + vertices[4 * i + 0] = float(i); + vertices[4 * i + 1] = 0.0f; + vertices[4 * i + 2] = 0.0f; + vertices[4 * i + 3] = 0.1f; + } + unsigned int indices[1] = { 0xFFFFFFFEu }; + + rtcSetSharedGeometryBuffer(curve, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT4, vertices, 0, 4 * sizeof(float), 8); + rtcSetSharedGeometryBuffer(curve, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT, indices, 0, sizeof(unsigned int), 1); + rtcCommitGeometry(curve); + + RTCScene scene = rtcNewScene(device); + rtcAttachGeometry(scene, curve); + rtcReleaseGeometry(curve); + rtcCommitScene(scene); + + RTCError err = consumeDeviceError(device); + rtcReleaseScene(scene); + + if (!errorIsAccepted(err)) { + return failResult("unexpected API error"); + } + return passResult("overflowing curve index handled safely without OOB access"); + } + + static CaseResult issue13_motion_derivative_root_bound(RTCDevice device) + { + RTCScene child = createTriangleScene(device); + RTCScene top = rtcNewScene(device); + RTCGeometry inst = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_INSTANCE); + if (!inst) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return skipResult("instance geometry unsupported"); + } + + rtcSetGeometryInstancedScene(inst, child); + rtcSetGeometryTimeStepCount(inst, 2); + + RTCQuaternionDecomposition q0; + RTCQuaternionDecomposition q1; + rtcInitQuaternionDecomposition(&q0); + rtcInitQuaternionDecomposition(&q1); + rtcQuaternionDecompositionSetQuaternion(&q0, 0.7071067f, 0.0f, 0.7071067f, 0.0f); + rtcQuaternionDecompositionSetQuaternion(&q1, 0.7071067f, 0.7071067f, 0.0f, 0.0f); + rtcQuaternionDecompositionSetScale(&q0, 10.0f, 0.1f, 5.0f); + rtcQuaternionDecompositionSetScale(&q1, 0.1f, 10.0f, 5.0f); + rtcQuaternionDecompositionSetTranslation(&q0, -1000.0f, 1000.0f, 0.0f); + rtcQuaternionDecompositionSetTranslation(&q1, 1000.0f, -1000.0f, 0.0f); + + rtcSetGeometryTransformQuaternion(inst, 0, &q0); + rtcSetGeometryTransformQuaternion(inst, 1, &q1); + + rtcCommitGeometry(inst); + rtcAttachGeometry(top, inst); + rtcReleaseGeometry(inst); + rtcCommitScene(top); + + RTCError err = consumeDeviceError(device); + rtcReleaseScene(top); + rtcReleaseScene(child); + if (err != RTC_ERROR_NONE) + return failResult("nonlinear bounds scene commit failed"); + return passResult("nonlinear derivative path completed safely"); + } + + static CaseResult issue14_grid_leaf_decode_guards(RTCDevice device) + { + RTCGeometry gridGeom = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_GRID); + if (!gridGeom) + return skipResult("grid geometry unsupported"); + + RTCGrid grid; + grid.startVertexID = 0; + grid.stride = 4; + grid.width = 4; + grid.height = 4; + + float vertices[4 * 4 * 3]; + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + const int i = 3 * (y * 4 + x); + vertices[i + 0] = float(x) * 0.25f; + vertices[i + 1] = float(y) * 0.25f; + vertices[i + 2] = 0.0f; + } + } + + rtcSetSharedGeometryBuffer(gridGeom, RTC_BUFFER_TYPE_GRID, 0, RTC_FORMAT_GRID, &grid, 0, sizeof(RTCGrid), 1); + rtcSetSharedGeometryBuffer(gridGeom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, vertices, 0, 3 * sizeof(float), 16); + + rtcCommitGeometry(gridGeom); + if (consumeDeviceError(device) != RTC_ERROR_NONE) { + rtcReleaseGeometry(gridGeom); + return failResult("grid geometry commit failed"); + } + + RTCScene scene = rtcNewScene(device); + rtcAttachGeometry(scene, gridGeom); + rtcReleaseGeometry(gridGeom); + rtcCommitScene(scene); + + RTCRayHit rayhit; + std::memset(&rayhit, 0, sizeof(rayhit)); + rayhit.ray.org_x = 0.5f; + rayhit.ray.org_y = 0.5f; + rayhit.ray.org_z = -1.0f; + rayhit.ray.dir_x = 0.0f; + rayhit.ray.dir_y = 0.0f; + rayhit.ray.dir_z = 1.0f; + rayhit.ray.tnear = 0.0f; + rayhit.ray.tfar = std::numeric_limits::infinity(); + rayhit.ray.mask = 0xFFFFFFFFu; + rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID; + + rtcIntersect1(scene, &rayhit, nullptr); + RTCError err = consumeDeviceError(device); + rtcReleaseScene(scene); + + if (err != RTC_ERROR_NONE) + return failResult("grid traversal failed"); + return passResult("grid traversal completed safely"); + } + + static CaseResult issue15_instance_array_time_clamp(RTCDevice device) + { + RTCScene child = createTriangleScene(device); + RTCScene top = rtcNewScene(device); + RTCGeometry iarr = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_INSTANCE_ARRAY); + if (!iarr) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return skipResult("instance array unsupported"); + } + + rtcSetGeometryTimeStepCount(iarr, 2); + rtcSetGeometryTimeRange(iarr, 0.75f, 0.25f); + + float xfm0[16]; + float xfm1[16]; + setIdentityXfm(xfm0); + setIdentityXfm(xfm1); + xfm1[12] = 0.5f; + + unsigned int objectIDs[1] = { 0u }; + RTCScene scenes[1] = { child }; + + rtcSetSharedGeometryBuffer(iarr, RTC_BUFFER_TYPE_TRANSFORM, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm0, 0, 16 * sizeof(float), 1); + rtcSetSharedGeometryBuffer(iarr, RTC_BUFFER_TYPE_TRANSFORM, 1, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, xfm1, 0, 16 * sizeof(float), 1); + rtcSetSharedGeometryBuffer(iarr, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT, objectIDs, 0, sizeof(unsigned int), 1); + rtcSetGeometryInstancedScenes(iarr, scenes, 1); + + rtcCommitGeometry(iarr); + rtcAttachGeometry(top, iarr); + rtcReleaseGeometry(iarr); + rtcCommitScene(top); + + RTCError err = consumeDeviceError(device); + if (!errorIsAccepted(err)) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("unexpected API error"); + } + + if (err == RTC_ERROR_NONE) { + RTCRayHit rayhit; + std::memset(&rayhit, 0, sizeof(rayhit)); + rayhit.ray.org_x = 0.2f; + rayhit.ray.org_y = 0.2f; + rayhit.ray.org_z = -1.0f; + rayhit.ray.dir_x = 0.0f; + rayhit.ray.dir_y = 0.0f; + rayhit.ray.dir_z = 1.0f; + rayhit.ray.tnear = 0.0f; + rayhit.ray.tfar = std::numeric_limits::infinity(); + rayhit.ray.time = 2.0f; + rayhit.ray.mask = 0xFFFFFFFFu; + rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID; + + rtcIntersect1(top, &rayhit, nullptr); + err = consumeDeviceError(device); + if (err != RTC_ERROR_NONE) { + rtcReleaseScene(top); + rtcReleaseScene(child); + return failResult("instance array traversal failed"); + } + } + + rtcReleaseScene(top); + rtcReleaseScene(child); + return passResult("instance array time range handled safely"); + } + + constexpr unsigned int max_branching_factor = 8; + + struct MortonNode + { + MortonNode() + { + for (unsigned int i = 0; i < max_branching_factor; ++i) + children[i] = nullptr; + } + + MortonNode* children[max_branching_factor]; + }; + + static bool mortonBuildProgress(void* /*userPtr*/, double /*f*/) + { + return true; + } + + static void* createMortonNode(RTCThreadLocalAllocator alloc, unsigned int childCount, void* /*userPtr*/) + { + assert(childCount <= max_branching_factor); + if (childCount > max_branching_factor) + return nullptr; + + MortonNode* node = (MortonNode*)rtcThreadLocalAlloc(alloc, sizeof(MortonNode), 16); + new (node) MortonNode(); + return node; + } + + static void setMortonNodeChildren(void* nodePtr, void** children, unsigned int childCount, void* /*userPtr*/) + { + assert(childCount <= max_branching_factor); + if (childCount > max_branching_factor) + return; + + MortonNode* node = (MortonNode*)nodePtr; + for (unsigned int i = 0; i < childCount; ++i) + node->children[i] = (MortonNode*)children[i]; + } + + static void setMortonNodeBounds(void* /*nodePtr*/, const RTCBounds** /*bounds*/, unsigned int childCount, void* /*userPtr*/) + { + assert(childCount <= max_branching_factor); + } + + static void* createMortonLeaf(RTCThreadLocalAllocator alloc, + const RTCBuildPrimitive* /*prims*/, + size_t /*primCount*/, + void* /*userPtr*/) + { + MortonNode* node = (MortonNode*)rtcThreadLocalAlloc(alloc, sizeof(MortonNode), 16); + new (node) MortonNode(); + return node; + } + + static std::vector makeMortonGridPrimitives(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.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 mortonBuilderRejectsOversizedBranchingFactor(RTCDevice device, unsigned int maxBranchingFactor) + { + RTCBVH bvh = rtcNewBVH(device); + if (!bvh) + return false; + + std::vector prims = makeMortonGridPrimitives(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 = createMortonNode; + args.setNodeChildren = setMortonNodeChildren; + args.setNodeBounds = setMortonNodeBounds; + args.createLeaf = createMortonLeaf; + args.buildProgress = mortonBuildProgress; + + 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 (!mortonBuilderRejectsOversizedBranchingFactor(device, 64)) + return failResult("maxBranchingFactor=64 was not rejected"); + if (!mortonBuilderRejectsOversizedBranchingFactor(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 }, + { "Issue-01", issue01_time_segment_range_clamp }, + { "Issue-02", issue02_lbbox_nan_range }, + { "Issue-03", issue03_lbbox_extreme_range }, + { "Issue-04", issue04_instance_bound_segment_guard }, + { "Issue-05", issue05_instance_nonlinear_bounds }, + { "Issue-06", issue06_mb_builder_range_check }, + { "Issue-07", issue07_user_bounds_time_range_validation }, + { "Issue-08", issue08_instance_array_object_id_validation }, + { "Issue-09", issue09_instance_array_transform_oob }, + { "Issue-10", issue10_line_segments_second_derivative_output }, + { "Issue-11", issue11_subdiv_verify_before_halfedge }, + { "Issue-12", issue12_curve_index_overflow_validation }, + { "Issue-13", issue13_motion_derivative_root_bound }, + { "Issue-14", issue14_grid_leaf_decode_guards }, + { "Issue-15", issue15_instance_array_time_clamp } + }; + + int failed = 0; + int skipped = 0; + + for (const TestCase& tc : tests) { + CaseResult r = tc.fn(device); + if (r.skip) { + ++skipped; + std::printf("SKIP %s: %s\n", tc.name, r.message.c_str()); + continue; + } + + if (r.pass) { + std::printf("PASS %s: %s\n", tc.name, r.message.c_str()); + } else { + ++failed; + std::printf("FAIL %s: %s\n", tc.name, r.message.c_str()); + } + } + + rtcReleaseDevice(device); + + std::printf("SUMMARY total=%u passed=%u failed=%d skipped=%d\n", + (unsigned)(sizeof(tests) / sizeof(tests[0])), + (unsigned)(sizeof(tests) / sizeof(tests[0])) - (unsigned)failed - (unsigned)skipped, + failed, + skipped); + + return failed == 0 ? 0 : 1; +}