From 0c67d17148ad948cc3f2ad7dacf69c20d4f4037e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 14 Jan 2012 19:31:46 +0100 Subject: [PATCH] MeshTools: implementation of Tipsify algorithm. Algorithm used: Pedro V. Sander, Diego Nehab, and Joshua Barczak, Fast Triangle Reordering for Vertex Locality and Reduced Overdraw, SIGGRAPH 2007, http://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/index.php Also created new shared MeshTools library. --- CMakeLists.txt | 1 + modules/FindMagnum.cmake | 4 +- src/MeshTools/CMakeLists.txt | 9 ++ src/MeshTools/Test/CMakeLists.txt | 2 + src/MeshTools/Test/TipsifyTest.cpp | 144 ++++++++++++++++++++++++++++ src/MeshTools/Test/TipsifyTest.h | 40 ++++++++ src/MeshTools/Tipsify.cpp | 148 +++++++++++++++++++++++++++++ src/MeshTools/Tipsify.h | 82 ++++++++++++++++ 8 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 src/MeshTools/Test/TipsifyTest.cpp create mode 100644 src/MeshTools/Test/TipsifyTest.h create mode 100644 src/MeshTools/Tipsify.cpp create mode 100644 src/MeshTools/Tipsify.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 454678b02..dbe5ce867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ find_package(Corrade REQUIRED) set_parent_scope(MAGNUM_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") set_parent_scope(MAGNUM_LIBRARY Magnum) set_parent_scope(MAGNUM_PRIMITIVES_LIBRARY MagnumPrimitives) +set_parent_scope(MAGNUM_MESHTOOLS_LIBRARY MagnumMeshTools) include(FindMagnum) add_subdirectory(modules) diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index bf8ee7339..dc85386f8 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -16,7 +16,7 @@ find_package(Corrade REQUIRED) find_package(OpenGL REQUIRED) find_package(GLEW REQUIRED) -if (MAGNUM_INCLUDE_DIR AND MAGNUM_LIBRARY AND MAGNUM_PRIMITIVES_LIBRARY) +if (MAGNUM_INCLUDE_DIR AND MAGNUM_LIBRARY AND MAGNUM_PRIMITIVES_LIBRARY AND MAGNUM_MESHTOOLS_LIBRARY) # Already in cache set(MAGNUM_FOUND TRUE) @@ -25,6 +25,7 @@ else() # Libraries find_library(MAGNUM_LIBRARY Magnum) find_library(MAGNUM_PRIMITIVES_LIBRARY MagnumPrimitives) + find_library(MAGNUM_MESHTOOLS_LIBRARY MagnumMeshTools) # Paths find_path(MAGNUM_INCLUDE_DIR @@ -37,6 +38,7 @@ else() MAGNUM_INCLUDE_DIR MAGNUM_LIBRARY MAGNUM_PRIMITIVES_LIBRARY + MAGNUM_MESHTOOLS_LIBRARY ) endif() diff --git a/src/MeshTools/CMakeLists.txt b/src/MeshTools/CMakeLists.txt index e70e0d132..0aeff3d3c 100644 --- a/src/MeshTools/CMakeLists.txt +++ b/src/MeshTools/CMakeLists.txt @@ -1,3 +1,12 @@ +set(MagnumMeshTools_SRCS + Tipsify.cpp +) + +add_library(MagnumMeshTools SHARED ${MagnumMeshTools_SRCS}) +target_link_libraries(MagnumMeshTools ${MAGNUM_LIBRARY}) + +install(TARGETS MagnumMeshTools DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}) + if(BUILD_TESTS) enable_testing() add_subdirectory(Test) diff --git a/src/MeshTools/Test/CMakeLists.txt b/src/MeshTools/Test/CMakeLists.txt index f12c60f3e..03197560e 100644 --- a/src/MeshTools/Test/CMakeLists.txt +++ b/src/MeshTools/Test/CMakeLists.txt @@ -2,3 +2,5 @@ corrade_add_test(CleanTest CleanTest.h CleanTest.cpp) corrade_add_test(SubdivideTest SubdivideTest.h SubdivideTest.cpp) corrade_add_test(SubdivideCleanBenchmark SubdivideCleanBenchmark.h SubdivideCleanBenchmark.cpp) target_link_libraries(SubdivideCleanBenchmark ${MAGNUM_PRIMITIVES_LIBRARY}) +corrade_add_test(TipsifyTest TipsifyTest.h TipsifyTest.cpp) +target_link_libraries(TipsifyTest ${MAGNUM_MESHTOOLS_LIBRARY}) diff --git a/src/MeshTools/Test/TipsifyTest.cpp b/src/MeshTools/Test/TipsifyTest.cpp new file mode 100644 index 000000000..db587a7bb --- /dev/null +++ b/src/MeshTools/Test/TipsifyTest.cpp @@ -0,0 +1,144 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "TipsifyTest.h" + +#include + +#include "MeshBuilder.h" +#include "MeshTools/Tipsify.h" + +QTEST_APPLESS_MAIN(Magnum::MeshTools::Test::TipsifyTest) + +using namespace std; + +namespace Magnum { namespace MeshTools { namespace Test { + +/* + + 0 ----- 1 ----- 2 ----- 3 + \ 0 / \ 7 / \ 2 / \ + \ / 11 \ / 13 \ / 12 \ + 4 ----- 5 ----- 6 ----- 7 + / \ 3 / \ 8 / \ 5 / + / 14 \ / 9 \ / 15 \ / + 8 ----- 9 ---- 10 ---- 11 18 ---- 17 + \ 4 / \ 1 / \ 17 / \ \ 18 / + \ / 16 \ / 10 \ / 6 \ \ / + 12 ---- 13 ---- 14 ---- 15 16 + +*/ + +TipsifyTest::TipsifyTest(QObject* parent): QObject(parent) { + unsigned int vertices[19]; /* who cares */ + static const unsigned int indices[] = { + 4, 1, 0, + 10, 9, 13, + 6, 3, 2, + 9, 5, 4, + 12, 9, 8, + 11, 7, 6, + + 14, 15, 11, + 2, 1, 5, + 10, 6, 5, + 10, 5, 9, + 13, 14, 10, + 1, 4, 5, + + 7, 3, 6, + 6, 2, 5, + 9, 4, 8, + 6, 10, 11, + 13, 9, 12, + 14, 11, 10, + + 16, 17, 18 + }; + builder.setData(vertices, indices, 19, 19*3); +} + +void TipsifyTest::buildAdjacency() { + vector liveTriangleCount, neighborOffset, neighbors; + Tipsify(builder).buildAdjacency(liveTriangleCount, neighborOffset, neighbors); + + QVERIFY((liveTriangleCount == vector{ + 1, 3, 3, 2, + 4, 6, 6, 2, + 2, 6, 6, 4, + 2, 3, 3, 1, + 1, 1, 1 + })); + + QVERIFY((neighborOffset == vector{ + 0, 1, 4, 7, + 9, 13, 19, 25, + 27, 29, 35, 41, + 45, 47, 50, 53, + 54, 55, 56, 57 + })); + + QVERIFY((neighbors == vector{ + 0, + 0, 7, 11, + 2, 7, 13, + 2, 12, + + 0, 3, 11, 14, + 3, 7, 8, 9, 11, 13, + 2, 5, 8, 12, 13, 15, + 5, 12, + + 4, 14, + 1, 3, 4, 9, 14, 16, + 1, 8, 9, 10, 15, 17, + 5, 6, 15, 17, + + 4, 16, + 1, 10, 16, + 6, 10, 17, + 6, + + 18, 18, 18 + })); +} + +void TipsifyTest::tipsify() { + MeshTools::tipsify(builder, 3); + + QVERIFY((builder.indices() == vector{ + 4, 1, 0, + 9, 5, 4, + 1, 4, 5, + 9, 4, 8, + 12, 9, 8, + 13, 9, 12, + 10, 9, 13, + 13, 14, 10, + 10, 6, 5, + 10, 5, 9, + 6, 10, 11, + 14, 11, 10, + 6, 3, 2, + 11, 7, 6, + 7, 3, 6, + 6, 2, 5, + 2, 1, 5, + 14, 15, 11, /* from dead-end vertex stack */ + 16, 17, 18 /* arbitrary vertex */ + })); +} + +}}} diff --git a/src/MeshTools/Test/TipsifyTest.h b/src/MeshTools/Test/TipsifyTest.h new file mode 100644 index 000000000..12b2c0047 --- /dev/null +++ b/src/MeshTools/Test/TipsifyTest.h @@ -0,0 +1,40 @@ +#ifndef Magnum_MeshTools_Test_TipsifyTest_h +#define Magnum_MeshTools_Test_TipsifyTest_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include + +#include "MeshBuilder.h" + +namespace Magnum { namespace MeshTools { namespace Test { + +class TipsifyTest: public QObject { + Q_OBJECT + + public: + explicit TipsifyTest(QObject* parent = nullptr); + + private slots: + void buildAdjacency(); + void tipsify(); + + private: + MeshBuilder builder; +}; + +}}} + +#endif diff --git a/src/MeshTools/Tipsify.cpp b/src/MeshTools/Tipsify.cpp new file mode 100644 index 000000000..01f3789b7 --- /dev/null +++ b/src/MeshTools/Tipsify.cpp @@ -0,0 +1,148 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "Tipsify.h" + +#include + +namespace Magnum { namespace MeshTools { + +void Tipsify::run(size_t cacheSize) { + /* Neighboring triangles for each vertex, per-vertex live triangle count */ + std::vector liveTriangleCount, neighborPosition, neighbors; + buildAdjacency(liveTriangleCount, neighborPosition, neighbors); + + /* Global time, per-vertex caching timestamps, per-triangle emmited flag */ + unsigned int time = cacheSize+1; + std::vector timestamp(vertexCount); + std::vector emitted(indices.size()/3); + + /* Dead-end vertex stack */ + std::stack deadEndStack; + + /* Output index buffer */ + std::vector outputIndices; + outputIndices.reserve(indices.size()); + + /* Starting vertex for fanning, cursor */ + unsigned int fanningVertex = 0; + unsigned int i = 0; + while(fanningVertex != 0xFFFFFFFFu) { + /* Array with candidates for next fanning vertex (in 1-ring around + fanning vertex) */ + std::vector candidates; + + /* For all neighbors of fanning vertex */ + for(unsigned int ti = neighborPosition[fanningVertex], t = neighbors[ti]; ti != neighborPosition[fanningVertex+1]; t = neighbors[++ti]) { + /* Continue if already emitted */ + if(emitted[t]) continue; + emitted[t] = true; + + /* Write all vertices of the triangle to output buffer */ + for(unsigned int vi = 0, v = indices[t*3]; vi != 3; v = indices[++vi+t*3]) { + outputIndices.push_back(v); + + /* Add to dead end stack and candidates array */ + /** @todo Limit size of dead end stack to cache size */ + deadEndStack.push(v); + candidates.push_back(v); + + /* Decrease live triangle count */ + --liveTriangleCount[v]; + + /* If not in cache, set timestamp */ + if(time-timestamp[v] > cacheSize) + timestamp[v] = time++; + } + } + + /* Get next fanning vertex */ + fanningVertex = 0xFFFFFFFFu; + + /* Go through candidates in 1-ring around fanning vertex */ + int candidatePriority = -1; + for(unsigned int v: candidates) { + /* Skip if it doesn't have any live triangles */ + if(!liveTriangleCount[v]) continue; + + /* Get most fresh candidate which will still be in cache even + after fanning. Every fanned triangle will generate at most + two cache misses, thus 2*liveTriangleCount */ + int priority = 0; + if(time-timestamp[v]+2*liveTriangleCount[v] <= cacheSize) + priority = time-timestamp[v]; + if(priority > candidatePriority) { + fanningVertex = v; + candidatePriority = priority; + } + } + + /* On dead-end */ + if(fanningVertex == 0xFFFFFFFFu) { + /* Find vertex with live triangles in dead-end stack */ + while(!deadEndStack.empty()) { + unsigned int d = deadEndStack.top(); + deadEndStack.pop(); + + if(!liveTriangleCount[d]) continue; + fanningVertex = d; + break; + } + + /* If not found, find next artbitrary vertex with live + triangles */ + while(++i < vertexCount) { + if(!liveTriangleCount[i]) continue; + + fanningVertex = i; + break; + } + } + } + + /* Swap original index buffer with optimized */ + std::swap(indices, outputIndices); +} + +void Tipsify::buildAdjacency(std::vector& liveTriangleCount, std::vector& neighborOffset, std::vector& neighbors) const { + /* How many times is each vertex referenced == count of neighboring + triangles for each vertex */ + liveTriangleCount.clear(); + liveTriangleCount.resize(vertexCount); + for(unsigned int i = 0; i != indices.size(); ++i) + ++liveTriangleCount[indices[i]]; + + /* Building offset array from counts. Neighbors for i-th vertex will at + the end be in interval neighbors[neighborOffset[i]] ; + neighbors[neighborOffset[i+1]]. Currently the values are shifted to + right, because the next loop will shift them back left. */ + neighborOffset.clear(); + neighborOffset.reserve(vertexCount+1); + neighborOffset.push_back(0); + unsigned int sum = 0; + for(unsigned int i = 0; i != vertexCount; ++i) { + neighborOffset.push_back(sum); + sum += liveTriangleCount[i]; + } + + /* Array of neighbors, using (and changing) neighborOffset array for + positioning */ + neighbors.clear(); + neighbors.resize(sum); + for(unsigned int i = 0; i != indices.size(); ++i) + neighbors[neighborOffset[indices[i]+1]++] = i/3; +} + +}} diff --git a/src/MeshTools/Tipsify.h b/src/MeshTools/Tipsify.h new file mode 100644 index 000000000..bd38c6168 --- /dev/null +++ b/src/MeshTools/Tipsify.h @@ -0,0 +1,82 @@ +#ifndef Magnum_MeshTools_Tipsify_h +#define Magnum_MeshTools_Tipsify_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::MeshTools::Tipsify + */ + +#include "AbstractTool.h" + +namespace Magnum { namespace MeshTools { + +/** +@brief %Mesh tipsifier implementation + +See tipsify() for full documentation. +*/ +class Tipsify: public AbstractIndexTool { + public: + /** @copydoc AbstractIndexTool::AbstractIndexTool() */ + template inline Tipsify(MeshBuilder& builder): AbstractIndexTool(builder) {} + + /** + * @brief Functor + * + * See tipsify() for full documentation. + */ + void run(size_t cacheSize); + + /** + * @brief Build vertex-triangle adjacency + * + * Computes count and indices of adjacent triangles for each vertex + * (used internally). + */ + void buildAdjacency(std::vector& liveTriangleCount, std::vector& neighborOffset, std::vector& neighbors) const; +}; + +/** +@brief %Tipsify the mesh +@tparam Vertex Vertex data type (the same as in MeshBuilder) +@param builder %Mesh builder to operate on +@param cacheSize Post-transform vertex cache size + +Optimizes the mesh for vertex-bound applications by rearranging its index +array for beter usage of post-transform vertex cache. Algorithm used: +Pedro V. Sander, Diego Nehab, and Joshua Barczak, +Fast +Triangle Reordering for Vertex Locality and Reduced Overdraw, SIGGRAPH +2007. + +This is convenience function supplementing direct usage of Tipsify class, +instead of +@code +MeshBuilder builder; +MeshTools::Tipsify(builder).run(cacheSize); +@endcode +you can just write +@code +MeshTools::tipsify(builder, cacheSize); +@endcode +*/ +template inline void tipsify(MeshBuilder& builder, size_t cacheSize) { + Tipsify(builder).run(cacheSize); +} + +}} + +#endif