From da6a5dc54f22a38405c1528bc642763de8b5ad0f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 3 Oct 2025 16:09:30 +0100 Subject: [PATCH 1/9] Initial attempt to optimise VertexDraw intersection Basically creates an addon with an acceleration structure with a bounding volume hierarchy so only a subset of triangles need to undergo intersection testing. --- include/vsg/all.h | 1 + .../nodes/IntersectionOptimizedVertexDraw.h | 94 ++++++ include/vsg/utils/LineSegmentIntersector.h | 5 +- src/vsg/CMakeLists.txt | 1 + .../nodes/IntersectionOptimizedVertexDraw.cpp | 309 ++++++++++++++++++ 5 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 include/vsg/nodes/IntersectionOptimizedVertexDraw.h create mode 100644 src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp diff --git a/include/vsg/all.h b/include/vsg/all.h index d97edbcf90..46f5f1366b 100644 --- a/include/vsg/all.h +++ b/include/vsg/all.h @@ -71,6 +71,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #include #include #include +#include #include #include #include diff --git a/include/vsg/nodes/IntersectionOptimizedVertexDraw.h b/include/vsg/nodes/IntersectionOptimizedVertexDraw.h new file mode 100644 index 0000000000..dff7768b5f --- /dev/null +++ b/include/vsg/nodes/IntersectionOptimizedVertexDraw.h @@ -0,0 +1,94 @@ +#pragma once + +/* + +Copyright(c) 2025 Chris Djali + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +#include +#include + +namespace vsg +{ + class LineSegmentIntersector; + + /** IntersectionOptimizedVertexDraw extends VertexDraw with a BVH to accelerate vsg::Intersector operations */ + class VSG_DECLSPEC IntersectionOptimizedVertexDraw : public Inherit + { + public: + IntersectionOptimizedVertexDraw(const VertexDraw& rhs, const CopyOp& copyop = {}); + IntersectionOptimizedVertexDraw(const IntersectionOptimizedVertexDraw& rhs, const CopyOp& copyop = {}); + + void rebuild(ArrayState& arrayState); + + void intersect(LineSegmentIntersector& lineSegmentIntersector) const; + + public: + ref_ptr clone(const CopyOp& copyop = {}) const override { return IntersectionOptimizedVertexDraw::create(*this, copyop); } + int compare(const Object& rhs) const override; + + void read(Input& input) override; + void write(Output& output) const override; + + void accept(ConstVisitor& visitor) const override; + + protected: + virtual ~IntersectionOptimizedVertexDraw(); + + struct Triangle + { + vec3 vertex0; + vec3 vertex1; + vec3 vertex2; + }; + static constexpr size_t trisPerLeaf = 16; + struct Leaf + { + std::array tris; + }; + struct NodeRef + { + enum NodeType + { + LEAF, + INTERNAL, + INVALID + }; + NodeType type; + uint32_t index; + }; + struct InternalNode + { + std::array, 2> children; + }; + + std::vector> internalNodes; + std::vector> leaves; + box bounds; + NodeRef boundingVolumeHeirarchy; + }; + VSG_type_name(vsg::IntersectionOptimizedVertexDraw) + + class VSG_DECLSPEC IntersectionOptimizeVisitor : public Inherit + { + public: + using ArrayStateStack = std::vector>; + + IntersectionOptimizeVisitor(ref_ptr initialArrayState = {}); + + void apply(Node& node) override; + + void apply(StateGroup& stateGroup) override; + + protected: + ArrayStateStack arrayStateStack; + }; + VSG_type_name(vsg::IntersectionOptimizeVisitor) +} // namespace vsg diff --git a/include/vsg/utils/LineSegmentIntersector.h b/include/vsg/utils/LineSegmentIntersector.h index 48ecd70a9e..ff32cc64e0 100644 --- a/include/vsg/utils/LineSegmentIntersector.h +++ b/include/vsg/utils/LineSegmentIntersector.h @@ -68,13 +68,16 @@ namespace vsg bool intersectDraw(uint32_t firstVertex, uint32_t vertexCount, uint32_t firstInstance, uint32_t instanceCount) override; bool intersectDrawIndexed(uint32_t firstIndex, uint32_t indexCount, uint32_t firstInstance, uint32_t instanceCount) override; - protected: struct LineSegment { dvec3 start; dvec3 end; }; + const LineSegment& lineSegment() { return _lineSegmentStack.back(); } + + protected: + std::vector _lineSegmentStack; }; VSG_type_name(vsg::LineSegmentIntersector); diff --git a/src/vsg/CMakeLists.txt b/src/vsg/CMakeLists.txt index 26e87199aa..86690ca462 100644 --- a/src/vsg/CMakeLists.txt +++ b/src/vsg/CMakeLists.txt @@ -50,6 +50,7 @@ set(SOURCES nodes/InstanceNode.cpp nodes/InstanceDraw.cpp nodes/InstanceDrawIndexed.cpp + nodes/IntersectionOptimizedVertexDraw.cpp lighting/Light.cpp lighting/AmbientLight.cpp diff --git a/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp b/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp new file mode 100644 index 0000000000..c8da8dd029 --- /dev/null +++ b/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp @@ -0,0 +1,309 @@ +/* + +Copyright(c) 2025 Chris Djali + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +#include +#include +#include +#include +#include +#include +#include + +using namespace vsg; + +IntersectionOptimizedVertexDraw::IntersectionOptimizedVertexDraw(const VertexDraw& vertexDraw, const CopyOp& copyop) : + Inherit(vertexDraw, copyop), + internalNodes(), + leaves(), + bounds(), + boundingVolumeHeirarchy({NodeRef::INVALID, 0u}) +{ +} + +IntersectionOptimizedVertexDraw::IntersectionOptimizedVertexDraw(const IntersectionOptimizedVertexDraw& rhs, const CopyOp& copyop) : + Inherit(rhs, copyop), + internalNodes(rhs.internalNodes), + leaves(rhs.leaves), + bounds(rhs.bounds), + boundingVolumeHeirarchy(rhs.boundingVolumeHeirarchy) +{ +} + +void vsg::IntersectionOptimizedVertexDraw::rebuild(vsg::ArrayState& arrayState) +{ + leaves.clear(); + internalNodes.clear(); + + arrayState.apply(*this); + uint32_t lastIndex = instanceCount > 1 ? (firstInstance + instanceCount) : firstInstance + 1; + uint32_t endVertex = firstVertex + vertexCount; + + // if instancing is used, accessing the nth triangle is a hassle, so grab them upfront + std::vector triangles; + triangles.reserve(instanceCount * vertexCount / 3); + + for (uint32_t instanceIndex = firstInstance; instanceIndex < lastIndex; ++instanceIndex) + { + if (auto vertices = arrayState.vertexArray(instanceIndex)) + { + for (uint32_t i = firstVertex; (i + 2) < endVertex; i += 3) + { + triangles.emplace_back(Triangle{vertices->at(i), vertices->at(i + 1), vertices->at(i + 2)}); + } + } + } + + std::vector barycenters; + barycenters.reserve(triangles.size()); + for (const auto& triangle : triangles) + { + barycenters.emplace_back((triangle.vertex0 + triangle.vertex1 + triangle.vertex2) / 3.f); + } + + std::vector indices; + indices.reserve(triangles.size()); + for (size_t i = 0; i < triangles.size(); ++i) + { + indices.emplace_back(i); + } + + leaves.reserve(triangles.size() / trisPerLeaf); + internalNodes.reserve(triangles.size() / (trisPerLeaf * 2)); + + using itr_t = decltype(indices)::iterator; + + auto computeKDTree = [&](itr_t first, itr_t last, auto&& computeKDTree) -> std::pair { + if (std::distance(first, last) < trisPerLeaf) + { + leaves.emplace_back(); + box bound; + itr_t itr = first; + for (size_t i = 0; i < trisPerLeaf; ++i) + { + if (itr != last) + { + leaves.back().tris[i] = triangles[*itr]; + bound.add(triangles[*itr].vertex0); + bound.add(triangles[*itr].vertex1); + bound.add(triangles[*itr].vertex2); + ++itr; + } + else + { + // add a degenerate triangle as we have to have trisPerLeaf + leaves.back().tris[i] = {vec3(), vec3(), vec3()}; + } + } + return std::make_pair(bound, NodeRef{NodeRef::LEAF, static_cast(leaves.size() - 1)}); + } + else + { + box baryBound; + for (itr_t itr = first; itr != last; ++itr) + { + baryBound.add(barycenters[*itr]); + } + vec3 range = baryBound.max - baryBound.min; + size_t axisIndex = range.x > range.y ? (range.x > range.z ? 0 : 2) : (range.y > range.z ? 1 : 2); + itr_t midpoint = first + std::distance(first, last) / 2; + std::nth_element(first, midpoint, last, [&, axisIndex](const size_t& lhs, const size_t& rhs) { return barycenters[lhs][axisIndex] < barycenters[rhs][axisIndex]; }); + internalNodes.emplace_back(InternalNode{{computeKDTree(first, midpoint, computeKDTree), computeKDTree(midpoint, last, computeKDTree)}}); + box overallBound; + for (const auto& [bound, ref] : internalNodes.back().children) + { + overallBound.add(bound); + } + return std::make_pair(overallBound, NodeRef{NodeRef::INTERNAL, static_cast(internalNodes.size() - 1)}); + } + }; + + std::tie(bounds, boundingVolumeHeirarchy) = computeKDTree(indices.begin(), indices.end(), computeKDTree); +} + +void vsg::IntersectionOptimizedVertexDraw::intersect(LineSegmentIntersector& lineSegmentIntersector) const +{ + const auto& ls = lineSegmentIntersector.lineSegment(); + + using value_type = double; + using vec_type = t_vec3; + const value_type epsilon = 1e-10; + + vec_type start = ls.start; + vec_type end = ls.end; + + vec_type d = end - start; + + auto intersectBox = [&](const box& bound) { + value_type t1 = (bound.min.x - start.x) / d.x; + value_type t2 = (bound.max.x - start.x) / d.x; + value_type tmin = std::min(t1, t2); + value_type tmax = std::max(t1, t2); + t1 = (bound.min.y - start.y) / d.y; + t2 = (bound.max.y - start.y) / d.y; + tmin = std::max(tmin, std::min(t1, t2)); + tmax = std::min(tmax, std::max(t1, t2)); + t1 = (bound.min.z - start.z) / d.z; + t2 = (bound.max.z - start.z) / d.z; + tmin = std::max(tmin, std::min(t1, t2)); + tmax = std::min(tmax, std::max(t1, t2)); + return tmax >= tmin && tmin < 1.0 && tmax > 0.0; + }; + + if (!bounds.valid() || !intersectBox(bounds)) + return; + + value_type length = ::length(d); + value_type inverseLength = length != 0.0 ? 1.0 / length : 0.0; + + vec_type dInvX = d.x != 0.0 ? d / d.x : vec_type{0.0, 0.0, 0.0}; + vec_type dInvY = d.y != 0.0 ? d / d.y : vec_type{0.0, 0.0, 0.0}; + vec_type dInvZ = d.z != 0.0 ? d / d.z : vec_type{0.0, 0.0, 0.0}; + + auto intersectLeaf = [&](uint32_t index) { + // using different but consecutive addresses for different triangle results should encourage vectorisation + std::array r, r0, r1, r2; + r.fill(std::numeric_limits::quiet_NaN()); + for (size_t i = 0; i < trisPerLeaf; ++i) + { + const auto& triangle = leaves[index].tris[i]; + + vec_type E1 = vec_type(triangle.vertex1) - vec_type(triangle.vertex0); + vec_type E2 = vec_type(triangle.vertex2) - vec_type(triangle.vertex0); + + vec_type P = cross(d, E2); + value_type det = dot(P, E1); + if (det > -epsilon && det < epsilon) continue; + + value_type inv_det = 1.0 / det; + vec_type T = vec_type(start) - vec_type(triangle.vertex0); + value_type u = inv_det * dot(P, T); + if (u < 0.0 || u > 1) continue; + + vec_type Q = cross(T, E1); + value_type v = inv_det * dot(Q, d); + if (v < 0.0 || u + v > 1.0) continue; + + value_type t = inv_det * dot(Q, E2); + if (t < epsilon) continue; + + r0[i] = 1.0 - u - v; + r1[i] = u; + r2[i] = v; + r[i] = t * inverseLength; + } + + // this loop is separate because the intersector modification won't vectorise + for (size_t i = 0; i < trisPerLeaf; ++i) + { + if (std::isnan(r[i])) continue; + + const auto& triangle = leaves[index].tris[i]; + dvec3 intersection = dvec3(triangle.vertex0) * double(r0[i]) + dvec3(triangle.vertex1) * double(r1[i]) + dvec3(triangle.vertex2) * double(r2[i]); + // todo: track original triangle indices and index + lineSegmentIntersector.add(intersection, double(r[i]), {}, 0); + } + }; + + auto intersectNode = [&](const NodeRef& nodeRef, auto&& intersectNode) -> void { + if (nodeRef.type == NodeRef::LEAF) + { + intersectLeaf(nodeRef.index); + } + else + { + const auto& node = internalNodes[nodeRef.index]; + for (const auto& [bound, child] : node.children) + { + if (intersectBox(bound)) + { + intersectNode(child, intersectNode); + } + } + } + }; + + intersectNode(boundingVolumeHeirarchy, intersectNode); +} + +IntersectionOptimizedVertexDraw::~IntersectionOptimizedVertexDraw() = default; + +int IntersectionOptimizedVertexDraw::compare(const Object& rhs_object) const +{ + int result = VertexDraw::compare(rhs_object); + if (result != 0) return result; + + const auto& rhs = static_cast(rhs_object); + // computation of BVH should be deterministic, so if the input is the same and it's actually been computed, we shouldn't need to scan all the nodes + if ((result = compare_value(boundingVolumeHeirarchy.type, rhs.boundingVolumeHeirarchy.type)) != 0) return result; + return compare_value(boundingVolumeHeirarchy.index, boundingVolumeHeirarchy.index); +} + +void IntersectionOptimizedVertexDraw::read(Input& input) +{ + VertexDraw::read(input); +} + +void IntersectionOptimizedVertexDraw::write(Output& output) const +{ + VertexDraw::write(output); +} + +void vsg::IntersectionOptimizedVertexDraw::accept(ConstVisitor& visitor) const +{ + if (boundingVolumeHeirarchy.type != NodeRef::INVALID && visitor.is_compatible(typeid(Intersector))) + { + auto* lineSegmentIntersector = dynamic_cast(&visitor); + if (lineSegmentIntersector) + { + intersect(*lineSegmentIntersector); + return; + } + } + Inherit::accept(visitor); +} + +vsg::IntersectionOptimizeVisitor::IntersectionOptimizeVisitor(ref_ptr initialArrayState) +{ + arrayStateStack.reserve(4); + arrayStateStack.emplace_back(initialArrayState ? initialArrayState : ArrayState::create()); +} + +void vsg::IntersectionOptimizeVisitor::apply(Node& node) +{ + node.traverse(*this); +} + +void vsg::IntersectionOptimizeVisitor::apply(StateGroup& stategroup) +{ + auto arrayState = stategroup.prototypeArrayState ? stategroup.prototypeArrayState->cloneArrayState(arrayStateStack.back()) : arrayStateStack.back()->cloneArrayState(); + + for (auto& statecommand : stategroup.stateCommands) + { + statecommand->accept(*arrayState); + } + + arrayStateStack.emplace_back(arrayState); + + stategroup.traverse(*this); + + for (auto& child : stategroup.children) + { + if (child->className() == "vsg::VertexDraw") + { + auto optimized = vsg::IntersectionOptimizedVertexDraw::create(static_cast(*child)); + optimized->rebuild(*arrayStateStack.back()); + child = optimized; + } + } + + arrayStateStack.pop_back(); +} From e5f93daa5551720193af6f4f083551358315a2f2 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 3 Oct 2025 18:04:56 +0100 Subject: [PATCH 2/9] Add intersection details --- include/vsg/nodes/IntersectionOptimizedVertexDraw.h | 12 ++++++++++++ src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp | 9 +++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/include/vsg/nodes/IntersectionOptimizedVertexDraw.h b/include/vsg/nodes/IntersectionOptimizedVertexDraw.h index dff7768b5f..ab3decde78 100644 --- a/include/vsg/nodes/IntersectionOptimizedVertexDraw.h +++ b/include/vsg/nodes/IntersectionOptimizedVertexDraw.h @@ -73,6 +73,18 @@ namespace vsg std::vector> leaves; box bounds; NodeRef boundingVolumeHeirarchy; + + struct TriangleMetadata + { + uint32_t index; + uint32_t instance; + }; + struct LeafMetadata + { + std::array tris; + }; + + std::vector> leafMetadata; }; VSG_type_name(vsg::IntersectionOptimizedVertexDraw) diff --git a/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp b/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp index c8da8dd029..2fb3b43175 100644 --- a/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp +++ b/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp @@ -50,6 +50,8 @@ void vsg::IntersectionOptimizedVertexDraw::rebuild(vsg::ArrayState& arrayState) // if instancing is used, accessing the nth triangle is a hassle, so grab them upfront std::vector triangles; triangles.reserve(instanceCount * vertexCount / 3); + std::vector metadata; + metadata.reserve(triangles.size()); for (uint32_t instanceIndex = firstInstance; instanceIndex < lastIndex; ++instanceIndex) { @@ -58,6 +60,7 @@ void vsg::IntersectionOptimizedVertexDraw::rebuild(vsg::ArrayState& arrayState) for (uint32_t i = firstVertex; (i + 2) < endVertex; i += 3) { triangles.emplace_back(Triangle{vertices->at(i), vertices->at(i + 1), vertices->at(i + 2)}); + metadata.emplace_back(TriangleMetadata{i, instanceIndex}); } } } @@ -85,6 +88,7 @@ void vsg::IntersectionOptimizedVertexDraw::rebuild(vsg::ArrayState& arrayState) if (std::distance(first, last) < trisPerLeaf) { leaves.emplace_back(); + leafMetadata.emplace_back(); box bound; itr_t itr = first; for (size_t i = 0; i < trisPerLeaf; ++i) @@ -95,6 +99,7 @@ void vsg::IntersectionOptimizedVertexDraw::rebuild(vsg::ArrayState& arrayState) bound.add(triangles[*itr].vertex0); bound.add(triangles[*itr].vertex1); bound.add(triangles[*itr].vertex2); + leafMetadata.back().tris[i] = metadata[*itr]; ++itr; } else @@ -208,8 +213,8 @@ void vsg::IntersectionOptimizedVertexDraw::intersect(LineSegmentIntersector& lin const auto& triangle = leaves[index].tris[i]; dvec3 intersection = dvec3(triangle.vertex0) * double(r0[i]) + dvec3(triangle.vertex1) * double(r1[i]) + dvec3(triangle.vertex2) * double(r2[i]); - // todo: track original triangle indices and index - lineSegmentIntersector.add(intersection, double(r[i]), {}, 0); + const auto& metadata = leafMetadata[index].tris[i]; + lineSegmentIntersector.add(intersection, double(r[i]), {{metadata.index, r0[i]}, {metadata.index + 1, r1[i]}, {metadata.index + 2, r2[i]}}, metadata.instance); } }; From d0aaa9f277368ae02a7cf08acbf624e8efd32233 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 6 Oct 2025 19:21:32 +0100 Subject: [PATCH 3/9] Abandon 'vectorisation-friendly' triangle intersection split loop It turns out that modern C++ compilers really don't want to vectorise this loop like a shader compiler would. vsg::vec3 being a struct apparently upsets them, even when it's only used within the function, so an array of vec3s could be rearranged to three arrays of components. --- .../nodes/IntersectionOptimizedVertexDraw.cpp | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp b/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp index 2fb3b43175..918baeb827 100644 --- a/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp +++ b/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp @@ -174,9 +174,6 @@ void vsg::IntersectionOptimizedVertexDraw::intersect(LineSegmentIntersector& lin vec_type dInvZ = d.z != 0.0 ? d / d.z : vec_type{0.0, 0.0, 0.0}; auto intersectLeaf = [&](uint32_t index) { - // using different but consecutive addresses for different triangle results should encourage vectorisation - std::array r, r0, r1, r2; - r.fill(std::numeric_limits::quiet_NaN()); for (size_t i = 0; i < trisPerLeaf; ++i) { const auto& triangle = leaves[index].tris[i]; @@ -199,22 +196,14 @@ void vsg::IntersectionOptimizedVertexDraw::intersect(LineSegmentIntersector& lin value_type t = inv_det * dot(Q, E2); if (t < epsilon) continue; - - r0[i] = 1.0 - u - v; - r1[i] = u; - r2[i] = v; - r[i] = t * inverseLength; - } - // this loop is separate because the intersector modification won't vectorise - for (size_t i = 0; i < trisPerLeaf; ++i) - { - if (std::isnan(r[i])) continue; + value_type r0 = 1.0 - u - v; + value_type r1 = u; + value_type r2 = v; - const auto& triangle = leaves[index].tris[i]; - dvec3 intersection = dvec3(triangle.vertex0) * double(r0[i]) + dvec3(triangle.vertex1) * double(r1[i]) + dvec3(triangle.vertex2) * double(r2[i]); + dvec3 intersection = dvec3(triangle.vertex0) * double(r0) + dvec3(triangle.vertex1) * double(r1) + dvec3(triangle.vertex2) * double(r2); const auto& metadata = leafMetadata[index].tris[i]; - lineSegmentIntersector.add(intersection, double(r[i]), {{metadata.index, r0[i]}, {metadata.index + 1, r1[i]}, {metadata.index + 2, r2[i]}}, metadata.instance); + lineSegmentIntersector.add(intersection, double(t * inverseLength), {{metadata.index, r0}, {metadata.index + 1, r1}, {metadata.index + 2, r2}}, metadata.instance); } }; From b1a2fa9579a9b7fba431ce955064ebe59b9b708d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 6 Oct 2025 19:41:14 +0100 Subject: [PATCH 4/9] Use conversion that works when value_type is float Switching to float makes things a bit faster, but made some intersections go undetected with my test data, so I'm not switching. --- src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp b/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp index 918baeb827..6ef01c81da 100644 --- a/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp +++ b/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp @@ -142,8 +142,8 @@ void vsg::IntersectionOptimizedVertexDraw::intersect(LineSegmentIntersector& lin using vec_type = t_vec3; const value_type epsilon = 1e-10; - vec_type start = ls.start; - vec_type end = ls.end; + vec_type start(ls.start); + vec_type end(ls.end); vec_type d = end - start; From e1e81dc98615fa1f2c3fec27a678acc63e0f9fad Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 7 Oct 2025 17:11:35 +0100 Subject: [PATCH 5/9] Make intersectors reusable A lot of time during intersector traversal is spent doing allocations and deallocations. This can be avoided by just reusing the existing allocations. The easiest way to do that is to reuse the thing that owns the allocations. Helpfully, just resizing a vector doesn't throw away its existing allocation, as usually a vector is used for things about the same size as the last time they were used. This doesn't totally remove all allocations - the array state stack has vectors of matrices in each element, so when elements are removed, those are destroyed and reallocated the next frame, and the intersection vector also has node paths etc. in each element. Eliminating these would be more challenging. With the test data I've tried, and using the accelerated IntersectionOptimizedVertexDraw class, reusing the same LineSegmentIntersector instance with this change knocks about 15% off the time to do a lot of intersection queries. 20% of the time is still spent doing allocations and deallocations, though. --- include/vsg/utils/Intersector.h | 2 + include/vsg/utils/LineSegmentIntersector.h | 4 ++ include/vsg/utils/PolytopeIntersector.h | 4 ++ src/vsg/utils/Intersector.cpp | 17 ++++++ src/vsg/utils/LineSegmentIntersector.cpp | 48 ++++++++++++++++ src/vsg/utils/PolytopeIntersector.cpp | 65 ++++++++++++++++++++++ 6 files changed, 140 insertions(+) diff --git a/include/vsg/utils/Intersector.h b/include/vsg/utils/Intersector.h index 7950c6e456..c35c1cbe9e 100644 --- a/include/vsg/utils/Intersector.h +++ b/include/vsg/utils/Intersector.h @@ -28,6 +28,8 @@ namespace vsg Intersector(ref_ptr initialArrayState = {}); + virtual void reset(ref_ptr initialArrayState = {}); + // // handle traverse of the scene graph // diff --git a/include/vsg/utils/LineSegmentIntersector.h b/include/vsg/utils/LineSegmentIntersector.h index ff32cc64e0..8a68aaa02b 100644 --- a/include/vsg/utils/LineSegmentIntersector.h +++ b/include/vsg/utils/LineSegmentIntersector.h @@ -34,6 +34,10 @@ namespace vsg LineSegmentIntersector(const dvec3& s, const dvec3& e, ref_ptr initialArrayData = {}); LineSegmentIntersector(const Camera& camera, int32_t x, int32_t y, ref_ptr initialArrayData = {}); + void reset(ref_ptr initialArrayData = {}) override; + virtual void reset(const dvec3& s, const dvec3& e, ref_ptr initialArrayData = {}); + virtual void reset(const Camera& camera, int32_t x, int32_t y, ref_ptr initialArrayData = {}); + class VSG_DECLSPEC Intersection : public Inherit { public: diff --git a/include/vsg/utils/PolytopeIntersector.h b/include/vsg/utils/PolytopeIntersector.h index f32bc23cc0..fdf8ece1a6 100644 --- a/include/vsg/utils/PolytopeIntersector.h +++ b/include/vsg/utils/PolytopeIntersector.h @@ -32,6 +32,10 @@ namespace vsg /// create intersector for a polytope with window space dimensions, projected into world coords using the Camera's projection and view matrices. PolytopeIntersector(const Camera& camera, double xMin, double yMin, double xMax, double yMax, ref_ptr initialArrayData = {}); + void reset(ref_ptr initialArrayData = {}) override; + virtual void reset(const Polytope& in_polytope, ref_ptr initialArrayData = {}); + virtual void reset(const Camera& camera, double xMin, double yMin, double xMax, double yMax, ref_ptr initialArrayData = {}); + class VSG_DECLSPEC Intersection : public Inherit { public: diff --git a/src/vsg/utils/Intersector.cpp b/src/vsg/utils/Intersector.cpp index fc58b52163..5199819bfc 100644 --- a/src/vsg/utils/Intersector.cpp +++ b/src/vsg/utils/Intersector.cpp @@ -48,6 +48,23 @@ Intersector::Intersector(ref_ptr initialArrayState) arrayStateStack.emplace_back(initialArrayState ? initialArrayState : ArrayState::create()); } +void Intersector::reset(ref_ptr initialArrayState) +{ + // don't reset arrayStateStack if we don't have to. + // it should be back in its initial state after a traversal has finished. + if (initialArrayState) + { + arrayStateStack.resize(1); + arrayStateStack.front() = initialArrayState; + } + + ubyte_indices.reset(); + ushort_indices.reset(); + uint_indices.reset(); + + _nodePath.clear(); +} + void Intersector::apply(const Node& node) { PushPopNode ppn(_nodePath, &node); diff --git a/src/vsg/utils/LineSegmentIntersector.cpp b/src/vsg/utils/LineSegmentIntersector.cpp index 2b385cdad9..a48405f68d 100644 --- a/src/vsg/utils/LineSegmentIntersector.cpp +++ b/src/vsg/utils/LineSegmentIntersector.cpp @@ -165,6 +165,54 @@ LineSegmentIntersector::LineSegmentIntersector(const Camera& camera, int32_t x, _lineSegmentStack.push_back(LineSegment{eyeToWorld * eye_near, eyeToWorld * eye_far}); } +void LineSegmentIntersector::reset(ref_ptr initialArrayData) +{ + Intersector::reset(initialArrayData); + + intersections.clear(); +} + +void LineSegmentIntersector::reset(const dvec3& s, const dvec3& e, ref_ptr initialArrayData) +{ + reset(initialArrayData); + + _lineSegmentStack.resize(1); + _lineSegmentStack.front() = {s, e}; +} + +void LineSegmentIntersector::reset(const Camera& camera, int32_t x, int32_t y, ref_ptr initialArrayData) +{ + reset(initialArrayData); + + _lineSegmentStack.resize(2); + + auto viewport = camera.getViewport(); + + vsg::vec2 ndc(0.0f, 0.0f); + if ((viewport.width > 0) && (viewport.height > 0)) + { + ndc.set((static_cast(x) - viewport.x) / viewport.width, (static_cast(y) - viewport.y) / viewport.height); + } + + auto projectionMatrix = camera.projectionMatrix->transform(); + auto viewMatrix = camera.viewMatrix->transform(); + + bool reverse_depth = (projectionMatrix(2, 2) > 0.0); + + vsg::dvec3 ndc_near(ndc.x * 2.0 - 1.0, ndc.y * 2.0 - 1.0, reverse_depth ? viewport.maxDepth : viewport.minDepth); + vsg::dvec3 ndc_far(ndc.x * 2.0 - 1.0, ndc.y * 2.0 - 1.0, reverse_depth ? viewport.minDepth : viewport.maxDepth); + + auto inv_projectionMatrix = vsg::inverse(projectionMatrix); + vsg::dvec3 eye_near = inv_projectionMatrix * ndc_near; + vsg::dvec3 eye_far = inv_projectionMatrix * ndc_far; + _lineSegmentStack.front() = LineSegment{eye_near, eye_far}; + + dmat4 eyeToWorld = inverse(viewMatrix); + localToWorldStack().push_back(viewMatrix); + worldToLocalStack().push_back(eyeToWorld); + _lineSegmentStack.back() = LineSegment{eyeToWorld * eye_near, eyeToWorld * eye_far}; +} + LineSegmentIntersector::Intersection::Intersection(const dvec3& in_localIntersection, const dvec3& in_worldIntersection, double in_ratio, const dmat4& in_localToWorld, const NodePath& in_nodePath, const DataList& in_arrays, const IndexRatios& in_indexRatios, uint32_t in_instanceIndex) : localIntersection(in_localIntersection), worldIntersection(in_worldIntersection), diff --git a/src/vsg/utils/PolytopeIntersector.cpp b/src/vsg/utils/PolytopeIntersector.cpp index bc60959657..5210990fca 100644 --- a/src/vsg/utils/PolytopeIntersector.cpp +++ b/src/vsg/utils/PolytopeIntersector.cpp @@ -243,6 +243,71 @@ PolytopeIntersector::PolytopeIntersector(const Camera& camera, double xMin, doub worldToLocalStack().push_back(eyeToWorld); } +void vsg::PolytopeIntersector::reset(ref_ptr initialArrayData) +{ + Intersector::reset(initialArrayData); + + intersections.clear(); +} + +void vsg::PolytopeIntersector::reset(const Polytope& in_polytope, ref_ptr initialArrayData) +{ + reset(initialArrayData); + + _polytopeStack.resize(1); + _polytopeStack.front() = in_polytope; +} + +void vsg::PolytopeIntersector::reset(const Camera& camera, double xMin, double yMin, double xMax, double yMax, ref_ptr initialArrayData) +{ + reset(initialArrayData); + + _polytopeStack.resize(2); + + auto viewport = camera.getViewport(); + + auto projectionMatrix = camera.projectionMatrix->transform(); + auto viewMatrix = camera.viewMatrix->transform(); + bool reverse_depth = (projectionMatrix(2, 2) > 0.0); + + double ndc_xMin = (viewport.width > 0) ? (2.0 * (xMin - static_cast(viewport.x)) / static_cast(viewport.width) - 1.0) : xMin; + double ndc_xMax = (viewport.width > 0) ? (2.0 * (xMax - static_cast(viewport.x)) / static_cast(viewport.width) - 1.0) : xMax; + + double ndc_yMin = (viewport.height > 0) ? (2.0 * (yMin - static_cast(viewport.y)) / static_cast(viewport.height) - 1.0) : yMin; + double ndc_yMax = (viewport.height > 0) ? (2.0 * (yMax - static_cast(viewport.y)) / static_cast(viewport.height) - 1.0) : yMax; + + double ndc_near = reverse_depth ? viewport.maxDepth : viewport.minDepth; + double ndc_far = reverse_depth ? viewport.minDepth : viewport.maxDepth; + + vsg::Polytope clipspace; + clipspace.push_back(dplane(1.0, 0.0, 0.0, -ndc_xMin)); // left + clipspace.push_back(dplane(-1.0, 0.0, 0.0, ndc_xMax)); // right + clipspace.push_back(dplane(0.0, 1.0, 0.0, -ndc_yMin)); // bottom + clipspace.push_back(dplane(0.0, -1.0, 0.0, ndc_yMax)); // top + clipspace.push_back(dplane(0.0, 0.0, -1.0, ndc_near)); // near + clipspace.push_back(dplane(0.0, 0.0, 1.0, ndc_far)); // far + + vsg::Polytope eyespace; + for (auto& pl : clipspace) + { + eyespace.push_back(pl * projectionMatrix); + } + + _polytopeStack.front() = eyespace; + + vsg::Polytope worldspace; + for (auto& pl : eyespace) + { + worldspace.push_back(pl * viewMatrix); + } + + _polytopeStack.back() = worldspace; + + dmat4 eyeToWorld = inverse(viewMatrix); + localToWorldStack().push_back(viewMatrix); + worldToLocalStack().push_back(eyeToWorld); +} + PolytopeIntersector::Intersection::Intersection(const dvec3& in_localIntersection, const dvec3& in_worldIntersection, const dmat4& in_localToWorld, const NodePath& in_nodePath, const DataList& in_arrays, const std::vector& in_indices, uint32_t in_instanceIndex) : localIntersection(in_localIntersection), worldIntersection(in_worldIntersection), From 817fd53bedad2e13de7015486e3140dcd8f3b04c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 8 Oct 2025 22:19:07 +0100 Subject: [PATCH 6/9] Make intersection acceleration not rely on subclassing VertexDraw --- include/vsg/all.h | 2 +- include/vsg/core/ConstVisitor.h | 2 + include/vsg/core/Visitor.h | 2 + ...imizedVertexDraw.h => IntersectionProxy.h} | 30 ++++-- include/vsg/utils/LineSegmentIntersector.h | 2 + src/vsg/CMakeLists.txt | 2 +- src/vsg/core/ConstVisitor.cpp | 4 + src/vsg/core/Visitor.cpp | 4 + ...edVertexDraw.cpp => IntersectionProxy.cpp} | 91 +++++++++++-------- src/vsg/utils/LineSegmentIntersector.cpp | 13 +++ 10 files changed, 104 insertions(+), 48 deletions(-) rename include/vsg/nodes/{IntersectionOptimizedVertexDraw.h => IntersectionProxy.h} (77%) rename src/vsg/nodes/{IntersectionOptimizedVertexDraw.cpp => IntersectionProxy.cpp} (80%) diff --git a/include/vsg/all.h b/include/vsg/all.h index 46f5f1366b..72bcadfbc4 100644 --- a/include/vsg/all.h +++ b/include/vsg/all.h @@ -71,7 +71,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #include #include #include -#include +#include #include #include #include diff --git a/include/vsg/core/ConstVisitor.h b/include/vsg/core/ConstVisitor.h index 3f7360bbcf..25f5cefab0 100644 --- a/include/vsg/core/ConstVisitor.h +++ b/include/vsg/core/ConstVisitor.h @@ -54,6 +54,7 @@ namespace vsg class InstanceNode; class InstanceDraw; class InstanceDrawIndexed; + class IntersectionProxy; // forward declare text classes class Text; @@ -360,6 +361,7 @@ namespace vsg virtual void apply(const InstanceNode&); virtual void apply(const InstanceDraw&); virtual void apply(const InstanceDrawIndexed&); + virtual void apply(const IntersectionProxy&); // text virtual void apply(const Text&); diff --git a/include/vsg/core/Visitor.h b/include/vsg/core/Visitor.h index 84abca3d54..c04041ea80 100644 --- a/include/vsg/core/Visitor.h +++ b/include/vsg/core/Visitor.h @@ -54,6 +54,7 @@ namespace vsg class InstanceNode; class InstanceDraw; class InstanceDrawIndexed; + class IntersectionProxy; // forward declare text classes class Text; @@ -360,6 +361,7 @@ namespace vsg virtual void apply(InstanceNode&); virtual void apply(InstanceDraw&); virtual void apply(InstanceDrawIndexed&); + virtual void apply(IntersectionProxy&); // text virtual void apply(Text&); diff --git a/include/vsg/nodes/IntersectionOptimizedVertexDraw.h b/include/vsg/nodes/IntersectionProxy.h similarity index 77% rename from include/vsg/nodes/IntersectionOptimizedVertexDraw.h rename to include/vsg/nodes/IntersectionProxy.h index ab3decde78..7a7f0bdca9 100644 --- a/include/vsg/nodes/IntersectionOptimizedVertexDraw.h +++ b/include/vsg/nodes/IntersectionProxy.h @@ -12,35 +12,47 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ -#include +#include #include namespace vsg { class LineSegmentIntersector; - /** IntersectionOptimizedVertexDraw extends VertexDraw with a BVH to accelerate vsg::Intersector operations */ - class VSG_DECLSPEC IntersectionOptimizedVertexDraw : public Inherit + /** IntersectionProxy wraps a node with a BVH to accelerate vsg::Intersector operations */ + class VSG_DECLSPEC IntersectionProxy : public Inherit { public: - IntersectionOptimizedVertexDraw(const VertexDraw& rhs, const CopyOp& copyop = {}); - IntersectionOptimizedVertexDraw(const IntersectionOptimizedVertexDraw& rhs, const CopyOp& copyop = {}); + IntersectionProxy(Node* in_original); + IntersectionProxy(const IntersectionProxy& rhs, const CopyOp& copyop = {}); void rebuild(ArrayState& arrayState); + bool valid() const; + void intersect(LineSegmentIntersector& lineSegmentIntersector) const; public: - ref_ptr clone(const CopyOp& copyop = {}) const override { return IntersectionOptimizedVertexDraw::create(*this, copyop); } + ref_ptr clone(const CopyOp& copyop = {}) const override { return IntersectionProxy::create(*this, copyop); } int compare(const Object& rhs) const override; + template + static void t_traverse(N& node, V& visitor) + { + node.original->accept(visitor); + } + + void traverse(Visitor& visitor) override { t_traverse(*this, visitor); } + void traverse(ConstVisitor& visitor) const override { t_traverse(*this, visitor); } + void traverse(RecordTraversal& visitor) const override { t_traverse(*this, visitor); } + void read(Input& input) override; void write(Output& output) const override; - void accept(ConstVisitor& visitor) const override; + ref_ptr original; protected: - virtual ~IntersectionOptimizedVertexDraw(); + virtual ~IntersectionProxy(); struct Triangle { @@ -86,7 +98,7 @@ namespace vsg std::vector> leafMetadata; }; - VSG_type_name(vsg::IntersectionOptimizedVertexDraw) + VSG_type_name(vsg::IntersectionProxy) class VSG_DECLSPEC IntersectionOptimizeVisitor : public Inherit { diff --git a/include/vsg/utils/LineSegmentIntersector.h b/include/vsg/utils/LineSegmentIntersector.h index 8a68aaa02b..8f5b043c28 100644 --- a/include/vsg/utils/LineSegmentIntersector.h +++ b/include/vsg/utils/LineSegmentIntersector.h @@ -63,6 +63,8 @@ namespace vsg ref_ptr add(const dvec3& coord, double ratio, const IndexRatios& indexRatios, uint32_t instanceIndex); + void apply(const IntersectionProxy& intersectionproxy) override; + void pushTransform(const Transform& transform) override; void popTransform() override; diff --git a/src/vsg/CMakeLists.txt b/src/vsg/CMakeLists.txt index 86690ca462..f0e5ead6dc 100644 --- a/src/vsg/CMakeLists.txt +++ b/src/vsg/CMakeLists.txt @@ -50,7 +50,7 @@ set(SOURCES nodes/InstanceNode.cpp nodes/InstanceDraw.cpp nodes/InstanceDrawIndexed.cpp - nodes/IntersectionOptimizedVertexDraw.cpp + nodes/IntersectionProxy.cpp lighting/Light.cpp lighting/AmbientLight.cpp diff --git a/src/vsg/core/ConstVisitor.cpp b/src/vsg/core/ConstVisitor.cpp index 4b72f37c51..0b2fd1d1a0 100644 --- a/src/vsg/core/ConstVisitor.cpp +++ b/src/vsg/core/ConstVisitor.cpp @@ -689,6 +689,10 @@ void ConstVisitor::apply(const InstanceDrawIndexed& value) { apply(static_cast(value)); } +void ConstVisitor::apply(const IntersectionProxy& value) +{ + apply(static_cast(value)); +} //////////////////////////////////////////////////////////////////////////////// // diff --git a/src/vsg/core/Visitor.cpp b/src/vsg/core/Visitor.cpp index 377757159c..3fad730115 100644 --- a/src/vsg/core/Visitor.cpp +++ b/src/vsg/core/Visitor.cpp @@ -689,6 +689,10 @@ void Visitor::apply(InstanceDrawIndexed& value) { apply(static_cast(value)); } +void Visitor::apply(IntersectionProxy& value) +{ + apply(static_cast(value)); +} //////////////////////////////////////////////////////////////////////////////// // diff --git a/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp b/src/vsg/nodes/IntersectionProxy.cpp similarity index 80% rename from src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp rename to src/vsg/nodes/IntersectionProxy.cpp index 6ef01c81da..97b0801690 100644 --- a/src/vsg/nodes/IntersectionOptimizedVertexDraw.cpp +++ b/src/vsg/nodes/IntersectionProxy.cpp @@ -13,15 +13,17 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #include #include #include -#include +#include #include +#include #include #include using namespace vsg; -IntersectionOptimizedVertexDraw::IntersectionOptimizedVertexDraw(const VertexDraw& vertexDraw, const CopyOp& copyop) : - Inherit(vertexDraw, copyop), +IntersectionProxy::IntersectionProxy(Node* in_original) : + Inherit(), + original(in_original), internalNodes(), leaves(), bounds(), @@ -29,8 +31,9 @@ IntersectionOptimizedVertexDraw::IntersectionOptimizedVertexDraw(const VertexDra { } -IntersectionOptimizedVertexDraw::IntersectionOptimizedVertexDraw(const IntersectionOptimizedVertexDraw& rhs, const CopyOp& copyop) : +IntersectionProxy::IntersectionProxy(const IntersectionProxy& rhs, const CopyOp& copyop) : Inherit(rhs, copyop), + original(rhs.original), internalNodes(rhs.internalNodes), leaves(rhs.leaves), bounds(rhs.bounds), @@ -38,32 +41,48 @@ IntersectionOptimizedVertexDraw::IntersectionOptimizedVertexDraw(const Intersect { } -void vsg::IntersectionOptimizedVertexDraw::rebuild(vsg::ArrayState& arrayState) +void vsg::IntersectionProxy::rebuild(vsg::ArrayState& arrayState) { leaves.clear(); internalNodes.clear(); - arrayState.apply(*this); - uint32_t lastIndex = instanceCount > 1 ? (firstInstance + instanceCount) : firstInstance + 1; - uint32_t endVertex = firstVertex + vertexCount; + if (!original) + { + warn("Attempting to build IntersectionProxy for null node."); + return; + } // if instancing is used, accessing the nth triangle is a hassle, so grab them upfront std::vector triangles; - triangles.reserve(instanceCount * vertexCount / 3); std::vector metadata; - metadata.reserve(triangles.size()); - for (uint32_t instanceIndex = firstInstance; instanceIndex < lastIndex; ++instanceIndex) + if (auto* vertexDraw = ::cast(original)) { - if (auto vertices = arrayState.vertexArray(instanceIndex)) + arrayState.apply(*vertexDraw); + + uint32_t lastIndex = vertexDraw->instanceCount > 1 ? (vertexDraw->firstInstance + vertexDraw->instanceCount) : vertexDraw->firstInstance + 1; + uint32_t endVertex = vertexDraw->firstVertex + vertexDraw->vertexCount; + + triangles.reserve(vertexDraw->instanceCount * vertexDraw->vertexCount / 3); + metadata.reserve(triangles.size()); + + for (uint32_t instanceIndex = vertexDraw->firstInstance; instanceIndex < lastIndex; ++instanceIndex) { - for (uint32_t i = firstVertex; (i + 2) < endVertex; i += 3) + if (auto vertices = arrayState.vertexArray(instanceIndex)) { - triangles.emplace_back(Triangle{vertices->at(i), vertices->at(i + 1), vertices->at(i + 2)}); - metadata.emplace_back(TriangleMetadata{i, instanceIndex}); + for (uint32_t i = vertexDraw->firstVertex; (i + 2) < endVertex; i += 3) + { + triangles.emplace_back(Triangle{vertices->at(i), vertices->at(i + 1), vertices->at(i + 2)}); + metadata.emplace_back(TriangleMetadata{i, instanceIndex}); + } } } } + else + { + warn("Unsupported node type when building IntersectionProxy: ", original->className()); + return; + } std::vector barycenters; barycenters.reserve(triangles.size()); @@ -134,7 +153,12 @@ void vsg::IntersectionOptimizedVertexDraw::rebuild(vsg::ArrayState& arrayState) std::tie(bounds, boundingVolumeHeirarchy) = computeKDTree(indices.begin(), indices.end(), computeKDTree); } -void vsg::IntersectionOptimizedVertexDraw::intersect(LineSegmentIntersector& lineSegmentIntersector) const +bool IntersectionProxy::valid() const +{ + return boundingVolumeHeirarchy.type != NodeRef::INVALID; +} + +void vsg::IntersectionProxy::intersect(LineSegmentIntersector& lineSegmentIntersector) const { const auto& ls = lineSegmentIntersector.lineSegment(); @@ -228,41 +252,34 @@ void vsg::IntersectionOptimizedVertexDraw::intersect(LineSegmentIntersector& lin intersectNode(boundingVolumeHeirarchy, intersectNode); } -IntersectionOptimizedVertexDraw::~IntersectionOptimizedVertexDraw() = default; +IntersectionProxy::~IntersectionProxy() = default; -int IntersectionOptimizedVertexDraw::compare(const Object& rhs_object) const +int IntersectionProxy::compare(const Object& rhs_object) const { - int result = VertexDraw::compare(rhs_object); + int result = Node::compare(rhs_object); if (result != 0) return result; const auto& rhs = static_cast(rhs_object); + if ((result = compare_pointer(original, rhs.original)) != 0) return result; // computation of BVH should be deterministic, so if the input is the same and it's actually been computed, we shouldn't need to scan all the nodes if ((result = compare_value(boundingVolumeHeirarchy.type, rhs.boundingVolumeHeirarchy.type)) != 0) return result; return compare_value(boundingVolumeHeirarchy.index, boundingVolumeHeirarchy.index); } -void IntersectionOptimizedVertexDraw::read(Input& input) +void IntersectionProxy::read(Input& input) { - VertexDraw::read(input); -} + Node::read(input); -void IntersectionOptimizedVertexDraw::write(Output& output) const -{ - VertexDraw::write(output); + input.read("original", original); + // todo: deserialise BVH } -void vsg::IntersectionOptimizedVertexDraw::accept(ConstVisitor& visitor) const +void IntersectionProxy::write(Output& output) const { - if (boundingVolumeHeirarchy.type != NodeRef::INVALID && visitor.is_compatible(typeid(Intersector))) - { - auto* lineSegmentIntersector = dynamic_cast(&visitor); - if (lineSegmentIntersector) - { - intersect(*lineSegmentIntersector); - return; - } - } - Inherit::accept(visitor); + Node::write(output); + + output.write("original", original); + // todo: serialise BVH } vsg::IntersectionOptimizeVisitor::IntersectionOptimizeVisitor(ref_ptr initialArrayState) @@ -293,7 +310,7 @@ void vsg::IntersectionOptimizeVisitor::apply(StateGroup& stategroup) { if (child->className() == "vsg::VertexDraw") { - auto optimized = vsg::IntersectionOptimizedVertexDraw::create(static_cast(*child)); + auto optimized = vsg::IntersectionProxy::create(child); optimized->rebuild(*arrayStateStack.back()); child = optimized; } diff --git a/src/vsg/utils/LineSegmentIntersector.cpp b/src/vsg/utils/LineSegmentIntersector.cpp index a48405f68d..aae95b9679 100644 --- a/src/vsg/utils/LineSegmentIntersector.cpp +++ b/src/vsg/utils/LineSegmentIntersector.cpp @@ -10,6 +10,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ +#include #include #include @@ -236,6 +237,18 @@ ref_ptr LineSegmentIntersector::add(const return intersection; } +void LineSegmentIntersector::apply(const IntersectionProxy& intersectionProxy) +{ + if (intersectionProxy.valid()) + { + intersectionProxy.intersect(*this); + } + else + { + Intersector::apply(intersectionProxy); + } +} + void LineSegmentIntersector::pushTransform(const Transform& transform) { auto& l2wStack = localToWorldStack(); From b108af7e0ffbb082e6f282280ead5a40fc0f4eb2 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 9 Oct 2025 17:40:03 +0100 Subject: [PATCH 7/9] Support VertexIndexDraw --- include/vsg/nodes/IntersectionProxy.h | 4 +- src/vsg/nodes/IntersectionProxy.cpp | 77 ++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/include/vsg/nodes/IntersectionProxy.h b/include/vsg/nodes/IntersectionProxy.h index 7a7f0bdca9..6d196d1112 100644 --- a/include/vsg/nodes/IntersectionProxy.h +++ b/include/vsg/nodes/IntersectionProxy.h @@ -88,7 +88,9 @@ namespace vsg struct TriangleMetadata { - uint32_t index; + uint32_t index0; + uint32_t index1; + uint32_t index2; uint32_t instance; }; struct LeafMetadata diff --git a/src/vsg/nodes/IntersectionProxy.cpp b/src/vsg/nodes/IntersectionProxy.cpp index 97b0801690..234094500f 100644 --- a/src/vsg/nodes/IntersectionProxy.cpp +++ b/src/vsg/nodes/IntersectionProxy.cpp @@ -16,11 +16,53 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #include #include #include +#include #include #include using namespace vsg; +namespace +{ + struct GetIndicesVisitor : public ConstVisitor + { + ref_ptr ubyte_indices; + ref_ptr ushort_indices; + ref_ptr uint_indices; + + void apply(const BufferInfo& bufferInfo) override + { + bufferInfo.data->accept(*this); + } + + void apply(const ubyteArray& array) override + { + ubyte_indices = &array; + ushort_indices = nullptr; + uint_indices = nullptr; + } + void apply(const ushortArray& array) override + { + ubyte_indices = nullptr; + ushort_indices = &array; + uint_indices = nullptr; + } + void apply(const uintArray& array) override + { + ubyte_indices = nullptr; + ushort_indices = nullptr; + uint_indices = &array; + } + + uint32_t operator[](size_t index) + { + if (ubyte_indices) return ubyte_indices->at(index); + if (ushort_indices) return ushort_indices->at(index); + return uint_indices->at(index); + } + }; +} + IntersectionProxy::IntersectionProxy(Node* in_original) : Inherit(), original(in_original), @@ -73,7 +115,38 @@ void vsg::IntersectionProxy::rebuild(vsg::ArrayState& arrayState) for (uint32_t i = vertexDraw->firstVertex; (i + 2) < endVertex; i += 3) { triangles.emplace_back(Triangle{vertices->at(i), vertices->at(i + 1), vertices->at(i + 2)}); - metadata.emplace_back(TriangleMetadata{i, instanceIndex}); + metadata.emplace_back(TriangleMetadata{i, i + 1, i + 2, instanceIndex}); + } + } + } + } + else if (auto* vertexIndexDraw = ::cast(original)) + { + arrayState.apply(*vertexIndexDraw); + + uint32_t lastIndex = vertexIndexDraw->instanceCount > 1 ? (vertexIndexDraw->firstInstance + vertexIndexDraw->instanceCount) : vertexIndexDraw->firstInstance + 1; + uint32_t endIndex = vertexIndexDraw->firstIndex + ((vertexIndexDraw->indexCount + 2) / 3) * 3; + + triangles.reserve(vertexIndexDraw->instanceCount * vertexIndexDraw->indexCount / 3); + metadata.reserve(triangles.size()); + + if (!vertexIndexDraw->indices || !vertexIndexDraw->indices->data) + { + warn("Attempting to build IntersectionProxy for VertexIndexDraw with no indices."); + return; + } + + GetIndicesVisitor indices; + vertexIndexDraw->indices->accept(indices); + + for (uint32_t instanceIndex = vertexIndexDraw->firstInstance; instanceIndex < lastIndex; ++instanceIndex) + { + if (auto vertices = arrayState.vertexArray(instanceIndex)) + { + for (uint32_t i = vertexIndexDraw->firstIndex; i < endIndex; i += 3) + { + triangles.emplace_back(Triangle{vertices->at(indices[i]), vertices->at(indices[i + 1]), vertices->at(indices[i + 2])}); + metadata.emplace_back(TriangleMetadata{indices[i], indices[i + 1], indices[i + 2], instanceIndex}); } } } @@ -227,7 +300,7 @@ void vsg::IntersectionProxy::intersect(LineSegmentIntersector& lineSegmentInters dvec3 intersection = dvec3(triangle.vertex0) * double(r0) + dvec3(triangle.vertex1) * double(r1) + dvec3(triangle.vertex2) * double(r2); const auto& metadata = leafMetadata[index].tris[i]; - lineSegmentIntersector.add(intersection, double(t * inverseLength), {{metadata.index, r0}, {metadata.index + 1, r1}, {metadata.index + 2, r2}}, metadata.instance); + lineSegmentIntersector.add(intersection, double(t * inverseLength), {{metadata.index0, r0}, {metadata.index1 + 1, r1}, {metadata.index2 + 2, r2}}, metadata.instance); } }; From 13669ea67fa46d581f079a2c2906fe321146a6d7 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 10 Oct 2025 18:33:30 +0100 Subject: [PATCH 8/9] Introduce ApplyVisitorReader --- include/vsg/all.h | 1 + include/vsg/io/ApplyVisitorReader.h | 48 +++++++++++++++ src/vsg/CMakeLists.txt | 1 + src/vsg/io/ApplyVisitorReader.cpp | 93 +++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 include/vsg/io/ApplyVisitorReader.h create mode 100644 src/vsg/io/ApplyVisitorReader.cpp diff --git a/include/vsg/all.h b/include/vsg/all.h index 72bcadfbc4..c62036fac0 100644 --- a/include/vsg/all.h +++ b/include/vsg/all.h @@ -266,6 +266,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #include // Input/Output header files +#include #include #include #include diff --git a/include/vsg/io/ApplyVisitorReader.h b/include/vsg/io/ApplyVisitorReader.h new file mode 100644 index 0000000000..148d590cae --- /dev/null +++ b/include/vsg/io/ApplyVisitorReader.h @@ -0,0 +1,48 @@ +#pragma once + +/* + +Copyright(c) 2025 Chris Djali + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +#include + +namespace vsg +{ + /// Class to wrap a ReaderWriter and apply a Visitor to everything it loads + class VSG_DECLSPEC ApplyVisitorReader : public Inherit + { + public: + ApplyVisitorReader(vsg::ref_ptr in_child, vsg::ref_ptr in_visitor); + ApplyVisitorReader(const ApplyVisitorReader& rhs, const CopyOp& copyop = {}); + + vsg::ref_ptr child; + vsg::ref_ptr visitor; + + void read(Input& input) override; + void write(Output& output) const override; + + vsg::ref_ptr read(const vsg::Path& filename, vsg::ref_ptr options = {}) const override; + vsg::ref_ptr read(std::istream& fin, vsg::ref_ptr options = {}) const override; + vsg::ref_ptr read(const uint8_t* ptr, size_t size, vsg::ref_ptr options = {}) const override; + + bool write(const vsg::Object* object, const vsg::Path& filename, vsg::ref_ptr options = {}) const override; + bool write(const vsg::Object* object, std::ostream& fout, vsg::ref_ptr options = {}) const override; + + bool readOptions(vsg::Options& options, vsg::CommandLine& arguments) const override; + + bool getFeatures(Features& features) const override; + + protected: + mutable std::mutex _visitorMutex; + }; + VSG_type_name(vsg::ApplyVisitorReader); + +} // namespace vsg diff --git a/src/vsg/CMakeLists.txt b/src/vsg/CMakeLists.txt index f0e5ead6dc..4e1d0dde6a 100644 --- a/src/vsg/CMakeLists.txt +++ b/src/vsg/CMakeLists.txt @@ -154,6 +154,7 @@ set(SOURCES io/read.cpp io/write.cpp io/mem_stream.cpp + io/ApplyVisitorReader.cpp text/CpuLayoutTechnique.cpp text/GpuLayoutTechnique.cpp diff --git a/src/vsg/io/ApplyVisitorReader.cpp b/src/vsg/io/ApplyVisitorReader.cpp new file mode 100644 index 0000000000..7bbe8370d0 --- /dev/null +++ b/src/vsg/io/ApplyVisitorReader.cpp @@ -0,0 +1,93 @@ +/* + +Copyright(c) 2025 Chris Djali + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +#include + +using namespace vsg; + +vsg::ApplyVisitorReader::ApplyVisitorReader(vsg::ref_ptr in_child, vsg::ref_ptr in_visitor) : + child(in_child), + visitor(in_visitor) +{ +} + +vsg::ApplyVisitorReader::ApplyVisitorReader(const ApplyVisitorReader& rhs, const CopyOp& copyop) : + Inherit(rhs), + child(copyop(rhs.child)), + visitor(copyop(rhs.visitor)) +{ +} + +void ApplyVisitorReader::read(Input& input) +{ + input.readObject("child", child); + input.readObject("visitor", visitor); +} + +void ApplyVisitorReader::write(Output& output) const +{ + output.writeObject("child", child); + output.writeObject("visitor", visitor); +} + +vsg::ref_ptr ApplyVisitorReader::read(const vsg::Path& filename, ref_ptr options) const +{ + auto object = child->read(filename, options); + if (object) + { + std::scoped_lock lock(_visitorMutex); + object->accept(*visitor); + } + return object; +} + +vsg::ref_ptr ApplyVisitorReader::read(std::istream& fin, ref_ptr options) const +{ + auto object = child->read(fin, options); + if (object) + { + std::scoped_lock lock(_visitorMutex); + object->accept(*visitor); + } + return object; +} + +vsg::ref_ptr ApplyVisitorReader::read(const uint8_t* ptr, size_t size, vsg::ref_ptr options) const +{ + auto object = child->read(ptr, size, options); + if (object) + { + std::scoped_lock lock(_visitorMutex); + object->accept(*visitor); + } + return object; +} + +bool ApplyVisitorReader::write(const vsg::Object* object, const vsg::Path& filename, ref_ptr options) const +{ + return child->write(object, filename, options); +} + +bool ApplyVisitorReader::write(const vsg::Object* object, std::ostream& fout, vsg::ref_ptr options) const +{ + return child->write(object, fout, options); +} + +bool ApplyVisitorReader::readOptions(vsg::Options& options, vsg::CommandLine& arguments) const +{ + return child->readOptions(options, arguments); +} + +bool ApplyVisitorReader::getFeatures(Features& features) const +{ + return child->getFeatures(features); +} From 973e8225722a1e8bde3de8343afb2c4ce2603470 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 7 Nov 2025 17:59:45 +0000 Subject: [PATCH 9/9] Micro-optimise a ref_ptr copy into a move This avoids a pair of atomic operations on a hot path --- src/vsg/utils/Intersector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vsg/utils/Intersector.cpp b/src/vsg/utils/Intersector.cpp index 5199819bfc..fa9fa9d372 100644 --- a/src/vsg/utils/Intersector.cpp +++ b/src/vsg/utils/Intersector.cpp @@ -83,7 +83,7 @@ void Intersector::apply(const StateGroup& stategroup) statecommand->accept(*arrayState); } - arrayStateStack.emplace_back(arrayState); + arrayStateStack.emplace_back(std::move(arrayState)); stategroup.traverse(*this);