From a521f045baf0c6a56539cda660edcddf96480d18 Mon Sep 17 00:00:00 2001 From: mrq Date: Tue, 3 Dec 2024 15:46:41 -0600 Subject: [PATCH] Repaired meshopt integration, it's preferable to instead optimize on meshlets instead of the raw mesh --- .../scenes/sourceengine/animal_crossing.json | 4 +- .../sourceengine/base_sourceengine.json | 10 ++-- .../scenes/sourceengine/sourceengine.json | 4 +- bin/data/scenes/sourceengine/ss2_medsci1.json | 4 +- docs/README.md | 2 + engine/inc/uf/ext/meshopt/meshopt.h | 50 ++++++++++++++++++- engine/inc/uf/utils/mesh/grid.h | 2 + engine/src/ext/gltf/gltf.cpp | 30 ++++++++++- engine/src/ext/gltf/processPrimitives.inl | 9 ++++ engine/src/ext/meshopt/meshopt.cpp | 32 ++++++++---- 10 files changed, 124 insertions(+), 23 deletions(-) diff --git a/bin/data/scenes/sourceengine/animal_crossing.json b/bin/data/scenes/sourceengine/animal_crossing.json index f42c1700..3dca6be7 100644 --- a/bin/data/scenes/sourceengine/animal_crossing.json +++ b/bin/data/scenes/sourceengine/animal_crossing.json @@ -1,10 +1,10 @@ { "import": "./base_sourceengine.json", "assets": [ - // { "filename": "./models/animal_crossing.glb" }, + { "filename": "./models/animal_crossing.glb" }, // { "filename": "./models/animal_crossing/graph.json" }, // { "filename": "./models/animal_crossing_small.glb" }, - { "filename": "./models/animal_crossing_small/graph.json" }, + // { "filename": "./models/animal_crossing_small/graph.json" }, { "filename": "/craeture.json", "delay": 2.0 } ], diff --git a/bin/data/scenes/sourceengine/base_sourceengine.json b/bin/data/scenes/sourceengine/base_sourceengine.json index ffdabe8b..1475bdb1 100644 --- a/bin/data/scenes/sourceengine/base_sourceengine.json +++ b/bin/data/scenes/sourceengine/base_sourceengine.json @@ -2,7 +2,7 @@ "import": "/model.json", "metadata": { "graph": { - // "renderer": { "separate": true }, + "renderer": { "separate": false }, "exporter": { "optimize": "tagged" }, @@ -11,13 +11,13 @@ // exact matches "worldspawn": { "physics": { "type": "mesh", "static": true }, - "grid": { "size": [3,1,3], "epsilon": 0.001, "cleanup": true, "print": true }, - // "optimize mesh": { "simplify": 0 }, + "grid": { "size": [16,1,16], "epsilon": 0.001, "cleanup": true, "print": true }, + "optimize meshlets": { "simplify": 0.125, "print": false }, "unwrap mesh": true }, "worldspawn_skybox": { - "grid": { "size": [3,1,3], "epsilon": 0.001, "cleanup": true, "print": true }, - // "optimize mesh": { "simplify": 0 }, + "grid": { "size": [16,1,16], "epsilon": 0.001, "cleanup": true, "print": true }, + "optimize meshlets": { "simplify": 0.125, "print": false }, "unwrap mesh": true }, "info_player_spawn": { "action": "attach", "filename": "./player.json", "transform": { "orientation": [ 0, 1, 0, 0 ] } }, diff --git a/bin/data/scenes/sourceengine/sourceengine.json b/bin/data/scenes/sourceengine/sourceengine.json index c8aedd25..0ca77b5c 100644 --- a/bin/data/scenes/sourceengine/sourceengine.json +++ b/bin/data/scenes/sourceengine/sourceengine.json @@ -2,7 +2,7 @@ // "import": "./rp_downtown_v2.json" // "import": "./ss2_medsci1.json" // "import": "./sh2_mcdonalds.json" -// "import": "./animal_crossing.json" - "import": "./mds_mcdonalds.json" + "import": "./animal_crossing.json" +// "import": "./mds_mcdonalds.json" // "import": "./gm_construct.json" } \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/ss2_medsci1.json b/bin/data/scenes/sourceengine/ss2_medsci1.json index 9e0b7a6a..906cf7c6 100644 --- a/bin/data/scenes/sourceengine/ss2_medsci1.json +++ b/bin/data/scenes/sourceengine/ss2_medsci1.json @@ -1,8 +1,8 @@ { "import": "./base_sourceengine.json", "assets": [ - // { "filename": "./models/ss2_medsci1.glb" } - { "filename": "./models/ss2_medsci1/graph.json" } + { "filename": "./models/ss2_medsci1_small.glb" } + // { "filename": "./models/ss2_medsci1_small/graph.json" } ], "metadata": { "graph": { diff --git a/docs/README.md b/docs/README.md index cf7e7e82..6da5f1f1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,7 @@ # Engine Docs +## Features + To be filled. ## Notices and Citations diff --git a/engine/inc/uf/ext/meshopt/meshopt.h b/engine/inc/uf/ext/meshopt/meshopt.h index e82810eb..ac7dd9cc 100644 --- a/engine/inc/uf/ext/meshopt/meshopt.h +++ b/engine/inc/uf/ext/meshopt/meshopt.h @@ -2,10 +2,56 @@ #include #include -#include + +#if UF_USE_MESHOPT +#include +#endif namespace ext { namespace meshopt { - bool UF_API optimize( uf::Mesh&, float simplify = 1.0f, size_t = SIZE_MAX ); + bool UF_API optimize( uf::Mesh&, float simplify = 1.0f, size_t = SIZE_MAX, bool verbose = false ); + + template + bool optimize( uf::Meshlet_T& meshlet, float simplify = 1.0f, size_t o = SIZE_MAX, bool verbose = false ) { + #if UF_USE_MESHOPT + size_t indicesCount = meshlet.indices.size(); + uf::stl::vector remap(indicesCount); + + size_t verticesCount = meshopt_generateVertexRemap(&remap[0], &meshlet.indices[0], indicesCount, &meshlet.vertices[0], meshlet.vertices.size(), sizeof(T)); + uf::stl::vector vertices(verticesCount); + uf::stl::vector indices(indicesCount); + + meshopt_remapIndexBuffer(&indices[0], &meshlet.indices[0], indicesCount, &remap[0]); + meshopt_remapVertexBuffer(&vertices[0], &meshlet.vertices[0], meshlet.vertices.size(), sizeof(T), &remap[0]); + + // + meshopt_optimizeVertexCache(&indices[0], &indices[0], indicesCount, verticesCount); + // + meshopt_optimizeOverdraw(&indices[0], &indices[0], indicesCount, &vertices[0].position.x, verticesCount, sizeof(T), 1.05f); + // + meshopt_optimizeVertexFetch(&vertices[0], &indices[0], indicesCount, &vertices[0], verticesCount, sizeof(T)); + + // almost always causes ID discontinuities + if ( 0.0f < simplify && simplify < 1.0f ) { + uf::stl::vector indicesSimplified(indicesCount); + + size_t targetIndices = indicesCount * simplify; + float targetError = 1e-2f / simplify; + + float realError = 0.0f; + size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetError, realError); + // size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetIndices); + + if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError); + + indicesCount = realIndices; + indices.swap( indicesSimplified ); + } + + meshlet.primitive.drawCommand.indices = indicesCount; + meshlet.primitive.drawCommand.vertices = verticesCount; + #endif + return true; + } } } \ No newline at end of file diff --git a/engine/inc/uf/utils/mesh/grid.h b/engine/inc/uf/utils/mesh/grid.h index ddc34d2c..5ad38399 100644 --- a/engine/inc/uf/utils/mesh/grid.h +++ b/engine/inc/uf/utils/mesh/grid.h @@ -84,6 +84,7 @@ namespace uf { uf::meshgrid::calculate( grid, eps ); // it's better to naively clip the mesh multiple times rather than calculate the triangles needed to clip + /* if ( clip ) { for ( auto& pair : grid.nodes ) { ++atlasID; @@ -129,6 +130,7 @@ namespace uf { return partitioned; } + */ for ( auto& meshlet : meshlets ) uf::meshgrid::partition( grid, meshlet.vertices, meshlet.indices, meshlet.primitive ); if ( cleanup ) uf::meshgrid::cleanup( grid ); diff --git a/engine/src/ext/gltf/gltf.cpp b/engine/src/ext/gltf/gltf.cpp index c871ef4e..4cb2423a 100644 --- a/engine/src/ext/gltf/gltf.cpp +++ b/engine/src/ext/gltf/gltf.cpp @@ -281,6 +281,12 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize bool cleanup = true; } meshgrid; + struct { + bool should = false; + bool print = false; + size_t level = SIZE_MAX; + float simplify = 1.0f; + } meshopt; ext::json::forEach( graph.metadata["tags"], [&]( const uf::stl::string& key, ext::json::Value& value ) { if ( !ext::json::isObject( value["grid"] ) ) return; // no tag["grid"] defined @@ -291,6 +297,26 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize meshgrid.metadata = value["grid"]; }); + #if UF_USE_MESHOPT + // cleanup if blender's exporter is poopy + if ( graph.metadata["exporter"]["optimize"].as(false) || graph.metadata["exporter"]["optimize"].as("") == "tagged" ) { + if ( graph.metadata["exporter"]["optimize"].as("") == "tagged" ) { + ext::json::forEach( graph.metadata["tags"], [&]( const uf::stl::string& key, ext::json::Value& value ) { + if ( ext::json::isNull( value["optimize meshlets"] ) ) return; + if ( uf::string::isRegex( key ) ) { + if ( !uf::string::matched( keyName, key ) ) return; + } else if ( keyName != key ) return; + meshopt.should = true; + if ( ext::json::isObject( value["optimize meshlets"] ) ) { + meshopt.level = value["optimize meshlets"]["level"].as(meshopt.level); + meshopt.simplify = value["optimize meshlets"]["simplify"].as(meshopt.simplify); + meshopt.print = value["optimize meshlets"]["print"].as(meshopt.print); + } + }); + } + } + #endif + if ( ext::json::isObject( meshgrid.metadata ) ) { if ( meshgrid.metadata["size"].is() ) { size_t d = meshgrid.metadata["size"].as(); @@ -497,6 +523,7 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize for ( auto& keyName : graph.meshes ) { size_t level = SIZE_MAX; float simplify = 1.0f; + bool print = false; if ( graph.metadata["exporter"]["optimize"].as("") == "tagged" ) { bool should = false; @@ -510,6 +537,7 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize if ( ext::json::isObject( value["optimize mesh"] ) ) { level = value["optimize mesh"]["level"].as(level); simplify = value["optimize mesh"]["simplify"].as(simplify); + print = value["optimize mesh"]["print"].as(print); } }); @@ -518,7 +546,7 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize auto& mesh = /*graph.storage*/storage.meshes[keyName]; UF_MSG_DEBUG("Optimizing mesh at level {}: {}", level, keyName); - if ( !ext::meshopt::optimize( mesh, simplify, level ) ) { + if ( !ext::meshopt::optimize( mesh, simplify, level, print ) ) { UF_MSG_ERROR("Mesh optimization failed: {}", keyName ); } } diff --git a/engine/src/ext/gltf/processPrimitives.inl b/engine/src/ext/gltf/processPrimitives.inl index d569ac2e..1f5337fd 100644 --- a/engine/src/ext/gltf/processPrimitives.inl +++ b/engine/src/ext/gltf/processPrimitives.inl @@ -360,6 +360,15 @@ if ( meshgrid.grid.divisions.x > 1 || meshgrid.grid.divisions.y > 1 || meshgrid. meshlets = std::move( partitioned ); } +// optimize each meshlet if requested +if ( meshopt.should ) { + for ( auto& meshlet : meshlets ) { + if ( !ext::meshopt::optimize( meshlet, meshopt.simplify, meshopt.level, meshopt.print ) ) { + UF_MSG_ERROR("Mesh optimization failed: {}", keyName ); + } + } +} + { size_t indexID = 0; size_t vertexID = 0; diff --git a/engine/src/ext/meshopt/meshopt.cpp b/engine/src/ext/meshopt/meshopt.cpp index 50e4e2b1..d8192e01 100644 --- a/engine/src/ext/meshopt/meshopt.cpp +++ b/engine/src/ext/meshopt/meshopt.cpp @@ -2,9 +2,9 @@ #if UF_USE_MESHOPT #include -bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o ) { +bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verbose ) { if ( mesh.isInterleaved() ) { - UF_MSG_ERROR("optimization of interleaved meshes is currently not supported"); + UF_MSG_ERROR("optimization of interleaved meshes is currently not supported. Consider optimizing on meshlets."); return false; } mesh.updateDescriptor(); @@ -26,14 +26,28 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o ) { stream.stride = p.attribute.stride; } - size_t indicesCount = mesh.vertex.count; + bool hasIndices = mesh.index.count > 0; + size_t indicesCount = hasIndices ? mesh.index.count : mesh.vertex.count; uf::stl::vector remap(indicesCount); - size_t verticesCount = meshopt_generateVertexRemapMulti( &remap[0], NULL, indicesCount, indicesCount, &streams[0], streams.size() ); + uint32_t* sourceIndicesPointer = NULL; + uf::stl::vector sourceIndices(indicesCount); + if ( hasIndices ) { + uint8_t* pointer = (uint8_t*) mesh.getBuffer(mesh.index).data(); + for ( auto index = 0; index < indicesCount; ++index ) { + switch ( mesh.index.size ) { + case 1: sourceIndices[index] = (( uint8_t*) pointer)[index]; break; + case 2: sourceIndices[index] = ((uint16_t*) pointer)[index]; break; + case 4: sourceIndices[index] = ((uint32_t*) pointer)[index]; break; + } + } + } + + size_t verticesCount = meshopt_generateVertexRemapMulti( &remap[0], sourceIndicesPointer, indicesCount, indicesCount, &streams[0], streams.size() ); // generate new indices, as they're going to be specific to a region of vertices due to drawcommand shittery uf::stl::vector indices(indicesCount); - meshopt_remapIndexBuffer(&indices[0], NULL, indicesCount, &remap[0]); + meshopt_remapIndexBuffer(&indices[0], sourceIndicesPointer, indicesCount, &remap[0]); // for ( auto& p : attributes ) { @@ -64,10 +78,10 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o ) { float targetError = 1e-2f / simplify; float realError = 0.0f; - // size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetError, realError); - size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetIndices); + size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetError, realError); + // size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetIndices); - UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError); + if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError); indicesCount = realIndices; indices.swap( indicesSimplified ); @@ -106,7 +120,7 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o ) { } else if ( lastID + 1 == id.x ) { lastID = id.x; } else { - UF_MSG_DEBUG("Discontinuity detected: {} | {} | {} | {}", index, vertex, lastID, id.x); + if ( verbose ) UF_MSG_DEBUG("Discontinuity detected: {} | {} | {} | {}", index, vertex, lastID, id.x); discontinuityDetected = true; lastID = id.x; }