Repository navigation
[Performance] Automatic LOD chains for the models of a .untoldpack - #1299
Conversation
CMeshOptimizer holds the files of meshoptimizer 1.3 (MIT) that the asset cook uses, unmodified: the simplifier, the index generator, the vertex cache and vertex fetch optimizers and the allocator they share. It is called from Swift through its C API; no C++ interoperability is needed.
A scene exported from Blender has no levels of detail, and a pack can place a very dense model hundreds of times. The cook now writes simplified levels of every model that is detailed enough, and the pack loader registers them on the entities of each placement. Cook (UntoldMeshLODCooker, `untoldengine bake-lods`, last step of `untoldengine export` for a pack): - levels at 50 %, 15 % and 3 % of the triangles by default, for models of 2,000 triangles or more, written next to the model as <name>_LOD<n>.untold - a level may deviate by the size of one of its triangles; vertices that two meshes of a model share stay in place; texture seams are kept on textured meshes unless they hold a level well above its target - the small detached pieces of an aggregate (leaves, twigs) are thinned evenly and the survivors enlarged, so a canopy keeps its area - the manifest records each chain under "lodChains", with a screen size per level The cook is a library of its own, UntoldEngineMeshCook, which the CLI links. The UntoldEngine module does not import the simplifier: code that is compiled against a built engine module without SwiftPM, as the editor compiles the components of a project, would otherwise need the module map of the C++ library and fail with "missing required module 'CMeshOptimizer'". Loader: - each level is built once per pack load and shared by the placements - a level's screen size becomes a switch distance per placement, from the model's bounds, the placement's scale and the field of view - levels are drawn with the model's materials, and the materials of an entity follow it from level to level LOD system: - the hysteresis takes at most a tenth of a level's switch distance, so a level that switches closer than the hysteresis can be returned to - the configuration is read once per update, not per entity
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 🧰 Additional context used📚 Code guidelines (1)ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (29)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughThe change adds mesh-cooking support for generating model LOD files and pack manifest chains. The engine loads those chains and selects levels at runtime. The CLI adds LOD baking for models and packs, and export enables pack LOD generation by default. ChangesAutomatic LOD Chains
Priority: ⬆️ High Estimated code review effort: 5 (Critical) | ~120 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant CLI as bake-lods CLI
participant UntoldMeshLODCooker
participant UntoldMeshLODSimplifier
participant UntoldMeshLODWriter
participant UntoldPackLODCooker
CLI->>UntoldMeshLODCooker: cook a model input
UntoldMeshLODCooker->>UntoldMeshLODSimplifier: simplify mesh levels
UntoldMeshLODCooker->>UntoldMeshLODWriter: write generated model levels
CLI->>UntoldPackLODCooker: cook a pack input
UntoldPackLODCooker->>UntoldMeshLODCooker: cook each distinct model path
UntoldPackLODCooker->>UntoldPackLODCooker: update pack manifest with LOD chains
Merge Risk: ⚪ Minimal · up to This change adds automatic LOD generation for pack models, along with runtime loading and level selection for those chains. No concrete defect was established. A model that cannot be cooked falls back to its full-detail mesh, and the export still succeeds. Security Architecture ReviewSecurity architecture risk: 🟠 High · up to Processing an untrusted pack can direct the new cooking flow outside the pack directory, where it can create or remove generated model levels using the caller’s filesystem permissions. The new cooker also accepts malformed geometry that can escape its recoverable failure handling. Ordinary-file collision checks and runtime fallback limit some consequences, but do not enforce these boundaries. Retained concerns
Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 25.49% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 204 functions across 21 files. (6 skipped: 6 unsupported.)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
Summary
Stage 1c of
docs/proposals/LargeSceneRendering.md. A scene exported from Blender has no levels of detail, and a pack can place a very dense model hundreds of times: on the BIM site of #1290 one tree of 1.85 M triangles is placed 175 times, 92.5 % of the 349 M triangles of the scene. The cook now writes simplified levels of every model that is detailed enough, and the pack loader registers them on the entities of each placement.The sheet is a procedural test model (a ground, a boulder and a tree whose canopy is 3,200 separate cards) and the three levels the cook writes for it, drawn at the same size: 197,200 triangles, then 98,598, 32,290 and 7,348. The cook gives each level the screen size from which it is detailed enough, here 58 %, 33 % and 16 % of the height of the screen, so levels 2 and 3 are shown larger than they are in use.
Release build, headless at 1920×1080 on an M-series Mac. From the same viewpoints the tree looks the same with and without the chain at 3, 7, 15 and 40 m.
Changes
Cook (
UntoldMeshLODCooker,untoldengine bake-lods, and the last step ofuntoldengine exportfor a pack unless--no-lods):<name>_LOD<n>.untold: the same entities, mesh records and materials with fewer triangles.lodChains, with a screen size per level. A manifest without the key loads as before, and an older engine ignores it.UntoldFileFlags.generatedLODLevel), never by its name: real packs have models named…_LOD4.The cook is a library of its own,
UntoldEngineMeshCook, which the CLI links. The simplifier is meshoptimizer 1.3 (MIT), vendored unmodified as the C++ targetCMeshOptimizer(the five source files the cook uses). TheUntoldEnginemodule does not import it: code compiled against a built engine module without SwiftPM, as the editor compiles a project's Code Components, would otherwise need the module map of the C++ library and fail with "missing required module 'CMeshOptimizer'". An app that only loads packs does not build the simplifier.Loader:
LODComponent.levelsShareMaterials).LOD system:
Verification
UntoldMeshLODCookerTests(26): the levels of a model and their triangle counts, the error limit, locked and protected vertices, aggregates and their area, the level file's tables and header flag, the skip reasons, re-cooking, the manifest, a name that is taken.UntoldPackLODRenderTests(11): a cooked pack registers its levels on every placement and shares their meshes, switch distances follow the placement's scale, the materials follow the entity from level to level, a pack without chains loads as before.LODSystemTests(+2) for the hysteresis;BakeLODsCommandTests(6) in the CLI package.swift test --filter UntoldEngineTests: 1583 run. The only failure isExternalRenderExtensionPackageTests, which fails locally whenever the checkout folder is not namedUntoldEngine; it passes from a checkout with that name.compare_psnr.pythat does the same arithmetic without OpenCV and scikit-image, which this machine does not have.UntoldEngineandUntoldEngineMeshCookbuild and link for the iOS and visionOS simulators. A file that importsUntoldEnginecompiles against the built module with only theCShaderTypesmodule map, as the editor's component compiler does.Limits
scripts/export-untold, which writes no chains; until it calls the cooker, rununtoldengine bake-lodson the pack.Summary by CodeRabbit
bake-lodscommand and an option to skip LOD generation during export.