@woosh/meep-engine 3.60.4 → 3.60.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2602 -0
- package/CONTRIBUTING.md +7 -0
- package/MIGRATION_2_TO_3.md +136 -0
- package/SECURITY.md +4 -0
- package/editor/EditorCameraNavigation.d.ts +2 -1
- package/editor/EditorCameraNavigation.d.ts.map +1 -1
- package/editor/EditorCameraNavigation.js +51 -57
- package/package.json +6 -2
- package/src/engine/EngineHarness.d.ts +3 -9
- package/src/engine/EngineHarness.d.ts.map +1 -1
- package/src/engine/EngineHarness.js +6 -8
- package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts +9 -6
- package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts.map +1 -1
- package/src/engine/graphics/camera/makeOrbitalCameraController.js +44 -71
- package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts +64 -0
- package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts.map +1 -0
- package/src/engine/graphics/camera/orbital/MomentumAxis.js +187 -0
- package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts +86 -0
- package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts.map +1 -0
- package/src/engine/graphics/camera/orbital/OrbitalCameraRig.js +319 -0
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts +34 -10
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +116 -30
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts +1 -7
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +1 -37
- package/src/engine/graphics/ecs/water/Water.d.ts +8 -0
- package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
- package/src/engine/graphics/ecs/water/Water.js +8 -0
- package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
- package/src/engine/graphics3/WaterSystem.js +731 -734
- package/src/engine/graphics3/decal/DecalTileShader.d.ts +61 -0
- package/src/engine/graphics3/decal/DecalTileShader.d.ts.map +1 -0
- package/src/engine/graphics3/decal/DecalTileShader.js +138 -0
- package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts +10 -4
- package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
- package/src/engine/graphics3/decal/GPUDecalRenderer.js +149 -44
- package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts +25 -0
- package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts.map +1 -0
- package/src/engine/graphics3/decal/chunk_decal_tile_vertex.js +55 -0
- package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts +22 -0
- package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts.map +1 -0
- package/src/engine/graphics3/decal/graph_gather_decal_tiles.js +70 -0
- package/src/engine/graphics3/decal/shader_decal_composite.d.ts +7 -5
- package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
- package/src/engine/graphics3/decal/shader_decal_composite.js +48 -44
- package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts +12 -0
- package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts.map +1 -0
- package/src/engine/graphics3/decal/shader_decal_tile_copy.js +78 -0
- package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts +11 -0
- package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts.map +1 -0
- package/src/engine/graphics3/decal/shader_gather_decal_tiles.js +80 -0
- package/src/engine/input/devices/mouse/MouseEventButtons.d.ts +13 -0
- package/src/engine/input/devices/mouse/MouseEventButtons.d.ts.map +1 -0
- package/src/engine/input/devices/mouse/MouseEventButtons.js +23 -0
- package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts +4 -0
- package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts.map +1 -1
- package/src/engine/input/ecs/controllers/KeyboardCameraController.js +9 -45
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/playground/water_refraction/README.md +5 -6
- package/src/shade/playground/water_refraction/index.html +1 -1
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +7 -5
- package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts +40 -80
- package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts.map +1 -1
- package/src/shade/renderer/camera/orbital/OrbitalCameraController.js +197 -147
- package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
- package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
- package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
- package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
- package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +2 -2
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +2 -2
- package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +2 -1
- package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
- package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +18 -1
- package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts +31 -0
- package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.js +343 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.js +29 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +7 -13
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +1 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +5 -5
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +10 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +1 -4
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +1 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts +5 -15
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.js +6 -34
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +1 -7
- package/src/shade/renderer/hiz/HZBFootprint.d.ts +14 -0
- package/src/shade/renderer/hiz/HZBFootprint.d.ts.map +1 -0
- package/src/shade/renderer/hiz/HZBFootprint.js +16 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts +10 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts.map +1 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.js +11 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts +10 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts.map +1 -0
- package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.js +11 -0
- package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +0 -7
- package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
- package/src/shade/renderer/hiz/HierarchicalZBuffer.js +405 -350
- package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts +12 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts.map +1 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_x.js +27 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts +12 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts.map +1 -0
- package/src/shade/renderer/hiz/chunk_hzb_footprint_y.js +27 -0
- package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts +14 -0
- package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts.map +1 -0
- package/src/shade/renderer/hiz/hzb_footprint_texels.js +34 -0
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +0 -5
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +37 -57
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts +11 -0
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts.map +1 -0
- package/src/shade/renderer/hiz/{shader_hzb_reduce_mip0_clip.js → shader_hzb_reduce_mip0.js} +120 -111
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/material/GPUMaterialManager.d.ts +7 -0
- package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
- package/src/shade/renderer/material/GPUMaterialManager.js +11 -0
- package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +12 -12
- package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
- package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +17 -118
- package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
- package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +7 -2
- package/src/shade/renderer/water/GPUWaterRenderer.d.ts +0 -6
- package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
- package/src/shade/renderer/water/GPUWaterRenderer.js +12 -17
- package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts +35 -0
- package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts.map +1 -0
- package/src/shade/renderer/water/medium/graph_water_meshlets.js +89 -0
- package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
- package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
- package/src/shade/renderer/water/shader_water.js +22 -13
- package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
- package/src/shade/renderer/water/shader_water_avboit.js +7 -7
- package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +1 -1
- package/src/shade/renderer/water/surface/graph_water_tessellation.js +1 -1
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +9 -1
- package/editor/REWORK_PLAN_2026_08.md +0 -692
- package/src/CODEC_LAYOUT_VERIFICATION.md +0 -419
- package/src/CORRECTNESS_HUNT_2026_07_24.md +0 -142
- package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +0 -738
- package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +0 -79
- package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +0 -93
- package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +0 -80
- package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +0 -164
- package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +0 -69
- package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +0 -110
- package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +0 -129
- package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +0 -72
- package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +0 -74
- package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +0 -169
- package/src/DUPLICATION_AUDIT_2026_08_07.md +0 -254
- package/src/DUPLICATION_AUDIT_REMEDIATION.md +0 -250
- package/src/DUPLICATION_REVIEW_2026_10_24.md +0 -1072
- package/src/REVIEW_2026_08_06/adv-core.md +0 -511
- package/src/REVIEW_2026_08_06/adv-gfx.md +0 -480
- package/src/REVIEW_2026_08_06/adv-view.md +0 -446
- package/src/REVIEW_2026_08_06/api-core.md +0 -296
- package/src/REVIEW_2026_08_06/api-engine.md +0 -195
- package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +0 -507
- package/src/REVIEW_2026_08_06/core-binary.md +0 -348
- package/src/REVIEW_2026_08_06/core-collection.md +0 -376
- package/src/REVIEW_2026_08_06/core-math.md +0 -221
- package/src/REVIEW_2026_08_06/core-process.md +0 -266
- package/src/REVIEW_2026_08_06/core-science.md +0 -223
- package/src/REVIEW_2026_08_06/engine-ai-nav.md +0 -279
- package/src/REVIEW_2026_08_06/engine-animation.md +0 -167
- package/src/REVIEW_2026_08_06/engine-asset.md +0 -166
- package/src/REVIEW_2026_08_06/engine-ecs.md +0 -920
- package/src/REVIEW_2026_08_06/engine-input.md +0 -197
- package/src/REVIEW_2026_08_06/engine-misc.md +0 -248
- package/src/REVIEW_2026_08_06/engine-terrain.md +0 -52
- package/src/REVIEW_2026_08_06/generation.md +0 -176
- package/src/REVIEW_2026_08_06/geom-2d.md +0 -434
- package/src/REVIEW_2026_08_06/geom-3d.md +0 -117
- package/src/REVIEW_2026_08_06/gfx-gi.md +0 -143
- package/src/REVIEW_2026_08_06/gfx-render.md +0 -740
- package/src/REVIEW_2026_08_06/gfx-texture.md +0 -690
- package/src/REVIEW_2026_08_06/prior-criticals.md +0 -49
- package/src/REVIEW_2026_08_06/prior-highs.md +0 -138
- package/src/REVIEW_2026_08_06/test-gaps.md +0 -364
- package/src/REVIEW_2026_08_06/view.md +0 -667
- package/src/REVIEW_2026_08_06.md +0 -610
- package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +0 -542
- package/src/core/bvh2/bvh3/NOTES.md +0 -2
- package/src/core/bvh8/NOTES.md +0 -63
- package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +0 -243
- package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +0 -417
- package/src/core/color/COLOR_REVIEW_2026_08_28.md +0 -779
- package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +0 -433
- package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +0 -486
- package/src/core/geom/3d/atlas/BFF_PLAN.md +0 -174
- package/src/core/geom/3d/atlas/REVIEW_02.md +0 -392
- package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +0 -146
- package/src/core/geom/3d/gjk/NOTES.md +0 -8
- package/src/core/geom/3d/topology/TOPO_MESH_RETIREMENT_PLAN.md +0 -135
- package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +0 -381
- package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +0 -684
- package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +0 -554
- package/src/core/model/reactive/evaluation/PREDICATE_APPLICABILITY_PROPOSAL.md +0 -446
- package/src/engine/REVIEW_2026_07_03.md +0 -211
- package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +0 -815
- package/src/engine/control/first-person/DESIGN.md +0 -658
- package/src/engine/control/first-person/DESIGN_COLLISION.md +0 -382
- package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +0 -563
- package/src/engine/control/first-person/TODO.md +0 -160
- package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +0 -531
- package/src/engine/ecs/HIERARCHY_REVIEW.md +0 -717
- package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +0 -262
- package/src/engine/ecs/ik/PLAN.md +0 -179
- package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +0 -272
- package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +0 -199
- package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +0 -521
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +0 -463
- package/src/engine/graphics/ecs/camera/pp/NOTES.md +0 -48
- package/src/engine/graphics/render/frame_graph/CORRECTNESS_FIXES_2026_09_04.md +0 -659
- package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +0 -235
- package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +0 -490
- package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +0 -466
- package/src/engine/input/GAMEPAD_API_PROPOSAL.md +0 -717
- package/src/engine/input/ecs/ism/ISM_DESIGN.md +0 -1523
- package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +0 -627
- package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +0 -661
- package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +0 -229
- package/src/engine/network/CONGESTION_CONTROL.md +0 -87
- package/src/engine/network/MESSAGE_TRANSPORT.md +0 -364
- package/src/engine/network/SMOOTHNESS_DESIGN.md +0 -400
- package/src/engine/physics/cloth/MEASUREMENTS.md +0 -5304
- package/src/engine/physics/cloth/PLAN.md +0 -3056
- package/src/engine/physics/fluid/GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md +0 -1382
- package/src/engine/physics/fluid/REVIEW_02_PLAN.md +0 -243
- package/src/engine/physics/fluid/solver/NOTES.md +0 -46
- package/src/engine/sound/SOUND_EMITTER_RETIREMENT_PLAN.md +0 -536
- package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +0 -432
- package/src/engine/sound/simulation/PROBE_PLACEMENT.md +0 -250
- package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +0 -322
- package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +0 -261
- package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +0 -121
- package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +0 -999
- package/src/format/image/avif/DECISIONS.md +0 -545
- package/src/format/image/jpeg/NOTES.md +0 -3
- package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +0 -814
- package/src/generation/COORDINATES.md +0 -54
- package/src/generation/REVIEW_01_ACTION_PLAN.md +0 -628
- package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +0 -220
- package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +0 -172
- package/src/generation/foliage/CROWN_FULLNESS_BRIEF_2026_10_26.md +0 -86
- package/src/generation/foliage/FOLIAGE_DESIGN.md +0 -547
- package/src/generation/foliage/FOLIAGE_PLAN.md +0 -198
- package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +0 -158
- package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +0 -521
- package/src/generation/foliage/REAL_TIME_TREES_BRIEF_2026_10_27.md +0 -86
- package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +0 -347
- package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +0 -248
- package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +0 -694
- package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +0 -552
- package/src/shade/RENDERER_CONTRACT.md +0 -697
- package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +0 -263
- package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +0 -569
- package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +0 -755
- package/src/shade/device/mock/EXTENSION_PROPOSAL_2026_08_22.md +0 -458
- package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +0 -255
- package/src/shade/device/mock/REVIEW_2026_08_22.md +0 -527
- package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +0 -894
- package/src/shade/device/timing/profile/SGPT_FORMAT.md +0 -345
- package/src/shade/renderer/NOTES.md +0 -78
- package/src/shade/renderer/PLAN.md +0 -8
- package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +0 -145
- package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +0 -357
- package/src/shade/renderer/atmosphere/NOTES.md +0 -6
- package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts +0 -76
- package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/MomentumAxis.js +0 -164
- package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts +0 -45
- package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.js +0 -66
- package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts +0 -98
- package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/OrbitalCameraInput.js +0 -184
- package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts +0 -175
- package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/OrbitalCameraRig.js +0 -411
- package/src/shade/renderer/camera/orbital/README.md +0 -81
- package/src/shade/renderer/camera/orbital/index.d.ts +0 -6
- package/src/shade/renderer/camera/orbital/index.d.ts.map +0 -1
- package/src/shade/renderer/camera/orbital/index.js +0 -9
- package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +0 -964
- package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +0 -361
- package/src/shade/renderer/geometry/bvh/BLAS_REFIT_BATCH_PLAN.md +0 -619
- package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +0 -674
- package/src/shade/renderer/geometry/sdf/NOTES.md +0 -6
- package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +0 -540
- package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +0 -305
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +0 -913
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +0 -787
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +0 -481
- package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +0 -301
- package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +0 -448
- package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +0 -385
- package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +0 -656
- package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +0 -308
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +0 -657
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +0 -551
- package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +0 -529
- package/src/shade/renderer/global_illumination/lpv/NOTES.md +0 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +0 -4306
- package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +0 -410
- package/src/shade/renderer/global_illumination/sharc/NOTES.md +0 -8
- package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +0 -1369
- package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +0 -202
- package/src/shade/renderer/gpu_primitive/indirect/NOTES.md +0 -94
- package/src/shade/renderer/hiz/NOTES.md +0 -3
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +0 -8
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +0 -1
- package/src/shade/renderer/light/cluster/NOTES.md +0 -11
- package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +0 -524
- package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +0 -1101
- package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts +0 -24
- package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts.map +0 -1
- package/src/shade/renderer/material/resident/shader_resident_texture_means.js +0 -120
- package/src/shade/renderer/particles/DESIGN.md +0 -1011
- package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +0 -280
- package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +0 -453
- package/src/shade/renderer/path_tracer/PATH_TRACED_MEDIA_PLAN_2026_10_02.md +0 -520
- package/src/shade/renderer/postprocess/bloom/NOTES.md +0 -6
- package/src/shade/renderer/postprocess/fsr/NOTES.md +0 -4
- package/src/shade/renderer/postprocess/gtao/NOTES.md +0 -6
- package/src/shade/renderer/postprocess/nss/NOTES.md +0 -332
- package/src/shade/renderer/postprocess/perspective/NOTES.md +0 -3
- package/src/shade/renderer/postprocess/sscs/NOTES.md +0 -5
- package/src/shade/renderer/postprocess/ssr/NOTES.md +0 -14
- package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +0 -286
- package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -39
- package/src/shade/renderer/postprocess/taa/NOTES.md +0 -11
- package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +0 -78
- package/src/shade/renderer/postprocess/vrs/NOTES.md +0 -4
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +0 -1218
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +0 -600
- package/src/shade/renderer/restir/reservoir/NOTES.md +0 -6
- package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +0 -6
- package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +0 -391
- package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +0 -153
- package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +0 -322
- package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +0 -247
- package/src/shade/renderer/shader/chunk/fsr/NOTES.md +0 -3
- package/src/shade/renderer/shader/chunk/fsr/spd/NOTES.md +0 -4
- package/src/shade/renderer/shader/chunk/geometry/ray/NOTES.md +0 -4
- package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +0 -159
- package/src/shade/renderer/shader/chunk/mboit/NOTES.md +0 -38
- package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/NOTES.md +0 -3
- package/src/shade/renderer/shader/chunk/spherical_harmonics/sampling/NOTES.md +0 -8
- package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +0 -927
- package/src/shade/renderer/shadow/contact/FINDINGS.md +0 -413
- package/src/shade/renderer/shadow/map/csm/NOTES.md +0 -5
- package/src/shade/renderer/shadow/ray/NOTES.md +0 -31
- package/src/shade/renderer/shadow/sdf/NOTES.md +0 -9
- package/src/shade/renderer/shadow/sdf/brixelizer/NOTES.md +0 -5
- package/src/shade/renderer/shadow/sdf/cascade/NOTES.md +0 -9
- package/src/shade/renderer/shadow/vsm/NOTES.md +0 -12
- package/src/shade/renderer/shadow/vsm/PLAN.md +0 -442
- package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +0 -43
- package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +0 -2444
- package/src/shade/renderer/texture/mipmaps/NOTES.md +0 -3
- package/src/shade/renderer/volumetrics/NOTES.md +0 -1329
- package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +0 -336
- package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +0 -23
- package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +0 -1
- package/src/shade/renderer/water/medium/GPUWaterBodies.js +0 -75
- package/src/shade/wgsl/NOTES.md +0 -4
|
@@ -1,697 +0,0 @@
|
|
|
1
|
-
# The renderer contract
|
|
2
|
-
|
|
3
|
-
meep 3.0 has one renderer, and this is what it promises.
|
|
4
|
-
|
|
5
|
-
It is written for somebody who has never seen the inside of it: by the end of §3 you have a model on
|
|
6
|
-
the screen, and by the end of §6 you have your own work in the frame. Everything here is the public
|
|
7
|
-
surface — the types you construct, the calls you make, and the two or three places where the answer
|
|
8
|
-
is "no, and here is why".
|
|
9
|
-
|
|
10
|
-
If you are looking for how it works rather than what it promises, the design documents are next to
|
|
11
|
-
the code they describe: `renderer/extension/RENDER_EXTENSION_DESIGN.md`,
|
|
12
|
-
`renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md`, `renderer/geometry/meshlet/README.md`,
|
|
13
|
-
`renderer/NOTES.md`.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 1. What it is
|
|
18
|
-
|
|
19
|
-
A **GPU-driven, visibility-buffer deferred renderer on WebGPU.** Geometry is clustered into meshlets
|
|
20
|
-
ahead of time and lives in one large GPU buffer; culling runs on the GPU; the rasterizer writes a
|
|
21
|
-
visibility buffer and a G-buffer rather than shading as it draws; lights are binned into froxels and
|
|
22
|
-
applied deferred; and the result is temporally resolved, upscaled, tonemapped and presented.
|
|
23
|
-
|
|
24
|
-
Three consequences that shape everything below:
|
|
25
|
-
|
|
26
|
-
- **A mesh is a small GPU-resident record, not a draw call you own.** You place a model at entities
|
|
27
|
-
of the scene's dataset and each primitive is a row; what is drawn, in what order, and whether it
|
|
28
|
-
is culled is the renderer's.
|
|
29
|
-
- **Preparing geometry is a precompute.** Meshlet building, encoding and BVH construction happen
|
|
30
|
-
once per geometry. Content that rewrites its vertices every frame goes down a separate path (§8).
|
|
31
|
-
- **The frame is a closed sequence with declared injection points.** You do not insert a pass
|
|
32
|
-
anywhere; you register an extension against a phase (§6).
|
|
33
|
-
|
|
34
|
-
### What is deliberately absent
|
|
35
|
-
|
|
36
|
-
| | |
|
|
37
|
-
|---|---|
|
|
38
|
-
| **No RHI, no backend abstraction** | There is one target, WebGPU. There is no seam where a second one would go, and adding one is not a small change. |
|
|
39
|
-
| **No WebGL fallback** | Below-floor hardware gets a clean failure message (§2), not a degraded picture. |
|
|
40
|
-
| **No degradation tiers** | There is one pipeline. Individual features can be switched off (`feature_shadows_enabled`, `feature_taa_enabled`, the resolution scale), but there is no "low quality" build of the frame. |
|
|
41
|
-
| **No compressed textures** | DDS, Draco and Basis are not supported anywhere. glTF files carrying `MSFT_texture_dds` load from their base PNG source. |
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## 2. The device floor
|
|
46
|
-
|
|
47
|
-
The renderer requires a WebGPU device that meets a fixed floor. There is no feature detection
|
|
48
|
-
beyond it and no branch that produces a lesser frame.
|
|
49
|
-
|
|
50
|
-
**Required features** — the adapter must have all three, or startup fails:
|
|
51
|
-
|
|
52
|
-
- `indirect-first-instance` — indirect draw is used throughout
|
|
53
|
-
- `float32-blendable` — the MBOIT transparency mode blends into 32-bit float targets. The default mode,
|
|
54
|
-
AVBOIT, blends `rgba16float` only and does not need it. The feature stays on the floor while MBOIT is
|
|
55
|
-
selectable at all; retiring it from the floor means retiring MBOIT.
|
|
56
|
-
- `texture-formats-tier1` — the hierarchical Z buffer is reduced in compute into `rg16float` storage
|
|
57
|
-
textures, which core WebGPU cannot write. Without it the HZB texture and every HZB pipeline fail
|
|
58
|
-
validation, and occlusion culling, SSR, screen-space shadows and light culling all read the HZB.
|
|
59
|
-
|
|
60
|
-
**Required limits:**
|
|
61
|
-
|
|
62
|
-
- `maxStorageBuffersPerShaderStage` ≥ **10**
|
|
63
|
-
- `maxColorAttachmentBytesPerSample` ≥ **32** — the G-buffer is wide
|
|
64
|
-
- `maxBufferSize` and `maxStorageBufferBindingSize` are requested at whatever the adapter offers,
|
|
65
|
-
because the defaults are far too small
|
|
66
|
-
|
|
67
|
-
The required features and the two fixed limits are one definition, `device/SHADE_DEVICE_FLOOR.js`.
|
|
68
|
-
All of it is checked against the adapter before a device is requested, so a refusal names every
|
|
69
|
-
feature and limit the adapter lacks, with the value it offers, rather than the first one found.
|
|
70
|
-
|
|
71
|
-
**Taken when offered, never required:** `timestamp-query`, `subgroups`.
|
|
72
|
-
`timestamp-query` drives GPU timers and nothing else, and some browsers withhold it on hardware that
|
|
73
|
-
is otherwise fine — so the timers go quiet when it is absent rather than the renderer refusing to
|
|
74
|
-
start.
|
|
75
|
-
|
|
76
|
-
### When it is not met
|
|
77
|
-
|
|
78
|
-
`Renderer.initialize()` throws a `ShadeDeviceFailure`, whose `message` is written to be shown to a
|
|
79
|
-
person rather than logged. It distinguishes:
|
|
80
|
-
|
|
81
|
-
| | |
|
|
82
|
-
|---|---|
|
|
83
|
-
| `webgpu_unavailable` | no `navigator.gpu` — WebGPU disabled or unsupported |
|
|
84
|
-
| `adapter_unavailable` | no adapter at all |
|
|
85
|
-
| `below_floor` | an adapter that does not meet §2; the message lists every unmet feature and limit |
|
|
86
|
-
| `device_request_failed` | the adapter refused the device |
|
|
87
|
-
| `device_lost` | a working device went away while running |
|
|
88
|
-
|
|
89
|
-
A device lost while running arrives on `Renderer.onDeviceFailure` instead of being thrown. **The
|
|
90
|
-
renderer does not recover a lost device.** Everything it built lived on it, and the honest response
|
|
91
|
-
is a message and a reload. The signal exists so an application can say so and save what it has.
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## 3. Putting a model on the screen
|
|
96
|
-
|
|
97
|
-
meep has no module aliases: every import is a relative path, and the ones below are written from
|
|
98
|
-
`src/shade/` so they read like the tree in §10.
|
|
99
|
-
|
|
100
|
-
```js
|
|
101
|
-
import { Renderer } from "./renderer/Renderer.js";
|
|
102
|
-
import { Scene } from "./renderer/scene/Scene.js";
|
|
103
|
-
import { PerspectiveCamera } from "./renderer/camera/PerspectiveCamera.js";
|
|
104
|
-
import { make_sunlight } from "./renderer/light/make_sunlight.js";
|
|
105
|
-
import { place_light } from "./renderer/light/place_light.js";
|
|
106
|
-
import { EntityComponentDataset } from "../engine/ecs/EntityComponentDataset.js";
|
|
107
|
-
import { Transform64 } from "../engine/ecs/transform/Transform64.js";
|
|
108
|
-
import { prefab_compile } from "../engine/graphics3/prefab/prefab_compile.js";
|
|
109
|
-
import { prefab_place } from "../engine/graphics3/prefab/prefab_place.js";
|
|
110
|
-
import { load_gltf } from "./renderer/loader/gltf/load_gltf.js";
|
|
111
|
-
import { create_frame_loop } from "./renderer/create_frame_loop.js";
|
|
112
|
-
import { DEG_TO_RAD } from "../core/math/DEG_TO_RAD.js";
|
|
113
|
-
import Vector3 from "../core/geom/Vector3.js";
|
|
114
|
-
|
|
115
|
-
const canvas = document.querySelector("canvas");
|
|
116
|
-
|
|
117
|
-
const renderer = new Renderer();
|
|
118
|
-
|
|
119
|
-
await renderer.initialize({ context: canvas.getContext("webgpu") });
|
|
120
|
-
|
|
121
|
-
const camera = new PerspectiveCamera();
|
|
122
|
-
|
|
123
|
-
camera.transform.position.set(0, 2, -5);
|
|
124
|
-
camera.transform.lookAt(Vector3.zero);
|
|
125
|
-
camera.near = 0.1;
|
|
126
|
-
camera.far = 200;
|
|
127
|
-
camera.fov = 72 * DEG_TO_RAD;
|
|
128
|
-
camera.aspect = renderer.aspect_ratio;
|
|
129
|
-
|
|
130
|
-
const scene = new Scene();
|
|
131
|
-
|
|
132
|
-
// everything in a scene stands at an entity, so the scene names a dataset — a map of components,
|
|
133
|
-
// no systems
|
|
134
|
-
scene.dataset = new EntityComponentDataset();
|
|
135
|
-
|
|
136
|
-
// nothing is lit without one, and the deferred shading pass has no default light
|
|
137
|
-
place_light(scene, make_sunlight({ intensity: 2.2 }));
|
|
138
|
-
|
|
139
|
-
const bundle = await load_gltf("model.glb", { assetManager });
|
|
140
|
-
|
|
141
|
-
// the model as loaded is compiled once; a placement of it is entities of the dataset, one per node
|
|
142
|
-
// of the file, each with a row of the scene's GPU database — the scene's context is where the rows
|
|
143
|
-
// live
|
|
144
|
-
prefab_place(renderer.scenes.obtain(scene), scene.dataset, prefab_compile(bundle), new Transform64());
|
|
145
|
-
|
|
146
|
-
create_frame_loop(dt => renderer.render(camera, scene, dt));
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
That is the whole of it. Five things in it are worth stating outright.
|
|
150
|
-
|
|
151
|
-
**`initialize` is asynchronous and may fail normally.** Acquiring a WebGPU device has no synchronous
|
|
152
|
-
form and being refused one is an ordinary outcome, so it is a rejected promise carrying a
|
|
153
|
-
`ShadeDeviceFailure` rather than a constructor that throws. Omit `context` and a full-viewport canvas
|
|
154
|
-
is created for you; pass a `device` to share one you already have.
|
|
155
|
-
|
|
156
|
-
**`assetManager` is required, and here is where one comes from.** A glTF is several files — a
|
|
157
|
-
container, its `.bin` buffers, its images — and which of them are already in memory is not the
|
|
158
|
-
parser's business to know. Every request the parse makes goes back through the manager, so they are
|
|
159
|
-
cached, counted and cancellable like anything else the application loads. Two loaders are all
|
|
160
|
-
`load_gltf` asks for:
|
|
161
|
-
|
|
162
|
-
```js
|
|
163
|
-
const assetManager = new AssetManager({ context: {}, executor: new ConcurrentExecutor() });
|
|
164
|
-
|
|
165
|
-
await assetManager.registerLoader(GameAssetType.ArrayBuffer, new ArrayBufferLoader());
|
|
166
|
-
await assetManager.registerLoader(GameAssetType.ImageBitmap, new ImageBitmapAssetLoader());
|
|
167
|
-
|
|
168
|
-
assetManager.startup();
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Pass a `scope` alongside it to tie a whole load to one cancellable unit, and a `fileMap` when the
|
|
172
|
-
files come from a drop rather than from the network. An application already running meep's `Engine`
|
|
173
|
-
has a configured manager on it and should use that one.
|
|
174
|
-
|
|
175
|
-
**A model does not have to come from a file.** Everything `load_gltf` produces you can build
|
|
176
|
-
directly, which is §4 in the other direction and needs no asset manager at all:
|
|
177
|
-
|
|
178
|
-
```js
|
|
179
|
-
const transform = new Transform64();
|
|
180
|
-
|
|
181
|
-
transform.setTranslation(0, 1, 0);
|
|
182
|
-
transform.updateMatrix();
|
|
183
|
-
|
|
184
|
-
place_primitive(
|
|
185
|
-
renderer.scenes.obtain(scene),
|
|
186
|
-
meshlet_geometry_build_from_geometry(make_torus_knot_geometry(1, 0.3, 128, 16)),
|
|
187
|
-
new StandardShadeMaterial(),
|
|
188
|
-
transform
|
|
189
|
-
);
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
**`render` returns a boolean.** `false` means the previous frame has not finished and this one was
|
|
193
|
-
not submitted. It is not an error.
|
|
194
|
-
|
|
195
|
-
**Everything drawn or posed is an entity of the scene's dataset, holding a row of the scene's GPU
|
|
196
|
-
database.** `prefab_place` answers what it placed — the root entity, the instance, its animation
|
|
197
|
-
registration — and `prefab_unplace` takes it all back; `place_primitive` answers the entity,
|
|
198
|
-
`move_primitive` moves it and `remove_primitive` takes it back. A game calls none of them: its
|
|
199
|
-
systems take rows as components appear. A light is the same shape — it stands at an entity, and
|
|
200
|
-
`scene.lights.add(light, entity)` — `place_light(scene, light)`, for a caller with no entity of its
|
|
201
|
-
own — is what puts it in the scene. Its record's pose is that entity's row, read on the GPU every
|
|
202
|
-
frame.
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## 4. The type model
|
|
207
|
-
|
|
208
|
-
Everything in a scene is an **entity** of the scene's dataset, and what the renderer knows about it
|
|
209
|
-
is **a row** of the scene's GPU database, at `entity + 1` in two tables: `transforms` — its local TRS
|
|
210
|
-
and the row of its parent — and, for a primitive, `meshes` — which geometry, which material, and the
|
|
211
|
-
world bounds. Row 0 is the scene root, identity. There is no CPU object per node, no parent pointer
|
|
212
|
-
to follow and no world matrix to recompose: the GPU composes every chain each frame, from the rows,
|
|
213
|
-
and derives every mesh row's bounds under the composed matrix.
|
|
214
|
-
|
|
215
|
-
**An entity is placed through its `Transform64`, and what the GPU holds is derived from it.** A
|
|
216
|
-
root entity's row carries its world pose; an entity attached under another (`TransformAttachment`)
|
|
217
|
-
carries its local offset and names its parent's row. A game's systems write these as components
|
|
218
|
-
change; a scene with no systems places through `prefab_place` and `place_primitive`, and moves a
|
|
219
|
-
primitive through `move_primitive`, which marks the row for the next build. The box that is drawn
|
|
220
|
-
is the GPU's — `GPUSceneContext.bounds` reads it back, a frame or two behind — and nothing on the
|
|
221
|
-
CPU keeps a copy of it.
|
|
222
|
-
|
|
223
|
-
**An entity's transform and a camera's are different types, deliberately, and this is the one place
|
|
224
|
-
a newcomer will trip.** `Transform64` is a flat `Float64Array` holding its own matrix, posed through
|
|
225
|
-
`setTranslation(x, y, z)` / `setRotation(x, y, z, w)` / `setScale(x, y, z)` and refreshed with
|
|
226
|
-
`updateMatrix()`, with the buffer layout part of its contract so hot code can write it directly. A
|
|
227
|
-
`Camera` is **not** an entity — it carries meep's ECS `Transform`, whose `position`, `rotation` and
|
|
228
|
-
`scale` are observable vectors you assign into. The two keep distinct shapes on purpose and neither
|
|
229
|
-
has the other's accessors: `transform.position` is `undefined` on a `Transform64`, and so is
|
|
230
|
-
`camera.transform.translation`.
|
|
231
|
-
|
|
232
|
-
```js
|
|
233
|
-
transform.setTranslation(0, 1, 0); // Transform64 — then updateMatrix()
|
|
234
|
-
camera.transform.position.set(0, 2, -5); // Transform — reactive, no refresh call
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
```
|
|
238
|
-
Scene
|
|
239
|
-
├── dataset EntityComponentDataset — every entity the scene draws or poses; its rows
|
|
240
|
-
├── lights LightCollection — each at an entity; their own pipeline, not a mesh row
|
|
241
|
-
├── light_probe_volume
|
|
242
|
-
└── volumetrics
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
A **primitive** is an entity holding a `meshes` row. Its `ShadedGeometry` component names what the
|
|
246
|
-
row draws, so a tool can read it back:
|
|
247
|
-
|
|
248
|
-
```js
|
|
249
|
-
geometry // MeshletGeometry — what to draw
|
|
250
|
-
material // ShadeMaterial — how to draw it
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
and the row carries the world-space `bounding_box` / `bounding_sphere` the renderer derives for
|
|
254
|
-
culling.
|
|
255
|
-
|
|
256
|
-
### Geometry: two types, one of which is a precompute
|
|
257
|
-
|
|
258
|
-
**`Geometry`** is the authoring form: named `Attribute`s over typed arrays, an optional index, a
|
|
259
|
-
`bounding_box`/`bounding_sphere` computed on demand by `ensureBounds()`, and a `version` you bump
|
|
260
|
-
through `needsUpdate = true` when you rewrite the arrays. Attributes are read by name:
|
|
261
|
-
|
|
262
|
-
```js
|
|
263
|
-
geometry.getAttribute(StandardAttributes.Position).data // Float32Array
|
|
264
|
-
geometry.index.data // Uint32Array
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
Ten generators ship, all returning a `Geometry`, in `renderer/geometry/primitives/`:
|
|
268
|
-
`make_box_geometry`, `make_plane_geometry`, `make_polyhedron_geometry`, `make_octahedron_geometry`,
|
|
269
|
-
`make_uv_sphere_geometry`, `make_capsule_geometry`, `make_cylinder_geometry`, `make_cone_geometry`,
|
|
270
|
-
`make_torus_geometry`, `make_torus_knot_geometry`.
|
|
271
|
-
|
|
272
|
-
**`MeshletGeometry`** is the GPU form: triangles clustered into meshlets, attributes encoded, and a
|
|
273
|
-
BVH over the clusters. This is what a mesh row names, and it is what buys cluster culling and a
|
|
274
|
-
compact residency.
|
|
275
|
-
|
|
276
|
-
```js
|
|
277
|
-
const meshlets = meshlet_geometry_build_from_geometry(geometry);
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
**That build is a precompute.** Do it once, when the content is created or loaded. If your vertices
|
|
281
|
-
change every frame, you are on the wrong path — see §8.
|
|
282
|
-
|
|
283
|
-
Seven of the primitives also come ready built, as `MeshletGeometry` subclasses one per module in
|
|
284
|
-
`renderer/geometry/`: `BoxGeometry`, `PlaneGeometry`, `UVSphereGeometry`, `CapsuleGeometry`,
|
|
285
|
-
`CylinderGeometry`, `ConeGeometry` and `TorusGeometry`. Each constructor runs its generator and then
|
|
286
|
-
this build, so constructing one is the same precompute.
|
|
287
|
-
|
|
288
|
-
### Materials
|
|
289
|
-
|
|
290
|
-
`ShadeMaterial` is the base: `transparency_mode`, `draw_mode` and `draw_side`.
|
|
291
|
-
|
|
292
|
-
**`draw_side` changes what is drawn.** `Front` (the default) draws the front face, `Back` the back
|
|
293
|
-
face, `Double` both. A face seen from behind is shaded with its normal flipped, so `Double` is a
|
|
294
|
-
working double-sided material and not a hint. glTF `doubleSided: true` arrives as `Double` and
|
|
295
|
-
stays that way; glTF cannot ask for `Back`, which comes from authoring.
|
|
296
|
-
|
|
297
|
-
**Only `Double` costs a pipeline of its own.** It culls nothing, and no amount of winding expresses
|
|
298
|
-
that. `Front` and `Back` differ by winding alone, and the vertex shader reverses winding per
|
|
299
|
-
instance, so the two share a rasterization bucket and draw in one pass — which matters because a
|
|
300
|
-
bucket is not a pipeline here but a whole chain: extract, expand to meshlets, select a
|
|
301
|
-
virtual-geometry cut, one render pass.
|
|
302
|
-
|
|
303
|
-
**That is also why a mirrored instance works.** Place a mesh under a negative scale, or under a
|
|
304
|
-
parent that reflects, and it draws right way out, with the handedness of its tangent frame flipped
|
|
305
|
-
to match — the reflection and the material's side are the same one bit, combined before a corner is
|
|
306
|
-
chosen. glTF makes this the rule rather than an extra: the determinant of a node's world transform
|
|
307
|
-
decides its winding, and a conformant renderer has to read it.
|
|
308
|
-
|
|
309
|
-
Shadow passes cull the other face — so a one-sided surface casts from its back face and does not
|
|
310
|
-
shadow-acne against its own lit side. `Double` has no opposite and stays unculled in both.
|
|
311
|
-
|
|
312
|
-
`StandardShadeMaterial` is the PBR one everything uses:
|
|
313
|
-
|
|
314
|
-
| | |
|
|
315
|
-
|---|---|
|
|
316
|
-
| `texture_albedo` | base colour, alpha carries transparency |
|
|
317
|
-
| `diffuse_color` | multiplied with the albedo |
|
|
318
|
-
| `texture_normal` | tangent-space normals |
|
|
319
|
-
| `texture_orm` | occlusion / roughness (G) / metalness (B), as glTF packs it |
|
|
320
|
-
| `texture_emissive`, `emissive_factor` | |
|
|
321
|
-
| `roughness_factor`, `metallic_factor` | multiplied with the ORM texture |
|
|
322
|
-
| `transmission_factor`, `ior_factor` | |
|
|
323
|
-
| `ambient_factors` | a `LinearModifier` over the indirect contribution |
|
|
324
|
-
| `vt_stack` | opt-in virtual texturing; when set, albedo/normal/ORM come from streamed pages instead. Opaque materials only |
|
|
325
|
-
|
|
326
|
-
### Textures
|
|
327
|
-
|
|
328
|
-
Two objects, and the split matters: **`ShadeImage`** is the pixels, **`ShadeTexture`** is how to
|
|
329
|
-
sample them. Two materials sampling one image differently are two textures over one image, and the
|
|
330
|
-
image is uploaded once.
|
|
331
|
-
|
|
332
|
-
```js
|
|
333
|
-
const image = ShadeImage.fromImageBitmap(bitmap); // or fromSampler2D, or fromArrayBuffer
|
|
334
|
-
const texture = ShadeTexture.from(image);
|
|
335
|
-
|
|
336
|
-
texture.wrapS = TextureWrapType.Repeat;
|
|
337
|
-
texture.magFilter = TextureFilterType.Linear;
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
`ShadeTexture` carries filters, wrap modes and flags (mipmap generation is on by default), and both
|
|
341
|
-
types answer `equals`/`hash`, which is what lets the renderer de-duplicate uploads.
|
|
342
|
-
|
|
343
|
-
### What a glTF becomes
|
|
344
|
-
|
|
345
|
-
`load_gltf` returns a **`SceneBundle`** — three arrays out of the one file:
|
|
346
|
-
|
|
347
|
-
```js
|
|
348
|
-
bundle.nodes // SceneNode[] — the file's nodes in pre-order: name, parent, local TRS, geometry, material
|
|
349
|
-
bundle.skins // Skin[] — joints and meshes as indices into nodes
|
|
350
|
-
bundle.clips // ShadeAnimationClip[] — each channel targets an index into nodes
|
|
351
|
-
bundle.lights // {light, node}[] — anchored by index
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
The bundle is **the model as loaded, not a placement of it** — records, with no transform state
|
|
355
|
-
anyone could pose. It is compiled once into a `Prefab` (`prefab_compile`), and every placement is
|
|
356
|
-
spawned from that — `prefab_place` in a scene with no systems, `prefab_instantiate` under a game's
|
|
357
|
-
own root entity — with its own entities, its own skins and its own retargeted clips, addressed by
|
|
358
|
-
entity, which is what makes two of one model possible.
|
|
359
|
-
|
|
360
|
-
---
|
|
361
|
-
|
|
362
|
-
## 5. The frame
|
|
363
|
-
|
|
364
|
-
```
|
|
365
|
-
FrameStart view updated, nothing rasterized
|
|
366
|
-
↓
|
|
367
|
-
[ visibility buffer + G-buffer ]
|
|
368
|
-
AfterGBuffer surface properties written and readable, nothing lit
|
|
369
|
-
↓
|
|
370
|
-
[ clustered lighting, deferred shading, volumetrics ]
|
|
371
|
-
AfterLighting opaque lit and composited
|
|
372
|
-
↓
|
|
373
|
-
[ transparency ]
|
|
374
|
-
AfterTransparency the complete scene, HDR, internal resolution
|
|
375
|
-
↓
|
|
376
|
-
[ TAA / NSS upscale ] ← resolution changes here
|
|
377
|
-
BeforePresent HDR, output resolution, immediately before tonemapping
|
|
378
|
-
↓
|
|
379
|
-
[ tonemap → canvas ]
|
|
380
|
-
Overlay display space, drawing onto the canvas
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
**The upscale is a phase boundary and it is the one thing about the frame you cannot assume away.**
|
|
384
|
-
Three phases are before it and two are after. With `feature_taa_enabled === false` there is no
|
|
385
|
-
upscale and the two sides are the same size. Nothing in this document states a resolution as a
|
|
386
|
-
constant: ask `frame.resolution`, which is derived from the live handle and is right in every
|
|
387
|
-
configuration, including under dynamic resolution scaling.
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## 6. Adding your own work to the frame — render extensions
|
|
392
|
-
|
|
393
|
-
An extension is **a named object with declarations and one method**.
|
|
394
|
-
|
|
395
|
-
```js
|
|
396
|
-
import { RenderExtension } from "./renderer/extension/RenderExtension.js";
|
|
397
|
-
import { FramePhase } from "./renderer/extension/FramePhase.js";
|
|
398
|
-
import { SceneColor } from "./renderer/extension/SceneColor.js";
|
|
399
|
-
import { ViewTextures } from "./renderer/extension/ViewTextures.js";
|
|
400
|
-
|
|
401
|
-
class PathExtension extends RenderExtension {
|
|
402
|
-
name = "path display";
|
|
403
|
-
|
|
404
|
-
phase = FramePhase.AfterTransparency;
|
|
405
|
-
|
|
406
|
-
record(frame) {
|
|
407
|
-
if (this.#batch.count === 0) {
|
|
408
|
-
// nothing to draw, and the frame's colour is left exactly as it was
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const scene = frame.get(SceneColor);
|
|
413
|
-
const view = frame.get(ViewTextures);
|
|
414
|
-
|
|
415
|
-
const drawn = this.#renderer.graph_draw({
|
|
416
|
-
graph: frame.graph,
|
|
417
|
-
batch: this.#batch,
|
|
418
|
-
color: scene.color,
|
|
419
|
-
depth: view.depth,
|
|
420
|
-
camera: frame.view.camera.buffer
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
scene.color = drawn.color;
|
|
424
|
-
view.depth = drawn.depth;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const registered = renderer.add_extension(new PathExtension());
|
|
429
|
-
// ...
|
|
430
|
-
renderer.remove_extension(registered);
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
That is the whole mechanism. The rest of this section is what the four lines of it mean.
|
|
434
|
-
|
|
435
|
-
**An extension draws over transparency, never inside it.** At `AfterTransparency` the transparent
|
|
436
|
-
surfaces are already resolved into the colour, so what an extension draws there covers them whatever
|
|
437
|
-
their depth. Content that has to sit among transparent surfaces — in front of some, behind others —
|
|
438
|
-
is a transparency channel instead (§8).
|
|
439
|
-
|
|
440
|
-
### Records are the channel
|
|
441
|
-
|
|
442
|
-
`record(frame)` **returns nothing.** Everything an extension exchanges with the frame goes through
|
|
443
|
-
typed **records**: you read a frame-graph handle out of one and write a replacement back, and
|
|
444
|
-
**writing a field is publication** — everything after you carries what you left.
|
|
445
|
-
|
|
446
|
-
There is no privileged resource. The scene colour is published exactly the way a G-buffer target is,
|
|
447
|
-
and exactly the way a record of your own is.
|
|
448
|
-
|
|
449
|
-
| record | carried at | writable |
|
|
450
|
-
|---|---|---|
|
|
451
|
-
| `SceneColor` — `.color`, HDR | `AfterLighting` … `BeforePresent` | yes |
|
|
452
|
-
| `GBufferTextures` — `.albedo` (AO in alpha), `.normal`, `.pbr`, `.emissive` | `AfterGBuffer` … `BeforePresent` | **`AfterGBuffer` only** |
|
|
453
|
-
| `ViewTextures` — `.depth`, `.depth_previous`, `.visibility_mesh`, `.visibility_triangle` | `AfterGBuffer` … `BeforePresent` | yes |
|
|
454
|
-
| `PresentTarget` — `.canvas` | `Overlay` only | yes |
|
|
455
|
-
|
|
456
|
-
Three of those rows are the whole reason phases exist:
|
|
457
|
-
|
|
458
|
-
- **`SceneColor` is not carried at `Overlay`.** A texture may not be a render attachment and a
|
|
459
|
-
sampled source in the same pass, so at `Overlay` the canvas is written and cannot be read. An
|
|
460
|
-
effect that needs the finished image as *input* — a colour grade, a full-screen re-composite —
|
|
461
|
-
belongs at `BeforePresent`. You find that out from `frame.get(SceneColor)`, which fails with a
|
|
462
|
-
sentence naming the phases that do carry it, rather than from a `-1` failing inside a binding
|
|
463
|
-
builder.
|
|
464
|
-
- **`GBufferTextures` is in/out at `AfterGBuffer` and frozen afterwards.** Changing surface
|
|
465
|
-
properties means reading the targets, writing replacements and publishing the new handles — two of
|
|
466
|
-
the four are integer formats and cannot be blended, and nothing can be attachment and source at
|
|
467
|
-
once. It stays *readable* to `BeforePresent` because outline and selection effects legitimately
|
|
468
|
-
read it late; at that phase it is on the far side of the upscale and is therefore **a different
|
|
469
|
-
size from the colour beside it**. `frame.describe(handle)` is how a pass working across that
|
|
470
|
-
boundary learns the ratio.
|
|
471
|
-
- **`PresentTarget` has one field and is not called `SceneColor` on purpose.** There is no colour
|
|
472
|
-
there to be tempted by. Keep the existing contents with a `load` op, write only your own pixels,
|
|
473
|
-
and publish the result back — that is what lets two overlay extensions compose.
|
|
474
|
-
|
|
475
|
-
### Extending the vocabulary
|
|
476
|
-
|
|
477
|
-
An extension can publish a record of its own for another extension to read, and doing so costs no
|
|
478
|
-
edit to the renderer:
|
|
479
|
-
|
|
480
|
-
```js
|
|
481
|
-
class VisibilityGrid extends FrameRecord {
|
|
482
|
-
#grid = -1;
|
|
483
|
-
|
|
484
|
-
static is_present_at(phase) { return true; }
|
|
485
|
-
|
|
486
|
-
get grid() { return this.read(this.#grid); }
|
|
487
|
-
set grid(handle) { this.#grid = this.publish(handle); }
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// at FrameStart
|
|
491
|
-
frame.create(VisibilityGrid).grid = record_the_grid_update(frame.graph);
|
|
492
|
-
|
|
493
|
-
// at AfterTransparency, in another extension
|
|
494
|
-
if (frame.has(VisibilityGrid)) { … frame.get(VisibilityGrid).grid … }
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
`has` / `get` / `create` are three calls rather than one `get_or_create` because **absent has to stay
|
|
498
|
-
distinguishable from empty.**
|
|
499
|
-
|
|
500
|
-
### Ordering
|
|
501
|
-
|
|
502
|
-
Order is **declared**, never emergent:
|
|
503
|
-
|
|
504
|
-
```js
|
|
505
|
-
class DecalExtension extends RenderExtension {
|
|
506
|
-
phase = FramePhase.AfterGBuffer;
|
|
507
|
-
relative_order = 1; // a decal composites over surfaces already written
|
|
508
|
-
}
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
`relative_order` defaults to `0`; lower records first, and any finite number will do — negative to
|
|
512
|
-
pull forward, positive to push back. Two extensions on one phase with the same value record in
|
|
513
|
-
**ascending `name` order**, which is reproducible rather than correct and is chosen precisely so
|
|
514
|
-
that adding an `await` to a system's startup cannot change the picture.
|
|
515
|
-
|
|
516
|
-
**It names nothing.** An extension states where it belongs from what it does — a fog conceals, so it
|
|
517
|
-
is late; a debug overlay must not be concealed, so it is later still — and needs no import of, or
|
|
518
|
-
reference to, whatever else is on the phase. That is deliberately less precise than a dependency
|
|
519
|
-
would be: it cannot say "immediately after that one", and there is no cycle to report because there
|
|
520
|
-
is no graph. An effect that genuinely needs one particular extension's result should read a record
|
|
521
|
-
that extension published, which is a real dependency rather than a positional one.
|
|
522
|
-
|
|
523
|
-
### What keeps your work alive
|
|
524
|
-
|
|
525
|
-
The graph elides work whose output nothing consumes, and that applies to an extension exactly as it
|
|
526
|
-
does to a built-in pass. Thread a result into a record and it is consumed. Write something imported —
|
|
527
|
-
the canvas, an imported texture — and it is a side effect. Want work that runs regardless and
|
|
528
|
-
`make_side_effect()` on the pass builder says so.
|
|
529
|
-
|
|
530
|
-
**An extension whose whole recording is elided is a correct, cheaper frame, not a failure**, and
|
|
531
|
-
nothing reports it as one. It is a grey cluster with your extension's name on it in a graph dump.
|
|
532
|
-
|
|
533
|
-
### The rest of `FrameContext`
|
|
534
|
-
|
|
535
|
-
| | |
|
|
536
|
-
|---|---|
|
|
537
|
-
| `frame.graph` | the frame's `FrameGraph`. Record into it with the `graph_*` helpers, exactly as the built-in passes do |
|
|
538
|
-
| `frame.view` | the `GPUViewContext` — camera, uniforms, frame index |
|
|
539
|
-
| `frame.phase` | which phase this is |
|
|
540
|
-
| `frame.resolution` | `[width, height]` of what you are drawing into, from the live handle |
|
|
541
|
-
| `frame.describe(handle)` | the descriptor of any resource, for a pass that has to relate two of them |
|
|
542
|
-
| `frame.is_open` | records are per-frame. A record held past the end of the frame is a retained handle by another name, and both reading and publishing through one say so |
|
|
543
|
-
|
|
544
|
-
### Not built yet
|
|
545
|
-
|
|
546
|
-
Two pieces of the extension design are named in `RENDER_EXTENSION_DESIGN.md` and **do not exist**:
|
|
547
|
-
`RenderExtensionRegistry.describe()` (printing the pipeline as data) and `FrameProduct` (opting into
|
|
548
|
-
optional frame products such as velocity or a readable copy of the colour). Do not write against
|
|
549
|
-
them.
|
|
550
|
-
|
|
551
|
-
---
|
|
552
|
-
|
|
553
|
-
## 7. Rendering somewhere other than the canvas
|
|
554
|
-
|
|
555
|
-
```js
|
|
556
|
-
import { GPUTextureContext } from "./renderer/texture/GPUTextureContext.js";
|
|
557
|
-
|
|
558
|
-
renderer.render_to_target(camera, scene, dt, GPUTextureContext.fromTexture(texture, renderer.device));
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
`render()` *is* this call with the canvas's current texture as the target — the canvas is one target
|
|
562
|
-
among others, not a privileged one.
|
|
563
|
-
|
|
564
|
-
**The target says where the picture goes, not how big it is.** Everything the frame is built out of —
|
|
565
|
-
the G-buffer, the visibility buffer, the depth ping-pong, the temporal history — is sized from
|
|
566
|
-
`renderer.output_resolution`, so **the target must be that size**. A caller wanting a different one
|
|
567
|
-
calls `renderer.resize()` first and puts the size back afterwards.
|
|
568
|
-
|
|
569
|
-
This is what makes a frame readable at all: a canvas texture is configured by the presentation path
|
|
570
|
-
and cannot carry `COPY_SRC`; a texture you own can. Thumbnails, asset previews and image-based tests
|
|
571
|
-
all go through here.
|
|
572
|
-
|
|
573
|
-
---
|
|
574
|
-
|
|
575
|
-
## 8. Content whose vertices change — the dynamic-mesh path
|
|
576
|
-
|
|
577
|
-
A trail behind a projectile, a ribbon, a tube along a path, a batch of CPU-simulated particles, a
|
|
578
|
-
debug line: content rewritten every frame cannot pay for a meshlet build, and rebuilding meshlets per
|
|
579
|
-
frame to force it down the standard path is explicitly not the answer.
|
|
580
|
-
|
|
581
|
-
```js
|
|
582
|
-
const mesh = DynamicMesh.from(geometry, [1, 0.5, 0, 1]); // Geometry, not MeshletGeometry
|
|
583
|
-
|
|
584
|
-
batch.add(mesh); // DynamicMeshBatch, yours — not the Scene's
|
|
585
|
-
mesh.visible = false; // hide without leaving the batch
|
|
586
|
-
geometry.needsUpdate = true; // you say when the vertices changed; nothing watches the arrays
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
This is **a separate path, not a variant of the standard one.** A `DynamicMesh` holds a plain
|
|
590
|
-
`Geometry` and its own `transform`, is no entity of the scene, gets no row in the scene database,
|
|
591
|
-
and is drawn by a pipeline of its own after the standard geometry path has finished with the frame.
|
|
592
|
-
|
|
593
|
-
Drawing it over the scene is a `GPUDynamicMeshRenderer` inside an extension of yours (§6); drawing it
|
|
594
|
-
inside the transparency pipeline is a `DynamicMeshAVBOITChannel` (below). Two things to know either
|
|
595
|
-
way:
|
|
596
|
-
|
|
597
|
-
**There is one vertex layout and it is fixed** — 8 floats: position at `[0..2]`, one reserved slot at
|
|
598
|
-
`[3]`, RGBA colour at `[4..7]`. A dynamic mesh is drawn by a single pipeline, so letting each geometry
|
|
599
|
-
declare its own attribute set would buy nothing and cost either a pipeline per layout or a shader
|
|
600
|
-
that branches per vertex. Your geometry needs a position attribute and an index; a colour attribute is
|
|
601
|
-
optional and is multiplied by the mesh's flat `color`.
|
|
602
|
-
|
|
603
|
-
**`GPUDynamicMeshRenderer.opaque` chooses blending or depth**, and it is a property of the pass
|
|
604
|
-
rather than of a mesh — the whole batch moves together. Left `false` the content is blended and only depth-tests,
|
|
605
|
-
which is what effects want. Set it and the pass writes depth instead: a solid surface with no meshlet
|
|
606
|
-
build behind it has no depth ordering otherwise, and no CPU sort can give it one, because a painter's
|
|
607
|
-
order over triangles is wrong wherever two of them interpenetrate.
|
|
608
|
-
|
|
609
|
-
`graph_draw` hands back `{ color, depth }`, and an extension publishes both, as in §6. The depth is a
|
|
610
|
-
new handle when the pass wrote it. Anything reading the scene depth after you — depth of field,
|
|
611
|
-
motion blur, the NSS upscaler, the next extension — has to be given that handle; a development build
|
|
612
|
-
refuses the frame when it is not. Left `false`, the depth comes back as it went in.
|
|
613
|
-
|
|
614
|
-
That is also what decides the phase for solid content. At `AfterLighting` transparency has not run
|
|
615
|
-
yet, and it tests against the depth you published, so glass in front of the batch covers it. At
|
|
616
|
-
`AfterTransparency` the glass is already resolved into the colour, and the batch is drawn over it.
|
|
617
|
-
|
|
618
|
-
Residency is keyed on **the geometry**, not on batch membership, so a mesh that blinks off for a
|
|
619
|
-
frame or is removed and re-added keeps its buffers.
|
|
620
|
-
|
|
621
|
-
### Inside the transparency pipeline instead of over it
|
|
622
|
-
|
|
623
|
-
Blended content drawn by an extension composites over transparency that is already resolved: a trail
|
|
624
|
-
behind a pane of glass is drawn on top of the glass. A batch that has to interleave with transparent
|
|
625
|
-
surfaces by depth is registered as a **transparency channel** instead:
|
|
626
|
-
|
|
627
|
-
```js
|
|
628
|
-
const channel = new DynamicMeshAVBOITChannel(batch);
|
|
629
|
-
|
|
630
|
-
renderer.add_transparency_channel(channel);
|
|
631
|
-
// ...
|
|
632
|
-
renderer.remove_transparency_channel(channel);
|
|
633
|
-
channel.destroy(); // the channel owns the batch's GPU buffers
|
|
634
|
-
channel.release(geometry); // ... and, like the renderer, frees a geometry gone for good
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
The channel is an `AVBOITSideChannel`, the contract the GPU particles already implement: its
|
|
638
|
-
fragments go into the same extinction volume and accumulators as the transparent meshes and the
|
|
639
|
-
particles, so all three occlude each other order-independently, within AVBOIT's own limits (two
|
|
640
|
-
surfaces in one depth slice blend rather than sort). What is drawn is straight alpha, unlit, fogged
|
|
641
|
-
by the volumetrics at its depth, depth-tested against the opaque scene and never written. A mesh is
|
|
642
|
-
drawn when it is visible, has positive flat alpha and has a triangle with a vertex of positive alpha;
|
|
643
|
-
a batch with none says so, and costs the frame nothing. **Under `ShadeTransparencyMode.MBOIT`, or
|
|
644
|
-
with transparency off, a channel is not drawn at all** — as the particles are not.
|
|
645
|
-
|
|
646
|
-
What that costs and changes: a channel with something live makes the transparency pipeline run for
|
|
647
|
-
the frame even when nothing else is transparent; its depth range shares the pipeline's 128 slices with
|
|
648
|
-
every transparent surface on screen (off-screen meshes mark none); and what extensions draw at
|
|
649
|
-
`AfterTransparency` — path display, the selection highlight — lands over it, since it is resolved
|
|
650
|
-
before that phase. Registering the same channel twice keeps one.
|
|
651
|
-
|
|
652
|
-
From the ECS side the same registration goes through `GraphicsEngine.add_transparency_channel`, which
|
|
653
|
-
holds channels across a restart the way `add_extension` holds extensions. `Trail3DSystem` draws
|
|
654
|
-
trails this way.
|
|
655
|
-
|
|
656
|
-
---
|
|
657
|
-
|
|
658
|
-
## 9. Frame-level switches
|
|
659
|
-
|
|
660
|
-
| | |
|
|
661
|
-
|---|---|
|
|
662
|
-
| `feature_shadows_enabled` | |
|
|
663
|
-
| `feature_taa_enabled` | when off, there is no upscale and the phases either side of it are the same size |
|
|
664
|
-
| `transparency_mode` | `ShadeTransparencyMode.AVBOIT` (default) or `.MBOIT`; both run at the one transparency call site with the same inputs, safe to flip per frame — except that side channels (the GPU particles, transparency channels such as trails) are drawn under AVBOIT only |
|
|
665
|
-
| `avboit` | `AVBOITSettings`: `transmission_tint_from_base_color` (default off) filters transmitted light by base colour per channel under AVBOIT — off, a flip of `transparency_mode` changes no material's meaning |
|
|
666
|
-
| `internal_resolution_scale` | render internally below output resolution; `DynamicResolutionScaling` drives it from frame time |
|
|
667
|
-
| `pixel_ratio`, `output_resolution`, `aspect_ratio`, `resize(x, y)` | |
|
|
668
|
-
| `motion_blur`, `dof`, `dof_unreal`, `nss`, `restir_di`, `path_tracer` | sub-objects with their own settings |
|
|
669
|
-
| `frame_count` | |
|
|
670
|
-
| `profile_session` | assign a `GPUProfileSession` to record GPU timings; null when nothing is profiling |
|
|
671
|
-
|
|
672
|
-
---
|
|
673
|
-
|
|
674
|
-
## 10. Where things live
|
|
675
|
-
|
|
676
|
-
```
|
|
677
|
-
shade/renderer/
|
|
678
|
-
├── Renderer.js the whole surface in §2, §7, §9
|
|
679
|
-
├── scene/ Scene, GPUSceneContext, rows/ — GPUSceneRows, place_primitive; DynamicMesh
|
|
680
|
-
├── geometry/ Geometry, MeshletGeometry, the builders, primitives/
|
|
681
|
-
├── material/ ShadeMaterial, StandardShadeMaterial
|
|
682
|
-
├── texture/ ShadeTexture, GPUTextureContext, source/ShadeImage
|
|
683
|
-
├── light/ LightCollection, make_sunlight, model/{Light,DirectionalLight,PointLight,SpotLight}
|
|
684
|
-
├── camera/ PerspectiveCamera
|
|
685
|
-
├── loader/gltf/ load_gltf, SceneBundle
|
|
686
|
-
├── extension/ RenderExtension, FrameContext, FrameRecord, the four records
|
|
687
|
-
├── dynamic/ GPUDynamicMeshRenderer, DynamicMeshAVBOITChannel, and their one vertex layout
|
|
688
|
-
└── animation/ GPUAnimationManager — skins and clips
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
`prefab_compile` and `prefab_place` live with the ECS, in `engine/graphics3/prefab/`, because a
|
|
692
|
-
placement is entities.
|
|
693
|
-
|
|
694
|
-
`shade/device/mock/` holds a **validating software device**: no shader execution, but real validation
|
|
695
|
-
of bindings, passes, resources and texture copies. It is how the renderer's own tests run without a
|
|
696
|
-
GPU, and it is available to yours. Specs must never acquire a real device — see `vitest.config.mjs`,
|
|
697
|
-
which explains at length why.
|