From efa37a7600ccd712039b0c23a0fc71b19ace148c Mon Sep 17 00:00:00 2001 From: kevyuu Date: Thu, 18 Sep 2025 13:31:56 +0700 Subject: [PATCH 01/10] Remove wage and store weighted normal instead --- include/nbl/asset/utils/CPolygonGeometryManipulator.h | 4 ++-- src/nbl/asset/utils/CSmoothNormalGenerator.cpp | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/nbl/asset/utils/CPolygonGeometryManipulator.h b/include/nbl/asset/utils/CPolygonGeometryManipulator.h index b9ee660309..247df17f03 100644 --- a/include/nbl/asset/utils/CPolygonGeometryManipulator.h +++ b/include/nbl/asset/utils/CPolygonGeometryManipulator.h @@ -22,9 +22,9 @@ class NBL_API2 CPolygonGeometryManipulator { uint32_t index; //offset of the vertex into index buffer uint32_t hash; // - float wage; //angle wage of the vertex + hlsl::float32_t3 weightedNormal; + hlsl::float32_t3 parentTriangleFaceNormal; hlsl::float32_t3 position; //position of the vertex in 3D space - hlsl::float32_t3 parentTriangleFaceNormal; // }; using VxCmpFunction = std::function; diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp index 73ac79b5ec..f9e414b143 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp @@ -219,9 +219,9 @@ CSmoothNormalGenerator::VertexHashMap CSmoothNormalGenerator::setupData(const as //set data for m_vertices const auto angleWages = getAngleWeight(v1, v2, v3); - vertices.add({ i, 0, angleWages.x, v1, faceNormal}); - vertices.add({ i + 1, 0, angleWages.y, v2, faceNormal}); - vertices.add({ i + 2, 0, angleWages.z, v3, faceNormal}); + vertices.add({ i, 0, faceNormal * angleWages.x, faceNormal, v1}); + vertices.add({ i + 1, 0, faceNormal * angleWages.y, faceNormal,v2}); + vertices.add({ i + 2, 0, faceNormal * angleWages.z, faceNormal, v3}); } vertices.validate(); @@ -260,7 +260,7 @@ core::smart_refctd_ptr CSmoothNormalGenerator::processConne for (core::vector::iterator processedVertex = processedBucket.begin; processedVertex != processedBucket.end; processedVertex++) { std::array neighboringCells = vertexHashMap.getNeighboringCellHashes(*processedVertex); - hlsl::float32_t3 normal = processedVertex->parentTriangleFaceNormal * processedVertex->wage; + hlsl::float32_t3 normal = processedVertex->weightedNormal; //iterate among all neighboring cells for (int i = 0; i < 8; i++) @@ -273,7 +273,7 @@ core::smart_refctd_ptr CSmoothNormalGenerator::processConne vxcmp(*processedVertex, *bounds.begin, polygon)) { //TODO: better mean calculation algorithm - normal += bounds.begin->parentTriangleFaceNormal * bounds.begin->wage; + normal += bounds.begin->weightedNormal; } } } @@ -373,7 +373,6 @@ core::smart_refctd_ptr CSmoothNormalGenerator::weldVertices for (core::vector::iterator processedVertex = processedBucket.begin; processedVertex != processedBucket.end; processedVertex++) { std::array neighboringCells = vertices.getNeighboringCellHashes(*processedVertex); - hlsl::float32_t3 normal = processedVertex->parentTriangleFaceNormal * processedVertex->wage; auto& groupIndex = groupIndexes[processedVertex->index]; From cc4a219905dc40b7c8b5b70c3d1c34593f969b7a Mon Sep 17 00:00:00 2001 From: kevyuu Date: Sat, 4 Oct 2025 10:30:20 +0700 Subject: [PATCH 02/10] Move getAngleWeight to hlsl/shapes/triangle.hlsl --- include/nbl/builtin/hlsl/shapes/triangle.hlsl | 43 +++++++++++++++++++ .../asset/utils/CSmoothNormalGenerator.cpp | 41 ++++-------------- src/nbl/builtin/CMakeLists.txt | 1 + 3 files changed, 52 insertions(+), 33 deletions(-) create mode 100644 include/nbl/builtin/hlsl/shapes/triangle.hlsl diff --git a/include/nbl/builtin/hlsl/shapes/triangle.hlsl b/include/nbl/builtin/hlsl/shapes/triangle.hlsl new file mode 100644 index 0000000000..2b24a6b525 --- /dev/null +++ b/include/nbl/builtin/hlsl/shapes/triangle.hlsl @@ -0,0 +1,43 @@ +// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _NBL_BUILTIN_HLSL_SHAPES_TRIANGLE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SHAPES_TRIANGLE_INCLUDED_ + +#include + +namespace nbl +{ +namespace hlsl +{ +namespace shapes +{ + +namespace util +{ + template + vector GetAngleWeight(const vector& e1, const vector& e2, const vector& e3) + { + // Calculate this triangle's weight for each of its three m_vertices + // start by calculating the lengths of its sides + const float_t a = dot(e1, e1); + const float_t asqrt = sqrt(a); + const float_t b = dot(e2, e2); + const float_t bsqrt = sqrt(b); + const float_t c = dot(e3, e3); + const float_t csqrt = sqrt(c); + + // use them to find the angle at each vertex + return vector( + acosf((b + c - a) / (2.f * bsqrt * csqrt)), + acosf((-b + c + a) / (2.f * asqrt * csqrt)), + acosf((b - c + a) / (2.f * bsqrt * asqrt))); + } +} + +} +} +} + +#endif diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp index f9e414b143..35ec4856d2 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp @@ -5,6 +5,7 @@ #include "CSmoothNormalGenerator.h" #include "nbl/core/declarations.h" +#include "nbl/builtin/hlsl/shapes/triangle.hlsl" #include #include @@ -71,40 +72,14 @@ static bool compareVertexPosition(const hlsl::float32_t3& a, const hlsl::float32 return (difference.x <= epsilon && difference.y <= epsilon && difference.z <= epsilon); } -static hlsl::float32_t3 getAngleWeight( - const hlsl::float32_t3& v1, - const hlsl::float32_t3& v2, - const hlsl::float32_t3& v3) -{ - auto distancesquared = [](const hlsl::float32_t3& v1, const hlsl::float32_t3& v2) - { - const auto diff = v1 - v2; - return hlsl::dot(diff, diff); - }; - // Calculate this triangle's weight for each of its three m_vertices - // start by calculating the lengths of its sides - const float a = distancesquared(v2, v3); - const float asqrt = sqrt(a); - const float b = distancesquared(v1,v3); - const float bsqrt = sqrt(b); - const float c = distancesquared(v1,v2); - const float csqrt = sqrt(c); - - // use them to find the angle at each vertex - return hlsl::float32_t3( - acosf((b + c - a) / (2.f * bsqrt * csqrt)), - acosf((-b + c + a) / (2.f * asqrt * csqrt)), - acosf((b - c + a) / (2.f * bsqrt * asqrt))); -} - core::smart_refctd_ptr CSmoothNormalGenerator::calculateNormals(const asset::ICPUPolygonGeometry* polygon, bool enableWelding, float epsilon, CPolygonGeometryManipulator::VxCmpFunction vxcmp) { - VertexHashMap vertexArray = setupData(polygon, epsilon); - const auto smoothPolygon = processConnectedVertices(polygon, vertexArray, epsilon,vxcmp); + VertexHashMap vertexHashMap = setupData(polygon, epsilon); + const auto smoothPolygon = processConnectedVertices(polygon, vertexHashMap, epsilon,vxcmp); if (enableWelding) { - return weldVertices(smoothPolygon.get(), vertexArray, epsilon); + return weldVertices(smoothPolygon.get(), vertexHashMap, epsilon); } return smoothPolygon; } @@ -217,11 +192,11 @@ CSmoothNormalGenerator::VertexHashMap CSmoothNormalGenerator::setupData(const as const auto faceNormal = normalize(cross(v2 - v1, v3 - v1)); //set data for m_vertices - const auto angleWages = getAngleWeight(v1, v2, v3); + const auto angleWages = hlsl::shapes::util::GetAngleWeight(v2 - v3, v1 - v3, v1 - v2); - vertices.add({ i, 0, faceNormal * angleWages.x, faceNormal, v1}); - vertices.add({ i + 1, 0, faceNormal * angleWages.y, faceNormal,v2}); - vertices.add({ i + 2, 0, faceNormal * angleWages.z, faceNormal, v3}); + vertices.add({ i, 0, faceNormal * angleWages.x, v1}); + vertices.add({ i + 1, 0, faceNormal * angleWages.y,v2}); + vertices.add({ i + 2, 0, faceNormal * angleWages.z, v3}); } vertices.validate(); diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index 3b9fe1c39a..96fb60d535 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -317,6 +317,7 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/circle.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/ellipse.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/line.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/beziers.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/triangle.hlsl") # LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/ndarray_addressing.hlsl") # From c018762d8ea9d5b7723f76e800ef6d45e5e860db Mon Sep 17 00:00:00 2001 From: kevyuu Date: Sat, 4 Oct 2025 10:32:43 +0700 Subject: [PATCH 03/10] Refactor erase to resize --- src/nbl/asset/utils/CSmoothNormalGenerator.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp index 35ec4856d2..8a1dd957d8 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp @@ -152,10 +152,10 @@ void CSmoothNormalGenerator::VertexHashMap::validate() // TODO: maybe use counting sort (or big radix) and use the histogram directly for the m_buckets auto finalSortedOutput = core::radix_sort(m_vertices.data(),m_vertices.data()+oldSize,oldSize,KeyAccessor()); // TODO: optimize out the erase - if (finalSortedOutput!=m_vertices.data()) - m_vertices.erase(m_vertices.begin(),m_vertices.begin()+oldSize); + if (finalSortedOutput != m_vertices.data()) + m_vertices.erase(m_vertices.begin(), m_vertices.begin() + oldSize); else - m_vertices.erase(m_vertices.begin()+oldSize,m_vertices.end()); + m_vertices.resize(oldSize); // TODO: are `m_buckets` even begin USED!? uint16_t prevHash = m_vertices[0].hash; @@ -179,9 +179,10 @@ CSmoothNormalGenerator::VertexHashMap CSmoothNormalGenerator::setupData(const as { const size_t idxCount = polygon->getPrimitiveCount() * 3; - VertexHashMap vertices(idxCount, std::min(16u * 1024u, core::roundUpToPoT(idxCount * 1.0f / 32.0f)), epsilon == 0.0f ? 0.00001f : epsilon * 2.f); + const auto cellCount = std::max(core::roundUpToPoT((idxCount + 31) >> 5), 4); + VertexHashMap vertices(idxCount, std::min(16u * 1024u, cellCount), epsilon == 0.0f ? 0.00001f : epsilon * 2.f); - for (uint32_t i = 0; i < idxCount; i += 3) + for (uint64_t i = 0; i < idxCount; i += 3) { //calculate face normal of parent triangle hlsl::float32_t3 v1, v2, v3; @@ -317,7 +318,7 @@ core::smart_refctd_ptr CSmoothNormalGenerator::weldVertices { struct Group { - uint32_t vertex_reference_index; // index to referenced vertex in the original polygon + uint64_t vertex_reference_index; // index to referenced vertex in the original polygon }; core::vector groups; groups.reserve(vertices.getVertexCount()); From 42ccf489dccf0fc9c86ad72ae123e1e2b1e00730 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Sat, 4 Oct 2025 12:01:28 +0700 Subject: [PATCH 04/10] Remove parentTriangleFaceNormal from SSNGVertexData --- include/nbl/asset/utils/CPolygonGeometryManipulator.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/nbl/asset/utils/CPolygonGeometryManipulator.h b/include/nbl/asset/utils/CPolygonGeometryManipulator.h index 247df17f03..591b6ebfb4 100644 --- a/include/nbl/asset/utils/CPolygonGeometryManipulator.h +++ b/include/nbl/asset/utils/CPolygonGeometryManipulator.h @@ -20,10 +20,9 @@ class NBL_API2 CPolygonGeometryManipulator //vertex data needed for CSmoothNormalGenerator struct SSNGVertexData { - uint32_t index; //offset of the vertex into index buffer + uint64_t index; //offset of the vertex into index buffer uint32_t hash; // hlsl::float32_t3 weightedNormal; - hlsl::float32_t3 parentTriangleFaceNormal; hlsl::float32_t3 position; //position of the vertex in 3D space }; @@ -247,7 +246,7 @@ class NBL_API2 CPolygonGeometryManipulator VxCmpFunction vxcmp = [](const CPolygonGeometryManipulator::SSNGVertexData& v0, const CPolygonGeometryManipulator::SSNGVertexData& v1, const ICPUPolygonGeometry* buffer) { static constexpr float cosOf45Deg = 0.70710678118f; - return dot(v0.parentTriangleFaceNormal,v1.parentTriangleFaceNormal) > cosOf45Deg; + return dot(normalize(v0.weightedNormal),normalize(v1.weightedNormal)) > cosOf45Deg; }); #if 0 // TODO: REDO From 05ee8eddd57dd1943b5c63de82638828a50b5eda Mon Sep 17 00:00:00 2001 From: kevyuu Date: Sat, 4 Oct 2025 13:04:25 +0700 Subject: [PATCH 05/10] Refactor getNeighboringCells --- .../asset/utils/CSmoothNormalGenerator.cpp | 56 ++++++++++--------- src/nbl/asset/utils/CSmoothNormalGenerator.h | 3 +- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp index 8a1dd957d8..b49e7115d6 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp @@ -235,11 +235,12 @@ core::smart_refctd_ptr CSmoothNormalGenerator::processConne for (core::vector::iterator processedVertex = processedBucket.begin; processedVertex != processedBucket.end; processedVertex++) { - std::array neighboringCells = vertexHashMap.getNeighboringCellHashes(*processedVertex); + std::array neighboringCells; + const auto cellCount = vertexHashMap.getNeighboringCellHashes(neighboringCells.data(), *processedVertex); hlsl::float32_t3 normal = processedVertex->weightedNormal; //iterate among all neighboring cells - for (int i = 0; i < 8; i++) + for (int i = 0; i < cellCount; i++) { VertexHashMap::BucketBounds bounds = vertexHashMap.getBucketBoundsByHash(neighboringCells[i]); for (; bounds.begin != bounds.end; bounds.begin++) @@ -263,55 +264,55 @@ core::smart_refctd_ptr CSmoothNormalGenerator::processConne return outPolygon; } -std::array CSmoothNormalGenerator::VertexHashMap::getNeighboringCellHashes(const CPolygonGeometryManipulator::SSNGVertexData & vertex) +uint8_t CSmoothNormalGenerator::VertexHashMap::getNeighboringCellHashes(uint32_t* outNeighbours, const CPolygonGeometryManipulator::SSNGVertexData& vertex) { - std::array neighbourhood; - - hlsl::float32_t3 cellFloatCoord = vertex.position / m_cellSize - hlsl::float32_t3(0.5f); + hlsl::float32_t3 cellFloatCoord = floor(vertex.position / m_cellSize - hlsl::float32_t3(0.5f)); hlsl::uint32_t3 neighbor = hlsl::uint32_t3(static_cast(cellFloatCoord.x), static_cast(cellFloatCoord.y), static_cast(cellFloatCoord.z)); + uint8_t neighbourCount = 0; + //left bottom near - neighbourhood[0] = hash(neighbor); + outNeighbours[neighbourCount] = hash(neighbor); + neighbourCount++; + + auto addUniqueNeighbour = [&neighbourCount, outNeighbours](uint32_t hashVal) + { + if (std::find(outNeighbours, outNeighbours + neighbourCount, hashVal) != outNeighbours + neighbourCount) + { + outNeighbours[neighbourCount] = hashVal; + neighbourCount++; + } + }; //right bottom near neighbor = neighbor + hlsl::uint32_t3(1, 0, 0); - neighbourhood[1] = hash(neighbor); + addUniqueNeighbour(hash(neighbor)); //right bottom far neighbor = neighbor + hlsl::uint32_t3(0, 0, 1); - neighbourhood[2] = hash(neighbor); + addUniqueNeighbour(hash(neighbor)); //left bottom far neighbor = neighbor - hlsl::uint32_t3(1, 0, 0); - neighbourhood[3] = hash(neighbor); + addUniqueNeighbour(hash(neighbor)); //left top far neighbor = neighbor + hlsl::uint32_t3(0, 1, 0); - neighbourhood[4] = hash(neighbor); + addUniqueNeighbour(hash(neighbor)); //right top far neighbor = neighbor + hlsl::uint32_t3(1, 0, 0); - neighbourhood[5] = hash(neighbor); + addUniqueNeighbour(hash(neighbor)); //righ top near neighbor = neighbor - hlsl::uint32_t3(0, 0, 1); - neighbourhood[6] = hash(neighbor); + addUniqueNeighbour(hash(neighbor)); //left top near neighbor = neighbor - hlsl::uint32_t3(1, 0, 0); - neighbourhood[7] = hash(neighbor); + addUniqueNeighbour(hash(neighbor)); - //erase duplicated hashes - for (int i = 0; i < 8; i++) - { - uint32_t currHash = neighbourhood[i]; - for (int j = i + 1; j < 8; j++) - { - if (neighbourhood[j] == currHash) - neighbourhood[j] = invalidHash; - } - } - return neighbourhood; + return neighbourCount; } core::smart_refctd_ptr CSmoothNormalGenerator::weldVertices(const ICPUPolygonGeometry* polygon, VertexHashMap& vertices, float epsilon) @@ -348,12 +349,13 @@ core::smart_refctd_ptr CSmoothNormalGenerator::weldVertices for (core::vector::iterator processedVertex = processedBucket.begin; processedVertex != processedBucket.end; processedVertex++) { - std::array neighboringCells = vertices.getNeighboringCellHashes(*processedVertex); + std::array neighboringCells; + const auto cellCount = vertices.getNeighboringCellHashes(neighboringCells.data(), *processedVertex); auto& groupIndex = groupIndexes[processedVertex->index]; //iterate among all neighboring cells - for (int i = 0; i < 8; i++) + for (int i = 0; i < cellCount; i++) { VertexHashMap::BucketBounds bounds = vertices.getBucketBoundsByHash(neighboringCells[i]); for (auto neighbourVertex_it = bounds.begin; neighbourVertex_it != bounds.end; neighbourVertex_it++) diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.h b/src/nbl/asset/utils/CSmoothNormalGenerator.h index c7d648d2d7..b7b19c4729 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.h +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.h @@ -39,8 +39,7 @@ class CSmoothNormalGenerator inline uint32_t getVertexCount() const { return m_vertices.size(); } - // - std::array getNeighboringCellHashes(const CPolygonGeometryManipulator::SSNGVertexData& vertex); + uint8_t getNeighboringCellHashes(uint32_t* outNeighbours, const CPolygonGeometryManipulator::SSNGVertexData& vertex); inline uint32_t getBucketCount() { return m_buckets.size(); } inline BucketBounds getBucketBoundsById(uint32_t index) const { return { m_buckets[index], m_buckets[index + 1] }; } From f4b8ac6b703d7d8ec227f5c50b34d0124686e230 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Sat, 4 Oct 2025 13:32:05 +0700 Subject: [PATCH 06/10] Slight improvement for readability --- src/nbl/asset/utils/CSmoothNormalGenerator.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.h b/src/nbl/asset/utils/CSmoothNormalGenerator.h index b7b19c4729..09dd0aee71 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.h +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.h @@ -22,10 +22,11 @@ class CSmoothNormalGenerator class VertexHashMap { public: + using collection_t = core::vector; struct BucketBounds { - core::vector::iterator begin; - core::vector::iterator end; + collection_t::iterator begin; + collection_t::iterator end; }; public: @@ -52,8 +53,8 @@ class CSmoothNormalGenerator static constexpr uint32_t primeNumber3 = 83492791; //holds iterators pointing to beginning of each bucket, last iterator points to m_vertices.end() - core::vector::iterator> m_buckets; - core::vector m_vertices; + core::vector m_buckets; + collection_t m_vertices; const uint32_t m_hashTableMaxSize; const float m_cellSize; From 0dd02e8f5d9ca727361c866ac2557f1ba3588f53 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Mon, 6 Oct 2025 21:15:26 +0700 Subject: [PATCH 07/10] Refactor Radix Sorter and use histogram as skip list --- examples_tests | 2 +- include/nbl/core/algorithm/radix_sort.h | 27 ++- .../asset/utils/CSmoothNormalGenerator.cpp | 174 ++++++++---------- src/nbl/asset/utils/CSmoothNormalGenerator.h | 35 +++- 4 files changed, 123 insertions(+), 115 deletions(-) diff --git a/examples_tests b/examples_tests index 03c3726b51..eeea1ebe20 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 03c3726b51fe1c5b5f9f53527d6f2a0d08dc64d5 +Subproject commit eeea1ebe20f661d1cd0dffc2fb7864d99c2218de diff --git a/include/nbl/core/algorithm/radix_sort.h b/include/nbl/core/algorithm/radix_sort.h index 749a8b8309..f0d87a60e0 100644 --- a/include/nbl/core/algorithm/radix_sort.h +++ b/include/nbl/core/algorithm/radix_sort.h @@ -38,10 +38,10 @@ constexpr int8_t find_msb(const T& a_variable) { static_assert(std::is_unsigned::value, "Variable must be unsigned"); - constexpr uint8_t number_of_bits = std::numeric_limits::digits; + constexpr int8_t number_of_bits = std::numeric_limits::digits; const std::bitset variable_bitset{a_variable}; - for (uint8_t msb = number_of_bits - 1; msb >= 0; msb--) + for (int8_t msb = number_of_bits - 1; msb >= 0; msb--) { if (variable_bitset[msb] == 1) return msb; @@ -49,12 +49,15 @@ constexpr int8_t find_msb(const T& a_variable) return -1; } + +} + template -struct RadixSorter +struct LSBSorter { _NBL_STATIC_INLINE_CONSTEXPR uint16_t histogram_bytesize = 8192u; _NBL_STATIC_INLINE_CONSTEXPR size_t histogram_size = size_t(histogram_bytesize)/sizeof(histogram_t); - _NBL_STATIC_INLINE_CONSTEXPR uint8_t radix_bits = find_msb(histogram_size); + _NBL_STATIC_INLINE_CONSTEXPR uint8_t radix_bits = impl::find_msb(histogram_size); _NBL_STATIC_INLINE_CONSTEXPR size_t last_pass = (key_bit_count-1ull)/size_t(radix_bits); _NBL_STATIC_INLINE_CONSTEXPR uint16_t radix_mask = (1u<(input,output,rangeSize,comp); } + + std::pair getHashBound(size_t key) const + { + constexpr histogram_t shift = static_cast(radix_bits * last_pass); + const auto histogramIx = (key >> shift) & radix_mask; + return { histogram[histogramIx], histogram[histogramIx + 1] }; + } + private: template inline RandomIt pass(RandomIt input, RandomIt output, const histogram_t rangeSize, const KeyAccessor& comp) @@ -91,19 +102,17 @@ struct RadixSorter alignas(sizeof(histogram_t)) histogram_t histogram[histogram_size]; }; -} - template inline RandomIt radix_sort(RandomIt input, RandomIt scratch, const size_t rangeSize, const KeyAccessor& comp) { assert(std::abs(std::distance(input,scratch))>=rangeSize); if (rangeSize(0x1ull<<16ull)) - return impl::RadixSorter()(input,scratch,static_cast(rangeSize),comp); + return LSBSorter()(input,scratch,static_cast(rangeSize),comp); if (rangeSize(0x1ull<<32ull)) - return impl::RadixSorter()(input,scratch,static_cast(rangeSize),comp); + return LSBSorter()(input,scratch,static_cast(rangeSize),comp); else - return impl::RadixSorter()(input,scratch,rangeSize,comp); + return LSBSorter()(input,scratch,rangeSize,comp); } //! Because Radix Sort needs O(2n) space and a number of passes dependant on the key length, the final sorted range can be either in `input` or `scratch` diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp index b49e7115d6..69ca08867f 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp @@ -84,14 +84,14 @@ core::smart_refctd_ptr CSmoothNormalGenerator::calculateNor return smoothPolygon; } -CSmoothNormalGenerator::VertexHashMap::VertexHashMap(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize) - :m_hashTableMaxSize(_hashTableMaxSize), - m_cellSize(_cellSize) +CSmoothNormalGenerator::VertexHashMap::VertexHashMap(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize) : + m_sorter(createSorter(_vertexCount)), + m_hashTableMaxSize(_hashTableMaxSize), + m_cellSize(_cellSize) { - assert((core::isPoT(m_hashTableMaxSize))); + assert((core::isPoT(m_hashTableMaxSize))); - m_vertices.reserve(_vertexCount); - m_buckets.reserve(_hashTableMaxSize + 1); + m_vertices.reserve(_vertexCount); } uint32_t CSmoothNormalGenerator::VertexHashMap::hash(const CPolygonGeometryManipulator::SSNGVertexData & vertex) const @@ -121,8 +121,14 @@ CSmoothNormalGenerator::VertexHashMap::BucketBounds CSmoothNormalGenerator::Vert if (hash == invalidHash) return { m_vertices.end(), m_vertices.end() }; - core::vector::iterator begin = std::lower_bound(m_vertices.begin(), m_vertices.end(), hash); - core::vector::iterator end = std::upper_bound(m_vertices.begin(), m_vertices.end(), hash); + const auto skipListBound = std::visit([&](auto& sorter) + { + auto hashBound = sorter.getHashBound(hash); + return std::pair(m_vertices.begin() + hashBound.first, m_vertices.begin() + hashBound.second); + }, m_sorter); + + auto begin = std::lower_bound(skipListBound.first, skipListBound.second, hash); + auto end = std::upper_bound(skipListBound.first, skipListBound.second, hash); //bucket missing if (begin == m_vertices.end()) @@ -135,22 +141,12 @@ CSmoothNormalGenerator::VertexHashMap::BucketBounds CSmoothNormalGenerator::Vert return { begin, end }; } -struct KeyAccessor -{ - _NBL_STATIC_INLINE_CONSTEXPR size_t key_bit_count = 32ull; - - template - inline decltype(radix_mask) operator()(const CPolygonGeometryManipulator::SSNGVertexData& item) const - { - return static_cast(item.hash>>static_cast(bit_offset))&radix_mask; - } -}; void CSmoothNormalGenerator::VertexHashMap::validate() { const auto oldSize = m_vertices.size(); m_vertices.resize(oldSize*2u); // TODO: maybe use counting sort (or big radix) and use the histogram directly for the m_buckets - auto finalSortedOutput = core::radix_sort(m_vertices.data(),m_vertices.data()+oldSize,oldSize,KeyAccessor()); + auto finalSortedOutput = std::visit( [&](auto& sorter) { return sorter(m_vertices.data(), m_vertices.data() + oldSize, oldSize, KeyAccessor()); },m_sorter ); // TODO: optimize out the erase if (finalSortedOutput != m_vertices.data()) m_vertices.erase(m_vertices.begin(), m_vertices.begin() + oldSize); @@ -160,19 +156,6 @@ void CSmoothNormalGenerator::VertexHashMap::validate() // TODO: are `m_buckets` even begin USED!? uint16_t prevHash = m_vertices[0].hash; core::vector::iterator prevBegin = m_vertices.begin(); - m_buckets.push_back(prevBegin); - - while (true) - { - core::vector::iterator next = std::upper_bound(prevBegin, m_vertices.end(), prevHash); - m_buckets.push_back(next); - - if (next == m_vertices.end()) - break; - - prevBegin = next; - prevHash = next->hash; - } } CSmoothNormalGenerator::VertexHashMap CSmoothNormalGenerator::setupData(const asset::ICPUPolygonGeometry* polygon, float epsilon) @@ -229,35 +212,31 @@ core::smart_refctd_ptr CSmoothNormalGenerator::processConne auto* normalPtr = reinterpret_cast(outPolygon->getNormalPtr()); auto normalStride = outPolygon->getNormalView().composed.stride; - for (uint32_t cell = 0; cell < vertexHashMap.getBucketCount() - 1; cell++) - { - VertexHashMap::BucketBounds processedBucket = vertexHashMap.getBucketBoundsById(cell); - for (core::vector::iterator processedVertex = processedBucket.begin; processedVertex != processedBucket.end; processedVertex++) - { - std::array neighboringCells; - const auto cellCount = vertexHashMap.getNeighboringCellHashes(neighboringCells.data(), *processedVertex); - hlsl::float32_t3 normal = processedVertex->weightedNormal; + for (auto processedVertex = vertexHashMap.vertices().begin(); processedVertex != vertexHashMap.vertices().end(); processedVertex++) + { + std::array neighboringCells; + const auto cellCount = vertexHashMap.getNeighboringCellHashes(neighboringCells.data(), *processedVertex); + hlsl::float32_t3 normal = processedVertex->weightedNormal; - //iterate among all neighboring cells - for (int i = 0; i < cellCount; i++) - { - VertexHashMap::BucketBounds bounds = vertexHashMap.getBucketBoundsByHash(neighboringCells[i]); - for (; bounds.begin != bounds.end; bounds.begin++) - { - if (processedVertex != bounds.begin) - if (compareVertexPosition(processedVertex->position, bounds.begin->position, epsilon) && - vxcmp(*processedVertex, *bounds.begin, polygon)) - { - //TODO: better mean calculation algorithm - normal += bounds.begin->weightedNormal; - } - } - } - normal = normalize(normal); - memcpy(normalPtr + (normalStride * processedVertex->index), &normal, sizeof(normal)); - } - } + //iterate among all neighboring cells + for (uint8_t i = 0; i < cellCount; i++) + { + VertexHashMap::BucketBounds bounds = vertexHashMap.getBucketBoundsByHash(neighboringCells[i]); + for (; bounds.begin != bounds.end; bounds.begin++) + { + if (processedVertex != bounds.begin) + if (compareVertexPosition(processedVertex->position, bounds.begin->position, epsilon) && + vxcmp(*processedVertex, *bounds.begin, polygon)) + { + //TODO: better mean calculation algorithm + normal += bounds.begin->weightedNormal; + } + } + } + normal = normalize(normal); + memcpy(normalPtr + (normalStride * processedVertex->index), &normal, sizeof(normal)); + } CPolygonGeometryManipulator::recomputeContentHashes(outPolygon.get()); @@ -343,49 +322,44 @@ core::smart_refctd_ptr CSmoothNormalGenerator::weldVertices return true; }; - for (uint32_t cell = 0; cell < vertices.getBucketCount() - 1; cell++) - { - VertexHashMap::BucketBounds processedBucket = vertices.getBucketBoundsById(cell); - - for (core::vector::iterator processedVertex = processedBucket.begin; processedVertex != processedBucket.end; processedVertex++) - { - std::array neighboringCells; - const auto cellCount = vertices.getNeighboringCellHashes(neighboringCells.data(), *processedVertex); + for (auto processedVertex = vertices.vertices().begin(); processedVertex != vertices.vertices().end(); processedVertex++) + { + std::array neighboringCells; + const auto cellCount = vertices.getNeighboringCellHashes(neighboringCells.data(), *processedVertex); - auto& groupIndex = groupIndexes[processedVertex->index]; + auto& groupIndex = groupIndexes[processedVertex->index]; - //iterate among all neighboring cells - for (int i = 0; i < cellCount; i++) - { - VertexHashMap::BucketBounds bounds = vertices.getBucketBoundsByHash(neighboringCells[i]); - for (auto neighbourVertex_it = bounds.begin; neighbourVertex_it != bounds.end; neighbourVertex_it++) - { - const auto neighbourGroupIndex = groupIndexes[neighbourVertex_it->index]; - - hlsl::float32_t3 normal1, normal2; - polygon->getNormalView().decodeElement(processedVertex->index, normal1); - polygon->getNormalView().decodeElement(neighbourVertex_it->index, normal2); - - hlsl::float32_t3 position1, position2; - polygon->getPositionView().decodeElement(processedVertex->index, position1); - polygon->getPositionView().decodeElement(neighbourVertex_it->index, position2); - - // find the first group that this vertex can join - if (processedVertex != neighbourVertex_it && neighbourGroupIndex && canJoinVertices(processedVertex->index, neighbourVertex_it->index)) - { - groupIndex = neighbourGroupIndex; - break; - } - } - } - if (!groupIndex) - { - // create new group if no group nearby that is compatible with this vertex - groupIndex = groups.size(); - groups.push_back({ processedVertex->index}); - } - } - } + //iterate among all neighboring cells + for (int i = 0; i < cellCount; i++) + { + VertexHashMap::BucketBounds bounds = vertices.getBucketBoundsByHash(neighboringCells[i]); + for (auto neighbourVertex_it = bounds.begin; neighbourVertex_it != bounds.end; neighbourVertex_it++) + { + const auto neighbourGroupIndex = groupIndexes[neighbourVertex_it->index]; + + hlsl::float32_t3 normal1, normal2; + polygon->getNormalView().decodeElement(processedVertex->index, normal1); + polygon->getNormalView().decodeElement(neighbourVertex_it->index, normal2); + + hlsl::float32_t3 position1, position2; + polygon->getPositionView().decodeElement(processedVertex->index, position1); + polygon->getPositionView().decodeElement(neighbourVertex_it->index, position2); + + // find the first group that this vertex can join + if (processedVertex != neighbourVertex_it && neighbourGroupIndex && canJoinVertices(processedVertex->index, neighbourVertex_it->index)) + { + groupIndex = neighbourGroupIndex; + break; + } + } + } + if (!groupIndex) + { + // create new group if no group nearby that is compatible with this vertex + groupIndex = groups.size(); + groups.push_back({ processedVertex->index}); + } + } auto outPolygon = core::move_and_static_cast(polygon->clone(0u)); outPolygon->setIndexing(IPolygonGeometryBase::TriangleList()); diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.h b/src/nbl/asset/utils/CSmoothNormalGenerator.h index 09dd0aee71..fbfea665d4 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.h +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.h @@ -29,6 +29,7 @@ class CSmoothNormalGenerator collection_t::iterator end; }; + public: VertexHashMap(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize); @@ -40,20 +41,44 @@ class CSmoothNormalGenerator inline uint32_t getVertexCount() const { return m_vertices.size(); } - uint8_t getNeighboringCellHashes(uint32_t* outNeighbours, const CPolygonGeometryManipulator::SSNGVertexData& vertex); + uint8_t getNeighboringCellHashes(uint32_t* outNeighbours, const CPolygonGeometryManipulator::SSNGVertexData& vertex); - inline uint32_t getBucketCount() { return m_buckets.size(); } - inline BucketBounds getBucketBoundsById(uint32_t index) const { return { m_buckets[index], m_buckets[index + 1] }; } BucketBounds getBucketBoundsByHash(uint32_t hash); + const collection_t& vertices() const { return m_vertices; } + private: + struct KeyAccessor + { + _NBL_STATIC_INLINE_CONSTEXPR size_t key_bit_count = 32ull; + + template + inline decltype(radix_mask) operator()(const CPolygonGeometryManipulator::SSNGVertexData& item) const + { + return static_cast(item.hash>>static_cast(bit_offset))&radix_mask; + } + }; + static constexpr uint32_t invalidHash = 0xFFFFFFFF; static constexpr uint32_t primeNumber1 = 73856093; static constexpr uint32_t primeNumber2 = 19349663; static constexpr uint32_t primeNumber3 = 83492791; - //holds iterators pointing to beginning of each bucket, last iterator points to m_vertices.end() - core::vector m_buckets; + using sorter_t = std::variant< + core::LSBSorter, + core::LSBSorter, + core::LSBSorter>; + sorter_t m_sorter; + + static sorter_t createSorter(size_t vertexCount) + { + if (vertexCount < (0x1ull << 16ull)) + return core::LSBSorter(); + if (vertexCount< (0x1ull << 32ull)) + return core::LSBSorter(); + return core::LSBSorter(); + } + collection_t m_vertices; const uint32_t m_hashTableMaxSize; const float m_cellSize; From ce99287fb0d3489864ceacf68cf963140af216bf Mon Sep 17 00:00:00 2001 From: kevyuu Date: Tue, 7 Oct 2025 13:46:07 +0700 Subject: [PATCH 08/10] Move VertexHashMap into its own file --- .../asset/utils/CPolygonGeometryManipulator.h | 11 +- include/nbl/asset/utils/CVertexHashMap.h | 87 +++++++++++ include/nbl/asset/utils/CVertexWelder.h | 19 +++ src/nbl/CMakeLists.txt | 1 + .../asset/utils/CSmoothNormalGenerator.cpp | 123 --------------- src/nbl/asset/utils/CSmoothNormalGenerator.h | 70 +-------- src/nbl/asset/utils/CVertexHashMap.cpp | 141 ++++++++++++++++++ 7 files changed, 252 insertions(+), 200 deletions(-) create mode 100644 include/nbl/asset/utils/CVertexHashMap.h create mode 100644 include/nbl/asset/utils/CVertexWelder.h create mode 100644 src/nbl/asset/utils/CVertexHashMap.cpp diff --git a/include/nbl/asset/utils/CPolygonGeometryManipulator.h b/include/nbl/asset/utils/CPolygonGeometryManipulator.h index 591b6ebfb4..db7125ed1c 100644 --- a/include/nbl/asset/utils/CPolygonGeometryManipulator.h +++ b/include/nbl/asset/utils/CPolygonGeometryManipulator.h @@ -9,6 +9,7 @@ #include "nbl/asset/ICPUPolygonGeometry.h" #include "nbl/asset/utils/CGeometryManipulator.h" +#include "nbl/asset/utils/CVertexHashMap.h" namespace nbl::asset { @@ -17,14 +18,8 @@ namespace nbl::asset class NBL_API2 CPolygonGeometryManipulator { public: - //vertex data needed for CSmoothNormalGenerator - struct SSNGVertexData - { - uint64_t index; //offset of the vertex into index buffer - uint32_t hash; // - hlsl::float32_t3 weightedNormal; - hlsl::float32_t3 position; //position of the vertex in 3D space - }; + + using SSNGVertexData = CVertexHashMap::VertexData; using VxCmpFunction = std::function; diff --git a/include/nbl/asset/utils/CVertexHashMap.h b/include/nbl/asset/utils/CVertexHashMap.h new file mode 100644 index 0000000000..f13847451c --- /dev/null +++ b/include/nbl/asset/utils/CVertexHashMap.h @@ -0,0 +1,87 @@ +#ifndef _NBL_ASSET_C_VERTEX_HASH_MAP_H_INCLUDED_ +#define _NBL_ASSET_C_VERTEX_HASH_MAP_H_INCLUDED_ + +#include "nbl/core/declarations.h" + +namespace nbl::asset +{ + +class CVertexHashMap +{ +public: + + struct VertexData + { + uint64_t index; //offset of the vertex into index buffer + uint32_t hash; // + hlsl::float32_t3 weightedNormal; + hlsl::float32_t3 position; //position of the vertex in 3D space + }; + + using collection_t = core::vector; + struct BucketBounds + { + collection_t::iterator begin; + collection_t::iterator end; + }; + + + CVertexHashMap(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize); + + //inserts vertex into hash table + void add(VertexData&& vertex); + + //sorts hashtable and sets iterators at beginnings of bucktes + void validate(); + + inline uint32_t getVertexCount() const { return m_vertices.size(); } + + uint8_t getNeighboringCellHashes(uint32_t* outNeighbours, const VertexData& vertex); + + BucketBounds getBucketBoundsByHash(uint32_t hash); + + const collection_t& vertices() const { return m_vertices; } + +private: + struct KeyAccessor + { + _NBL_STATIC_INLINE_CONSTEXPR size_t key_bit_count = 32ull; + + template + inline decltype(radix_mask) operator()(const VertexData& item) const + { + return static_cast(item.hash >> static_cast(bit_offset)) & radix_mask; + } + }; + + static constexpr uint32_t invalidHash = 0xFFFFFFFF; + static constexpr uint32_t primeNumber1 = 73856093; + static constexpr uint32_t primeNumber2 = 19349663; + static constexpr uint32_t primeNumber3 = 83492791; + + using sorter_t = std::variant< + core::LSBSorter, + core::LSBSorter, + core::LSBSorter>; + sorter_t m_sorter; + + static sorter_t createSorter(size_t vertexCount) + { + if (vertexCount < (0x1ull << 16ull)) + return core::LSBSorter(); + if (vertexCount < (0x1ull << 32ull)) + return core::LSBSorter(); + return core::LSBSorter(); + } + + collection_t m_vertices; + const uint32_t m_hashTableMaxSize; + const float m_cellSize; + + uint32_t hash(const VertexData& vertex) const; + uint32_t hash(const hlsl::uint32_t3& position) const; + +}; + +} +#endif \ No newline at end of file diff --git a/include/nbl/asset/utils/CVertexWelder.h b/include/nbl/asset/utils/CVertexWelder.h new file mode 100644 index 0000000000..d69ea76c18 --- /dev/null +++ b/include/nbl/asset/utils/CVertexWelder.h @@ -0,0 +1,19 @@ +// Copyright (C) 2018-2025 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h +#ifndef _NBL_ASSET_C_POLYGON_VERTEX_WELDER_H_INCLUDED_ +#define _NBL_ASSET_C_POLYGON_VERTEX_WELDER_H_INCLUDED_ + +#include "nbl/asset/utils/CPolygonGeometryManipulator.h" + +namespace nbl::asset { + +class CVertexWelder { + + template + static core::smart_refctd_ptr weldVertices(const ICPUPolygonGeometry* polygon, const AccelStructureT& vertices, float epsilon); +}; + +} + +#endif diff --git a/src/nbl/CMakeLists.txt b/src/nbl/CMakeLists.txt index 935beffe2c..7117eb910d 100755 --- a/src/nbl/CMakeLists.txt +++ b/src/nbl/CMakeLists.txt @@ -184,6 +184,7 @@ set(NBL_ASSET_SOURCES asset/utils/CPolygonGeometryManipulator.cpp asset/utils/COverdrawPolygonGeometryOptimizer.cpp asset/utils/CSmoothNormalGenerator.cpp + asset/utils/CVertexHashMap.cpp # Mesh loaders asset/interchange/COBJMeshFileLoader.cpp diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp index 69ca08867f..ef61613bbc 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp @@ -84,79 +84,6 @@ core::smart_refctd_ptr CSmoothNormalGenerator::calculateNor return smoothPolygon; } -CSmoothNormalGenerator::VertexHashMap::VertexHashMap(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize) : - m_sorter(createSorter(_vertexCount)), - m_hashTableMaxSize(_hashTableMaxSize), - m_cellSize(_cellSize) -{ - assert((core::isPoT(m_hashTableMaxSize))); - - m_vertices.reserve(_vertexCount); -} - -uint32_t CSmoothNormalGenerator::VertexHashMap::hash(const CPolygonGeometryManipulator::SSNGVertexData & vertex) const -{ - const hlsl::float32_t3 position = vertex.position / m_cellSize; - - return ((static_cast(position.x) * primeNumber1) ^ - (static_cast(position.y) * primeNumber2) ^ - (static_cast(position.z) * primeNumber3))& (m_hashTableMaxSize - 1); -} - -uint32_t CSmoothNormalGenerator::VertexHashMap::hash(const hlsl::uint32_t3& position) const -{ - return ((position.x * primeNumber1) ^ - (position.y * primeNumber2) ^ - (position.z * primeNumber3))& (m_hashTableMaxSize - 1); -} - -void CSmoothNormalGenerator::VertexHashMap::add(CPolygonGeometryManipulator::SSNGVertexData && vertex) -{ - vertex.hash = hash(vertex); - m_vertices.push_back(vertex); -} - -CSmoothNormalGenerator::VertexHashMap::BucketBounds CSmoothNormalGenerator::VertexHashMap::getBucketBoundsByHash(uint32_t hash) -{ - if (hash == invalidHash) - return { m_vertices.end(), m_vertices.end() }; - - const auto skipListBound = std::visit([&](auto& sorter) - { - auto hashBound = sorter.getHashBound(hash); - return std::pair(m_vertices.begin() + hashBound.first, m_vertices.begin() + hashBound.second); - }, m_sorter); - - auto begin = std::lower_bound(skipListBound.first, skipListBound.second, hash); - auto end = std::upper_bound(skipListBound.first, skipListBound.second, hash); - - //bucket missing - if (begin == m_vertices.end()) - return { m_vertices.end(), m_vertices.end() }; - - //bucket missing - if (begin->hash != hash) - return { m_vertices.end(), m_vertices.end() }; - - return { begin, end }; -} - -void CSmoothNormalGenerator::VertexHashMap::validate() -{ - const auto oldSize = m_vertices.size(); - m_vertices.resize(oldSize*2u); - // TODO: maybe use counting sort (or big radix) and use the histogram directly for the m_buckets - auto finalSortedOutput = std::visit( [&](auto& sorter) { return sorter(m_vertices.data(), m_vertices.data() + oldSize, oldSize, KeyAccessor()); },m_sorter ); - // TODO: optimize out the erase - if (finalSortedOutput != m_vertices.data()) - m_vertices.erase(m_vertices.begin(), m_vertices.begin() + oldSize); - else - m_vertices.resize(oldSize); - - // TODO: are `m_buckets` even begin USED!? - uint16_t prevHash = m_vertices[0].hash; - core::vector::iterator prevBegin = m_vertices.begin(); -} CSmoothNormalGenerator::VertexHashMap CSmoothNormalGenerator::setupData(const asset::ICPUPolygonGeometry* polygon, float epsilon) { @@ -243,56 +170,6 @@ core::smart_refctd_ptr CSmoothNormalGenerator::processConne return outPolygon; } -uint8_t CSmoothNormalGenerator::VertexHashMap::getNeighboringCellHashes(uint32_t* outNeighbours, const CPolygonGeometryManipulator::SSNGVertexData& vertex) -{ - hlsl::float32_t3 cellFloatCoord = floor(vertex.position / m_cellSize - hlsl::float32_t3(0.5f)); - hlsl::uint32_t3 neighbor = hlsl::uint32_t3(static_cast(cellFloatCoord.x), static_cast(cellFloatCoord.y), static_cast(cellFloatCoord.z)); - - uint8_t neighbourCount = 0; - - //left bottom near - outNeighbours[neighbourCount] = hash(neighbor); - neighbourCount++; - - auto addUniqueNeighbour = [&neighbourCount, outNeighbours](uint32_t hashVal) - { - if (std::find(outNeighbours, outNeighbours + neighbourCount, hashVal) != outNeighbours + neighbourCount) - { - outNeighbours[neighbourCount] = hashVal; - neighbourCount++; - } - }; - - //right bottom near - neighbor = neighbor + hlsl::uint32_t3(1, 0, 0); - addUniqueNeighbour(hash(neighbor)); - - //right bottom far - neighbor = neighbor + hlsl::uint32_t3(0, 0, 1); - addUniqueNeighbour(hash(neighbor)); - - //left bottom far - neighbor = neighbor - hlsl::uint32_t3(1, 0, 0); - addUniqueNeighbour(hash(neighbor)); - - //left top far - neighbor = neighbor + hlsl::uint32_t3(0, 1, 0); - addUniqueNeighbour(hash(neighbor)); - - //right top far - neighbor = neighbor + hlsl::uint32_t3(1, 0, 0); - addUniqueNeighbour(hash(neighbor)); - - //righ top near - neighbor = neighbor - hlsl::uint32_t3(0, 0, 1); - addUniqueNeighbour(hash(neighbor)); - - //left top near - neighbor = neighbor - hlsl::uint32_t3(1, 0, 0); - addUniqueNeighbour(hash(neighbor)); - - return neighbourCount; -} core::smart_refctd_ptr CSmoothNormalGenerator::weldVertices(const ICPUPolygonGeometry* polygon, VertexHashMap& vertices, float epsilon) { diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.h b/src/nbl/asset/utils/CSmoothNormalGenerator.h index fbfea665d4..476d962f95 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.h +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.h @@ -19,76 +19,8 @@ class CSmoothNormalGenerator static core::smart_refctd_ptr calculateNormals(const ICPUPolygonGeometry* polygon, bool enableWelding, float epsilon, CPolygonGeometryManipulator::VxCmpFunction function); private: - class VertexHashMap - { - public: - using collection_t = core::vector; - struct BucketBounds - { - collection_t::iterator begin; - collection_t::iterator end; - }; + using VertexHashMap = CVertexHashMap; - - public: - VertexHashMap(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize); - - //inserts vertex into hash table - void add(CPolygonGeometryManipulator::SSNGVertexData&& vertex); - - //sorts hashtable and sets iterators at beginnings of bucktes - void validate(); - - inline uint32_t getVertexCount() const { return m_vertices.size(); } - - uint8_t getNeighboringCellHashes(uint32_t* outNeighbours, const CPolygonGeometryManipulator::SSNGVertexData& vertex); - - BucketBounds getBucketBoundsByHash(uint32_t hash); - - const collection_t& vertices() const { return m_vertices; } - - private: - struct KeyAccessor - { - _NBL_STATIC_INLINE_CONSTEXPR size_t key_bit_count = 32ull; - - template - inline decltype(radix_mask) operator()(const CPolygonGeometryManipulator::SSNGVertexData& item) const - { - return static_cast(item.hash>>static_cast(bit_offset))&radix_mask; - } - }; - - static constexpr uint32_t invalidHash = 0xFFFFFFFF; - static constexpr uint32_t primeNumber1 = 73856093; - static constexpr uint32_t primeNumber2 = 19349663; - static constexpr uint32_t primeNumber3 = 83492791; - - using sorter_t = std::variant< - core::LSBSorter, - core::LSBSorter, - core::LSBSorter>; - sorter_t m_sorter; - - static sorter_t createSorter(size_t vertexCount) - { - if (vertexCount < (0x1ull << 16ull)) - return core::LSBSorter(); - if (vertexCount< (0x1ull << 32ull)) - return core::LSBSorter(); - return core::LSBSorter(); - } - - collection_t m_vertices; - const uint32_t m_hashTableMaxSize; - const float m_cellSize; - - uint32_t hash(const CPolygonGeometryManipulator::SSNGVertexData& vertex) const; - uint32_t hash(const hlsl::uint32_t3& position) const; - - }; - - private: static VertexHashMap setupData(const ICPUPolygonGeometry* polygon, float epsilon); static core::smart_refctd_ptr processConnectedVertices(const ICPUPolygonGeometry* polygon, VertexHashMap& vertices, float epsilon, CPolygonGeometryManipulator::VxCmpFunction vxcmp); static core::smart_refctd_ptr weldVertices(const ICPUPolygonGeometry* polygon, VertexHashMap& vertices, float epsilon); diff --git a/src/nbl/asset/utils/CVertexHashMap.cpp b/src/nbl/asset/utils/CVertexHashMap.cpp new file mode 100644 index 0000000000..72208c2625 --- /dev/null +++ b/src/nbl/asset/utils/CVertexHashMap.cpp @@ -0,0 +1,141 @@ +#include "nbl/asset/utils/CVertexHashMap.h" + +namespace nbl::asset { + +CVertexHashMap::CVertexHashMap(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize) : + m_sorter(createSorter(_vertexCount)), + m_hashTableMaxSize(_hashTableMaxSize), + m_cellSize(_cellSize) +{ + assert((core::isPoT(m_hashTableMaxSize))); + + m_vertices.reserve(_vertexCount); +} + +uint32_t CVertexHashMap::hash(const VertexData& vertex) const +{ + const hlsl::float32_t3 position = vertex.position / m_cellSize; + + return ((static_cast(position.x) * primeNumber1) ^ + (static_cast(position.y) * primeNumber2) ^ + (static_cast(position.z) * primeNumber3))& (m_hashTableMaxSize - 1); +} + +uint32_t CVertexHashMap::hash(const hlsl::uint32_t3& position) const +{ + return ((position.x * primeNumber1) ^ + (position.y * primeNumber2) ^ + (position.z * primeNumber3))& (m_hashTableMaxSize - 1); +} + +void CVertexHashMap::add(VertexData&& vertex) +{ + vertex.hash = hash(vertex); + m_vertices.push_back(vertex); +} + +CVertexHashMap::BucketBounds CVertexHashMap::getBucketBoundsByHash(uint32_t hash) +{ + if (hash == invalidHash) + return { m_vertices.end(), m_vertices.end() }; + + const auto skipListBound = std::visit([&](auto& sorter) + { + auto hashBound = sorter.getHashBound(hash); + return std::pair(m_vertices.begin() + hashBound.first, m_vertices.begin() + hashBound.second); + }, m_sorter); + + auto begin = std::lower_bound( + skipListBound.first, + skipListBound.second, + hash, + [](const VertexData& vertex, uint32_t hash) + { + return vertex.hash < hash; + }); + + auto end = std::upper_bound( + skipListBound.first, + skipListBound.second, + hash, + [](uint32_t hash, const VertexData& vertex) + { + return hash < vertex.hash; + }); + + //bucket missing + if (begin == m_vertices.end()) + return { m_vertices.end(), m_vertices.end() }; + + //bucket missing + if (begin->hash != hash) + return { m_vertices.end(), m_vertices.end() }; + + return { begin, end }; +} + +void CVertexHashMap::validate() +{ + const auto oldSize = m_vertices.size(); + m_vertices.resize(oldSize*2u); + // TODO: maybe use counting sort (or big radix) and use the histogram directly for the m_buckets + auto finalSortedOutput = std::visit( [&](auto& sorter) { return sorter(m_vertices.data(), m_vertices.data() + oldSize, oldSize, KeyAccessor()); },m_sorter ); + // TODO: optimize out the erase + if (finalSortedOutput != m_vertices.data()) + m_vertices.erase(m_vertices.begin(), m_vertices.begin() + oldSize); + else + m_vertices.resize(oldSize); +} + +uint8_t CVertexHashMap::getNeighboringCellHashes(uint32_t* outNeighbours, const VertexData& vertex) +{ + hlsl::float32_t3 cellFloatCoord = floor(vertex.position / m_cellSize - hlsl::float32_t3(0.5f)); + hlsl::uint32_t3 neighbor = hlsl::uint32_t3(static_cast(cellFloatCoord.x), static_cast(cellFloatCoord.y), static_cast(cellFloatCoord.z)); + + uint8_t neighbourCount = 0; + + //left bottom near + outNeighbours[neighbourCount] = hash(neighbor); + neighbourCount++; + + auto addUniqueNeighbour = [&neighbourCount, outNeighbours](uint32_t hashVal) + { + if (std::find(outNeighbours, outNeighbours + neighbourCount, hashVal) != outNeighbours + neighbourCount) + { + outNeighbours[neighbourCount] = hashVal; + neighbourCount++; + } + }; + + //right bottom near + neighbor = neighbor + hlsl::uint32_t3(1, 0, 0); + addUniqueNeighbour(hash(neighbor)); + + //right bottom far + neighbor = neighbor + hlsl::uint32_t3(0, 0, 1); + addUniqueNeighbour(hash(neighbor)); + + //left bottom far + neighbor = neighbor - hlsl::uint32_t3(1, 0, 0); + addUniqueNeighbour(hash(neighbor)); + + //left top far + neighbor = neighbor + hlsl::uint32_t3(0, 1, 0); + addUniqueNeighbour(hash(neighbor)); + + //right top far + neighbor = neighbor + hlsl::uint32_t3(1, 0, 0); + addUniqueNeighbour(hash(neighbor)); + + //righ top near + neighbor = neighbor - hlsl::uint32_t3(0, 0, 1); + addUniqueNeighbour(hash(neighbor)); + + //left top near + neighbor = neighbor - hlsl::uint32_t3(1, 0, 0); + addUniqueNeighbour(hash(neighbor)); + + return neighbourCount; +} + +} \ No newline at end of file From 28f73f59a3263817b88798efbdcf41bf5e677e71 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Sat, 18 Oct 2025 14:24:26 +0700 Subject: [PATCH 09/10] Refactor smooth normal generator to use VertexHashGrid and separate weldVertices into CVertexWelder --- .../asset/utils/CPolygonGeometryManipulator.h | 27 +- include/nbl/asset/utils/CVertexHashMap.h | 87 ----- include/nbl/asset/utils/CVertexWelder.h | 118 ++++++- include/nbl/core/algorithm/radix_sort.h | 37 +- src/nbl/CMakeLists.txt | 1 - src/nbl/asset/utils/CGeometryCreator.cpp | 1 - .../utils/CPolygonGeometryManipulator.cpp | 73 +++- .../asset/utils/CSmoothNormalGenerator.cpp | 326 ++---------------- src/nbl/asset/utils/CSmoothNormalGenerator.h | 11 +- src/nbl/asset/utils/CVertexHashMap.cpp | 141 -------- 10 files changed, 288 insertions(+), 534 deletions(-) delete mode 100644 include/nbl/asset/utils/CVertexHashMap.h delete mode 100644 src/nbl/asset/utils/CVertexHashMap.cpp diff --git a/include/nbl/asset/utils/CPolygonGeometryManipulator.h b/include/nbl/asset/utils/CPolygonGeometryManipulator.h index db7125ed1c..b1182fd983 100644 --- a/include/nbl/asset/utils/CPolygonGeometryManipulator.h +++ b/include/nbl/asset/utils/CPolygonGeometryManipulator.h @@ -9,7 +9,7 @@ #include "nbl/asset/ICPUPolygonGeometry.h" #include "nbl/asset/utils/CGeometryManipulator.h" -#include "nbl/asset/utils/CVertexHashMap.h" +#include "nbl/asset/utils/CVertexHashGrid.h" namespace nbl::asset { @@ -19,7 +19,30 @@ class NBL_API2 CPolygonGeometryManipulator { public: - using SSNGVertexData = CVertexHashMap::VertexData; + struct SSNGVertexData + { + uint64_t index; //offset of the vertex into index buffer + uint32_t hash; + hlsl::float32_t3 weightedNormal; + // TODO(kevinyu): Should we separate this from SSNGVertexData, and store it in its own vector in VertexHashGrid? Similar like how hashmap work. Or keep it intrusive? + hlsl::float32_t3 position; //position of the vertex in 3D space + + hlsl::float32_t3 getPosition() const + { + return position; + } + + void setHash(uint32_t hash) + { + this->hash = hash; + } + + uint32_t getHash() const + { + return hash; + }; + + }; using VxCmpFunction = std::function; diff --git a/include/nbl/asset/utils/CVertexHashMap.h b/include/nbl/asset/utils/CVertexHashMap.h deleted file mode 100644 index f13847451c..0000000000 --- a/include/nbl/asset/utils/CVertexHashMap.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef _NBL_ASSET_C_VERTEX_HASH_MAP_H_INCLUDED_ -#define _NBL_ASSET_C_VERTEX_HASH_MAP_H_INCLUDED_ - -#include "nbl/core/declarations.h" - -namespace nbl::asset -{ - -class CVertexHashMap -{ -public: - - struct VertexData - { - uint64_t index; //offset of the vertex into index buffer - uint32_t hash; // - hlsl::float32_t3 weightedNormal; - hlsl::float32_t3 position; //position of the vertex in 3D space - }; - - using collection_t = core::vector; - struct BucketBounds - { - collection_t::iterator begin; - collection_t::iterator end; - }; - - - CVertexHashMap(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize); - - //inserts vertex into hash table - void add(VertexData&& vertex); - - //sorts hashtable and sets iterators at beginnings of bucktes - void validate(); - - inline uint32_t getVertexCount() const { return m_vertices.size(); } - - uint8_t getNeighboringCellHashes(uint32_t* outNeighbours, const VertexData& vertex); - - BucketBounds getBucketBoundsByHash(uint32_t hash); - - const collection_t& vertices() const { return m_vertices; } - -private: - struct KeyAccessor - { - _NBL_STATIC_INLINE_CONSTEXPR size_t key_bit_count = 32ull; - - template - inline decltype(radix_mask) operator()(const VertexData& item) const - { - return static_cast(item.hash >> static_cast(bit_offset)) & radix_mask; - } - }; - - static constexpr uint32_t invalidHash = 0xFFFFFFFF; - static constexpr uint32_t primeNumber1 = 73856093; - static constexpr uint32_t primeNumber2 = 19349663; - static constexpr uint32_t primeNumber3 = 83492791; - - using sorter_t = std::variant< - core::LSBSorter, - core::LSBSorter, - core::LSBSorter>; - sorter_t m_sorter; - - static sorter_t createSorter(size_t vertexCount) - { - if (vertexCount < (0x1ull << 16ull)) - return core::LSBSorter(); - if (vertexCount < (0x1ull << 32ull)) - return core::LSBSorter(); - return core::LSBSorter(); - } - - collection_t m_vertices; - const uint32_t m_hashTableMaxSize; - const float m_cellSize; - - uint32_t hash(const VertexData& vertex) const; - uint32_t hash(const hlsl::uint32_t3& position) const; - -}; - -} -#endif \ No newline at end of file diff --git a/include/nbl/asset/utils/CVertexWelder.h b/include/nbl/asset/utils/CVertexWelder.h index d69ea76c18..4b5a06a540 100644 --- a/include/nbl/asset/utils/CVertexWelder.h +++ b/include/nbl/asset/utils/CVertexWelder.h @@ -10,8 +10,124 @@ namespace nbl::asset { class CVertexWelder { +public: + using WeldPredicateFn = std::function; + template - static core::smart_refctd_ptr weldVertices(const ICPUPolygonGeometry* polygon, const AccelStructureT& vertices, float epsilon); + static core::smart_refctd_ptr weldVertices(const ICPUPolygonGeometry* polygon, const AccelStructureT& as, WeldPredicateFn shouldWeldFn) { + auto outPolygon = core::move_and_static_cast(polygon->clone(0u)); + outPolygon->setIndexing(IPolygonGeometryBase::TriangleList()); + + core::vector vertexIndexToAsIndex(as.getVertexCount()); + + for (uint64_t vertexData_i = 0u; vertexData_i < as.getVertexCount(); vertexData_i++) + { + const auto& vertexData = as.vertices()[vertexData_i]; + vertexIndexToAsIndex[vertexData.index] = vertexData.index; + } + + static constexpr auto INVALID_INDEX = std::numeric_limits::max(); + core::vector remappedVertexIndexes(as.getVertexCount()); + std::fill(remappedVertexIndexes.begin(), remappedVertexIndexes.end(), INVALID_INDEX); + + uint64_t maxRemappedIndex = 0; + // iterate by index, so that we always use the smallest index when multiple vertexes can be welded together + for (uint64_t index = 0; index < as.getVertexCount(); index++) + { + const auto asIndex = vertexIndexToAsIndex[index]; + const auto& vertexData = as.vertices()[asIndex]; + auto& remappedVertexIndex = remappedVertexIndexes[index]; + as.iterateBroadphaseCandidates(vertexData, [&, polygon, index](const typename AccelStructureT::vertex_data_t& neighbor) { + const auto neighborRemappedIndex = remappedVertexIndexes[neighbor.index]; + if (shouldWeldFn(polygon, index, neighbor.index) && neighborRemappedIndex != INVALID_INDEX) { + remappedVertexIndex = neighborRemappedIndex; + return false; + } + return true; + }); + if (remappedVertexIndex != INVALID_INDEX) { + remappedVertexIndex = vertexData.index; + maxRemappedIndex = vertexData.index; + } + } + + // TODO(kevinyu): Handle when indexBuffer is not exist + + const auto& indexView = outPolygon->getIndexView(); + if (indexView) + { + auto remappedIndexView = [&] + { + const auto bytesize = indexView.src.size; + auto indices = ICPUBuffer::create({bytesize,IBuffer::EUF_INDEX_BUFFER_BIT}); + + auto retval = indexView; + retval.src.buffer = std::move(indices); + if (retval.composed.rangeFormat == IGeometryBase::EAABBFormat::U16) + retval.composed.encodedDataRange.u16.maxVx[0] = maxRemappedIndex; + else if (retval.composed.rangeFormat == IGeometryBase::EAABBFormat::U32) + retval.composed.encodedDataRange.u32.maxVx[0] = maxRemappedIndex; + + return retval; + }(); + + + auto remappedIndexes = [&]() { + auto* indexPtr = reinterpret_cast(remappedIndexView.getPointer()); + for (uint64_t index_i = 0; index_i < polygon->getIndexCount(); index_i++) + { + hlsl::vector index; + indexView.decodeElement>(index_i, index); + IndexT remappedIndex = remappedVertexIndexes[index.x]; + indexPtr[index_i] = remappedIndex; + } + }; + + if (indexView.composed.rangeFormat == IGeometryBase::EAABBFormat::U16) { + remappedIndexes.template operator()(); + } + else if (indexView.composed.rangeFormat == IGeometryBase::EAABBFormat::U32) { + remappedIndexes.template operator()(); + } + + outPolygon->setIndexView(std::move(remappedIndexView)); + } else + { + const uint32_t indexSize = (outPolygon->getPositionView().getElementCount() - 1 < std::numeric_limits::max()) ? sizeof(uint16_t) : sizeof(uint32_t); + auto remappedIndexBuffer = ICPUBuffer::create({indexSize * outPolygon->getVertexReferenceCount(), IBuffer::EUF_INDEX_BUFFER_BIT}); + auto remappedIndexView = ICPUPolygonGeometry::SDataView{ + .composed = { + .stride = indexSize, + }, + .src = { + .offset = 0, + .size = remappedIndexBuffer->getSize(), + .buffer = std::move(remappedIndexBuffer) + } + }; + + auto fillRemappedIndex = [&](){ + auto remappedIndexBufferPtr = reinterpret_cast(remappedIndexBuffer->getPointer()); + for (uint64_t index = 0; index < outPolygon->getPositionView().getElementCount(); index++) + { + remappedIndexBufferPtr[index] = remappedVertexIndexes[index]; + } + }; + + if (indexView.composed.rangeFormat == IGeometryBase::EAABBFormat::U16) { + fillRemappedIndex.template operator()(); + } + else if (indexView.composed.rangeFormat == IGeometryBase::EAABBFormat::U32) { + fillRemappedIndex.template operator()(); + } + + outPolygon->setIndexView(std::move(remappedIndexView)); + + } + + CPolygonGeometryManipulator::recomputeContentHashes(outPolygon.get()); + return outPolygon; + } }; } diff --git a/include/nbl/core/algorithm/radix_sort.h b/include/nbl/core/algorithm/radix_sort.h index f0d87a60e0..057598963a 100644 --- a/include/nbl/core/algorithm/radix_sort.h +++ b/include/nbl/core/algorithm/radix_sort.h @@ -71,7 +71,8 @@ struct LSBSorter { constexpr histogram_t shift = static_cast(radix_bits * last_pass); const auto histogramIx = (key >> shift) & radix_mask; - return { histogram[histogramIx], histogram[histogramIx + 1] }; + const auto boundBegin = histogramIx == 0 ? 0 : histogram[histogramIx - 1]; + return { boundBegin, histogram[histogramIx] }; } private: @@ -82,21 +83,41 @@ struct LSBSorter std::fill_n(histogram,histogram_size,static_cast(0u)); // count constexpr histogram_t shift = static_cast(radix_bits*pass_ix); - for (histogram_t i=0u; i(input[i])]; // prefix sum - std::inclusive_scan(histogram,histogram+histogram_size,histogram); + std::inclusive_scan(histogram, histogram + histogram_size, histogram); // scatter - for (histogram_t i=rangeSize; i!=0u;) - { - i--; - output[--histogram[comp.template operator()(input[i])]] = input[i]; - } if constexpr (pass_ix != last_pass) + { + + for (histogram_t i = rangeSize; i != 0u;) + { + i--; + const auto& val = input[i]; + const auto& histogramIx = comp.template operator()(val); + output[--histogram[histogramIx]] = val; + } + return pass(output,input,rangeSize,comp); + } else + { + // need to preserve histogram value for the skip list, so we copy to temporary histogramArray and use that + std::array tmpHistogram; + std::copy(histogram, histogram + histogram_size, tmpHistogram.data()); + + for (histogram_t i = rangeSize; i != 0u;) + { + i--; + const auto& val = input[i]; + const auto& histogramIx = comp.template operator()(val); + output[--tmpHistogram[histogramIx]] = val; + } + return output; + } } alignas(sizeof(histogram_t)) histogram_t histogram[histogram_size]; diff --git a/src/nbl/CMakeLists.txt b/src/nbl/CMakeLists.txt index 7117eb910d..935beffe2c 100755 --- a/src/nbl/CMakeLists.txt +++ b/src/nbl/CMakeLists.txt @@ -184,7 +184,6 @@ set(NBL_ASSET_SOURCES asset/utils/CPolygonGeometryManipulator.cpp asset/utils/COverdrawPolygonGeometryOptimizer.cpp asset/utils/CSmoothNormalGenerator.cpp - asset/utils/CVertexHashMap.cpp # Mesh loaders asset/interchange/COBJMeshFileLoader.cpp diff --git a/src/nbl/asset/utils/CGeometryCreator.cpp b/src/nbl/asset/utils/CGeometryCreator.cpp index 00bc425ef5..f36ee38125 100644 --- a/src/nbl/asset/utils/CGeometryCreator.cpp +++ b/src/nbl/asset/utils/CGeometryCreator.cpp @@ -83,7 +83,6 @@ template requires(std::is_same_v || std::is_same_v) static ICPUPolygonGeometry::SDataView createIndexView(size_t indexCount, size_t maxIndex) { - const auto bytesize = sizeof(IndexT) * indexCount; auto indices = ICPUBuffer::create({bytesize,IBuffer::EUF_INDEX_BUFFER_BIT}); diff --git a/src/nbl/asset/utils/CPolygonGeometryManipulator.cpp b/src/nbl/asset/utils/CPolygonGeometryManipulator.cpp index 03b07fc22f..f3c8761b3c 100644 --- a/src/nbl/asset/utils/CPolygonGeometryManipulator.cpp +++ b/src/nbl/asset/utils/CPolygonGeometryManipulator.cpp @@ -9,6 +9,7 @@ #include #include "nbl/asset/utils/CPolygonGeometryManipulator.h" +#include "nbl/asset/utils/CVertexWelder.h" #include "nbl/asset/utils/CSmoothNormalGenerator.h" @@ -131,6 +132,53 @@ core::smart_refctd_ptr CPolygonGeometryManipulator::createU return outGeometry; } + +namespace +{ + static bool isAttributeEqual(const ICPUPolygonGeometry::SDataView& view, uint64_t index1, uint64_t index2, float epsilon) + { + if (!view) return true; + const auto channelCount = getFormatChannelCount(view.composed.format); + switch (view.composed.rangeFormat) + { + case IGeometryBase::EAABBFormat::U64: + case IGeometryBase::EAABBFormat::U32: + { + hlsl::uint64_t4 val1, val2; + view.decodeElement(index1, val1); + view.decodeElement(index2, val2); + for (auto channel_i = 0u; channel_i < channelCount; channel_i++) + if (val1[channel_i] != val2[channel_i]) return false; + break; + } + case IGeometryBase::EAABBFormat::S64: + case IGeometryBase::EAABBFormat::S32: + { + hlsl::int64_t4 val1, val2; + view.decodeElement(index1, val1); + view.decodeElement(index2, val2); + for (auto channel_i = 0u; channel_i < channelCount; channel_i++) + if (val1[channel_i] != val2[channel_i]) return false; + break; + } + default: + { + hlsl::float64_t4 val1, val2; + view.decodeElement(index1, val1); + view.decodeElement(index2, val2); + for (auto channel_i = 0u; channel_i < channelCount; channel_i++) + { + const auto diff = abs(val1[channel_i] - val2[channel_i]); + if (diff > epsilon) return false; + } + break; + } + } + return true; + } +} + + core::smart_refctd_ptr CPolygonGeometryManipulator::createSmoothVertexNormal(const ICPUPolygonGeometry* inPolygon, bool enableWelding, float epsilon, VxCmpFunction vxcmp) { if (inPolygon == nullptr) @@ -146,8 +194,31 @@ core::smart_refctd_ptr CPolygonGeometryManipulator::createS return nullptr; } - return CSmoothNormalGenerator::calculateNormals(inPolygon, enableWelding, epsilon, vxcmp); + auto canJoinVertices = [epsilon](const ICPUPolygonGeometry* polygon, uint32_t index1, uint32_t index2)-> bool +{ + if (!isAttributeEqual(polygon->getPositionView(), index1, index2, epsilon)) + return false; + if (!isAttributeEqual(polygon->getNormalView(), index1, index2, epsilon)) + return false; + for (const auto& jointWeightView : polygon->getJointWeightViews()) + { + if (!isAttributeEqual(jointWeightView.indices, index1, index2, epsilon)) return false; + if (!isAttributeEqual(jointWeightView.weights, index1, index2, epsilon)) return false; + } + for (const auto& auxAttributeView : polygon->getAuxAttributeViews()) + if (!isAttributeEqual(auxAttributeView, index1, index2, epsilon)) return false; + + return true; + }; + + auto result = CSmoothNormalGenerator::calculateNormals(inPolygon, epsilon, vxcmp); + if (enableWelding) + { + return CVertexWelder::weldVertices(result.geom.get(), result.vertexHashGrid, canJoinVertices); + } + return result.geom; } + } // end namespace nbl::asset diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp index ef61613bbc..a6067adddd 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.cpp +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.cpp @@ -8,7 +8,6 @@ #include "nbl/builtin/hlsl/shapes/triangle.hlsl" #include -#include namespace nbl { @@ -24,64 +23,17 @@ static bool operator<(const CPolygonGeometryManipulator::SSNGVertexData& lhs, ui return lhs.hash < rhs; } -static bool isAttributeEqual(const ICPUPolygonGeometry::SDataView& view, uint32_t index1, uint32_t index2, float epsilon) -{ - if (!view) return true; - const auto channelCount = getFormatChannelCount(view.composed.format); - switch (view.composed.rangeFormat) - { - case IGeometryBase::EAABBFormat::U64: - case IGeometryBase::EAABBFormat::U32: - { - hlsl::uint64_t4 val1, val2; - view.decodeElement(index1, val1); - view.decodeElement(index2, val2); - for (auto channel_i = 0u; channel_i < channelCount; channel_i++) - if (val1[channel_i] != val2[channel_i]) return false; - break; - } - case IGeometryBase::EAABBFormat::S64: - case IGeometryBase::EAABBFormat::S32: - { - hlsl::int64_t4 val1, val2; - view.decodeElement(index1, val1); - view.decodeElement(index2, val2); - for (auto channel_i = 0u; channel_i < channelCount; channel_i++) - if (val1[channel_i] != val2[channel_i]) return false; - break; - } - default: - { - hlsl::float64_t4 val1, val2; - view.decodeElement(index1, val1); - view.decodeElement(index2, val2); - for (auto channel_i = 0u; channel_i < channelCount; channel_i++) - { - const auto diff = abs(val1[channel_i] - val2[channel_i]); - if (diff > epsilon) return false; - } - break; - } - } - return true; -} - static bool compareVertexPosition(const hlsl::float32_t3& a, const hlsl::float32_t3& b, float epsilon) { const hlsl::float32_t3 difference = abs(b - a); return (difference.x <= epsilon && difference.y <= epsilon && difference.z <= epsilon); } -core::smart_refctd_ptr CSmoothNormalGenerator::calculateNormals(const asset::ICPUPolygonGeometry* polygon, bool enableWelding, float epsilon, CPolygonGeometryManipulator::VxCmpFunction vxcmp) +CSmoothNormalGenerator::Result CSmoothNormalGenerator::calculateNormals(const asset::ICPUPolygonGeometry* polygon, float epsilon, CPolygonGeometryManipulator::VxCmpFunction vxcmp) { VertexHashMap vertexHashMap = setupData(polygon, epsilon); const auto smoothPolygon = processConnectedVertices(polygon, vertexHashMap, epsilon,vxcmp); - - if (enableWelding) - { - return weldVertices(smoothPolygon.get(), vertexHashMap, epsilon); - } - return smoothPolygon; + return { vertexHashMap, smoothPolygon }; } @@ -117,256 +69,52 @@ CSmoothNormalGenerator::VertexHashMap CSmoothNormalGenerator::setupData(const as core::smart_refctd_ptr CSmoothNormalGenerator::processConnectedVertices(const asset::ICPUPolygonGeometry* polygon, VertexHashMap& vertexHashMap, float epsilon, CPolygonGeometryManipulator::VxCmpFunction vxcmp) { - auto outPolygon = core::move_and_static_cast(polygon->clone(0u)); - static constexpr auto NormalFormat = EF_R32G32B32_SFLOAT; - const auto normalFormatBytesize = asset::getTexelOrBlockBytesize(NormalFormat); - auto normalBuf = ICPUBuffer::create({ normalFormatBytesize * outPolygon->getPositionView().getElementCount()}); - auto normalView = polygon->getNormalView(); - - hlsl::shapes::AABB<4,hlsl::float32_t> aabb; - aabb.maxVx = hlsl::float32_t4(1, 1, 1, 0.f); - aabb.minVx = -aabb.maxVx; - outPolygon->setNormalView({ - .composed = { - .encodedDataRange = {.f32 = aabb}, - .stride = sizeof(hlsl::float32_t3), - .format = NormalFormat, - .rangeFormat = IGeometryBase::EAABBFormat::F32 - }, - .src = { .offset = 0, .size = normalBuf->getSize(), .buffer = std::move(normalBuf) }, - }); + auto outPolygon = core::move_and_static_cast(polygon->clone(0u)); + static constexpr auto NormalFormat = EF_R32G32B32_SFLOAT; + const auto normalFormatBytesize = asset::getTexelOrBlockBytesize(NormalFormat); + auto normalBuf = ICPUBuffer::create({ normalFormatBytesize * outPolygon->getPositionView().getElementCount()}); + auto normalView = polygon->getNormalView(); + + hlsl::shapes::AABB<4,hlsl::float32_t> aabb; + aabb.maxVx = hlsl::float32_t4(1, 1, 1, 0.f); + aabb.minVx = -aabb.maxVx; + outPolygon->setNormalView({ + .composed = { + .encodedDataRange = {.f32 = aabb}, + .stride = sizeof(hlsl::float32_t3), + .format = NormalFormat, + .rangeFormat = IGeometryBase::EAABBFormat::F32 + }, + .src = { .offset = 0, .size = normalBuf->getSize(), .buffer = std::move(normalBuf) }, + }); auto* normalPtr = reinterpret_cast(outPolygon->getNormalPtr()); auto normalStride = outPolygon->getNormalView().composed.stride; - for (auto processedVertex = vertexHashMap.vertices().begin(); processedVertex != vertexHashMap.vertices().end(); processedVertex++) - { - std::array neighboringCells; - const auto cellCount = vertexHashMap.getNeighboringCellHashes(neighboringCells.data(), *processedVertex); - hlsl::float32_t3 normal = processedVertex->weightedNormal; + for (auto& processedVertex : vertexHashMap.vertices()) + { + auto normal = processedVertex.weightedNormal; - //iterate among all neighboring cells - for (uint8_t i = 0; i < cellCount; i++) - { - VertexHashMap::BucketBounds bounds = vertexHashMap.getBucketBoundsByHash(neighboringCells[i]); - for (; bounds.begin != bounds.end; bounds.begin++) - { - if (processedVertex != bounds.begin) - if (compareVertexPosition(processedVertex->position, bounds.begin->position, epsilon) && - vxcmp(*processedVertex, *bounds.begin, polygon)) - { - //TODO: better mean calculation algorithm - normal += bounds.begin->weightedNormal; - } - } - } - normal = normalize(normal); - memcpy(normalPtr + (normalStride * processedVertex->index), &normal, sizeof(normal)); - } + vertexHashMap.iterateBroadphaseCandidates(processedVertex, [&](const VertexHashMap::vertex_data_t& candidate) + { + if (compareVertexPosition(processedVertex.position, candidate.position, epsilon) && + vxcmp(processedVertex, candidate, polygon)) + { + //TODO: better mean calculation algorithm + normal += candidate.weightedNormal; + } + return true; + }); + + normal = normalize(normal); + memcpy(normalPtr + (normalStride * processedVertex.index), &normal, sizeof(normal)); + } CPolygonGeometryManipulator::recomputeContentHashes(outPolygon.get()); return outPolygon; } - -core::smart_refctd_ptr CSmoothNormalGenerator::weldVertices(const ICPUPolygonGeometry* polygon, VertexHashMap& vertices, float epsilon) -{ - struct Group - { - uint64_t vertex_reference_index; // index to referenced vertex in the original polygon - }; - core::vector groups; - groups.reserve(vertices.getVertexCount()); - - core::vector> groupIndexes(vertices.getVertexCount()); - - auto canJoinVertices = [&](uint32_t index1, uint32_t index2)-> bool - { - if (!isAttributeEqual(polygon->getPositionView(), index1, index2, epsilon)) - return false; - if (!isAttributeEqual(polygon->getNormalView(), index1, index2, epsilon)) - return false; - for (const auto& jointWeightView : polygon->getJointWeightViews()) - { - if (!isAttributeEqual(jointWeightView.indices, index1, index2, epsilon)) return false; - if (!isAttributeEqual(jointWeightView.weights, index1, index2, epsilon)) return false; - } - for (const auto& auxAttributeView : polygon->getAuxAttributeViews()) - if (!isAttributeEqual(auxAttributeView, index1, index2, epsilon)) return false; - - return true; - }; - - for (auto processedVertex = vertices.vertices().begin(); processedVertex != vertices.vertices().end(); processedVertex++) - { - std::array neighboringCells; - const auto cellCount = vertices.getNeighboringCellHashes(neighboringCells.data(), *processedVertex); - - auto& groupIndex = groupIndexes[processedVertex->index]; - - //iterate among all neighboring cells - for (int i = 0; i < cellCount; i++) - { - VertexHashMap::BucketBounds bounds = vertices.getBucketBoundsByHash(neighboringCells[i]); - for (auto neighbourVertex_it = bounds.begin; neighbourVertex_it != bounds.end; neighbourVertex_it++) - { - const auto neighbourGroupIndex = groupIndexes[neighbourVertex_it->index]; - - hlsl::float32_t3 normal1, normal2; - polygon->getNormalView().decodeElement(processedVertex->index, normal1); - polygon->getNormalView().decodeElement(neighbourVertex_it->index, normal2); - - hlsl::float32_t3 position1, position2; - polygon->getPositionView().decodeElement(processedVertex->index, position1); - polygon->getPositionView().decodeElement(neighbourVertex_it->index, position2); - - // find the first group that this vertex can join - if (processedVertex != neighbourVertex_it && neighbourGroupIndex && canJoinVertices(processedVertex->index, neighbourVertex_it->index)) - { - groupIndex = neighbourGroupIndex; - break; - } - } - } - if (!groupIndex) - { - // create new group if no group nearby that is compatible with this vertex - groupIndex = groups.size(); - groups.push_back({ processedVertex->index}); - } - } - - auto outPolygon = core::move_and_static_cast(polygon->clone(0u)); - outPolygon->setIndexing(IPolygonGeometryBase::TriangleList()); - - const uint32_t indexSize = (groups.size() < std::numeric_limits::max()) ? sizeof(uint16_t) : sizeof(uint32_t); - auto indexBuffer = ICPUBuffer::create({indexSize * groupIndexes.size(), IBuffer::EUF_INDEX_BUFFER_BIT}); - auto indexBufferPtr = reinterpret_cast(indexBuffer->getPointer()); - auto indexView = ICPUPolygonGeometry::SDataView{ - .composed = { - .stride = indexSize, - }, - .src = { - .offset = 0, - .size = indexBuffer->getSize(), - .buffer = std::move(indexBuffer) - } - }; - if (indexSize == 2) - { - indexView.composed.encodedDataRange.u16.minVx[0] = 0; - indexView.composed.encodedDataRange.u16.maxVx[0] = groups.size() - 1; - indexView.composed.format = EF_R16_UINT; - indexView.composed.rangeFormat = IGeometryBase::EAABBFormat::U16; - } else if (indexSize == 4) - { - indexView.composed.encodedDataRange.u32.minVx[0] = 0; - indexView.composed.encodedDataRange.u32.maxVx[0] = groups.size() - 1; - indexView.composed.format = EF_R32_UINT; - indexView.composed.rangeFormat = IGeometryBase::EAABBFormat::U32; - } - - for (auto index_i = 0u; index_i < groupIndexes.size(); index_i++) - { - if (indexSize == 2) - { - uint16_t index = *groupIndexes[index_i]; - memcpy(indexBufferPtr + indexSize * index_i, &index, sizeof(index)); - } else if (indexSize == 4) - { - uint32_t index = *groupIndexes[index_i]; - memcpy(indexBufferPtr + indexSize * index_i, &index, sizeof(index)); - } - } - outPolygon->setIndexView(std::move(indexView)); - - - using position_t = hlsl::float32_t3; - constexpr auto PositionAttrSize = sizeof(position_t); - auto positionBuffer = ICPUBuffer::create({ PositionAttrSize * groups.size(), IBuffer::EUF_NONE }); - auto outPositions = reinterpret_cast(positionBuffer->getPointer()); - const auto inPositions = reinterpret_cast(polygon->getPositionView().getPointer()); - outPolygon->setPositionView({ - .composed = polygon->getPositionView().composed, - .src = {.offset = 0, .size = positionBuffer->getSize(), .buffer = std::move(positionBuffer)} - }); - - using normal_t = hlsl::float32_t3; - constexpr auto NormalAttrSize = sizeof(normal_t); - auto normalBuffer = ICPUBuffer::create({ NormalAttrSize * groups.size(), IBuffer::EUF_NONE }); - auto outNormals = reinterpret_cast(normalBuffer->getPointer()); - const auto inNormals = reinterpret_cast(polygon->getNormalView().getPointer()); - outPolygon->setNormalView({ - .composed = polygon->getNormalView().composed, - .src = {.offset = 0, .size = normalBuffer->getSize(), .buffer = std::move(normalBuffer)} - }); - - auto createOutView = [&](const ICPUPolygonGeometry::SDataView& view) - { - auto buffer = ICPUBuffer::create({ view.composed.stride * groups.size(), view.src.buffer->getUsageFlags() }); - return ICPUPolygonGeometry::SDataView{ - .composed = view.composed, - .src = {.offset = 0, .size = buffer->getSize(), .buffer = std::move(buffer)} - }; - }; - - const auto& inJointWeightViews = polygon->getJointWeightViews(); - auto* outJointWeightViews = outPolygon->getJointWeightViews(); - outJointWeightViews->resize(inJointWeightViews.size()); - for (auto jointWeightView_i = 0u; jointWeightView_i < inJointWeightViews.size(); jointWeightView_i++) - { - const auto& inJointWeightView = inJointWeightViews[jointWeightView_i]; - outJointWeightViews->operator[](jointWeightView_i).indices = createOutView(inJointWeightView.indices); - outJointWeightViews->operator[](jointWeightView_i).weights = createOutView(inJointWeightView.weights); - } - - const auto& inAuxAttributeViews = polygon->getAuxAttributeViews(); - auto* outAuxAttributeViews = outPolygon->getAuxAttributeViews(); - outAuxAttributeViews->resize(inAuxAttributeViews.size()); - for (auto auxAttributeView_i = 0u; auxAttributeView_i < inAuxAttributeViews.size(); auxAttributeView_i++) - { - const auto& inAuxAttributeView = inAuxAttributeViews[auxAttributeView_i]; - outAuxAttributeViews->operator[](auxAttributeView_i) = createOutView(inAuxAttributeView); - } - - for (auto group_i = 0u; group_i < groups.size(); group_i++) - { - const auto srcIndex = groups[group_i].vertex_reference_index; - outPositions[group_i] = inPositions[srcIndex]; - outNormals[group_i] = inPositions[srcIndex]; - - for (uint64_t jointView_i = 0u; jointView_i < polygon->getJointWeightViews().size(); jointView_i++) - { - auto& inView = polygon->getJointWeightViews()[jointView_i]; - auto& outView = outPolygon->getJointWeightViews()->operator[](jointView_i); - - const std::byte* const inJointIndices = reinterpret_cast(inView.indices.getPointer()); - const auto jointIndexSize = inView.indices.composed.stride; - std::byte* const outJointIndices = reinterpret_cast(outView.indices.getPointer()); - memcpy(outJointIndices + group_i * jointIndexSize, inJointIndices + srcIndex * jointIndexSize, jointIndexSize); - - const std::byte* const inWeights = reinterpret_cast(inView.weights.getPointer()); - const auto jointWeightSize = inView.weights.composed.stride; - std::byte* const outWeights = reinterpret_cast(outView.weights.getPointer()); - memcpy(outWeights + group_i * jointWeightSize, inWeights + srcIndex * jointWeightSize, jointWeightSize); - } - - for (auto auxView_i = 0u; auxView_i < polygon->getAuxAttributeViews().size(); auxView_i++) - { - auto& inView = polygon->getAuxAttributeViews()[auxView_i]; - auto& outView = outPolygon->getAuxAttributeViews()->operator[](auxView_i); - const auto attrSize = inView.composed.stride; - const std::byte* const inAuxs = reinterpret_cast(inView.getPointer()); - std::byte* const outAuxs = reinterpret_cast(outView.getPointer()); - memcpy(outAuxs + group_i * attrSize, inAuxs + srcIndex * attrSize, attrSize); - } - } - - CPolygonGeometryManipulator::recomputeContentHashes(outPolygon.get()); - return outPolygon; - -} } } \ No newline at end of file diff --git a/src/nbl/asset/utils/CSmoothNormalGenerator.h b/src/nbl/asset/utils/CSmoothNormalGenerator.h index 476d962f95..90c72e45ee 100644 --- a/src/nbl/asset/utils/CSmoothNormalGenerator.h +++ b/src/nbl/asset/utils/CSmoothNormalGenerator.h @@ -16,14 +16,19 @@ class CSmoothNormalGenerator CSmoothNormalGenerator() = delete; ~CSmoothNormalGenerator() = delete; - static core::smart_refctd_ptr calculateNormals(const ICPUPolygonGeometry* polygon, bool enableWelding, float epsilon, CPolygonGeometryManipulator::VxCmpFunction function); + using VertexHashMap = CVertexHashGrid; + + struct Result + { + VertexHashMap vertexHashGrid; + core::smart_refctd_ptr geom; + }; + static Result calculateNormals(const ICPUPolygonGeometry* polygon, float epsilon, CPolygonGeometryManipulator::VxCmpFunction function); private: - using VertexHashMap = CVertexHashMap; static VertexHashMap setupData(const ICPUPolygonGeometry* polygon, float epsilon); static core::smart_refctd_ptr processConnectedVertices(const ICPUPolygonGeometry* polygon, VertexHashMap& vertices, float epsilon, CPolygonGeometryManipulator::VxCmpFunction vxcmp); - static core::smart_refctd_ptr weldVertices(const ICPUPolygonGeometry* polygon, VertexHashMap& vertices, float epsilon); }; } diff --git a/src/nbl/asset/utils/CVertexHashMap.cpp b/src/nbl/asset/utils/CVertexHashMap.cpp deleted file mode 100644 index 72208c2625..0000000000 --- a/src/nbl/asset/utils/CVertexHashMap.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "nbl/asset/utils/CVertexHashMap.h" - -namespace nbl::asset { - -CVertexHashMap::CVertexHashMap(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize) : - m_sorter(createSorter(_vertexCount)), - m_hashTableMaxSize(_hashTableMaxSize), - m_cellSize(_cellSize) -{ - assert((core::isPoT(m_hashTableMaxSize))); - - m_vertices.reserve(_vertexCount); -} - -uint32_t CVertexHashMap::hash(const VertexData& vertex) const -{ - const hlsl::float32_t3 position = vertex.position / m_cellSize; - - return ((static_cast(position.x) * primeNumber1) ^ - (static_cast(position.y) * primeNumber2) ^ - (static_cast(position.z) * primeNumber3))& (m_hashTableMaxSize - 1); -} - -uint32_t CVertexHashMap::hash(const hlsl::uint32_t3& position) const -{ - return ((position.x * primeNumber1) ^ - (position.y * primeNumber2) ^ - (position.z * primeNumber3))& (m_hashTableMaxSize - 1); -} - -void CVertexHashMap::add(VertexData&& vertex) -{ - vertex.hash = hash(vertex); - m_vertices.push_back(vertex); -} - -CVertexHashMap::BucketBounds CVertexHashMap::getBucketBoundsByHash(uint32_t hash) -{ - if (hash == invalidHash) - return { m_vertices.end(), m_vertices.end() }; - - const auto skipListBound = std::visit([&](auto& sorter) - { - auto hashBound = sorter.getHashBound(hash); - return std::pair(m_vertices.begin() + hashBound.first, m_vertices.begin() + hashBound.second); - }, m_sorter); - - auto begin = std::lower_bound( - skipListBound.first, - skipListBound.second, - hash, - [](const VertexData& vertex, uint32_t hash) - { - return vertex.hash < hash; - }); - - auto end = std::upper_bound( - skipListBound.first, - skipListBound.second, - hash, - [](uint32_t hash, const VertexData& vertex) - { - return hash < vertex.hash; - }); - - //bucket missing - if (begin == m_vertices.end()) - return { m_vertices.end(), m_vertices.end() }; - - //bucket missing - if (begin->hash != hash) - return { m_vertices.end(), m_vertices.end() }; - - return { begin, end }; -} - -void CVertexHashMap::validate() -{ - const auto oldSize = m_vertices.size(); - m_vertices.resize(oldSize*2u); - // TODO: maybe use counting sort (or big radix) and use the histogram directly for the m_buckets - auto finalSortedOutput = std::visit( [&](auto& sorter) { return sorter(m_vertices.data(), m_vertices.data() + oldSize, oldSize, KeyAccessor()); },m_sorter ); - // TODO: optimize out the erase - if (finalSortedOutput != m_vertices.data()) - m_vertices.erase(m_vertices.begin(), m_vertices.begin() + oldSize); - else - m_vertices.resize(oldSize); -} - -uint8_t CVertexHashMap::getNeighboringCellHashes(uint32_t* outNeighbours, const VertexData& vertex) -{ - hlsl::float32_t3 cellFloatCoord = floor(vertex.position / m_cellSize - hlsl::float32_t3(0.5f)); - hlsl::uint32_t3 neighbor = hlsl::uint32_t3(static_cast(cellFloatCoord.x), static_cast(cellFloatCoord.y), static_cast(cellFloatCoord.z)); - - uint8_t neighbourCount = 0; - - //left bottom near - outNeighbours[neighbourCount] = hash(neighbor); - neighbourCount++; - - auto addUniqueNeighbour = [&neighbourCount, outNeighbours](uint32_t hashVal) - { - if (std::find(outNeighbours, outNeighbours + neighbourCount, hashVal) != outNeighbours + neighbourCount) - { - outNeighbours[neighbourCount] = hashVal; - neighbourCount++; - } - }; - - //right bottom near - neighbor = neighbor + hlsl::uint32_t3(1, 0, 0); - addUniqueNeighbour(hash(neighbor)); - - //right bottom far - neighbor = neighbor + hlsl::uint32_t3(0, 0, 1); - addUniqueNeighbour(hash(neighbor)); - - //left bottom far - neighbor = neighbor - hlsl::uint32_t3(1, 0, 0); - addUniqueNeighbour(hash(neighbor)); - - //left top far - neighbor = neighbor + hlsl::uint32_t3(0, 1, 0); - addUniqueNeighbour(hash(neighbor)); - - //right top far - neighbor = neighbor + hlsl::uint32_t3(1, 0, 0); - addUniqueNeighbour(hash(neighbor)); - - //righ top near - neighbor = neighbor - hlsl::uint32_t3(0, 0, 1); - addUniqueNeighbour(hash(neighbor)); - - //left top near - neighbor = neighbor - hlsl::uint32_t3(1, 0, 0); - addUniqueNeighbour(hash(neighbor)); - - return neighbourCount; -} - -} \ No newline at end of file From 5acb40da363979978c04a8a367ac986b8de0f6f4 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Sat, 18 Oct 2025 14:46:16 +0700 Subject: [PATCH 10/10] Fix normal comparison to use dot instead of value by value comparison --- include/nbl/asset/utils/CVertexHashGrid.h | 212 ++++++++++++++++++ include/nbl/asset/utils/CVertexWelder.h | 2 - .../utils/CPolygonGeometryManipulator.cpp | 47 +++- 3 files changed, 252 insertions(+), 9 deletions(-) create mode 100644 include/nbl/asset/utils/CVertexHashGrid.h diff --git a/include/nbl/asset/utils/CVertexHashGrid.h b/include/nbl/asset/utils/CVertexHashGrid.h new file mode 100644 index 0000000000..b978b9f576 --- /dev/null +++ b/include/nbl/asset/utils/CVertexHashGrid.h @@ -0,0 +1,212 @@ +#ifndef _NBL_ASSET_C_VERTEX_HASH_MAP_H_INCLUDED_ +#define _NBL_ASSET_C_VERTEX_HASH_MAP_H_INCLUDED_ + +#include "nbl/core/declarations.h" + +namespace nbl::asset +{ + +template +concept HashGridVertexData = requires(T obj, T const cobj, uint32_t hash) { + { cobj.getHash() } -> std::same_as; + { obj.setHash(hash) } -> std::same_as; + { cobj.getPosition() } -> std::same_as; +}; + +template +class CVertexHashGrid +{ +public: + + using vertex_data_t = VertexData; + using collection_t = core::vector; + struct BucketBounds + { + collection_t::const_iterator begin; + collection_t::const_iterator end; + }; + + CVertexHashGrid(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize) : + m_sorter(createSorter(_vertexCount)), + m_hashTableMaxSize(_hashTableMaxSize), + m_cellSize(_cellSize) + { + assert((core::isPoT(m_hashTableMaxSize))); + + m_vertices.reserve(_vertexCount); + } + + //inserts vertex into hash table + void add(VertexData&& vertex) + { + vertex.setHash(hash(vertex)); + m_vertices.push_back(vertex); + } + + void validate() + { + const auto oldSize = m_vertices.size(); + m_vertices.resize(oldSize*2u); + auto finalSortedOutput = std::visit( [&](auto& sorter) { return sorter(m_vertices.data(), m_vertices.data() + oldSize, oldSize, KeyAccessor()); },m_sorter ); + + if (finalSortedOutput != m_vertices.data()) + m_vertices.erase(m_vertices.begin(), m_vertices.begin() + oldSize); + else + m_vertices.resize(oldSize); + } + + const collection_t& vertices() const { return m_vertices; } + + collection_t& vertices(){ return m_vertices; } + + inline uint32_t getVertexCount() const { return m_vertices.size(); } + + template + void iterateBroadphaseCandidates(const VertexData& vertex, Fn fn) const + { + std::array neighboringCells; + const auto cellCount = getNeighboringCellHashes(neighboringCells.data(), vertex); + + //iterate among all neighboring cells + for (uint8_t i = 0; i < cellCount; i++) + { + const auto& neighborCell = neighboringCells[i]; + BucketBounds bounds = getBucketBoundsByHash(neighborCell); + for (; bounds.begin != bounds.end; bounds.begin++) + { + const vertex_data_t& neighborVertex = *bounds.begin; + if (&vertex != &neighborVertex) + if (!fn(neighborVertex)) break; + } + } + + }; + +private: + struct KeyAccessor + { + _NBL_STATIC_INLINE_CONSTEXPR size_t key_bit_count = 32ull; + + template + inline decltype(radix_mask) operator()(const VertexData& item) const + { + return static_cast(item.getHash() >> static_cast(bit_offset)) & radix_mask; + } + }; + + static constexpr uint32_t primeNumber1 = 73856093; + static constexpr uint32_t primeNumber2 = 19349663; + static constexpr uint32_t primeNumber3 = 83492791; + + static constexpr uint32_t invalidHash = 0xFFFFFFFF; + + using sorter_t = std::variant< + core::LSBSorter, + core::LSBSorter, + core::LSBSorter>; + sorter_t m_sorter; + + static sorter_t createSorter(size_t vertexCount) + { + if (vertexCount < (0x1ull << 16ull)) + return core::LSBSorter(); + if (vertexCount < (0x1ull << 32ull)) + return core::LSBSorter(); + return core::LSBSorter(); + } + + collection_t m_vertices; + const uint32_t m_hashTableMaxSize; + const float m_cellSize; + + uint32_t hash(const VertexData& vertex) const + { + const hlsl::float32_t3 position = floor(vertex.getPosition() / m_cellSize); + + return ((static_cast(position.x) * primeNumber1) ^ + (static_cast(position.y) * primeNumber2) ^ + (static_cast(position.z) * primeNumber3))& (m_hashTableMaxSize - 1); + } + + uint32_t hash(const hlsl::uint32_t3& position) const + { + return ((position.x * primeNumber1) ^ + (position.y * primeNumber2) ^ + (position.z * primeNumber3))& (m_hashTableMaxSize - 1); + } + + uint8_t getNeighboringCellHashes(uint32_t* outNeighbors, const VertexData& vertex) const + { + hlsl::float32_t3 cellfloatcoord = floor(vertex.getPosition() / m_cellSize - hlsl::float32_t3(0.5)); + hlsl::uint32_t3 baseCoord = hlsl::uint32_t3(static_cast(cellfloatcoord.x), static_cast(cellfloatcoord.y), static_cast(cellfloatcoord.z)); + + uint8_t neighborCount = 0; + + outNeighbors[neighborCount] = hash(baseCoord); + neighborCount++; + + auto addUniqueNeighbor = [&neighborCount, outNeighbors](uint32_t hashval) + { + if (std::find(outNeighbors, outNeighbors + neighborCount, hashval) == outNeighbors + neighborCount) + { + outNeighbors[neighborCount] = hashval; + neighborCount++; + } + }; + + addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(0, 0, 1))); + addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(0, 1, 0))); + addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(1, 0, 0))); + addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(1, 1, 0))); + addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(1, 0, 1))); + addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(0, 1, 1))); + addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(1, 1, 1))); + + return neighborCount; + } + + BucketBounds getBucketBoundsByHash(uint32_t hash) const + { + if (hash == invalidHash) + return { m_vertices.end(), m_vertices.end() }; + + const auto skipListBound = std::visit([&](auto& sorter) + { + auto hashBound = sorter.getHashBound(hash); + return std::pair(m_vertices.begin() + hashBound.first, m_vertices.begin() + hashBound.second); + }, m_sorter); + + auto begin = std::lower_bound( + skipListBound.first, + skipListBound.second, + hash, + [](const VertexData& vertex, uint32_t hash) + { + return vertex.hash < hash; + }); + + auto end = std::upper_bound( + skipListBound.first, + skipListBound.second, + hash, + [](uint32_t hash, const VertexData& vertex) + { + return hash < vertex.hash; + }); + + const auto beginIx = begin - m_vertices.begin(); + const auto endIx = end - m_vertices.begin(); + //bucket missing + if (begin == end) + return { m_vertices.end(), m_vertices.end() }; + + //bucket missing + if (begin->hash != hash) + return { m_vertices.end(), m_vertices.end() }; + + return { begin, end }; + } +}; + +} +#endif \ No newline at end of file diff --git a/include/nbl/asset/utils/CVertexWelder.h b/include/nbl/asset/utils/CVertexWelder.h index 4b5a06a540..54f407fdbf 100644 --- a/include/nbl/asset/utils/CVertexWelder.h +++ b/include/nbl/asset/utils/CVertexWelder.h @@ -51,8 +51,6 @@ class CVertexWelder { } } - // TODO(kevinyu): Handle when indexBuffer is not exist - const auto& indexView = outPolygon->getIndexView(); if (indexView) { diff --git a/src/nbl/asset/utils/CPolygonGeometryManipulator.cpp b/src/nbl/asset/utils/CPolygonGeometryManipulator.cpp index f3c8761b3c..859d1224a3 100644 --- a/src/nbl/asset/utils/CPolygonGeometryManipulator.cpp +++ b/src/nbl/asset/utils/CPolygonGeometryManipulator.cpp @@ -135,7 +135,7 @@ core::smart_refctd_ptr CPolygonGeometryManipulator::createU namespace { - static bool isAttributeEqual(const ICPUPolygonGeometry::SDataView& view, uint64_t index1, uint64_t index2, float epsilon) + bool isAttributeValEqual(const ICPUPolygonGeometry::SDataView& view, uint64_t index1, uint64_t index2, float epsilon) { if (!view) return true; const auto channelCount = getFormatChannelCount(view.composed.format); @@ -175,7 +175,40 @@ namespace } } return true; - } + } + + bool isAttributeDirEqual(const ICPUPolygonGeometry::SDataView& view, uint64_t index1, uint64_t index2, float epsilon) +{ + if (!view) return true; + const auto channelCount = getFormatChannelCount(view.composed.format); + switch (view.composed.rangeFormat) + { + case IGeometryBase::EAABBFormat::U64: + case IGeometryBase::EAABBFormat::U32: + { + hlsl::uint64_t4 val1, val2; + view.decodeElement(index1, val1); + view.decodeElement(index2, val2); + return (1.0 - hlsl::dot(val1, val2)) < epsilon; + } + case IGeometryBase::EAABBFormat::S64: + case IGeometryBase::EAABBFormat::S32: + { + hlsl::int64_t4 val1, val2; + view.decodeElement(index1, val1); + view.decodeElement(index2, val2); + return (1.0 - hlsl::dot(val1, val2)) < epsilon; + } + default: + { + hlsl::float64_t4 val1, val2; + view.decodeElement(index1, val1); + view.decodeElement(index2, val2); + return (1.0 - hlsl::dot(val1, val2)) < epsilon; + } + } + return true; +} } @@ -196,17 +229,17 @@ core::smart_refctd_ptr CPolygonGeometryManipulator::createS auto canJoinVertices = [epsilon](const ICPUPolygonGeometry* polygon, uint32_t index1, uint32_t index2)-> bool { - if (!isAttributeEqual(polygon->getPositionView(), index1, index2, epsilon)) + if (!isAttributeValEqual(polygon->getPositionView(), index1, index2, epsilon)) return false; - if (!isAttributeEqual(polygon->getNormalView(), index1, index2, epsilon)) + if (!isAttributeDirEqual(polygon->getNormalView(), index1, index2, epsilon)) return false; for (const auto& jointWeightView : polygon->getJointWeightViews()) { - if (!isAttributeEqual(jointWeightView.indices, index1, index2, epsilon)) return false; - if (!isAttributeEqual(jointWeightView.weights, index1, index2, epsilon)) return false; + if (!isAttributeValEqual(jointWeightView.indices, index1, index2, epsilon)) return false; + if (!isAttributeValEqual(jointWeightView.weights, index1, index2, epsilon)) return false; } for (const auto& auxAttributeView : polygon->getAuxAttributeViews()) - if (!isAttributeEqual(auxAttributeView, index1, index2, epsilon)) return false; + if (!isAttributeValEqual(auxAttributeView, index1, index2, epsilon)) return false; return true; };