From a544cbcfdec0c01c70395c17f372edd0959dc2f3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Jun 2026 18:41:45 +0200 Subject: [PATCH] Add mesh index buffer optimization based on `meshoptimizer` library Work similar to existing impl but yields better results with even less overdraw. --- .../Engine/Graphics/Models/ModelData.Tool.cpp | 42 ++++++++++++++++++- Source/Engine/Graphics/Models/ModelData.h | 7 ++++ .../Tools/ModelTool/ModelTool.Assimp.cpp | 8 +++- .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 13 +----- 5 files changed, 57 insertions(+), 15 deletions(-) diff --git a/Source/Engine/Graphics/Models/ModelData.Tool.cpp b/Source/Engine/Graphics/Models/ModelData.Tool.cpp index f1c84cfb7..992c5dc50 100644 --- a/Source/Engine/Graphics/Models/ModelData.Tool.cpp +++ b/Source/Engine/Graphics/Models/ModelData.Tool.cpp @@ -10,11 +10,12 @@ #include "Engine/Core/Collections/BitArray.h" #include "Engine/Tools/ModelTool/ModelTool.h" #include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h" -#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Platform/Platform.h" #include "Engine/Platform/MessageBox.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include #define USE_MIKKTSPACE 1 -#include "ThirdParty/MikkTSpace/mikktspace.h" +#include #if USE_ASSIMP #define USE_SPATIAL_SORT 1 #define ASSIMP_BUILD_NO_EXPORT @@ -25,6 +26,26 @@ #endif #include +bool InitedMeshOpt = false; + +void* MeshOptAllocate(size_t size) +{ + return Allocator::Allocate(size); +} + +void MeshOptDeallocate(void* ptr) +{ + Allocator::Free(ptr); +} + +void InitMeshOpt() +{ + if (InitedMeshOpt) + return; + InitedMeshOpt = true; + meshopt_setAllocator(MeshOptAllocate, MeshOptDeallocate); +} + #if PLATFORM_WINDOWS // Import UVAtlas library @@ -934,6 +955,23 @@ void MeshData::ImproveCacheLocality() LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("optimized indices")); } +void MeshData::Optimize() +{ + if (Indices.IsEmpty() || Positions.IsEmpty()) + return; + PROFILE_CPU(); + InitMeshOpt(); + + // Vertex cache optimization (https://meshoptimizer.org/#vertex-cache-optimization) + Array tmpIndices; + tmpIndices.Resize(Indices.Count()); + meshopt_optimizeVertexCache(tmpIndices.Get(), Indices.Get(), Indices.Count(), Positions.Count()); + + // Overdraw optimization (https://meshoptimizer.org/#overdraw-optimization) + const float overdrawThreshold = 1.05f; + meshopt_optimizeOverdraw(Indices.Get(), tmpIndices.Get(), Indices.Count(), (const float*)Positions.Get(), Positions.Count(), sizeof(Float3), overdrawThreshold); +} + float MeshData::CalculateTrianglesArea() const { PROFILE_CPU(); diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 169ff3339..7bb3ae8c5 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -228,9 +228,16 @@ public: /// /// Reorders all triangles for improved vertex cache locality. It tries to arrange all triangles to fans and to render triangles which share vertices directly one after the other. + /// [Deprecated in v1.13] /// + DEPRECATED("Use Optimize instead as it does more advanced optimizations on index buffer.") void ImproveCacheLocality(); + /// + /// Optimizes mesh vertex and index buffers for runtime rendering. Uses 'meshoptimizer' library. + /// + void Optimize(); + /// /// Sums the area of all triangles in the mesh. /// diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index 4509d287d..d3ef6a016 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -437,6 +437,12 @@ bool ProcessMesh(ModelData& result, AssimpImporterData& data, const aiMesh* aMes } } } + + if (data.Options.OptimizeMeshes) + { + mesh.Optimize(); + } + return false; } @@ -730,7 +736,7 @@ bool ModelTool::ImportDataAssimp(const String& path, ModelData& data, Options& o if (options.ReverseWindingOrder) flags &= ~aiProcess_FlipWindingOrder; if (options.OptimizeMeshes) - flags |= aiProcess_OptimizeMeshes | aiProcess_SplitLargeMeshes | aiProcess_ImproveCacheLocality; + flags |= aiProcess_OptimizeMeshes; if (options.MergeMeshes) flags |= aiProcess_RemoveRedundantMaterials; } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 5e28daf5d..b9b64c0a9 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -988,7 +988,7 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* if (data.Options.OptimizeMeshes) { - mesh.ImproveCacheLocality(); + mesh.Optimize(); } // Apply FBX Mesh geometry transformation diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 119fa9555..73f1bf402 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -45,6 +45,7 @@ #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Animations/Graph/AnimGraph.h" #include +extern void InitMeshOpt(); #endif ModelSDFHeader::ModelSDFHeader(const ModelBase::SDFData& sdf, const GPUTextureDescription& desc) @@ -940,16 +941,6 @@ void OptimizeCurve(LinearCurve& curve) } } -void* MeshOptAllocate(size_t size) -{ - return Allocator::Allocate(size); -} - -void MeshOptDeallocate(void* ptr) -{ - Allocator::Free(ptr); -} - void TrySetupMaterialParameter(MaterialInstance* instance, Span paramNames, const Variant& value, MaterialParameterType type) { for (const Char* name : paramNames) @@ -1954,8 +1945,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option // Automatic LOD generation if (options.GenerateLODs && options.LODCount > 1 && data.LODs.HasItems() && options.TriangleReduction < 1.0f - ZeroTolerance) { + InitMeshOpt(); auto lodStartTime = DateTime::NowUTC(); - meshopt_setAllocator(MeshOptAllocate, MeshOptDeallocate); float triangleReduction = Math::Saturate(options.TriangleReduction); int32 lodCount = Math::Max(options.LODCount, data.LODs.Count()); int32 baseLOD = Math::Clamp(options.BaseLOD, 0, lodCount - 1);