From b5e561904c5f2c0f3c010d1c083736ba352b3862 Mon Sep 17 00:00:00 2001 From: Rbanh Date: Thu, 9 Jul 2026 23:43:47 -0400 Subject: [PATCH] Upgrade to Bevy 0.19 and harden editor workflows --- .cargo/config.toml | 1 + .github/workflows/ci.yml | 2 +- .vscode/tasks.json | 13 + Cargo.lock | 2323 +++++++------- Cargo.toml | 32 +- README.md | 40 +- assets/levels/editor_scene.scn.ron | 2739 +++++++++++------ ...a7f-454d-b9ab-93ebf0feccbe.static_mesh.ron | 2 +- crates/editor/src/assets/asset_db.rs | 27 +- crates/editor/src/assets/operators.rs | 6 +- crates/editor/src/assets/static_mesh.rs | 6 +- crates/editor/src/assets/thumbnails/cache.rs | 2 +- crates/editor/src/assets/thumbnails/job.rs | 2 +- crates/editor/src/assets/thumbnails/studio.rs | 12 +- crates/editor/src/ext/extensibility.rs | 187 +- crates/editor/src/history/mod.rs | 11 +- crates/editor/src/lib.rs | 2 - crates/editor/src/project/mod.rs | 1 - crates/editor/src/project/workspace.rs | 150 - crates/editor/src/scene/scene_io.rs | 83 +- crates/editor/src/ui/asset_browser/panel.rs | 8 + crates/editor/src/ui/dock_tabs.rs | 19 +- crates/editor/src/ui/fonts.rs | 21 + crates/editor/src/ui/hierarchy.rs | 76 +- crates/editor/src/ui/hierarchy_ops.rs | 17 +- crates/editor/src/ui/hierarchy_state.rs | 13 +- crates/editor/src/ui/inspector.rs | 25 +- crates/editor/src/ui/menu.rs | 16 +- crates/editor/src/ui/mod.rs | 58 +- crates/editor/src/ui/status_bar.rs | 112 +- crates/editor/src/ui/theme.rs | 6 +- crates/editor/src/ui/toolbar.rs | 54 +- crates/editor/src/ui/viewport_chrome.rs | 62 +- crates/editor/src/viewport/actor_icons.rs | 4 +- crates/editor/src/viewport/brush_edit.rs | 383 ++- crates/editor/src/viewport/brush_tool.rs | 18 +- crates/editor/src/viewport/camera.rs | 7 +- crates/editor/src/viewport/panel.rs | 4 + crates/editor/src/viewport/render_view.rs | 20 +- .../src/viewport/rendering_diagnostics.rs | 8 +- crates/editor/src/viewport/selection.rs | 26 +- .../editor/src/viewport/selection_outline.rs | 8 +- crates/editor/src/viewport/viewport_mode.rs | 4 + crates/game/src/lib.rs | 9 +- crates/game_hot/src/lib.rs | 8 +- crates/game_hot/src/player/spawn.rs | 1 + crates/game_hot/src/rendering/camera_fx.rs | 131 +- .../src/rendering/fullscreen_effects.rs | 32 +- crates/game_hot/src/rendering/mod.rs | 2 +- crates/game_hot/src/rendering/solari.rs | 12 +- .../game_hot/src/rendering/viewport_camera.rs | 42 +- crates/game_hot/src/rendering/volumes.rs | 4 + crates/game_hot/src/world/lighting.rs | 6 +- crates/scene/src/lib.rs | 5 +- crates/scene/src/migrate.rs | 4 +- crates/settings/src/lib.rs | 8 +- crates/shared/src/actor.rs | 4 +- crates/shared/src/brush_math.rs | 30 +- crates/shared/src/components.rs | 13 +- crates/shared/src/hydration/brushes.rs | 55 +- crates/shared/src/hydration/lights.rs | 28 +- crates/shared/src/hydration/materials.rs | 8 +- crates/shared/src/hydration/mod.rs | 67 +- crates/shared/src/hydration/models.rs | 6 +- crates/shared/src/hydration/prefabs.rs | 6 +- crates/shared/src/hydration/static_meshes.rs | 4 + crates/shared/src/hydration/strip.rs | 11 +- crates/shared/src/hydration/visibility.rs | 84 +- docs/README.md | 3 +- .../0002-bevy-version-and-migration-policy.md | 4 +- ...endering-tiers-and-post-process-volumes.md | 6 +- .../0015-viewport-camera-stack-ownership.md | 4 +- docs/adr/0016-unified-rendering-contract.md | 4 +- .../adr/0017-normalized-static-mesh-assets.md | 2 +- .../0019-local-bevy-render-timeout-patch.md | 13 +- docs/adr/0022-bevy-0-19-upgrade.md | 33 + docs/editor/README.md | 4 +- docs/editor/architecture.md | 16 +- docs/editor/brp.md | 8 +- docs/editor/brushes.md | 6 +- docs/editor/release-notes.md | 2 +- docs/editor/rendering.md | 5 +- docs/editor/roadmap.md | 4 +- third_party/bevy_render/Cargo.toml | 296 -- third_party/bevy_render/Cargo.toml.orig | 155 - third_party/bevy_render/README.md | 7 - third_party/bevy_render/src/alpha.rs | 62 - .../src/batching/gpu_preprocessing.rs | 2187 ------------- third_party/bevy_render/src/batching/mod.rs | 225 -- .../src/batching/no_gpu_preprocessing.rs | 182 -- third_party/bevy_render/src/bindless.wgsl | 37 - third_party/bevy_render/src/camera.rs | 702 ----- .../bevy_render/src/color_operations.wgsl | 47 - .../erased_render_asset_diagnostic_plugin.rs | 81 - .../bevy_render/src/diagnostic/internal.rs | 711 ----- .../mesh_allocator_diagnostic_plugin.rs | 91 - third_party/bevy_render/src/diagnostic/mod.rs | 199 -- .../render_asset_diagnostic_plugin.rs | 77 - .../bevy_render/src/diagnostic/tracy_gpu.rs | 69 - .../bevy_render/src/erased_render_asset.rs | 427 --- .../bevy_render/src/experimental/mod.rs | 6 - .../mesh_preprocess_types.wgsl | 69 - .../src/experimental/occlusion_culling/mod.rs | 104 - .../bevy_render/src/extract_component.rs | 236 -- .../bevy_render/src/extract_instances.rs | 137 - third_party/bevy_render/src/extract_param.rs | 177 -- .../bevy_render/src/extract_resource.rs | 70 - third_party/bevy_render/src/globals.rs | 79 - third_party/bevy_render/src/globals.wgsl | 16 - .../src/gpu_component_array_buffer.rs | 59 - third_party/bevy_render/src/gpu_readback.rs | 414 --- third_party/bevy_render/src/lib.rs | 582 ---- third_party/bevy_render/src/maths.wgsl | 186 -- third_party/bevy_render/src/mesh/allocator.rs | 1056 ------- third_party/bevy_render/src/mesh/mod.rs | 203 -- .../bevy_render/src/pipelined_rendering.rs | 204 -- third_party/bevy_render/src/render_asset.rs | 542 ---- .../bevy_render/src/render_graph/app.rs | 174 -- .../src/render_graph/camera_driver_node.rs | 107 - .../bevy_render/src/render_graph/context.rs | 286 -- .../bevy_render/src/render_graph/edge.rs | 57 - .../bevy_render/src/render_graph/graph.rs | 918 ------ .../bevy_render/src/render_graph/mod.rs | 56 - .../bevy_render/src/render_graph/node.rs | 426 --- .../bevy_render/src/render_graph/node_slot.rs | 165 - .../bevy_render/src/render_phase/draw.rs | 396 --- .../src/render_phase/draw_state.rs | 671 ---- .../bevy_render/src/render_phase/mod.rs | 1911 ------------ .../src/render_phase/rangefinder.rs | 39 - .../render_resource/batched_uniform_buffer.rs | 157 - .../src/render_resource/bind_group.rs | 740 ----- .../src/render_resource/bind_group_entries.rs | 322 -- .../src/render_resource/bind_group_layout.rs | 63 - .../bind_group_layout_entries.rs | 592 ---- .../src/render_resource/bindless.rs | 374 --- .../bevy_render/src/render_resource/buffer.rs | 70 - .../src/render_resource/buffer_vec.rs | 587 ---- .../src/render_resource/gpu_array_buffer.rs | 116 - .../bevy_render/src/render_resource/mod.rs | 74 - .../src/render_resource/pipeline.rs | 199 -- .../src/render_resource/pipeline_cache.rs | 882 ------ .../render_resource/pipeline_specializer.rs | 259 -- .../src/render_resource/resource_macros.rs | 39 - .../src/render_resource/specializer.rs | 353 --- .../src/render_resource/storage_buffer.rs | 285 -- .../src/render_resource/texture.rs | 166 - .../src/render_resource/uniform_buffer.rs | 402 --- .../bevy_render/src/renderer/graph_runner.rs | 292 -- third_party/bevy_render/src/renderer/mod.rs | 689 ----- .../src/renderer/raw_vulkan_init.rs | 148 - .../bevy_render/src/renderer/render_device.rs | 310 -- .../bevy_render/src/renderer/wgpu_wrapper.rs | 50 - third_party/bevy_render/src/settings.rs | 226 -- third_party/bevy_render/src/storage.rs | 158 - third_party/bevy_render/src/sync_component.rs | 42 - third_party/bevy_render/src/sync_world.rs | 602 ---- .../bevy_render/src/texture/fallback_image.rs | 274 -- .../bevy_render/src/texture/gpu_image.rs | 153 - .../src/texture/manual_texture_view.rs | 68 - third_party/bevy_render/src/texture/mod.rs | 73 - .../src/texture/texture_attachment.rs | 172 -- .../bevy_render/src/texture/texture_cache.rs | 108 - third_party/bevy_render/src/view/mod.rs | 1172 ------- third_party/bevy_render/src/view/view.wgsl | 272 -- .../bevy_render/src/view/visibility/mod.rs | 54 - .../bevy_render/src/view/visibility/range.rs | 228 -- .../bevy_render/src/view/window/mod.rs | 490 --- .../bevy_render/src/view/window/screenshot.rs | 695 ----- .../src/view/window/screenshot.wgsl | 16 - third_party/bevy_ufbx/.cargo-ok | 1 + third_party/bevy_ufbx/.cargo_vcs_info.json | 6 + .../.github/ISSUE_TEMPLATE/bug_report.md | 38 + .../.github/ISSUE_TEMPLATE/feature_request.md | 20 + third_party/bevy_ufbx/.github/dependabot.yml | 11 + .../bevy_ufbx/.github/workflows/ci.yml | 107 + third_party/bevy_ufbx/.gitignore | 24 + third_party/bevy_ufbx/Cargo.toml | 109 + third_party/bevy_ufbx/Cargo.toml.orig | 29 + .../{bevy_render => bevy_ufbx}/LICENSE-APACHE | 25 + .../{bevy_render => bevy_ufbx}/LICENSE-MIT | 2 + third_party/bevy_ufbx/README.md | 112 + third_party/bevy_ufbx/examples/load_fbx.rs | 92 + third_party/bevy_ufbx/src/error.rs | 28 + third_party/bevy_ufbx/src/label.rs | 63 + third_party/bevy_ufbx/src/lib.rs | 53 + third_party/bevy_ufbx/src/loader.rs | 150 + third_party/bevy_ufbx/src/material.rs | 246 ++ third_party/bevy_ufbx/src/mesh.rs | 218 ++ third_party/bevy_ufbx/src/node.rs | 135 + third_party/bevy_ufbx/src/scene.rs | 142 + third_party/bevy_ufbx/src/types.rs | 195 ++ third_party/bevy_ufbx/src/utils.rs | 58 + third_party/bevy_ufbx/tests/basic_loading.rs | 25 + third_party/bevy_ufbx/tests/integration.rs | 99 + third_party/bevy_ufbx/tests/label.rs | 78 + .../bevy_ufbx/tests/load_all_ufbx_data.rs | 160 + .../bevy_ufbx/tests/loader_settings.rs | 65 + third_party/bevy_ufbx/tests/types.rs | 146 + third_party/bevy_ufbx/tests/utils.rs | 96 + third_party/transform-gizmo-bevy/Cargo.toml | 42 +- third_party/transform-gizmo-bevy/src/lib.rs | 32 +- .../transform-gizmo-bevy/src/render.rs | 86 +- xtask/Cargo.toml | 7 +- xtask/src/clean_target.rs | 2 +- 204 files changed, 6972 insertions(+), 29907 deletions(-) delete mode 100644 crates/editor/src/project/workspace.rs create mode 100644 docs/adr/0022-bevy-0-19-upgrade.md delete mode 100644 third_party/bevy_render/Cargo.toml delete mode 100644 third_party/bevy_render/Cargo.toml.orig delete mode 100644 third_party/bevy_render/README.md delete mode 100644 third_party/bevy_render/src/alpha.rs delete mode 100644 third_party/bevy_render/src/batching/gpu_preprocessing.rs delete mode 100644 third_party/bevy_render/src/batching/mod.rs delete mode 100644 third_party/bevy_render/src/batching/no_gpu_preprocessing.rs delete mode 100644 third_party/bevy_render/src/bindless.wgsl delete mode 100644 third_party/bevy_render/src/camera.rs delete mode 100644 third_party/bevy_render/src/color_operations.wgsl delete mode 100644 third_party/bevy_render/src/diagnostic/erased_render_asset_diagnostic_plugin.rs delete mode 100644 third_party/bevy_render/src/diagnostic/internal.rs delete mode 100644 third_party/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs delete mode 100644 third_party/bevy_render/src/diagnostic/mod.rs delete mode 100644 third_party/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs delete mode 100644 third_party/bevy_render/src/diagnostic/tracy_gpu.rs delete mode 100644 third_party/bevy_render/src/erased_render_asset.rs delete mode 100644 third_party/bevy_render/src/experimental/mod.rs delete mode 100644 third_party/bevy_render/src/experimental/occlusion_culling/mesh_preprocess_types.wgsl delete mode 100644 third_party/bevy_render/src/experimental/occlusion_culling/mod.rs delete mode 100644 third_party/bevy_render/src/extract_component.rs delete mode 100644 third_party/bevy_render/src/extract_instances.rs delete mode 100644 third_party/bevy_render/src/extract_param.rs delete mode 100644 third_party/bevy_render/src/extract_resource.rs delete mode 100644 third_party/bevy_render/src/globals.rs delete mode 100644 third_party/bevy_render/src/globals.wgsl delete mode 100644 third_party/bevy_render/src/gpu_component_array_buffer.rs delete mode 100644 third_party/bevy_render/src/gpu_readback.rs delete mode 100644 third_party/bevy_render/src/lib.rs delete mode 100644 third_party/bevy_render/src/maths.wgsl delete mode 100644 third_party/bevy_render/src/mesh/allocator.rs delete mode 100644 third_party/bevy_render/src/mesh/mod.rs delete mode 100644 third_party/bevy_render/src/pipelined_rendering.rs delete mode 100644 third_party/bevy_render/src/render_asset.rs delete mode 100644 third_party/bevy_render/src/render_graph/app.rs delete mode 100644 third_party/bevy_render/src/render_graph/camera_driver_node.rs delete mode 100644 third_party/bevy_render/src/render_graph/context.rs delete mode 100644 third_party/bevy_render/src/render_graph/edge.rs delete mode 100644 third_party/bevy_render/src/render_graph/graph.rs delete mode 100644 third_party/bevy_render/src/render_graph/mod.rs delete mode 100644 third_party/bevy_render/src/render_graph/node.rs delete mode 100644 third_party/bevy_render/src/render_graph/node_slot.rs delete mode 100644 third_party/bevy_render/src/render_phase/draw.rs delete mode 100644 third_party/bevy_render/src/render_phase/draw_state.rs delete mode 100644 third_party/bevy_render/src/render_phase/mod.rs delete mode 100644 third_party/bevy_render/src/render_phase/rangefinder.rs delete mode 100644 third_party/bevy_render/src/render_resource/batched_uniform_buffer.rs delete mode 100644 third_party/bevy_render/src/render_resource/bind_group.rs delete mode 100644 third_party/bevy_render/src/render_resource/bind_group_entries.rs delete mode 100644 third_party/bevy_render/src/render_resource/bind_group_layout.rs delete mode 100644 third_party/bevy_render/src/render_resource/bind_group_layout_entries.rs delete mode 100644 third_party/bevy_render/src/render_resource/bindless.rs delete mode 100644 third_party/bevy_render/src/render_resource/buffer.rs delete mode 100644 third_party/bevy_render/src/render_resource/buffer_vec.rs delete mode 100644 third_party/bevy_render/src/render_resource/gpu_array_buffer.rs delete mode 100644 third_party/bevy_render/src/render_resource/mod.rs delete mode 100644 third_party/bevy_render/src/render_resource/pipeline.rs delete mode 100644 third_party/bevy_render/src/render_resource/pipeline_cache.rs delete mode 100644 third_party/bevy_render/src/render_resource/pipeline_specializer.rs delete mode 100644 third_party/bevy_render/src/render_resource/resource_macros.rs delete mode 100644 third_party/bevy_render/src/render_resource/specializer.rs delete mode 100644 third_party/bevy_render/src/render_resource/storage_buffer.rs delete mode 100644 third_party/bevy_render/src/render_resource/texture.rs delete mode 100644 third_party/bevy_render/src/render_resource/uniform_buffer.rs delete mode 100644 third_party/bevy_render/src/renderer/graph_runner.rs delete mode 100644 third_party/bevy_render/src/renderer/mod.rs delete mode 100644 third_party/bevy_render/src/renderer/raw_vulkan_init.rs delete mode 100644 third_party/bevy_render/src/renderer/render_device.rs delete mode 100644 third_party/bevy_render/src/renderer/wgpu_wrapper.rs delete mode 100644 third_party/bevy_render/src/settings.rs delete mode 100644 third_party/bevy_render/src/storage.rs delete mode 100644 third_party/bevy_render/src/sync_component.rs delete mode 100644 third_party/bevy_render/src/sync_world.rs delete mode 100644 third_party/bevy_render/src/texture/fallback_image.rs delete mode 100644 third_party/bevy_render/src/texture/gpu_image.rs delete mode 100644 third_party/bevy_render/src/texture/manual_texture_view.rs delete mode 100644 third_party/bevy_render/src/texture/mod.rs delete mode 100644 third_party/bevy_render/src/texture/texture_attachment.rs delete mode 100644 third_party/bevy_render/src/texture/texture_cache.rs delete mode 100644 third_party/bevy_render/src/view/mod.rs delete mode 100644 third_party/bevy_render/src/view/view.wgsl delete mode 100644 third_party/bevy_render/src/view/visibility/mod.rs delete mode 100644 third_party/bevy_render/src/view/visibility/range.rs delete mode 100644 third_party/bevy_render/src/view/window/mod.rs delete mode 100644 third_party/bevy_render/src/view/window/screenshot.rs delete mode 100644 third_party/bevy_render/src/view/window/screenshot.wgsl create mode 100644 third_party/bevy_ufbx/.cargo-ok create mode 100644 third_party/bevy_ufbx/.cargo_vcs_info.json create mode 100644 third_party/bevy_ufbx/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 third_party/bevy_ufbx/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 third_party/bevy_ufbx/.github/dependabot.yml create mode 100644 third_party/bevy_ufbx/.github/workflows/ci.yml create mode 100644 third_party/bevy_ufbx/.gitignore create mode 100644 third_party/bevy_ufbx/Cargo.toml create mode 100644 third_party/bevy_ufbx/Cargo.toml.orig rename third_party/{bevy_render => bevy_ufbx}/LICENSE-APACHE (89%) rename third_party/{bevy_render => bevy_ufbx}/LICENSE-MIT (96%) create mode 100644 third_party/bevy_ufbx/README.md create mode 100644 third_party/bevy_ufbx/examples/load_fbx.rs create mode 100644 third_party/bevy_ufbx/src/error.rs create mode 100644 third_party/bevy_ufbx/src/label.rs create mode 100644 third_party/bevy_ufbx/src/lib.rs create mode 100644 third_party/bevy_ufbx/src/loader.rs create mode 100644 third_party/bevy_ufbx/src/material.rs create mode 100644 third_party/bevy_ufbx/src/mesh.rs create mode 100644 third_party/bevy_ufbx/src/node.rs create mode 100644 third_party/bevy_ufbx/src/scene.rs create mode 100644 third_party/bevy_ufbx/src/types.rs create mode 100644 third_party/bevy_ufbx/src/utils.rs create mode 100644 third_party/bevy_ufbx/tests/basic_loading.rs create mode 100644 third_party/bevy_ufbx/tests/integration.rs create mode 100644 third_party/bevy_ufbx/tests/label.rs create mode 100644 third_party/bevy_ufbx/tests/load_all_ufbx_data.rs create mode 100644 third_party/bevy_ufbx/tests/loader_settings.rs create mode 100644 third_party/bevy_ufbx/tests/types.rs create mode 100644 third_party/bevy_ufbx/tests/utils.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index a4367a8..9c57a99 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,3 +6,4 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"] [alias] clean-target = "run -p xtask --bin clean-target --" +validate-levels = "run -p xtask --features validate-levels --bin validate-levels --" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec3dc7b..54d188f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: run: cargo test -p sim - name: Validate level scenes - run: cargo run -p xtask --bin validate-levels + run: cargo validate-levels - name: Test scene schema crate run: cargo test -p scene diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8370bc7..7610ed4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -122,6 +122,19 @@ "problemMatcher": [], "group": "build" }, + { + "label": "target cleanup (deep stale, 3 days)", + "type": "shell", + "command": "cargo clean-target --include-artifacts --days 3 --apply", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "CARGO_TARGET_DIR": "${workspaceFolder}/target" + } + }, + "problemMatcher": [], + "group": "build" + }, { "label": "run editor (dev fast-link)", "type": "cargo", diff --git a/Cargo.lock b/Cargo.lock index 30c4b9f..e58c263 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,33 +20,44 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "accesskit" -version = "0.21.1" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf203f9d3bd8f29f98833d1fbef628df18f759248a547e7e01cfbf63cda36a99" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" dependencies = [ "enumn", "serde", + "uuid", ] [[package]] name = "accesskit_consumer" -version = "0.31.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db81010a6895d8707f9072e6ce98070579b43b717193d2614014abd5cb17dd43" +checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10" dependencies = [ "accesskit", - "hashbrown 0.15.5", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950720ce064757a1b629caad3a408e8d2c63bb01f29b8a3ff8daa331053ffeb" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", ] [[package]] name = "accesskit_macos" -version = "0.22.2" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0089e5c0ac0ca281e13ea374773898d9354cc28d15af9f0f7394d44a495b575" +checksum = "17cb8b66cef272d48161b02a6317cc2bdd5f98bb0a5e79c68f704a5862aa396b" dependencies = [ "accesskit", - "accesskit_consumer", - "hashbrown 0.15.5", + "accesskit_consumer 0.37.0", + "hashbrown 0.16.1", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", @@ -54,23 +65,23 @@ dependencies = [ [[package]] name = "accesskit_windows" -version = "0.29.2" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d63dd5041e49c363d83f5419a896ecb074d309c414036f616dc0b04faca971" +checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21" dependencies = [ "accesskit", - "accesskit_consumer", - "hashbrown 0.15.5", + "accesskit_consumer 0.35.0", + "hashbrown 0.16.1", "static_assertions", - "windows 0.61.3", - "windows-core 0.61.2", + "windows", + "windows-core", ] [[package]] name = "accesskit_winit" -version = "0.29.2" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cfabe59d0eaca7412bfb1f70198dd31e3b0496fee7e15b066f9c36a1a140a0" +checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313" dependencies = [ "accesskit", "accesskit_macos", @@ -116,21 +127,21 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alsa" -version = "0.9.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d" dependencies = [ "alsa-sys", - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "libc", ] [[package]] name = "alsa-sys" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04" dependencies = [ "libc", "pkg-config", @@ -143,16 +154,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" dependencies = [ "android-properties", - "bitflags 2.11.1", + "bitflags 2.13.0", "cc", "jni 0.22.4", "libc", "log", - "ndk 0.9.0", + "ndk", "ndk-context", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", - "simd_cesu8", "thiserror 2.0.18", ] @@ -238,9 +248,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" [[package]] name = "as-raw-xcb-connection" @@ -382,9 +392,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "avian3d" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0751cde05c2be789fad86cd19219daf8a8907e0c6c2175f2c8144706493f010" +checksum = "35a30cb730c72527ffaca7f8806881c7e4acc70a4a74c54eb1ffb56c27f64235" dependencies = [ "approx", "avian_derive", @@ -392,11 +402,11 @@ dependencies = [ "bevy_heavy", "bevy_math", "bevy_transform_interpolation", - "bitflags 2.11.1", + "bitflags 2.13.0", "derive_more", "disqualified", "glam_matrix_extras", - "itertools 0.14.0", + "itertools 0.15.0", "obvhs", "parry3d", "parry3d-f64", @@ -408,11 +418,11 @@ dependencies = [ [[package]] name = "avian_derive" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b257f601a1535e0d4a7a7796f535e3a13de62fd422b16dff7c14d27f0d4048" +checksum = "b95522267606c85f77ba40b55735583618f7a48e873be71a934f71dd5519ce86" dependencies = [ - "proc-macro-error2", + "proc-macro-error3", "proc-macro2", "quote", "syn", @@ -438,9 +448,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bevy" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd310426290cec560221f9750c2f4484be4a8eeea7de3483c423329b465c40e" +checksum = "950238d3049d81006a6fd6b898a34cfc01bb5462a7668795a54d640aed19fd0a" dependencies = [ "bevy_dylib", "bevy_internal", @@ -448,9 +458,9 @@ dependencies = [ [[package]] name = "bevy-inspector-egui" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265c78b2d2b770351e2eb90c5bc997c7036d453a4e2cd62e066561fefeecedec" +checksum = "38e425288304f5ec24cd026d39c813883225e670a88c94655272066d43d38078" dependencies = [ "bevy-inspector-egui-derive", "bevy_app", @@ -466,6 +476,7 @@ dependencies = [ "bevy_log", "bevy_math", "bevy_mesh", + "bevy_mikktspace", "bevy_pbr", "bevy_platform", "bevy_reflect", @@ -487,9 +498,9 @@ dependencies = [ [[package]] name = "bevy-inspector-egui-derive" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8405b6aee62eebfc27a95c513e04398869fb7911ea8266ec91675994b11eb749" +checksum = "99bdb0f08cbcec82bf7909b880acd52d7a527e6d47156b99f8b60e4cb9765f3f" dependencies = [ "proc-macro2", "quote", @@ -498,9 +509,9 @@ dependencies = [ [[package]] name = "bevy_a11y" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e887b25c84f384ffe3278a17cf0e4b405eaa3c8fbc3db24d05d560a11780676d" +checksum = "e86f2cb45b000a9d121a5a7606f9af4a49e72b8b6a3885dc2acac2f0897a04f1" dependencies = [ "accesskit", "bevy_app", @@ -512,18 +523,18 @@ dependencies = [ [[package]] name = "bevy_android" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c58de772ac1148884112e8a456c4f127a94b95a0e42ab5b160b7a11895a241" +checksum = "e6e5e2a949be318bf7184eb084622892afcc1e8b2af44973ac53ab53201756e0" dependencies = [ "android-activity", ] [[package]] name = "bevy_animation" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5bf5b285f0d3fab983b4505e62e195e06930a29007ffc95bdabde834e163a2" +checksum = "7da2fcd94cc32ba1c875c62e88506c0ba4775555a413e34cd172ea95fa26098e" dependencies = [ "bevy_animation_macros", "bevy_app", @@ -554,9 +565,9 @@ dependencies = [ [[package]] name = "bevy_animation_macros" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cf35516d0e7ac9ec25df533be1bf8cbaa20596a8e65f36838a3f7803a267d6d" +checksum = "4fa3d816d7788e27da936b9c7ec27f9828906d53cb1d06e26c58cac5c5ee799f" dependencies = [ "bevy_macro_utils", "quote", @@ -565,9 +576,9 @@ dependencies = [ [[package]] name = "bevy_anti_alias" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726cc494eb7d6a84ce6291c23636fd451fa4846604dc059fa93febca4e60a928" +checksum = "815414ea2e11afe1f2396d89279cfa0986907ef3ec673ec936d9fa695b2bfbf9" dependencies = [ "bevy_app", "bevy_asset", @@ -587,9 +598,9 @@ dependencies = [ [[package]] name = "bevy_app" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def9f41aa5bf9b9dec8beda307a332798609cffb9d44f71005e0cfb45164f2f6" +checksum = "671ae6f3a8d84f9ed696c531a138558596e041231a8414b6efeafa8469e841f9" dependencies = [ "bevy_derive", "bevy_ecs", @@ -597,7 +608,6 @@ dependencies = [ "bevy_reflect", "bevy_tasks", "bevy_utils", - "cfg-if", "console_error_panic_hook", "ctrlc", "downcast-rs 2.0.2", @@ -610,9 +620,9 @@ dependencies = [ [[package]] name = "bevy_asset" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f86fed15972b9fb1a3f7b092cf0390e67131caaedab15a2707c043e3a3c886" +checksum = "43a05907de6a362d2c609a7a1d1b4dca3b58b768f746b719ca1a3c7dfa87d391" dependencies = [ "async-broadcast", "async-channel", @@ -629,7 +639,7 @@ dependencies = [ "bevy_reflect", "bevy_tasks", "bevy_utils", - "bitflags 2.11.1", + "bitflags 2.13.0", "blake3", "crossbeam-channel", "derive_more", @@ -653,9 +663,9 @@ dependencies = [ [[package]] name = "bevy_asset_macros" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cb8d948365b06561b43b7d709282e62a6abb756baac5d8e295206d5e156168" +checksum = "c9444998cc37b51dfd1572c23a501865733ea65619a7d88b47aa654ae2290a4f" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -665,9 +675,9 @@ dependencies = [ [[package]] name = "bevy_audio" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d68da32468ce7f4bb2863b71326acfaaa88e9aef8da8306257cd487d40cede4" +checksum = "06af3bd91de885f2a5c024569779a7fb43349df611d92f7b795c66c8797378db" dependencies = [ "bevy_app", "bevy_asset", @@ -675,17 +685,15 @@ dependencies = [ "bevy_math", "bevy_reflect", "bevy_transform", - "coreaudio-sys", - "cpal", "rodio", "tracing", ] [[package]] name = "bevy_camera" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ed9eed054e14341852236d06a7244597b1ace39ff9ae023fbd188ffde88619" +checksum = "20105c3e77c2fb391bf3ded3a473d60e4058b837c9bf4e3412225833e524c75a" dependencies = [ "bevy_app", "bevy_asset", @@ -708,10 +716,25 @@ dependencies = [ ] [[package]] -name = "bevy_color" -version = "0.18.1" +name = "bevy_clipboard" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb41e8310a85811d14a4e75cfc2d6c07ac70661d6a4883509fc960f622970a8" +checksum = "6dc5fb913f25c5a16a2a1aa2942ba3f1365a6d0f2db3c0ea5bc0be01f85dca5d" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_log", + "bevy_platform", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "bevy_color" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2dfc0091be6eb62fec9158b2d3e6f21b002be7bdb42dd3b8322e949c8426b0" dependencies = [ "bevy_math", "bevy_reflect", @@ -725,9 +748,9 @@ dependencies = [ [[package]] name = "bevy_core_pipeline" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d0810e85c2436e50c67448d48a83bf0bb1b5849899619ae2c7ea817221e9172" +checksum = "885d640bbff6c4fa3beadbb043d0d1ae53b2fda864da18a2bf2670fb90428865" dependencies = [ "bevy_app", "bevy_asset", @@ -737,6 +760,8 @@ dependencies = [ "bevy_diagnostic", "bevy_ecs", "bevy_image", + "bevy_light", + "bevy_log", "bevy_math", "bevy_platform", "bevy_reflect", @@ -745,19 +770,16 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags 2.11.1", + "bitflags 2.13.0", + "indexmap", "nonmax", - "radsort", - "smallvec", - "thiserror 2.0.18", - "tracing", ] [[package]] name = "bevy_derive" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "318ee0532c3da93749859d18f89a889c638fbc56aabac4d866583df7b951d103" +checksum = "293df2b2f7ed65ef2ce02b2e7359faac1a9a92a8cc96c182c9de77eb06049ae0" dependencies = [ "bevy_macro_utils", "quote", @@ -766,20 +788,24 @@ dependencies = [ [[package]] name = "bevy_dev_tools" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f1464a3f5ef5c23d917987714ee89881f9f791e9ff97ecf6600ee846b9569e" +checksum = "db815000affd21545a6d6986f180a66393aee8c84330843e854d376c8537b10f" dependencies = [ "bevy_app", "bevy_asset", "bevy_camera", "bevy_color", + "bevy_core_pipeline", "bevy_diagnostic", "bevy_ecs", "bevy_image", "bevy_input", + "bevy_log", "bevy_math", + "bevy_pbr", "bevy_picking", + "bevy_platform", "bevy_reflect", "bevy_render", "bevy_shader", @@ -789,15 +815,19 @@ dependencies = [ "bevy_transform", "bevy_ui", "bevy_ui_render", + "bevy_utils", "bevy_window", + "ron 0.12.1", + "serde", + "thiserror 2.0.18", "tracing", ] [[package]] name = "bevy_diagnostic" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8543a0f7afd56d3499ba80ffab6ef0bad12f93c2d2ca9aa7b1f1b8816c3980" +checksum = "a73e11555051f37c3e0e9ef7775e77108f2482310242996fb5bb6ef8b685b78e" dependencies = [ "atomic-waker", "bevy_app", @@ -813,18 +843,18 @@ dependencies = [ [[package]] name = "bevy_dylib" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f142f72bbbc1a61338bf6da5dc7cf6dcf223683b9d53bcd635ebff4bfeb5308f" +checksum = "70547c664f17a903cdef1e8e673496e9bdfa3d69dece9c01523d79067a876e34" dependencies = [ "bevy_internal", ] [[package]] name = "bevy_ecs" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9cf7a3ee41342dd7b5a5d82e200d0e8efb933169247fce853b4ad633d51e87d" +checksum = "4654b9b3535fa7813d2878a50e1b5ad80a361dc1c913b96ded57667f65d70a01" dependencies = [ "arrayvec", "bevy_ecs_macros", @@ -833,7 +863,7 @@ dependencies = [ "bevy_reflect", "bevy_tasks", "bevy_utils", - "bitflags 2.11.1", + "bitflags 2.13.0", "bumpalo", "concurrent-queue", "derive_more", @@ -849,10 +879,10 @@ dependencies = [ ] [[package]] -name = "bevy_ecs_macros" -version = "0.18.1" +name = "bevy_ecs_macro_logic" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "908baf585e2ea16bd53ef0da57b69580478af0059d2dbdb4369991ac9794b618" +checksum = "ee0b98a74141ce8dce4654c60020ebbaefab32646f42af6bbae55f282ae65ff7" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -861,10 +891,23 @@ dependencies = [ ] [[package]] -name = "bevy_egui" -version = "0.39.1" +name = "bevy_ecs_macros" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d0a7e4806f3f242326d2c6157531c36d710f3bf320ebc0a1678e44635ed0eac" +checksum = "4dc958a194d226d618404e0fea99a3c8b4146207a00a3ba07fa712bf71607240" +dependencies = [ + "bevy_ecs_macro_logic", + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_egui" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da859aa4d04119d840e4f9c6dcc50c980d140507122553f4c7d607eb80d2aa5c" dependencies = [ "arboard", "bevy_app", @@ -894,10 +937,11 @@ dependencies = [ "crossbeam-channel", "egui", "encase", - "getrandom 0.3.4", + "getrandom 0.4.3", "image", "itertools 0.14.0", "js-sys", + "smithay-clipboard", "thread_local", "wasm-bindgen", "wasm-bindgen-futures", @@ -909,9 +953,9 @@ dependencies = [ [[package]] name = "bevy_encase_derive" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fee46eeddcbc00a805ae00ffa973f224671fc5cf0fe1a796963804faeade90" +checksum = "14dc5bf9e6cb46c4401a66625b5f7eb9654a7f3c8586423c428b79586a1c55cd" dependencies = [ "bevy_macro_utils", "encase_derive_impl", @@ -919,9 +963,9 @@ dependencies = [ [[package]] name = "bevy_feathers" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb29be8f8443c5cc44e1c4710bbe02877e73703c60228ca043f20529a5496c6" +checksum = "e744e047f9328a9c072982569fabbbc837db0474fd902473b5f438f8688c30b6" dependencies = [ "accesskit", "bevy_a11y", @@ -931,6 +975,7 @@ dependencies = [ "bevy_color", "bevy_derive", "bevy_ecs", + "bevy_input", "bevy_input_focus", "bevy_log", "bevy_math", @@ -938,6 +983,7 @@ dependencies = [ "bevy_platform", "bevy_reflect", "bevy_render", + "bevy_scene", "bevy_shader", "bevy_text", "bevy_ui", @@ -949,9 +995,9 @@ dependencies = [ [[package]] name = "bevy_gilrs" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611827ab0ce43b88c0a695e6603901b5f34687efecaf526c861456c9d8e6fedb" +checksum = "7998fe83c89d527efa83c85c574a17f7bb6e34881d638ecb7f706d9d79e82f3c" dependencies = [ "bevy_app", "bevy_ecs", @@ -965,9 +1011,9 @@ dependencies = [ [[package]] name = "bevy_gizmos" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aaff0dd5f405c83d290c5cd591835f1ae8009894947ab19dadcb323062bd7e7" +checksum = "9c0cbd55cc33b6412777d60d8c24a96bee148c82a5731f35ac85b2df4a7cf957" dependencies = [ "bevy_app", "bevy_asset", @@ -975,19 +1021,22 @@ dependencies = [ "bevy_color", "bevy_ecs", "bevy_gizmos_macros", - "bevy_light", + "bevy_input", + "bevy_log", "bevy_math", + "bevy_mesh", "bevy_reflect", "bevy_time", "bevy_transform", "bevy_utils", + "bevy_window", ] [[package]] name = "bevy_gizmos_macros" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6960ea308d7e94adcac5c712553ff86614bba6b663511f3f3812f6bec028b51e" +checksum = "9903f2614f53bacacb92bef1e407afe3a27a4abddb500bfd479909c5d7b254df" dependencies = [ "bevy_macro_utils", "quote", @@ -996,20 +1045,24 @@ dependencies = [ [[package]] name = "bevy_gizmos_render" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a8d18c089102de4c5e9326023ad96ba618a6961029f8102a33640b966883237" +checksum = "dd569456c4b19bafa6d37c1bd9cc743fdadda3a2f23d79d2ceb0a088ea8d475f" dependencies = [ "bevy_app", "bevy_asset", "bevy_camera", + "bevy_color", "bevy_core_pipeline", "bevy_ecs", "bevy_gizmos", "bevy_image", + "bevy_log", + "bevy_material", "bevy_math", "bevy_mesh", "bevy_pbr", + "bevy_reflect", "bevy_render", "bevy_shader", "bevy_sprite_render", @@ -1021,9 +1074,9 @@ dependencies = [ [[package]] name = "bevy_gltf" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f37fb52655d0439656ca0a1db027d46926e463c81d893d4b1639668e5d7f1c1" +checksum = "84e21ad83b7dc8d456491ea3d50057685d8c4cca8bc52a1b4a7161c86c369842" dependencies = [ "async-lock", "base64 0.22.1", @@ -1035,15 +1088,14 @@ dependencies = [ "bevy_ecs", "bevy_image", "bevy_light", + "bevy_material", "bevy_math", "bevy_mesh", - "bevy_pbr", "bevy_platform", "bevy_reflect", - "bevy_render", - "bevy_scene", "bevy_tasks", "bevy_transform", + "bevy_world_serialization", "fixedbitset", "gltf", "itertools 0.14.0", @@ -1053,13 +1105,14 @@ dependencies = [ "smallvec", "thiserror 2.0.18", "tracing", + "wgpu-types", ] [[package]] name = "bevy_heavy" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc496d1d43b890896cf561d8ce3dcf7b7b8e4c03c4e837a49a83a530abccbc5e" +checksum = "afbd898e2c06dfba5854db187a3daece1eaf655c8fc385604bbe5b9304ed3f9b" dependencies = [ "bevy_math", "bevy_reflect", @@ -1069,9 +1122,9 @@ dependencies = [ [[package]] name = "bevy_image" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a71daf9b2afdd032c2b1122d1d501f99126218cb3e9983b3604ec381daa35f22" +checksum = "37bc41f69a0c6ade2e7961602517545b39ab8e42bc8c4a729bd24ffee3bcd904" dependencies = [ "bevy_app", "bevy_asset", @@ -1081,7 +1134,7 @@ dependencies = [ "bevy_platform", "bevy_reflect", "bevy_utils", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "futures-lite", "guillotiere", @@ -1098,9 +1151,9 @@ dependencies = [ [[package]] name = "bevy_input" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc8ffbd02df34dfc52faf420a5263985973765e228043adf542fd0d790a6b21" +checksum = "fd221cf0732f5bf06caf594bdb233361ac735d4d416cf5849757b64bf4e8472a" dependencies = [ "bevy_app", "bevy_ecs", @@ -1116,9 +1169,9 @@ dependencies = [ [[package]] name = "bevy_input_focus" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d48a5bceccb9157549a39ab3de4017f5368b65db6471605e9a3f1c19d91bbc" +checksum = "0e1a14f56c6e722048ec9dc20dbbc6f46b8037f61e319ea776829199278da166" dependencies = [ "bevy_app", "bevy_ecs", @@ -1133,9 +1186,9 @@ dependencies = [ [[package]] name = "bevy_internal" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a11df62e49897def470471551c02f13c6fb488e55dddb5ab7ef098132e07754" +checksum = "d52a85632a749f956f92b7b391c31ce1f229e57b06de879d1b60a3bb91e4e240" dependencies = [ "bevy_a11y", "bevy_android", @@ -1145,6 +1198,7 @@ dependencies = [ "bevy_asset", "bevy_audio", "bevy_camera", + "bevy_clipboard", "bevy_color", "bevy_core_pipeline", "bevy_derive", @@ -1161,6 +1215,7 @@ dependencies = [ "bevy_input_focus", "bevy_light", "bevy_log", + "bevy_material", "bevy_math", "bevy_mesh", "bevy_pbr", @@ -1182,37 +1237,44 @@ dependencies = [ "bevy_transform", "bevy_ui", "bevy_ui_render", + "bevy_ui_widgets", "bevy_utils", "bevy_window", "bevy_winit", + "bevy_world_serialization", ] [[package]] name = "bevy_light" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d9d2ac64390a9baacb3c0fa0f5456ac1553959d5a387874c102a09aab8b92cc" +checksum = "bd7b5009e9cc765c30b21daf2277ea4423cbd347b05280ea0943b50c5a80cd42" dependencies = [ "bevy_app", "bevy_asset", "bevy_camera", "bevy_color", "bevy_ecs", + "bevy_gizmos", "bevy_image", + "bevy_log", "bevy_math", "bevy_mesh", "bevy_platform", "bevy_reflect", "bevy_transform", "bevy_utils", + "half", + "smallvec", "tracing", + "wgpu-types", ] [[package]] name = "bevy_log" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2aac1187f83a1ab2eae887564f7fb14b4abb3fbe8b2267a6426663463923120" +checksum = "2017a5fe12ea230d00cfdca00c65c262924be26e0324f7e0033c64f8cc265888" dependencies = [ "android_log-sys", "bevy_app", @@ -1228,27 +1290,61 @@ dependencies = [ [[package]] name = "bevy_macro_utils" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b147843b81a7ec548876ff97fa7bfdc646ef2567cb465566259237b39664438" +checksum = "746a19912c6dc1bbe79188778573e8a253d5832c696b2fcb95578c17b29ff7ba" dependencies = [ "proc-macro2", "quote", "syn", - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit", +] + +[[package]] +name = "bevy_material" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "423710403b4a4d8e9ee872c6ac8108787f2396422b582d15a660a40731ddfbf0" +dependencies = [ + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_material_macros", + "bevy_mesh", + "bevy_platform", + "bevy_reflect", + "bevy_shader", + "bevy_utils", + "encase", + "smallvec", + "thiserror 2.0.18", + "tracing", + "variadics_please", + "wgpu-types", +] + +[[package]] +name = "bevy_material_macros" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6928e167592472f6ee900f80de199cc2dafd31d4f082a86e8d594b81b973e3d" +dependencies = [ + "bevy_macro_utils", + "quote", + "syn", ] [[package]] name = "bevy_math" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e931fa969f89c83498b22c97432383afe90e90fd1a5e04fa07be8da4d3bcac84" +checksum = "d7c280440d2a5bc07ea393b5346b5712eec73ea30f0133097b8b13dade385beb" dependencies = [ "approx", "arrayvec", "bevy_reflect", "derive_more", - "glam 0.30.10", + "glam 0.32.1", "itertools 0.14.0", "libm", "rand", @@ -1260,23 +1356,26 @@ dependencies = [ [[package]] name = "bevy_mesh" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "288f590c8173d4cca3cae5f2ba579accd5ed1a35dd3fab338f427eb39d55f05e" +checksum = "c59863e6f37ef0ba1f37021bce9dd940f9dc31d15e03f548cda5fd3e674f409e" dependencies = [ "bevy_app", "bevy_asset", "bevy_derive", "bevy_ecs", - "bevy_image", + "bevy_encase_derive", "bevy_math", "bevy_mikktspace", "bevy_platform", "bevy_reflect", "bevy_transform", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "derive_more", + "encase", + "glam 0.32.1", + "half", "hexasphere", "serde", "thiserror 2.0.18", @@ -1286,16 +1385,17 @@ dependencies = [ [[package]] name = "bevy_mikktspace" -version = "0.17.0-dev" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef8e4b7e61dfe7719bb03c884dc270cd46a82efb40f93e9933b990c5c190c59" +checksum = "bff34eb29ff4b8a8688bc7299f14fb6b597461ca80fec03ed7d22939ab33e48f" [[package]] name = "bevy_pbr" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab6944ffc6fd71604c0fbca68cc3e2a3654edfcdbfd232f9d8b88e3d20fdc0" +checksum = "38ebc777de2d7f45888c68e8e04949bfb8a40aee62f700283fc340f525e67f52" dependencies = [ + "arrayvec", "bevy_app", "bevy_asset", "bevy_camera", @@ -1304,34 +1404,39 @@ dependencies = [ "bevy_derive", "bevy_diagnostic", "bevy_ecs", + "bevy_gltf", "bevy_image", "bevy_light", "bevy_log", + "bevy_material", "bevy_math", "bevy_mesh", "bevy_platform", "bevy_reflect", "bevy_render", "bevy_shader", + "bevy_tasks", "bevy_transform", "bevy_utils", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "derive_more", "fixedbitset", + "indexmap", "nonmax", "offset-allocator", "smallvec", "static_assertions", "thiserror 2.0.18", "tracing", + "wgpu-types", ] [[package]] name = "bevy_picking" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7d524dbc8f2c9e73f7ab70c148c8f7886f3c24b8aa8c252a38ba68ed06cbf10" +checksum = "93468ac5937f76be3af763d496f01e899edf6d9c1a1d39a87ae4ce8eba36f78b" dependencies = [ "bevy_app", "bevy_asset", @@ -1353,13 +1458,15 @@ dependencies = [ [[package]] name = "bevy_platform" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec6b36504169b644acd26a5469fd8d371aa6f1d73ee5c01b1b1181ae1cefbf9b" +checksum = "3120670bb2308980f723477c9d82476fcda31f08be9b256c3f0acdee82a65094" dependencies = [ + "async-io", "critical-section", "foldhash 0.2.0", "futures-channel", + "futures-lite", "hashbrown 0.16.1", "js-sys", "portable-atomic", @@ -1369,13 +1476,14 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-time", + "windows-sys 0.61.2", ] [[package]] name = "bevy_post_process" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f77a4e894aea992e3d6938f1d5898a1cdbb87dba6eebfb95cb4038d0a2600e9" +checksum = "57379b51aa0f1b2066c956263e1f12686d22cf8939cf9644eebf2ee42d3dd460" dependencies = [ "bevy_app", "bevy_asset", @@ -1390,12 +1498,7 @@ dependencies = [ "bevy_reflect", "bevy_render", "bevy_shader", - "bevy_transform", "bevy_utils", - "bevy_window", - "bitflags 2.11.1", - "nonmax", - "radsort", "smallvec", "thiserror 2.0.18", "tracing", @@ -1403,15 +1506,15 @@ dependencies = [ [[package]] name = "bevy_ptr" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a9329e8dc4e01ced480eeec4902e6d7cb56e56ec37f6fbc4323e5c937290a7" +checksum = "b511e89f7078fd21fdea77061a0ca3e737297c7011bb10c073e0aecb17c9fea6" [[package]] name = "bevy_reflect" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1dfeb67a9fe4f59003a84f5f99ba6302141c70e926601cbc6abfd4a1eea9ca9" +checksum = "92abd59cbba1524c1847e9a50f74d94e6b7836c80a8edfa9c47e59f7fd81021d" dependencies = [ "assert_type_match", "bevy_platform", @@ -1423,7 +1526,7 @@ dependencies = [ "downcast-rs 2.0.2", "erased-serde", "foldhash 0.2.0", - "glam 0.30.10", + "glam 0.32.1", "indexmap", "inventory", "petgraph", @@ -1438,9 +1541,9 @@ dependencies = [ [[package]] name = "bevy_reflect_derive" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "475f68c93e9cd5f17e9167635c8533a4f388f12d38245a202359e4c2721d87ba" +checksum = "90fa4e6b97fd2d582dd5ed96762a70d04505a477c833cf4f69d016dcd03d2d04" dependencies = [ "bevy_macro_utils", "indexmap", @@ -1452,20 +1555,23 @@ dependencies = [ [[package]] name = "bevy_remote" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0129e24bf3e281dd52996a9290c35f8c4821ca09e1ce8b8b222671e1ad9da0c" +checksum = "2c3a8f11f0017b2b7b8b8bb907c499ace3baa9cda240065457fb7391a3fa2ee8" dependencies = [ "anyhow", "async-channel", "async-io", "bevy_app", "bevy_asset", + "bevy_color", "bevy_derive", + "bevy_dev_tools", "bevy_ecs", "bevy_log", "bevy_platform", "bevy_reflect", + "bevy_render", "bevy_tasks", "bevy_utils", "http-body-util", @@ -1477,7 +1583,9 @@ dependencies = [ [[package]] name = "bevy_render" -version = "0.18.1" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89b2cc850a4c3fd12a6a088a4fc3e80118b2e1b569da8936f6d4d99ca1b6ee7" dependencies = [ "async-channel", "bevy_app", @@ -1489,6 +1597,9 @@ dependencies = [ "bevy_ecs", "bevy_encase_derive", "bevy_image", + "bevy_log", + "bevy_material", + "bevy_material_macros", "bevy_math", "bevy_mesh", "bevy_platform", @@ -1500,15 +1611,15 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "derive_more", "downcast-rs 2.0.2", "encase", - "fixedbitset", - "glam 0.30.10", + "glam 0.32.1", "image", "indexmap", + "itertools 0.14.0", "js-sys", "naga", "nonmax", @@ -1516,18 +1627,19 @@ dependencies = [ "send_wrapper", "smallvec", "thiserror 2.0.18", - "tracing", "variadics_please", "wasm-bindgen", + "weak-table", "web-sys", "wgpu", + "wgpu-types", ] [[package]] name = "bevy_render_macros" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b6325e9c495a71270446784611e8d7f446f927eac8506c4c099fd10cb4c3ed" +checksum = "e0eb5cffe5253a6ab4e4b84b2e40a6a2f4673deb16fd63ab9dfc4a015449873a" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -1537,48 +1649,62 @@ dependencies = [ [[package]] name = "bevy_scene" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cc1047d85ec8048261b63ef675c12f1e6b5782dc0b422fbcee0c140d026bd4" +checksum = "28a3c891148c40e1352fc41001c0e1894ba63fc4e2d3aca33447343c4122b782" dependencies = [ "bevy_app", "bevy_asset", - "bevy_camera", "bevy_derive", "bevy_ecs", + "bevy_log", "bevy_platform", "bevy_reflect", - "bevy_transform", + "bevy_scene_macros", "bevy_utils", - "derive_more", - "ron 0.12.1", - "serde", + "smallvec", "thiserror 2.0.18", - "uuid", + "tracing", + "variadics_please", +] + +[[package]] +name = "bevy_scene_macros" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4bc33a36a4e9e344985a96cd8b4abc816cf2c5d2fa002d72fd7bcd11396589c" +dependencies = [ + "bevy_ecs_macro_logic", + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "bevy_shader" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eea95f0273c32be13d6a0b799a93bc256ad7830759ede595c404d5234302da2" +checksum = "fa0ac51378bb6a021165ee334f846b515cd06082e24ce962adce58ad7df1c39a" dependencies = [ "bevy_asset", "bevy_platform", "bevy_reflect", + "bevy_utils", "naga", "naga_oil", "serde", "thiserror 2.0.18", "tracing", + "wgpu-naga-bridge", "wgpu-types", ] [[package]] name = "bevy_solari" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "304f48da01c8f245da7ff73c404fd30883831788c958ea6531881cf0e72ec97e" +checksum = "7b2edac80e47ba799a747eb47c3dff6f9a46a85cf88bb0ceaad8ab5a5c6afdea" dependencies = [ "bevy_anti_alias", "bevy_app", @@ -1606,9 +1732,9 @@ dependencies = [ [[package]] name = "bevy_sprite" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ec5bc0cbdee551b610a46f41d30374bbe42b8951ffc676253c6243ab2b9395" +checksum = "c679b88cc655881d403929bcdf02d30f688fd8916eb1635e6e7f8585d8ae6894" dependencies = [ "bevy_app", "bevy_asset", @@ -1617,6 +1743,7 @@ dependencies = [ "bevy_derive", "bevy_ecs", "bevy_image", + "bevy_log", "bevy_math", "bevy_mesh", "bevy_picking", @@ -1631,9 +1758,9 @@ dependencies = [ [[package]] name = "bevy_sprite_render" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b82cb08905e7ddcea2694a95f757ae7f1fd01e6a7304076bad595d2158e4bfe0" +checksum = "e5a65ca9a286dd5815ca4d2d41d4f83bdbb22c8a639d10b4c33eac114b369c36" dependencies = [ "bevy_app", "bevy_asset", @@ -1643,6 +1770,7 @@ dependencies = [ "bevy_derive", "bevy_ecs", "bevy_image", + "bevy_material", "bevy_math", "bevy_mesh", "bevy_platform", @@ -1653,7 +1781,7 @@ dependencies = [ "bevy_text", "bevy_transform", "bevy_utils", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "derive_more", "fixedbitset", @@ -1663,9 +1791,9 @@ dependencies = [ [[package]] name = "bevy_state" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae0682968e97d29c1eccc8c6bb6283f2678d362779bc03f1bb990967059473b" +checksum = "8c22e694ea52e42994aea6ed2d0b378fa1e9d5a9b615db88626e33508bc03f35" dependencies = [ "bevy_app", "bevy_ecs", @@ -1679,9 +1807,9 @@ dependencies = [ [[package]] name = "bevy_state_macros" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73d32f90f9cfcef5a44401db7ce206770daaa1707b0fb95eb7a96a6933f54f1b" +checksum = "835f45091fa0df1ff3cb10f4ca5a397d4e9249b550f1be6893b08a2a06d6eed7" dependencies = [ "bevy_macro_utils", "quote", @@ -1690,13 +1818,12 @@ dependencies = [ [[package]] name = "bevy_tasks" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384eb04d80aa38664d69988fd30cbbe03e937ecb65c66aa6abe60ce0bca826aa" +checksum = "fa34e6b9b804851ec08a41c0346c859d82e2fa98c906d74b965ee61bbbb688e4" dependencies = [ "async-channel", "async-executor", - "async-io", "async-task", "atomic-waker", "bevy_platform", @@ -1705,17 +1832,18 @@ dependencies = [ "derive_more", "futures-lite", "heapless 0.9.3", - "pin-project", + "web-task", ] [[package]] name = "bevy_text" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc5233291dfc22e584de2535f2e37ae9766d37cb5a01652de2133ba202dcb9b" +checksum = "38e1d7eb8b10229f424b4fc56c864a26c32ac02b8cd184e2cc25eceea7b5e892" dependencies = [ "bevy_app", "bevy_asset", + "bevy_clipboard", "bevy_color", "bevy_derive", "bevy_ecs", @@ -1725,9 +1853,11 @@ dependencies = [ "bevy_platform", "bevy_reflect", "bevy_utils", - "cosmic-text", + "parley", "serde", "smallvec", + "smol_str", + "swash", "sys-locale", "thiserror 2.0.18", "tracing", @@ -1736,9 +1866,9 @@ dependencies = [ [[package]] name = "bevy_time" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5ef9af4e523195e561074cf60fbfad0f4cb8d1db504855fee3c4ce8896c7244" +checksum = "c21e3be2aa4426ea1e0a7036d3d1dbbded84c319459d9f3e2a616b33563372f2" dependencies = [ "bevy_app", "bevy_ecs", @@ -1751,13 +1881,12 @@ dependencies = [ [[package]] name = "bevy_transform" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3bb3de7842fef699344beb03f22bdbff16599d788fe0f47fbb3b1e6fa320eb" +checksum = "c95fefe69c9223d10e6b52a0fdf12811bab9d0c7dcb27570cb86824b451f180d" dependencies = [ "bevy_app", "bevy_ecs", - "bevy_log", "bevy_math", "bevy_reflect", "bevy_tasks", @@ -1769,9 +1898,9 @@ dependencies = [ [[package]] name = "bevy_transform_interpolation" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88545c8b0fa8f3502b9a439c71fa6b596ee9e808bfb16f27a51c8c6f7405a657" +checksum = "ea40784f1c6a3f61333064ea1d1c7663ef677c1ce46b5ca148c46c4a62c27e62" dependencies = [ "bevy", "serde", @@ -1779,11 +1908,11 @@ dependencies = [ [[package]] name = "bevy_ufbx" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31af4df234bae92d017442aa63e5d20e18e53d04c3c149180752569818fc7e" +version = "0.18.1-rc.1" dependencies = [ "bevy", + "bevy_image", + "bevy_material", "serde", "thiserror 2.0.18", "ufbx", @@ -1791,9 +1920,9 @@ dependencies = [ [[package]] name = "bevy_ui" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1691a411014085e0d35f8bb8208e5f973edd7ace061a4b1c41c83de21579dc70" +checksum = "e5519c799ecf14e2eeece8b9b823250a00c9df14523e2638b647fccfe4ff0d20" dependencies = [ "accesskit", "bevy_a11y", @@ -1806,18 +1935,22 @@ dependencies = [ "bevy_image", "bevy_input", "bevy_input_focus", + "bevy_log", "bevy_math", "bevy_picking", "bevy_platform", "bevy_reflect", "bevy_sprite", "bevy_text", + "bevy_time", "bevy_transform", "bevy_utils", "bevy_window", "derive_more", + "parley", "serde", "smallvec", + "swash", "taffy", "thiserror 2.0.18", "tracing", @@ -1826,9 +1959,9 @@ dependencies = [ [[package]] name = "bevy_ui_render" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2c35402d8a052f512e3fec1f36b26e83eee713fcca57f965c244ee795e1fcb0" +checksum = "4f1d3921936d88bd2638dd4d87bff8fc3b9908a046a50658548045102249f93e" dependencies = [ "bevy_app", "bevy_asset", @@ -1838,6 +1971,7 @@ dependencies = [ "bevy_derive", "bevy_ecs", "bevy_image", + "bevy_input_focus", "bevy_math", "bevy_mesh", "bevy_platform", @@ -1852,14 +1986,15 @@ dependencies = [ "bevy_utils", "bytemuck", "derive_more", + "indexmap", "tracing", ] [[package]] name = "bevy_ui_widgets" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a63cb818b0de41bdb14990e0ce1aaaa347f871750ab280f80c427e83d72712" +checksum = "d5fc616ccc51b36dea5cb1de91ed82569b41649923388e5ba82cb7d2f1ef18d8" dependencies = [ "accesskit", "bevy_a11y", @@ -1872,25 +2007,31 @@ dependencies = [ "bevy_math", "bevy_picking", "bevy_reflect", + "bevy_text", "bevy_ui", + "bevy_window", + "parley", + "smol_str", ] [[package]] name = "bevy_utils" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2111910cd7a4b1e6ce07eaaeb6f68a2c0ea0ca609ed0d0d506e3eb161101435b" +checksum = "3aceea6c7ffdd568f866d5ca4dfe50c33440c54f7bc230c13a9ba7ab0c91aed1" dependencies = [ + "async-channel", "bevy_platform", "disqualified", + "indexmap", "thread_local", ] [[package]] name = "bevy_window" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df06e6993a0896bae2fe7644ae6def29a1a92b45dfb1bcebbd92af782be3638" +checksum = "efcc255b43db156fba393b2ff8fa552aac5e7e02ae4c6298eacdbab655ddf988" dependencies = [ "bevy_app", "bevy_asset", @@ -1907,9 +2048,9 @@ dependencies = [ [[package]] name = "bevy_winit" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2de1c13d32ab8528435b58eca7ab874a1068184c6d6f266ee11433ae99d4069" +checksum = "308c9eb04f74f340593b3def6a7c1778fd42581d779c6db836d690c7d471f94c" dependencies = [ "accesskit", "accesskit_winit", @@ -1930,7 +2071,6 @@ dependencies = [ "bevy_tasks", "bevy_window", "bytemuck", - "cfg-if", "js-sys", "tracing", "wasm-bindgen", @@ -1940,37 +2080,41 @@ dependencies = [ ] [[package]] -name = "bindgen" -version = "0.72.1" +name = "bevy_world_serialization" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +checksum = "de58ae74410d4f940e3f00484f58603b4f1a53a7d84ebe3fca258d2a3aa96620" dependencies = [ - "bitflags 2.11.1", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "proc-macro2", - "quote", - "regex", - "rustc-hash 2.1.2", - "shlex 1.3.0", - "syn", + "bevy_app", + "bevy_asset", + "bevy_camera", + "bevy_derive", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_transform", + "bevy_utils", + "derive_more", + "ron 0.12.1", + "serde", + "thiserror 2.0.18", + "uuid", ] [[package]] name = "bit-set" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" [[package]] name = "bitflags" @@ -1980,9 +2124,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "bytemuck", "serde_core", @@ -2002,12 +2146,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-pseudorand" version = "0.1.2" @@ -2100,9 +2238,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "calloop" @@ -2110,7 +2248,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "log", "polling", "rustix 0.38.44", @@ -2118,18 +2256,43 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.13.0", + "polling", + "rustix 1.1.4", + "slab", + "tracing", +] + [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "calloop", + "calloop 0.13.0", "rustix 0.38.44", "wayland-backend", "wayland-client", ] +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.4", + "rustix 1.1.4", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cast" version = "0.3.0" @@ -2138,14 +2301,14 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.63" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex 2.0.1", + "shlex", ] [[package]] @@ -2154,15 +2317,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -2211,17 +2365,6 @@ dependencies = [ "half", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.6.1" @@ -2267,6 +2410,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "color" version = "0.3.3" @@ -2385,7 +2539,7 @@ checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types 0.1.3", + "core-graphics-types", "foreign-types", "libc", ] @@ -2401,17 +2555,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "libc", -] - [[package]] name = "core_maths" version = "0.1.1" @@ -2423,69 +2566,46 @@ dependencies = [ [[package]] name = "coreaudio-rs" -version = "0.11.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58" dependencies = [ - "bitflags 1.3.2", - "core-foundation-sys", - "coreaudio-sys", -] - -[[package]] -name = "coreaudio-sys" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" -dependencies = [ - "bindgen", -] - -[[package]] -name = "cosmic-text" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cadaea21e24c49c0c82116f2b465ae6a49d63c90e428b0f8d9ae1f638ac91f" -dependencies = [ - "bitflags 2.11.1", - "fontdb", - "harfrust", - "linebender_resource_handle", - "log", - "rangemap", - "rustc-hash 1.1.0", - "self_cell", - "skrifa 0.39.0", - "smol_str", - "swash", - "sys-locale", - "unicode-bidi", - "unicode-linebreak", - "unicode-script", - "unicode-segmentation", + "bitflags 2.13.0", + "libc", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", ] [[package]] name = "cpal" -version = "0.15.3" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +checksum = "d8942da362c0f0d895d7cac616263f2f9424edc5687364dfd1d25ef7eba506d7" dependencies = [ "alsa", - "core-foundation-sys", "coreaudio-rs", "dasp_sample", "jni 0.21.1", "js-sys", "libc", "mach2", - "ndk 0.8.0", + "ndk", "ndk-context", - "oboe", + "num-derive", + "num-traits", + "objc2 0.6.4", + "objc2-audio-toolbox", + "objc2-avf-audio", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation 0.3.2", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows 0.54.0", + "windows", ] [[package]] @@ -2604,7 +2724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.31.3", + "nix", "windows-sys 0.61.2", ] @@ -2695,7 +2815,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -2765,24 +2885,15 @@ dependencies = [ "proc-macro2-diagnostics", ] -[[package]] -name = "ecolor" -version = "0.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71ddb8ac7643d1dba1bb02110e804406dd459a838efcb14011ced10556711a8e" -dependencies = [ - "bytemuck", - "emath 0.33.3", - "serde", -] - [[package]] name = "ecolor" version = "0.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a05fbfa222ffb51989d5ccf33e5f7aebfcf96c5023413856b0c3618a7f79896e" dependencies = [ - "emath 0.34.3", + "bytemuck", + "emath", + "serde", ] [[package]] @@ -2818,15 +2929,15 @@ dependencies = [ [[package]] name = "egui" -version = "0.33.3" +version = "0.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9b567d356674e9a5121ed3fedfb0a7c31e059fe71f6972b691bcd0bfc284e3" +checksum = "42112be0ae157289312b92b3dfaf20e911b5a3c4c65d4aab0e7c47fbc0ce16e3" dependencies = [ "accesskit", "ahash", - "bitflags 2.11.1", - "emath 0.33.3", - "epaint 0.33.3", + "bitflags 2.13.0", + "emath", + "epaint", "log", "nohash-hasher", "profiling", @@ -2837,21 +2948,22 @@ dependencies = [ [[package]] name = "egui_dock" -version = "0.18.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0f689738efbd4afb811433b873379c6a8f3af48f2d296d9849fe70ee021d6" +checksum = "db51b9023b9d65cb4d561fd981946f987c54f25413c75b2c8e634c774c3d70ad" dependencies = [ "duplicate", "egui", "paste", "serde", + "thiserror 2.0.18", ] [[package]] name = "egui_phosphor_icons" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633bbce043c6529ff60011aa7c4f2f1d12b9d2b5a87dc18749b615a7304f05fe" +checksum = "8f8f77d6c508e0e26e055f304dd4ad08ee1c37074174377eaccb41bca01818f8" dependencies = [ "egui", ] @@ -2864,20 +2976,14 @@ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "emath" -version = "0.33.3" +version = "0.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "491bdf728bf25ddd9ad60d4cf1c48588fa82c013a2440b91aa7fc43e34a07c32" +checksum = "b53f0d33a479321da6b0caa71366c9f67e8a2c149762d90bdc0d16e601ee8ecb" dependencies = [ "bytemuck", "serde", ] -[[package]] -name = "emath" -version = "0.34.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53f0d33a479321da6b0caa71366c9f67e8a2c149762d90bdc0d16e601ee8ecb" - [[package]] name = "ena" version = "0.14.4" @@ -2962,25 +3068,6 @@ dependencies = [ "syn", ] -[[package]] -name = "epaint" -version = "0.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009d0dd3c2163823a0abdb899451ecbc78798dec545ee91b43aff1fa790bab62" -dependencies = [ - "ab_glyph", - "ahash", - "bytemuck", - "ecolor 0.33.3", - "emath 0.33.3", - "epaint_default_fonts 0.33.3", - "log", - "nohash-hasher", - "parking_lot", - "profiling", - "serde", -] - [[package]] name = "epaint" version = "0.34.3" @@ -2988,26 +3075,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6675898a291ec212fc3df04f537d177fce8496120244590e6359dcaa4c25da79" dependencies = [ "ahash", - "ecolor 0.34.3", - "emath 0.34.3", - "epaint_default_fonts 0.34.3", - "font-types 0.11.3", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "font-types", "log", "nohash-hasher", "parking_lot", "profiling", "self_cell", + "serde", "skrifa 0.40.0", "smallvec", "vello_cpu", ] -[[package]] -name = "epaint_default_fonts" -version = "0.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fbe202b6578d3d56428fa185cdf114a05e49da05f477b3c7f0fbb221f1862" - [[package]] name = "epaint_default_fonts" version = "0.34.3" @@ -3166,15 +3249,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "font-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" -dependencies = [ - "bytemuck", -] - [[package]] name = "font-types" version = "0.11.3" @@ -3182,29 +3256,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" dependencies = [ "bytemuck", + "serde", ] [[package]] -name = "fontconfig-parser" -version = "0.5.8" +name = "fontique" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +checksum = "7c20b425addb8661e97fe1d51c4d8bcec3ec29ed6ad0db983976a7521276b8f7" dependencies = [ - "roxmltree", -] - -[[package]] -name = "fontdb" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" -dependencies = [ - "fontconfig-parser", - "log", + "hashbrown 0.17.1", + "linebender_resource_handle", "memmap2", - "slotmap", - "tinyvec", - "ttf-parser", + "parlance", + "read-fonts 0.39.2", + "smallvec", ] [[package]] @@ -3361,7 +3427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ "rustix 1.1.4", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3371,31 +3437,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", + "wasm-bindgen", ] [[package]] name = "gilrs" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa85c2e35dc565c90511917897ea4eae16b77f2773d5223536f7b602536d462" +checksum = "902fb00d3f6398e635be22e5c837b303c501835cca7ac11a47bba138f7aafdd8" dependencies = [ "fnv", "gilrs-core", @@ -3406,23 +3470,23 @@ dependencies = [ [[package]] name = "gilrs-core" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23f2cc5144060a7f8d9e02d3fce5d06705376568256a509cdbc3c24d47e4f04" +checksum = "dc7f0ce6237abcc0523f2a5502b1e3fe5802daaae47ac14e166fe49551301ea9" dependencies = [ - "inotify 0.11.1", + "inotify 0.11.2", "js-sys", "libc", "libudev-sys", "log", - "nix 0.30.1", + "nix", "objc2-core-foundation", "objc2-io-kit", "uuid", "vec_map", "wasm-bindgen", "web-sys", - "windows 0.62.2", + "windows", ] [[package]] @@ -3436,21 +3500,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "glam" -version = "0.30.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" -dependencies = [ - "approx", - "bytemuck", - "encase", - "libm", - "mint", - "rand", - "serde_core", -] - [[package]] name = "glam" version = "0.31.1" @@ -3466,44 +3515,44 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f70749695b063ecbf6b62949ccccde2e733ec3ecbbd71d467dca4e5c6c97cca0" dependencies = [ + "approx", + "bytemuck", + "encase", + "libm", "mint", + "rand", + "serde_core", ] [[package]] name = "glam_matrix_extras" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4664468a60479272b880a8bfc00ad2915229b93d2b2d585556fb33f9ba80e72" +checksum = "db060a7f23a5666ccbe9c770cd0e0e419b2eb94c86a7c8bfd091bacf8b4926cd" dependencies = [ "bevy_reflect", - "glam 0.30.10", + "glam 0.32.1", "serde", ] [[package]] name = "glamx" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375b4fa374a343fef990a18a6e0413d54bd990c6d7b8c7ada2d3c884275edea3" +checksum = "59157a5886a9c68eb694df59254f7c9419c5648fc6ca1b728601a8baecc0dfcd" dependencies = [ "approx", - "glam 0.30.10", + "glam 0.32.1", "num-traits", "serde", "simba", ] -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "glow" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" dependencies = [ "js-sys", "slotmap", @@ -3559,35 +3608,18 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.11.1", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.11.1", -] - [[package]] name = "gpu-allocator" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" dependencies = [ + "ash", + "hashbrown 0.16.1", "log", "presser", - "thiserror 1.0.69", - "windows 0.58.0", + "thiserror 2.0.18", + "windows", ] [[package]] @@ -3596,7 +3628,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "gpu-descriptor-types", "hashbrown 0.15.5", ] @@ -3607,7 +3639,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -3632,22 +3664,24 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ + "bytemuck", "cfg-if", "crunchy", "num-traits", + "serde", "zerocopy", ] [[package]] name = "harfrust" -version = "0.4.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0caaee032384c10dd597af4579c67dee16650d862a9ccbe1233ff1a379abc07" +checksum = "551ed25397e4b444e89686602877d5cf3a7f6e3d548dcac37a8357d1e195f4df" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "core_maths", - "read-fonts 0.36.0", + "read-fonts 0.39.2", "smallvec", ] @@ -3687,6 +3721,9 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] [[package]] name = "heapless" @@ -3723,12 +3760,12 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hexasphere" -version = "16.0.0" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a164ceff4500f2a72b1d21beaa8aa8ad83aec2b641844c659b190cb3ea2e0b" +checksum = "177ea6330876de4ef08e184f827b98acc037614a4ce409902c5b0d53a9c2e951" dependencies = [ "constgebra", - "glam 0.30.10", + "glam 0.32.1", "tinyvec", ] @@ -3766,9 +3803,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -3843,6 +3880,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_locale" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a396343c7208121dc86e35623d3dfe19814a7613cfd14964994cdc9c9a2e26" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_locale_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + [[package]] name = "icu_locale_core" version = "2.2.0" @@ -3851,11 +3903,18 @@ checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", ] +[[package]] +name = "icu_locale_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fdcc9ac77c6d74ff5cf6e65ef3181d6af32003b16fce3a77fb451d2f695993" + [[package]] name = "icu_normalizer" version = "2.2.0" @@ -3904,6 +3963,8 @@ checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", + "serde", + "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -3912,10 +3973,25 @@ dependencies = [ ] [[package]] -name = "id-arena" -version = "2.3.0" +name = "icu_segmenter" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +checksum = "5c0794db0b1a86193ac9c48768d0e6c52c54448e0870ad87907d456ee0dac964" +dependencies = [ + "icu_collections", + "icu_locale", + "icu_provider", + "icu_segmenter_data", + "potential_utf", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a2c462a4d927d512f5f882a033ddd62f33a05bb9f230d98f736ac3dc85938f" [[package]] name = "ident_case" @@ -3991,11 +4067,11 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +checksum = "533e68a5842e734946fe159fb03fc9bbbb254f590dd0d8ad321ae5ff7beca2c1" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "inotify-sys", "libc", ] @@ -4040,18 +4116,18 @@ dependencies = [ [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" dependencies = [ "either", ] @@ -4092,7 +4168,7 @@ dependencies = [ "simd_cesu8", "thiserror 2.0.18", "walkdir", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4148,13 +4224,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -4177,9 +4252,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kqueue" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" dependencies = [ "kqueue-sys", "libc", @@ -4191,17 +4266,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", ] [[package]] name = "ktx2" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7f53bdf698e7aa7ec916411bbdc8078135da11b66db5182675b2227f6c0d07" +checksum = "1d50be8a24c37debdf3170be6ca396d5b5b165f37f94837891adac735ca416b8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -4222,12 +4297,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "lewton" version = "0.10.2" @@ -4252,7 +4321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4267,10 +4336,10 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", "plain", - "redox_syscall 0.8.0", + "redox_syscall 0.8.1", ] [[package]] @@ -4324,24 +4393,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.30" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "mach2" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" dependencies = [ "libc", ] @@ -4357,40 +4417,19 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] -[[package]] -name = "metal" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" -dependencies = [ - "bitflags 2.11.1", - "block", - "core-graphics-types 0.2.0", - "foreign-types", - "log", - "objc", - "paste", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -4431,16 +4470,16 @@ dependencies = [ [[package]] name = "naga" -version = "27.0.3" +version = "29.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" +checksum = "0dd91265cc2454558f659b3b4b9640f0ddb8cc6521277f166b8a8c181c898079" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", - "codespan-reporting", + "codespan-reporting 0.13.1", "half", "hashbrown 0.16.1", "hexf-parse", @@ -4450,7 +4489,7 @@ dependencies = [ "num-traits", "once_cell", "pp-rs", - "rustc-hash 1.1.0", + "rustc-hash", "spirv", "thiserror 2.0.18", "unicode-ident", @@ -4458,16 +4497,16 @@ dependencies = [ [[package]] name = "naga_oil" -version = "0.20.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310c347db1b30e69581f3b84dc9a5c311ed583f67851b39b77953cb7a066c97f" +checksum = "319f03062940ed85c03da020042618356bb8ca5263020322930883b05e30208e" dependencies = [ - "codespan-reporting", + "codespan-reporting 0.12.0", "data-encoding", "indexmap", "naga", "regex", - "rustc-hash 1.1.0", + "rustc-hash", "thiserror 2.0.18", "tracing", "unicode-ident", @@ -4479,30 +4518,16 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "729eb334247daa1803e0a094d0a5c55711b85571179f5ec6e53eccfdf7008958" -[[package]] -name = "ndk" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" -dependencies = [ - "bitflags 2.11.1", - "jni-sys 0.3.1", - "log", - "ndk-sys 0.5.0+25.2.9519653", - "num_enum", - "thiserror 1.0.69", -] - [[package]] name = "ndk" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "jni-sys 0.3.1", "log", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "raw-window-handle", "thiserror 1.0.69", @@ -4514,15 +4539,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys 0.3.1", -] - [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -4532,25 +4548,13 @@ dependencies = [ "jni-sys 0.3.1", ] -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nix" version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -4562,16 +4566,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nonmax" version = "0.5.5" @@ -4593,7 +4587,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crossbeam-channel", "filetime", "fsevent-sys", @@ -4638,6 +4632,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -4658,6 +4662,26 @@ dependencies = [ "syn", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -4690,15 +4714,6 @@ dependencies = [ "syn", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -4730,14 +4745,14 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "libc", "objc2 0.5.2", "objc2-core-data", "objc2-core-image", "objc2-foundation 0.2.2", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -4746,20 +4761,45 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-core-graphics", "objc2-foundation 0.3.2", ] +[[package]] +name = "objc2-audio-toolbox" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" +dependencies = [ + "bitflags 2.13.0", + "libc", + "objc2 0.6.4", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -4777,13 +4817,36 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2", + "objc2 0.6.4", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +dependencies = [ + "bitflags 2.13.0", + "objc2 0.6.4", +] + [[package]] name = "objc2-core-data" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4795,8 +4858,10 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", + "block2 0.6.2", "dispatch2", + "libc", "objc2 0.6.4", ] @@ -4806,7 +4871,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2 0.6.4", "objc2-core-foundation", @@ -4822,7 +4887,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", ] [[package]] @@ -4849,7 +4914,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "dispatch", "libc", @@ -4862,7 +4927,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", + "block2 0.6.2", + "libc", "objc2 0.6.4", "objc2-core-foundation", ] @@ -4873,7 +4940,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", "objc2-core-foundation", ] @@ -4884,7 +4951,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", ] @@ -4907,23 +4974,48 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.0", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", ] [[package]] @@ -4942,7 +5034,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", @@ -4951,7 +5043,7 @@ dependencies = [ "objc2-core-location", "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", @@ -4974,36 +5066,13 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", ] -[[package]] -name = "oboe" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" -dependencies = [ - "jni 0.21.1", - "ndk 0.8.0", - "ndk-context", - "num-derive", - "num-traits", - "oboe-sys", -] - -[[package]] -name = "oboe-sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" -dependencies = [ - "cc", -] - [[package]] name = "obvhs" version = "0.3.1" @@ -5051,9 +5120,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opener" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fa337e0cf13357c13ef1dc108df1333eb192f75fc170bea03fcf1fd404c2ee" +checksum = "b2b03ff07a220d0d0ec9a1f0f238951b7967a5a2e96aefcd21a117b1083415e9" dependencies = [ "bstr", "normpath", @@ -5114,18 +5183,51 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", +] + +[[package]] +name = "parlance" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6937eda350acc1a5d05872c3cbf99fe78619c269096e2be3d4a350058639d5" + +[[package]] +name = "parley" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fad031076f48f0d4d85ce1aea9b94b4e715a4d636a030a123038f8f5b5e4343" +dependencies = [ + "fontique", + "harfrust", + "hashbrown 0.17.1", + "icu_normalizer", + "icu_properties", + "icu_segmenter", + "linebender_resource_handle", + "parlance", + "parley_data", + "skrifa 0.42.1", +] + +[[package]] +name = "parley_data" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ab9ace3fad1b9ed603ddac5b595e69931fc50263d7e04e4055015b77b02da5" +dependencies = [ + "icu_properties", ] [[package]] name = "parry3d" -version = "0.26.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8598fd0fa548d469b6ca4f9e90b446434fb879c12494a91e41c341d8f0f558d8" +checksum = "99613136af5c3ae1e26907eb8ef90183636f54d02341c0a1121caa813a242cb5" dependencies = [ "approx", "arrayvec", - "bitflags 2.11.1", + "bitflags 2.13.0", "downcast-rs 2.0.2", "either", "ena", @@ -5149,13 +5251,13 @@ dependencies = [ [[package]] name = "parry3d-f64" -version = "0.26.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f7e4d41f17de279308ca679ae0e30e7eef68e36088dd5a34e7d7923461cf0d" +checksum = "c1f331c2ecfa5c34dfe379eff7efb824e5e8cfcc933d9624d74e7f867ea696dd" dependencies = [ "approx", "arrayvec", - "bitflags 2.11.1", + "bitflags 2.13.0", "downcast-rs 2.0.2", "either", "ena", @@ -5303,7 +5405,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", @@ -5360,6 +5462,8 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ + "serde_core", + "writeable", "zerovec", ] @@ -5372,57 +5476,38 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "presser" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.12+spec-1.1.0", + "toml_edit", ] [[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" +name = "proc-macro-error-attr3" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +checksum = "34e4dd828515431dd6c4a030d26f7eaed7dd4778226e9d2bb968d65ca4ec3d4d" dependencies = [ "proc-macro2", "quote", ] [[package]] -name = "proc-macro-error2" -version = "2.0.1" +name = "proc-macro-error3" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +checksum = "5ee475e440453418ff1335189eddf7101ba502cd818ab7ae04209bc83aa925aa" dependencies = [ - "proc-macro-error-attr2", + "proc-macro-error-attr3", "proc-macro2", "quote", "syn", @@ -5486,9 +5571,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -5513,38 +5598,25 @@ checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22" [[package]] name = "rand" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", + "getrandom 0.4.3", "rand_core", ] [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_distr" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" dependencies = [ "num-traits", "rand", @@ -5556,18 +5628,24 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" -[[package]] -name = "rangemap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" - [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + [[package]] name = "rayon" version = "1.12.0" @@ -5602,17 +5680,6 @@ dependencies = [ "voracious_radix_sort", ] -[[package]] -name = "read-fonts" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eaa2941a4c05443ee3a7b26ab076a553c343ad5995230cc2b1d3e993bdc6345" -dependencies = [ - "bytemuck", - "core_maths", - "font-types 0.10.1", -] - [[package]] name = "read-fonts" version = "0.37.0" @@ -5620,7 +5687,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" dependencies = [ "bytemuck", - "font-types 0.11.3", + "font-types", +] + +[[package]] +name = "read-fonts" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81" +dependencies = [ + "bytemuck", + "font-types", ] [[package]] @@ -5644,23 +5721,23 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] name = "redox_syscall" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7591fa2c6b601dfcfe5f043f65a1c39fcdf50efefcd7f1572e538c1f4b398d" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -5681,9 +5758,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "renderdoc-sys" @@ -5726,12 +5803,16 @@ checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" [[package]] name = "rodio" -version = "0.20.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1" +checksum = "d0a536bb79db59098ef71a4dd4246c02eb87b316deceb1b68e0cde7167ec01eb" dependencies = [ "cpal", + "dasp_sample", "lewton", + "num-rational", + "thiserror 2.0.18", + "tracing", ] [[package]] @@ -5741,7 +5822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.11.1", + "bitflags 2.13.0", "serde", "serde_derive", ] @@ -5752,7 +5833,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "once_cell", "serde", "serde_derive", @@ -5760,12 +5841,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "roxmltree" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" - [[package]] name = "rstar" version = "0.12.2" @@ -5783,12 +5858,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - [[package]] name = "rustc_version" version = "0.4.1" @@ -5804,7 +5873,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -5817,7 +5886,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -5887,7 +5956,7 @@ dependencies = [ "ab_glyph", "log", "memmap2", - "smithay-client-toolkit", + "smithay-client-toolkit 0.19.2", "tiny-skia", ] @@ -5990,12 +6059,6 @@ dependencies = [ "settings", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "shlex" version = "2.0.1" @@ -6048,16 +6111,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "skrifa" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9eb0b904a04d09bd68c65d946617b8ff733009999050f3b851c32fb3cfb60e" -dependencies = [ - "bytemuck", - "read-fonts 0.36.0", -] - [[package]] name = "skrifa" version = "0.40.0" @@ -6068,6 +6121,16 @@ dependencies = [ "read-fonts 0.37.0", ] +[[package]] +name = "skrifa" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564" +dependencies = [ + "bytemuck", + "read-fonts 0.39.2", +] + [[package]] name = "slab" version = "0.4.12" @@ -6085,9 +6148,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" dependencies = [ "serde", ] @@ -6098,9 +6161,9 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.11.1", - "calloop", - "calloop-wayland-source", + "bitflags 2.13.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", "cursor-icon", "libc", "log", @@ -6117,6 +6180,44 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.13.0", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.4", + "thiserror 2.0.18", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + [[package]] name = "smol-hyper" version = "0.1.1" @@ -6163,11 +6264,11 @@ dependencies = [ [[package]] name = "spirv" -version = "0.3.0+sdk-1.3.268.0" +version = "0.4.0+sdk-1.4.341.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -6205,20 +6306,20 @@ checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" [[package]] name = "swash" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" +checksum = "0811b01ca2c4e8718760713911feaf4675c24f94e50530a015ec646cfb622f7c" dependencies = [ - "skrifa 0.40.0", + "skrifa 0.42.1", "yazi", "zeno", ] [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -6247,23 +6348,23 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.37.2" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows 0.61.3", + "windows", ] [[package]] name = "taffy" -version = "0.9.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ba83ebaf2954d31d05d67340fd46cebe99da2b7133b0dd68d70c65473a437b" +checksum = "aea22054047c16c3f34d3ac473a2170be1424b1115b2a3adcf28cfb067c88859" dependencies = [ "arrayvec", "grid", @@ -6395,6 +6496,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -6432,15 +6534,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -6450,18 +6543,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.23.10+spec-1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" -dependencies = [ - "indexmap", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "winnow 0.7.15", -] - [[package]] name = "toml_edit" version = "0.25.12+spec-1.1.0" @@ -6469,9 +6550,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", + "toml_datetime", "toml_parser", - "winnow 1.0.3", + "winnow", ] [[package]] @@ -6480,7 +6561,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.3", + "winnow", ] [[package]] @@ -6489,6 +6570,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -6574,11 +6656,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e52e45b2efe33a248468cabe2d32415f5695d28fd1282381cc000c178cdfc2f7" dependencies = [ "ahash", - "ecolor 0.34.3", - "emath 0.34.3", + "ecolor", + "emath", "enum_dispatch", "enumset", - "epaint 0.34.3", + "epaint", "glam 0.32.1", "mint", ] @@ -6617,9 +6699,6 @@ name = "ttf-parser" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -dependencies = [ - "core_maths", -] [[package]] name = "twox-hash" @@ -6648,35 +6727,17 @@ dependencies = [ "cc", ] -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - -[[package]] -name = "unicode-script" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" - [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -6716,11 +6777,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", @@ -6808,27 +6869,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -6839,9 +6891,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" dependencies = [ "js-sys", "wasm-bindgen", @@ -6849,9 +6901,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6859,9 +6911,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", @@ -6872,47 +6924,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "wayland-backend" version = "0.3.15" @@ -6933,7 +6951,7 @@ version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "rustix 1.1.4", "wayland-backend", "wayland-scanner", @@ -6945,7 +6963,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cursor-icon", "wayland-backend", ] @@ -6963,23 +6981,49 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.12" +version = "0.32.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-scanner", ] +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-protocols-plasma" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -6992,7 +7036,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7018,19 +7062,38 @@ checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ "dlib", "log", + "once_cell", "pkg-config", ] [[package]] -name = "web-sys" -version = "0.3.99" +name = "weak-table" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" + +[[package]] +name = "web-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "web-task" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdc136a53ccd64a1211f107ccc34404769fbcc0f165f1afa065f5d88ab93538" +dependencies = [ + "async-task", + "cfg-if", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web-time" version = "1.1.0" @@ -7065,12 +7128,13 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "wgpu" -version = "27.0.1" +version = "29.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" +checksum = "bb3feacc458f7bee8bc1737149b42b6c731aa461039a4264a67bb6681646b250" dependencies = [ "arrayvec", - "bitflags 2.11.1", + "bitflags 2.13.0", + "bytemuck", "cfg-if", "cfg_aliases", "document-features", @@ -7092,14 +7156,14 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "27.0.3" +version = "29.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" +checksum = "02da3ad1b568337f25513b317870960ef87073ea0945502e44b864b67a8c77b7" dependencies = [ "arrayvec", "bit-set", "bit-vec", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "cfg_aliases", "document-features", @@ -7112,62 +7176,61 @@ dependencies = [ "portable-atomic", "profiling", "raw-window-handle", - "rustc-hash 1.1.0", + "rustc-hash", "smallvec", "thiserror 2.0.18", "wgpu-core-deps-apple", "wgpu-core-deps-wasm", "wgpu-core-deps-windows-linux-android", "wgpu-hal", + "wgpu-naga-bridge", "wgpu-types", ] [[package]] name = "wgpu-core-deps-apple" -version = "27.0.0" +version = "29.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" +checksum = "62e51b5447e144b3dbba4feb01f80f4fa21696fa0cd99afb2c3df1affd6fdb28" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-wasm" -version = "27.0.0" +version = "29.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1027dcf3b027a877e44819df7ceb0e2e98578830f8cd34cd6c3c7c2a7a50b7" +checksum = "0c2f2fb042f36920771deb0b966543c5751b18f3d327760ffc90f74e20b2dcd4" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "27.0.0" +version = "29.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" +checksum = "1bfb01076d0aa08b0ba9bd741e178b5cc440f5abe99d9581323a4c8b5d1a1916" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "27.0.4" +version = "29.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" +checksum = "31f8e1a9e7a8512f276f7c62e018c7fa8d60954303fed2e5750114332049193f" dependencies = [ "android_system_properties", "arrayvec", "ash", "bit-set", - "bitflags 2.11.1", - "block", + "bitflags 2.13.0", + "block2 0.6.2", "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", - "gpu-alloc", "gpu-allocator", "gpu-descriptor", "hashbrown 0.16.1", @@ -7176,10 +7239,13 @@ dependencies = [ "libc", "libloading", "log", - "metal", "naga", - "ndk-sys 0.6.0+11769913", - "objc", + "ndk-sys", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", "once_cell", "ordered-float", "parking_lot", @@ -7188,28 +7254,42 @@ dependencies = [ "profiling", "range-alloc", "raw-window-handle", + "raw-window-metal", "renderdoc-sys", "smallvec", "thiserror 2.0.18", "wasm-bindgen", + "wayland-sys", "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows", + "windows-core", + "windows-result", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c654c483f058800972c3645e95388a7eca31bf9fe1933bc20e036588a0be02" +dependencies = [ + "naga", "wgpu-types", - "windows 0.58.0", - "windows-core 0.58.0", ] [[package]] name = "wgpu-types" -version = "27.0.1" +version = "29.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +checksum = "a9bcc31518a0e9735aefebedb5f7a9ef3ed1c42549c9f4c882fa9060ceaac639" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "js-sys", "log", + "raw-window-handle", "serde", - "thiserror 2.0.18", "web-sys", ] @@ -7254,58 +7334,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" -dependencies = [ - "windows-core 0.54.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections 0.3.2", - "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", ] [[package]] @@ -7314,43 +7352,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" -dependencies = [ - "windows-result 0.1.2", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -7359,22 +7361,11 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -7383,20 +7374,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-core", + "windows-link", + "windows-threading", ] [[package]] @@ -7410,17 +7390,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -7432,63 +7401,20 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - [[package]] name = "windows-numerics" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", -] - -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", + "windows-core", + "windows-link", ] [[package]] @@ -7497,26 +7423,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -7525,7 +7432,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -7579,7 +7486,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -7634,7 +7541,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -7645,22 +7552,13 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -7852,10 +7750,10 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "bytemuck", - "calloop", + "calloop 0.13.0", "cfg_aliases", "concurrent-queue", "core-foundation 0.9.4", @@ -7865,7 +7763,7 @@ dependencies = [ "js-sys", "libc", "memmap2", - "ndk 0.9.0", + "ndk", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", @@ -7877,7 +7775,7 @@ dependencies = [ "redox_syscall 0.4.1", "rustix 0.38.44", "sctk-adwaita", - "smithay-client-toolkit", + "smithay-client-toolkit 0.19.2", "smol_str", "tracing", "unicode-segmentation", @@ -7895,15 +7793,6 @@ dependencies = [ "xkbcommon-dl", ] -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "1.0.3" @@ -7913,100 +7802,12 @@ dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "writeable" version = "0.6.3" @@ -8057,7 +7858,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dlib", "log", "once_cell", @@ -8092,9 +7893,9 @@ checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -8121,18 +7922,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -8169,6 +7970,7 @@ dependencies = [ "displaydoc", "yoke", "zerofrom", + "zerovec", ] [[package]] @@ -8177,6 +7979,7 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", diff --git a/Cargo.toml b/Cargo.toml index bf03ba5..1f2960a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ edition = "2021" license = "MIT OR Apache-2.0" [workspace.dependencies] -avian3d = { version = "0.6", default-features = false, features = [ +avian3d = { version = "0.7", default-features = false, features = [ "3d", "f32", "parry-f32", @@ -26,14 +26,14 @@ avian3d = { version = "0.6", default-features = false, features = [ "collider-from-mesh", "serialize", ] } -bevy = { version = "0.18", features = ["serialize", "jpeg"] } -bevy_core_pipeline = "0.18" -bevy_solari = "0.18" -bevy_ufbx = "0.18" -bevy_egui = "0.39" -bevy-inspector-egui = "0.36" -egui_dock = { version = "0.18", features = ["serde"] } -egui_phosphor_icons = "0.2" +bevy = { version = "0.19", features = ["serialize", "jpeg"] } +bevy_core_pipeline = "0.19" +bevy_solari = "0.19" +bevy_ufbx = "0.18.1-rc.1" +bevy_egui = "0.40" +bevy-inspector-egui = "0.37" +egui_dock = { version = "0.19.1", features = ["serde"] } +egui_phosphor_icons = { version = "0.3.1", default-features = false } transform-gizmo-bevy = "0.9" serde = { version = "1", features = ["derive"] } shared = { path = "crates/shared" } @@ -45,8 +45,8 @@ settings = { path = "crates/settings" } scene = { path = "crates/scene" } [patch.crates-io] -# Linux surface acquire timeouts can be transient on Wayland/Xwayland drivers. -bevy_render = { path = "third_party/bevy_render" } +# Local Bevy 0.19 compatibility patch until upstream publishes a matching FBX loader. +bevy_ufbx = { path = "third_party/bevy_ufbx" } # Atmosphere-aware mesh view bind groups (WYSIWYG editor + transform gizmos). transform-gizmo-bevy = { path = "third_party/transform-gizmo-bevy" } @@ -59,6 +59,16 @@ rpath = true [profile.dev.package."*"] opt-level = 3 +# Tests keep source line information without retaining full multi-gigabyte Bevy +# debug images or incremental caches. The dev profile remains fully debuggable. +[profile.test] +opt-level = 1 +debug = "line-tables-only" +incremental = false + +[profile.test.package."*"] +opt-level = 3 + # A snappy release profile for shipping builds. [profile.release] lto = "thin" diff --git a/README.md b/README.md index 059a582..81b9c85 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Bevy FPS Foundation -A modular first-person game foundation and in-process editor built on **Bevy 0.18** and -**Avian 0.6** physics. +A modular first-person game foundation and in-process editor built on **Bevy 0.19** and +**Avian 0.7** physics. The runtime game provides a high-fidelity PBR stack (HDR, procedural atmosphere + image-based lighting, cascaded shadows, SSAO, TAA, bloom, fog, ACES tonemapping) plus Hybrid Auto Solari @@ -76,13 +76,20 @@ Cargo/Bevy debug artifacts can grow quickly. Use the workspace cleanup task befo | `cargo clean-target --apply` | Safe cleanup: removes incremental and rust-analyzer flycheck cache. | | `cargo clean-target --include-artifacts --days 3 --apply` | Deeper cleanup: also removes stale hashed `deps`, `build`, `.fingerprint`, and example artifacts older than 3 days. Cargo will rebuild anything still needed. | -VS Code tasks: **target cleanup (dry run)** and **target cleanup (safe apply)**. +Use the safe cleanup during normal iteration and the three-day deep cleanup after Bevy upgrades, +feature-matrix builds, or large test runs. The cutoff preserves recent artifacts and avoids the full +rebuild caused by `cargo clean`. The workspace test profile keeps line tables but disables full test +debuginfo and incremental test caches, so routine test binaries stay materially smaller without +reducing normal editor debugging fidelity. The cleanup binary deliberately excludes the scene/Bevy +validation dependency; `cargo clean-target` therefore stays cheap even from a cold target. Use +`cargo validate-levels` when scene validation is required. VS Code tasks expose dry-run, safe, and +deep-stale variants. ### Launch Troubleshooting - The native game/editor windows force an opaque Wayland surface and opaque camera clears to avoid compositor alpha issues on mixed HDR/SDR desktops. - If the window maps but appears transparent on Hyprland or another Wayland compositor, launch with `BEVY_FPS_HDR=0` to force the SDR camera path while debugging monitor/compositor behavior. -- The workspace patches `bevy_render` locally so transient Linux swapchain acquire timeouts skip one frame instead of panicking in `prepare_windows`. +- Bevy 0.19 removed the prior local `bevy_render` swapchain-timeout patch; launch troubleshooting should start from current wgpu/driver/compositor logs. - Debug launch configs and run tasks set `WGPU_VALIDATION=0` to quiet the known wgpu/Vulkan validation-layer warning `VUID-StandaloneSpirv-MemorySemantics-10871`. This only disables the Vulkan validation layer for those launches; Rust panics and application errors still surface normally. - If **CodeLLDB / mold** fails with hundreds of `undefined symbol` linker errors, the incremental `target/` cache is stale. Run the VS Code task **clean build editor (dev)** or `cargo clean -p editor && cargo build -p editor --features dev`, then launch **Debug editor** again. Use `cargo run -p editor --features dev` from the terminal if you need `libbevy_dylib` on `LD_LIBRARY_PATH` automatically. @@ -111,9 +118,9 @@ VS Code tasks: **target cleanup (dry run)** and **target cleanup (safe apply)**. | `W` / `E` / `R` | Translate / rotate / scale gizmo | | `X` | Toggle world/local gizmo orientation | | `B` | Enter Draw Brush mode | -| Draw Brush: LMB / `Enter` / `Esc` / `Backspace` | Place floor points / create brush / cancel / remove last point | +| Draw Brush: LMB / `Enter` / mouse up-down / `Esc` / `Backspace` | Place floor points / enter height phase or create / set height / cancel / remove point or return to outline | | Brush selected: `1` / `2` / `3` / `4` | Vertex / edge / face / clip edit modes | -| Brush edit mode: LMB / `Shift+LMB` / `Esc` | Select element / toggle element selection / return to object mode | +| Brush edit mode: LMB / `Shift+LMB` / `W` / `E` / `R` / `Esc` | Select element / toggle element selection / move / rotate / scale selected brush elements / return to object mode | | Viewport toolbar (sun / brush / box icons) | Shading: Lit, Unlit (albedo), Colliders (mesh off) | | Viewport eye/options | Toggle actor root icon categories, adjust icon/gizmo size, and control colliders, lights, spawns, prefab/model anchors, and runtime player/camera visualizers | | `Tab` in viewport | Cycle selection through overlapping objects at last click | @@ -121,7 +128,7 @@ VS Code tasks: **target cleanup (dry run)** and **target cleanup (safe apply)**. | Select Project Sun | Inspect project default lighting; create a scene sun override | | Asset Browser project/file views | Browse `assets/`, search/filter/sort, switch grid/list, expand model subassets with generated thumbnails, inspect staged import/material settings, drag assets/submeshes into the viewport | | Asset Browser context/details actions | Apply textures/materials, regenerate thumbnails, reimport models, place assets/submeshes, or move file assets to `assets/.trash/` | -| `Ctrl+P` | Command palette; type to filter, use arrow keys to select, Enter to run (`scene.reset_lighting`, `selection.group`, `selection.focus`, `selection.reset_transform`, play commands) | +| `Ctrl+P` | Centered command palette; search human labels or stable command IDs, use arrow keys to select, Enter to run | | `F7` | While paused in Play: advance one sim tick | | Shift/Ctrl + click (Hierarchy) | Additive selection | | Hierarchy context | Reparent to other selection / Unparent | @@ -182,11 +189,13 @@ Viewport shortcut keys require the pointer to be in the viewport and are suspend artifacts in `assets/meshes/generated/`. Renderer slots reference imported content-browser mesh and material assets, while generated collision is stored separately in `ColliderDesc` plus `RigidBodyDesc`. Switch a model asset's placement mode to **Scene Instance** in Asset Browser - details when you need full `SceneRoot` loading for animation/skinning/scene data. Expanding a + details when you need full `WorldAssetRoot` loading for animation/skinning/scene data. Expanding a model in the Asset Browser exposes normalized mesh/material/texture subassets; dragging a mesh subasset places that part through the same static mesh renderer path. -- Brush actors are persisted as `ActorKind::Brush + BrushDesc`; the MVP hydrates additive convex - cube brushes into generated preview meshes while face/edge/CSG editing remains roadmap work. +- Brush actors are persisted as `ActorKind::Brush + BrushDesc`; valid convex faces hydrate into + generated preview meshes. Vertex, edge, and face selections use the standard transform gizmo, + face material/UV fields are undoable, and clip/intersect/merge/subtract provide conservative + bounds-based blockout operations with preview-before-commit. - Draw Brush mode (`B` or toolbar pencil) places snapped floor points; `Enter` locks the outline, mouse up/down adjusts height, and `Enter` or left-click commits additive prism brushes. Simple concave outlines decompose into convex brush parts, while self-intersections are blocked with @@ -207,8 +216,8 @@ Viewport shortcut keys require the pointer to be in the viewport and are suspend 1. Check the **mode badge** (bottom-right of the viewport). **Collider** hides meshes — click the **sun** toolbar icon for **Lit** shading. 2. **Window → Rendering → Active Camera**: check **Runtime lights** (directional/point/spot counts), scene vs project sun status, and contributing post-process volumes. Scene directionals override the project sun; use **Scene → Lighting → Use project sun** to restore project defaults. -3. **Solari requested but not visually changing**: check **Window → Rendering → Active Camera** for requested/effective GI, fallback reason, tagged meshes, Solari-compatible mesh assets, render instances, bind-group readiness, and compatible lights. Effective Solari forces an HDR camera target because Bevy Solari writes through a storage texture; imported/custom meshes need TriangleList geometry, POSITION/NORMAL/UV_0/TANGENT attributes, and U32 indices for Bevy 0.18 Solari. -4. **Lighting changes do nothing in Solari**: Bevy 0.18 Solari samples directional lights and emissive meshes, not point/spot lights. Use Directional lights or emissive materials in Solari; switch to Forward PBR for point/spot light authoring. +3. **Solari requested but not visually changing**: check **Window → Rendering → Active Camera** for requested/effective GI, fallback reason, tagged meshes, Solari-compatible mesh assets, render instances, bind-group readiness, and compatible lights. Effective Solari forces an HDR camera target because Bevy Solari writes through a storage texture; imported/custom meshes need TriangleList geometry, POSITION/NORMAL/UV_0/TANGENT attributes, and U32 indices for Bevy 0.19 Solari. +4. **Lighting changes do nothing in Solari**: Bevy 0.19 Solari samples directional lights and emissive meshes, not point/spot lights. Use Directional lights or emissive materials in Solari; switch to Forward PBR for point/spot light authoring. 5. **GiMode Auto shows Forward**: Solari RT wgpu features are unavailable on this GPU. Dev RT override: `BEVY_FPS_FORCE_SOLARI=1`. 6. **Volume overrides ignored**: confirm camera is inside volume AABB; check priority in Rendering → Volumes tab. 7. **Custom post FX**: RON under `assets/post_fx/`; assign path in volume inspector (see [docs/editor/rendering.md](docs/editor/rendering.md)). @@ -297,7 +306,8 @@ crates/ - [x] Command palette: reset lighting, group selection, focus selection - [x] CI: `cargo test -p shared`, scene authoring-only check on repo level, `cargo check -p editor --features dev` - [x] `scene` crate schema stamp/migrate/validate on save/load + CI `validate-levels` -- [x] Project Settings draft + Apply (HDR/swapchain safe); File → Project New/Open +- [x] Project Settings draft + Apply (HDR/swapchain safe) +- [ ] Project launcher/scaffolding with startup-time asset-root selection; the unsafe in-process File → Project scaffold was removed pending BS-JD-001 - [x] Editor lib/bin split + `EditorPluginGroup`; game EditorPlugin dogfood panel - [x] FBX/glTF model import + normalized `StaticMeshRenderer` placement; explicit scene-instance load via `bevy_ufbx` / `ModelRef` - [x] Asset browser model thumbnails (unified `assets/thumbnails/` pipeline; `ThumbnailState` cache; FBX via `FbxThumbnailSource`) @@ -307,6 +317,8 @@ crates/ - [x] Static mesh renderer component, generated normalized mesh artifacts, source/one-actor hierarchy placement, and inspector renderer slots ([ADR 0017](docs/adr/0017-normalized-static-mesh-assets.md)) - [x] Componentized actor inspector, unified component cards, thumbnail static mesh slots, imported asset Browse/Locate/Clear refs with inherited source defaults, collider split, shader-aware actor material data, and texture picker/drop refs ([ADR 0018](docs/adr/0018-componentized-actor-inspector-and-materials.md)) - [x] Brush authoring schema MVP with `ActorKind::Brush`, cube `BrushDesc`, generated mesh hydration, scene migration, and inspector Add Component support ([ADR 0021](docs/adr/0021-brush-authoring-schema.md)) +- [x] Brush draw, vertex/edge/face gizmo editing, face material/UV authoring, clip, and bounds-based CSG preview/commit workflow ([brush guide](docs/editor/brushes.md)) +- [x] Searchable command palette with human labels/stable IDs and a status bar that exposes scene I/O, tool, history, mode, and selection feedback ## Notes / Future Work @@ -319,7 +331,7 @@ crates/ Model assets generate normalized static mesh manifests under `assets/meshes/generated/`; drag/drop uses `StaticMeshRenderer` by default with imported asset refs and optional separate static mesh collider components. Expanded mesh subassets generate independent thumbnails and can be placed independently. Asset details can switch - placement to **Scene Instance** for `ModelRef`/`SceneRoot` playback, shared material assets can be + placement to **Scene Instance** for `ModelRef`/`WorldAssetRoot` playback, shared material assets can be edited from the browser, and delete actions move files to `assets/.trash/`. Skeletal animation playback is not supported by the static mesh path yet. - Prefab instances use stable asset IDs (`PrefabInstance` + registry) with serialized `PrefabOverrides` (transform, material, per-child visibility by name). Inspector **Apply/Revert** per field group; **Unpack** removes the instance link. diff --git a/assets/levels/editor_scene.scn.ron b/assets/levels/editor_scene.scn.ron index f73cf85..5055b76 100644 --- a/assets/levels/editor_scene.scn.ron +++ b/assets/levels/editor_scene.scn.ron @@ -1,900 +1,37 @@ (schema_version: 2, resources: {}, entities: { - 4294967133: ( + 4294967158: ( components: { - "bevy_ecs::name::Name": "Player Start", + "bevy_ecs::name::Name": "Scene Sun", "bevy_transform::components::transform::Transform": ( - translation: (0.0, 0.9900649, 12.46175), - rotation: (0.0, 0.0, 0.0, 1.0), + translation: (0.0, 5.316672, 0.0), + rotation: (-0.21401538, 0.31161797, 0.67592126, 0.6326311), scale: (1.0, 1.0, 1.0), ), - "shared::components::LevelObject": (), - "shared::components::PlayerSpawn": (), - "shared::components::ActorKind": PlayerSpawn, - }, - ), - 4294967162: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( - translation: (-1.3999996, 0.6, 4.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), + "shared::components::ActorKind": Light, + "shared::components::EditorVisibility": ( + visible: true, ), + "shared::components::HierarchySiblingIndex": (0), "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.9, - g: 0.9, - b: 0.92, + "shared::components::LightDesc": ( + kind: Directional, + color: ( + r: 1.0, + g: 0.95, + b: 0.85, a: 1.0, ), - metallic: 1.0, - roughness: 0.95, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, + intensity: 100000.0, + range: 0.0, + shadows: true, + inner_angle_deg: 25.0, + outer_angle_deg: 35.0, ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Sphere( - radius: 0.6, - ), - ), - "shared::components::Primitive": ( - shape: Sphere, - size: (1.2, 1.2, 1.2), - ), - "shared::components::ActorKind": StaticMesh, }, ), - 4294967163: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( - translation: (-3.6, 0.6, 4.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.9, - g: 0.9, - b: 0.92, - a: 1.0, - ), - metallic: 1.0, - roughness: 0.6, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Sphere( - radius: 0.6, - ), - ), - "shared::components::Primitive": ( - shape: Sphere, - size: (1.2, 1.2, 1.2), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967164: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( - translation: (-5.8, 0.6, 4.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.9, - g: 0.9, - b: 0.92, - a: 1.0, - ), - metallic: 1.0, - roughness: 0.3, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Sphere( - radius: 0.6, - ), - ), - "shared::components::Primitive": ( - shape: Sphere, - size: (1.2, 1.2, 1.2), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967165: ( - components: { - "bevy_ecs::name::Name": "ShowcaseSphere", - "bevy_transform::components::transform::Transform": ( - translation: (-8.0, 0.6, 4.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.9, - g: 0.9, - b: 0.92, - a: 1.0, - ), - metallic: 1.0, - roughness: 0.05, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Sphere( - radius: 0.6, - ), - ), - "shared::components::Primitive": ( - shape: Sphere, - size: (1.2, 1.2, 1.2), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967166: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.90000004, 9.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.8000001, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.8000001, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967167: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.81000006, 10.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.6200001, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.6200001, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967168: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.72, 10.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.44, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.44, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967169: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.63, 11.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.26, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.26, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967170: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.54, 11.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 1.08, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 1.08, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967171: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.45000002, 12.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.90000004, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.90000004, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967172: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.36, 12.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.72, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.72, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967173: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.27, 13.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.54, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.54, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967174: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.18, 13.5), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.36, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.36, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967175: ( - components: { - "bevy_ecs::name::Name": "Stair", - "bevy_transform::components::transform::Transform": ( - translation: (12.0, 0.09, 14.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.55, - g: 0.52, - b: 0.48, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.8, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.18, - z_length: 0.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.18, 0.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967176: ( - components: { - "bevy_ecs::name::Name": "Ramp", - "bevy_transform::components::transform::Transform": ( - translation: (-14.0, 1.6, 0.0), - rotation: (-0.1736482, 0.0, 0.0, 0.9848078), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.3, - g: 0.45, - b: 0.7, - a: 1.0, - ), - metallic: 0.1, - roughness: 0.55, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 5.0, - y_length: 0.4, - z_length: 9.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (5.0, 0.4, 9.0), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967177: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (5.4, 0.5, 2.4), - rotation: (0.0, 0.94948566, 0.0, -0.31381062), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.72, - g: 0.45, - b: 0.2, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.85, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.0, - y_length: 1.0, - z_length: 1.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967178: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (4.0, 0.75, 2.0), - rotation: (0.0, 0.9854498, 0.0, 0.16996716), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.72, - g: 0.45, - b: 0.2, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.85, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.5, - y_length: 1.5, - z_length: 1.5, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.5, 1.5, 1.5), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967179: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (-2.3, 0.5, -3.1), - rotation: (0.0, 0.72083056, 0.0, -0.6931113), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.72, - g: 0.45, - b: 0.2, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.85, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.0, - y_length: 1.0, - z_length: 1.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967180: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (-1.6, 0.5, -3.2), - rotation: (0.0, -0.53118616, 0.0, 0.84725505), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.72, - g: 0.45, - b: 0.2, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.85, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.0, - y_length: 1.0, - z_length: 1.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967181: ( - components: { - "bevy_ecs::name::Name": "Crate", - "bevy_transform::components::transform::Transform": ( - translation: (-3.0, 0.5, -3.0), - rotation: (0.0, 0.86742324, 0.0, -0.4975711), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.72, - g: 0.45, - b: 0.2, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.85, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.0, - y_length: 1.0, - z_length: 1.0, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.0, 1.0, 1.0), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967182: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( - translation: (8.0, 3.5, 6.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.85, - g: 0.85, - b: 0.88, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.3, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.2, - y_length: 7.0, - z_length: 1.2, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.2, 7.0, 1.2), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967183: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( - translation: (-8.0, 3.5, 6.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.85, - g: 0.85, - b: 0.88, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.3, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.2, - y_length: 7.0, - z_length: 1.2, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.2, 7.0, 1.2), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967184: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( - translation: (8.0, 3.5, -6.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.85, - g: 0.85, - b: 0.88, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.3, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.2, - y_length: 7.0, - z_length: 1.2, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.2, 7.0, 1.2), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967185: ( - components: { - "bevy_ecs::name::Name": "Pillar", - "bevy_transform::components::transform::Transform": ( - translation: (-8.0, 3.5, -6.0), - rotation: (0.0, 0.0, 0.0, 1.0), - scale: (1.0, 1.0, 1.0), - ), - "shared::components::LevelObject": (), - "shared::components::MaterialDesc": ( - base_color: ( - r: 0.85, - g: 0.85, - b: 0.88, - a: 1.0, - ), - metallic: 0.0, - roughness: 0.3, - base_color_texture: None, - normal_map_texture: None, - metallic_roughness_texture: None, - ), - "shared::components::PhysicsBody": ( - body: Static, - collider: Cuboid( - x_length: 1.2, - y_length: 7.0, - z_length: 1.2, - ), - ), - "shared::components::Primitive": ( - shape: Box, - size: (1.2, 7.0, 1.2), - ), - "shared::components::ActorKind": StaticMesh, - }, - ), - 4294967186: ( + 4294967159: ( components: { "bevy_ecs::name::Name": "Ground", "bevy_transform::components::transform::Transform": ( @@ -902,8 +39,18 @@ rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (1), "shared::components::LevelObject": (), "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), base_color: ( r: 0.42, g: 0.45, @@ -912,9 +59,20 @@ ), metallic: 0.0, roughness: 0.92, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, base_color_texture: None, + emissive_texture: None, normal_map_texture: None, metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], ), "shared::components::PhysicsBody": ( body: Static, @@ -928,31 +86,1816 @@ shape: Box, size: (200.0, 1.0, 200.0), ), - "shared::components::ActorKind": StaticMesh, }, ), - 25769803611: ( + 8589934456: ( components: { - "bevy_ecs::name::Name": "Scene Sun", + "bevy_ecs::name::Name": "Player Start", "bevy_transform::components::transform::Transform": ( - translation: (0.0, 5.316672, 0.0), - rotation: (-0.21401538, 0.31161797, 0.67592126, 0.6326311), + translation: (0.0, 0.9900649, 12.46175), + rotation: (0.0, 0.0, 0.0, 1.0), scale: (1.0, 1.0, 1.0), ), + "shared::components::ActorKind": PlayerSpawn, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (2), "shared::components::LevelObject": (), - "shared::components::LightDesc": ( - kind: Directional, - color: ( - r: 1.0, - g: 0.95, - b: 0.85, + "shared::components::PlayerSpawn": (), + }, + ), + 8589934457: ( + components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( + translation: (-1.3999996, 0.6, 4.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (3), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.9, + g: 0.9, + b: 0.92, a: 1.0, ), - intensity: 100000.0, - range: 0.0, - shadows: true, + metallic: 1.0, + roughness: 0.95, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Sphere( + radius: 0.6, + ), + ), + "shared::components::Primitive": ( + shape: Sphere, + size: (1.2, 1.2, 1.2), + ), + }, + ), + 8589934458: ( + components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( + translation: (-3.6, 0.6, 4.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (4), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.9, + g: 0.9, + b: 0.92, + a: 1.0, + ), + metallic: 1.0, + roughness: 0.6, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Sphere( + radius: 0.6, + ), + ), + "shared::components::Primitive": ( + shape: Sphere, + size: (1.2, 1.2, 1.2), + ), + }, + ), + 8589934459: ( + components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( + translation: (-5.8, 0.6, 4.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (5), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.9, + g: 0.9, + b: 0.92, + a: 1.0, + ), + metallic: 1.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Sphere( + radius: 0.6, + ), + ), + "shared::components::Primitive": ( + shape: Sphere, + size: (1.2, 1.2, 1.2), + ), + }, + ), + 8589934460: ( + components: { + "bevy_ecs::name::Name": "ShowcaseSphere", + "bevy_transform::components::transform::Transform": ( + translation: (-8.0, 0.6, 4.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (6), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.9, + g: 0.9, + b: 0.92, + a: 1.0, + ), + metallic: 1.0, + roughness: 0.05, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Sphere( + radius: 0.6, + ), + ), + "shared::components::Primitive": ( + shape: Sphere, + size: (1.2, 1.2, 1.2), + ), + }, + ), + 8589934461: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.90000004, 9.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (7), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.8000001, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.8000001, 0.5), + ), + }, + ), + 8589934462: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.81000006, 10.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (8), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.6200001, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.6200001, 0.5), + ), + }, + ), + 8589934463: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.72, 10.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (9), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.44, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.44, 0.5), + ), + }, + ), + 8589934464: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.63, 11.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (10), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.26, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.26, 0.5), + ), + }, + ), + 8589934465: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.54, 11.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (11), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 1.08, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 1.08, 0.5), + ), + }, + ), + 8589934466: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.45000002, 12.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (12), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.90000004, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.90000004, 0.5), + ), + }, + ), + 8589934467: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.36, 12.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (13), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.72, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.72, 0.5), + ), + }, + ), + 8589934468: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.27, 13.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (14), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.54, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.54, 0.5), + ), + }, + ), + 8589934469: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.18, 13.5), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (15), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.36, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.36, 0.5), + ), + }, + ), + 8589934470: ( + components: { + "bevy_ecs::name::Name": "Stair", + "bevy_transform::components::transform::Transform": ( + translation: (12.0, 0.09, 14.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (16), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.52, + b: 0.48, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.8, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.18, + z_length: 0.5, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.18, 0.5), + ), + }, + ), + 8589934471: ( + components: { + "bevy_ecs::name::Name": "Ramp", + "bevy_transform::components::transform::Transform": ( + translation: (-14.0, 1.6, 0.0), + rotation: (-0.17364821, 0.0, 0.0, 0.9848078), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (17), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.3, + g: 0.45, + b: 0.7, + a: 1.0, + ), + metallic: 0.1, + roughness: 0.55, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 5.0, + y_length: 0.4, + z_length: 9.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (5.0, 0.4, 9.0), + ), + }, + ), + 8589934472: ( + components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (5.4, 0.5, 2.4), + rotation: (0.0, 0.94948566, 0.0, -0.31381062), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (18), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.72, + g: 0.45, + b: 0.2, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.0, + y_length: 1.0, + z_length: 1.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.0, 1.0, 1.0), + ), + }, + ), + 8589934473: ( + components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (14.575503, 4.5, -15.5), + rotation: (0.77714586, -0.0, -0.6293205, -0.0), + scale: (8.5, 6.7, 5.6), + ), + "shared::components::ActorKind": Brush, + "shared::components::BrushDesc": ( + kind: Additive, + faces: [ + ( + id: ("face:+x"), + plane: ( + normal: (1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, 0.5, 0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-x"), + plane: ( + normal: (-1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, 0.5, -0.5), + (-0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+y"), + plane: ( + normal: (0.0, 1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, 0.5, -0.5), + (-0.5, 0.5, 0.5), + (0.5, 0.5, 0.5), + (0.5, 0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-y"), + plane: ( + normal: (0.0, -1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, -0.5, -0.5), + (0.5, -0.5, -0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+z"), + plane: ( + normal: (0.0, 0.0, 1.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, 0.5), + (0.5, 0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-z"), + plane: ( + normal: (0.0, 0.0, -1.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, -0.5), + (-0.5, 0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ], + cast_shadows: true, + receive_shadows: true, + ), + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (19), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.55, + g: 0.54, + b: 0.52, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: Some("assets/materials/concrete.ron"), + parameters: [], + textures: [], + ), + }, + ), + 8589934474: ( + components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (-2.3, 0.5, -3.1), + rotation: (0.0, 0.72083056, 0.0, -0.6931113), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (20), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.72, + g: 0.45, + b: 0.2, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.0, + y_length: 1.0, + z_length: 1.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.0, 1.0, 1.0), + ), + }, + ), + 8589934475: ( + components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (-1.6, 0.5, -3.2), + rotation: (0.0, -0.53118616, 0.0, 0.8472551), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (21), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.72, + g: 0.45, + b: 0.2, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.0, + y_length: 1.0, + z_length: 1.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.0, 1.0, 1.0), + ), + }, + ), + 8589934476: ( + components: { + "bevy_ecs::name::Name": "Crate", + "bevy_transform::components::transform::Transform": ( + translation: (-3.0, 0.5, -3.0), + rotation: (0.0, 0.86742324, 0.0, -0.49757105), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (22), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.72, + g: 0.45, + b: 0.2, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.85, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.0, + y_length: 1.0, + z_length: 1.0, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.0, 1.0, 1.0), + ), + }, + ), + 8589934477: ( + components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( + translation: (8.0, 3.5, 6.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (23), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.85, + g: 0.85, + b: 0.88, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.2, + y_length: 7.0, + z_length: 1.2, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.2, 7.0, 1.2), + ), + }, + ), + 8589934478: ( + components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( + translation: (-8.0, 3.5, 6.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (24), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.85, + g: 0.85, + b: 0.88, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.2, + y_length: 7.0, + z_length: 1.2, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.2, 7.0, 1.2), + ), + }, + ), + 8589934479: ( + components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( + translation: (8.0, 3.5, -6.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (25), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.85, + g: 0.85, + b: 0.88, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.2, + y_length: 7.0, + z_length: 1.2, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.2, 7.0, 1.2), + ), + }, + ), + 8589934480: ( + components: { + "bevy_ecs::name::Name": "Pillar", + "bevy_transform::components::transform::Transform": ( + translation: (-8.0, 3.5, -6.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorKind": StaticMesh, + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (26), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.85, + g: 0.85, + b: 0.88, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.3, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], + ), + "shared::components::PhysicsBody": ( + body: Static, + collider: Cuboid( + x_length: 1.2, + y_length: 7.0, + z_length: 1.2, + ), + ), + "shared::components::Primitive": ( + shape: Box, + size: (1.2, 7.0, 1.2), + ), + }, + ), + 21474836282: ( + components: { + "bevy_ecs::name::Name": "Post Process Volume", + "bevy_transform::components::transform::Transform": ( + translation: (28.0, 2.0, -1.0), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: (8.257915, 1.0, 1.0), + ), + "shared::components::ActorId": ("77e41415-e7c0-4fef-82ba-8dacad7ad0a0"), + "shared::components::ActorKind": Brush, + "shared::components::BrushDesc": ( + kind: Additive, + faces: [ + ( + id: ("face:+x"), + plane: ( + normal: (1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, 0.5, 0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-x"), + plane: ( + normal: (-1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, 0.5, -0.5), + (-0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+y"), + plane: ( + normal: (0.0, 1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, 0.5, -0.5), + (-0.5, 0.5, 0.5), + (0.5, 0.5, 0.5), + (0.5, 0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-y"), + plane: ( + normal: (0.0, -1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, -0.5, -0.5), + (0.5, -0.5, -0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+z"), + plane: ( + normal: (0.0, 0.0, 1.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, 0.5), + (0.5, 0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-z"), + plane: ( + normal: (0.0, 0.0, -1.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, -0.5), + (-0.5, 0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ], + cast_shadows: true, + receive_shadows: true, + ), + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (27), + "shared::components::LevelObject": (), + "shared::components::PostProcessVolumeDesc": ( + half_extents: (4.0, 2.0, 4.0), + priority: 0, + blend_distance: 2.0, + overrides: ( + exposure_ev100: None, + fog_color: None, + fog_density: None, + bloom: None, + taa: None, + ssao: None, + tonemapping_aces: None, + atmosphere: None, + hdr: None, + ), + fullscreen_effect: None, + profile: None, + label: None, + ), + }, + ), + 21474836285: ( + components: { + "bevy_ecs::name::Name": "Crate Copy", + "bevy_transform::components::transform::Transform": ( + translation: (15.624287, 0.75, 1.8058548), + rotation: (0.0, 0.77714604, 0.0, -0.62932026), + scale: (1.0, 1.0, 1.0), + ), + "shared::components::ActorId": ("a3abc97b-7560-4b41-9670-023efb5d61bd"), + "shared::components::ActorKind": Brush, + "shared::components::BrushDesc": ( + kind: SubtractiveMarker, + faces: [ + ( + id: ("face:+x"), + plane: ( + normal: (1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, 0.5, 0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-x"), + plane: ( + normal: (-1.0, 0.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, 0.5, -0.5), + (-0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+y"), + plane: ( + normal: (0.0, 1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, 0.5, -0.5), + (-0.5, 0.5, 0.5), + (0.5, 0.5, 0.5), + (0.5, 0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-y"), + plane: ( + normal: (0.0, -1.0, 0.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, 0.5), + (-0.5, -0.5, -0.5), + (0.5, -0.5, -0.5), + (0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:+z"), + plane: ( + normal: (0.0, 0.0, 1.0), + distance: 0.5, + ), + vertices: [ + (0.5, -0.5, 0.5), + (0.5, 0.5, 0.5), + (-0.5, 0.5, 0.5), + (-0.5, -0.5, 0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ( + id: ("face:-z"), + plane: ( + normal: (0.0, 0.0, -1.0), + distance: 0.5, + ), + vertices: [ + (-0.5, -0.5, -0.5), + (-0.5, 0.5, -0.5), + (0.5, 0.5, -0.5), + (0.5, -0.5, -0.5), + ], + material: None, + texture: None, + uv_offset: (0.0, 0.0), + uv_scale: (1.0, 1.0), + uv_rotation: 0.0, + smoothing_group: 0, + ), + ], + cast_shadows: true, + receive_shadows: true, + ), + "shared::components::EditorVisibility": ( + visible: true, + ), + "shared::components::HierarchySiblingIndex": (19), + "shared::components::LevelObject": (), + "shared::components::MaterialDesc": ( + shader: ( + kind: StandardLit, + schema_path: None, + shader_path: None, + ), + base_color: ( + r: 0.8, + g: 0.8, + b: 0.8, + a: 1.0, + ), + metallic: 0.0, + roughness: 0.65, + emissive_color: ( + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + ), + emissive_intensity: 0.0, + base_color_texture: None, + emissive_texture: None, + normal_map_texture: None, + metallic_roughness_texture: None, + material_asset_path: None, + parameters: [], + textures: [], ), - "shared::components::ActorKind": Light, }, ), }, diff --git a/assets/meshes/generated/71071fc3-7a7f-454d-b9ab-93ebf0feccbe.static_mesh.ron b/assets/meshes/generated/71071fc3-7a7f-454d-b9ab-93ebf0feccbe.static_mesh.ron index ca1db68..33bee9d 100644 --- a/assets/meshes/generated/71071fc3-7a7f-454d-b9ab-93ebf0feccbe.static_mesh.ron +++ b/assets/meshes/generated/71071fc3-7a7f-454d-b9ab-93ebf0feccbe.static_mesh.ron @@ -7,7 +7,7 @@ format: "gltf", fingerprint: ( byte_len: 2790, - modified_unix_secs: 1780712587, + modified_unix_secs: 1780734867, ), dependencies: [ "assets/models/metal_stool_01.bin", diff --git a/crates/editor/src/assets/asset_db.rs b/crates/editor/src/assets/asset_db.rs index bd8800b..847d2f9 100644 --- a/crates/editor/src/assets/asset_db.rs +++ b/crates/editor/src/assets/asset_db.rs @@ -25,42 +25,27 @@ impl Default for AssetId { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] pub enum ModelPlacementMode { + #[default] StaticAsset, SceneInstance, } -impl Default for ModelPlacementMode { - fn default() -> Self { - Self::StaticAsset - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] pub enum ModelHierarchyMode { + #[default] SingleActor, SourceHierarchy, } -impl Default for ModelHierarchyMode { - fn default() -> Self { - Self::SingleActor - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] pub enum MaterialImportPolicy { + #[default] SourceMaterials, AuthoringOverride, } -impl Default for MaterialImportPolicy { - fn default() -> Self { - Self::SourceMaterials - } -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ImportSettings { pub scale: f32, diff --git a/crates/editor/src/assets/operators.rs b/crates/editor/src/assets/operators.rs index 7581600..fae0062 100644 --- a/crates/editor/src/assets/operators.rs +++ b/crates/editor/src/assets/operators.rs @@ -59,14 +59,13 @@ pub fn apply_texture_operator( ) -> bool { let label = format!("Apply Texture {}", asset.label); let availability = selection_availability(world, selected); - let selected = selected; run_immediate_operator( world, "assets.apply_texture", &label, availability, move |world| { - apply_texture_to_selection(world, &asset, &selected); + apply_texture_to_selection(world, &asset, selected); Ok(()) }, ) @@ -79,14 +78,13 @@ pub fn apply_material_operator( ) -> bool { let label = format!("Apply Material {}", asset.label); let availability = selection_availability(world, selected); - let selected = selected; run_immediate_operator( world, "assets.apply_material", &label, availability, move |world| { - apply_material_asset_to_selection(world, &asset, &selected); + apply_material_asset_to_selection(world, &asset, selected); Ok(()) }, ) diff --git a/crates/editor/src/assets/static_mesh.rs b/crates/editor/src/assets/static_mesh.rs index 9ae8962..db8d374 100644 --- a/crates/editor/src/assets/static_mesh.rs +++ b/crates/editor/src/assets/static_mesh.rs @@ -206,9 +206,9 @@ fn build_static_mesh_manifest(record: &AssetRecord) -> Result, }, } diff --git a/crates/editor/src/assets/thumbnails/studio.rs b/crates/editor/src/assets/thumbnails/studio.rs index 4abaa57..a2b82e7 100644 --- a/crates/editor/src/assets/thumbnails/studio.rs +++ b/crates/editor/src/assets/thumbnails/studio.rs @@ -10,7 +10,6 @@ use bevy::math::bounding::Aabb3d; use bevy::mesh::VertexAttributeValues; use bevy::prelude::*; use bevy::render::render_resource::TextureFormat; -use bevy::scene::SceneRoot; use bevy_egui::EguiUserTextures; use shared::{material_from_desc, ModelRef}; @@ -134,6 +133,7 @@ fn setup_thumbnail_studio(mut commands: Commands, mut images: ResMut { if uses_scene_root(model_path) { - commands.entity(root).insert(SceneRoot( + commands.entity(root).insert(WorldAssetRoot( asset_server.load(model_scene_asset_path(model_path, 0)), )); } @@ -494,6 +494,10 @@ fn finish_active_thumbnail( studio.cooldown_frames = COOLDOWN_FRAMES; } +#[expect( + clippy::too_many_arguments, + reason = "thumbnail failure coordinates the job, cache, render target, and camera state" +)] fn fail_active_thumbnail( commands: &mut Commands, studio: &mut ThumbnailStudio, diff --git a/crates/editor/src/ext/extensibility.rs b/crates/editor/src/ext/extensibility.rs index 0b7e11b..a2e6d98 100644 --- a/crates/editor/src/ext/extensibility.rs +++ b/crates/editor/src/ext/extensibility.rs @@ -35,6 +35,12 @@ pub struct EditorCommandRegistry { commands: Vec>, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EditorCommandEntry { + pub name: String, + pub label: String, +} + impl EditorCommandRegistry { pub fn register(&mut self, command: Box) { self.commands.push(command); @@ -47,6 +53,16 @@ impl EditorCommandRegistry { .collect() } + pub fn entries(&self) -> Vec { + self.commands + .iter() + .map(|command| EditorCommandEntry { + name: command.name().to_string(), + label: command.label().to_string(), + }) + .collect() + } + pub fn command_state(&self, world: &World, name: &str) -> Option<(String, Option)> { self.commands .iter() @@ -249,6 +265,10 @@ impl EditorCommand for TogglePlayCommand { "play.toggle" } + fn label(&self) -> &str { + "Toggle Play / Edit" + } + fn execute(&self, world: &mut World) { toggle_play_mode(world); } @@ -261,6 +281,15 @@ impl EditorCommand for TogglePlayPausedCommand { "play.toggle_pause" } + fn label(&self) -> &str { + "Pause / Resume Simulation" + } + + fn disabled_reason(&self, world: &World) -> Option { + (*world.resource::>().get() != EditorMode::Playing) + .then(|| "Enter Play mode before pausing the simulation".to_string()) + } + fn execute(&self, world: &mut World) { toggle_play_paused(world); } @@ -273,6 +302,10 @@ impl EditorCommand for TogglePossessionCommand { "play.toggle_possession" } + fn label(&self) -> &str { + "Possess / Eject Player" + } + fn disabled_reason(&self, world: &World) -> Option { (*world.resource::>().get() != EditorMode::Playing) .then(|| "Enter Play mode before toggling possession".to_string()) @@ -294,6 +327,10 @@ impl EditorCommand for ResetLightingCommand { "scene.reset_lighting" } + fn label(&self) -> &str { + "Reset Scene Lighting" + } + fn execute(&self, world: &mut World) { reset_scene_lighting_to_project_defaults(world); } @@ -306,6 +343,10 @@ impl EditorCommand for GroupSelectionCommand { "selection.group" } + fn label(&self) -> &str { + "Group Selection" + } + fn execute(&self, world: &mut World) { let selected: Vec = world .resource::() @@ -323,6 +364,10 @@ impl EditorCommand for FocusSelectionCommand { "selection.focus" } + fn label(&self) -> &str { + "Focus Selection" + } + fn execute(&self, world: &mut World) { focus_editor_camera_on_selection(world); } @@ -335,6 +380,10 @@ impl EditorCommand for ResetSelectionTransformCommand { "selection.reset_transform" } + fn label(&self) -> &str { + "Reset Selection Transform" + } + fn execute(&self, world: &mut World) { reset_selected_transforms(world); } @@ -428,6 +477,10 @@ impl EditorCommand for CreatePostProcessVolumeCommand { "rendering.create_volume" } + fn label(&self) -> &str { + "Create Post-process Volume" + } + fn execute(&self, world: &mut World) { crate::rendering_diagnostics::spawn_post_process_volume_at_camera(world); } @@ -440,6 +493,10 @@ impl EditorCommand for FocusActiveVolumesCommand { "rendering.focus_active_volumes" } + fn label(&self) -> &str { + "Focus Active Post-process Volumes" + } + fn execute(&self, world: &mut World) { crate::rendering_diagnostics::select_volumes_at_camera(world); focus_editor_camera_on_selection(world); @@ -453,6 +510,10 @@ impl EditorCommand for SelectVolumesAtCameraCommand { "rendering.select_volumes_at_camera" } + fn label(&self) -> &str { + "Select Volumes at Camera" + } + fn execute(&self, world: &mut World) { crate::rendering_diagnostics::select_volumes_at_camera(world); } @@ -471,6 +532,7 @@ fn command_palette_ui( if ctx.input(|input| input.modifiers.command && input.key_pressed(egui::Key::P)) { palette.open = true; + palette.filter.clear(); palette.focus_filter = true; palette.selected_index = 0; } @@ -480,19 +542,20 @@ fn command_palette_ui( } let mut open = palette.open; + let palette_width = (ctx.content_rect().width() - 32.0).clamp(320.0, 520.0); egui::Window::new("Command Palette") .open(&mut open) - .default_width(400.0) - .frame(egui::Frame::window(&ctx.style()).fill(PANEL_BG)) + .anchor(egui::Align2::CENTER_TOP, egui::vec2(0.0, 64.0)) + .collapsible(false) + .resizable(false) + .default_width(palette_width) + .min_width(palette_width) + .max_width(palette_width) + .frame(egui::Frame::window(&ctx.global_style()).fill(PANEL_BG)) .show(ctx, |ui| { - ui.label( - egui::RichText::new("Ctrl+P — filter and run commands") - .color(TEXT_DIM) - .small(), - ); let filter_response = ui.add( egui::TextEdit::singleline(&mut palette.filter) - .hint_text("Type to filter...") + .hint_text("Search commands...") .desired_width(f32::INFINITY), ); if palette.focus_filter { @@ -503,35 +566,30 @@ fn command_palette_ui( palette.selected_index = 0; } - let filter = palette.filter.to_lowercase(); - let filtered_names: Vec = registry - .names() - .into_iter() - .filter(|name| filter.is_empty() || name.to_lowercase().contains(&filter)) - .collect(); - if !filtered_names.is_empty() { - palette.selected_index = palette.selected_index.min(filtered_names.len() - 1); + let filtered_entries = filtered_command_entries(®istry, &palette.filter); + if !filtered_entries.is_empty() { + palette.selected_index = palette.selected_index.min(filtered_entries.len() - 1); } else { palette.selected_index = 0; } if ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::ArrowDown)) - && !filtered_names.is_empty() + && !filtered_entries.is_empty() { - palette.selected_index = (palette.selected_index + 1) % filtered_names.len(); + palette.selected_index = (palette.selected_index + 1) % filtered_entries.len(); } if ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::ArrowUp)) - && !filtered_names.is_empty() + && !filtered_entries.is_empty() { palette.selected_index = if palette.selected_index == 0 { - filtered_names.len() - 1 + filtered_entries.len() - 1 } else { palette.selected_index - 1 }; } if ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::Enter)) { - if let Some(name) = filtered_names.get(palette.selected_index) { - palette.pending_run = Some(name.clone()); + if let Some(entry) = filtered_entries.get(palette.selected_index) { + palette.pending_run = Some(entry.name.clone()); palette.open = false; ui.close(); } @@ -543,18 +601,23 @@ fn command_palette_ui( ui.separator(); egui::ScrollArea::vertical() - .max_height(280.0) + .max_height(320.0) .show(ui, |ui| { - for (index, name) in filtered_names.iter().enumerate() { + if filtered_entries.is_empty() { + ui.weak("No matching commands"); + } + for (index, entry) in filtered_entries.iter().enumerate() { let selected = index == palette.selected_index; - let response = - ui.selectable_label(selected, egui::RichText::new(name).color(TEXT)); + let response = ui.add_sized( + [ui.available_width(), 40.0], + egui::Button::selectable(selected, command_palette_row(entry)), + ); if selected { response.scroll_to_me(Some(egui::Align::Center)); } if response.clicked() { palette.selected_index = index; - palette.pending_run = Some(name.clone()); + palette.pending_run = Some(entry.name.clone()); palette.open = false; ui.close(); } @@ -565,3 +628,73 @@ fn command_palette_ui( Ok(()) } + +fn filtered_command_entries( + registry: &EditorCommandRegistry, + filter: &str, +) -> Vec { + let filter = filter.trim().to_lowercase(); + registry + .entries() + .into_iter() + .filter(|entry| { + filter.is_empty() + || entry.name.to_lowercase().contains(&filter) + || entry.label.to_lowercase().contains(&filter) + }) + .collect() +} + +fn command_palette_row(entry: &EditorCommandEntry) -> egui::WidgetText { + let mut job = egui::text::LayoutJob::default(); + job.append( + &entry.label, + 0.0, + egui::TextFormat { + font_id: egui::FontId::new(13.0, egui::FontFamily::Proportional), + color: TEXT, + ..Default::default() + }, + ); + job.append( + &format!("\n{}", entry.name), + 0.0, + egui::TextFormat { + font_id: egui::FontId::new(11.0, egui::FontFamily::Monospace), + color: TEXT_DIM, + ..Default::default() + }, + ); + job.into() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn command_filter_matches_human_label_and_stable_id() { + let mut registry = EditorCommandRegistry::default(); + registry.register(Box::new(TogglePlayCommand)); + registry.register(Box::new(ResetLightingCommand)); + + let by_label = filtered_command_entries(®istry, "scene lighting"); + assert_eq!(by_label.len(), 1); + assert_eq!(by_label[0].name, "scene.reset_lighting"); + + let by_id = filtered_command_entries(®istry, "play.toggle"); + assert_eq!(by_id.len(), 1); + assert_eq!(by_id[0].label, "Toggle Play / Edit"); + } + + #[test] + fn empty_command_filter_preserves_registration_order() { + let mut registry = EditorCommandRegistry::default(); + registry.register(Box::new(TogglePlayCommand)); + registry.register(Box::new(ResetLightingCommand)); + + let entries = filtered_command_entries(®istry, " "); + assert_eq!(entries[0].name, "play.toggle"); + assert_eq!(entries[1].name, "scene.reset_lighting"); + } +} diff --git a/crates/editor/src/history/mod.rs b/crates/editor/src/history/mod.rs index 7a49add..d60de83 100644 --- a/crates/editor/src/history/mod.rs +++ b/crates/editor/src/history/mod.rs @@ -282,10 +282,8 @@ pub fn spawn_many_with_history( if snapshots.is_empty() { return Vec::new(); } - let mut sibling_index = next_sibling_index(world, None); - for snapshot in &mut snapshots { + for (sibling_index, snapshot) in (next_sibling_index(world, None)..).zip(snapshots.iter_mut()) { snapshot.hierarchy_sibling_index = sibling_index; - sibling_index += 1; } let entities: Vec = snapshots .iter() @@ -1489,11 +1487,14 @@ fn capture_gizmo_transform_edits(world: &mut World) { Entity, &Transform, &transform_gizmo_bevy::prelude::GizmoTarget, + Option<&crate::viewport::brush_edit::BrushElementGizmo>, )>(); let active = query .iter(world) - .find(|(_, _, target)| target.is_active()) - .map(|(entity, transform, _)| (entity, *transform)); + .find(|(_, _, target, brush_element_gizmo)| { + brush_element_gizmo.is_none() && target.is_active() + }) + .map(|(entity, transform, _, _)| (entity, *transform)); let start_group = active.map(|(entity, transform)| selected_transforms(world, &selection, entity, transform)); diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 8de22f6..b0763da 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -24,7 +24,6 @@ pub use play::net_editor; pub use play::state; pub use project::project_io; pub use project::settings_ui; -pub use project::workspace; pub use scene::scene_io; pub use scene::scene_schema; pub use scene::scene_view; @@ -81,7 +80,6 @@ impl PluginGroup for EditorPluginGroup { let group = PluginGroupBuilder::start::() .add(EditorInfraPlugin) .add(ProjectIoPlugin) - .add(workspace::WorkspacePlugin) .add(scene_schema::SceneSchemaPlugin) .add(net_editor::NetEditorPlugin) .add(AssetDbPlugin) diff --git a/crates/editor/src/project/mod.rs b/crates/editor/src/project/mod.rs index ebcbc80..e1fbc7a 100644 --- a/crates/editor/src/project/mod.rs +++ b/crates/editor/src/project/mod.rs @@ -2,4 +2,3 @@ pub mod project_io; pub mod settings_ui; -pub mod workspace; diff --git a/crates/editor/src/project/workspace.rs b/crates/editor/src/project/workspace.rs deleted file mode 100644 index 2c76ea0..0000000 --- a/crates/editor/src/project/workspace.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! Project workspace New/Open and recent scene switcher (H2). - -use std::path::{Path, PathBuf}; - -use bevy::prelude::*; -use settings::{ - load_project_settings_from_path, ProjectSettings, ProjectSettingsIo, DEFAULT_PROJECT_PATH, -}; - -use crate::history::EditorHistory; -use crate::project_io::{push_recent, ProjectWorkspace, UserPreferences}; -use crate::scene_io::{SceneIo, SceneIoRequest}; - -pub struct WorkspacePlugin; - -impl Plugin for WorkspacePlugin { - fn build(&self, app: &mut App) { - app.init_resource::() - .init_resource::() - .add_systems(Update, process_workspace_requests); - } -} - -#[derive(Resource, Default)] -pub struct WorkspaceState { - pub project_name: String, -} - -#[derive(Resource, Default, Debug)] -pub struct WorkspaceIoRequest { - pub new_project: bool, - pub open_project: bool, -} - -pub fn request_new_project(world: &mut World) { - world.resource_mut::().new_project = true; -} - -pub fn request_open_project(world: &mut World) { - world.resource_mut::().open_project = true; -} - -pub fn request_open_recent_scene(world: &mut World, index: usize) { - world.resource_mut::().request = Some(SceneIoRequest::OpenRecent(index)); -} - -pub fn sync_recent_levels_to_scene_io(world: &mut World) { - let recent: Vec = world - .resource::() - .recent_levels - .iter() - .map(PathBuf::from) - .collect(); - world.resource_mut::().recent_paths = recent; -} - -pub fn remember_opened_level(world: &mut World, path: String) { - if let Some(mut prefs) = world.get_resource_mut::() { - push_recent(&mut prefs.recent_levels, path, 8); - let _ = crate::project_io::write_user_preferences(&prefs); - } -} - -fn process_workspace_requests(world: &mut World) { - let (new_project, open_project) = { - let mut req = world.resource_mut::(); - let new_project = req.new_project; - let open_project = req.open_project; - req.new_project = false; - req.open_project = false; - (new_project, open_project) - }; - - if new_project { - world.resource_mut::().status = apply_new_project(world); - } else if open_project { - world.resource_mut::().status = apply_open_project(world); - } -} - -fn apply_new_project(world: &mut World) -> String { - let default_settings = ProjectSettings::default(); - let settings_path = DEFAULT_PROJECT_PATH.to_string(); - - *world.resource_mut::() = default_settings.clone(); - world.resource_mut::().path = settings_path.clone(); - world.resource_mut::().dirty = false; - - if let Some(mut workspace) = world.get_resource_mut::() { - workspace.root = std::env::current_dir() - .ok() - .and_then(|p| p.into_os_string().into_string().ok()) - .unwrap_or_else(|| ".".into()); - workspace.settings_path = settings_path; - workspace.settings_dirty = false; - } - - world.resource_mut::().project_name = default_settings.name.clone(); - world.resource_mut::().request = Some(SceneIoRequest::New); - world.resource_mut::().clear(); - sync_recent_levels_to_scene_io(world); - format!("New project: {}", default_settings.name) -} - -fn apply_open_project(world: &mut World) -> String { - let Some(root) = rfd::FileDialog::new() - .set_title("Open Project Folder") - .pick_folder() - else { - return "Open project cancelled".to_string(); - }; - - let settings_path = root.join("assets/project.ron"); - if !settings_path.exists() { - return format!( - "No assets/project.ron in {} — pick a project root folder", - root.display() - ); - } - - let settings_path_label = settings_path.display().to_string(); - let settings = load_project_settings_from_path(&settings_path_label); - - *world.resource_mut::() = settings.clone(); - world.resource_mut::().path = settings_path_label.clone(); - world.resource_mut::().dirty = false; - - if let Some(mut workspace) = world.get_resource_mut::() { - workspace.root = root.display().to_string(); - workspace.settings_path = settings_path_label; - workspace.settings_dirty = false; - } - - if let Some(mut prefs) = world.get_resource_mut::() { - push_recent(&mut prefs.recent_projects, workspace_root_label(&root), 8); - prefs.last_project_root = Some(root.display().to_string()); - let _ = crate::project_io::write_user_preferences(&prefs); - } - - world.resource_mut::().project_name = settings.name.clone(); - sync_recent_levels_to_scene_io(world); - format!("Opened project: {}", settings.name) -} - -fn workspace_root_label(path: &Path) -> String { - path.file_name() - .and_then(|name| name.to_str()) - .map(str::to_string) - .unwrap_or_else(|| path.display().to_string()) -} diff --git a/crates/editor/src/scene/scene_io.rs b/crates/editor/src/scene/scene_io.rs index d3c8c76..138aed8 100644 --- a/crates/editor/src/scene/scene_io.rs +++ b/crates/editor/src/scene/scene_io.rs @@ -3,10 +3,9 @@ use std::path::{Path, PathBuf}; use bevy::ecs::entity::EntityHashMap; use bevy::ecs::system::SystemState; use bevy::prelude::*; -use bevy::scene::serde::SceneDeserializer; -use bevy::scene::DynamicScene; -use bevy::scene::DynamicSceneBuilder; use bevy::window::PrimaryWindow; +use bevy::world_serialization::serde::WorldDeserializer; +use bevy::world_serialization::{DynamicWorld, DynamicWorldBuilder}; use scene::{document::SceneDocument, strip_schema_version, validate_level_text}; use serde::de::DeserializeSeed; use shared::{ @@ -469,42 +468,42 @@ fn save_entities(world: &mut World, path: &Path, entities: Vec) -> Resul } } - let scene = DynamicSceneBuilder::from_world(world) - .deny_all() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .allow_component::() - .extract_entities(entities.into_iter()) - .remove_empty_entities() - .build(); - let ron = { let registry = world.resource::().read(); + let scene = DynamicWorldBuilder::from_world(world, ®istry) + .deny_all() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .allow_component::() + .extract_entities(entities.into_iter()) + .remove_empty_entities() + .build(); + scene .serialize(®istry) .map_err(|err| format!("could not serialize scene: {err}"))? @@ -536,10 +535,12 @@ fn load_level(world: &mut World, path: &Path) -> Result<(), String> { clear_loaded_scene_roots(world); clear_level_objects(world); - let dynamic_scene: DynamicScene = { + let dynamic_scene: DynamicWorld = { let registry = world.resource::().read(); - let scene_deserializer = SceneDeserializer { + let mut asset_server = world.resource::().clone(); + let scene_deserializer = WorldDeserializer { type_registry: ®istry, + load_from_path: &mut asset_server, }; let mut deserializer = ron::de::Deserializer::from_str(&bevy_ron).map_err(|err| err.to_string())?; @@ -590,7 +591,9 @@ fn finalize_scene_load(world: &mut World) { )> = SystemState::new(world); { - let (settings, scene_suns, project_suns) = state.get_mut(world); + let (settings, scene_suns, project_suns) = state + .get_mut(world) + .expect("finalize_scene_load system params should be valid"); game_hot::sync_project_sun_from_settings(settings, scene_suns, project_suns); } state.apply(world); diff --git a/crates/editor/src/ui/asset_browser/panel.rs b/crates/editor/src/ui/asset_browser/panel.rs index 3683477..30eba3e 100644 --- a/crates/editor/src/ui/asset_browser/panel.rs +++ b/crates/editor/src/ui/asset_browser/panel.rs @@ -897,6 +897,10 @@ fn visible_assets( rows } +#[expect( + clippy::too_many_arguments, + reason = "asset grid rendering keeps immediate-mode UI inputs explicit" +)] fn asset_grid( world: &mut World, ui: &mut egui::Ui, @@ -1169,6 +1173,10 @@ fn prefetch_embedded_thumbnails( }); } +#[expect( + clippy::too_many_arguments, + reason = "embedded asset rendering keeps immediate-mode UI inputs explicit" +)] fn draw_embedded_asset_cell( world: &mut World, ui: &mut egui::Ui, diff --git a/crates/editor/src/ui/dock_tabs.rs b/crates/editor/src/ui/dock_tabs.rs index 67afb8f..8fa92d0 100644 --- a/crates/editor/src/ui/dock_tabs.rs +++ b/crates/editor/src/ui/dock_tabs.rs @@ -1,6 +1,6 @@ //! Dock tab open/focus helpers and default panel node placement. -use egui_dock::{DockState, Node, NodeIndex, SurfaceIndex, TabIndex, Tree}; +use egui_dock::{DockState, Node, NodeIndex, NodePath, SurfaceIndex, TabIndex, TabPath, Tree}; use serde::{Deserialize, Serialize}; use super::EditorTab; @@ -43,15 +43,15 @@ impl PanelNodes { /// Scan the main surface for open tabs and refresh node indices (handles user drag/split). pub fn discover(dock: &DockState, fallback: Self) -> Self { let mut nodes = fallback; - for ((surface, node_idx), tab) in dock.iter_all_tabs() { - if surface != SurfaceIndex::main() { + for (path, tab) in dock.iter_all_tabs() { + if path.surface != SurfaceIndex::main() { continue; } match tab { - EditorTab::Viewport | EditorTab::GameView => nodes.main_view = node_idx.0, - EditorTab::Hierarchy => nodes.hierarchy = node_idx.0, - EditorTab::Inspector => nodes.inspector = node_idx.0, - EditorTab::AssetBrowser | EditorTab::Toolbar => nodes.bottom = node_idx.0, + EditorTab::Viewport | EditorTab::GameView => nodes.main_view = path.node.0, + EditorTab::Hierarchy => nodes.hierarchy = path.node.0, + EditorTab::Inspector => nodes.inspector = path.node.0, + EditorTab::AssetBrowser | EditorTab::Toolbar => nodes.bottom = path.node.0, } } nodes @@ -93,8 +93,9 @@ pub fn open_and_focus_tab( let tab_idx = push_tab_to_node(tree, node, tab); (node, tab_idx) }; - dock.set_active_tab((SurfaceIndex::main(), node, tab_idx)); - dock.set_focused_node_and_surface((SurfaceIndex::main(), node)); + let node_path = NodePath::new(SurfaceIndex::main(), node); + let _ = dock.set_active_tab(TabPath::new(SurfaceIndex::main(), node, tab_idx)); + dock.set_focused_node_and_surface(node_path); (node, tab_idx) } diff --git a/crates/editor/src/ui/fonts.rs b/crates/editor/src/ui/fonts.rs index 879a3bd..647f326 100644 --- a/crates/editor/src/ui/fonts.rs +++ b/crates/editor/src/ui/fonts.rs @@ -17,5 +17,26 @@ fn configure_editor_fonts(mut contexts: Query<&mut EguiContext, Added) } } +#[expect( + clippy::too_many_arguments, + reason = "recursive hierarchy rendering threads interaction state explicitly" +)] fn draw_authored_node( world: &mut World, ui: &mut egui::Ui, @@ -278,7 +283,7 @@ fn draw_authored_node( } else { icons::CARET_RIGHT }; - if ui.small_button(twistie.as_str()).clicked() { + if ui.small_button(twistie.regular()).clicked() { let path = path.clone(); let expanding = !world .resource::() @@ -353,7 +358,8 @@ fn draw_authored_node( } else { let selected = selected_entities.contains(entity); let label = hierarchy_label_for_entity(world, entity, HierarchyNodeKind::Authored); - let response = ui.selectable_label(selected, label); + let color = if selected { TEXT_SELECTED } else { TEXT }; + let response = ui.selectable_label(selected, hierarchy_label_text(label, color)); if response.clicked() && !is_locked { let additive = ui.input(|input| { input.modifiers.shift || input.modifiers.command || input.modifiers.ctrl @@ -512,7 +518,7 @@ fn draw_generated_node( } else { icons::CARET_RIGHT }; - if ui.small_button(twistie.as_str()).clicked() { + if ui.small_button(twistie.regular()).clicked() { is_expanded = !is_expanded; } } else { @@ -520,14 +526,10 @@ fn draw_generated_node( } ui.add_enabled( false, - egui::Label::new( - egui::RichText::new(hierarchy_label_for_entity( - world, - entity, - HierarchyNodeKind::Generated, - )) - .weak(), - ), + egui::Label::new(hierarchy_label_text( + hierarchy_label_for_entity(world, entity, HierarchyNodeKind::Generated), + TEXT_DIM, + )), ); }); if !is_expanded { @@ -555,7 +557,9 @@ fn draw_runtime_node( ui.add_space(depth as f32 * 14.0); ui.add_space(18.0); let label = hierarchy_label_for_entity(world, entity, HierarchyNodeKind::Runtime); - let response = ui.selectable_label(selected_entities.contains(entity), label); + let selected = selected_entities.contains(entity); + let color = if selected { TEXT_SELECTED } else { TEXT_DIM }; + let response = ui.selectable_label(selected, hierarchy_label_text(label, color)); if response.clicked() { selected_entities.select_replace(entity); } @@ -575,6 +579,29 @@ fn draw_runtime_node( } } +fn hierarchy_label_text(label: HierarchyLabel, color: egui::Color32) -> egui::WidgetText { + let mut job = egui::text::LayoutJob::default(); + job.append( + label.icon, + 0.0, + egui::TextFormat { + font_id: egui::FontId::new(13.0, egui::FontFamily::Name("phosphor-regular".into())), + color, + ..Default::default() + }, + ); + job.append( + &format!(" {}", label.text), + 0.0, + egui::TextFormat { + font_id: egui::FontId::new(13.0, egui::FontFamily::Proportional), + color, + ..Default::default() + }, + ); + job.into() +} + fn authored_context_menu( world: &mut World, ui: &mut egui::Ui, @@ -596,15 +623,16 @@ fn authored_context_menu( delete_entities_with_history(world, &[entity]); ui.close(); } - if selected_entities.len() >= 2 && selected_entities.contains(entity) { - if ui.button("Group").clicked() { - let entities: Vec = selected_entities - .iter() - .filter(|e| is_level_object(world, *e)) - .collect(); - group_selection_with_history(world, &entities); - ui.close(); - } + if selected_entities.len() >= 2 + && selected_entities.contains(entity) + && ui.button("Group").clicked() + { + let entities: Vec = selected_entities + .iter() + .filter(|e| is_level_object(world, *e)) + .collect(); + group_selection_with_history(world, &entities); + ui.close(); } if ui.button("Unparent").clicked() { reparent_with_history(world, entity, None); @@ -701,7 +729,7 @@ pub(crate) fn is_hierarchy_generated_child(world: &World, entity: Entity) -> boo && (entity_ref.contains::() || entity_ref.contains::()) && (entity_ref.contains::() || entity_ref.contains::() - || entity_ref.contains::()) + || entity_ref.contains::()) }) } diff --git a/crates/editor/src/ui/hierarchy_ops.rs b/crates/editor/src/ui/hierarchy_ops.rs index 2eeed79..c1def6d 100644 --- a/crates/editor/src/ui/hierarchy_ops.rs +++ b/crates/editor/src/ui/hierarchy_ops.rs @@ -350,11 +350,16 @@ pub fn actor_kind_icon(kind: ActorKind) -> &'static str { } } +pub struct HierarchyLabel { + pub icon: &'static str, + pub text: String, +} + pub fn hierarchy_label_for_entity( world: &World, entity: Entity, kind: HierarchyNodeKind, -) -> String { +) -> HierarchyLabel { use egui_phosphor_icons::icons; let icon = world .get::(entity) @@ -365,18 +370,18 @@ pub fn hierarchy_label_for_entity( HierarchyNodeKind::Generated => icons::STACK.as_str(), HierarchyNodeKind::Runtime => icons::CPU.as_str(), }); - let mut label = format!("{} {}", icon, entity_name(world, entity)); + let mut text = entity_name(world, entity); if let Some(light) = world.get::(entity) { if matches!(light.kind, AuthoringLightKind::Directional) { - label.push_str(" ☀"); + text.push_str(" (sun)"); } } match kind { HierarchyNodeKind::Authored => {} - HierarchyNodeKind::Generated => label.push_str(" (generated)"), - HierarchyNodeKind::Runtime => label.push_str(" (runtime)"), + HierarchyNodeKind::Generated => text.push_str(" (generated)"), + HierarchyNodeKind::Runtime => text.push_str(" (runtime)"), } - label + HierarchyLabel { icon, text } } pub fn entity_matches_filter(world: &World, entity: Entity, filter_lower: &str) -> bool { diff --git a/crates/editor/src/ui/hierarchy_state.rs b/crates/editor/src/ui/hierarchy_state.rs index f21896f..f315b2d 100644 --- a/crates/editor/src/ui/hierarchy_state.rs +++ b/crates/editor/src/ui/hierarchy_state.rs @@ -48,12 +48,13 @@ impl Default for HierarchyPanelState { impl HierarchyPanelState { pub fn from_prefs(prefs: &UserPreferences) -> Self { - let mut state = Self::default(); - state.filter = prefs.hierarchy_filter.clone(); - state.show_generated = prefs.hierarchy_show_generated; - state.show_runtime = prefs.hierarchy_show_runtime; - state.sort_mode = prefs.hierarchy_sort_mode; - state + Self { + filter: prefs.hierarchy_filter.clone(), + show_generated: prefs.hierarchy_show_generated, + show_runtime: prefs.hierarchy_show_runtime, + sort_mode: prefs.hierarchy_sort_mode, + ..Self::default() + } } pub fn mark_dirty(&mut self) { diff --git a/crates/editor/src/ui/inspector.rs b/crates/editor/src/ui/inspector.rs index dac6a0d..61ab8bb 100644 --- a/crates/editor/src/ui/inspector.rs +++ b/crates/editor/src/ui/inspector.rs @@ -764,7 +764,7 @@ fn add_component_picker_shelf( target: Entity, anchor: egui::Rect, ) { - if !world.get_entity(target).is_ok() { + if world.get_entity(target).is_err() { if let Some(mut state) = world.get_resource_mut::() { state.add_component_open = false; state.add_component_target = None; @@ -777,7 +777,7 @@ fn add_component_picker_shelf( .descriptors .clone(); - let visible = ui.clip_rect().intersect(ui.ctx().available_rect()); + let visible = ui.clip_rect().intersect(ui.ctx().content_rect()); let space_above = (anchor.min.y - visible.top()).max(0.0); let space_below = (visible.bottom() - anchor.max.y).max(0.0); let direction = if space_below >= space_above { @@ -865,7 +865,7 @@ fn add_component_picker_shelf_contents( } let search = search_input.to_lowercase(); - let filtered = filtered_component_descriptors(&descriptors, &search); + let filtered = filtered_component_descriptors(descriptors, &search); { let mut state = world.resource_mut::(); if !filtered.is_empty() { @@ -905,7 +905,7 @@ fn add_component_picker_shelf_contents( .resource::() .add_component_selected_index; if let Some(descriptor) = filtered.get(selected_index) { - let add_state = component_add_state(world, target, descriptor, &descriptors); + let add_state = component_add_state(world, target, descriptor, descriptors); if add_state.addable { insert_registered_component(world, target, descriptor.type_name); let mut state = world.resource_mut::(); @@ -941,8 +941,7 @@ fn add_component_picker_shelf_contents( last_category = Some(descriptor.category); } - let add_state = - component_add_state(world, target, descriptor, &descriptors); + let add_state = component_add_state(world, target, descriptor, descriptors); let selected = index == selected_index; let row = ui .horizontal(|ui| { @@ -1893,8 +1892,10 @@ fn static_mesh_renderer_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) .clicked(); if add_slot { let index = renderer.slots.len(); - let mut entry = StaticMeshRendererEntry::default(); - entry.id = ComponentInstanceId::new(format!("slot:{index}")); + let entry = StaticMeshRendererEntry { + id: ComponentInstanceId::new(format!("slot:{index}")), + ..Default::default() + }; renderer.slots.push(entry); changed = true; } @@ -2026,6 +2027,10 @@ fn thumbnail_for_mesh( .and_then(|candidate| candidate.texture_id) } +#[expect( + clippy::too_many_arguments, + reason = "asset selector rows keep immediate-mode UI inputs explicit" +)] fn asset_selector_row( ui: &mut egui::Ui, label: &str, @@ -2075,6 +2080,10 @@ fn asset_selector_row( response } +#[expect( + clippy::too_many_arguments, + reason = "asset selector controls keep immediate-mode UI inputs explicit" +)] fn asset_selector_control( ui: &mut egui::Ui, icon: Icon, diff --git a/crates/editor/src/ui/menu.rs b/crates/editor/src/ui/menu.rs index b4987a1..b82a5a6 100644 --- a/crates/editor/src/ui/menu.rs +++ b/crates/editor/src/ui/menu.rs @@ -9,7 +9,6 @@ use crate::history::{apply_command_redo, apply_command_undo, EditorHistory}; use crate::scene_io::{SceneIo, SceneIoRequest}; use crate::settings_ui::{open_project_settings_panel, ProjectSettingsPanel}; use crate::state::{EditorMode, PlayPaused}; -use crate::workspace::{request_new_project, request_open_project}; use super::diagnostics::{BrushDiagnosticsPanel, DiagnosticsPanel}; use super::dock_tabs::{open_and_focus_tab, tab_is_open, tab_label, PanelNodes, PANEL_TABS}; @@ -22,25 +21,14 @@ use super::EditorTab; pub fn top_menu_bar( world: &mut World, - ctx: &egui::Context, + root_ui: &mut egui::Ui, selected: &SelectedEntities, dock_state: &mut DockState, panel_nodes: &mut PanelNodes, ) { - egui::TopBottomPanel::top("editor_menu_bar").show(ctx, |ui| { + egui::Panel::top("editor_menu_bar").show_inside(root_ui, |ui| { egui::MenuBar::new().ui(ui, |ui| { ui.menu_button("File", |ui| { - ui.menu_button("Project", |ui| { - if menu_item(ui, "New Project", None, true).clicked() { - request_new_project(world); - ui.close(); - } - if menu_item(ui, "Open Project...", None, true).clicked() { - request_open_project(world); - ui.close(); - } - }); - ui.separator(); if menu_item(ui, "New Scene", None, true).clicked() { world.resource_mut::().request = Some(SceneIoRequest::New); ui.close(); diff --git a/crates/editor/src/ui/mod.rs b/crates/editor/src/ui/mod.rs index 9311c20..f73ff50 100644 --- a/crates/editor/src/ui/mod.rs +++ b/crates/editor/src/ui/mod.rs @@ -66,7 +66,7 @@ pub struct UiState { } pub fn egui_captures_keyboard(ctx: &egui::Context) -> bool { - ctx.wants_keyboard_input() + ctx.egui_wants_keyboard_input() } pub fn egui_captures_keyboard_from_world(world: &mut World) -> bool { @@ -118,6 +118,10 @@ impl UiState { } } + #[expect( + deprecated, + reason = "bevy_egui exposes a Context during EguiPrimaryContextPass, so one top-level panel must bridge to the new Ui-based panel API" + )] fn ui(&mut self, world: &mut World, ctx: &mut egui::Context, dt: f32) { apply_editor_theme(ctx); @@ -129,17 +133,6 @@ impl UiState { } } - top_menu_bar( - world, - ctx, - &self.selected_entities, - &mut self.dock_state, - &mut self.panel_nodes, - ); - - editor_toolbar_panel(world, ctx); - status_bar_ui(world, ctx, &self.selected_entities, mode); - self.pointer_in_viewport = false; self.viewport_pointer_pos = None; @@ -154,21 +147,34 @@ impl UiState { }); } - let mut viewer = TabViewer { - world, - viewport_rect: &mut self.viewport_rect, - pointer_in_viewport: &mut self.pointer_in_viewport, - viewport_pointer_pos: &mut self.viewport_pointer_pos, - playing, - cursor_grabbed, - selected_entities: &mut self.selected_entities, - renaming_entity: &mut self.renaming_entity, - rename_buffer: &mut self.rename_buffer, - }; + egui::CentralPanel::no_frame().show(ctx, |root_ui| { + top_menu_bar( + world, + root_ui, + &self.selected_entities, + &mut self.dock_state, + &mut self.panel_nodes, + ); - DockArea::new(&mut self.dock_state) - .style(editor_dock_style(ctx)) - .show(ctx, &mut viewer); + editor_toolbar_panel(world, root_ui); + status_bar_ui(world, root_ui, &self.selected_entities, mode); + + let mut viewer = TabViewer { + world, + viewport_rect: &mut self.viewport_rect, + pointer_in_viewport: &mut self.pointer_in_viewport, + viewport_pointer_pos: &mut self.viewport_pointer_pos, + playing, + cursor_grabbed, + selected_entities: &mut self.selected_entities, + renaming_entity: &mut self.renaming_entity, + rename_buffer: &mut self.rename_buffer, + }; + + DockArea::new(&mut self.dock_state) + .style(editor_dock_style(ctx)) + .show_inside(root_ui, &mut viewer); + }); self.panel_nodes = PanelNodes::discover(&self.dock_state, self.panel_nodes); diff --git a/crates/editor/src/ui/status_bar.rs b/crates/editor/src/ui/status_bar.rs index 074e4bc..d7baf10 100644 --- a/crates/editor/src/ui/status_bar.rs +++ b/crates/editor/src/ui/status_bar.rs @@ -13,59 +13,87 @@ use super::theme::{status_bar_frame, TEXT}; pub fn status_bar_ui( world: &World, - ctx: &egui::Context, + root_ui: &mut egui::Ui, selected: &SelectedEntities, mode: EditorMode, ) { - egui::TopBottomPanel::bottom("editor_status_bar") - .exact_height(24.0) + egui::Panel::bottom("editor_status_bar") + .exact_size(24.0) .frame(status_bar_frame()) - .show(ctx, |ui| { - ui.horizontal_centered(|ui| { - ui.spacing_mut().item_spacing.x = 16.0; - ui.label(egui::RichText::new(scene_line(world)).color(TEXT)); + .show_inside(root_ui, |ui| { + ui.spacing_mut().item_spacing.x = 12.0; + egui::containers::Sides::new() + .shrink_left() + .truncate() + .show( + ui, + |ui| { + let status = primary_status_line(world); + ui.add( + egui::Label::new(egui::RichText::new(&status).color(TEXT)).truncate(), + ) + .on_hover_text(status); + }, + |ui| { + let mode_label = match mode { + EditorMode::Editing => "Edit".to_string(), + EditorMode::Playing => { + if world.resource::().0 { + "Play (paused)".to_string() + } else { + "Play".to_string() + } + } + }; + ui.label(egui::RichText::new(mode_label).color(TEXT)); - let mode_label = match mode { - EditorMode::Editing => "Edit".to_string(), - EditorMode::Playing => { - if world.resource::().0 { - "Play (paused)".to_string() - } else { - "Play".to_string() + let count = selected.len(); + let selection = match count { + 0 => "None".to_string(), + 1 => "1 selected".to_string(), + _ => format!("{count} selected"), + }; + ui.label(egui::RichText::new(selection).color(TEXT)); + + ui.label( + egui::RichText::new(world.resource::().status.clone()) + .color(TEXT), + ); + + if let Some(operator_label) = world.resource::().label() { + ui.label(egui::RichText::new(operator_label).color(TEXT)); } - } - }; - ui.label(egui::RichText::new(mode_label).color(TEXT)); - let count = selected.len(); - let sel = if count == 0 { - "None".to_string() - } else if count == 1 { - "1 selected".to_string() - } else { - format!("{count} selected") - }; - ui.label(egui::RichText::new(sel).color(TEXT)); - - ui.label( - egui::RichText::new(world.resource::().status.clone()) - .color(TEXT), + #[cfg(feature = "hot-reload")] + if let Some(hot) = world.get_resource::() + { + ui.label(egui::RichText::new(hot.label.clone()).color(TEXT)); + } + }, ); - - if let Some(operator_label) = world.resource::().label() { - ui.label(egui::RichText::new(operator_label).color(TEXT)); - } - - #[cfg(feature = "hot-reload")] - if let Some(hot) = world.get_resource::() { - ui.label(egui::RichText::new(hot.label.clone()).color(TEXT)); - } - }); }); } -fn scene_line(world: &World) -> String { - let scene_io = world.resource::(); +fn scene_line(scene_io: &SceneIo) -> String { let dirty = if scene_io.dirty { " *" } else { "" }; format!("Scene: {}{dirty}", scene_io.active_path_label()) } + +fn primary_status_line(world: &World) -> String { + let scene_io = world.resource::(); + format!("{} | {}", scene_io.status, scene_line(scene_io)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn scene_line_marks_unsaved_changes() { + let mut scene_io = SceneIo::default(); + assert!(!scene_line(&scene_io).ends_with('*')); + + scene_io.mark_dirty(); + assert!(scene_line(&scene_io).ends_with('*')); + } +} diff --git a/crates/editor/src/ui/theme.rs b/crates/editor/src/ui/theme.rs index 13e00cc..bd6240d 100644 --- a/crates/editor/src/ui/theme.rs +++ b/crates/editor/src/ui/theme.rs @@ -52,7 +52,7 @@ pub fn apply_editor_theme(ctx: &egui::Context) { visuals.window_stroke = Stroke::new(1.0, BORDER); visuals.window_corner_radius = CornerRadius::same(4); - let mut style = (*ctx.style()).clone(); + let mut style = (*ctx.global_style()).clone(); style.visuals = visuals; style.spacing.item_spacing = egui::vec2(8.0, 6.0); style.spacing.button_padding = egui::vec2(8.0, 4.0); @@ -69,11 +69,11 @@ pub fn apply_editor_theme(ctx: &egui::Context) { egui::TextStyle::Heading, FontId::new(15.0, egui::FontFamily::Proportional), ); - ctx.set_style(style); + ctx.set_global_style(style); } pub fn editor_dock_style(ctx: &egui::Context) -> DockStyle { - let mut style = DockStyle::from_egui(ctx.style().as_ref()); + let mut style = DockStyle::from_egui(ctx.global_style().as_ref()); style.dock_area_padding = Some(egui::Margin::same(2)); style.main_surface_border_stroke = Stroke::new(1.0, BORDER); style.separator.width = 2.0; diff --git a/crates/editor/src/ui/toolbar.rs b/crates/editor/src/ui/toolbar.rs index c849632..9474fe6 100644 --- a/crates/editor/src/ui/toolbar.rs +++ b/crates/editor/src/ui/toolbar.rs @@ -19,11 +19,13 @@ use super::widgets::{icon_button, panel_toolbar_row, toolbar_separator, transpor /// Toolbar strip height (menu bar is separate). const TOOLBAR_HEIGHT: f32 = 56.0; +const TRANSPORT_WIDTH: f32 = 152.0; +const TRANSPORT_SLOT_STEP: f32 = 54.0; -pub fn editor_toolbar_panel(world: &mut World, ctx: &egui::Context) { - egui::TopBottomPanel::top("editor_toolbar") - .exact_height(TOOLBAR_HEIGHT) - .show(ctx, |ui| { +pub fn editor_toolbar_panel(world: &mut World, root_ui: &mut egui::Ui) { + egui::Panel::top("editor_toolbar") + .exact_size(TOOLBAR_HEIGHT) + .show_inside(root_ui, |ui| { toolbar_ui(world, ui); }); } @@ -41,7 +43,7 @@ pub fn toolbar_ui(world: &mut World, ui: &mut egui::Ui) { ); let transport_rect = egui::Rect::from_center_size( egui::pos2(toolbar_rect.center().x, center_y), - egui::vec2(transport_width(world), 44.0), + egui::vec2(TRANSPORT_WIDTH, 44.0), ); let mut left_ui = ui.new_child( @@ -63,16 +65,6 @@ pub fn toolbar_ui(world: &mut World, ui: &mut egui::Ui) { transport_controls(world, &mut transport_ui); } -fn transport_width(world: &World) -> f32 { - let button_count = match *world.resource::>().get() { - EditorMode::Editing => 1.0, - EditorMode::Playing => 3.0, - }; - let gap_count = button_count - 1.0; - - button_count * 44.0 + gap_count * 10.0 -} - fn left_toolbar(world: &mut World, ui: &mut egui::Ui) { panel_toolbar_row(ui, |ui| { if icon_button(ui, icons::CUBE, "Spawn cube").clicked() { @@ -113,7 +105,13 @@ fn transport_controls(world: &mut World, ui: &mut egui::Ui) { let mode = *world.resource::>().get(); match mode { EditorMode::Editing => { - if transport_button_large(ui, icons::PLAY, "Play (F5)", true).clicked() { + ui.add_space(TRANSPORT_SLOT_STEP); + let play_clicked = ui + .push_id("transport_center", |ui| { + transport_button_large(ui, icons::PLAY, "Play (F5)", true).clicked() + }) + .inner; + if play_clicked { toggle_play_mode(world); } } @@ -125,12 +123,21 @@ fn transport_controls(world: &mut World, ui: &mut egui::Ui) { } else { "Pause simulation (F6)" }; - if transport_button_large(ui, pause_icon, pause_tip, false).clicked() { + let pause_clicked = ui + .push_id("transport_left", |ui| { + transport_button_large(ui, pause_icon, pause_tip, false).clicked() + }) + .inner; + if pause_clicked { toggle_play_paused(world); } - if transport_button_large(ui, icons::STOP, "Stop and return to Edit (F5)", false) - .clicked() - { + let stop_clicked = ui + .push_id("transport_center", |ui| { + transport_button_large(ui, icons::STOP, "Stop and return to Edit (F5)", false) + .clicked() + }) + .inner; + if stop_clicked { toggle_play_mode(world); } @@ -139,7 +146,12 @@ fn transport_controls(world: &mut World, ui: &mut egui::Ui) { PlayPossession::Possessed => (icons::SIGN_OUT, "Eject from player (F8)"), PlayPossession::Ejected => (icons::USER, "Possess player (F8)"), }; - if transport_button_large(ui, eject_icon, eject_tip, false).clicked() { + let possession_clicked = ui + .push_id("transport_right", |ui| { + transport_button_large(ui, eject_icon, eject_tip, false).clicked() + }) + .inner; + if possession_clicked { toggle_possession(world); } } diff --git a/crates/editor/src/ui/viewport_chrome.rs b/crates/editor/src/ui/viewport_chrome.rs index 22fdb90..9fcccb2 100644 --- a/crates/editor/src/ui/viewport_chrome.rs +++ b/crates/editor/src/ui/viewport_chrome.rs @@ -15,7 +15,7 @@ use crate::render_target::ViewportRenderTarget; use crate::selection::ViewportClick; use crate::state::PlayPossession; use crate::viewport::actor_icons::ActorIconSettings; -use crate::viewport::brush_edit::BrushEditMode; +use crate::viewport::brush_edit::{BrushEditMode, BrushElementSelection}; use crate::viewport::brush_tool::{BrushToolPhase, BrushToolState}; use crate::viewport::{ snap_translation, viewport_ground_position, EditorViewportMode, ViewportDisplayMode, @@ -36,6 +36,10 @@ pub struct ViewportUiState { const VIEWPORT_TOOLTIP: &str = "RMB + WASD/QE: fly | MMB: pan | Scroll: dolly\nW/E/R: gizmo | X: world/local | F: focus | G: game view | Ctrl+G: grid\nTab: cycle overlapping picks | Play/Pause/Stop: main toolbar (F5 / F6)"; +#[expect( + clippy::too_many_arguments, + reason = "viewport tab rendering keeps immediate-mode UI inputs explicit" +)] pub fn viewport_tab_ui( world: &mut World, ui: &mut egui::Ui, @@ -161,20 +165,58 @@ fn scene_view_brush_mode_badge(world: &World, ctx: &egui::Context, scene_rect: e if !mode.is_element_mode() { return; } + let selected_count = world + .get_resource::() + .map(|selection| selection.elements.len()) + .unwrap_or(0); egui::Area::new(egui::Id::new("scene_view_brush_mode_badge")) - .fixed_pos(scene_rect.right_bottom() + egui::vec2(-150.0, -22.0)) + .fixed_pos(scene_rect.left_top() + egui::vec2(8.0, 52.0)) .interactable(false) .show(ctx, |ui| { egui::Frame::new() - .fill(egui::Color32::from_rgba_unmultiplied(35, 44, 50, 220)) - .corner_radius(egui::CornerRadius::same(3)) - .inner_margin(egui::Margin::symmetric(6, 2)) + .fill(egui::Color32::from_rgba_unmultiplied(20, 18, 28, 230)) + .stroke(egui::Stroke::new( + 1.0, + egui::Color32::from_rgba_unmultiplied(210, 140, 255, 210), + )) + .corner_radius(egui::CornerRadius::same(4)) + .inner_margin(egui::Margin::symmetric(8, 6)) .show(ui, |ui| { - ui.label( - egui::RichText::new(format!("Brush: {}", mode.label())) - .color(egui::Color32::from_rgb(170, 220, 255)) - .small(), - ); + ui.horizontal(|ui| { + ui.label( + egui::RichText::new(format!("Brush {}", mode.label())) + .color(egui::Color32::from_rgb(230, 190, 255)) + .strong(), + ); + ui.label( + egui::RichText::new(format!("{selected_count} selected")) + .color(egui::Color32::from_rgb(205, 200, 215)) + .small(), + ); + ui.label( + egui::RichText::new("Element gizmo") + .color(egui::Color32::from_rgb(255, 190, 120)) + .small(), + ); + }); + ui.add_space(4.0); + ui.horizontal_wrapped(|ui| { + match mode { + BrushEditMode::Vertex => key_hint(ui, "LMB", "Select vertex"), + BrushEditMode::Edge => key_hint(ui, "LMB", "Select edge"), + BrushEditMode::Face => key_hint(ui, "LMB", "Select face"), + BrushEditMode::Clip => key_hint(ui, "LMB", "Select face"), + BrushEditMode::Object => {} + } + if !matches!(mode, BrushEditMode::Clip) { + key_hint(ui, "W/E/R", "Move/rotate/scale"); + } + if matches!(mode, BrushEditMode::Clip) { + key_hint(ui, "Enter", "Clip"); + } + key_hint(ui, "Shift+LMB", "Multi"); + key_hint(ui, "Esc", "Object"); + }); }); }); } diff --git a/crates/editor/src/viewport/actor_icons.rs b/crates/editor/src/viewport/actor_icons.rs index 7048b51..aa9fadb 100644 --- a/crates/editor/src/viewport/actor_icons.rs +++ b/crates/editor/src/viewport/actor_icons.rs @@ -141,8 +141,8 @@ impl Material for ActorIconOverlayMaterial { ) -> Result<(), bevy::render::render_resource::SpecializedMeshPipelineError> { descriptor.primitive.cull_mode = None; if let Some(depth) = &mut descriptor.depth_stencil { - depth.depth_write_enabled = false; - depth.depth_compare = CompareFunction::Always; + depth.depth_write_enabled = Some(false); + depth.depth_compare = Some(CompareFunction::Always); } if let Some(fragment) = &mut descriptor.fragment { if let Some(target) = fragment diff --git a/crates/editor/src/viewport/brush_edit.rs b/crates/editor/src/viewport/brush_edit.rs index 55a27e5..5a81c15 100644 --- a/crates/editor/src/viewport/brush_edit.rs +++ b/crates/editor/src/viewport/brush_edit.rs @@ -9,12 +9,14 @@ use shared::{ use crate::camera::EditorCamera; use crate::history::{push_command, set_brush_transform_with_history, EditorCommand}; +use crate::infra::EditorOnly; use crate::operators::{ActiveOperator, OperatorPhase, OperatorStatus}; use crate::scene_io::SceneIo; use crate::selection::ViewportClick; use crate::state::scene_tools_active; use crate::ui::{viewport_keyboard_shortcuts_active, UiState}; use crate::viewport::{scene_view_ray, ViewportDisplayMode}; +use transform_gizmo_bevy::prelude::GizmoTarget; #[derive(Resource, Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum BrushEditMode { @@ -98,27 +100,33 @@ impl Plugin for BrushEditPlugin { fn build(&self, app: &mut App) { app.init_resource::() .init_resource::() - .init_resource::() + .init_resource::() .add_systems( Update, ( brush_edit_hotkeys, brush_element_pick, - brush_element_drag, + sync_brush_element_gizmo, brush_clip_commit, draw_brush_edit_overlays, ) .chain() .run_if(scene_tools_active), - ); + ) + .add_systems(Last, apply_brush_element_gizmo.run_if(scene_tools_active)); } } +#[derive(Component, Debug, Clone, Copy)] +pub struct BrushElementGizmo; + #[derive(Resource, Default, Debug, Clone)] -struct BrushElementDrag { +struct BrushElementGizmoState { + entity: Option, brush: Option, - old: Option, - last_floor: Option, + old_brush: Option, + old_gizmo: Option, + last_gizmo: Option, changed: bool, } @@ -139,7 +147,9 @@ fn brush_edit_hotkeys( return Ok(()); } let ctx = contexts.ctx_mut()?; - if !viewport_keyboard_shortcuts_active(&ui_state, ctx, &buttons) || ctx.wants_keyboard_input() { + if !viewport_keyboard_shortcuts_active(&ui_state, ctx, &buttons) + || ctx.egui_wants_keyboard_input() + { return Ok(()); } if keys.just_pressed(KeyCode::Escape) && mode.is_element_mode() { @@ -233,121 +243,272 @@ fn brush_element_pick( } } -#[allow(clippy::too_many_arguments)] -fn brush_element_drag( +fn sync_brush_element_gizmo( mut commands: Commands, mode: Res, selection: Res, - mut drag: ResMut, - mut active_operator: ResMut, - buttons: Res>, - ui_state: Res, - cameras: Query<(&Camera, &GlobalTransform), With>, - mut brushes: Query<(&mut BrushDesc, &GlobalTransform)>, + mut state: ResMut, + brushes: Query<(&BrushDesc, &GlobalTransform)>, + mut gizmos: Query<&mut Transform, With>, ) { if !mode.is_element_mode() || matches!(*mode, BrushEditMode::Clip) { - clear_drag(&mut drag); + clear_brush_element_gizmo(&mut commands, &mut state); return; } let Some(brush_entity) = selection.brush else { - clear_drag(&mut drag); + clear_brush_element_gizmo(&mut commands, &mut state); return; }; if selection.elements.is_empty() { - clear_drag(&mut drag); + clear_brush_element_gizmo(&mut commands, &mut state); return; } - let Some(pointer_pos) = ui_state.viewport_pointer_pos else { + let Ok((brush, brush_transform)) = brushes.get(brush_entity) else { + clear_brush_element_gizmo(&mut commands, &mut state); return; }; - let Some(current_floor) = pointer_floor_position(&cameras, pointer_pos, ui_state.viewport_rect) + let Some(pivot) = selected_element_world_pivot(brush, brush_transform, &selection.elements) else { + clear_brush_element_gizmo(&mut commands, &mut state); return; }; - if buttons.just_pressed(MouseButton::Left) && drag.brush.is_none() { - if let Ok((brush, _)) = brushes.get(brush_entity) { - drag.brush = Some(brush_entity); - drag.old = Some(brush.clone()); - drag.last_floor = Some(current_floor); - drag.changed = false; - } + let rotation = brush_transform.to_scale_rotation_translation().1; + let desired = Transform::from_translation(pivot).with_rotation(rotation); + let needs_new_entity = state.brush != Some(brush_entity) + || state + .entity + .is_none_or(|entity| gizmos.get(entity).is_err()); + if needs_new_entity { + clear_brush_element_gizmo(&mut commands, &mut state); + let entity = commands + .spawn(( + BrushElementGizmo, + EditorOnly, + GizmoTarget::default(), + desired, + GlobalTransform::default(), + Visibility::Hidden, + InheritedVisibility::default(), + )) + .id(); + state.entity = Some(entity); + state.brush = Some(brush_entity); + state.old_brush = None; + state.old_gizmo = Some(desired); + state.last_gizmo = Some(desired); + state.changed = false; return; } - if buttons.pressed(MouseButton::Left) && drag.brush == Some(brush_entity) { - let Some(last_floor) = drag.last_floor else { - drag.last_floor = Some(current_floor); + let Some(entity) = state.entity else { + return; + }; + let Ok(mut transform) = gizmos.get_mut(entity) else { + clear_brush_element_gizmo(&mut commands, &mut state); + return; + }; + if state.old_brush.is_none() { + let current = *transform; + if !gizmo_transform_is_active(&state, current) { + *transform = desired; + state.old_gizmo = Some(desired); + state.last_gizmo = Some(desired); + } + } +} + +#[allow(clippy::too_many_arguments)] +fn apply_brush_element_gizmo( + mut commands: Commands, + mode: Res, + selection: Res, + mut state: ResMut, + mut active_operator: ResMut, + mut brushes: Query<(&mut BrushDesc, &GlobalTransform)>, + gizmos: Query<(&Transform, &GizmoTarget), With>, +) { + if !mode.is_element_mode() || matches!(*mode, BrushEditMode::Clip) { + clear_brush_element_gizmo(&mut commands, &mut state); + return; + } + let (Some(gizmo_entity), Some(brush_entity)) = (state.entity, selection.brush) else { + return; + }; + if selection.elements.is_empty() || state.brush != Some(brush_entity) { + clear_brush_element_gizmo(&mut commands, &mut state); + return; + } + let Ok((gizmo_transform, target)) = gizmos.get(gizmo_entity) else { + clear_brush_element_gizmo(&mut commands, &mut state); + return; + }; + + if target.is_active() { + let Ok((mut brush, brush_transform)) = brushes.get_mut(brush_entity) else { + clear_brush_element_gizmo(&mut commands, &mut state); return; }; - let world_delta = current_floor - last_floor; - if world_delta.length_squared() <= 0.000001 { + if state.old_brush.is_none() { + state.old_brush = Some(brush.clone()); + state.old_gizmo = Some(*gizmo_transform); + state.last_gizmo = Some(*gizmo_transform); + state.changed = false; return; } - if let Ok((mut brush, transform)) = brushes.get_mut(brush_entity) { - let local_delta = transform.affine().inverse().transform_vector3(world_delta); - if move_selected_elements(&mut brush, &selection.elements, local_delta) { - drag.changed = true; - drag.last_floor = Some(current_floor); + let Some(last_gizmo) = state.last_gizmo else { + state.last_gizmo = Some(*gizmo_transform); + return; + }; + if !transforms_nearly_equal(last_gizmo, *gizmo_transform) + && transform_selected_elements_by_gizmo( + &mut brush, + &selection.elements, + brush_transform, + last_gizmo, + *gizmo_transform, + ) + { + state.changed = true; + state.last_gizmo = Some(*gizmo_transform); + set_brush_edit_status( + &mut active_operator, + OperatorPhase::Preview, + format!( + "Gizmo editing {} brush element(s)", + selection.elements.len() + ), + ); + } + return; + } + + if let (Some(old), Some(_)) = (state.old_brush.take(), state.old_gizmo.take()) { + if state.changed { + let Ok((mut brush, _)) = brushes.get_mut(brush_entity) else { + clear_brush_element_gizmo(&mut commands, &mut state); + return; + }; + let new = brush.clone(); + let validation = validate_brush(&new); + if validation.is_valid() { + commands.queue(move |world: &mut World| { + push_command( + world, + EditorCommand::SetBrush { + entity: brush_entity, + old: Some(old), + new, + }, + ); + }); set_brush_edit_status( &mut active_operator, - OperatorPhase::Preview, - format!("Dragging {} element(s)", selection.elements.len()), + OperatorPhase::Committed, + "Committed brush element gizmo edit", + ); + } else { + *brush = old; + let message = validation + .diagnostics + .iter() + .find(|diagnostic| diagnostic.severity == BrushDiagnosticSeverity::Error) + .map(|diagnostic| diagnostic.message.as_str()) + .unwrap_or("Brush edit produced invalid geometry"); + set_brush_edit_status( + &mut active_operator, + OperatorPhase::Blocked, + format!("Rejected brush edit: {message}"), ); } } } + state.last_gizmo = Some(*gizmo_transform); + state.changed = false; +} - if buttons.just_released(MouseButton::Left) { - if let (Some(brush), Some(old)) = (drag.brush, drag.old.take()) { - if drag.changed { - if let Ok((mut brush_desc, _)) = brushes.get_mut(brush) { - let new = brush_desc.clone(); - let validation = validate_brush(&new); - if validation.is_valid() { - commands.queue(move |world: &mut World| { - push_command( - world, - EditorCommand::SetBrush { - entity: brush, - old: Some(old), - new, - }, - ); - }); - set_brush_edit_status( - &mut active_operator, - OperatorPhase::Committed, - "Committed brush element edit", - ); - } else { - *brush_desc = old; - let message = validation - .diagnostics - .iter() - .find(|diagnostic| { - diagnostic.severity == BrushDiagnosticSeverity::Error - }) - .map(|diagnostic| diagnostic.message.as_str()) - .unwrap_or("Brush edit produced invalid geometry"); - set_brush_edit_status( - &mut active_operator, - OperatorPhase::Blocked, - format!("Rejected brush edit: {message}"), - ); - } +fn clear_brush_element_gizmo(commands: &mut Commands, state: &mut BrushElementGizmoState) { + if let Some(entity) = state.entity.take() { + if let Ok(mut entity_mut) = commands.get_entity(entity) { + entity_mut.despawn(); + } + } + state.brush = None; + state.old_brush = None; + state.old_gizmo = None; + state.last_gizmo = None; + state.changed = false; +} + +fn selected_element_world_pivot( + brush: &BrushDesc, + transform: &GlobalTransform, + selected: &[BrushElementKey], +) -> Option { + let anchors = selected_anchor_positions(brush, selected); + if anchors.is_empty() { + return None; + } + let affine = transform.affine(); + Some( + anchors + .iter() + .map(|anchor| affine.transform_point3(*anchor)) + .sum::() + / anchors.len() as f32, + ) +} + +fn gizmo_transform_is_active(state: &BrushElementGizmoState, transform: Transform) -> bool { + state + .last_gizmo + .is_some_and(|last| !transforms_nearly_equal(last, transform)) + || state.old_brush.is_some() +} + +fn transform_selected_elements_by_gizmo( + brush: &mut BrushDesc, + selected: &[BrushElementKey], + brush_transform: &GlobalTransform, + old_gizmo: Transform, + new_gizmo: Transform, +) -> bool { + let anchors = selected_anchor_positions(brush, selected); + if anchors.is_empty() { + return false; + } + let old_world_from_gizmo = old_gizmo.compute_affine(); + let new_world_from_gizmo = new_gizmo.compute_affine(); + let world_delta = new_world_from_gizmo * old_world_from_gizmo.inverse(); + let brush_from_world = brush_transform.affine().inverse(); + let world_from_brush = brush_transform.affine(); + let mut changed = false; + for face in &mut brush.faces { + for vertex in &mut face.vertices { + if anchors + .iter() + .any(|anchor| vertex.distance_squared(*anchor) <= 0.0001) + { + let world = world_from_brush.transform_point3(*vertex); + let transformed = world_delta.transform_point3(world); + let local = brush_from_world.transform_point3(transformed); + if vertex.distance_squared(local) > 0.000001 { + *vertex = local; + changed = true; } } } - clear_drag(&mut drag); } + if changed { + recompute_face_planes(brush); + } + changed } -fn clear_drag(drag: &mut BrushElementDrag) { - drag.brush = None; - drag.old = None; - drag.last_floor = None; - drag.changed = false; +fn transforms_nearly_equal(a: Transform, b: Transform) -> bool { + a.translation.distance_squared(b.translation) <= 0.000001 + && a.rotation.dot(b.rotation).abs() >= 0.99999 + && a.scale.distance_squared(b.scale) <= 0.000001 } fn brush_clip_commit( @@ -677,50 +838,6 @@ fn pick_element( } } -fn pointer_floor_position( - cameras: &Query<(&Camera, &GlobalTransform), With>, - pointer_pos: egui::Pos2, - scene_rect: egui::Rect, -) -> Option { - let ray = viewport_ray(cameras, pointer_pos, scene_rect)?; - let direction = ray.direction.as_vec3(); - if direction.y.abs() < 0.0001 { - return Some(ray.origin); - } - let t = -ray.origin.y / direction.y; - Some(ray.origin + direction * t.max(0.0)) -} - -fn move_selected_elements( - brush: &mut BrushDesc, - selected: &[BrushElementKey], - delta: Vec3, -) -> bool { - if delta.length_squared() <= 0.000001 { - return false; - } - let anchors = selected_anchor_positions(brush, selected); - if anchors.is_empty() { - return false; - } - let mut changed = false; - for face in &mut brush.faces { - for vertex in &mut face.vertices { - if anchors - .iter() - .any(|anchor| vertex.distance_squared(*anchor) <= 0.0001) - { - *vertex += delta; - changed = true; - } - } - } - if changed { - recompute_face_planes(brush); - } - changed -} - fn selected_anchor_positions(brush: &BrushDesc, selected: &[BrushElementKey]) -> Vec { let mut anchors = Vec::new(); for element in selected { @@ -986,10 +1103,12 @@ mod tests { .count(); let face = brush.faces[0].id.clone(); - let changed = move_selected_elements( + let changed = transform_selected_elements_by_gizmo( &mut brush, &[BrushElementKey::Vertex { face, index: 0 }], - Vec3::new(0.5, 0.0, 0.0), + &GlobalTransform::default(), + Transform::default(), + Transform::from_translation(Vec3::new(0.5, 0.0, 0.0)), ); let after = brush @@ -1006,10 +1125,12 @@ mod tests { fn collapsed_face_move_produces_invalid_brush() { let mut brush = BrushDesc::default(); let face = brush.faces[0].id.clone(); - assert!(move_selected_elements( + assert!(transform_selected_elements_by_gizmo( &mut brush, &[BrushElementKey::Face { face }], - Vec3::new(-1.0, 0.0, 0.0), + &GlobalTransform::default(), + Transform::default(), + Transform::from_translation(Vec3::new(-1.0, 0.0, 0.0)), )); assert!(!validate_brush(&brush).is_valid()); } diff --git a/crates/editor/src/viewport/brush_tool.rs b/crates/editor/src/viewport/brush_tool.rs index de48ecd..804a551 100644 --- a/crates/editor/src/viewport/brush_tool.rs +++ b/crates/editor/src/viewport/brush_tool.rs @@ -148,7 +148,7 @@ fn brush_tool_input( } let ctx = contexts.ctx_mut()?; - let egui_keyboard_busy = ctx.wants_keyboard_input(); + let egui_keyboard_busy = ctx.egui_wants_keyboard_input(); let keyboard_available = viewport_keyboard_shortcuts_active(&ui_state, ctx, &buttons) && !egui_keyboard_busy; @@ -591,13 +591,15 @@ mod tests { #[test] fn draw_brush_status_explains_invalid_shape() { - let mut tool = BrushToolState::default(); - tool.vertices = vec![ - Vec3::new(-1.0, 0.0, -1.0), - Vec3::new(1.0, 0.0, -1.0), - Vec3::new(-1.0, 0.0, 1.0), - Vec3::new(1.0, 0.0, 1.0), - ]; + let tool = BrushToolState { + vertices: vec![ + Vec3::new(-1.0, 0.0, -1.0), + Vec3::new(1.0, 0.0, -1.0), + Vec3::new(-1.0, 0.0, 1.0), + Vec3::new(1.0, 0.0, 1.0), + ], + ..Default::default() + }; let status = draw_brush_status(&tool); assert!(status.contains("polygon edges cross")); diff --git a/crates/editor/src/viewport/camera.rs b/crates/editor/src/viewport/camera.rs index 3e7ae5f..1ff1872 100644 --- a/crates/editor/src/viewport/camera.rs +++ b/crates/editor/src/viewport/camera.rs @@ -87,6 +87,7 @@ fn spawn_editor_camera( RenderLayers::default(), GizmoCamera, Camera3d::default(), + Msaa::Off, Camera { clear_color: ClearColorConfig::Custom(Color::srgba(0.0, 0.0, 0.0, 1.0)), ..default() @@ -149,12 +150,12 @@ fn update_editor_camera( if input_state.mode.is_none() && navigation_pressed { return Ok(()); } - if !in_scene && input_state.mode.is_none() && ctx.wants_pointer_input() { + if !in_scene && input_state.mode.is_none() && ctx.egui_wants_pointer_input() { return Ok(()); } if in_scene && input_state.mode.is_none() - && ctx.wants_pointer_input() + && ctx.egui_wants_pointer_input() && !buttons.pressed(MouseButton::Right) { return Ok(()); @@ -162,7 +163,7 @@ fn update_editor_camera( let dt = time.delta_secs(); let raw_scroll_delta: f32 = scroll.read().map(|event| event.y).sum(); - let scroll_delta = if in_scene && !ctx.wants_pointer_input() { + let scroll_delta = if in_scene && !ctx.egui_wants_pointer_input() { raw_scroll_delta } else { 0.0 diff --git a/crates/editor/src/viewport/panel.rs b/crates/editor/src/viewport/panel.rs index 4d96a7a..77330b9 100644 --- a/crates/editor/src/viewport/panel.rs +++ b/crates/editor/src/viewport/panel.rs @@ -154,6 +154,10 @@ pub fn snap_translation(position: Vec3, settings: &ViewportSettings) -> Vec3 { ) } +#[expect( + clippy::too_many_arguments, + reason = "Bevy system parameters represent independent editor input and scene state" +)] fn focus_selection_hotkey( keys: Res>, buttons: Res>, diff --git a/crates/editor/src/viewport/render_view.rs b/crates/editor/src/viewport/render_view.rs index fc1842c..236c0ef 100644 --- a/crates/editor/src/viewport/render_view.rs +++ b/crates/editor/src/viewport/render_view.rs @@ -5,16 +5,17 @@ use std::collections::HashSet; use bevy::anti_alias::taa::TemporalAntiAliasing; +use bevy::camera::Hdr; use bevy::core_pipeline::tonemapping::Tonemapping; -use bevy::pbr::{Atmosphere, ScatteringMedium, ScreenSpaceAmbientOcclusion}; +use bevy::light::atmosphere::ScatteringMedium; +use bevy::pbr::{AtmosphereSettings, ScreenSpaceAmbientOcclusion}; use bevy::post_process::auto_exposure::AutoExposure; use bevy::post_process::bloom::Bloom; use bevy::prelude::*; -use bevy::render::view::Hdr; use game::rendering::{ clear_viewport_camera_stack, has_local_shadow_lights, resolve_viewport_camera_owner, - sync_viewport_camera_stack, SolariRaytracingSceneStats, ViewportCameraOwner, - ViewportFxSnapshot, ViewportStackApply, + sync_project_atmosphere, sync_viewport_camera_stack, ProjectAtmosphere, + SolariRaytracingSceneStats, ViewportCameraOwner, ViewportFxSnapshot, ViewportStackApply, }; use settings::{ ActiveCameraRenderProfile, EffectiveRenderStack, GiPath, ProjectRenderCamera, ProjectSettings, @@ -65,7 +66,7 @@ type CameraFxState = (bool, bool, bool, bool, bool, bool, bool, bool); fn fx_snapshot(state: CameraFxState) -> ViewportFxSnapshot { ViewportFxSnapshot { has_stack: state.0, - has_atmosphere: state.1, + has_atmosphere_settings: state.1, has_tonemapping: state.2, has_bloom: state.3, has_ssao: state.4, @@ -101,9 +102,10 @@ fn sync_project_render_view( editor_cameras: Query>, player_cameras: Query>, stacked: Query>, + atmospheres: Query>, camera_fx: Query<( Has, - Has, + Has, Has, Has, Has, @@ -189,6 +191,12 @@ fn sync_project_render_view( || solari_readiness_changed || local_shadow_lights_changed { + sync_project_atmosphere( + &mut commands, + &mut mediums, + profile.atmosphere, + atmospheres.iter(), + ); let force_full_apply = owner_changed || target_changed || solari_readiness_changed; for entity in &active { let apply = if force_full_apply { diff --git a/crates/editor/src/viewport/rendering_diagnostics.rs b/crates/editor/src/viewport/rendering_diagnostics.rs index 3d75f4c..c72a41f 100644 --- a/crates/editor/src/viewport/rendering_diagnostics.rs +++ b/crates/editor/src/viewport/rendering_diagnostics.rs @@ -99,7 +99,7 @@ fn active_camera_tab(world: &mut World, ui: &mut egui::Ui) { { ui.colored_label( egui::Color32::from_rgb(255, 180, 100), - "Solari deferred: directional lights and emissive meshes affect lighting; point/spot LightDesc components are disabled in Bevy 0.18.", + "Solari deferred: directional lights and emissive meshes affect lighting; point/spot LightDesc components are disabled in Bevy 0.19.", ); } else if stack.fallback_reason == Some(RenderFallbackReason::RtUnsupported) || (!caps.rt_supported && profile.gi_path == GiPath::Forward) @@ -221,17 +221,17 @@ fn runtime_lighting_summary(world: &mut World, ui: &mut egui::Ui) { let shadowed_directional = world .query::<&DirectionalLight>() .iter(world) - .filter(|light| light.shadows_enabled) + .filter(|light| light.shadow_maps_enabled) .count(); let shadowed_point = world .query::<&PointLight>() .iter(world) - .filter(|light| light.shadows_enabled) + .filter(|light| light.shadow_maps_enabled) .count(); let shadowed_spot = world .query::<&SpotLight>() .iter(world) - .filter(|light| light.shadows_enabled) + .filter(|light| light.shadow_maps_enabled) .count(); ui.label(format!( "Counts: {directional} directional, {point} point, {spot} spot" diff --git a/crates/editor/src/viewport/selection.rs b/crates/editor/src/viewport/selection.rs index c69fc29..f04e28e 100644 --- a/crates/editor/src/viewport/selection.rs +++ b/crates/editor/src/viewport/selection.rs @@ -14,7 +14,7 @@ use crate::ui::hierarchy_ops::is_entity_locked; use crate::ui::hierarchy_state::HierarchyPanelState; use crate::ui::UiState; use crate::viewport::actor_icons::ActorIconProxy; -use crate::viewport::brush_edit::BrushEditMode; +use crate::viewport::brush_edit::{BrushEditMode, BrushElementGizmo}; use crate::viewport::brush_tool::BrushToolState; use crate::viewport::ViewportDisplayMode; use crate::visualizers::EditorVisualizerProxy; @@ -238,6 +238,10 @@ fn cycle_overlapping_viewport_pick( Ok(()) } +#[expect( + clippy::too_many_arguments, + reason = "viewport picking combines explicit camera, ray-cast, selection, and input state" +)] fn apply_viewport_pick( hierarchy: Option<&HierarchyPanelState>, ui_state: &mut UiState, @@ -369,30 +373,40 @@ fn pick_target_for_entity(entity: Entity, pick_targets: &PickTargetQueries) -> O None } +#[expect( + clippy::too_many_arguments, + reason = "Bevy system parameters represent independent selection and gizmo state" +)] fn sync_gizmo_targets( mut ui_state: ResMut, mut selected: ResMut, mut commands: Commands, - targets: Query>, + targets: Query<(Entity, Option<&BrushElementGizmo>), With>, level_objects: Query<(), (With, Without)>, editor_only: Query<(), With>, transforms: Query<(), With>, hierarchy: Option>, display: Res, + brush_mode: Res, ) { ui_state .selected_entities .retain(|entity| transforms.contains(entity) && !editor_only.contains(entity)); selected.0 = ui_state.selected_entities.as_slice().first().copied(); - if display.clean_game_view { - for entity in &targets { - commands.entity(entity).remove::(); + if display.clean_game_view || brush_mode.is_element_mode() { + for (entity, brush_element_gizmo) in &targets { + if brush_element_gizmo.is_none() { + commands.entity(entity).remove::(); + } } return; } - for entity in &targets { + for (entity, brush_element_gizmo) in &targets { + if brush_element_gizmo.is_some() { + continue; + } if !ui_state.selected_entities.contains(entity) { commands.entity(entity).remove::(); } diff --git a/crates/editor/src/viewport/selection_outline.rs b/crates/editor/src/viewport/selection_outline.rs index 7eb7bbe..4cf18bc 100644 --- a/crates/editor/src/viewport/selection_outline.rs +++ b/crates/editor/src/viewport/selection_outline.rs @@ -83,8 +83,8 @@ macro_rules! impl_outline_material { ) -> Result<(), bevy::render::render_resource::SpecializedMeshPipelineError> { descriptor.primitive.cull_mode = Some(Face::Front); if let Some(depth) = &mut descriptor.depth_stencil { - depth.depth_write_enabled = false; - depth.depth_compare = $depth; + depth.depth_write_enabled = Some(false); + depth.depth_compare = Some($depth); } if let Some(fragment) = &mut descriptor.fragment { if let Some(target) = fragment.targets.first_mut().and_then(|t| t.as_mut()) { @@ -270,6 +270,10 @@ fn sync_selection_outline_meshes( } } +#[expect( + clippy::too_many_arguments, + reason = "selection bounds combine distinct authored geometry and physics sources" +)] fn selection_bounds( root: Entity, children: &Query<&Children>, diff --git a/crates/editor/src/viewport/viewport_mode.rs b/crates/editor/src/viewport/viewport_mode.rs index 72d8ebc..944dd7e 100644 --- a/crates/editor/src/viewport/viewport_mode.rs +++ b/crates/editor/src/viewport/viewport_mode.rs @@ -44,6 +44,10 @@ impl Plugin for EditorViewportModePlugin { } } +#[expect( + clippy::too_many_arguments, + reason = "Bevy system parameters represent independent viewport render state" +)] fn apply_viewport_mode_on_change( mode: Res, mut last: Local>, diff --git a/crates/game/src/lib.rs b/crates/game/src/lib.rs index a700c67..5ebae6b 100644 --- a/crates/game/src/lib.rs +++ b/crates/game/src/lib.rs @@ -16,9 +16,10 @@ pub mod rendering { clear_viewport_camera_stack, effective_gi_path_for_camera, effective_viewport_gi_path, effective_viewport_render_stack, has_local_shadow_lights, hdr_enabled_profile, patch_camera_effects, resolve_viewport_camera_owner, strip_project_camera_fx, - sync_optional_rendering_fx, sync_viewport_camera_stack, world_has_local_shadow_lights, - FullscreenEffectsPlugin, SolariRaytracingSceneStats, SolariRenderingPlugin, - ViewportCameraOwner, ViewportFxSnapshot, ViewportStackApply, HDR_ENV_VAR, + sync_optional_rendering_fx, sync_project_atmosphere, sync_viewport_camera_stack, + world_has_local_shadow_lights, FullscreenEffectsPlugin, ProjectAtmosphere, + SolariRaytracingSceneStats, SolariRenderingPlugin, ViewportCameraOwner, ViewportFxSnapshot, + ViewportStackApply, HDR_ENV_VAR, }; } @@ -66,7 +67,7 @@ mod hot { use bevy::prelude::*; use bevy::time::Fixed; use bevy::window::{CursorOptions, PrimaryWindow}; - use game_hot::SolariRaytracingSceneStats; + use game_hot::{ProjectAtmosphere, SolariRaytracingSceneStats}; use protocol::PlayerInputIntent; use settings::{ ActiveCameraRenderProfile, ProjectRenderCamera, ProjectSettings, RenderingCapabilities, diff --git a/crates/game_hot/src/lib.rs b/crates/game_hot/src/lib.rs index 346f0d3..2db0a30 100644 --- a/crates/game_hot/src/lib.rs +++ b/crates/game_hot/src/lib.rs @@ -17,10 +17,10 @@ pub use rendering::{ patch_camera_effects, refresh_player_camera_fx, resolve_active_camera_render_profile_system, resolve_viewport_camera_owner, sample_volumes_at, setup_project_camera_effects, snapshot_viewport_fx, strip_project_camera_fx, sync_optional_rendering_fx, - sync_viewport_camera_stack, tag_player_camera, world_has_local_shadow_lights, - AutoExposureRenderingPlugin, FullscreenEffectsPlugin, SolariRaytracingSceneStats, - SolariRenderingPlugin, ViewportCameraOwner, ViewportFxSnapshot, ViewportStackApply, - HDR_ENV_VAR, + sync_project_atmosphere, sync_viewport_camera_stack, tag_player_camera, + world_has_local_shadow_lights, AutoExposureRenderingPlugin, FullscreenEffectsPlugin, + ProjectAtmosphere, SolariRaytracingSceneStats, SolariRenderingPlugin, ViewportCameraOwner, + ViewportFxSnapshot, ViewportStackApply, HDR_ENV_VAR, }; pub use sim_systems::{apply_player_intent, move_and_slide, sync_player_physics_position}; pub use world::{ diff --git a/crates/game_hot/src/player/spawn.rs b/crates/game_hot/src/player/spawn.rs index 3d8bd3f..1cbad3c 100644 --- a/crates/game_hot/src/player/spawn.rs +++ b/crates/game_hot/src/player/spawn.rs @@ -36,6 +36,7 @@ pub fn spawn_player(mut commands: Commands, existing: Query<(), With>) { parent.spawn(( PlayerCamera, Camera3d::default(), + Msaa::Off, Camera { clear_color: ClearColorConfig::Custom(Color::srgba(0.0, 0.0, 0.0, 1.0)), ..default() diff --git a/crates/game_hot/src/rendering/camera_fx.rs b/crates/game_hot/src/rendering/camera_fx.rs index b75e694..26797eb 100644 --- a/crates/game_hot/src/rendering/camera_fx.rs +++ b/crates/game_hot/src/rendering/camera_fx.rs @@ -1,13 +1,13 @@ //! Assembles the high-fidelity rendering stack on project cameras. use bevy::anti_alias::taa::TemporalAntiAliasing; -use bevy::camera::Exposure; +use bevy::camera::{Exposure, Hdr}; use bevy::core_pipeline::tonemapping::Tonemapping; -use bevy::pbr::{Atmosphere, AtmosphereSettings, ScatteringMedium, ScreenSpaceAmbientOcclusion}; +use bevy::light::{atmosphere::ScatteringMedium, Atmosphere}; +use bevy::pbr::{AtmosphereSettings, ScreenSpaceAmbientOcclusion}; use bevy::post_process::auto_exposure::AutoExposure; use bevy::post_process::bloom::Bloom; use bevy::prelude::*; -use bevy::render::view::Hdr; use settings::{ effective_auto_exposure, ActiveCameraRenderProfile, GiPath, ProjectRenderCamera, ProjectSettings, RenderingCapabilities, RenderingSettings, @@ -20,6 +20,10 @@ use super::fullscreen_effects::{strip_fullscreen_effects, sync_fullscreen_effect use super::solari::{strip_gi_path, sync_gi_path, SolariRaytracingSceneStats}; use super::viewport_camera::effective_viewport_gi_path; +/// Marker for the single project-owned atmosphere entity used by Bevy 0.19 cameras. +#[derive(Component, Debug, Clone, Copy)] +pub struct ProjectAtmosphere; + /// Environment variable name for HDR override (matches game launch). pub const HDR_ENV_VAR: &str = "BEVY_FPS_HDR"; @@ -73,6 +77,10 @@ pub fn hdr_required_for_render_stack( /// Applies the shared project rendering profile to all [`ProjectRenderCamera`] entities. #[unsafe(no_mangle)] +#[expect( + clippy::too_many_arguments, + reason = "Bevy systems expose independent render resources and light queries" +)] pub fn setup_project_camera_effects( mut commands: Commands, mut mediums: ResMut>, @@ -81,6 +89,7 @@ pub fn setup_project_camera_effects( caps: Option>, solari_stats: Option>, cameras: Query>, + atmospheres: Query>, points: Query<&PointLight>, spots: Query<&SpotLight>, ) { @@ -93,6 +102,12 @@ pub fn setup_project_camera_effects( ) }); let has_local_shadow_lights = super::viewport_camera::has_local_shadow_lights(&points, &spots); + sync_project_atmosphere( + &mut commands, + &mut mediums, + active.atmosphere, + atmospheres.iter(), + ); for entity in &cameras { apply_camera_render_profile( &mut commands, @@ -106,6 +121,29 @@ pub fn setup_project_camera_effects( } } +/// Keeps the Bevy 0.19 atmosphere model stable: one world entity, camera settings per view. +pub fn sync_project_atmosphere( + commands: &mut Commands, + mediums: &mut Assets, + enabled: bool, + atmospheres: impl IntoIterator, +) { + let existing: Vec = atmospheres.into_iter().collect(); + if enabled { + if existing.is_empty() { + let medium = mediums.add(ScatteringMedium::default()); + commands.spawn((ProjectAtmosphere, Atmosphere::earth(medium))); + } + for entity in existing.iter().skip(1) { + commands.entity(*entity).despawn(); + } + } else { + for entity in existing { + commands.entity(entity).despawn(); + } + } +} + /// Inserts or removes manual [`Exposure`] vs [`AutoExposure`] for the active profile. pub fn sync_exposure( commands: &mut Commands, @@ -135,7 +173,7 @@ pub fn apply_camera_render_profile( profile: &ActiveCameraRenderProfile, caps: &RenderingCapabilities, solari_stats: Option<&SolariRaytracingSceneStats>, - mediums: &mut Assets, + _mediums: &mut Assets, has_local_shadow_lights: bool, ) { commands.entity(entity).insert(Msaa::Off); @@ -155,13 +193,15 @@ pub fn apply_camera_render_profile( }); if profile.atmosphere { - let medium = mediums.add(ScatteringMedium::default()); - commands - .entity(entity) - .insert(Atmosphere::earthlike(medium)); commands .entity(entity) + .remove::() .insert(AtmosphereSettings::default()); + } else { + commands + .entity(entity) + .remove::() + .remove::(); } if profile.tonemapping_aces { commands.entity(entity).insert(Tonemapping::AcesFitted); @@ -245,8 +285,8 @@ pub fn sync_optional_rendering_fx( profile: &ActiveCameraRenderProfile, caps: &RenderingCapabilities, solari_stats: Option<&SolariRaytracingSceneStats>, - mediums: &mut Assets, - has_atmosphere: bool, + _mediums: &mut Assets, + has_atmosphere_settings: bool, has_tonemapping: bool, has_bloom: bool, has_ssao: bool, @@ -258,19 +298,18 @@ pub fn sync_optional_rendering_fx( let effective_gi = effective_viewport_gi_path(profile.gi_path, caps, solari_stats, has_local_shadow_lights); - if profile.atmosphere && !has_atmosphere { - let medium = mediums.add(ScatteringMedium::default()); - commands - .entity(entity) - .insert(Atmosphere::earthlike(medium)); + if profile.atmosphere && !has_atmosphere_settings { commands .entity(entity) + .remove::() .insert(AtmosphereSettings::default()); - } else if !profile.atmosphere && has_atmosphere { + } else if !profile.atmosphere && has_atmosphere_settings { commands .entity(entity) .remove::() .remove::(); + } else if profile.atmosphere { + commands.entity(entity).remove::(); } if profile.tonemapping_aces && !has_tonemapping { @@ -365,31 +404,6 @@ pub fn patch_camera_effects( } } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn solari_forces_hdr_render_target() { - assert!(hdr_required_for_render_stack( - false, - false, - GiPath::SolariDeferred - )); - assert!(hdr_required_for_render_stack(true, false, GiPath::Forward)); - assert!(!hdr_required_for_render_stack( - false, - false, - GiPath::Forward - )); - } - - #[test] - fn atmosphere_forces_hdr_render_target() { - assert!(hdr_required_for_render_stack(false, true, GiPath::Forward)); - } -} - /// Re-applies player camera FX tags after a hot reload. pub fn refresh_player_camera_fx(world: &mut World) { let Some(settings) = world.get_resource::().cloned() else { @@ -419,6 +433,16 @@ pub fn refresh_player_camera_fx(world: &mut World) { } world.resource_scope(|world, mut mediums: Mut>| { + let atmospheres: Vec = world + .query_filtered::>() + .iter(world) + .collect(); + sync_project_atmosphere( + &mut world.commands(), + &mut mediums, + profile.atmosphere, + atmospheres, + ); for entity in cameras { apply_camera_render_profile( &mut world.commands(), @@ -432,3 +456,28 @@ pub fn refresh_player_camera_fx(world: &mut World) { } }); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn solari_forces_hdr_render_target() { + assert!(hdr_required_for_render_stack( + false, + false, + GiPath::SolariDeferred + )); + assert!(hdr_required_for_render_stack(true, false, GiPath::Forward)); + assert!(!hdr_required_for_render_stack( + false, + false, + GiPath::Forward + )); + } + + #[test] + fn atmosphere_forces_hdr_render_target() { + assert!(hdr_required_for_render_stack(false, true, GiPath::Forward)); + } +} diff --git a/crates/game_hot/src/rendering/fullscreen_effects.rs b/crates/game_hot/src/rendering/fullscreen_effects.rs index d2f4591..d8008ea 100644 --- a/crates/game_hot/src/rendering/fullscreen_effects.rs +++ b/crates/game_hot/src/rendering/fullscreen_effects.rs @@ -2,11 +2,11 @@ use bevy::prelude::*; use bevy::render::extract_component::ExtractComponent; -use bevy::render::render_graph::{InternedRenderLabel, RenderLabel, RenderSubGraph}; use bevy::render::render_resource::ShaderType; use bevy::shader::ShaderRef; -use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_core_pipeline::fullscreen_material::{FullscreenMaterial, FullscreenMaterialPlugin}; +use bevy_core_pipeline::tonemapping::tonemapping; +use bevy_core_pipeline::Core3dSystems; use shared::{PostProcessEffectAsset, PostProcessEffectKind}; /// Vignette fullscreen pass. @@ -21,16 +21,10 @@ impl FullscreenMaterial for VignetteFx { "post_fx/vignette.wgsl".into() } - fn node_edges() -> Vec { - vec![ - Node3d::Tonemapping.intern(), - Self::node_label().intern(), - Node3d::EndMainPassPostProcessing.intern(), - ] - } - - fn sub_graph() -> Option { - Some(Core3d.intern()) + fn schedule_configs( + system: bevy::ecs::schedule::ScheduleConfigs, + ) -> bevy::ecs::schedule::ScheduleConfigs { + system.in_set(Core3dSystems::PostProcess).after(tonemapping) } } @@ -46,16 +40,10 @@ impl FullscreenMaterial for ChromaticAberrationFx { "post_fx/chromatic_aberration.wgsl".into() } - fn node_edges() -> Vec { - vec![ - Node3d::Tonemapping.intern(), - Self::node_label().intern(), - Node3d::EndMainPassPostProcessing.intern(), - ] - } - - fn sub_graph() -> Option { - Some(Core3d.intern()) + fn schedule_configs( + system: bevy::ecs::schedule::ScheduleConfigs, + ) -> bevy::ecs::schedule::ScheduleConfigs { + system.in_set(Core3dSystems::PostProcess).after(tonemapping) } } diff --git a/crates/game_hot/src/rendering/mod.rs b/crates/game_hot/src/rendering/mod.rs index 8b55eda..466a41c 100644 --- a/crates/game_hot/src/rendering/mod.rs +++ b/crates/game_hot/src/rendering/mod.rs @@ -12,7 +12,7 @@ pub use camera_fx::{ apply_camera_render_profile, apply_project_camera_fx, camera_auto_exposure_active, hdr_enabled_profile, patch_camera_effects, refresh_player_camera_fx, setup_project_camera_effects, strip_project_camera_fx, sync_optional_rendering_fx, - tag_player_camera, HDR_ENV_VAR, + sync_project_atmosphere, tag_player_camera, ProjectAtmosphere, HDR_ENV_VAR, }; pub use fullscreen_effects::FullscreenEffectsPlugin; pub use solari::{effective_gi_path_for_camera, SolariRaytracingSceneStats, SolariRenderingPlugin}; diff --git a/crates/game_hot/src/rendering/solari.rs b/crates/game_hot/src/rendering/solari.rs index 092c0da..563bb71 100644 --- a/crates/game_hot/src/rendering/solari.rs +++ b/crates/game_hot/src/rendering/solari.rs @@ -204,6 +204,10 @@ fn sync_auxiliary_camera_deferred_prepass( } } +#[expect( + clippy::too_many_arguments, + reason = "Bevy systems expose independent render assets and scene queries" +)] fn sync_hydrated_raytracing_meshes( caps: Res, mut commands: Commands, @@ -510,7 +514,9 @@ mod tests { let mut state: SystemState<(Query<&ChildOf>, Query<(), With>)> = SystemState::new(&mut world); - let (parents, level_roots) = state.get(&world); + let (parents, level_roots) = state + .get(&world) + .expect("solari test system params should be valid"); assert!(has_level_object_ancestor(child, &parents, &level_roots)); assert!(has_level_object_ancestor(root, &parents, &level_roots)); @@ -523,7 +529,9 @@ mod tests { let mut state: SystemState<(Query<&ChildOf>, Query<(), With>)> = SystemState::new(&mut world); - let (parents, level_roots) = state.get(&world); + let (parents, level_roots) = state + .get(&world) + .expect("solari test system params should be valid"); assert!(!has_level_object_ancestor(orphan, &parents, &level_roots)); } diff --git a/crates/game_hot/src/rendering/viewport_camera.rs b/crates/game_hot/src/rendering/viewport_camera.rs index 9fe0aee..6a6d206 100644 --- a/crates/game_hot/src/rendering/viewport_camera.rs +++ b/crates/game_hot/src/rendering/viewport_camera.rs @@ -6,13 +6,13 @@ use super::camera_fx::{ }; use super::solari::SolariRaytracingSceneStats; use bevy::anti_alias::taa::TemporalAntiAliasing; -use bevy::camera::Exposure; +use bevy::camera::{Exposure, Hdr}; use bevy::core_pipeline::tonemapping::Tonemapping; -use bevy::pbr::{Atmosphere, ScatteringMedium, ScreenSpaceAmbientOcclusion}; +use bevy::light::atmosphere::ScatteringMedium; +use bevy::pbr::{AtmosphereSettings, ScreenSpaceAmbientOcclusion}; use bevy::post_process::auto_exposure::AutoExposure; use bevy::post_process::bloom::Bloom; use bevy::prelude::*; -use bevy::render::view::Hdr; use settings::{ resolve_effective_render_stack, write_effective_render_stack, ActiveCameraRenderProfile, EffectiveRenderStack, GiPath, ProjectRenderCamera, RenderingCapabilities, @@ -39,18 +39,18 @@ pub fn resolve_viewport_camera_owner( /// True when any point or spot light in the world casts shadows. pub fn has_local_shadow_lights(points: &Query<&PointLight>, spots: &Query<&SpotLight>) -> bool { - points.iter().any(|l| l.shadows_enabled) || spots.iter().any(|l| l.shadows_enabled) + points.iter().any(|l| l.shadow_maps_enabled) || spots.iter().any(|l| l.shadow_maps_enabled) } /// [`has_local_shadow_lights`] for exclusive [`World`] access (hot reload, diagnostics). pub fn world_has_local_shadow_lights(world: &mut World) -> bool { for pl in world.query::<&PointLight>().iter(world) { - if pl.shadows_enabled { + if pl.shadow_maps_enabled { return true; } } for sl in world.query::<&SpotLight>().iter(world) { - if sl.shadows_enabled { + if sl.shadow_maps_enabled { return true; } } @@ -92,7 +92,7 @@ pub fn clear_viewport_camera_stack(commands: &mut Commands, entity: Entity) { #[derive(Debug, Clone, Copy, Default)] pub struct ViewportFxSnapshot { pub has_stack: bool, - pub has_atmosphere: bool, + pub has_atmosphere_settings: bool, pub has_tonemapping: bool, pub has_bloom: bool, pub has_ssao: bool, @@ -156,7 +156,7 @@ pub fn sync_viewport_camera_stack( caps, solari_stats, mediums, - snap.has_atmosphere, + snap.has_atmosphere_settings, snap.has_tonemapping, snap.has_bloom, snap.has_ssao, @@ -203,6 +203,19 @@ pub fn sync_viewport_camera_stack( } /// Builds a [`ViewportFxSnapshot`] from an entity's current components. +pub fn snapshot_viewport_fx(entity: Entity, world: &World) -> ViewportFxSnapshot { + ViewportFxSnapshot { + has_stack: world.get::(entity).is_some(), + has_atmosphere_settings: world.get::(entity).is_some(), + has_tonemapping: world.get::(entity).is_some(), + has_bloom: world.get::(entity).is_some(), + has_ssao: world.get::(entity).is_some(), + has_taa: world.get::(entity).is_some(), + has_hdr: world.get::(entity).is_some(), + has_auto_exposure: world.get::(entity).is_some(), + } +} + #[cfg(test)] mod tests { use super::*; @@ -230,16 +243,3 @@ mod tests { ); } } - -pub fn snapshot_viewport_fx(entity: Entity, world: &World) -> ViewportFxSnapshot { - ViewportFxSnapshot { - has_stack: world.get::(entity).is_some(), - has_atmosphere: world.get::(entity).is_some(), - has_tonemapping: world.get::(entity).is_some(), - has_bloom: world.get::(entity).is_some(), - has_ssao: world.get::(entity).is_some(), - has_taa: world.get::(entity).is_some(), - has_hdr: world.get::(entity).is_some(), - has_auto_exposure: world.get::(entity).is_some(), - } -} diff --git a/crates/game_hot/src/rendering/volumes.rs b/crates/game_hot/src/rendering/volumes.rs index 79fd34f..4b4f717 100644 --- a/crates/game_hot/src/rendering/volumes.rs +++ b/crates/game_hot/src/rendering/volumes.rs @@ -252,6 +252,10 @@ pub fn resolve_active_camera_render_profile( } /// Bevy system: updates [`ActiveCameraRenderProfile`] and [`ActiveVolumeContribution`]. +#[expect( + clippy::too_many_arguments, + reason = "Bevy systems expose independent render resources, queries, and local state" +)] pub fn sync_active_camera_render_profile( mut commands: Commands, settings: Res, diff --git a/crates/game_hot/src/world/lighting.rs b/crates/game_hot/src/world/lighting.rs index 4ed0c13..b9c335f 100644 --- a/crates/game_hot/src/world/lighting.rs +++ b/crates/game_hot/src/world/lighting.rs @@ -28,7 +28,7 @@ pub fn spawn_sun( ProjectSun, DirectionalLight { illuminance: rendering.sun_illuminance, - shadows_enabled: !has_scene_sun_override(&scene_suns), + shadow_maps_enabled: !has_scene_sun_override(&scene_suns), ..default() }, Visibility::default(), @@ -44,7 +44,7 @@ pub fn sync_project_sun_visibility( ) { let scene_override = has_scene_sun_override(&scene_suns); for (mut sun, mut visibility) in &mut project_suns { - sun.shadows_enabled = !scene_override; + sun.shadow_maps_enabled = !scene_override; *visibility = if scene_override { Visibility::Hidden } else { @@ -106,7 +106,7 @@ pub fn sync_project_sun_from_settings( let illuminance = settings.rendering.sun_illuminance; for (mut sun, mut visibility) in &mut project_suns { sun.illuminance = illuminance; - sun.shadows_enabled = !scene_override; + sun.shadow_maps_enabled = !scene_override; *visibility = if scene_override { Visibility::Hidden } else { diff --git a/crates/scene/src/lib.rs b/crates/scene/src/lib.rs index 9a6615c..98c25cf 100644 --- a/crates/scene/src/lib.rs +++ b/crates/scene/src/lib.rs @@ -107,7 +107,8 @@ pub const FORBIDDEN_SAVED_COMPONENT_MARKERS: &[&str] = &[ "bevy_mesh::MeshMaterial3d", "avian3d::RigidBody", "avian3d::Collider", - "bevy_scene::SceneRoot", + "bevy_world_serialization::components::WorldAssetRoot", + "bevy_world_serialization::components::DynamicWorldRoot", "bevy_render::visibility::ComputedVisibility", ]; @@ -189,7 +190,7 @@ mod tests { }, ), }))"; - let migrated = migrate_scene_text(&v1).unwrap(); + let migrated = migrate_scene_text(v1).unwrap(); assert!(migrated.contains("ActorKind")); assert!(migrated.contains("StaticMesh")); } diff --git a/crates/scene/src/migrate.rs b/crates/scene/src/migrate.rs index 7eee9a1..5fb3b4e 100644 --- a/crates/scene/src/migrate.rs +++ b/crates/scene/src/migrate.rs @@ -173,9 +173,7 @@ fn fix_directional_intensity(block: &str) -> String { }; let after = &block[intensity_idx + "intensity:".len()..]; let trimmed = after.trim_start(); - let end = trimmed - .find(|c: char| c == ',' || c == ')') - .unwrap_or(trimmed.len()); + let end = trimmed.find([',', ')']).unwrap_or(trimmed.len()); let value_str = trimmed[..end].trim(); let Ok(value) = value_str.parse::() else { return block.to_string(); diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 3df56b8..1505ad5 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -245,9 +245,11 @@ mod tests { #[test] fn effective_auto_exposure_requires_hdr_and_compute() { - let mut profile = ActiveCameraRenderProfile::default(); - profile.exposure_mode = ExposureMode::Auto; - profile.hdr = true; + let mut profile = ActiveCameraRenderProfile { + exposure_mode: ExposureMode::Auto, + hdr: true, + ..Default::default() + }; let caps = RenderingCapabilities { auto_exposure_supported: true, ..Default::default() diff --git a/crates/shared/src/actor.rs b/crates/shared/src/actor.rs index f50f5fe..a93b206 100644 --- a/crates/shared/src/actor.rs +++ b/crates/shared/src/actor.rs @@ -11,9 +11,7 @@ use crate::{ /// One-shot deterministic kind from authoring components (scene migration only). pub fn infer_actor_kind(entity: EntityRef<'_>) -> Option { - if entity.get::().is_none() { - return None; - } + entity.get::()?; if entity.get::().is_some() { return Some(ActorKind::PlayerSpawn); } diff --git a/crates/shared/src/brush_math.rs b/crates/shared/src/brush_math.rs index cd9bf97..2ec4c9f 100644 --- a/crates/shared/src/brush_math.rs +++ b/crates/shared/src/brush_math.rs @@ -125,6 +125,20 @@ pub fn validate_brush(brush: &BrushDesc) -> BrushValidationReport { "Face UV scale is zero or non-finite.", ); } + if !face.uv_offset.is_finite() { + report.push( + BrushDiagnosticSeverity::Warning, + Some(face.id.clone()), + "Face UV offset is non-finite.", + ); + } + if !face.uv_rotation.is_finite() { + report.push( + BrushDiagnosticSeverity::Warning, + Some(face.id.clone()), + "Face UV rotation is non-finite.", + ); + } } let non_manifold_edges = count_non_manifold_edges(brush); @@ -547,12 +561,26 @@ mod tests { fn reports_uv_warnings_without_invalidating_brush() { let mut brush = BrushDesc::default(); brush.faces[0].uv_scale = Vec2::ZERO; + brush.faces[1].uv_offset = Vec2::new(f32::NAN, 0.0); + brush.faces[2].uv_rotation = f32::INFINITY; let report = validate_brush(&brush); assert!(report.is_valid()); + assert_eq!( + report + .diagnostics + .iter() + .filter(|diagnostic| diagnostic.severity == BrushDiagnosticSeverity::Warning) + .count(), + 3 + ); assert!(report .diagnostics .iter() - .any(|diagnostic| diagnostic.severity == BrushDiagnosticSeverity::Warning)); + .any(|diagnostic| diagnostic.message.contains("UV offset"))); + assert!(report + .diagnostics + .iter() + .any(|diagnostic| diagnostic.message.contains("UV rotation"))); } #[test] diff --git a/crates/shared/src/components.rs b/crates/shared/src/components.rs index f29c5e9..0c7743d 100644 --- a/crates/shared/src/components.rs +++ b/crates/shared/src/components.rs @@ -765,7 +765,7 @@ impl Default for MaterialDesc { } /// Reflectable reference to an imported 3D model scene (glTF/GLB or FBX). -/// Hydration turns this into a `SceneRoot` on the entity. +/// Hydration turns this into a `WorldAssetRoot` on the entity. #[derive(Component, Reflect, Default, Debug, Clone, Serialize, Deserialize)] #[reflect(Component, Default, Debug, Serialize, Deserialize)] pub struct ModelRef { @@ -790,7 +790,7 @@ impl ModelRef { } /// Reflectable reference to a saved `.scn.ron` dynamic scene under `assets/`. -/// Hydration turns this into a `DynamicSceneRoot`. +/// Hydration turns this into a `DynamicWorldRoot`. #[derive(Component, Reflect, Default, Debug, Clone, Serialize, Deserialize)] #[reflect(Component, Default, Debug, Serialize, Deserialize)] pub struct PrefabRef { @@ -1256,9 +1256,12 @@ mod tests { #[test] fn point_spot_lumen_max_covers_bevy_cinema_reference() { - assert!( - super::AUTHORING_POINT_SPOT_LUMENS_MAX >= light_consts::lumens::VERY_LARGE_CINEMA_LIGHT - ); + const { + assert!( + super::AUTHORING_POINT_SPOT_LUMENS_MAX + >= light_consts::lumens::VERY_LARGE_CINEMA_LIGHT + ); + } } #[test] diff --git a/crates/shared/src/hydration/brushes.rs b/crates/shared/src/hydration/brushes.rs index 6b368cc..8571b38 100644 --- a/crates/shared/src/hydration/brushes.rs +++ b/crates/shared/src/hydration/brushes.rs @@ -67,6 +67,10 @@ pub fn hydrate_brushes( } } +#[expect( + clippy::too_many_arguments, + reason = "hydration keeps Bevy asset stores and authored brush inputs explicit" +)] pub fn spawn_brush_mesh( commands: &mut Commands, asset_server: &AssetServer, @@ -243,7 +247,12 @@ fn append_face( for vertex in &face.vertices { positions.push([vertex.x, vertex.y, vertex.z]); normals.push([normal.x, normal.y, normal.z]); - let uv = Vec2::new(vertex.dot(u_axis), vertex.dot(v_axis)) * face.uv_scale + face.uv_offset; + let uv = transform_face_uv( + Vec2::new(vertex.dot(u_axis), vertex.dot(v_axis)), + face.uv_scale, + face.uv_rotation, + face.uv_offset, + ); uvs.push([uv.x, uv.y]); } @@ -262,6 +271,29 @@ fn append_face( Some(()) } +fn transform_face_uv(base: Vec2, scale: Vec2, rotation_degrees: f32, offset: Vec2) -> Vec2 { + let scale = if scale.is_finite() { scale } else { Vec2::ONE }; + let rotation_degrees = if rotation_degrees.is_finite() { + rotation_degrees + } else { + 0.0 + }; + let offset = if offset.is_finite() { + offset + } else { + Vec2::ZERO + }; + let scaled = base * scale; + if rotation_degrees == 0.0 { + return scaled + offset; + } + let (sin, cos) = rotation_degrees.to_radians().sin_cos(); + Vec2::new( + scaled.x * cos - scaled.y * sin, + scaled.x * sin + scaled.y * cos, + ) + offset +} + fn face_axes(normal: Vec3) -> (Vec3, Vec3) { let tangent_seed = if normal.y.abs() > 0.9 { Vec3::X @@ -319,6 +351,27 @@ mod tests { })); } + #[test] + fn brush_face_uv_transform_applies_scale_rotation_and_offset() { + let uv = transform_face_uv( + Vec2::new(2.0, 1.0), + Vec2::new(2.0, 3.0), + 90.0, + Vec2::new(0.25, -0.5), + ); + + assert!((uv.x + 2.75).abs() < 0.0001); + assert!((uv.y - 3.5).abs() < 0.0001); + + let sanitized = transform_face_uv( + Vec2::new(2.0, 1.0), + Vec2::new(f32::NAN, 3.0), + f32::INFINITY, + Vec2::new(0.25, f32::NAN), + ); + assert_eq!(sanitized, Vec2::new(2.0, 1.0)); + } + #[test] fn invalid_brush_returns_none() { let mut brush = BrushDesc::default(); diff --git a/crates/shared/src/hydration/lights.rs b/crates/shared/src/hydration/lights.rs index 5d607f4..a6eab96 100644 --- a/crates/shared/src/hydration/lights.rs +++ b/crates/shared/src/hydration/lights.rs @@ -77,6 +77,10 @@ pub(crate) fn normalized_directional_lux(intensity: f32) -> f32 { /// Re-applies runtime lights when authoring [`LightDesc`] exists but hydration was stripped. #[allow(clippy::type_complexity)] +#[expect( + clippy::too_many_arguments, + reason = "Bevy systems expose independent resource and query parameters" +)] pub fn reconcile_missing_runtime_lights( mut commands: Commands, settings: Res, @@ -140,7 +144,7 @@ pub(crate) fn insert_light_from_desc( color, intensity: light.intensity, range: light.range, - shadows_enabled: light.shadows, + shadow_maps_enabled: light.shadows, ..default() }, Visibility::Visible, @@ -152,7 +156,7 @@ pub(crate) fn insert_light_from_desc( color, intensity: light.intensity, range: light.range, - shadows_enabled: light.shadows, + shadow_maps_enabled: light.shadows, outer_angle: light.outer_angle_deg.to_radians(), inner_angle: light.inner_angle_deg.to_radians(), ..default() @@ -165,7 +169,7 @@ pub(crate) fn insert_light_from_desc( DirectionalLight { color, illuminance: normalized_directional_lux(light.intensity), - shadows_enabled: light.shadows, + shadow_maps_enabled: light.shadows, ..default() }, Visibility::Visible, @@ -192,7 +196,7 @@ mod tests { let rendering = RenderingSettings::default(); let spot = LightDesc::for_kind(AuthoringLightKind::Spot); { - let mut commands = Commands::new(&mut queue, &mut world); + let mut commands = Commands::new(&mut queue, &world); let mut entity_commands = commands.entity(entity); insert_light_from_desc(&mut entity_commands, &spot, &rendering); } @@ -202,7 +206,7 @@ mod tests { let point = LightDesc::for_kind(AuthoringLightKind::Point); queue = CommandQueue::default(); { - let mut commands = Commands::new(&mut queue, &mut world); + let mut commands = Commands::new(&mut queue, &world); let mut entity_commands = commands.entity(entity); strip_light_components(&mut entity_commands); insert_light_from_desc(&mut entity_commands, &point, &rendering); @@ -221,7 +225,7 @@ mod tests { let mut light = LightDesc::for_kind(AuthoringLightKind::Directional); light.shadows = true; { - let mut commands = Commands::new(&mut queue, &mut world); + let mut commands = Commands::new(&mut queue, &world); let mut entity_commands = commands.entity(entity); insert_light_from_desc(&mut entity_commands, &light, &rendering); } @@ -239,10 +243,12 @@ mod tests { #[test] fn scene_directional_cascades_match_project_shadow_settings() { - let mut rendering = RenderingSettings::default(); - rendering.shadow_cascades = 3; - rendering.shadow_first_cascade = 12.0; - rendering.shadow_max_distance = 400.0; + let rendering = RenderingSettings { + shadow_cascades: 3, + shadow_first_cascade: 12.0, + shadow_max_distance: 400.0, + ..Default::default() + }; let config = cascade_config_from_rendering(&rendering); let expected = cascade_config_from_rendering(&rendering); assert_eq!(config.bounds, expected.bounds); @@ -257,7 +263,7 @@ mod tests { let spot_entity = world.spawn(LevelObject).id(); let rendering = RenderingSettings::default(); { - let mut commands = Commands::new(&mut queue, &mut world); + let mut commands = Commands::new(&mut queue, &world); insert_light_from_desc( &mut commands.entity(point_entity), &LightDesc::for_kind(AuthoringLightKind::Point), diff --git a/crates/shared/src/hydration/materials.rs b/crates/shared/src/hydration/materials.rs index d91dfbc..6885c41 100644 --- a/crates/shared/src/hydration/materials.rs +++ b/crates/shared/src/hydration/materials.rs @@ -79,9 +79,11 @@ mod tests { let mut app = App::new(); app.add_plugins((MinimalPlugins, AssetPlugin::default())); let asset_server = app.world().resource::(); - let mut desc = MaterialDesc::default(); - desc.emissive_color = ColorDesc::srgb(0.5, 0.25, 1.0); - desc.emissive_intensity = 1000.0; + let desc = MaterialDesc { + emissive_color: ColorDesc::srgb(0.5, 0.25, 1.0), + emissive_intensity: 1000.0, + ..Default::default() + }; let material = material_from_desc(asset_server, &desc); diff --git a/crates/shared/src/hydration/mod.rs b/crates/shared/src/hydration/mod.rs index 4f2dd2b..650027c 100644 --- a/crates/shared/src/hydration/mod.rs +++ b/crates/shared/src/hydration/mod.rs @@ -29,7 +29,10 @@ use static_meshes::{ hydrate_static_mesh_renderers, spawn_static_mesh_parts, HydratedStaticMeshPart, StaticMeshArtifactCache, }; -use visibility::{init_editor_visibility_on_spawn, sync_editor_visibility}; +use visibility::{ + ensure_level_object_visibility_hierarchy, init_editor_visibility_on_spawn, + sync_editor_visibility, visibility_from_editor, +}; use crate::{ inspector_component_active, BrushDesc, ColliderDesc, EditorVisibility, InspectorOrder, @@ -61,12 +64,21 @@ impl Plugin for HydrationPlugin { ) .add_systems( Update, - (sync_editor_visibility, init_editor_visibility_on_spawn).chain(), + ( + ensure_level_object_visibility_hierarchy, + sync_editor_visibility, + init_editor_visibility_on_spawn, + ) + .chain(), ); } } /// Runs the full hydration chain immediately (e.g. after scene load in the editor). +#[expect( + clippy::type_complexity, + reason = "exclusive hydration snapshots several authored component combinations" +)] pub fn flush_level_object_hydration(world: &mut World) { if !world.contains_resource::() { world.insert_resource(StaticMeshArtifactCache::default()); @@ -227,8 +239,9 @@ pub fn flush_level_object_hydration(world: &mut World) { )> = SystemState::new(world); { - let (mut commands, asset_server, mut meshes, mut materials, mut artifact_cache) = - state.get_mut(world); + let (mut commands, asset_server, mut meshes, mut materials, mut artifact_cache) = state + .get_mut(world) + .expect("hydrate_level_objects system params should be valid"); for (entity, primitive) in primitives { let mesh = meshes.add(primitive_mesh(&primitive)); @@ -285,12 +298,21 @@ pub fn flush_level_object_hydration(world: &mut World) { state.apply(world); for (entity, editor) in visibility_targets { + let visibility = visibility_from_editor(editor); if let Some(mut vis) = world.get_mut::(entity) { - *vis = if editor.visible { - Visibility::Visible - } else { - Visibility::Hidden - }; + *vis = visibility; + } else if let Ok(mut entity_mut) = world.get_entity_mut(entity) { + entity_mut.insert(visibility); + } + if world.get::(entity).is_none() { + if let Ok(mut entity_mut) = world.get_entity_mut(entity) { + entity_mut.insert(InheritedVisibility::default()); + } + } + if world.get::(entity).is_none() { + if let Ok(mut entity_mut) = world.get_entity_mut(entity) { + entity_mut.insert(ViewVisibility::default()); + } } } } @@ -300,8 +322,8 @@ mod tests { use super::flush_level_object_hydration; use super::strip::strip_hydrated_entity; use crate::{ - AuthoringLightKind, ColorDesc, InspectorOrder, LevelObject, LightDesc, MaterialDesc, - Primitive, COMPONENT_LIGHT_DESC, + AuthoringLightKind, ColorDesc, EditorVisibility, InspectorOrder, LevelObject, LightDesc, + MaterialDesc, Primitive, COMPONENT_LIGHT_DESC, }; use avian3d::prelude::*; use bevy::prelude::*; @@ -324,6 +346,29 @@ mod tests { assert!(app.world().get::(entity).is_some()); } + #[test] + fn flush_hydration_initializes_level_object_visibility_hierarchy() { + let mut app = App::new(); + app.add_plugins(MinimalPlugins); + app.add_plugins(AssetPlugin::default()); + app.add_plugins(bevy::pbr::MaterialPlugin::::default()); + app.init_asset::(); + + let entity = app + .world_mut() + .spawn((LevelObject, EditorVisibility { visible: false })) + .id(); + + flush_level_object_hydration(app.world_mut()); + + assert_eq!( + app.world().get::(entity), + Some(&Visibility::Hidden) + ); + assert!(app.world().get::(entity).is_some()); + assert!(app.world().get::(entity).is_some()); + } + #[test] fn flush_hydration_skips_point_light_when_solari_is_active() { let mut app = App::new(); diff --git a/crates/shared/src/hydration/models.rs b/crates/shared/src/hydration/models.rs index 4e41103..1022313 100644 --- a/crates/shared/src/hydration/models.rs +++ b/crates/shared/src/hydration/models.rs @@ -1,8 +1,8 @@ //! Imported model scenes from [`ModelRef`] authoring data. use bevy::gltf::GltfAssetLabel; +use bevy::prelude::WorldAssetRoot; use bevy::prelude::*; -use bevy::scene::SceneRoot; use crate::{asset_server_path, LevelObject, ModelRef}; @@ -23,7 +23,7 @@ pub fn hydrate_models( despawn_generated_children(&mut commands, entity, child_list); } - commands.entity(entity).remove::(); + commands.entity(entity).remove::(); let path = asset_server_path(&model.path); let handle = if path.ends_with(".fbx") { @@ -31,6 +31,6 @@ pub fn hydrate_models( } else { asset_server.load(GltfAssetLabel::Scene(model.scene_index).from_asset(path)) }; - commands.entity(entity).insert(SceneRoot(handle)); + commands.entity(entity).insert(WorldAssetRoot(handle)); } } diff --git a/crates/shared/src/hydration/prefabs.rs b/crates/shared/src/hydration/prefabs.rs index 3805caa..007ccdc 100644 --- a/crates/shared/src/hydration/prefabs.rs +++ b/crates/shared/src/hydration/prefabs.rs @@ -1,7 +1,7 @@ //! Prefab scene roots from [`PrefabRef`] / [`PrefabInstance`] authoring data. +use bevy::prelude::DynamicWorldRoot; use bevy::prelude::*; -use bevy::scene::DynamicSceneRoot; use crate::{LevelObject, PrefabInstance, PrefabRef}; @@ -28,7 +28,7 @@ pub fn hydrate_prefabs( >, ) { for (entity, prefab) in &prefabs { - commands.entity(entity).insert(DynamicSceneRoot( + commands.entity(entity).insert(DynamicWorldRoot( asset_server.load(asset_server_path(&prefab.path)), )); } @@ -37,7 +37,7 @@ pub fn hydrate_prefabs( let path = asset_server_path(&instance.source_path); commands.entity(entity).insert(( PrefabRef::new(instance.source_path.clone()), - DynamicSceneRoot(asset_server.load(path)), + DynamicWorldRoot(asset_server.load(path)), )); } } diff --git a/crates/shared/src/hydration/static_meshes.rs b/crates/shared/src/hydration/static_meshes.rs index ab36829..259a17c 100644 --- a/crates/shared/src/hydration/static_meshes.rs +++ b/crates/shared/src/hydration/static_meshes.rs @@ -202,6 +202,10 @@ pub fn hydrate_static_mesh_renderers( } } +#[expect( + clippy::too_many_arguments, + reason = "hydration keeps Bevy asset stores and authored mesh inputs explicit" +)] pub fn spawn_static_mesh_parts( commands: &mut Commands, asset_server: &AssetServer, diff --git a/crates/shared/src/hydration/strip.rs b/crates/shared/src/hydration/strip.rs index e6a786e..3140d99 100644 --- a/crates/shared/src/hydration/strip.rs +++ b/crates/shared/src/hydration/strip.rs @@ -2,7 +2,6 @@ use avian3d::prelude::*; use bevy::prelude::*; -use bevy::scene::{DynamicSceneRoot, SceneRoot}; /// Strips hydrated components via [`EntityWorldMut`] (e.g. before scene save). pub fn strip_hydrated_entity(entity: &mut EntityWorldMut<'_>) { @@ -12,8 +11,8 @@ pub fn strip_hydrated_entity(entity: &mut EntityWorldMut<'_>) { .remove::() .remove::() .remove::() - .remove::() - .remove::() + .remove::() + .remove::() .remove::() .remove::(); } @@ -26,13 +25,13 @@ pub fn strip_hydrated(entity_commands: &mut EntityCommands<'_>) { .remove::() .remove::() .remove::() - .remove::() - .remove::() + .remove::() + .remove::() .remove::() .remove::(); } -/// Despawns all child entities (e.g. prior `SceneRoot` instance hierarchy). +/// Despawns all child entities (e.g. prior `WorldAssetRoot` instance hierarchy). pub fn despawn_generated_children(commands: &mut Commands, entity: Entity, children: &Children) { for child in children.iter() { commands.entity(child).despawn(); diff --git a/crates/shared/src/hydration/visibility.rs b/crates/shared/src/hydration/visibility.rs index 6448742..77e42c0 100644 --- a/crates/shared/src/hydration/visibility.rs +++ b/crates/shared/src/hydration/visibility.rs @@ -3,6 +3,41 @@ use crate::{EditorVisibility, LevelObject}; use bevy::prelude::*; +#[expect( + clippy::type_complexity, + reason = "the query mirrors Bevy's three visibility hierarchy components" +)] +pub fn ensure_level_object_visibility_hierarchy( + mut commands: Commands, + query: Query< + ( + Entity, + &EditorVisibility, + Option<&Visibility>, + Option<&InheritedVisibility>, + Option<&ViewVisibility>, + ), + With, + >, +) { + for (entity, editor, visibility, inherited_visibility, view_visibility) in &query { + let mut entity_commands = commands.entity(entity); + if visibility.is_none() { + entity_commands.insert(visibility_from_editor(*editor)); + } + if inherited_visibility.is_none() { + entity_commands.insert(InheritedVisibility::default()); + } + if view_visibility.is_none() { + entity_commands.insert(ViewVisibility::default()); + } + } +} + +#[expect( + clippy::type_complexity, + reason = "the query expresses Bevy change detection at the system boundary" +)] pub fn sync_editor_visibility( mut query: Query< (&EditorVisibility, &mut Visibility), @@ -13,14 +48,14 @@ pub fn sync_editor_visibility( >, ) { for (editor, mut visibility) in query.iter_mut() { - *visibility = if editor.visible { - Visibility::Visible - } else { - Visibility::Hidden - }; + *visibility = visibility_from_editor(*editor); } } +#[expect( + clippy::type_complexity, + reason = "the query expresses Bevy spawn detection at the system boundary" +)] pub fn init_editor_visibility_on_spawn( mut query: Query< (&EditorVisibility, &mut Visibility), @@ -32,10 +67,39 @@ pub fn init_editor_visibility_on_spawn( >, ) { for (editor, mut visibility) in query.iter_mut() { - *visibility = if editor.visible { - Visibility::Visible - } else { - Visibility::Hidden - }; + *visibility = visibility_from_editor(*editor); + } +} + +pub fn visibility_from_editor(editor: EditorVisibility) -> Visibility { + if editor.visible { + Visibility::Visible + } else { + Visibility::Hidden + } +} + +#[cfg(test)] +mod tests { + use super::ensure_level_object_visibility_hierarchy; + use crate::{EditorVisibility, LevelObject}; + use bevy::prelude::*; + + #[test] + fn level_object_visibility_hierarchy_is_inserted_for_generated_children() { + let mut app = App::new(); + app.add_systems(Update, ensure_level_object_visibility_hierarchy); + + let entity = app + .world_mut() + .spawn((LevelObject, EditorVisibility { visible: false })) + .id(); + + app.update(); + + let world = app.world(); + assert_eq!(world.get::(entity), Some(&Visibility::Hidden)); + assert!(world.get::(entity).is_some()); + assert!(world.get::(entity).is_some()); } } diff --git a/docs/README.md b/docs/README.md index e419add..9dffd91 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,9 +33,10 @@ Immutable-style log of significant decisions. Add a new numbered ADR when changi | [0016](adr/0016-unified-rendering-contract.md) | Requested/effective render stack, Solari eligibility, emissive materials | | [0017](adr/0017-normalized-static-mesh-assets.md) | Normalized static mesh assets and renderer placement | | [0018](adr/0018-componentized-actor-inspector-and-materials.md) | Componentized actor inspector, imported asset refs, collider split, material overrides | -| [0019](adr/0019-local-bevy-render-timeout-patch.md) | Local `bevy_render` patch for transient Linux swapchain timeouts | +| [0019](adr/0019-local-bevy-render-timeout-patch.md) | Superseded local `bevy_render` patch for transient Linux swapchain timeouts | | [0020](adr/0020-jackdaw-inspired-editor-roadmap.md) | Jackdaw-inspired roadmap source policy and attribution requirements | | [0021](adr/0021-brush-authoring-schema.md) | Brush authoring schema and hydration contract | +| [0022](adr/0022-bevy-0-19-upgrade.md) | Bevy 0.19 / Avian 0.7 upgrade and compatibility patches | ## Editor framework diff --git a/docs/adr/0002-bevy-version-and-migration-policy.md b/docs/adr/0002-bevy-version-and-migration-policy.md index b2e2943..fae6e7e 100644 --- a/docs/adr/0002-bevy-version-and-migration-policy.md +++ b/docs/adr/0002-bevy-version-and-migration-policy.md @@ -6,7 +6,7 @@ Accepted ## Context -Bevy moves quickly, and the project depends on ecosystem crates that track Bevy on their own schedules. The current workspace targets Bevy 0.18 with Avian 0.6, bevy_egui, bevy-inspector-egui, and transform-gizmo-bevy. +Bevy moves quickly, and the project depends on ecosystem crates that track Bevy on their own schedules. The current workspace targets Bevy 0.19 with Avian 0.7, bevy_egui, bevy-inspector-egui, and local compatibility patches for transform-gizmo-bevy and bevy_ufbx. Unplanned upgrades can break editor work, rendering, scene serialization, physics, and later networking. Delayed upgrades can also make migrations larger and riskier. @@ -18,9 +18,11 @@ Before merging a Bevy upgrade: - Confirm matching versions exist for editor, physics, and networking-related crates used by the active milestone. - Read Bevy migration guides for every skipped release. +- Preserve authored scene compatibility when Bevy renames runtime scene components or serialization types. - Run `cargo fmt --check`, `cargo check --workspace`, `cargo clippy --workspace`, strict clippy on changed/foundation crates with `-D warnings`, focused tests for changed crates, and focused binary builds. - Smoke-test the game and editor manually after CI is green. - Record any project-specific migration notes in docs or ADR follow-ups. +- Keep the normal development profile fully debuggable. Use line-table-only debug information and no incremental cache for the test profile so Bevy upgrade matrices do not retain multi-gigabyte test images indefinitely; use the indexed target cleanup workflow in the root README after migration passes. ## Consequences diff --git a/docs/adr/0013-rendering-tiers-and-post-process-volumes.md b/docs/adr/0013-rendering-tiers-and-post-process-volumes.md index bdaf6f5..50eca6a 100644 --- a/docs/adr/0013-rendering-tiers-and-post-process-volumes.md +++ b/docs/adr/0013-rendering-tiers-and-post-process-volumes.md @@ -6,7 +6,7 @@ Accepted ## Context -All rendering configuration previously lived in `RenderingSettings` (`assets/project.ron`) and was applied globally to the single `ProjectRenderCamera`. Bevy 0.18 adds experimental Solari ray-traced GI and `FullscreenMaterial` for custom post passes, but no spatial post-process volumes. Game teams need local overrides (rooms, biomes) without forking Rust or duplicating levels. +All rendering configuration previously lived in `RenderingSettings` (`assets/project.ron`) and was applied globally to the single `ProjectRenderCamera`. Bevy 0.19 provides experimental Solari ray-traced GI and `FullscreenMaterial` for custom post passes, but no spatial post-process volumes. Game teams need local overrides (rooms, biomes) without forking Rust or duplicating levels. ## Decision @@ -21,7 +21,7 @@ All rendering configuration previously lived in `RenderingSettings` (`assets/pro Runtime exposes `RenderingCapabilities { rt_supported, active_gi_path }` and a resolved `ActiveCameraRenderProfile` each frame. ADR 0016 supersedes the ambiguous `active_gi_path` wording with explicit requested/effective GI fields and `EffectiveRenderStack` fallback reasons. -Bevy 0.18 Solari samples directional lights and emissive meshes, while point and spot lights require the normal PBR light pass that Solari cameras skip. The editor surfaces this limitation directly on point/spot `LightDesc` components and runtime-disables them while Solari is active; point/spot light authoring uses Forward PBR. Requested Solari tags project geometry for raytracing and switches camera plus opaque-renderer components to Solari as soon as RT support is available; render-world readiness counters report whether BLAS/TLAS/bind-group setup and compatible lights are actually ready. +Bevy 0.19 Solari samples directional lights and emissive meshes, while point and spot lights require the normal PBR light pass that Solari cameras skip. The editor surfaces this limitation directly on point/spot `LightDesc` components and runtime-disables them while Solari is active; point/spot light authoring uses Forward PBR. Requested Solari tags project geometry for raytracing and switches camera plus opaque-renderer components to Solari as soon as RT support is available; render-world readiness counters report whether BLAS/TLAS/bind-group setup and compatible lights are actually ready. ### WYSIWYG @@ -52,4 +52,4 @@ Authoring is saved to disk; BRP mutates authoring only ([ADR 0009](0009-authorin - `camera_fx.rs` consumes `ActiveCameraRenderProfile`, not raw `RenderingSettings`. - Editor Rendering panel explains GI fallback, overlapping volumes, and active contributors. - Game teams keep global look in `project.ron`, local looks in volumes, reusable bundles in `assets/rendering_profiles/`, custom shaders in `assets/post_fx/`. -- Solari may break some debug draws and does not support point/spot lights in Bevy 0.18; `GiMode::Auto` remains the default startup path and the Rendering panel reports the limitation and render-scene readiness. +- Solari may break some debug draws and does not support point/spot lights in Bevy 0.19; `GiMode::Auto` remains the default startup path and the Rendering panel reports the limitation and render-scene readiness. diff --git a/docs/adr/0015-viewport-camera-stack-ownership.md b/docs/adr/0015-viewport-camera-stack-ownership.md index 5a12932..148306f 100644 --- a/docs/adr/0015-viewport-camera-stack-ownership.md +++ b/docs/adr/0015-viewport-camera-stack-ownership.md @@ -21,7 +21,7 @@ used by `ProjectSun`. ## Decision 1. **`game_hot::rendering::viewport_camera`** is the only module that adds/removes the viewport - render stack (`ProjectRenderCamera`, exposure, GI, atmosphere, bloom, fog, etc.). + render stack (`ProjectRenderCamera`, exposure, GI, camera atmosphere settings, bloom, fog, etc.). 2. **Editor** `scene_view` and `play/session` set `RenderTarget`, `Camera::is_active`, and `viewport = None` only. **`render_view::sync_project_render_view`** calls `clear_viewport_camera_stack` on owner handoff and `sync_viewport_camera_stack` on the active @@ -41,3 +41,5 @@ used by `ProjectSun`. - Thumbnail studio and multi-viewport remain out of scope; they do not use the viewport camera sync path. - `setup_project_camera_effects` no longer skips cameras that already have atmosphere; editor defers player FX via `GameRenderBootstrap` and relies on `sync_project_render_view` once HDR RTT exists. +- On Bevy 0.19, the project owns one stable world-space `Atmosphere` entity and cameras receive + `AtmosphereSettings`; render-stack sync strips stale camera-side `Atmosphere` components. diff --git a/docs/adr/0016-unified-rendering-contract.md b/docs/adr/0016-unified-rendering-contract.md index e27bdc1..5350e9a 100644 --- a/docs/adr/0016-unified-rendering-contract.md +++ b/docs/adr/0016-unified-rendering-contract.md @@ -10,7 +10,7 @@ Rendering state crossed settings, hydration, game runtime, Solari readiness, edi ownership, and diagnostics. The project already had `ActiveCameraRenderProfile` for project and volume intent, but GI fallback decisions were repeated across camera sync, Solari systems, and UI. -Bevy 0.18 Solari is still experimental. It needs raytracing-capable hardware, eligible +Bevy 0.19 Solari is still experimental. It needs raytracing-capable hardware, eligible `Mesh3d + MeshMaterial3d` scene geometry, Solari-compatible mesh assets (TriangleList, POSITION/NORMAL/UV_0/TANGENT, U32 indices), a render-scene bind group, and a compatible directional or emissive light source. It also does not replace Forward PBR local @@ -52,5 +52,5 @@ point/spot lighting. required Solari mesh attributes or U32 indices. - Existing scenes and material assets remain compatible through serde defaults for new emissive fields. -- Bevy 0.19+ rendering upgrades remain a separate migration track under ADR 0002, not part of this +- Future Bevy rendering upgrades remain a separate migration track under ADR 0002, not part of this contract change. diff --git a/docs/adr/0017-normalized-static-mesh-assets.md b/docs/adr/0017-normalized-static-mesh-assets.md index c9ecb50..ac5bf20 100644 --- a/docs/adr/0017-normalized-static-mesh-assets.md +++ b/docs/adr/0017-normalized-static-mesh-assets.md @@ -6,7 +6,7 @@ Accepted ## Context -glTF/GLB and FBX previously entered the editor as imported scenes through `ModelRef`, with runtime hydration loading a format-specific `SceneRoot`. That path is still useful for full scene playback, but it makes ordinary static mesh placement depend on source format behavior and prevents the inspector from exposing a stable renderer component. +glTF/GLB and FBX previously entered the editor as imported scenes through `ModelRef`, with runtime hydration loading a format-specific `WorldAssetRoot`. That path is still useful for full scene playback, but it makes ordinary static mesh placement depend on source format behavior and prevents the inspector from exposing a stable renderer component. The editor needs model drag/drop to create editable static mesh actors by default, while retaining an explicit scene-instance path for animation, skinning, cameras, and other full-scene data. diff --git a/docs/adr/0019-local-bevy-render-timeout-patch.md b/docs/adr/0019-local-bevy-render-timeout-patch.md index fd65647..a21378f 100644 --- a/docs/adr/0019-local-bevy-render-timeout-patch.md +++ b/docs/adr/0019-local-bevy-render-timeout-patch.md @@ -1,18 +1,18 @@ -Status: Accepted +Status: Superseded by [ADR 0022](0022-bevy-0-19-upgrade.md) Context ======= The editor targets Linux desktop sessions where transient swapchain acquire timeouts can occur during heavy GPU work, compositor stalls, or driver hiccups. -In upstream `bevy_render` 0.18.1, `prepare_windows` treats +In upstream `bevy_render` 0.18.1, `prepare_windows` treated `wgpu::SurfaceError::Timeout` as a fatal path. That can crash the editor even though the next frame can often recover. Decision ======== -Patch `bevy_render` locally through `[patch.crates-io]` and vendor the scoped +For the Bevy 0.18 line, patch `bevy_render` locally through `[patch.crates-io]` and vendor the scoped crate copy under `third_party/bevy_render`. The patch is limited to transient surface acquire timeouts in `prepare_windows`: log the timeout and skip the frame instead of panicking. Other surface errors remain on their existing paths. @@ -23,6 +23,7 @@ Consequences The editor is more tolerant of transient Linux swapchain stalls while preserving the current Bevy API surface for the rest of the workspace. -The vendor patch must be reviewed during every Bevy upgrade and removed once -upstream behavior is sufficient. Keep the diff narrow; do not use the vendored -crate for unrelated render changes. +The Bevy 0.19 upgrade removed the active `[patch.crates-io]` entry for `bevy_render`. +The historical `third_party/bevy_render` copy was removed with the upgrade. Future +swapchain timeout work should start from Bevy 0.19/wgpu behavior rather than carrying +this patch forward by default. diff --git a/docs/adr/0022-bevy-0-19-upgrade.md b/docs/adr/0022-bevy-0-19-upgrade.md new file mode 100644 index 0000000..9279473 --- /dev/null +++ b/docs/adr/0022-bevy-0-19-upgrade.md @@ -0,0 +1,33 @@ +# ADR 0022: Bevy 0.19 Upgrade + +## Status + +Accepted + +## Context + +Avian 0.7 supports Bevy 0.19, which lets the workspace move off Bevy 0.18 while keeping the physics stack current. Bevy 0.19 also renamed the old dynamic scene/world serialization surface: runtime scene roots are `WorldAssetRoot`, saved dynamic scenes are `DynamicWorld`, and `.scn.ron` loading uses `WorldDeserializer`. + +Two ecosystem crates used by the editor still need local compatibility shims during the upgrade: + +- `transform-gizmo-bevy` 0.9 needs Bevy 0.19 render API updates. +- `bevy_ufbx` 0.18.1-rc.1 still targets Bevy 0.18 but is required for FBX import and scene-instance playback. + +The old local `bevy_render` timeout patch was specific to Bevy 0.18 window preparation behavior. +Bevy 0.19 also changed the atmosphere model: `Atmosphere` is a world-space planet entity selected by nearby cameras, while cameras opt in with `AtmosphereSettings`. + +## Decision + +Upgrade the workspace to Bevy 0.19 and Avian 0.7. Keep FBX support by vendoring `bevy_ufbx` under `third_party/bevy_ufbx` and patching it to emit Bevy 0.19 `WorldAsset` scene labels. Keep the existing local `transform-gizmo-bevy` patch and update it for Bevy 0.19 render pipeline and phase APIs. + +Remove the active `[patch.crates-io]` override for `bevy_render`. Runtime scene hydration now uses `WorldAssetRoot` for glTF/FBX model scenes and `DynamicWorldRoot` for saved `.scn.ron` prefabs. Scene save/load continues to write the project’s schema-wrapped `.scn.ron` format through `DynamicWorld`. + +Keep one stable project-owned `Atmosphere` entity for the active render profile. Editor and game cameras only receive `AtmosphereSettings`; camera-side `Atmosphere` components are stripped during render-stack sync to avoid unstable planet transforms and extraction jitter. + +## Consequences + +The dependency graph has one Bevy 0.19 line and one egui 0.34 line. FBX support remains available, but `third_party/bevy_ufbx` should be removed when upstream publishes a Bevy 0.19-compatible release. + +Docs and tooling should use `WorldAssetRoot` / `DynamicWorldRoot` for runtime hydrated scene components. Existing authoring components (`ModelRef`, `PrefabRef`, `StaticMeshRenderer`) remain the stable scene-file surface. + +Swapchain timeout behavior now follows upstream Bevy 0.19 and wgpu behavior. If Linux timeout crashes return, investigate current upstream code before reintroducing a local `bevy_render` patch. diff --git a/docs/editor/README.md b/docs/editor/README.md index 306627f..b8f5885 100644 --- a/docs/editor/README.md +++ b/docs/editor/README.md @@ -45,7 +45,7 @@ Docs for the in-process egui editor (`crates/editor/`). Update this index when a - **Static mesh renderer** — model drag/drop defaults to `ActorKind::StaticMesh + StaticMeshRenderer` using normalized artifacts under `assets/meshes/generated/`. Renderer slots store imported `EditorAssetRef` mesh/material references, not source FBX/glTF paths. `SceneInstance` placement keeps the legacy `ImportedModel + ModelRef` path for full scene playback. - **Brush authoring** — `ActorKind::Brush + BrushDesc` stores persisted convex blockout faces, validates authored geometry in the inspector and Window → Brush Diagnostics, and hydrates active valid brushes into generated mesh children. See [brushes.md](brushes.md). - **Draw Brush** — `B`, toolbar pencil, or command `brush.draw` enters a floor-polygon draw mode. LMB places snapped points, Backspace removes the last point, Enter locks the outline for height editing, mouse up/down adjusts height, and Enter/LMB creates additive prism brushes through history. Esc/right-click cancels. Simple concave outlines decompose into convex brush parts; self-intersections remain blocked. -- **Brush edit modes** — with a brush selected, `1`/`2`/`3`/`4` enter vertex, edge, face, and clip element modes. Element modes show brush handles in the viewport, own LMB picking, support Shift multi-select, show a mode badge, and Esc returns to object mode. Vertex/edge/face selections can be dragged on the viewport floor plane and commit undoable `SetBrush` edits; clip mode is a preview/selection mode until CSG split support lands. +- **Brush edit modes** — with a brush selected, `1`/`2`/`3`/`4` enter vertex, edge, face, and clip element modes. Element modes show brush handles in the viewport, own LMB picking, support Shift multi-select, show a mode badge, and Esc returns to object mode. Vertex/edge/face selections use the standard `W`/`E`/`R` gizmo at the element pivot and commit undoable `SetBrush` edits. Clip previews a bounds-based half-brush and commits with Enter; command-palette intersect, convex merge, and subtract operations use the same preview/commit lifecycle for conservative cuboid/prism blockout. - **Collider split** — imported mesh collision lives in a separate `ColliderDesc::StaticMesh` plus optional `RigidBodyDesc`; renderer slots own only render visibility, shadows, mesh, and material references. - **Material assets** — `MaterialAsset` RON (`label` + `MaterialDesc`) is shader-schema aware. Actor material edits live on the actor `MaterialDesc`; static mesh slot material refs are source/default selectors, and content-browser asset inspectors are the path for editing shared material assets. - **Prefab overrides** — `PrefabInstance.overrides_ron` stores transform/material/child-visibility overrides; inspector collapsible groups with **Apply** / **Revert** (replaces transform-only revert v1). @@ -55,7 +55,7 @@ Docs for the in-process egui editor (`crates/editor/`). Update this index when a - **World lighting** has two layers: project default sun/ambient settings, and optional scene-authored directional `LightDesc` overrides. **Scene → Lighting** menu and **Rendering** panel (Window) restore project sun or bake a scene directional from project settings. **Apply** in Project Settings updates ambient and sun illuminance live. - **Rendering** — `GiMode` + post-process volumes; see [rendering.md](rendering.md). **Window → Rendering** shows requested/effective active-camera stack, fallback reason, Solari eligibility, viewport GI badge, and volume HUD. - **Inspector** shows a responsive header, **Transform**, registered authoring component cards, and an Add Component footer (`actor_inspector`) that expands an inline search shelf above the button; only the component body scrolls. Rows wrap or stack inside narrow docks instead of forcing the panel wider, and legacy authoring sections use the same component-card shell. Component card carets collapse/expand for the current editor session, status dots toggle persisted active state, and triple-dot menus provide reset, copy/paste values, move, remove, and documentation slots. Inactive authoring components remain in the scene but are skipped by hydration/runtime queries, stripping their generated meshes, lights, physics, or post-process effects. The Add Component shelf is registry-driven with persistent search, category groups, descriptions, hydration hints, duplicate/conflict disabled states, and recommendation hints. `StaticMeshRenderer` uses concept-style slot cards with model thumbnails, imported mesh/source-material selectors, visibility, and shadow flags. Actor material authoring lives in the `Authoring Material` component, whose texture refs use Asset Browser picker/drop controls. Empty source material refs display the inherited source material from the generated static mesh artifact as **Source default**; selector **Browse** assigns explicit normalized imported sub-assets, **Locate** focuses the owning asset in the Asset Browser, and **Clear** returns optional material slots to the inherited source default. `ColliderDesc` and `RigidBodyDesc` are separate component cards. Game crates add sections via `ActorInspectorSection` + `game::editor_ext::actor_inspector_section_ids()`. -- **Command palette** (`Ctrl+P`): focuses the filter on open, selects the first filtered command, supports arrow-key selection and Enter to run. Commands include `scene.reset_lighting`, `brush.draw`, `brush.subtract`, `brush.intersect`, `brush.merge_convex`, `rendering.create_volume`, `rendering.select_volumes_at_camera`, `rendering.focus_active_volumes`, `selection.group`, `selection.focus`, `selection.reset_transform`, and play commands. +- **Command palette** (`Ctrl+P`): opens centered, clears and focuses the filter, selects the first result, searches both human-facing labels and stable command IDs, and supports arrow-key selection and Enter to run. Commands include `scene.reset_lighting`, `brush.draw`, `brush.subtract`, `brush.intersect`, `brush.merge_convex`, `rendering.create_volume`, `rendering.select_volumes_at_camera`, `rendering.focus_active_volumes`, `selection.group`, `selection.focus`, `selection.reset_transform`, and play commands. - **Editor input routing** gives egui text fields first claim on keyboard input. Viewport shortcut keys require viewport pointer focus and are suspended while RMB/MMB camera navigation is active. - **ActorKind** is required on saved level objects (schema v2). Save strips hydrated ECS before writing `.scn.ron`. - **Scene visualizers** expose actor root icons, colliders, lights, player spawns, prefab/model anchors, Project Sun, and runtime player/camera markers without changing saved scene data. Viewport options include actor icon size and transform gizmo size sliders. diff --git a/docs/editor/architecture.md b/docs/editor/architecture.md index 8fafc12..d082a51 100644 --- a/docs/editor/architecture.md +++ b/docs/editor/architecture.md @@ -59,6 +59,12 @@ Workflow: run `cargo watch … build -p game_hot` in one terminal, `cargo run -p Do not add egui or editor-only state to the `settings` crate. +The active project root is a startup boundary because `AssetPlugin.file_path`, project settings, +the asset catalog, and the initial level must agree before editor systems initialize. The editor +currently starts in the repository project root. Cross-project selection and scaffolding belong in +the planned BS-JD-001 launcher before the main app is built; do not reintroduce in-process project +switching that only changes settings metadata while leaving the AssetServer on the old root. + ## Camera and viewport model See [ADR 0014](../adr/0014-unified-viewport-model.md) for the unified viewport decision. @@ -115,7 +121,7 @@ The egui layer lives under `crates/editor/src/ui/`: | `diagnostics.rs` | Detailed stats (Window → Diagnostics, Asset Browser footer) | | `layout.rs` | Dock layout RON persistence in `editor_prefs.ron` | -The Viewport fills the tab (no inline help text). Gizmo/grid/play controls sit on a semi-transparent overlay, and `G` hides editor-only overlays for a clean game view. A blue status bar shows scene path, mode, selection count, and history. Dock layout restores from `~/.config/bevy-fps/editor_prefs.ron` on startup; **View → Reset Layout** restores defaults. +The Viewport fills the tab (no inline help text). Gizmo/grid/play controls sit on a semi-transparent overlay, and `G` hides editor-only overlays for a clean game view. A blue status bar prioritizes `SceneIo.status` feedback and also shows scene path/dirty state, mode, selection count, history, and the active operator. Long feedback is truncated responsively and available in a hover tooltip. Dock layout restores from `~/.config/bevy-fps/editor_prefs.ron` on startup; **View → Reset Layout** restores defaults. **Follow-up (Phase 4b):** hierarchy expand/collapse persistence, inspector search, and richer per-asset previews. @@ -146,7 +152,7 @@ On play enter/exit the editor snapshots **player sim state only** (transform, ve 2. `EditorOnly` entities (cameras, helpers) are filtered from hierarchy and save; visualizer proxies can be picked but resolve back to source entities. 3. Player placement is stored as `PlayerSpawn`; the runtime `Player` is never serialized. 4. `SceneIo` writes native Bevy dynamic scenes under `assets/levels/`, routed through the Bevy-free `scene::document::SceneDocument` seam. The document layer preserves the current RON storage while exposing stable actor/component document concepts and patch vocabulary for tools. -5. Hydration systems in `game` spawn meshes, colliders, lights, and static mesh renderer parts at runtime. `ActorId` and `ComponentInstanceId` are the persisted identities; raw Bevy `Entity` IDs are runtime-only and must not become tool-facing document IDs. +5. Hydration systems in `game` spawn meshes, colliders, lights, and static mesh renderer parts at runtime. Level-object roots are initialized with Bevy visibility hierarchy components before generated children are attached, so authoring `EditorVisibility` participates in normal visibility propagation. `ActorId` and `ComponentInstanceId` are the persisted identities; raw Bevy `Entity` IDs are runtime-only and must not become tool-facing document IDs. 6. Brush actors store authored convex geometry as `BrushDesc`; hydration rebuilds generated mesh children from those faces and strips the children before save. ## Model import (glTF + FBX) @@ -156,7 +162,7 @@ On play enter/exit the editor snapshots **player sim state only** (transform, ve - **Browser subassets:** model rows can expand into a content shelf backed by the generated manifest. Mesh subassets can be selected or dragged into the viewport as `StaticMeshRenderer` actors, material subassets expose source defaults, and texture dependencies can be applied to selected actors without treating glTF and FBX differently. The thumbnail studio renders each generated model/subasset/material preview into its own render target before registering it in the cache so later thumbnails cannot overwrite earlier cache entries. - **Default placement:** model drag/drop creates `ActorKind::StaticMesh + StaticMeshRenderer`. Renderer slots reference imported `EditorAssetRef` values (`asset_id` + `sub_asset_id`) and hydration resolves those through the generated artifact. `SingleActor` hierarchy mode stores all parts in one renderer; `SourceHierarchy` creates a root with child static mesh actors. - **Collision:** when collider generation is enabled, placement adds `RigidBodyDesc` and `ColliderDesc::StaticMesh` using the same imported mesh refs. Renderer slots no longer own collider state. -- **Scene-instance placement:** asset details can switch placement to `SceneInstance`; that keeps `ImportedModel + ModelRef`, which hydrates to `SceneRoot`. glTF uses `GltfAssetLabel::Scene`; FBX uses `bevy_ufbx` (`path#SceneN`). `FbxPlugin` is registered in `GamePlugin`. +- **Scene-instance placement:** asset details can switch placement to `SceneInstance`; that keeps `ImportedModel + ModelRef`, which hydrates to `WorldAssetRoot`. glTF uses `GltfAssetLabel::Scene`; FBX uses `bevy_ufbx` (`path#SceneN`). `FbxPlugin` is registered in `GamePlugin`. - **Limitations:** Binary FBX only (not ASCII). Static mesh placement stores animation/skinning/camera/light metadata and warnings but does not play those features; use Scene Instance for full-scene playback. ## Undo / history @@ -181,7 +187,7 @@ PIE stop restores player simulation state only; authored `LevelObject` edits mad | `viewport/` | Camera, selection, gizmos, render views, panel settings | | `play/` | PIE session, editor mode, net editor profiles | | `assets/` | Catalog, asset DB, static mesh artifacts, prefab overrides | -| `project/` | Workspace, project I/O, settings UI | +| `project/` | Project I/O and settings UI | | `ext/` | Command palette, BRP, game panel adapters | | `history/` | Undo commands + plugin | | `ui/` | egui dock shell | @@ -192,7 +198,7 @@ Shared scene schema lives in `crates/scene` (stamp/migrate/validate on save, loa The unified viewport renders through an HDR offscreen target (`render_target.rs`). When the panel target is missing (egui reflow), cameras must strip atmosphere/post-FX before targeting the swapchain. User-authored HDR may be disabled, but atmosphere and effective Solari still force `Hdr` on the active viewport camera because Bevy sky/Solari pipelines are not safe to carry across SDR targets. `render_view::sync_project_render_view` runs in **PostUpdate** after RTT target assignment and reapplies the project FX stack whenever the active panel target handle changes (startup, resize), not only on settings Apply. Project Settings uses a **draft buffer**; **Apply** commits after render targets resync. -The workspace patches `bevy_render` under `third_party/bevy_render` so Linux `wgpu::SurfaceError::Timeout` during swapchain texture acquisition logs and skips the frame instead of panicking in `prepare_windows`. Keep this patch scoped to transient surface acquire timeouts and re-evaluate it during Bevy upgrades. +Bevy 0.19 removed the workspace's prior local `bevy_render` swapchain-timeout patch. The only active Bevy-adjacent patches are compatibility shims for ecosystem crates: `transform-gizmo-bevy` and `bevy_ufbx` under `third_party/`. ## Extensibility (phase 6) diff --git a/docs/editor/brp.md b/docs/editor/brp.md index 6cbfd6b..2f4233f 100644 --- a/docs/editor/brp.md +++ b/docs/editor/brp.md @@ -15,17 +15,17 @@ Launch the editor with dev features as usual: cargo run -p editor --features dev ``` -Default HTTP endpoint follows Bevy 0.18 remote defaults (port **15702**, routes under `/remote/`). +Default HTTP endpoint follows Bevy 0.19 remote defaults (port **15702**, routes under `/remote/`). ## Authoring-only mutation policy -**BRP and automation must mutate authoring components only** — the same allowlist used on scene save in `scene_io.rs`. Hydration rebuilds runtime ECS (`Mesh3d`, Bevy lights, physics, `SceneRoot`, etc.) on the next frame. +**BRP and automation must mutate authoring components only** — the same allowlist used on scene save in `scene_io.rs`. Hydration rebuilds runtime ECS (`Mesh3d`, Bevy lights, physics, `WorldAssetRoot`, etc.) on the next frame. | Allowed (examples) | Forbidden on level objects | |--------------------|----------------------------| | `Transform`, `Name`, `ActorKind` | `PointLight`, `SpotLight`, `DirectionalLight` | | `Primitive`, `StaticMeshRenderer`, `MaterialDesc`, `MaterialOverride`, `LightDesc` | `Mesh3d`, `MeshMaterial3d`, `RigidBody`, `Collider` | -| `ModelRef`, `RigidBodyDesc`, `ColliderDesc`, legacy `PhysicsBody`, gameplay markers | `SceneRoot`, generated static mesh children, internal visibility types | +| `ModelRef`, `RigidBodyDesc`, `ColliderDesc`, legacy `PhysicsBody`, gameplay markers | `WorldAssetRoot`, generated static mesh children, internal visibility types | | `PostProcessVolumeDesc` | Runtime post-process components on cameras | Prefer: @@ -107,7 +107,7 @@ External automation can enqueue the same names through the editor command queue Validate committed levels without the editor UI: ```bash -cargo run -p xtask --bin validate-levels +cargo validate-levels ``` This runs `scene::validate_level_file` (schema migrate + **authoring-only** component check). diff --git a/docs/editor/brushes.md b/docs/editor/brushes.md index f684485..6e98cbc 100644 --- a/docs/editor/brushes.md +++ b/docs/editor/brushes.md @@ -16,13 +16,13 @@ Brushes are persisted blockout geometry stored as `ActorKind::Brush + BrushDesc` ## Element Modes -With a brush actor selected, `1`/`2`/`3`/`4` enter vertex, edge, face, and clip modes. Element modes show viewport overlays for the selected brush, route LMB to element picking, support Shift+LMB multi-select, and display a `Brush: ` badge. `Esc` returns to object mode. +With a brush actor selected, `1`/`2`/`3`/`4` enter vertex, edge, face, and clip modes. Element modes show viewport overlays for the selected brush, route LMB to element picking, support Shift+LMB multi-select, and display a brush edit banner with the active mode, selected element count, and available controls. Actor transform gizmos are replaced by a brush element transform gizmo at the selected element pivot while a brush element mode is active. `Esc` returns to object mode. -Vertex, edge, and face selections can be dragged on the viewport floor plane. The editor moves matching duplicated corner vertices across all brush faces so simple cube/prism brushes stay welded, recomputes face planes, validates the result, and commits one undoable brush edit when the drag releases. Invalid drag results are rejected with operator status text and reverted to the pre-drag brush. +Vertex, edge, and face selections are transformed through the standard viewport gizmo. `W`, `E`, and `R` choose translate, rotate, and scale for the selected brush elements. The editor transforms matching duplicated corner vertices across all brush faces so simple cube/prism brushes stay welded, recomputes face planes, validates the result, and commits one undoable brush edit when the gizmo releases. Invalid gizmo results are rejected with operator status text and reverted to the pre-edit brush. Clip mode is a bounds-based MVP for cuboid/prism blockout. Select one face in Clip mode to preview the half-brush result along that face's dominant normal axis; the magenta wireframe shows the pending trimmed bounds. **Enter** commits the clip through history and **Esc** returns to Object mode. -When one or more faces are selected in Face mode, the Brush inspector shows selected-face controls for UV offset, UV scale, UV rotation, material ref, and texture ref. Material and texture rows use Asset Browser-aware picker controls with clear, locate, browse, and compatible drag/drop assignment. These fields edit the persisted `BrushFaceDesc` data through undoable brush updates. Face refs cache loadable source paths when available; hydration groups faces by material/texture path and spawns separate generated mesh children for those batches. +When one or more faces are selected in Face mode, the Brush inspector shows selected-face controls for UV offset, UV scale, UV rotation in degrees, material ref, and texture ref. Material and texture rows use Asset Browser-aware picker controls with clear, locate, browse, and compatible drag/drop assignment. These fields edit the persisted `BrushFaceDesc` data through undoable brush updates and update hydrated brush mesh UVs. Face refs cache loadable source paths when available; hydration groups faces by material/texture path and spawns separate generated mesh children for those batches. Non-finite UV offset/scale/rotation values are reported as brush diagnostics and sanitized during hydration so invalid authoring data does not emit NaN mesh UVs. ## Boolean Operations diff --git a/docs/editor/release-notes.md b/docs/editor/release-notes.md index 9be9b6b..9fabf4c 100644 --- a/docs/editor/release-notes.md +++ b/docs/editor/release-notes.md @@ -13,7 +13,7 @@ - PIE step while paused (F7) - Net editor state + determinism HUD stub - `EditorPlugin` / `EditorCommand` extensibility surface -- `cargo run -p validate-levels --manifest-path xtask/Cargo.toml` +- `cargo validate-levels` ## Gates diff --git a/docs/editor/rendering.md b/docs/editor/rendering.md index 731e0f9..8615746 100644 --- a/docs/editor/rendering.md +++ b/docs/editor/rendering.md @@ -8,6 +8,8 @@ Step-by-step guide for game teams managing look development without Rust changes 2. **Post-process volumes** — scene actors with local overrides (rooms, biomes, boss arenas). 3. **Requested profile** — the active viewport camera always uses `ActiveCameraRenderProfile` (project + blended volumes), whether it is the editor fly camera or the possessed player camera. 4. **Effective stack** — `EffectiveRenderStack` records requested GI, effective GI, and fallback reason for renderer sync and diagnostics. +5. **Atmosphere ownership** — Bevy 0.19 uses one stable project `Atmosphere` entity; active cameras only carry `AtmosphereSettings` when the profile enables sky rendering. +6. **Antialiasing ownership** — all 3D cameras spawn with MSAA disabled. The project rendering profile owns temporal antialiasing, and Bevy's deferred path does not support MSAA. See [ADR 0013](../adr/0013-rendering-tiers-and-post-process-volumes.md) for tier policy and crate boundaries, and [ADR 0016](../adr/0016-unified-rendering-contract.md) for the requested/effective stack contract. @@ -60,7 +62,7 @@ Viewport overlays: **GI badge** (Forward / Solari), **volume HUD** when inside a | Forward | Bevy Forward PBR with HDR/TAA/SSAO/atmosphere/shadows | | Solari | Request Solari; the camera attaches Bevy Solari when RT support is available | -Solari in Bevy 0.18 samples directional lights and emissive meshes. Point and spot lights require the normal PBR light pass, so `LightDesc` point/spot components are runtime-disabled while Solari is active. The component remains in the scene, the inspector shows an unsupported message, and hydration strips/skips the Bevy `PointLight` / `SpotLight` until authors switch the light to Directional or switch GI to Forward. The viewport resolves Hybrid Auto through **`EffectiveRenderStack`**: requested Solari keeps project meshes eligible for raytracing and attaches Bevy Solari when RT support is available. Effective GI falls back to **Forward** only when RT is unavailable. Effective Solari forces an HDR camera target even if project HDR is off, because Bevy Solari writes to the main texture through a storage binding and sRGB textures are invalid for that use. Primitive hydration generates tangents so built-in boxes/spheres can enter Solari BLAS/TLAS; imported/custom meshes still need TriangleList geometry, POSITION/NORMAL/UV_0/TANGENT attributes, and U32 indices. The Rendering panel reports the exact fallback reason, Solari-compatible asset counts, bind-group state, and compatible light counters. Set `BEVY_FPS_FORCE_SOLARI=1` to force RT feature probing in dev (see README troubleshooting). +Solari in Bevy 0.19 samples directional lights and emissive meshes. Point and spot lights require the normal PBR light pass, so `LightDesc` point/spot components are runtime-disabled while Solari is active. The component remains in the scene, the inspector shows an unsupported message, and hydration strips/skips the Bevy `PointLight` / `SpotLight` until authors switch the light to Directional or switch GI to Forward. The viewport resolves Hybrid Auto through **`EffectiveRenderStack`**: requested Solari keeps project meshes eligible for raytracing and attaches Bevy Solari when RT support is available. Effective GI falls back to **Forward** only when RT is unavailable. Effective Solari forces an HDR camera target even if project HDR is off, because Bevy Solari writes to the main texture through a storage binding and sRGB textures are invalid for that use. Primitive hydration generates tangents so built-in boxes/spheres can enter Solari BLAS/TLAS; imported/custom meshes still need TriangleList geometry, POSITION/NORMAL/UV_0/TANGENT attributes, and U32 indices. The Rendering panel reports the exact fallback reason, Solari-compatible asset counts, bind-group state, and compatible light counters. Set `BEVY_FPS_FORCE_SOLARI=1` to force RT feature probing in dev (see README troubleshooting). ## Emissive lighting @@ -103,5 +105,6 @@ Open `assets/levels/rendering_showcase.scn.ron` for fog, exposure, and vignette | GI badge shows Forward unexpectedly | Solari RT wgpu features unavailable; Rendering → Active Camera tab | | Volume has no effect | Camera inside AABB? Priority vs other volumes? Overrides enabled? | | Custom FX missing | RON path in volume matches `assets/post_fx/`; shader path in RON | +| Atmosphere flickers or artifacts | Confirm only the project `Atmosphere` entity exists and active cameras have `AtmosphereSettings`, not camera-side `Atmosphere` components. | Command palette: `rendering.select_volumes_at_camera`, `rendering.focus_active_volumes`. diff --git a/docs/editor/roadmap.md b/docs/editor/roadmap.md index 044950c..a4f1992 100644 --- a/docs/editor/roadmap.md +++ b/docs/editor/roadmap.md @@ -39,7 +39,7 @@ Phased work for the in-process editor (`crates/editor`). Status reflects the cur | `ProjectWorkspace` resource | Done | Root, settings path, dirty flags | | User prefs (`~/.config/bevy-fps/editor_prefs.ron`) | Done | Recent levels, load on startup | | Window title from project + scene | Done | `project_io::window_title` | -| New/Open project menu | Done | File → Project; distinct from level New/Open | +| Project launcher and scaffolding | Planned | BS-JD-001; project selection must happen before AssetServer startup, so the former in-process New/Open menu was removed | | Dock layout persistence | Done | RON in `editor_prefs.ron`; View → Reset Layout | ## Phase 2 — Viewport excellence @@ -101,7 +101,7 @@ Phased work for the in-process editor (`crates/editor`). Status reflects the cur | Item | Status | Notes | |------|--------|-------| | Dark pro theme + Phosphor icons | Done | `ui/theme.rs`, `ui/fonts.rs`, high-contrast selection states, overlay toolbars | -| Fixed status bar | Done | Replaces Status dock tab | +| Fixed status bar | Done | Replaces Status dock tab; exposes scene I/O and operator feedback alongside scene/mode/selection/history | | Viewport overlay toolbar | Done | Gizmo/grid/focus/options/game-view toggle (play moved to main toolbar) | | Main toolbar transport | Done | Centered Play/Pause/Stop/Eject; taller bar (~52px) | | Pause in PIE | Done | `PlayPaused` freezes sim while staying in Play (`F6`) | diff --git a/third_party/bevy_render/Cargo.toml b/third_party/bevy_render/Cargo.toml deleted file mode 100644 index 86032bb..0000000 --- a/third_party/bevy_render/Cargo.toml +++ /dev/null @@ -1,296 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2024" -name = "bevy_render" -version = "0.18.1" -build = false -autolib = false -autobins = false -autoexamples = false -autotests = false -autobenches = false -description = "Provides rendering functionality for Bevy Engine" -homepage = "https://bevy.org" -readme = "README.md" -keywords = ["bevy"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/bevyengine/bevy" -resolver = "2" - -[package.metadata.docs.rs] -rustdoc-args = [ - "-Zunstable-options", - "--generate-link-to-definition", -] -all-features = true - -[features] -ci_limits = [] -decoupled_naga = ["bevy_shader/decoupled_naga"] -detailed_trace = [] -gles = ["wgpu/gles"] -morph = ["bevy_mesh/morph"] -multi_threaded = ["bevy_tasks/multi_threaded"] -raw_vulkan_init = ["wgpu/vulkan"] -serialize = ["bevy_mesh/serialize"] -shader_format_spirv = [ - "bevy_shader/shader_format_spirv", - "wgpu/spirv", -] -spirv_shader_passthrough = ["wgpu/spirv"] -statically-linked-dxc = ["wgpu/static-dxc"] -trace = ["profiling"] -tracing-tracy = ["dep:tracy-client"] -vulkan-portability = ["wgpu/vulkan-portability"] -webgl = ["wgpu/webgl"] -webgpu = ["wgpu/webgpu"] - -[lib] -name = "bevy_render" -path = "src/lib.rs" - -[dependencies.async-channel] -version = "2.3.0" - -[dependencies.bevy_app] -version = "0.18.0" - -[dependencies.bevy_asset] -version = "0.18.0" - -[dependencies.bevy_camera] -version = "0.18.0" - -[dependencies.bevy_color] -version = "0.18.0" -features = [ - "serialize", - "wgpu-types", -] - -[dependencies.bevy_derive] -version = "0.18.0" - -[dependencies.bevy_diagnostic] -version = "0.18.0" - -[dependencies.bevy_ecs] -version = "0.18.0" - -[dependencies.bevy_encase_derive] -version = "0.18.0" - -[dependencies.bevy_image] -version = "0.18.0" - -[dependencies.bevy_math] -version = "0.18.0" - -[dependencies.bevy_mesh] -version = "0.18.0" - -[dependencies.bevy_platform] -version = "0.18.0" -features = [ - "std", - "serialize", -] -default-features = false - -[dependencies.bevy_reflect] -version = "0.18.0" - -[dependencies.bevy_render_macros] -version = "0.18.0" - -[dependencies.bevy_shader] -version = "0.18.0" - -[dependencies.bevy_tasks] -version = "0.18.0" - -[dependencies.bevy_time] -version = "0.18.0" - -[dependencies.bevy_transform] -version = "0.18.0" - -[dependencies.bevy_utils] -version = "0.18.0" - -[dependencies.bevy_window] -version = "0.18.0" - -[dependencies.bitflags] -version = "2" - -[dependencies.bytemuck] -version = "1.5" -features = [ - "derive", - "must_cast", -] - -[dependencies.derive_more] -version = "2" -features = ["from"] -default-features = false - -[dependencies.downcast-rs] -version = "2" -features = ["std"] -default-features = false - -[dependencies.encase] -version = "0.12" - -[dependencies.fixedbitset] -version = "0.5" - -[dependencies.glam] -version = "0.30.7" -features = [ - "std", - "encase", -] -default-features = false - -[dependencies.image] -version = "0.25.2" -default-features = false - -[dependencies.indexmap] -version = "2" - -[dependencies.naga] -version = "27" -features = ["wgsl-in"] - -[dependencies.nonmax] -version = "0.5" - -[dependencies.offset-allocator] -version = "0.2" - -[dependencies.profiling] -version = "1" -features = ["profile-with-tracing"] -optional = true - -[dependencies.smallvec] -version = "1" -features = ["const_new"] -default-features = false - -[dependencies.thiserror] -version = "2" -default-features = false - -[dependencies.tracing] -version = "0.1" -features = ["std"] -default-features = false - -[dependencies.tracy-client] -version = "0.18.3" -optional = true - -[dependencies.variadics_please] -version = "1.1" - -[dependencies.wgpu] -version = "27" -features = [ - "wgsl", - "dx12", - "metal", - "vulkan", - "naga-ir", - "fragile-send-sync-non-atomic-wasm", -] -default-features = false - -[dev-dependencies.proptest] -version = "1" - -[target.'cfg(all(target_arch = "wasm32", target_feature = "atomics"))'.dependencies.send_wrapper] -version = "0.6.0" - -[target.'cfg(target_arch = "wasm32")'.dependencies.bevy_app] -version = "0.18.0" -features = ["web"] -default-features = false - -[target.'cfg(target_arch = "wasm32")'.dependencies.bevy_platform] -version = "0.18.0" -features = ["web"] -default-features = false - -[target.'cfg(target_arch = "wasm32")'.dependencies.bevy_reflect] -version = "0.18.0" -features = ["web"] -default-features = false - -[target.'cfg(target_arch = "wasm32")'.dependencies.js-sys] -version = "0.3.83" - -[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen] -version = "0.2" - -[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] -version = "0.3.67" -features = [ - "Blob", - "Document", - "Element", - "HtmlElement", - "Node", - "Url", - "Window", -] - -[lints.clippy] -alloc_instead_of_core = "warn" -allow_attributes = "warn" -allow_attributes_without_reason = "warn" -doc_markdown = "warn" -manual_let_else = "warn" -match_same_arms = "warn" -needless_lifetimes = "allow" -nonstandard_macro_braces = "warn" -print_stderr = "warn" -print_stdout = "warn" -ptr_as_ptr = "warn" -ptr_cast_constness = "warn" -redundant_closure_for_method_calls = "warn" -redundant_else = "warn" -ref_as_ptr = "warn" -semicolon_if_nothing_returned = "warn" -std_instead_of_alloc = "warn" -std_instead_of_core = "warn" -too_long_first_doc_paragraph = "allow" -too_many_arguments = "allow" -type_complexity = "allow" -undocumented_unsafe_blocks = "warn" -unwrap_or_default = "warn" - -[lints.rust] -missing_docs = "warn" -unsafe_code = "deny" -unsafe_op_in_unsafe_fn = "warn" -unused_qualifications = "warn" - -[lints.rust.unexpected_cfgs] -level = "warn" -priority = 0 -check-cfg = ["cfg(docsrs_dep)"] diff --git a/third_party/bevy_render/Cargo.toml.orig b/third_party/bevy_render/Cargo.toml.orig deleted file mode 100644 index e20e2e5..0000000 --- a/third_party/bevy_render/Cargo.toml.orig +++ /dev/null @@ -1,155 +0,0 @@ -[package] -name = "bevy_render" -version = "0.18.1" -edition = "2024" -description = "Provides rendering functionality for Bevy Engine" -homepage = "https://bevy.org" -repository = "https://github.com/bevyengine/bevy" -license = "MIT OR Apache-2.0" -keywords = ["bevy"] - -[features] -# Bevy users should _never_ turn this feature on. -# -# Bevy/wgpu developers can turn this feature on to test a newer version of wgpu without needing to also update naga_oil. -# -# When turning this feature on, you can add the following to bevy/Cargo.toml (not this file), and then run `cargo update`: -# [patch.crates-io] -# wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "..." } -# wgpu-core = { git = "https://github.com/gfx-rs/wgpu", rev = "..." } -# wgpu-hal = { git = "https://github.com/gfx-rs/wgpu", rev = "..." } -# wgpu-types = { git = "https://github.com/gfx-rs/wgpu", rev = "..." } -decoupled_naga = ["bevy_shader/decoupled_naga"] - -multi_threaded = ["bevy_tasks/multi_threaded"] - -morph = ["bevy_mesh/morph"] - -shader_format_spirv = ["bevy_shader/shader_format_spirv", "wgpu/spirv"] - -# Enable SPIR-V shader passthrough -spirv_shader_passthrough = ["wgpu/spirv"] - -# Statically linked DXC shader compiler for DirectX 12 -# TODO: When wgpu switches to DirectX 12 instead of Vulkan by default on windows, make this a default feature -statically-linked-dxc = ["wgpu/static-dxc"] - -# Forces the wgpu instance to be initialized using the raw Vulkan HAL, enabling additional configuration -raw_vulkan_init = ["wgpu/vulkan"] - -trace = ["profiling"] -tracing-tracy = ["dep:tracy-client"] -ci_limits = [] -webgl = ["wgpu/webgl"] -webgpu = ["wgpu/webgpu"] -vulkan-portability = ["wgpu/vulkan-portability"] -gles = ["wgpu/gles"] -detailed_trace = [] -## Adds serialization support through `serde`. -serialize = ["bevy_mesh/serialize"] - -[dependencies] -# bevy -bevy_app = { path = "../bevy_app", version = "0.18.0" } -bevy_asset = { path = "../bevy_asset", version = "0.18.0" } -bevy_color = { path = "../bevy_color", version = "0.18.0", features = [ - "serialize", - "wgpu-types", -] } -bevy_derive = { path = "../bevy_derive", version = "0.18.0" } -bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.18.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.18.0" } -bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.18.0" } -bevy_math = { path = "../bevy_math", version = "0.18.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.18.0" } -bevy_render_macros = { path = "macros", version = "0.18.0" } -bevy_time = { path = "../bevy_time", version = "0.18.0" } -bevy_transform = { path = "../bevy_transform", version = "0.18.0" } -bevy_window = { path = "../bevy_window", version = "0.18.0" } -bevy_utils = { path = "../bevy_utils", version = "0.18.0" } -bevy_tasks = { path = "../bevy_tasks", version = "0.18.0" } -bevy_image = { path = "../bevy_image", version = "0.18.0" } -bevy_mesh = { path = "../bevy_mesh", version = "0.18.0" } -bevy_camera = { path = "../bevy_camera", version = "0.18.0" } -bevy_shader = { path = "../bevy_shader", version = "0.18.0" } -bevy_platform = { path = "../bevy_platform", version = "0.18.0", default-features = false, features = [ - "std", - "serialize", -] } - -# rendering -image = { version = "0.25.2", default-features = false } - -# misc -# `fragile-send-sync-non-atomic-wasm` feature means we can't use Wasm threads for rendering -# It is enabled for now to avoid having to do a significant overhaul of the renderer just for wasm. -# When the 'atomics' feature is enabled `fragile-send-sync-non-atomic` does nothing -# and Bevy instead wraps `wgpu` types to verify they are not used off their origin thread. -wgpu = { version = "27", default-features = false, features = [ - "wgsl", - "dx12", - "metal", - "vulkan", - "naga-ir", - "fragile-send-sync-non-atomic-wasm", -] } -naga = { version = "27", features = ["wgsl-in"] } -bytemuck = { version = "1.5", features = ["derive", "must_cast"] } -downcast-rs = { version = "2", default-features = false, features = ["std"] } -thiserror = { version = "2", default-features = false } -derive_more = { version = "2", default-features = false, features = ["from"] } -encase = "0.12" -glam = { version = "0.30.7", default-features = false, features = [ - "std", - "encase", -] } -# For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans. -profiling = { version = "1", features = [ - "profile-with-tracing", -], optional = true } -async-channel = "2.3.0" -nonmax = "0.5" -smallvec = { version = "1", default-features = false, features = ["const_new"] } -offset-allocator = "0.2" -variadics_please = "1.1" -tracing = { version = "0.1", default-features = false, features = ["std"] } -tracy-client = { version = "0.18.3", optional = true } -indexmap = { version = "2" } -fixedbitset = { version = "0.5" } -bitflags = "2" - -[target.'cfg(all(target_arch = "wasm32", target_feature = "atomics"))'.dependencies] -send_wrapper = { version = "0.6.0" } - -[dev-dependencies] -proptest = "1" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -js-sys = "0.3.83" -web-sys = { version = "0.3.67", features = [ - 'Blob', - 'Document', - 'Element', - 'HtmlElement', - 'Node', - 'Url', - 'Window', -] } -wasm-bindgen = "0.2" -# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption. -bevy_app = { path = "../bevy_app", version = "0.18.0", default-features = false, features = [ - "web", -] } -bevy_platform = { path = "../bevy_platform", version = "0.18.0", default-features = false, features = [ - "web", -] } -bevy_reflect = { path = "../bevy_reflect", version = "0.18.0", default-features = false, features = [ - "web", -] } - -[lints] -workspace = true - -[package.metadata.docs.rs] -rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] -all-features = true diff --git a/third_party/bevy_render/README.md b/third_party/bevy_render/README.md deleted file mode 100644 index 0106a5c..0000000 --- a/third_party/bevy_render/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Bevy Render - -[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license) -[![Crates.io](https://img.shields.io/crates/v/bevy_render.svg)](https://crates.io/crates/bevy_render) -[![Downloads](https://img.shields.io/crates/d/bevy_render.svg)](https://crates.io/crates/bevy_render) -[![Docs](https://docs.rs/bevy_render/badge.svg)](https://docs.rs/bevy_render/latest/bevy_render/) -[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) diff --git a/third_party/bevy_render/src/alpha.rs b/third_party/bevy_render/src/alpha.rs deleted file mode 100644 index dd74881..0000000 --- a/third_party/bevy_render/src/alpha.rs +++ /dev/null @@ -1,62 +0,0 @@ -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; - -// TODO: add discussion about performance. -/// Sets how a material's base color alpha channel is used for transparency. -#[derive(Debug, Default, Reflect, Copy, Clone, PartialEq)] -#[reflect(Default, Debug, Clone)] -pub enum AlphaMode { - /// Base color alpha values are overridden to be fully opaque (1.0). - #[default] - Opaque, - /// Reduce transparency to fully opaque or fully transparent - /// based on a threshold. - /// - /// Compares the base color alpha value to the specified threshold. - /// If the value is below the threshold, - /// considers the color to be fully transparent (alpha is set to 0.0). - /// If it is equal to or above the threshold, - /// considers the color to be fully opaque (alpha is set to 1.0). - Mask(f32), - /// The base color alpha value defines the opacity of the color. - /// Standard alpha-blending is used to blend the fragment's color - /// with the color behind it. - Blend, - /// Similar to [`AlphaMode::Blend`], however assumes RGB channel values are - /// [premultiplied](https://en.wikipedia.org/wiki/Alpha_compositing#Straight_versus_premultiplied). - /// - /// For otherwise constant RGB values, behaves more like [`AlphaMode::Blend`] for - /// alpha values closer to 1.0, and more like [`AlphaMode::Add`] for - /// alpha values closer to 0.0. - /// - /// Can be used to avoid “border” or “outline” artifacts that can occur - /// when using plain alpha-blended textures. - Premultiplied, - /// Spreads the fragment out over a hardware-dependent number of sample - /// locations proportional to the alpha value. This requires multisample - /// antialiasing; if MSAA isn't on, this is identical to - /// [`AlphaMode::Mask`] with a value of 0.5. - /// - /// Alpha to coverage provides improved performance and better visual - /// fidelity over [`AlphaMode::Blend`], as Bevy doesn't have to sort objects - /// when it's in use. It's especially useful for complex transparent objects - /// like foliage. - /// - /// [alpha to coverage]: https://en.wikipedia.org/wiki/Alpha_to_coverage - AlphaToCoverage, - /// Combines the color of the fragments with the colors behind them in an - /// additive process, (i.e. like light) producing lighter results. - /// - /// Black produces no effect. Alpha values can be used to modulate the result. - /// - /// Useful for effects like holograms, ghosts, lasers and other energy beams. - Add, - /// Combines the color of the fragments with the colors behind them in a - /// multiplicative process, (i.e. like pigments) producing darker results. - /// - /// White produces no effect. Alpha values can be used to modulate the result. - /// - /// Useful for effects like stained glass, window tint film and some colored liquids. - Multiply, -} - -impl Eq for AlphaMode {} diff --git a/third_party/bevy_render/src/batching/gpu_preprocessing.rs b/third_party/bevy_render/src/batching/gpu_preprocessing.rs deleted file mode 100644 index 71ba6a8..0000000 --- a/third_party/bevy_render/src/batching/gpu_preprocessing.rs +++ /dev/null @@ -1,2187 +0,0 @@ -//! Batching functionality when GPU preprocessing is in use. - -use core::{any::TypeId, marker::PhantomData, mem}; - -use bevy_app::{App, Plugin}; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{ - prelude::Entity, - query::{Has, With}, - resource::Resource, - schedule::IntoScheduleConfigs as _, - system::{Query, Res, ResMut, StaticSystemParam}, - world::{FromWorld, World}, -}; -use bevy_encase_derive::ShaderType; -use bevy_math::UVec4; -use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; -use bevy_tasks::ComputeTaskPool; -use bevy_utils::{default, TypeIdMap}; -use bytemuck::{Pod, Zeroable}; -use encase::{internal::WriteInto, ShaderSize}; -use indexmap::IndexMap; -use nonmax::NonMaxU32; -use tracing::{error, info}; -use wgpu::{BindingResource, BufferUsages, DownlevelFlags, Features}; - -use crate::{ - experimental::occlusion_culling::OcclusionCulling, - render_phase::{ - BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSet, - BinnedRenderPhaseBatchSets, CachedRenderPipelinePhaseItem, PhaseItem, - PhaseItemBatchSetKey as _, PhaseItemExtraIndex, RenderBin, SortedPhaseItem, - SortedRenderPhase, UnbatchableBinnedEntityIndices, ViewBinnedRenderPhases, - ViewSortedRenderPhases, - }, - render_resource::{Buffer, GpuArrayBufferable, RawBufferVec, UninitBufferVec}, - renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper}, - sync_world::MainEntity, - view::{ExtractedView, NoIndirectDrawing, RetainedViewEntity}, - Render, RenderApp, RenderDebugFlags, RenderSystems, -}; - -use super::{BatchMeta, GetBatchData, GetFullBatchData}; - -#[derive(Default)] -pub struct BatchingPlugin { - /// Debugging flags that can optionally be set when constructing the renderer. - pub debug_flags: RenderDebugFlags, -} - -impl Plugin for BatchingPlugin { - fn build(&self, app: &mut App) { - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - render_app - .insert_resource(IndirectParametersBuffers::new( - self.debug_flags - .contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS), - )) - .add_systems( - Render, - write_indirect_parameters_buffers.in_set(RenderSystems::PrepareResourcesFlush), - ) - .add_systems( - Render, - clear_indirect_parameters_buffers.in_set(RenderSystems::ManageViews), - ); - } - - fn finish(&self, app: &mut App) { - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - render_app.init_resource::(); - } -} - -/// Records whether GPU preprocessing and/or GPU culling are supported on the -/// device. -/// -/// No GPU preprocessing is supported on WebGL because of the lack of compute -/// shader support. GPU preprocessing is supported on DirectX 12, but due to [a -/// `wgpu` limitation] GPU culling is not. -/// -/// [a `wgpu` limitation]: https://github.com/gfx-rs/wgpu/issues/2471 -#[derive(Clone, Copy, PartialEq, Resource)] -pub struct GpuPreprocessingSupport { - /// The maximum amount of GPU preprocessing available on this platform. - pub max_supported_mode: GpuPreprocessingMode, -} - -impl GpuPreprocessingSupport { - /// Returns true if this GPU preprocessing support level isn't `None`. - #[inline] - pub fn is_available(&self) -> bool { - self.max_supported_mode != GpuPreprocessingMode::None - } - - /// Returns the given GPU preprocessing mode, capped to the current - /// preprocessing mode. - pub fn min(&self, mode: GpuPreprocessingMode) -> GpuPreprocessingMode { - match (self.max_supported_mode, mode) { - (GpuPreprocessingMode::None, _) | (_, GpuPreprocessingMode::None) => { - GpuPreprocessingMode::None - } - (mode, GpuPreprocessingMode::Culling) | (GpuPreprocessingMode::Culling, mode) => mode, - (GpuPreprocessingMode::PreprocessingOnly, GpuPreprocessingMode::PreprocessingOnly) => { - GpuPreprocessingMode::PreprocessingOnly - } - } - } - - /// Returns true if GPU culling is supported on this platform. - pub fn is_culling_supported(&self) -> bool { - self.max_supported_mode == GpuPreprocessingMode::Culling - } -} - -/// The amount of GPU preprocessing (compute and indirect draw) that we do. -#[derive(Clone, Copy, PartialEq)] -pub enum GpuPreprocessingMode { - /// No GPU preprocessing is in use at all. - /// - /// This is used when GPU compute isn't available. - None, - - /// GPU preprocessing is in use, but GPU culling isn't. - /// - /// This is used when the [`NoIndirectDrawing`] component is present on the - /// camera. - PreprocessingOnly, - - /// Both GPU preprocessing and GPU culling are in use. - /// - /// This is used by default. - Culling, -} - -/// The GPU buffers holding the data needed to render batches. -/// -/// For example, in the 3D PBR pipeline this holds `MeshUniform`s, which are the -/// `BD` type parameter in that mode. -/// -/// We have a separate *buffer data input* type (`BDI`) here, which a compute -/// shader is expected to expand to the full buffer data (`BD`) type. GPU -/// uniform building is generally faster and uses less system RAM to VRAM bus -/// bandwidth, but only implemented for some pipelines (for example, not in the -/// 2D pipeline at present) and only when compute shader is available. -#[derive(Resource)] -pub struct BatchedInstanceBuffers -where - BD: GpuArrayBufferable + Sync + Send + 'static, - BDI: Pod + Default, -{ - /// The uniform data inputs for the current frame. - /// - /// These are uploaded during the extraction phase. - pub current_input_buffer: InstanceInputUniformBuffer, - - /// The uniform data inputs for the previous frame. - /// - /// The indices don't generally line up between `current_input_buffer` - /// and `previous_input_buffer`, because, among other reasons, entities - /// can spawn or despawn between frames. Instead, each current buffer - /// data input uniform is expected to contain the index of the - /// corresponding buffer data input uniform in this list. - pub previous_input_buffer: InstanceInputUniformBuffer, - - /// The data needed to render buffers for each phase. - /// - /// The keys of this map are the type IDs of each phase: e.g. `Opaque3d`, - /// `AlphaMask3d`, etc. - pub phase_instance_buffers: TypeIdMap>, -} - -impl Default for BatchedInstanceBuffers -where - BD: GpuArrayBufferable + Sync + Send + 'static, - BDI: Pod + Sync + Send + Default + 'static, -{ - fn default() -> Self { - BatchedInstanceBuffers { - current_input_buffer: InstanceInputUniformBuffer::new(), - previous_input_buffer: InstanceInputUniformBuffer::new(), - phase_instance_buffers: HashMap::default(), - } - } -} - -/// The GPU buffers holding the data needed to render batches for a single -/// phase. -/// -/// These are split out per phase so that we can run the phases in parallel. -/// This is the version of the structure that has a type parameter, which -/// enables Bevy's scheduler to run the batching operations for the different -/// phases in parallel. -/// -/// See the documentation for [`BatchedInstanceBuffers`] for more information. -#[derive(Resource)] -pub struct PhaseBatchedInstanceBuffers -where - PI: PhaseItem, - BD: GpuArrayBufferable + Sync + Send + 'static, -{ - /// The buffers for this phase. - pub buffers: UntypedPhaseBatchedInstanceBuffers, - phantom: PhantomData, -} - -impl Default for PhaseBatchedInstanceBuffers -where - PI: PhaseItem, - BD: GpuArrayBufferable + Sync + Send + 'static, -{ - fn default() -> Self { - PhaseBatchedInstanceBuffers { - buffers: UntypedPhaseBatchedInstanceBuffers::default(), - phantom: PhantomData, - } - } -} - -/// The GPU buffers holding the data needed to render batches for a single -/// phase, without a type parameter for that phase. -/// -/// Since this structure doesn't have a type parameter, it can be placed in -/// [`BatchedInstanceBuffers::phase_instance_buffers`]. -pub struct UntypedPhaseBatchedInstanceBuffers -where - BD: GpuArrayBufferable + Sync + Send + 'static, -{ - /// A storage area for the buffer data that the GPU compute shader is - /// expected to write to. - /// - /// There will be one entry for each index. - pub data_buffer: UninitBufferVec, - - /// The index of the buffer data in the current input buffer that - /// corresponds to each instance. - /// - /// This is keyed off each view. Each view has a separate buffer. - pub work_item_buffers: HashMap, - - /// A buffer that holds the number of indexed meshes that weren't visible in - /// the previous frame, when GPU occlusion culling is in use. - /// - /// There's one set of [`LatePreprocessWorkItemIndirectParameters`] per - /// view. Bevy uses this value to determine how many threads to dispatch to - /// check meshes that weren't visible next frame to see if they became newly - /// visible this frame. - pub late_indexed_indirect_parameters_buffer: - RawBufferVec, - - /// A buffer that holds the number of non-indexed meshes that weren't - /// visible in the previous frame, when GPU occlusion culling is in use. - /// - /// There's one set of [`LatePreprocessWorkItemIndirectParameters`] per - /// view. Bevy uses this value to determine how many threads to dispatch to - /// check meshes that weren't visible next frame to see if they became newly - /// visible this frame. - pub late_non_indexed_indirect_parameters_buffer: - RawBufferVec, -} - -/// Holds the GPU buffer of instance input data, which is the data about each -/// mesh instance that the CPU provides. -/// -/// `BDI` is the *buffer data input* type, which the GPU mesh preprocessing -/// shader is expected to expand to the full *buffer data* type. -pub struct InstanceInputUniformBuffer -where - BDI: Pod + Default, -{ - /// The buffer containing the data that will be uploaded to the GPU. - buffer: RawBufferVec, - - /// Indices of slots that are free within the buffer. - /// - /// When adding data, we preferentially overwrite these slots first before - /// growing the buffer itself. - free_uniform_indices: Vec, -} - -impl InstanceInputUniformBuffer -where - BDI: Pod + Default, -{ - /// Creates a new, empty buffer. - pub fn new() -> InstanceInputUniformBuffer { - InstanceInputUniformBuffer { - buffer: RawBufferVec::new(BufferUsages::STORAGE), - free_uniform_indices: vec![], - } - } - - /// Clears the buffer and entity list out. - pub fn clear(&mut self) { - self.buffer.clear(); - self.free_uniform_indices.clear(); - } - - /// Returns the [`RawBufferVec`] corresponding to this input uniform buffer. - #[inline] - pub fn buffer(&self) -> &RawBufferVec { - &self.buffer - } - - /// Adds a new piece of buffered data to the uniform buffer and returns its - /// index. - pub fn add(&mut self, element: BDI) -> u32 { - match self.free_uniform_indices.pop() { - Some(uniform_index) => { - self.buffer.values_mut()[uniform_index as usize] = element; - uniform_index - } - None => self.buffer.push(element) as u32, - } - } - - /// Removes a piece of buffered data from the uniform buffer. - /// - /// This simply marks the data as free. - pub fn remove(&mut self, uniform_index: u32) { - self.free_uniform_indices.push(uniform_index); - } - - /// Returns the piece of buffered data at the given index. - /// - /// Returns [`None`] if the index is out of bounds or the data is removed. - pub fn get(&self, uniform_index: u32) -> Option { - if (uniform_index as usize) >= self.buffer.len() - || self.free_uniform_indices.contains(&uniform_index) - { - None - } else { - Some(self.get_unchecked(uniform_index)) - } - } - - /// Returns the piece of buffered data at the given index. - /// Can return data that has previously been removed. - /// - /// # Panics - /// if `uniform_index` is not in bounds of [`Self::buffer`]. - pub fn get_unchecked(&self, uniform_index: u32) -> BDI { - self.buffer.values()[uniform_index as usize] - } - - /// Stores a piece of buffered data at the given index. - /// - /// # Panics - /// if `uniform_index` is not in bounds of [`Self::buffer`]. - pub fn set(&mut self, uniform_index: u32, element: BDI) { - self.buffer.values_mut()[uniform_index as usize] = element; - } - - // Ensures that the buffers are nonempty, which the GPU requires before an - // upload can take place. - pub fn ensure_nonempty(&mut self) { - if self.buffer.is_empty() { - self.buffer.push(default()); - } - } - - /// Returns the number of instances in this buffer. - pub fn len(&self) -> usize { - self.buffer.len() - } - - /// Returns true if this buffer has no instances or false if it contains any - /// instances. - pub fn is_empty(&self) -> bool { - self.buffer.is_empty() - } - - /// Consumes this [`InstanceInputUniformBuffer`] and returns the raw buffer - /// ready to be uploaded to the GPU. - pub fn into_buffer(self) -> RawBufferVec { - self.buffer - } -} - -impl Default for InstanceInputUniformBuffer -where - BDI: Pod + Default, -{ - fn default() -> Self { - Self::new() - } -} - -/// The buffer of GPU preprocessing work items for a single view. -#[cfg_attr( - not(target_arch = "wasm32"), - expect( - clippy::large_enum_variant, - reason = "See https://github.com/bevyengine/bevy/issues/19220" - ) -)] -pub enum PreprocessWorkItemBuffers { - /// The work items we use if we aren't using indirect drawing. - /// - /// Because we don't have to separate indexed from non-indexed meshes in - /// direct mode, we only have a single buffer here. - Direct(RawBufferVec), - - /// The buffer of work items we use if we are using indirect drawing. - /// - /// We need to separate out indexed meshes from non-indexed meshes in this - /// case because the indirect parameters for these two types of meshes have - /// different sizes. - Indirect { - /// The buffer of work items corresponding to indexed meshes. - indexed: RawBufferVec, - /// The buffer of work items corresponding to non-indexed meshes. - non_indexed: RawBufferVec, - /// The work item buffers we use when GPU occlusion culling is in use. - gpu_occlusion_culling: Option, - }, -} - -/// The work item buffers we use when GPU occlusion culling is in use. -pub struct GpuOcclusionCullingWorkItemBuffers { - /// The buffer of work items corresponding to indexed meshes. - pub late_indexed: UninitBufferVec, - /// The buffer of work items corresponding to non-indexed meshes. - pub late_non_indexed: UninitBufferVec, - /// The offset into the - /// [`UntypedPhaseBatchedInstanceBuffers::late_indexed_indirect_parameters_buffer`] - /// where this view's indirect dispatch counts for indexed meshes live. - pub late_indirect_parameters_indexed_offset: u32, - /// The offset into the - /// [`UntypedPhaseBatchedInstanceBuffers::late_non_indexed_indirect_parameters_buffer`] - /// where this view's indirect dispatch counts for non-indexed meshes live. - pub late_indirect_parameters_non_indexed_offset: u32, -} - -/// A GPU-side data structure that stores the number of workgroups to dispatch -/// for the second phase of GPU occlusion culling. -/// -/// The late mesh preprocessing phase checks meshes that weren't visible frame -/// to determine if they're potentially visible this frame. -#[derive(Clone, Copy, ShaderType, Pod, Zeroable)] -#[repr(C)] -pub struct LatePreprocessWorkItemIndirectParameters { - /// The number of workgroups to dispatch. - /// - /// This will be equal to `work_item_count / 64`, rounded *up*. - dispatch_x: u32, - /// The number of workgroups along the abstract Y axis to dispatch: always - /// 1. - dispatch_y: u32, - /// The number of workgroups along the abstract Z axis to dispatch: always - /// 1. - dispatch_z: u32, - /// The actual number of work items. - /// - /// The GPU indirect dispatch doesn't read this, but it's used internally to - /// determine the actual number of work items that exist in the late - /// preprocessing work item buffer. - work_item_count: u32, - /// Padding to 64-byte boundaries for some hardware. - pad: UVec4, -} - -impl Default for LatePreprocessWorkItemIndirectParameters { - fn default() -> LatePreprocessWorkItemIndirectParameters { - LatePreprocessWorkItemIndirectParameters { - dispatch_x: 0, - dispatch_y: 1, - dispatch_z: 1, - work_item_count: 0, - pad: default(), - } - } -} - -/// Returns the set of work item buffers for the given view, first creating it -/// if necessary. -/// -/// Bevy uses work item buffers to tell the mesh preprocessing compute shader -/// which meshes are to be drawn. -/// -/// You may need to call this function if you're implementing your own custom -/// render phases. See the `specialized_mesh_pipeline` example. -pub fn get_or_create_work_item_buffer<'a, I>( - work_item_buffers: &'a mut HashMap, - view: RetainedViewEntity, - no_indirect_drawing: bool, - enable_gpu_occlusion_culling: bool, -) -> &'a mut PreprocessWorkItemBuffers -where - I: 'static, -{ - let preprocess_work_item_buffers = match work_item_buffers.entry(view) { - Entry::Occupied(occupied_entry) => occupied_entry.into_mut(), - Entry::Vacant(vacant_entry) => { - if no_indirect_drawing { - vacant_entry.insert(PreprocessWorkItemBuffers::Direct(RawBufferVec::new( - BufferUsages::STORAGE, - ))) - } else { - vacant_entry.insert(PreprocessWorkItemBuffers::Indirect { - indexed: RawBufferVec::new(BufferUsages::STORAGE), - non_indexed: RawBufferVec::new(BufferUsages::STORAGE), - // We fill this in below if `enable_gpu_occlusion_culling` - // is set. - gpu_occlusion_culling: None, - }) - } - } - }; - - // Initialize the GPU occlusion culling buffers if necessary. - if let PreprocessWorkItemBuffers::Indirect { - ref mut gpu_occlusion_culling, - .. - } = *preprocess_work_item_buffers - { - match ( - enable_gpu_occlusion_culling, - gpu_occlusion_culling.is_some(), - ) { - (false, false) | (true, true) => {} - (false, true) => { - *gpu_occlusion_culling = None; - } - (true, false) => { - *gpu_occlusion_culling = Some(GpuOcclusionCullingWorkItemBuffers { - late_indexed: UninitBufferVec::new(BufferUsages::STORAGE), - late_non_indexed: UninitBufferVec::new(BufferUsages::STORAGE), - late_indirect_parameters_indexed_offset: 0, - late_indirect_parameters_non_indexed_offset: 0, - }); - } - } - } - - preprocess_work_item_buffers -} - -/// Initializes work item buffers for a phase in preparation for a new frame. -pub fn init_work_item_buffers( - work_item_buffers: &mut PreprocessWorkItemBuffers, - late_indexed_indirect_parameters_buffer: &'_ mut RawBufferVec< - LatePreprocessWorkItemIndirectParameters, - >, - late_non_indexed_indirect_parameters_buffer: &'_ mut RawBufferVec< - LatePreprocessWorkItemIndirectParameters, - >, -) { - // Add the offsets for indirect parameters that the late phase of mesh - // preprocessing writes to. - if let PreprocessWorkItemBuffers::Indirect { - gpu_occlusion_culling: - Some(GpuOcclusionCullingWorkItemBuffers { - ref mut late_indirect_parameters_indexed_offset, - ref mut late_indirect_parameters_non_indexed_offset, - .. - }), - .. - } = *work_item_buffers - { - *late_indirect_parameters_indexed_offset = late_indexed_indirect_parameters_buffer - .push(LatePreprocessWorkItemIndirectParameters::default()) - as u32; - *late_indirect_parameters_non_indexed_offset = late_non_indexed_indirect_parameters_buffer - .push(LatePreprocessWorkItemIndirectParameters::default()) - as u32; - } -} - -impl PreprocessWorkItemBuffers { - /// Adds a new work item to the appropriate buffer. - /// - /// `indexed` specifies whether the work item corresponds to an indexed - /// mesh. - pub fn push(&mut self, indexed: bool, preprocess_work_item: PreprocessWorkItem) { - match *self { - PreprocessWorkItemBuffers::Direct(ref mut buffer) => { - buffer.push(preprocess_work_item); - } - PreprocessWorkItemBuffers::Indirect { - indexed: ref mut indexed_buffer, - non_indexed: ref mut non_indexed_buffer, - ref mut gpu_occlusion_culling, - } => { - if indexed { - indexed_buffer.push(preprocess_work_item); - } else { - non_indexed_buffer.push(preprocess_work_item); - } - - if let Some(ref mut gpu_occlusion_culling) = *gpu_occlusion_culling { - if indexed { - gpu_occlusion_culling.late_indexed.add(); - } else { - gpu_occlusion_culling.late_non_indexed.add(); - } - } - } - } - } - - /// Clears out the GPU work item buffers in preparation for a new frame. - pub fn clear(&mut self) { - match *self { - PreprocessWorkItemBuffers::Direct(ref mut buffer) => { - buffer.clear(); - } - PreprocessWorkItemBuffers::Indirect { - indexed: ref mut indexed_buffer, - non_indexed: ref mut non_indexed_buffer, - ref mut gpu_occlusion_culling, - } => { - indexed_buffer.clear(); - non_indexed_buffer.clear(); - - if let Some(ref mut gpu_occlusion_culling) = *gpu_occlusion_culling { - gpu_occlusion_culling.late_indexed.clear(); - gpu_occlusion_culling.late_non_indexed.clear(); - gpu_occlusion_culling.late_indirect_parameters_indexed_offset = 0; - gpu_occlusion_culling.late_indirect_parameters_non_indexed_offset = 0; - } - } - } - } -} - -/// One invocation of the preprocessing shader: i.e. one mesh instance in a -/// view. -#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)] -#[repr(C)] -pub struct PreprocessWorkItem { - /// The index of the batch input data in the input buffer that the shader - /// reads from. - pub input_index: u32, - - /// In direct mode, the index of the mesh uniform; in indirect mode, the - /// index of the [`IndirectParametersGpuMetadata`]. - /// - /// In indirect mode, this is the index of the - /// [`IndirectParametersGpuMetadata`] in the - /// `IndirectParametersBuffers::indexed_metadata` or - /// `IndirectParametersBuffers::non_indexed_metadata`. - pub output_or_indirect_parameters_index: u32, -} - -/// The `wgpu` indirect parameters structure that specifies a GPU draw command. -/// -/// This is the variant for indexed meshes. We generate the instances of this -/// structure in the `build_indirect_params.wgsl` compute shader. -#[derive(Clone, Copy, Debug, Pod, Zeroable, ShaderType)] -#[repr(C)] -pub struct IndirectParametersIndexed { - /// The number of indices that this mesh has. - pub index_count: u32, - /// The number of instances we are to draw. - pub instance_count: u32, - /// The offset of the first index for this mesh in the index buffer slab. - pub first_index: u32, - /// The offset of the first vertex for this mesh in the vertex buffer slab. - pub base_vertex: u32, - /// The index of the first mesh instance in the `MeshUniform` buffer. - pub first_instance: u32, -} - -/// The `wgpu` indirect parameters structure that specifies a GPU draw command. -/// -/// This is the variant for non-indexed meshes. We generate the instances of -/// this structure in the `build_indirect_params.wgsl` compute shader. -#[derive(Clone, Copy, Debug, Pod, Zeroable, ShaderType)] -#[repr(C)] -pub struct IndirectParametersNonIndexed { - /// The number of vertices that this mesh has. - pub vertex_count: u32, - /// The number of instances we are to draw. - pub instance_count: u32, - /// The offset of the first vertex for this mesh in the vertex buffer slab. - pub base_vertex: u32, - /// The index of the first mesh instance in the `Mesh` buffer. - pub first_instance: u32, -} - -/// A structure, initialized on CPU and read on GPU, that contains metadata -/// about each batch. -/// -/// Each batch will have one instance of this structure. -#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)] -#[repr(C)] -pub struct IndirectParametersCpuMetadata { - /// The index of the first instance of this mesh in the array of - /// `MeshUniform`s. - /// - /// Note that this is the *first* output index in this batch. Since each - /// instance of this structure refers to arbitrarily many instances, the - /// `MeshUniform`s corresponding to this batch span the indices - /// `base_output_index..(base_output_index + instance_count)`. - pub base_output_index: u32, - - /// The index of the batch set that this batch belongs to in the - /// [`IndirectBatchSet`] buffer. - /// - /// A *batch set* is a set of meshes that may be multi-drawn together. - /// Multiple batches (and therefore multiple instances of - /// [`IndirectParametersGpuMetadata`] structures) can be part of the same - /// batch set. - pub batch_set_index: u32, -} - -/// A structure, written and read GPU, that records how many instances of each -/// mesh are actually to be drawn. -/// -/// The GPU mesh preprocessing shader increments the -/// [`Self::early_instance_count`] and [`Self::late_instance_count`] as it -/// determines that meshes are visible. The indirect parameter building shader -/// reads this metadata in order to construct the indirect draw parameters. -/// -/// Each batch will have one instance of this structure. -#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)] -#[repr(C)] -pub struct IndirectParametersGpuMetadata { - /// The index of the first mesh in this batch in the array of - /// `MeshInputUniform`s. - pub mesh_index: u32, - - /// The number of instances that were judged visible last frame. - /// - /// The CPU sets this value to 0, and the GPU mesh preprocessing shader - /// increments it as it culls mesh instances. - pub early_instance_count: u32, - - /// The number of instances that have been judged potentially visible this - /// frame that weren't in the last frame's potentially visible set. - /// - /// The CPU sets this value to 0, and the GPU mesh preprocessing shader - /// increments it as it culls mesh instances. - pub late_instance_count: u32, -} - -/// A structure, shared between CPU and GPU, that holds the number of on-GPU -/// indirect draw commands for each *batch set*. -/// -/// A *batch set* is a set of meshes that may be multi-drawn together. -/// -/// If the current hardware and driver support `multi_draw_indirect_count`, the -/// indirect parameters building shader increments -/// [`Self::indirect_parameters_count`] as it generates indirect parameters. The -/// `multi_draw_indirect_count` command reads -/// [`Self::indirect_parameters_count`] in order to determine how many commands -/// belong to each batch set. -#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)] -#[repr(C)] -pub struct IndirectBatchSet { - /// The number of indirect parameter commands (i.e. batches) in this batch - /// set. - /// - /// The CPU sets this value to 0 before uploading this structure to GPU. The - /// indirect parameters building shader increments this value as it creates - /// indirect parameters. Then the `multi_draw_indirect_count` command reads - /// this value in order to determine how many indirect draw commands to - /// process. - pub indirect_parameters_count: u32, - - /// The offset within the `IndirectParametersBuffers::indexed_data` or - /// `IndirectParametersBuffers::non_indexed_data` of the first indirect draw - /// command for this batch set. - /// - /// The CPU fills out this value. - pub indirect_parameters_base: u32, -} - -/// The buffers containing all the information that indirect draw commands -/// (`multi_draw_indirect`, `multi_draw_indirect_count`) use to draw the scene. -/// -/// In addition to the indirect draw buffers themselves, this structure contains -/// the buffers that store [`IndirectParametersGpuMetadata`], which are the -/// structures that culling writes to so that the indirect parameter building -/// pass can determine how many meshes are actually to be drawn. -/// -/// These buffers will remain empty if indirect drawing isn't in use. -#[derive(Resource, Deref, DerefMut)] -pub struct IndirectParametersBuffers { - /// A mapping from a phase type ID to the indirect parameters buffers for - /// that phase. - /// - /// Examples of phase type IDs are `Opaque3d` and `AlphaMask3d`. - #[deref] - pub buffers: TypeIdMap, - /// If true, this sets the `COPY_SRC` flag on indirect draw parameters so - /// that they can be read back to CPU. - /// - /// This is a debugging feature that may reduce performance. It primarily - /// exists for the `occlusion_culling` example. - pub allow_copies_from_indirect_parameter_buffers: bool, -} - -impl IndirectParametersBuffers { - /// Initializes a new [`IndirectParametersBuffers`] resource. - pub fn new(allow_copies_from_indirect_parameter_buffers: bool) -> IndirectParametersBuffers { - IndirectParametersBuffers { - buffers: TypeIdMap::default(), - allow_copies_from_indirect_parameter_buffers, - } - } -} - -/// The buffers containing all the information that indirect draw commands use -/// to draw the scene, for a single phase. -/// -/// This is the version of the structure that has a type parameter, so that the -/// batching for different phases can run in parallel. -/// -/// See the [`IndirectParametersBuffers`] documentation for more information. -#[derive(Resource)] -pub struct PhaseIndirectParametersBuffers -where - PI: PhaseItem, -{ - /// The indirect draw buffers for the phase. - pub buffers: UntypedPhaseIndirectParametersBuffers, - phantom: PhantomData, -} - -impl PhaseIndirectParametersBuffers -where - PI: PhaseItem, -{ - pub fn new(allow_copies_from_indirect_parameter_buffers: bool) -> Self { - PhaseIndirectParametersBuffers { - buffers: UntypedPhaseIndirectParametersBuffers::new( - allow_copies_from_indirect_parameter_buffers, - ), - phantom: PhantomData, - } - } -} - -/// The buffers containing all the information that indirect draw commands use -/// to draw the scene, for a single phase. -/// -/// This is the version of the structure that doesn't have a type parameter, so -/// that it can be inserted into [`IndirectParametersBuffers::buffers`] -/// -/// See the [`IndirectParametersBuffers`] documentation for more information. -pub struct UntypedPhaseIndirectParametersBuffers { - /// Information that indirect draw commands use to draw indexed meshes in - /// the scene. - pub indexed: MeshClassIndirectParametersBuffers, - /// Information that indirect draw commands use to draw non-indexed meshes - /// in the scene. - pub non_indexed: MeshClassIndirectParametersBuffers, -} - -impl UntypedPhaseIndirectParametersBuffers { - /// Creates the indirect parameters buffers. - pub fn new( - allow_copies_from_indirect_parameter_buffers: bool, - ) -> UntypedPhaseIndirectParametersBuffers { - let mut indirect_parameter_buffer_usages = BufferUsages::STORAGE | BufferUsages::INDIRECT; - if allow_copies_from_indirect_parameter_buffers { - indirect_parameter_buffer_usages |= BufferUsages::COPY_SRC; - } - - UntypedPhaseIndirectParametersBuffers { - non_indexed: MeshClassIndirectParametersBuffers::new( - allow_copies_from_indirect_parameter_buffers, - ), - indexed: MeshClassIndirectParametersBuffers::new( - allow_copies_from_indirect_parameter_buffers, - ), - } - } - - /// Reserves space for `count` new batches. - /// - /// The `indexed` parameter specifies whether the meshes that these batches - /// correspond to are indexed or not. - pub fn allocate(&mut self, indexed: bool, count: u32) -> u32 { - if indexed { - self.indexed.allocate(count) - } else { - self.non_indexed.allocate(count) - } - } - - /// Returns the number of batches currently allocated. - /// - /// The `indexed` parameter specifies whether the meshes that these batches - /// correspond to are indexed or not. - fn batch_count(&self, indexed: bool) -> usize { - if indexed { - self.indexed.batch_count() - } else { - self.non_indexed.batch_count() - } - } - - /// Returns the number of batch sets currently allocated. - /// - /// The `indexed` parameter specifies whether the meshes that these batch - /// sets correspond to are indexed or not. - pub fn batch_set_count(&self, indexed: bool) -> usize { - if indexed { - self.indexed.batch_sets.len() - } else { - self.non_indexed.batch_sets.len() - } - } - - /// Adds a new batch set to `Self::indexed_batch_sets` or - /// `Self::non_indexed_batch_sets` as appropriate. - /// - /// `indexed` specifies whether the meshes that these batch sets correspond - /// to are indexed or not. `indirect_parameters_base` specifies the offset - /// within `Self::indexed_data` or `Self::non_indexed_data` of the first - /// batch in this batch set. - #[inline] - pub fn add_batch_set(&mut self, indexed: bool, indirect_parameters_base: u32) { - if indexed { - self.indexed.batch_sets.push(IndirectBatchSet { - indirect_parameters_base, - indirect_parameters_count: 0, - }); - } else { - self.non_indexed.batch_sets.push(IndirectBatchSet { - indirect_parameters_base, - indirect_parameters_count: 0, - }); - } - } - - /// Returns the index that a newly-added batch set will have. - /// - /// The `indexed` parameter specifies whether the meshes in such a batch set - /// are indexed or not. - pub fn get_next_batch_set_index(&self, indexed: bool) -> Option { - NonMaxU32::new(self.batch_set_count(indexed) as u32) - } - - /// Clears out the buffers in preparation for a new frame. - pub fn clear(&mut self) { - self.indexed.clear(); - self.non_indexed.clear(); - } -} - -/// The buffers containing all the information that indirect draw commands use -/// to draw the scene, for a single mesh class (indexed or non-indexed), for a -/// single phase. -pub struct MeshClassIndirectParametersBuffers -where - IP: Clone + ShaderSize + WriteInto, -{ - /// The GPU buffer that stores the indirect draw parameters for the meshes. - /// - /// The indirect parameters building shader writes to this buffer, while the - /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from - /// it to perform the draws. - data: UninitBufferVec, - - /// The GPU buffer that holds the data used to construct indirect draw - /// parameters for meshes. - /// - /// The GPU mesh preprocessing shader writes to this buffer, and the - /// indirect parameters building shader reads this buffer to construct the - /// indirect draw parameters. - cpu_metadata: RawBufferVec, - - /// The GPU buffer that holds data built by the GPU used to construct - /// indirect draw parameters for meshes. - /// - /// The GPU mesh preprocessing shader writes to this buffer, and the - /// indirect parameters building shader reads this buffer to construct the - /// indirect draw parameters. - gpu_metadata: UninitBufferVec, - - /// The GPU buffer that holds the number of indirect draw commands for each - /// phase of each view, for meshes. - /// - /// The indirect parameters building shader writes to this buffer, and the - /// `multi_draw_indirect_count` command reads from it in order to know how - /// many indirect draw commands to process. - batch_sets: RawBufferVec, -} - -impl MeshClassIndirectParametersBuffers -where - IP: Clone + ShaderSize + WriteInto, -{ - fn new( - allow_copies_from_indirect_parameter_buffers: bool, - ) -> MeshClassIndirectParametersBuffers { - let mut indirect_parameter_buffer_usages = BufferUsages::STORAGE | BufferUsages::INDIRECT; - if allow_copies_from_indirect_parameter_buffers { - indirect_parameter_buffer_usages |= BufferUsages::COPY_SRC; - } - - MeshClassIndirectParametersBuffers { - data: UninitBufferVec::new(indirect_parameter_buffer_usages), - cpu_metadata: RawBufferVec::new(BufferUsages::STORAGE), - gpu_metadata: UninitBufferVec::new(BufferUsages::STORAGE), - batch_sets: RawBufferVec::new(indirect_parameter_buffer_usages), - } - } - - /// Returns the GPU buffer that stores the indirect draw parameters for - /// indexed meshes. - /// - /// The indirect parameters building shader writes to this buffer, while the - /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from - /// it to perform the draws. - #[inline] - pub fn data_buffer(&self) -> Option<&Buffer> { - self.data.buffer() - } - - /// Returns the GPU buffer that holds the CPU-constructed data used to - /// construct indirect draw parameters for meshes. - /// - /// The CPU writes to this buffer, and the indirect parameters building - /// shader reads this buffer to construct the indirect draw parameters. - #[inline] - pub fn cpu_metadata_buffer(&self) -> Option<&Buffer> { - self.cpu_metadata.buffer() - } - - /// Returns the GPU buffer that holds the GPU-constructed data used to - /// construct indirect draw parameters for meshes. - /// - /// The GPU mesh preprocessing shader writes to this buffer, and the - /// indirect parameters building shader reads this buffer to construct the - /// indirect draw parameters. - #[inline] - pub fn gpu_metadata_buffer(&self) -> Option<&Buffer> { - self.gpu_metadata.buffer() - } - - /// Returns the GPU buffer that holds the number of indirect draw commands - /// for each phase of each view. - /// - /// The indirect parameters building shader writes to this buffer, and the - /// `multi_draw_indirect_count` command reads from it in order to know how - /// many indirect draw commands to process. - #[inline] - pub fn batch_sets_buffer(&self) -> Option<&Buffer> { - self.batch_sets.buffer() - } - - /// Reserves space for `count` new batches. - /// - /// This allocates in the [`Self::cpu_metadata`], [`Self::gpu_metadata`], - /// and [`Self::data`] buffers. - fn allocate(&mut self, count: u32) -> u32 { - let length = self.data.len(); - self.cpu_metadata.reserve_internal(count as usize); - self.gpu_metadata.add_multiple(count as usize); - for _ in 0..count { - self.data.add(); - self.cpu_metadata - .push(IndirectParametersCpuMetadata::default()); - } - length as u32 - } - - /// Sets the [`IndirectParametersCpuMetadata`] for the mesh at the given - /// index. - pub fn set(&mut self, index: u32, value: IndirectParametersCpuMetadata) { - self.cpu_metadata.set(index, value); - } - - /// Returns the number of batches corresponding to meshes that are currently - /// allocated. - #[inline] - pub fn batch_count(&self) -> usize { - self.data.len() - } - - /// Clears out all the buffers in preparation for a new frame. - pub fn clear(&mut self) { - self.data.clear(); - self.cpu_metadata.clear(); - self.gpu_metadata.clear(); - self.batch_sets.clear(); - } -} - -impl Default for IndirectParametersBuffers { - fn default() -> Self { - // By default, we don't allow GPU indirect parameter mapping, since - // that's a debugging option. - Self::new(false) - } -} - -impl FromWorld for GpuPreprocessingSupport { - fn from_world(world: &mut World) -> Self { - let adapter = world.resource::(); - let device = world.resource::(); - - // Filter Android drivers that are incompatible with GPU preprocessing: - // - We filter out Adreno 730 and earlier GPUs (except 720, as it's newer - // than 730). - // - We filter out Mali GPUs with driver versions lower than 48. - fn is_non_supported_android_device(adapter_info: &RenderAdapterInfo) -> bool { - crate::get_adreno_model(adapter_info).is_some_and(|model| model != 720 && model <= 730) - || crate::get_mali_driver_version(adapter_info).is_some_and(|version| version < 48) - } - - let culling_feature_support = device - .features() - .contains(Features::INDIRECT_FIRST_INSTANCE | Features::PUSH_CONSTANTS); - // Depth downsampling for occlusion culling requires 12 textures - let limit_support = device.limits().max_storage_textures_per_shader_stage >= 12 && - // Even if the adapter supports compute, we might be simulating a lack of - // compute via device limits (see `WgpuSettingsPriority::WebGL2` and - // `wgpu::Limits::downlevel_webgl2_defaults()`). This will have set all the - // `max_compute_*` limits to zero, so we arbitrarily pick one as a canary. - device.limits().max_compute_workgroup_storage_size != 0; - - let downlevel_support = adapter - .get_downlevel_capabilities() - .flags - .contains(DownlevelFlags::COMPUTE_SHADERS); - - let adapter_info = RenderAdapterInfo(WgpuWrapper::new(adapter.get_info())); - - let max_supported_mode = if device.limits().max_compute_workgroup_size_x == 0 - || is_non_supported_android_device(&adapter_info) - || adapter_info.backend == wgpu::Backend::Gl - { - info!( - "GPU preprocessing is not supported on this device. \ - Falling back to CPU preprocessing.", - ); - GpuPreprocessingMode::None - } else if !(culling_feature_support && limit_support && downlevel_support) { - info!("Some GPU preprocessing are limited on this device."); - GpuPreprocessingMode::PreprocessingOnly - } else { - info!("GPU preprocessing is fully supported on this device."); - GpuPreprocessingMode::Culling - }; - - GpuPreprocessingSupport { max_supported_mode } - } -} - -impl BatchedInstanceBuffers -where - BD: GpuArrayBufferable + Sync + Send + 'static, - BDI: Pod + Sync + Send + Default + 'static, -{ - /// Creates new buffers. - pub fn new() -> Self { - Self::default() - } - - /// Clears out the buffers in preparation for a new frame. - pub fn clear(&mut self) { - for phase_instance_buffer in self.phase_instance_buffers.values_mut() { - phase_instance_buffer.clear(); - } - } -} - -impl UntypedPhaseBatchedInstanceBuffers -where - BD: GpuArrayBufferable + Sync + Send + 'static, -{ - pub fn new() -> Self { - UntypedPhaseBatchedInstanceBuffers { - data_buffer: UninitBufferVec::new(BufferUsages::STORAGE), - work_item_buffers: HashMap::default(), - late_indexed_indirect_parameters_buffer: RawBufferVec::new( - BufferUsages::STORAGE | BufferUsages::INDIRECT, - ), - late_non_indexed_indirect_parameters_buffer: RawBufferVec::new( - BufferUsages::STORAGE | BufferUsages::INDIRECT, - ), - } - } - - /// Returns the binding of the buffer that contains the per-instance data. - /// - /// This buffer needs to be filled in via a compute shader. - pub fn instance_data_binding(&self) -> Option> { - self.data_buffer - .buffer() - .map(|buffer| buffer.as_entire_binding()) - } - - /// Clears out the buffers in preparation for a new frame. - pub fn clear(&mut self) { - self.data_buffer.clear(); - self.late_indexed_indirect_parameters_buffer.clear(); - self.late_non_indexed_indirect_parameters_buffer.clear(); - - // Clear each individual set of buffers, but don't depopulate the hash - // table. We want to avoid reallocating these vectors every frame. - for view_work_item_buffers in self.work_item_buffers.values_mut() { - view_work_item_buffers.clear(); - } - } -} - -impl Default for UntypedPhaseBatchedInstanceBuffers -where - BD: GpuArrayBufferable + Sync + Send + 'static, -{ - fn default() -> Self { - Self::new() - } -} - -/// Information about a render batch that we're building up during a sorted -/// render phase. -struct SortedRenderBatch -where - F: GetBatchData, -{ - /// The index of the first phase item in this batch in the list of phase - /// items. - phase_item_start_index: u32, - - /// The index of the first instance in this batch in the instance buffer. - instance_start_index: u32, - - /// True if the mesh in question has an index buffer; false otherwise. - indexed: bool, - - /// The index of the indirect parameters for this batch in the - /// [`IndirectParametersBuffers`]. - /// - /// If CPU culling is being used, then this will be `None`. - indirect_parameters_index: Option, - - /// Metadata that can be used to determine whether an instance can be placed - /// into this batch. - /// - /// If `None`, the item inside is unbatchable. - meta: Option>, -} - -impl SortedRenderBatch -where - F: GetBatchData, -{ - /// Finalizes this batch and updates the [`SortedRenderPhase`] with the - /// appropriate indices. - /// - /// `instance_end_index` is the index of the last instance in this batch - /// plus one. - fn flush( - self, - instance_end_index: u32, - phase: &mut SortedRenderPhase, - phase_indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers, - ) where - I: CachedRenderPipelinePhaseItem + SortedPhaseItem, - { - let (batch_range, batch_extra_index) = - phase.items[self.phase_item_start_index as usize].batch_range_and_extra_index_mut(); - *batch_range = self.instance_start_index..instance_end_index; - *batch_extra_index = match self.indirect_parameters_index { - Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex { - range: u32::from(indirect_parameters_index) - ..(u32::from(indirect_parameters_index) + 1), - batch_set_index: None, - }, - None => PhaseItemExtraIndex::None, - }; - if let Some(indirect_parameters_index) = self.indirect_parameters_index { - phase_indirect_parameters_buffers - .add_batch_set(self.indexed, indirect_parameters_index.into()); - } - } -} - -/// A system that runs early in extraction and clears out all the -/// [`BatchedInstanceBuffers`] for the frame. -/// -/// We have to run this during extraction because, if GPU preprocessing is in -/// use, the extraction phase will write to the mesh input uniform buffers -/// directly, so the buffers need to be cleared before then. -pub fn clear_batched_gpu_instance_buffers( - gpu_batched_instance_buffers: Option< - ResMut>, - >, -) where - GFBD: GetFullBatchData, -{ - // Don't clear the entire table, because that would delete the buffers, and - // we want to reuse those allocations. - if let Some(mut gpu_batched_instance_buffers) = gpu_batched_instance_buffers { - gpu_batched_instance_buffers.clear(); - } -} - -/// A system that removes GPU preprocessing work item buffers that correspond to -/// deleted [`ExtractedView`]s. -/// -/// This is a separate system from [`clear_batched_gpu_instance_buffers`] -/// because [`ExtractedView`]s aren't created until after the extraction phase -/// is completed. -pub fn delete_old_work_item_buffers( - mut gpu_batched_instance_buffers: ResMut< - BatchedInstanceBuffers, - >, - extracted_views: Query<&ExtractedView>, -) where - GFBD: GetFullBatchData, -{ - let retained_view_entities: HashSet<_> = extracted_views - .iter() - .map(|extracted_view| extracted_view.retained_view_entity) - .collect(); - for phase_instance_buffers in gpu_batched_instance_buffers - .phase_instance_buffers - .values_mut() - { - phase_instance_buffers - .work_item_buffers - .retain(|retained_view_entity, _| { - retained_view_entities.contains(retained_view_entity) - }); - } -} - -/// Batch the items in a sorted render phase, when GPU instance buffer building -/// is in use. This means comparing metadata needed to draw each phase item and -/// trying to combine the draws into a batch. -pub fn batch_and_prepare_sorted_render_phase( - mut phase_batched_instance_buffers: ResMut>, - mut phase_indirect_parameters_buffers: ResMut>, - mut sorted_render_phases: ResMut>, - mut views: Query<( - &ExtractedView, - Has, - Has, - )>, - system_param_item: StaticSystemParam, -) where - I: CachedRenderPipelinePhaseItem + SortedPhaseItem, - GFBD: GetFullBatchData, -{ - // We only process GPU-built batch data in this function. - let UntypedPhaseBatchedInstanceBuffers { - ref mut data_buffer, - ref mut work_item_buffers, - ref mut late_indexed_indirect_parameters_buffer, - ref mut late_non_indexed_indirect_parameters_buffer, - } = phase_batched_instance_buffers.buffers; - - for (extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views { - let Some(phase) = sorted_render_phases.get_mut(&extracted_view.retained_view_entity) else { - continue; - }; - - // Create the work item buffer if necessary. - let work_item_buffer = get_or_create_work_item_buffer::( - work_item_buffers, - extracted_view.retained_view_entity, - no_indirect_drawing, - gpu_occlusion_culling, - ); - - // Initialize those work item buffers in preparation for this new frame. - init_work_item_buffers( - work_item_buffer, - late_indexed_indirect_parameters_buffer, - late_non_indexed_indirect_parameters_buffer, - ); - - // Walk through the list of phase items, building up batches as we go. - let mut batch: Option> = None; - - for current_index in 0..phase.items.len() { - // Get the index of the input data, and comparison metadata, for - // this entity. - let item = &phase.items[current_index]; - let entity = item.main_entity(); - let item_is_indexed = item.indexed(); - let current_batch_input_index = - GFBD::get_index_and_compare_data(&system_param_item, entity); - - // Unpack that index and metadata. Note that it's possible for index - // and/or metadata to not be present, which signifies that this - // entity is unbatchable. In that case, we break the batch here. - // If the index isn't present the item is not part of this pipeline and so will be skipped. - let Some((current_input_index, current_meta)) = current_batch_input_index else { - // Break a batch if we need to. - if let Some(batch) = batch.take() { - batch.flush( - data_buffer.len() as u32, - phase, - &mut phase_indirect_parameters_buffers.buffers, - ); - } - - continue; - }; - let current_meta = - current_meta.map(|meta| BatchMeta::new(&phase.items[current_index], meta)); - - // Determine if this entity can be included in the batch we're - // building up. - let can_batch = batch.as_ref().is_some_and(|batch| { - // `None` for metadata indicates that the items are unbatchable. - match (¤t_meta, &batch.meta) { - (Some(current_meta), Some(batch_meta)) => current_meta == batch_meta, - (_, _) => false, - } - }); - - // Make space in the data buffer for this instance. - let output_index = data_buffer.add() as u32; - - // If we can't batch, break the existing batch and make a new one. - if !can_batch { - // Break a batch if we need to. - if let Some(batch) = batch.take() { - batch.flush( - output_index, - phase, - &mut phase_indirect_parameters_buffers.buffers, - ); - } - - let indirect_parameters_index = if no_indirect_drawing { - None - } else if item_is_indexed { - Some( - phase_indirect_parameters_buffers - .buffers - .indexed - .allocate(1), - ) - } else { - Some( - phase_indirect_parameters_buffers - .buffers - .non_indexed - .allocate(1), - ) - }; - - // Start a new batch. - if let Some(indirect_parameters_index) = indirect_parameters_index { - GFBD::write_batch_indirect_parameters_metadata( - item_is_indexed, - output_index, - None, - &mut phase_indirect_parameters_buffers.buffers, - indirect_parameters_index, - ); - }; - - batch = Some(SortedRenderBatch { - phase_item_start_index: current_index as u32, - instance_start_index: output_index, - indexed: item_is_indexed, - indirect_parameters_index: indirect_parameters_index.and_then(NonMaxU32::new), - meta: current_meta, - }); - } - - // Add a new preprocessing work item so that the preprocessing - // shader will copy the per-instance data over. - if let Some(batch) = batch.as_ref() { - work_item_buffer.push( - item_is_indexed, - PreprocessWorkItem { - input_index: current_input_index.into(), - output_or_indirect_parameters_index: match ( - no_indirect_drawing, - batch.indirect_parameters_index, - ) { - (true, _) => output_index, - (false, Some(indirect_parameters_index)) => { - indirect_parameters_index.into() - } - (false, None) => 0, - }, - }, - ); - } - } - - // Flush the final batch if necessary. - if let Some(batch) = batch.take() { - batch.flush( - data_buffer.len() as u32, - phase, - &mut phase_indirect_parameters_buffers.buffers, - ); - } - } -} - -/// Creates batches for a render phase that uses bins. -pub fn batch_and_prepare_binned_render_phase( - mut phase_batched_instance_buffers: ResMut>, - phase_indirect_parameters_buffers: ResMut>, - mut binned_render_phases: ResMut>, - mut views: Query< - ( - &ExtractedView, - Has, - Has, - ), - With, - >, - param: StaticSystemParam, -) where - BPI: BinnedPhaseItem, - GFBD: GetFullBatchData, -{ - let system_param_item = param.into_inner(); - - let phase_indirect_parameters_buffers = phase_indirect_parameters_buffers.into_inner(); - - let UntypedPhaseBatchedInstanceBuffers { - ref mut data_buffer, - ref mut work_item_buffers, - ref mut late_indexed_indirect_parameters_buffer, - ref mut late_non_indexed_indirect_parameters_buffer, - } = phase_batched_instance_buffers.buffers; - - for (extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views { - let Some(phase) = binned_render_phases.get_mut(&extracted_view.retained_view_entity) else { - continue; - }; - - // Create the work item buffer if necessary; otherwise, just mark it as - // used this frame. - let work_item_buffer = get_or_create_work_item_buffer::( - work_item_buffers, - extracted_view.retained_view_entity, - no_indirect_drawing, - gpu_occlusion_culling, - ); - - // Initialize those work item buffers in preparation for this new frame. - init_work_item_buffers( - work_item_buffer, - late_indexed_indirect_parameters_buffer, - late_non_indexed_indirect_parameters_buffer, - ); - - // Prepare multidrawables. - - if let ( - &mut BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut batch_sets), - &mut PreprocessWorkItemBuffers::Indirect { - indexed: ref mut indexed_work_item_buffer, - non_indexed: ref mut non_indexed_work_item_buffer, - gpu_occlusion_culling: ref mut gpu_occlusion_culling_buffers, - }, - ) = (&mut phase.batch_sets, &mut *work_item_buffer) - { - let mut output_index = data_buffer.len() as u32; - - // Initialize the state for both indexed and non-indexed meshes. - let mut indexed_preparer: MultidrawableBatchSetPreparer = - MultidrawableBatchSetPreparer::new( - phase_indirect_parameters_buffers.buffers.batch_count(true) as u32, - phase_indirect_parameters_buffers - .buffers - .indexed - .batch_sets - .len() as u32, - ); - let mut non_indexed_preparer: MultidrawableBatchSetPreparer = - MultidrawableBatchSetPreparer::new( - phase_indirect_parameters_buffers.buffers.batch_count(false) as u32, - phase_indirect_parameters_buffers - .buffers - .non_indexed - .batch_sets - .len() as u32, - ); - - // Prepare each batch set. - for (batch_set_key, bins) in &phase.multidrawable_meshes { - if batch_set_key.indexed() { - indexed_preparer.prepare_multidrawable_binned_batch_set( - bins, - &mut output_index, - data_buffer, - indexed_work_item_buffer, - &mut phase_indirect_parameters_buffers.buffers.indexed, - batch_sets, - ); - } else { - non_indexed_preparer.prepare_multidrawable_binned_batch_set( - bins, - &mut output_index, - data_buffer, - non_indexed_work_item_buffer, - &mut phase_indirect_parameters_buffers.buffers.non_indexed, - batch_sets, - ); - } - } - - // Reserve space in the occlusion culling buffers, if necessary. - if let Some(gpu_occlusion_culling_buffers) = gpu_occlusion_culling_buffers { - gpu_occlusion_culling_buffers - .late_indexed - .add_multiple(indexed_preparer.work_item_count); - gpu_occlusion_culling_buffers - .late_non_indexed - .add_multiple(non_indexed_preparer.work_item_count); - } - } - - // Prepare batchables. - - for (key, bin) in &phase.batchable_meshes { - let mut batch: Option = None; - for (&main_entity, &input_index) in bin.entities() { - let output_index = data_buffer.add() as u32; - - match batch { - Some(ref mut batch) => { - batch.instance_range.end = output_index + 1; - - // Append to the current batch. - // - // If we're in indirect mode, then we write the first - // output index of this batch, so that we have a - // tightly-packed buffer if GPU culling discards some of - // the instances. Otherwise, we can just write the - // output index directly. - work_item_buffer.push( - key.0.indexed(), - PreprocessWorkItem { - input_index: *input_index, - output_or_indirect_parameters_index: match ( - no_indirect_drawing, - &batch.extra_index, - ) { - (true, _) => output_index, - ( - false, - PhaseItemExtraIndex::IndirectParametersIndex { - range: indirect_parameters_range, - .. - }, - ) => indirect_parameters_range.start, - (false, &PhaseItemExtraIndex::DynamicOffset(_)) - | (false, &PhaseItemExtraIndex::None) => 0, - }, - }, - ); - } - - None if !no_indirect_drawing => { - // Start a new batch, in indirect mode. - let indirect_parameters_index = phase_indirect_parameters_buffers - .buffers - .allocate(key.0.indexed(), 1); - let batch_set_index = phase_indirect_parameters_buffers - .buffers - .get_next_batch_set_index(key.0.indexed()); - - GFBD::write_batch_indirect_parameters_metadata( - key.0.indexed(), - output_index, - batch_set_index, - &mut phase_indirect_parameters_buffers.buffers, - indirect_parameters_index, - ); - work_item_buffer.push( - key.0.indexed(), - PreprocessWorkItem { - input_index: *input_index, - output_or_indirect_parameters_index: indirect_parameters_index, - }, - ); - batch = Some(BinnedRenderPhaseBatch { - representative_entity: (Entity::PLACEHOLDER, main_entity), - instance_range: output_index..output_index + 1, - extra_index: PhaseItemExtraIndex::IndirectParametersIndex { - range: indirect_parameters_index..(indirect_parameters_index + 1), - batch_set_index: None, - }, - }); - } - - None => { - // Start a new batch, in direct mode. - work_item_buffer.push( - key.0.indexed(), - PreprocessWorkItem { - input_index: *input_index, - output_or_indirect_parameters_index: output_index, - }, - ); - batch = Some(BinnedRenderPhaseBatch { - representative_entity: (Entity::PLACEHOLDER, main_entity), - instance_range: output_index..output_index + 1, - extra_index: PhaseItemExtraIndex::None, - }); - } - } - } - - if let Some(batch) = batch { - match phase.batch_sets { - BinnedRenderPhaseBatchSets::DynamicUniforms(_) => { - error!("Dynamic uniform batch sets shouldn't be used here"); - } - BinnedRenderPhaseBatchSets::Direct(ref mut vec) => { - vec.push(batch); - } - BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => { - // The Bevy renderer will never mark a mesh as batchable - // but not multidrawable if multidraw is in use. - // However, custom render pipelines might do so, such as - // the `specialized_mesh_pipeline` example. - vec.push(BinnedRenderPhaseBatchSet { - first_batch: batch, - batch_count: 1, - bin_key: key.1.clone(), - index: phase_indirect_parameters_buffers - .buffers - .batch_set_count(key.0.indexed()) - as u32, - }); - } - } - } - } - - // Prepare unbatchables. - for (key, unbatchables) in &mut phase.unbatchable_meshes { - // Allocate the indirect parameters if necessary. - let mut indirect_parameters_offset = if no_indirect_drawing { - None - } else if key.0.indexed() { - Some( - phase_indirect_parameters_buffers - .buffers - .indexed - .allocate(unbatchables.entities.len() as u32), - ) - } else { - Some( - phase_indirect_parameters_buffers - .buffers - .non_indexed - .allocate(unbatchables.entities.len() as u32), - ) - }; - - for main_entity in unbatchables.entities.keys() { - let Some(input_index) = GFBD::get_binned_index(&system_param_item, *main_entity) - else { - continue; - }; - let output_index = data_buffer.add() as u32; - - if let Some(ref mut indirect_parameters_index) = indirect_parameters_offset { - // We're in indirect mode, so add an indirect parameters - // index. - GFBD::write_batch_indirect_parameters_metadata( - key.0.indexed(), - output_index, - None, - &mut phase_indirect_parameters_buffers.buffers, - *indirect_parameters_index, - ); - work_item_buffer.push( - key.0.indexed(), - PreprocessWorkItem { - input_index: input_index.into(), - output_or_indirect_parameters_index: *indirect_parameters_index, - }, - ); - unbatchables - .buffer_indices - .add(UnbatchableBinnedEntityIndices { - instance_index: *indirect_parameters_index, - extra_index: PhaseItemExtraIndex::IndirectParametersIndex { - range: *indirect_parameters_index..(*indirect_parameters_index + 1), - batch_set_index: None, - }, - }); - phase_indirect_parameters_buffers - .buffers - .add_batch_set(key.0.indexed(), *indirect_parameters_index); - *indirect_parameters_index += 1; - } else { - work_item_buffer.push( - key.0.indexed(), - PreprocessWorkItem { - input_index: input_index.into(), - output_or_indirect_parameters_index: output_index, - }, - ); - unbatchables - .buffer_indices - .add(UnbatchableBinnedEntityIndices { - instance_index: output_index, - extra_index: PhaseItemExtraIndex::None, - }); - } - } - } - } -} - -/// The state that [`batch_and_prepare_binned_render_phase`] uses to construct -/// multidrawable batch sets. -/// -/// The [`batch_and_prepare_binned_render_phase`] system maintains two of these: -/// one for indexed meshes and one for non-indexed meshes. -struct MultidrawableBatchSetPreparer -where - BPI: BinnedPhaseItem, - GFBD: GetFullBatchData, -{ - /// The offset in the indirect parameters buffer at which the next indirect - /// parameters will be written. - indirect_parameters_index: u32, - /// The number of batch sets we've built so far for this mesh class. - batch_set_index: u32, - /// The number of work items we've emitted so far for this mesh class. - work_item_count: usize, - phantom: PhantomData<(BPI, GFBD)>, -} - -impl MultidrawableBatchSetPreparer -where - BPI: BinnedPhaseItem, - GFBD: GetFullBatchData, -{ - /// Creates a new [`MultidrawableBatchSetPreparer`] that will start writing - /// indirect parameters and batch sets at the given indices. - #[inline] - fn new(initial_indirect_parameters_index: u32, initial_batch_set_index: u32) -> Self { - MultidrawableBatchSetPreparer { - indirect_parameters_index: initial_indirect_parameters_index, - batch_set_index: initial_batch_set_index, - work_item_count: 0, - phantom: PhantomData, - } - } - - /// Creates batch sets and writes the GPU data needed to draw all visible - /// entities of one mesh class in the given batch set. - /// - /// The *mesh class* represents whether the mesh has indices or not. - #[inline] - fn prepare_multidrawable_binned_batch_set( - &mut self, - bins: &IndexMap, - output_index: &mut u32, - data_buffer: &mut UninitBufferVec, - indexed_work_item_buffer: &mut RawBufferVec, - mesh_class_buffers: &mut MeshClassIndirectParametersBuffers, - batch_sets: &mut Vec>, - ) where - IP: Clone + ShaderSize + WriteInto, - { - let current_indexed_batch_set_index = self.batch_set_index; - let current_output_index = *output_index; - - let indirect_parameters_base = self.indirect_parameters_index; - - // We're going to write the first entity into the batch set. Do this - // here so that we can preload the bin into cache as a side effect. - let Some((first_bin_key, first_bin)) = bins.iter().next() else { - return; - }; - let first_bin_len = first_bin.entities().len(); - let first_bin_entity = first_bin - .entities() - .keys() - .next() - .copied() - .unwrap_or(MainEntity::from(Entity::PLACEHOLDER)); - - // Traverse the batch set, processing each bin. - for bin in bins.values() { - // Record the first output index for this batch, as well as its own - // index. - mesh_class_buffers - .cpu_metadata - .push(IndirectParametersCpuMetadata { - base_output_index: *output_index, - batch_set_index: self.batch_set_index, - }); - - // Traverse the bin, pushing `PreprocessWorkItem`s for each entity - // within it. This is a hot loop, so make it as fast as possible. - for &input_index in bin.entities().values() { - indexed_work_item_buffer.push(PreprocessWorkItem { - input_index: *input_index, - output_or_indirect_parameters_index: self.indirect_parameters_index, - }); - } - - // Reserve space for the appropriate number of entities in the data - // buffer. Also, advance the output index and work item count. - let bin_entity_count = bin.entities().len(); - data_buffer.add_multiple(bin_entity_count); - *output_index += bin_entity_count as u32; - self.work_item_count += bin_entity_count; - - self.indirect_parameters_index += 1; - } - - // Reserve space for the bins in this batch set in the GPU buffers. - let bin_count = bins.len(); - mesh_class_buffers.gpu_metadata.add_multiple(bin_count); - mesh_class_buffers.data.add_multiple(bin_count); - - // Write the information the GPU will need about this batch set. - mesh_class_buffers.batch_sets.push(IndirectBatchSet { - indirect_parameters_base, - indirect_parameters_count: 0, - }); - - self.batch_set_index += 1; - - // Record the batch set. The render node later processes this record to - // render the batches. - batch_sets.push(BinnedRenderPhaseBatchSet { - first_batch: BinnedRenderPhaseBatch { - representative_entity: (Entity::PLACEHOLDER, first_bin_entity), - instance_range: current_output_index..(current_output_index + first_bin_len as u32), - extra_index: PhaseItemExtraIndex::maybe_indirect_parameters_index(NonMaxU32::new( - indirect_parameters_base, - )), - }, - bin_key: (*first_bin_key).clone(), - batch_count: self.indirect_parameters_index - indirect_parameters_base, - index: current_indexed_batch_set_index, - }); - } -} - -/// A system that gathers up the per-phase GPU buffers and inserts them into the -/// [`BatchedInstanceBuffers`] and [`IndirectParametersBuffers`] tables. -/// -/// This runs after the [`batch_and_prepare_binned_render_phase`] or -/// [`batch_and_prepare_sorted_render_phase`] systems. It takes the per-phase -/// [`PhaseBatchedInstanceBuffers`] and [`PhaseIndirectParametersBuffers`] -/// resources and inserts them into the global [`BatchedInstanceBuffers`] and -/// [`IndirectParametersBuffers`] tables. -/// -/// This system exists so that the [`batch_and_prepare_binned_render_phase`] and -/// [`batch_and_prepare_sorted_render_phase`] can run in parallel with one -/// another. If those two systems manipulated [`BatchedInstanceBuffers`] and -/// [`IndirectParametersBuffers`] directly, then they wouldn't be able to run in -/// parallel. -pub fn collect_buffers_for_phase( - mut phase_batched_instance_buffers: ResMut>, - mut phase_indirect_parameters_buffers: ResMut>, - mut batched_instance_buffers: ResMut< - BatchedInstanceBuffers, - >, - mut indirect_parameters_buffers: ResMut, -) where - PI: PhaseItem, - GFBD: GetFullBatchData + Send + Sync + 'static, -{ - // Insert the `PhaseBatchedInstanceBuffers` into the global table. Replace - // the contents of the per-phase resource with the old batched instance - // buffers in order to reuse allocations. - let untyped_phase_batched_instance_buffers = - mem::take(&mut phase_batched_instance_buffers.buffers); - if let Some(mut old_untyped_phase_batched_instance_buffers) = batched_instance_buffers - .phase_instance_buffers - .insert(TypeId::of::(), untyped_phase_batched_instance_buffers) - { - old_untyped_phase_batched_instance_buffers.clear(); - phase_batched_instance_buffers.buffers = old_untyped_phase_batched_instance_buffers; - } - - // Insert the `PhaseIndirectParametersBuffers` into the global table. - // Replace the contents of the per-phase resource with the old indirect - // parameters buffers in order to reuse allocations. - let untyped_phase_indirect_parameters_buffers = mem::replace( - &mut phase_indirect_parameters_buffers.buffers, - UntypedPhaseIndirectParametersBuffers::new( - indirect_parameters_buffers.allow_copies_from_indirect_parameter_buffers, - ), - ); - if let Some(mut old_untyped_phase_indirect_parameters_buffers) = indirect_parameters_buffers - .insert( - TypeId::of::(), - untyped_phase_indirect_parameters_buffers, - ) - { - old_untyped_phase_indirect_parameters_buffers.clear(); - phase_indirect_parameters_buffers.buffers = old_untyped_phase_indirect_parameters_buffers; - } -} - -/// A system that writes all instance buffers to the GPU. -pub fn write_batched_instance_buffers( - render_device: Res, - render_queue: Res, - gpu_array_buffer: ResMut>, -) where - GFBD: GetFullBatchData, -{ - let BatchedInstanceBuffers { - current_input_buffer, - previous_input_buffer, - phase_instance_buffers, - } = gpu_array_buffer.into_inner(); - - let render_device = &*render_device; - let render_queue = &*render_queue; - - ComputeTaskPool::get().scope(|scope| { - scope.spawn(async { - let _span = tracing::info_span!("write_current_input_buffers").entered(); - current_input_buffer - .buffer - .write_buffer(render_device, render_queue); - }); - scope.spawn(async { - let _span = tracing::info_span!("write_previous_input_buffers").entered(); - previous_input_buffer - .buffer - .write_buffer(render_device, render_queue); - }); - - for phase_instance_buffers in phase_instance_buffers.values_mut() { - let UntypedPhaseBatchedInstanceBuffers { - ref mut data_buffer, - ref mut work_item_buffers, - ref mut late_indexed_indirect_parameters_buffer, - ref mut late_non_indexed_indirect_parameters_buffer, - } = *phase_instance_buffers; - - scope.spawn(async { - let _span = tracing::info_span!("write_phase_instance_buffers").entered(); - data_buffer.write_buffer(render_device); - late_indexed_indirect_parameters_buffer.write_buffer(render_device, render_queue); - late_non_indexed_indirect_parameters_buffer - .write_buffer(render_device, render_queue); - }); - - for phase_work_item_buffers in work_item_buffers.values_mut() { - scope.spawn(async { - let _span = tracing::info_span!("write_work_item_buffers").entered(); - match *phase_work_item_buffers { - PreprocessWorkItemBuffers::Direct(ref mut buffer_vec) => { - buffer_vec.write_buffer(render_device, render_queue); - } - PreprocessWorkItemBuffers::Indirect { - ref mut indexed, - ref mut non_indexed, - ref mut gpu_occlusion_culling, - } => { - indexed.write_buffer(render_device, render_queue); - non_indexed.write_buffer(render_device, render_queue); - - if let Some(GpuOcclusionCullingWorkItemBuffers { - ref mut late_indexed, - ref mut late_non_indexed, - late_indirect_parameters_indexed_offset: _, - late_indirect_parameters_non_indexed_offset: _, - }) = *gpu_occlusion_culling - { - if !late_indexed.is_empty() { - late_indexed.write_buffer(render_device); - } - if !late_non_indexed.is_empty() { - late_non_indexed.write_buffer(render_device); - } - } - } - } - }); - } - } - }); -} - -pub fn clear_indirect_parameters_buffers( - mut indirect_parameters_buffers: ResMut, -) { - for phase_indirect_parameters_buffers in indirect_parameters_buffers.values_mut() { - phase_indirect_parameters_buffers.clear(); - } -} - -pub fn write_indirect_parameters_buffers( - render_device: Res, - render_queue: Res, - mut indirect_parameters_buffers: ResMut, -) { - let render_device = &*render_device; - let render_queue = &*render_queue; - ComputeTaskPool::get().scope(|scope| { - for phase_indirect_parameters_buffers in indirect_parameters_buffers.values_mut() { - scope.spawn(async { - let _span = tracing::info_span!("indexed_data").entered(); - phase_indirect_parameters_buffers - .indexed - .data - .write_buffer(render_device); - }); - scope.spawn(async { - let _span = tracing::info_span!("non_indexed_data").entered(); - phase_indirect_parameters_buffers - .non_indexed - .data - .write_buffer(render_device); - }); - - scope.spawn(async { - let _span = tracing::info_span!("indexed_cpu_metadata").entered(); - phase_indirect_parameters_buffers - .indexed - .cpu_metadata - .write_buffer(render_device, render_queue); - }); - scope.spawn(async { - let _span = tracing::info_span!("non_indexed_cpu_metadata").entered(); - phase_indirect_parameters_buffers - .non_indexed - .cpu_metadata - .write_buffer(render_device, render_queue); - }); - - scope.spawn(async { - let _span = tracing::info_span!("non_indexed_gpu_metadata").entered(); - phase_indirect_parameters_buffers - .non_indexed - .gpu_metadata - .write_buffer(render_device); - }); - scope.spawn(async { - let _span = tracing::info_span!("indexed_gpu_metadata").entered(); - phase_indirect_parameters_buffers - .indexed - .gpu_metadata - .write_buffer(render_device); - }); - - scope.spawn(async { - let _span = tracing::info_span!("indexed_batch_sets").entered(); - phase_indirect_parameters_buffers - .indexed - .batch_sets - .write_buffer(render_device, render_queue); - }); - scope.spawn(async { - let _span = tracing::info_span!("non_indexed_batch_sets").entered(); - phase_indirect_parameters_buffers - .non_indexed - .batch_sets - .write_buffer(render_device, render_queue); - }); - } - }); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn instance_buffer_correct_behavior() { - let mut instance_buffer = InstanceInputUniformBuffer::new(); - - let index = instance_buffer.add(2); - instance_buffer.remove(index); - assert_eq!(instance_buffer.get_unchecked(index), 2); - assert_eq!(instance_buffer.get(index), None); - - instance_buffer.add(5); - assert_eq!(instance_buffer.buffer().len(), 1); - } -} diff --git a/third_party/bevy_render/src/batching/mod.rs b/third_party/bevy_render/src/batching/mod.rs deleted file mode 100644 index bceca62..0000000 --- a/third_party/bevy_render/src/batching/mod.rs +++ /dev/null @@ -1,225 +0,0 @@ -use bevy_ecs::{ - component::Component, - entity::Entity, - system::{ResMut, SystemParam, SystemParamItem}, -}; -use bytemuck::Pod; -use gpu_preprocessing::UntypedPhaseIndirectParametersBuffers; -use nonmax::NonMaxU32; - -use crate::{ - render_phase::{ - BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItemExtraIndex, - SortedPhaseItem, SortedRenderPhase, ViewBinnedRenderPhases, - }, - render_resource::{CachedRenderPipelineId, GpuArrayBufferable}, - sync_world::MainEntity, -}; - -pub mod gpu_preprocessing; -pub mod no_gpu_preprocessing; - -/// Add this component to mesh entities to disable automatic batching -#[derive(Component, Default, Clone, Copy)] -pub struct NoAutomaticBatching; - -/// Data necessary to be equal for two draw commands to be mergeable -/// -/// This is based on the following assumptions: -/// - Only entities with prepared assets (pipelines, materials, meshes) are -/// queued to phases -/// - View bindings are constant across a phase for a given draw function as -/// phases are per-view -/// - `batch_and_prepare_render_phase` is the only system that performs this -/// batching and has sole responsibility for preparing the per-object data. -/// As such the mesh binding and dynamic offsets are assumed to only be -/// variable as a result of the `batch_and_prepare_render_phase` system, e.g. -/// due to having to split data across separate uniform bindings within the -/// same buffer due to the maximum uniform buffer binding size. -#[derive(PartialEq)] -struct BatchMeta { - /// The pipeline id encompasses all pipeline configuration including vertex - /// buffers and layouts, shaders and their specializations, bind group - /// layouts, etc. - pipeline_id: CachedRenderPipelineId, - /// The draw function id defines the `RenderCommands` that are called to - /// set the pipeline and bindings, and make the draw command - draw_function_id: DrawFunctionId, - dynamic_offset: Option, - user_data: T, -} - -impl BatchMeta { - fn new(item: &impl CachedRenderPipelinePhaseItem, user_data: T) -> Self { - BatchMeta { - pipeline_id: item.cached_pipeline(), - draw_function_id: item.draw_function(), - dynamic_offset: match item.extra_index() { - PhaseItemExtraIndex::DynamicOffset(dynamic_offset) => { - NonMaxU32::new(dynamic_offset) - } - PhaseItemExtraIndex::None | PhaseItemExtraIndex::IndirectParametersIndex { .. } => { - None - } - }, - user_data, - } - } -} - -/// A trait to support getting data used for batching draw commands via phase -/// items. -/// -/// This is a simple version that only allows for sorting, not binning, as well -/// as only CPU processing, not GPU preprocessing. For these fancier features, -/// see [`GetFullBatchData`]. -pub trait GetBatchData { - /// The system parameters [`GetBatchData::get_batch_data`] needs in - /// order to compute the batch data. - type Param: SystemParam + 'static; - /// Data used for comparison between phase items. If the pipeline id, draw - /// function id, per-instance data buffer dynamic offset and this data - /// matches, the draws can be batched. - type CompareData: PartialEq; - /// The per-instance data to be inserted into the - /// [`crate::render_resource::GpuArrayBuffer`] containing these data for all - /// instances. - type BufferData: GpuArrayBufferable + Sync + Send + 'static; - /// Get the per-instance data to be inserted into the - /// [`crate::render_resource::GpuArrayBuffer`]. If the instance can be - /// batched, also return the data used for comparison when deciding whether - /// draws can be batched, else return None for the `CompareData`. - /// - /// This is only called when building instance data on CPU. In the GPU - /// instance data building path, we use - /// [`GetFullBatchData::get_index_and_compare_data`] instead. - fn get_batch_data( - param: &SystemParamItem, - query_item: (Entity, MainEntity), - ) -> Option<(Self::BufferData, Option)>; -} - -/// A trait to support getting data used for batching draw commands via phase -/// items. -/// -/// This version allows for binning and GPU preprocessing. -pub trait GetFullBatchData: GetBatchData { - /// The per-instance data that was inserted into the - /// [`crate::render_resource::BufferVec`] during extraction. - type BufferInputData: Pod + Default + Sync + Send; - - /// Get the per-instance data to be inserted into the - /// [`crate::render_resource::GpuArrayBuffer`]. - /// - /// This is only called when building uniforms on CPU. In the GPU instance - /// buffer building path, we use - /// [`GetFullBatchData::get_index_and_compare_data`] instead. - fn get_binned_batch_data( - param: &SystemParamItem, - query_item: MainEntity, - ) -> Option; - - /// Returns the index of the [`GetFullBatchData::BufferInputData`] that the - /// GPU preprocessing phase will use. - /// - /// We already inserted the [`GetFullBatchData::BufferInputData`] during the - /// extraction phase before we got here, so this function shouldn't need to - /// look up any render data. If CPU instance buffer building is in use, this - /// function will never be called. - fn get_index_and_compare_data( - param: &SystemParamItem, - query_item: MainEntity, - ) -> Option<(NonMaxU32, Option)>; - - /// Returns the index of the [`GetFullBatchData::BufferInputData`] that the - /// GPU preprocessing phase will use. - /// - /// We already inserted the [`GetFullBatchData::BufferInputData`] during the - /// extraction phase before we got here, so this function shouldn't need to - /// look up any render data. - /// - /// This function is currently only called for unbatchable entities when GPU - /// instance buffer building is in use. For batchable entities, the uniform - /// index is written during queuing (e.g. in `queue_material_meshes`). In - /// the case of CPU instance buffer building, the CPU writes the uniforms, - /// so there's no index to return. - fn get_binned_index( - param: &SystemParamItem, - query_item: MainEntity, - ) -> Option; - - /// Writes the [`gpu_preprocessing::IndirectParametersGpuMetadata`] - /// necessary to draw this batch into the given metadata buffer at the given - /// index. - /// - /// This is only used if GPU culling is enabled (which requires GPU - /// preprocessing). - /// - /// * `indexed` is true if the mesh is indexed or false if it's non-indexed. - /// - /// * `base_output_index` is the index of the first mesh instance in this - /// batch in the `MeshUniform` output buffer. - /// - /// * `batch_set_index` is the index of the batch set in the - /// [`gpu_preprocessing::IndirectBatchSet`] buffer, if this batch belongs to - /// a batch set. - /// - /// * `indirect_parameters_buffers` is the buffer in which to write the - /// metadata. - /// - /// * `indirect_parameters_offset` is the index in that buffer at which to - /// write the metadata. - fn write_batch_indirect_parameters_metadata( - indexed: bool, - base_output_index: u32, - batch_set_index: Option, - indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers, - indirect_parameters_offset: u32, - ); -} - -/// Sorts a render phase that uses bins. -pub fn sort_binned_render_phase(mut phases: ResMut>) -where - BPI: BinnedPhaseItem, -{ - for phase in phases.values_mut() { - phase.multidrawable_meshes.sort_unstable_keys(); - phase.batchable_meshes.sort_unstable_keys(); - phase.unbatchable_meshes.sort_unstable_keys(); - phase.non_mesh_items.sort_unstable_keys(); - } -} - -/// Batches the items in a sorted render phase. -/// -/// This means comparing metadata needed to draw each phase item and trying to -/// combine the draws into a batch. -/// -/// This is common code factored out from -/// [`gpu_preprocessing::batch_and_prepare_sorted_render_phase`] and -/// [`no_gpu_preprocessing::batch_and_prepare_sorted_render_phase`]. -fn batch_and_prepare_sorted_render_phase( - phase: &mut SortedRenderPhase, - mut process_item: impl FnMut(&mut I) -> Option, -) where - I: CachedRenderPipelinePhaseItem + SortedPhaseItem, - GBD: GetBatchData, -{ - let items = phase.items.iter_mut().map(|item| { - let batch_data = match process_item(item) { - Some(compare_data) if I::AUTOMATIC_BATCHING => Some(BatchMeta::new(item, compare_data)), - _ => None, - }; - (item.batch_range_mut(), batch_data) - }); - - items.reduce(|(start_range, prev_batch_meta), (range, batch_meta)| { - if batch_meta.is_some() && prev_batch_meta == batch_meta { - start_range.end = range.end; - (start_range, prev_batch_meta) - } else { - (range, batch_meta) - } - }); -} diff --git a/third_party/bevy_render/src/batching/no_gpu_preprocessing.rs b/third_party/bevy_render/src/batching/no_gpu_preprocessing.rs deleted file mode 100644 index 66ba078..0000000 --- a/third_party/bevy_render/src/batching/no_gpu_preprocessing.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! Batching functionality when GPU preprocessing isn't in use. - -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::entity::Entity; -use bevy_ecs::resource::Resource; -use bevy_ecs::system::{Res, ResMut, StaticSystemParam}; -use smallvec::{smallvec, SmallVec}; -use tracing::error; -use wgpu::{BindingResource, Limits}; - -use crate::{ - render_phase::{ - BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSets, - CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, SortedPhaseItem, - ViewBinnedRenderPhases, ViewSortedRenderPhases, - }, - render_resource::{GpuArrayBuffer, GpuArrayBufferable}, - renderer::{RenderDevice, RenderQueue}, -}; - -use super::{GetBatchData, GetFullBatchData}; - -/// The GPU buffers holding the data needed to render batches. -/// -/// For example, in the 3D PBR pipeline this holds `MeshUniform`s, which are the -/// `BD` type parameter in that mode. -#[derive(Resource, Deref, DerefMut)] -pub struct BatchedInstanceBuffer(pub GpuArrayBuffer) -where - BD: GpuArrayBufferable + Sync + Send + 'static; - -impl BatchedInstanceBuffer -where - BD: GpuArrayBufferable + Sync + Send + 'static, -{ - /// Creates a new buffer. - pub fn new(limits: &Limits) -> Self { - BatchedInstanceBuffer(GpuArrayBuffer::new(limits)) - } - - /// Returns the binding of the buffer that contains the per-instance data. - /// - /// If we're in the GPU instance buffer building mode, this buffer needs to - /// be filled in via a compute shader. - pub fn instance_data_binding(&self) -> Option> { - self.binding() - } -} - -/// A system that clears out the [`BatchedInstanceBuffer`] for the frame. -/// -/// This needs to run before the CPU batched instance buffers are used. -pub fn clear_batched_cpu_instance_buffers( - cpu_batched_instance_buffer: Option>>, -) where - GBD: GetBatchData, -{ - if let Some(mut cpu_batched_instance_buffer) = cpu_batched_instance_buffer { - cpu_batched_instance_buffer.clear(); - } -} - -/// Batch the items in a sorted render phase, when GPU instance buffer building -/// isn't in use. This means comparing metadata needed to draw each phase item -/// and trying to combine the draws into a batch. -pub fn batch_and_prepare_sorted_render_phase( - batched_instance_buffer: ResMut>, - mut phases: ResMut>, - param: StaticSystemParam, -) where - I: CachedRenderPipelinePhaseItem + SortedPhaseItem, - GBD: GetBatchData, -{ - let system_param_item = param.into_inner(); - - // We only process CPU-built batch data in this function. - let batched_instance_buffer = batched_instance_buffer.into_inner(); - - for phase in phases.values_mut() { - super::batch_and_prepare_sorted_render_phase::(phase, |item| { - let (buffer_data, compare_data) = - GBD::get_batch_data(&system_param_item, (item.entity(), item.main_entity()))?; - let buffer_index = batched_instance_buffer.push(buffer_data); - - let index = buffer_index.index; - let (batch_range, extra_index) = item.batch_range_and_extra_index_mut(); - *batch_range = index..index + 1; - *extra_index = PhaseItemExtraIndex::maybe_dynamic_offset(buffer_index.dynamic_offset); - - compare_data - }); - } -} - -/// Creates batches for a render phase that uses bins, when GPU batch data -/// building isn't in use. -pub fn batch_and_prepare_binned_render_phase( - gpu_array_buffer: ResMut>, - mut phases: ResMut>, - param: StaticSystemParam, -) where - BPI: BinnedPhaseItem, - GFBD: GetFullBatchData, -{ - let gpu_array_buffer = gpu_array_buffer.into_inner(); - let system_param_item = param.into_inner(); - - for phase in phases.values_mut() { - // Prepare batchables. - - for bin in phase.batchable_meshes.values_mut() { - let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![]; - for main_entity in bin.entities().keys() { - let Some(buffer_data) = - GFBD::get_binned_batch_data(&system_param_item, *main_entity) - else { - continue; - }; - let instance = gpu_array_buffer.push(buffer_data); - - // If the dynamic offset has changed, flush the batch. - // - // This is the only time we ever have more than one batch per - // bin. Note that dynamic offsets are only used on platforms - // with no storage buffers. - if !batch_set.last().is_some_and(|batch| { - batch.instance_range.end == instance.index - && batch.extra_index - == PhaseItemExtraIndex::maybe_dynamic_offset(instance.dynamic_offset) - }) { - batch_set.push(BinnedRenderPhaseBatch { - representative_entity: (Entity::PLACEHOLDER, *main_entity), - instance_range: instance.index..instance.index, - extra_index: PhaseItemExtraIndex::maybe_dynamic_offset( - instance.dynamic_offset, - ), - }); - } - - if let Some(batch) = batch_set.last_mut() { - batch.instance_range.end = instance.index + 1; - } - } - - match phase.batch_sets { - BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut batch_sets) => { - batch_sets.push(batch_set); - } - BinnedRenderPhaseBatchSets::Direct(_) - | BinnedRenderPhaseBatchSets::MultidrawIndirect { .. } => { - error!( - "Dynamic uniform batch sets should be used when GPU preprocessing is off" - ); - } - } - } - - // Prepare unbatchables. - for unbatchables in phase.unbatchable_meshes.values_mut() { - for main_entity in unbatchables.entities.keys() { - let Some(buffer_data) = - GFBD::get_binned_batch_data(&system_param_item, *main_entity) - else { - continue; - }; - let instance = gpu_array_buffer.push(buffer_data); - unbatchables.buffer_indices.add(instance.into()); - } - } - } -} - -/// Writes the instance buffer data to the GPU. -pub fn write_batched_instance_buffer( - render_device: Res, - render_queue: Res, - mut cpu_batched_instance_buffer: ResMut>, -) where - GBD: GetBatchData, -{ - cpu_batched_instance_buffer.write_buffer(&render_device, &render_queue); -} diff --git a/third_party/bevy_render/src/bindless.wgsl b/third_party/bevy_render/src/bindless.wgsl deleted file mode 100644 index 6c8eff1..0000000 --- a/third_party/bevy_render/src/bindless.wgsl +++ /dev/null @@ -1,37 +0,0 @@ -// Defines the common arrays used to access bindless resources. -// -// This need to be kept up to date with the `BINDING_NUMBERS` table in -// `bindless.rs`. -// -// You access these by indexing into the bindless index table, and from there -// indexing into the appropriate binding array. For example, to access the base -// color texture of a `StandardMaterial` in bindless mode, write -// `bindless_textures_2d[materials[slot].base_color_texture]`, where -// `materials` is the bindless index table and `slot` is the index into that -// table (which can be found in the `Mesh`). - -#define_import_path bevy_render::bindless - -#ifdef BINDLESS - -// Binding 0 is the bindless index table. -// Filtering samplers. -@group(#{MATERIAL_BIND_GROUP}) @binding(1) var bindless_samplers_filtering: binding_array; -// Non-filtering samplers (nearest neighbor). -@group(#{MATERIAL_BIND_GROUP}) @binding(2) var bindless_samplers_non_filtering: binding_array; -// Comparison samplers (typically for shadow mapping). -@group(#{MATERIAL_BIND_GROUP}) @binding(3) var bindless_samplers_comparison: binding_array; -// 1D textures. -@group(#{MATERIAL_BIND_GROUP}) @binding(4) var bindless_textures_1d: binding_array>; -// 2D textures. -@group(#{MATERIAL_BIND_GROUP}) @binding(5) var bindless_textures_2d: binding_array>; -// 2D array textures. -@group(#{MATERIAL_BIND_GROUP}) @binding(6) var bindless_textures_2d_array: binding_array>; -// 3D textures. -@group(#{MATERIAL_BIND_GROUP}) @binding(7) var bindless_textures_3d: binding_array>; -// Cubemap textures. -@group(#{MATERIAL_BIND_GROUP}) @binding(8) var bindless_textures_cube: binding_array>; -// Cubemap array textures. -@group(#{MATERIAL_BIND_GROUP}) @binding(9) var bindless_textures_cube_array: binding_array>; - -#endif // BINDLESS diff --git a/third_party/bevy_render/src/camera.rs b/third_party/bevy_render/src/camera.rs deleted file mode 100644 index 27dac44..0000000 --- a/third_party/bevy_render/src/camera.rs +++ /dev/null @@ -1,702 +0,0 @@ -use crate::{ - batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, - extract_component::{ExtractComponent, ExtractComponentPlugin}, - extract_resource::{ExtractResource, ExtractResourcePlugin}, - render_asset::RenderAssets, - render_graph::{CameraDriverNode, InternedRenderSubGraph, RenderGraph, RenderSubGraph}, - render_resource::TextureView, - sync_world::{RenderEntity, SyncToRenderWorld}, - texture::{GpuImage, ManualTextureViews}, - view::{ - ColorGrading, ExtractedView, ExtractedWindows, Hdr, Msaa, NoIndirectDrawing, - RenderVisibleEntities, RetainedViewEntity, ViewUniformOffset, - }, - Extract, ExtractSchedule, Render, RenderApp, RenderSystems, -}; - -use bevy_app::{App, Plugin, PostStartup, PostUpdate}; -use bevy_asset::{AssetEvent, AssetEventSystems, AssetId, Assets}; -use bevy_camera::{ - primitives::Frustum, - visibility::{self, RenderLayers, VisibleEntities}, - Camera, Camera2d, Camera3d, CameraMainTextureUsages, CameraOutputMode, CameraUpdateSystems, - ClearColor, ClearColorConfig, Exposure, ManualTextureViewHandle, MsaaWriteback, - NormalizedRenderTarget, Projection, RenderTarget, RenderTargetInfo, Viewport, -}; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{ - change_detection::DetectChanges, - component::Component, - entity::{ContainsEntity, Entity}, - error::BevyError, - lifecycle::HookContext, - message::MessageReader, - prelude::With, - query::{Has, QueryItem}, - reflect::ReflectComponent, - resource::Resource, - schedule::IntoScheduleConfigs, - system::{Commands, Query, Res, ResMut}, - world::DeferredWorld, -}; -use bevy_image::Image; -use bevy_math::{uvec2, vec2, Mat4, URect, UVec2, UVec4, Vec2}; -use bevy_platform::collections::{HashMap, HashSet}; -use bevy_reflect::prelude::*; -use bevy_transform::components::GlobalTransform; -use bevy_window::{PrimaryWindow, Window, WindowCreated, WindowResized, WindowScaleFactorChanged}; -use tracing::warn; -use wgpu::TextureFormat; - -#[derive(Default)] -pub struct CameraPlugin; - -impl Plugin for CameraPlugin { - fn build(&self, app: &mut App) { - app.register_required_components::() - .register_required_components::() - .register_required_components::() - .register_required_components::() - .add_plugins(( - ExtractResourcePlugin::::default(), - ExtractComponentPlugin::::default(), - )) - .add_systems(PostStartup, camera_system.in_set(CameraUpdateSystems)) - .add_systems( - PostUpdate, - camera_system - .in_set(CameraUpdateSystems) - .before(AssetEventSystems) - .before(visibility::update_frusta), - ); - app.world_mut() - .register_component_hooks::() - .on_add(warn_on_no_render_graph); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .add_systems(ExtractSchedule, extract_cameras) - .add_systems(Render, sort_cameras.in_set(RenderSystems::ManageViews)); - let camera_driver_node = CameraDriverNode::new(render_app.world_mut()); - let mut render_graph = render_app.world_mut().resource_mut::(); - render_graph.add_node(crate::graph::CameraDriverLabel, camera_driver_node); - } - } -} - -fn warn_on_no_render_graph(world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) { - if !world.entity(entity).contains::() { - warn!("{}Entity {entity} has a `Camera` component, but it doesn't have a render graph configured. Usually, adding a `Camera2d` or `Camera3d` component will work. - However, you may instead need to enable `bevy_core_pipeline`, or may want to manually add a `CameraRenderGraph` component to create a custom render graph.", caller.map(|location|format!("{location}: ")).unwrap_or_default()); - } -} - -impl ExtractResource for ClearColor { - type Source = Self; - - fn extract_resource(source: &Self::Source) -> Self { - source.clone() - } -} -impl ExtractComponent for CameraMainTextureUsages { - type QueryData = &'static Self; - type QueryFilter = (); - type Out = Self; - - fn extract_component(item: QueryItem) -> Option { - Some(*item) - } -} -impl ExtractComponent for Camera2d { - type QueryData = &'static Self; - type QueryFilter = With; - type Out = Self; - - fn extract_component(item: QueryItem) -> Option { - Some(item.clone()) - } -} -impl ExtractComponent for Camera3d { - type QueryData = &'static Self; - type QueryFilter = With; - type Out = Self; - - fn extract_component(item: QueryItem) -> Option { - Some(item.clone()) - } -} - -/// Configures the [`RenderGraph`] name assigned to be run for a given [`Camera`] entity. -#[derive(Component, Debug, Deref, DerefMut, Reflect, Clone)] -#[reflect(opaque)] -#[reflect(Component, Debug, Clone)] -pub struct CameraRenderGraph(InternedRenderSubGraph); - -impl CameraRenderGraph { - /// Creates a new [`CameraRenderGraph`] from any string-like type. - #[inline] - pub fn new(name: T) -> Self { - Self(name.intern()) - } - - /// Sets the graph name. - #[inline] - pub fn set(&mut self, name: T) { - self.0 = name.intern(); - } -} - -pub trait NormalizedRenderTargetExt { - fn get_texture_view<'a>( - &self, - windows: &'a ExtractedWindows, - images: &'a RenderAssets, - manual_texture_views: &'a ManualTextureViews, - ) -> Option<&'a TextureView>; - - /// Retrieves the [`TextureFormat`] of this render target, if it exists. - fn get_texture_view_format<'a>( - &self, - windows: &'a ExtractedWindows, - images: &'a RenderAssets, - manual_texture_views: &'a ManualTextureViews, - ) -> Option; - - fn get_render_target_info<'a>( - &self, - resolutions: impl IntoIterator, - images: &Assets, - manual_texture_views: &ManualTextureViews, - ) -> Result; - - // Check if this render target is contained in the given changed windows or images. - fn is_changed( - &self, - changed_window_ids: &HashSet, - changed_image_handles: &HashSet<&AssetId>, - ) -> bool; -} - -impl NormalizedRenderTargetExt for NormalizedRenderTarget { - fn get_texture_view<'a>( - &self, - windows: &'a ExtractedWindows, - images: &'a RenderAssets, - manual_texture_views: &'a ManualTextureViews, - ) -> Option<&'a TextureView> { - match self { - NormalizedRenderTarget::Window(window_ref) => windows - .get(&window_ref.entity()) - .and_then(|window| window.swap_chain_texture_view.as_ref()), - NormalizedRenderTarget::Image(image_target) => images - .get(&image_target.handle) - .map(|image| &image.texture_view), - NormalizedRenderTarget::TextureView(id) => { - manual_texture_views.get(id).map(|tex| &tex.texture_view) - } - NormalizedRenderTarget::None { .. } => None, - } - } - - /// Retrieves the texture view's [`TextureFormat`] of this render target, if it exists. - fn get_texture_view_format<'a>( - &self, - windows: &'a ExtractedWindows, - images: &'a RenderAssets, - manual_texture_views: &'a ManualTextureViews, - ) -> Option { - match self { - NormalizedRenderTarget::Window(window_ref) => windows - .get(&window_ref.entity()) - .and_then(|window| window.swap_chain_texture_view_format), - NormalizedRenderTarget::Image(image_target) => images - .get(&image_target.handle) - .map(|image| image.texture_view_format.unwrap_or(image.texture_format)), - NormalizedRenderTarget::TextureView(id) => { - manual_texture_views.get(id).map(|tex| tex.view_format) - } - NormalizedRenderTarget::None { .. } => None, - } - } - - fn get_render_target_info<'a>( - &self, - resolutions: impl IntoIterator, - images: &Assets, - manual_texture_views: &ManualTextureViews, - ) -> Result { - match self { - NormalizedRenderTarget::Window(window_ref) => resolutions - .into_iter() - .find(|(entity, _)| *entity == window_ref.entity()) - .map(|(_, window)| RenderTargetInfo { - physical_size: window.physical_size(), - scale_factor: window.resolution.scale_factor(), - }) - .ok_or(MissingRenderTargetInfoError::Window { - window: window_ref.entity(), - }), - NormalizedRenderTarget::Image(image_target) => images - .get(&image_target.handle) - .map(|image| RenderTargetInfo { - physical_size: image.size(), - scale_factor: image_target.scale_factor, - }) - .ok_or(MissingRenderTargetInfoError::Image { - image: image_target.handle.id(), - }), - NormalizedRenderTarget::TextureView(id) => manual_texture_views - .get(id) - .map(|tex| RenderTargetInfo { - physical_size: tex.size, - scale_factor: 1.0, - }) - .ok_or(MissingRenderTargetInfoError::TextureView { texture_view: *id }), - NormalizedRenderTarget::None { width, height } => Ok(RenderTargetInfo { - physical_size: uvec2(*width, *height), - scale_factor: 1.0, - }), - } - } - - // Check if this render target is contained in the given changed windows or images. - fn is_changed( - &self, - changed_window_ids: &HashSet, - changed_image_handles: &HashSet<&AssetId>, - ) -> bool { - match self { - NormalizedRenderTarget::Window(window_ref) => { - changed_window_ids.contains(&window_ref.entity()) - } - NormalizedRenderTarget::Image(image_target) => { - changed_image_handles.contains(&image_target.handle.id()) - } - NormalizedRenderTarget::TextureView(_) => true, - NormalizedRenderTarget::None { .. } => false, - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum MissingRenderTargetInfoError { - #[error("RenderTarget::Window missing ({window:?}): Make sure the provided entity has a Window component.")] - Window { window: Entity }, - #[error("RenderTarget::Image missing ({image:?}): Make sure the Image's usages include RenderAssetUsages::MAIN_WORLD.")] - Image { image: AssetId }, - #[error("RenderTarget::TextureView missing ({texture_view:?}): make sure the texture view handle was not removed.")] - TextureView { - texture_view: ManualTextureViewHandle, - }, -} - -/// System in charge of updating a [`Camera`] when its window or projection changes. -/// -/// The system detects window creation, resize, and scale factor change events to update the camera -/// [`Projection`] if needed. -/// -/// ## World Resources -/// -/// [`Res>`](Assets) -- For cameras that render to an image, this resource is used to -/// inspect information about the render target. This system will not access any other image assets. -/// -/// [`OrthographicProjection`]: bevy_camera::OrthographicProjection -/// [`PerspectiveProjection`]: bevy_camera::PerspectiveProjection -pub fn camera_system( - mut window_resized_reader: MessageReader, - mut window_created_reader: MessageReader, - mut window_scale_factor_changed_reader: MessageReader, - mut image_asset_event_reader: MessageReader>, - primary_window: Query>, - windows: Query<(Entity, &Window)>, - images: Res>, - manual_texture_views: Res, - mut cameras: Query<(&mut Camera, &RenderTarget, &mut Projection)>, -) -> Result<(), BevyError> { - let primary_window = primary_window.iter().next(); - - let mut changed_window_ids = >::default(); - changed_window_ids.extend(window_created_reader.read().map(|event| event.window)); - changed_window_ids.extend(window_resized_reader.read().map(|event| event.window)); - let scale_factor_changed_window_ids: HashSet<_> = window_scale_factor_changed_reader - .read() - .map(|event| event.window) - .collect(); - changed_window_ids.extend(scale_factor_changed_window_ids.clone()); - - let changed_image_handles: HashSet<&AssetId> = image_asset_event_reader - .read() - .filter_map(|event| match event { - AssetEvent::Modified { id } | AssetEvent::Added { id } => Some(id), - _ => None, - }) - .collect(); - - for (mut camera, render_target, mut camera_projection) in &mut cameras { - let mut viewport_size = camera - .viewport - .as_ref() - .map(|viewport| viewport.physical_size); - - if let Some(normalized_target) = render_target.normalize(primary_window) - && (normalized_target.is_changed(&changed_window_ids, &changed_image_handles) - || camera.is_added() - || camera_projection.is_changed() - || camera.computed.old_viewport_size != viewport_size - || camera.computed.old_sub_camera_view != camera.sub_camera_view) - { - let new_computed_target_info = normalized_target.get_render_target_info( - windows, - &images, - &manual_texture_views, - )?; - // Check for the scale factor changing, and resize the viewport if needed. - // This can happen when the window is moved between monitors with different DPIs. - // Without this, the viewport will take a smaller portion of the window moved to - // a higher DPI monitor. - if normalized_target.is_changed(&scale_factor_changed_window_ids, &HashSet::default()) - && let Some(old_scale_factor) = camera - .computed - .target_info - .as_ref() - .map(|info| info.scale_factor) - { - let resize_factor = new_computed_target_info.scale_factor / old_scale_factor; - if let Some(ref mut viewport) = camera.viewport { - let resize = |vec: UVec2| (vec.as_vec2() * resize_factor).as_uvec2(); - viewport.physical_position = resize(viewport.physical_position); - viewport.physical_size = resize(viewport.physical_size); - viewport_size = Some(viewport.physical_size); - } - } - // This check is needed because when changing WindowMode to Fullscreen, the viewport may have invalid - // arguments due to a sudden change on the window size to a lower value. - // If the size of the window is lower, the viewport will match that lower value. - if let Some(viewport) = &mut camera.viewport { - viewport.clamp_to_size(new_computed_target_info.physical_size); - } - camera.computed.target_info = Some(new_computed_target_info); - if let Some(size) = camera.logical_viewport_size() - && size.x != 0.0 - && size.y != 0.0 - { - camera_projection.update(size.x, size.y); - camera.computed.clip_from_view = match &camera.sub_camera_view { - Some(sub_view) => camera_projection.get_clip_from_view_for_sub(sub_view), - None => camera_projection.get_clip_from_view(), - } - } - } - - if camera.computed.old_viewport_size != viewport_size { - camera.computed.old_viewport_size = viewport_size; - } - - if camera.computed.old_sub_camera_view != camera.sub_camera_view { - camera.computed.old_sub_camera_view = camera.sub_camera_view; - } - } - Ok(()) -} - -#[derive(Component, Debug)] -pub struct ExtractedCamera { - pub target: Option, - pub physical_viewport_size: Option, - pub physical_target_size: Option, - pub viewport: Option, - pub render_graph: InternedRenderSubGraph, - pub order: isize, - pub output_mode: CameraOutputMode, - pub msaa_writeback: MsaaWriteback, - pub clear_color: ClearColorConfig, - pub sorted_camera_index_for_target: usize, - pub exposure: f32, - pub hdr: bool, -} - -pub fn extract_cameras( - mut commands: Commands, - query: Extract< - Query<( - Entity, - RenderEntity, - &Camera, - &RenderTarget, - &CameraRenderGraph, - &GlobalTransform, - &VisibleEntities, - &Frustum, - ( - Has, - Option<&ColorGrading>, - Option<&Exposure>, - Option<&TemporalJitter>, - Option<&MipBias>, - Option<&RenderLayers>, - Option<&Projection>, - Has, - ), - )>, - >, - primary_window: Extract>>, - gpu_preprocessing_support: Res, - mapper: Extract>, -) { - let primary_window = primary_window.iter().next(); - type ExtractedCameraComponents = ( - ExtractedCamera, - ExtractedView, - RenderVisibleEntities, - TemporalJitter, - MipBias, - RenderLayers, - Projection, - NoIndirectDrawing, - ViewUniformOffset, - ); - for ( - main_entity, - render_entity, - camera, - render_target, - camera_render_graph, - transform, - visible_entities, - frustum, - ( - hdr, - color_grading, - exposure, - temporal_jitter, - mip_bias, - render_layers, - projection, - no_indirect_drawing, - ), - ) in query.iter() - { - if !camera.is_active { - commands - .entity(render_entity) - .remove::(); - continue; - } - - let color_grading = color_grading.unwrap_or(&ColorGrading::default()).clone(); - - if let ( - Some(URect { - min: viewport_origin, - .. - }), - Some(viewport_size), - Some(target_size), - ) = ( - camera.physical_viewport_rect(), - camera.physical_viewport_size(), - camera.physical_target_size(), - ) { - if target_size.x == 0 || target_size.y == 0 { - commands - .entity(render_entity) - .remove::(); - continue; - } - - let render_visible_entities = RenderVisibleEntities { - entities: visible_entities - .entities - .iter() - .map(|(type_id, entities)| { - let entities = entities - .iter() - .map(|entity| { - let render_entity = mapper - .get(*entity) - .cloned() - .map(|entity| entity.id()) - .unwrap_or(Entity::PLACEHOLDER); - (render_entity, (*entity).into()) - }) - .collect(); - (*type_id, entities) - }) - .collect(), - }; - - let mut commands = commands.entity(render_entity); - commands.insert(( - ExtractedCamera { - target: render_target.normalize(primary_window), - viewport: camera.viewport.clone(), - physical_viewport_size: Some(viewport_size), - physical_target_size: Some(target_size), - render_graph: camera_render_graph.0, - order: camera.order, - output_mode: camera.output_mode, - msaa_writeback: camera.msaa_writeback, - clear_color: camera.clear_color, - // this will be set in sort_cameras - sorted_camera_index_for_target: 0, - exposure: exposure - .map(Exposure::exposure) - .unwrap_or_else(|| Exposure::default().exposure()), - hdr, - }, - ExtractedView { - retained_view_entity: RetainedViewEntity::new(main_entity.into(), None, 0), - clip_from_view: camera.clip_from_view(), - world_from_view: *transform, - clip_from_world: None, - hdr, - viewport: UVec4::new( - viewport_origin.x, - viewport_origin.y, - viewport_size.x, - viewport_size.y, - ), - color_grading, - invert_culling: camera.invert_culling, - }, - render_visible_entities, - *frustum, - )); - - if let Some(temporal_jitter) = temporal_jitter { - commands.insert(temporal_jitter.clone()); - } else { - commands.remove::(); - } - - if let Some(mip_bias) = mip_bias { - commands.insert(mip_bias.clone()); - } else { - commands.remove::(); - } - - if let Some(render_layers) = render_layers { - commands.insert(render_layers.clone()); - } else { - commands.remove::(); - } - - if let Some(projection) = projection { - commands.insert(projection.clone()); - } else { - commands.remove::(); - } - - if no_indirect_drawing - || !matches!( - gpu_preprocessing_support.max_supported_mode, - GpuPreprocessingMode::Culling - ) - { - commands.insert(NoIndirectDrawing); - } else { - commands.remove::(); - } - }; - } -} - -/// Cameras sorted by their order field. This is updated in the [`sort_cameras`] system. -#[derive(Resource, Default)] -pub struct SortedCameras(pub Vec); - -pub struct SortedCamera { - pub entity: Entity, - pub order: isize, - pub target: Option, - pub hdr: bool, -} - -pub fn sort_cameras( - mut sorted_cameras: ResMut, - mut cameras: Query<(Entity, &mut ExtractedCamera)>, -) { - sorted_cameras.0.clear(); - for (entity, camera) in cameras.iter() { - sorted_cameras.0.push(SortedCamera { - entity, - order: camera.order, - target: camera.target.clone(), - hdr: camera.hdr, - }); - } - // sort by order and ensure within an order, RenderTargets of the same type are packed together - sorted_cameras - .0 - .sort_by(|c1, c2| (c1.order, &c1.target).cmp(&(c2.order, &c2.target))); - let mut previous_order_target = None; - let mut ambiguities = >::default(); - let mut target_counts = >::default(); - for sorted_camera in &mut sorted_cameras.0 { - let new_order_target = (sorted_camera.order, sorted_camera.target.clone()); - if let Some(previous_order_target) = previous_order_target - && previous_order_target == new_order_target - { - ambiguities.insert(new_order_target.clone()); - } - if let Some(target) = &sorted_camera.target { - let count = target_counts - .entry((target.clone(), sorted_camera.hdr)) - .or_insert(0usize); - let (_, mut camera) = cameras.get_mut(sorted_camera.entity).unwrap(); - camera.sorted_camera_index_for_target = *count; - *count += 1; - } - previous_order_target = Some(new_order_target); - } - - if !ambiguities.is_empty() { - warn!( - "Camera order ambiguities detected for active cameras with the following priorities: {:?}. \ - To fix this, ensure there is exactly one Camera entity spawned with a given order for a given RenderTarget. \ - Ambiguities should be resolved because either (1) multiple active cameras were spawned accidentally, which will \ - result in rendering multiple instances of the scene or (2) for cases where multiple active cameras is intentional, \ - ambiguities could result in unpredictable render results.", - ambiguities - ); - } -} - -/// A subpixel offset to jitter a perspective camera's frustum by. -/// -/// Useful for temporal rendering techniques. -#[derive(Component, Clone, Default, Reflect)] -#[reflect(Default, Component, Clone)] -pub struct TemporalJitter { - /// Offset is in range [-0.5, 0.5]. - pub offset: Vec2, -} - -impl TemporalJitter { - pub fn jitter_projection(&self, clip_from_view: &mut Mat4, view_size: Vec2) { - // https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/d7531ae47d8b36a5d4025663e731a47a38be882f/docs/techniques/media/super-resolution-temporal/jitter-space.svg - let mut jitter = (self.offset * vec2(2.0, -2.0)) / view_size; - - // orthographic - if clip_from_view.w_axis.w == 1.0 { - jitter *= vec2(clip_from_view.x_axis.x, clip_from_view.y_axis.y) * 0.5; - } - - clip_from_view.z_axis.x += jitter.x; - clip_from_view.z_axis.y += jitter.y; - } -} - -/// Camera component specifying a mip bias to apply when sampling from material textures. -/// -/// Often used in conjunction with antialiasing post-process effects to reduce textures blurriness. -#[derive(Component, Reflect, Clone)] -#[reflect(Default, Component)] -pub struct MipBias(pub f32); - -impl Default for MipBias { - fn default() -> Self { - Self(-1.0) - } -} diff --git a/third_party/bevy_render/src/color_operations.wgsl b/third_party/bevy_render/src/color_operations.wgsl deleted file mode 100644 index b68ad2a..0000000 --- a/third_party/bevy_render/src/color_operations.wgsl +++ /dev/null @@ -1,47 +0,0 @@ -#define_import_path bevy_render::color_operations - -#import bevy_render::maths::FRAC_PI_3 - -// Converts HSV to RGB. -// -// Input: H ∈ [0, 2π), S ∈ [0, 1], V ∈ [0, 1]. -// Output: R ∈ [0, 1], G ∈ [0, 1], B ∈ [0, 1]. -// -// -fn hsv_to_rgb(hsv: vec3) -> vec3 { - let n = vec3(5.0, 3.0, 1.0); - let k = (n + hsv.x / FRAC_PI_3) % 6.0; - return hsv.z - hsv.z * hsv.y * max(vec3(0.0), min(k, min(4.0 - k, vec3(1.0)))); -} - -// Converts RGB to HSV. -// -// Input: R ∈ [0, 1], G ∈ [0, 1], B ∈ [0, 1]. -// Output: H ∈ [0, 2π), S ∈ [0, 1], V ∈ [0, 1]. -// -// -fn rgb_to_hsv(rgb: vec3) -> vec3 { - let x_max = max(rgb.r, max(rgb.g, rgb.b)); // i.e. V - let x_min = min(rgb.r, min(rgb.g, rgb.b)); - let c = x_max - x_min; // chroma - - var swizzle = vec3(0.0); - if (x_max == rgb.r) { - swizzle = vec3(rgb.gb, 0.0); - } else if (x_max == rgb.g) { - swizzle = vec3(rgb.br, 2.0); - } else { - swizzle = vec3(rgb.rg, 4.0); - } - - let h = FRAC_PI_3 * (((swizzle.x - swizzle.y) / c + swizzle.z) % 6.0); - - // Avoid division by zero. - var s = 0.0; - if (x_max > 0.0) { - s = c / x_max; - } - - return vec3(h, s, x_max); -} - diff --git a/third_party/bevy_render/src/diagnostic/erased_render_asset_diagnostic_plugin.rs b/third_party/bevy_render/src/diagnostic/erased_render_asset_diagnostic_plugin.rs deleted file mode 100644 index 62cfa68..0000000 --- a/third_party/bevy_render/src/diagnostic/erased_render_asset_diagnostic_plugin.rs +++ /dev/null @@ -1,81 +0,0 @@ -use core::{any::type_name, marker::PhantomData}; - -use bevy_app::{Plugin, PreUpdate}; -use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic}; -use bevy_ecs::{resource::Resource, system::Res}; -use bevy_platform::sync::atomic::{AtomicUsize, Ordering}; - -use crate::{ - erased_render_asset::{ErasedRenderAsset, ErasedRenderAssets}, - Extract, ExtractSchedule, RenderApp, -}; - -/// Collects diagnostics for a [`ErasedRenderAsset`]. -/// -/// If the [`ErasedRenderAsset::ErasedAsset`] is shared between other -/// [`ErasedRenderAsset`], they all will report the same number. -pub struct ErasedRenderAssetDiagnosticPlugin { - suffix: &'static str, - _phantom: PhantomData, -} - -impl ErasedRenderAssetDiagnosticPlugin { - pub fn new(suffix: &'static str) -> Self { - Self { - suffix, - _phantom: PhantomData, - } - } - - pub fn render_asset_diagnostic_path() -> DiagnosticPath { - DiagnosticPath::from_components(["erased_render_asset", type_name::()]) - } -} - -impl Plugin for ErasedRenderAssetDiagnosticPlugin { - fn build(&self, app: &mut bevy_app::App) { - app.register_diagnostic( - Diagnostic::new(Self::render_asset_diagnostic_path()).with_suffix(self.suffix), - ) - .init_resource::>() - .add_systems(PreUpdate, add_erased_render_asset_measurement::); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_systems(ExtractSchedule, measure_erased_render_asset::); - } - } -} - -#[derive(Debug, Resource)] -struct ErasedRenderAssetMeasurements { - assets: AtomicUsize, - _phantom: PhantomData, -} - -impl Default for ErasedRenderAssetMeasurements { - fn default() -> Self { - Self { - assets: AtomicUsize::default(), - _phantom: PhantomData, - } - } -} - -fn add_erased_render_asset_measurement( - mut diagnostics: Diagnostics, - measurements: Res>, -) { - diagnostics.add_measurement( - &ErasedRenderAssetDiagnosticPlugin::::render_asset_diagnostic_path(), - || measurements.assets.load(Ordering::Relaxed) as f64, - ); -} - -fn measure_erased_render_asset( - measurements: Extract>>, - assets: Res>, -) { - measurements - .assets - .store(assets.iter().count(), Ordering::Relaxed); -} diff --git a/third_party/bevy_render/src/diagnostic/internal.rs b/third_party/bevy_render/src/diagnostic/internal.rs deleted file mode 100644 index 641f5a9..0000000 --- a/third_party/bevy_render/src/diagnostic/internal.rs +++ /dev/null @@ -1,711 +0,0 @@ -use alloc::{borrow::Cow, sync::Arc}; -use core::{ - ops::{DerefMut, Range}, - sync::atomic::{AtomicBool, Ordering}, -}; -use std::thread::{self, ThreadId}; - -use bevy_diagnostic::{Diagnostic, DiagnosticMeasurement, DiagnosticPath, DiagnosticsStore}; -use bevy_ecs::resource::Resource; -use bevy_ecs::system::{Res, ResMut}; -use bevy_platform::time::Instant; -use std::sync::Mutex; -use wgpu::{ - Buffer, BufferDescriptor, BufferUsages, CommandEncoder, ComputePass, Features, MapMode, - PipelineStatisticsTypes, QuerySet, QuerySetDescriptor, QueryType, RenderPass, -}; - -use crate::renderer::{RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper}; - -use super::RecordDiagnostics; - -// buffer offset must be divisible by 256, so this constant must be divisible by 32 (=256/8) -const MAX_TIMESTAMP_QUERIES: u32 = 256; -const MAX_PIPELINE_STATISTICS: u32 = 128; - -const TIMESTAMP_SIZE: u64 = 8; -const PIPELINE_STATISTICS_SIZE: u64 = 40; - -struct DiagnosticsRecorderInternal { - timestamp_period_ns: f32, - features: Features, - current_frame: Mutex, - submitted_frames: Vec, - finished_frames: Vec, - #[cfg(feature = "tracing-tracy")] - tracy_gpu_context: tracy_client::GpuContext, -} - -/// Records diagnostics into [`QuerySet`]'s keeping track of the mapping between -/// spans and indices to the corresponding entries in the [`QuerySet`]. -#[derive(Resource)] -pub struct DiagnosticsRecorder(WgpuWrapper); - -impl DiagnosticsRecorder { - /// Creates the new `DiagnosticsRecorder`. - pub fn new( - adapter_info: &RenderAdapterInfo, - device: &RenderDevice, - queue: &RenderQueue, - ) -> DiagnosticsRecorder { - let features = device.features(); - - #[cfg(feature = "tracing-tracy")] - let tracy_gpu_context = - super::tracy_gpu::new_tracy_gpu_context(adapter_info, device, queue); - let _ = adapter_info; // Prevent unused variable warnings when tracing-tracy is not enabled - - DiagnosticsRecorder(WgpuWrapper::new(DiagnosticsRecorderInternal { - timestamp_period_ns: queue.get_timestamp_period(), - features, - current_frame: Mutex::new(FrameData::new( - device, - features, - #[cfg(feature = "tracing-tracy")] - tracy_gpu_context.clone(), - )), - submitted_frames: Vec::new(), - finished_frames: Vec::new(), - #[cfg(feature = "tracing-tracy")] - tracy_gpu_context, - })) - } - - fn current_frame_mut(&mut self) -> &mut FrameData { - self.0.current_frame.get_mut().expect("lock poisoned") - } - - fn current_frame_lock(&self) -> impl DerefMut + '_ { - self.0.current_frame.lock().expect("lock poisoned") - } - - /// Begins recording diagnostics for a new frame. - pub fn begin_frame(&mut self) { - let internal = &mut self.0; - let mut idx = 0; - while idx < internal.submitted_frames.len() { - let timestamp = internal.timestamp_period_ns; - if internal.submitted_frames[idx].run_mapped_callback(timestamp) { - let removed = internal.submitted_frames.swap_remove(idx); - internal.finished_frames.push(removed); - } else { - idx += 1; - } - } - - self.current_frame_mut().begin(); - } - - /// Copies data from [`QuerySet`]'s to a [`Buffer`], after which it can be downloaded to CPU. - /// - /// Should be called before [`DiagnosticsRecorder::finish_frame`]. - pub fn resolve(&mut self, encoder: &mut CommandEncoder) { - self.current_frame_mut().resolve(encoder); - } - - /// Finishes recording diagnostics for the current frame. - /// - /// The specified `callback` will be invoked when diagnostics become available. - /// - /// Should be called after [`DiagnosticsRecorder::resolve`], - /// and **after** all commands buffers have been queued. - pub fn finish_frame( - &mut self, - device: &RenderDevice, - callback: impl FnOnce(RenderDiagnostics) + Send + Sync + 'static, - ) { - #[cfg(feature = "tracing-tracy")] - let tracy_gpu_context = self.0.tracy_gpu_context.clone(); - - let internal = &mut self.0; - internal - .current_frame - .get_mut() - .expect("lock poisoned") - .finish(callback); - - // reuse one of the finished frames, if we can - let new_frame = match internal.finished_frames.pop() { - Some(frame) => frame, - None => FrameData::new( - device, - internal.features, - #[cfg(feature = "tracing-tracy")] - tracy_gpu_context, - ), - }; - - let old_frame = core::mem::replace( - internal.current_frame.get_mut().expect("lock poisoned"), - new_frame, - ); - internal.submitted_frames.push(old_frame); - } -} - -impl RecordDiagnostics for DiagnosticsRecorder { - fn begin_time_span(&self, encoder: &mut E, span_name: Cow<'static, str>) { - self.current_frame_lock() - .begin_time_span(encoder, span_name); - } - - fn end_time_span(&self, encoder: &mut E) { - self.current_frame_lock().end_time_span(encoder); - } - - fn begin_pass_span(&self, pass: &mut P, span_name: Cow<'static, str>) { - self.current_frame_lock().begin_pass(pass, span_name); - } - - fn end_pass_span(&self, pass: &mut P) { - self.current_frame_lock().end_pass(pass); - } -} - -struct SpanRecord { - thread_id: ThreadId, - path_range: Range, - pass_kind: Option, - begin_timestamp_index: Option, - end_timestamp_index: Option, - begin_instant: Option, - end_instant: Option, - pipeline_statistics_index: Option, -} - -struct FrameData { - timestamps_query_set: Option, - num_timestamps: u32, - supports_timestamps_inside_passes: bool, - supports_timestamps_inside_encoders: bool, - pipeline_statistics_query_set: Option, - num_pipeline_statistics: u32, - buffer_size: u64, - pipeline_statistics_buffer_offset: u64, - resolve_buffer: Option, - read_buffer: Option, - path_components: Vec>, - open_spans: Vec, - closed_spans: Vec, - is_mapped: Arc, - callback: Option>, - #[cfg(feature = "tracing-tracy")] - tracy_gpu_context: tracy_client::GpuContext, -} - -impl FrameData { - fn new( - device: &RenderDevice, - features: Features, - #[cfg(feature = "tracing-tracy")] tracy_gpu_context: tracy_client::GpuContext, - ) -> FrameData { - let wgpu_device = device.wgpu_device(); - let mut buffer_size = 0; - - let timestamps_query_set = if features.contains(Features::TIMESTAMP_QUERY) { - buffer_size += u64::from(MAX_TIMESTAMP_QUERIES) * TIMESTAMP_SIZE; - Some(wgpu_device.create_query_set(&QuerySetDescriptor { - label: Some("timestamps_query_set"), - ty: QueryType::Timestamp, - count: MAX_TIMESTAMP_QUERIES, - })) - } else { - None - }; - - let pipeline_statistics_buffer_offset = buffer_size; - - let pipeline_statistics_query_set = - if features.contains(Features::PIPELINE_STATISTICS_QUERY) { - buffer_size += u64::from(MAX_PIPELINE_STATISTICS) * PIPELINE_STATISTICS_SIZE; - Some(wgpu_device.create_query_set(&QuerySetDescriptor { - label: Some("pipeline_statistics_query_set"), - ty: QueryType::PipelineStatistics(PipelineStatisticsTypes::all()), - count: MAX_PIPELINE_STATISTICS, - })) - } else { - None - }; - - let (resolve_buffer, read_buffer) = if buffer_size > 0 { - let resolve_buffer = wgpu_device.create_buffer(&BufferDescriptor { - label: Some("render_statistics_resolve_buffer"), - size: buffer_size, - usage: BufferUsages::QUERY_RESOLVE | BufferUsages::COPY_SRC, - mapped_at_creation: false, - }); - let read_buffer = wgpu_device.create_buffer(&BufferDescriptor { - label: Some("render_statistics_read_buffer"), - size: buffer_size, - usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ, - mapped_at_creation: false, - }); - (Some(resolve_buffer), Some(read_buffer)) - } else { - (None, None) - }; - - FrameData { - timestamps_query_set, - num_timestamps: 0, - supports_timestamps_inside_passes: features - .contains(Features::TIMESTAMP_QUERY_INSIDE_PASSES), - supports_timestamps_inside_encoders: features - .contains(Features::TIMESTAMP_QUERY_INSIDE_ENCODERS), - pipeline_statistics_query_set, - num_pipeline_statistics: 0, - buffer_size, - pipeline_statistics_buffer_offset, - resolve_buffer, - read_buffer, - path_components: Vec::new(), - open_spans: Vec::new(), - closed_spans: Vec::new(), - is_mapped: Arc::new(AtomicBool::new(false)), - callback: None, - #[cfg(feature = "tracing-tracy")] - tracy_gpu_context, - } - } - - fn begin(&mut self) { - self.num_timestamps = 0; - self.num_pipeline_statistics = 0; - self.path_components.clear(); - self.open_spans.clear(); - self.closed_spans.clear(); - } - - fn write_timestamp( - &mut self, - encoder: &mut impl WriteTimestamp, - is_inside_pass: bool, - ) -> Option { - // `encoder.write_timestamp` is unsupported on WebGPU. - if !self.supports_timestamps_inside_encoders { - return None; - } - - if is_inside_pass && !self.supports_timestamps_inside_passes { - return None; - } - - if self.num_timestamps >= MAX_TIMESTAMP_QUERIES { - return None; - } - - let set = self.timestamps_query_set.as_ref()?; - let index = self.num_timestamps; - encoder.write_timestamp(set, index); - self.num_timestamps += 1; - Some(index) - } - - fn write_pipeline_statistics( - &mut self, - encoder: &mut impl WritePipelineStatistics, - ) -> Option { - if self.num_pipeline_statistics >= MAX_PIPELINE_STATISTICS { - return None; - } - - let set = self.pipeline_statistics_query_set.as_ref()?; - let index = self.num_pipeline_statistics; - encoder.begin_pipeline_statistics_query(set, index); - self.num_pipeline_statistics += 1; - Some(index) - } - - fn open_span( - &mut self, - pass_kind: Option, - name: Cow<'static, str>, - ) -> &mut SpanRecord { - let thread_id = thread::current().id(); - - let parent = self.open_spans.iter().rfind(|v| v.thread_id == thread_id); - - let path_range = match &parent { - Some(parent) if parent.path_range.end == self.path_components.len() => { - parent.path_range.start..parent.path_range.end + 1 - } - Some(parent) => { - self.path_components - .extend_from_within(parent.path_range.clone()); - self.path_components.len() - parent.path_range.len()..self.path_components.len() + 1 - } - None => self.path_components.len()..self.path_components.len() + 1, - }; - - self.path_components.push(name); - - self.open_spans.push(SpanRecord { - thread_id, - path_range, - pass_kind, - begin_timestamp_index: None, - end_timestamp_index: None, - begin_instant: None, - end_instant: None, - pipeline_statistics_index: None, - }); - - self.open_spans.last_mut().unwrap() - } - - fn close_span(&mut self) -> &mut SpanRecord { - let thread_id = thread::current().id(); - - let iter = self.open_spans.iter(); - let (index, _) = iter - .enumerate() - .rfind(|(_, v)| v.thread_id == thread_id) - .unwrap(); - - let span = self.open_spans.swap_remove(index); - self.closed_spans.push(span); - self.closed_spans.last_mut().unwrap() - } - - fn begin_time_span(&mut self, encoder: &mut impl WriteTimestamp, name: Cow<'static, str>) { - let begin_instant = Instant::now(); - let begin_timestamp_index = self.write_timestamp(encoder, false); - - let span = self.open_span(None, name); - span.begin_instant = Some(begin_instant); - span.begin_timestamp_index = begin_timestamp_index; - } - - fn end_time_span(&mut self, encoder: &mut impl WriteTimestamp) { - let end_timestamp_index = self.write_timestamp(encoder, false); - - let span = self.close_span(); - span.end_timestamp_index = end_timestamp_index; - span.end_instant = Some(Instant::now()); - } - - fn begin_pass(&mut self, pass: &mut P, name: Cow<'static, str>) { - let begin_instant = Instant::now(); - - let begin_timestamp_index = self.write_timestamp(pass, true); - let pipeline_statistics_index = self.write_pipeline_statistics(pass); - - let span = self.open_span(Some(P::KIND), name); - span.begin_instant = Some(begin_instant); - span.begin_timestamp_index = begin_timestamp_index; - span.pipeline_statistics_index = pipeline_statistics_index; - } - - fn end_pass(&mut self, pass: &mut impl Pass) { - let end_timestamp_index = self.write_timestamp(pass, true); - - let span = self.close_span(); - span.end_timestamp_index = end_timestamp_index; - - if span.pipeline_statistics_index.is_some() { - pass.end_pipeline_statistics_query(); - } - - span.end_instant = Some(Instant::now()); - } - - fn resolve(&mut self, encoder: &mut CommandEncoder) { - let Some(resolve_buffer) = &self.resolve_buffer else { - return; - }; - - match &self.timestamps_query_set { - Some(set) if self.num_timestamps > 0 => { - encoder.resolve_query_set(set, 0..self.num_timestamps, resolve_buffer, 0); - } - _ => {} - } - - match &self.pipeline_statistics_query_set { - Some(set) if self.num_pipeline_statistics > 0 => { - encoder.resolve_query_set( - set, - 0..self.num_pipeline_statistics, - resolve_buffer, - self.pipeline_statistics_buffer_offset, - ); - } - _ => {} - } - - let Some(read_buffer) = &self.read_buffer else { - return; - }; - - encoder.copy_buffer_to_buffer(resolve_buffer, 0, read_buffer, 0, self.buffer_size); - } - - fn diagnostic_path(&self, range: &Range, field: &str) -> DiagnosticPath { - DiagnosticPath::from_components( - core::iter::once("render") - .chain(self.path_components[range.clone()].iter().map(|v| &**v)) - .chain(core::iter::once(field)), - ) - } - - fn finish(&mut self, callback: impl FnOnce(RenderDiagnostics) + Send + Sync + 'static) { - let Some(read_buffer) = &self.read_buffer else { - // we still have cpu timings, so let's use them - - let mut diagnostics = Vec::new(); - - for span in &self.closed_spans { - if let (Some(begin), Some(end)) = (span.begin_instant, span.end_instant) { - diagnostics.push(RenderDiagnostic { - path: self.diagnostic_path(&span.path_range, "elapsed_cpu"), - suffix: "ms", - value: (end - begin).as_secs_f64() * 1000.0, - }); - } - } - - callback(RenderDiagnostics(diagnostics)); - return; - }; - - self.callback = Some(Box::new(callback)); - - let is_mapped = self.is_mapped.clone(); - read_buffer.slice(..).map_async(MapMode::Read, move |res| { - if let Err(e) = res { - tracing::warn!("Failed to download render statistics buffer: {e}"); - return; - } - - is_mapped.store(true, Ordering::Release); - }); - } - - // returns true if the frame is considered finished, false otherwise - fn run_mapped_callback(&mut self, timestamp_period_ns: f32) -> bool { - let Some(read_buffer) = &self.read_buffer else { - return true; - }; - if !self.is_mapped.load(Ordering::Acquire) { - // need to wait more - return false; - } - let Some(callback) = self.callback.take() else { - return true; - }; - - let data = read_buffer.slice(..).get_mapped_range(); - - let timestamps = data[..(self.num_timestamps * 8) as usize] - .chunks(8) - .map(|v| u64::from_le_bytes(v.try_into().unwrap())) - .collect::>(); - - let start = self.pipeline_statistics_buffer_offset as usize; - let len = (self.num_pipeline_statistics as usize) * 40; - let pipeline_statistics = data[start..start + len] - .chunks(8) - .map(|v| u64::from_le_bytes(v.try_into().unwrap())) - .collect::>(); - - let mut diagnostics = Vec::new(); - - for span in &self.closed_spans { - if let (Some(begin), Some(end)) = (span.begin_instant, span.end_instant) { - diagnostics.push(RenderDiagnostic { - path: self.diagnostic_path(&span.path_range, "elapsed_cpu"), - suffix: "ms", - value: (end - begin).as_secs_f64() * 1000.0, - }); - } - - if let (Some(begin), Some(end)) = (span.begin_timestamp_index, span.end_timestamp_index) - { - let begin = timestamps[begin as usize] as f64; - let end = timestamps[end as usize] as f64; - let value = (end - begin) * (timestamp_period_ns as f64) / 1e6; - - #[cfg(feature = "tracing-tracy")] - { - // Calling span_alloc() and end_zone() here instead of in open_span() and close_span() means that tracy does not know where each GPU command was recorded on the CPU timeline. - // Unfortunately we must do it this way, because tracy does not play nicely with multithreaded command recording. The start/end pairs would get all mixed up. - // The GPU spans themselves are still accurate though, and it's probably safe to assume that each GPU span in frame N belongs to the corresponding CPU render node span from frame N-1. - let name = &self.path_components[span.path_range.clone()].join("/"); - let mut tracy_gpu_span = - self.tracy_gpu_context.span_alloc(name, "", "", 0).unwrap(); - tracy_gpu_span.end_zone(); - tracy_gpu_span.upload_timestamp_start(begin as i64); - tracy_gpu_span.upload_timestamp_end(end as i64); - } - - diagnostics.push(RenderDiagnostic { - path: self.diagnostic_path(&span.path_range, "elapsed_gpu"), - suffix: "ms", - value, - }); - } - - if let Some(index) = span.pipeline_statistics_index { - let index = (index as usize) * 5; - - if span.pass_kind == Some(PassKind::Render) { - diagnostics.push(RenderDiagnostic { - path: self.diagnostic_path(&span.path_range, "vertex_shader_invocations"), - suffix: "", - value: pipeline_statistics[index] as f64, - }); - - diagnostics.push(RenderDiagnostic { - path: self.diagnostic_path(&span.path_range, "clipper_invocations"), - suffix: "", - value: pipeline_statistics[index + 1] as f64, - }); - - diagnostics.push(RenderDiagnostic { - path: self.diagnostic_path(&span.path_range, "clipper_primitives_out"), - suffix: "", - value: pipeline_statistics[index + 2] as f64, - }); - - diagnostics.push(RenderDiagnostic { - path: self.diagnostic_path(&span.path_range, "fragment_shader_invocations"), - suffix: "", - value: pipeline_statistics[index + 3] as f64, - }); - } - - if span.pass_kind == Some(PassKind::Compute) { - diagnostics.push(RenderDiagnostic { - path: self.diagnostic_path(&span.path_range, "compute_shader_invocations"), - suffix: "", - value: pipeline_statistics[index + 4] as f64, - }); - } - } - } - - callback(RenderDiagnostics(diagnostics)); - - drop(data); - read_buffer.unmap(); - self.is_mapped.store(false, Ordering::Release); - - true - } -} - -/// Resource which stores render diagnostics of the most recent frame. -#[derive(Debug, Default, Clone, Resource)] -pub struct RenderDiagnostics(Vec); - -/// A render diagnostic which has been recorded, but not yet stored in [`DiagnosticsStore`]. -#[derive(Debug, Clone, Resource)] -pub struct RenderDiagnostic { - pub path: DiagnosticPath, - pub suffix: &'static str, - pub value: f64, -} - -/// Stores render diagnostics before they can be synced with the main app. -/// -/// This mutex is locked twice per frame: -/// 1. in `PreUpdate`, during [`sync_diagnostics`], -/// 2. after rendering has finished and statistics have been downloaded from GPU. -#[derive(Debug, Default, Clone, Resource)] -pub struct RenderDiagnosticsMutex(pub(crate) Arc>>); - -/// Updates render diagnostics measurements. -pub fn sync_diagnostics(mutex: Res, mut store: ResMut) { - let Some(diagnostics) = mutex.0.lock().ok().and_then(|mut v| v.take()) else { - return; - }; - - let time = Instant::now(); - - for diagnostic in &diagnostics.0 { - if store.get(&diagnostic.path).is_none() { - store.add(Diagnostic::new(diagnostic.path.clone()).with_suffix(diagnostic.suffix)); - } - - store - .get_mut(&diagnostic.path) - .unwrap() - .add_measurement(DiagnosticMeasurement { - time, - value: diagnostic.value, - }); - } -} - -pub trait WriteTimestamp { - fn write_timestamp(&mut self, query_set: &QuerySet, index: u32); -} - -impl WriteTimestamp for CommandEncoder { - fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) { - if cfg!(target_os = "macos") { - // When using tracy (and thus this function), rendering was flickering on macOS Tahoe. - // See: https://github.com/bevyengine/bevy/issues/22257 - // The issue seems to be triggered when `write_timestamp` is called very close to frame - // presentation. - return; - } - CommandEncoder::write_timestamp(self, query_set, index); - } -} - -impl WriteTimestamp for RenderPass<'_> { - fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) { - RenderPass::write_timestamp(self, query_set, index); - } -} - -impl WriteTimestamp for ComputePass<'_> { - fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) { - ComputePass::write_timestamp(self, query_set, index); - } -} - -pub trait WritePipelineStatistics { - fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32); - - fn end_pipeline_statistics_query(&mut self); -} - -impl WritePipelineStatistics for RenderPass<'_> { - fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32) { - RenderPass::begin_pipeline_statistics_query(self, query_set, index); - } - - fn end_pipeline_statistics_query(&mut self) { - RenderPass::end_pipeline_statistics_query(self); - } -} - -impl WritePipelineStatistics for ComputePass<'_> { - fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32) { - ComputePass::begin_pipeline_statistics_query(self, query_set, index); - } - - fn end_pipeline_statistics_query(&mut self) { - ComputePass::end_pipeline_statistics_query(self); - } -} - -pub trait Pass: WritePipelineStatistics + WriteTimestamp { - const KIND: PassKind; -} - -impl Pass for RenderPass<'_> { - const KIND: PassKind = PassKind::Render; -} - -impl Pass for ComputePass<'_> { - const KIND: PassKind = PassKind::Compute; -} - -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub enum PassKind { - Render, - Compute, -} diff --git a/third_party/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs b/third_party/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs deleted file mode 100644 index cd0b70d..0000000 --- a/third_party/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs +++ /dev/null @@ -1,91 +0,0 @@ -use bevy_app::{Plugin, PreUpdate}; -use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic}; -use bevy_ecs::{resource::Resource, system::Res}; -use bevy_platform::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; - -use crate::{mesh::allocator::MeshAllocator, Extract, ExtractSchedule, RenderApp}; - -/// Number of meshes allocated by the allocator -static MESH_ALLOCATOR_SLABS: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_slabs"); - -/// Total size of all slabs -static MESH_ALLOCATOR_SLABS_SIZE: DiagnosticPath = - DiagnosticPath::const_new("mesh_allocator_slabs_size"); - -/// Number of meshes allocated into slabs -static MESH_ALLOCATOR_ALLOCATIONS: DiagnosticPath = - DiagnosticPath::const_new("mesh_allocator_allocations"); - -pub struct MeshAllocatorDiagnosticPlugin; - -impl MeshAllocatorDiagnosticPlugin { - /// Get the [`DiagnosticPath`] for slab count - pub fn slabs_diagnostic_path() -> &'static DiagnosticPath { - &MESH_ALLOCATOR_SLABS - } - /// Get the [`DiagnosticPath`] for total slabs size - pub fn slabs_size_diagnostic_path() -> &'static DiagnosticPath { - &MESH_ALLOCATOR_SLABS_SIZE - } - /// Get the [`DiagnosticPath`] for mesh allocations - pub fn allocations_diagnostic_path() -> &'static DiagnosticPath { - &MESH_ALLOCATOR_ALLOCATIONS - } -} - -impl Plugin for MeshAllocatorDiagnosticPlugin { - fn build(&self, app: &mut bevy_app::App) { - app.register_diagnostic( - Diagnostic::new(MESH_ALLOCATOR_SLABS.clone()).with_suffix(" slabs"), - ) - .register_diagnostic( - Diagnostic::new(MESH_ALLOCATOR_SLABS_SIZE.clone()).with_suffix(" bytes"), - ) - .register_diagnostic( - Diagnostic::new(MESH_ALLOCATOR_ALLOCATIONS.clone()).with_suffix(" meshes"), - ) - .init_resource::() - .add_systems(PreUpdate, add_mesh_allocator_measurement); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_systems(ExtractSchedule, measure_allocator); - } - } -} - -#[derive(Debug, Default, Resource)] -struct MeshAllocatorMeasurements { - slabs: AtomicUsize, - slabs_size: AtomicU64, - allocations: AtomicUsize, -} - -fn add_mesh_allocator_measurement( - mut diagnostics: Diagnostics, - measurements: Res, -) { - diagnostics.add_measurement(&MESH_ALLOCATOR_SLABS, || { - measurements.slabs.load(Ordering::Relaxed) as f64 - }); - diagnostics.add_measurement(&MESH_ALLOCATOR_SLABS_SIZE, || { - measurements.slabs_size.load(Ordering::Relaxed) as f64 - }); - diagnostics.add_measurement(&MESH_ALLOCATOR_ALLOCATIONS, || { - measurements.allocations.load(Ordering::Relaxed) as f64 - }); -} - -fn measure_allocator( - measurements: Extract>, - allocator: Res, -) { - measurements - .slabs - .store(allocator.slab_count(), Ordering::Relaxed); - measurements - .slabs_size - .store(allocator.slabs_size(), Ordering::Relaxed); - measurements - .allocations - .store(allocator.allocations(), Ordering::Relaxed); -} diff --git a/third_party/bevy_render/src/diagnostic/mod.rs b/third_party/bevy_render/src/diagnostic/mod.rs deleted file mode 100644 index d2425d3..0000000 --- a/third_party/bevy_render/src/diagnostic/mod.rs +++ /dev/null @@ -1,199 +0,0 @@ -//! Infrastructure for recording render diagnostics. -//! -//! For more info, see [`RenderDiagnosticsPlugin`]. - -mod erased_render_asset_diagnostic_plugin; -pub(crate) mod internal; -mod mesh_allocator_diagnostic_plugin; -mod render_asset_diagnostic_plugin; -#[cfg(feature = "tracing-tracy")] -mod tracy_gpu; - -use alloc::{borrow::Cow, sync::Arc}; -use core::marker::PhantomData; - -use bevy_app::{App, Plugin, PreUpdate}; - -use crate::{renderer::RenderAdapterInfo, RenderApp}; - -use self::internal::{ - sync_diagnostics, DiagnosticsRecorder, Pass, RenderDiagnosticsMutex, WriteTimestamp, -}; -pub use self::{ - erased_render_asset_diagnostic_plugin::ErasedRenderAssetDiagnosticPlugin, - mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin, - render_asset_diagnostic_plugin::RenderAssetDiagnosticPlugin, -}; - -use crate::renderer::{RenderDevice, RenderQueue}; - -/// Enables collecting render diagnostics, such as CPU/GPU elapsed time per render pass, -/// as well as pipeline statistics (number of primitives, number of shader invocations, etc). -/// -/// To access the diagnostics, you can use the [`DiagnosticsStore`](bevy_diagnostic::DiagnosticsStore) resource, -/// add [`LogDiagnosticsPlugin`](bevy_diagnostic::LogDiagnosticsPlugin), or use [Tracy](https://github.com/bevyengine/bevy/blob/main/docs/profiling.md#tracy-renderqueue). -/// -/// To record diagnostics in your own passes: -/// 1. First, obtain the diagnostic recorder using [`RenderContext::diagnostic_recorder`](crate::renderer::RenderContext::diagnostic_recorder). -/// -/// It won't do anything unless [`RenderDiagnosticsPlugin`] is present, -/// so you're free to omit `#[cfg]` clauses. -/// ```ignore -/// let diagnostics = render_context.diagnostic_recorder(); -/// ``` -/// 2. Begin the span inside a command encoder, or a render/compute pass encoder. -/// ```ignore -/// let time_span = diagnostics.time_span(render_context.command_encoder(), "shadows"); -/// ``` -/// 3. End the span, providing the same encoder. -/// ```ignore -/// time_span.end(render_context.command_encoder()); -/// ``` -/// -/// # Supported platforms -/// Timestamp queries and pipeline statistics are currently supported only on Vulkan and DX12. -/// On other platforms (Metal, WebGPU, WebGL2) only CPU time will be recorded. -#[derive(Default)] -pub struct RenderDiagnosticsPlugin; - -impl Plugin for RenderDiagnosticsPlugin { - fn build(&self, app: &mut App) { - let render_diagnostics_mutex = RenderDiagnosticsMutex::default(); - app.insert_resource(render_diagnostics_mutex.clone()) - .add_systems(PreUpdate, sync_diagnostics); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.insert_resource(render_diagnostics_mutex); - } - } - - fn finish(&self, app: &mut App) { - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - let adapter_info = render_app.world().resource::(); - let device = render_app.world().resource::(); - let queue = render_app.world().resource::(); - render_app.insert_resource(DiagnosticsRecorder::new(adapter_info, device, queue)); - } -} - -/// Allows recording diagnostic spans. -pub trait RecordDiagnostics: Send + Sync { - /// Begin a time span, which will record elapsed CPU and GPU time. - /// - /// Returns a guard, which will panic on drop unless you end the span. - fn time_span(&self, encoder: &mut E, name: N) -> TimeSpanGuard<'_, Self, E> - where - E: WriteTimestamp, - N: Into>, - { - self.begin_time_span(encoder, name.into()); - TimeSpanGuard { - recorder: self, - marker: PhantomData, - } - } - - /// Begin a pass span, which will record elapsed CPU and GPU time, - /// as well as pipeline statistics on supported platforms. - /// - /// Returns a guard, which will panic on drop unless you end the span. - fn pass_span(&self, pass: &mut P, name: N) -> PassSpanGuard<'_, Self, P> - where - P: Pass, - N: Into>, - { - let name = name.into(); - self.begin_pass_span(pass, name.clone()); - PassSpanGuard { - recorder: self, - name, - marker: PhantomData, - } - } - - #[doc(hidden)] - fn begin_time_span(&self, encoder: &mut E, name: Cow<'static, str>); - - #[doc(hidden)] - fn end_time_span(&self, encoder: &mut E); - - #[doc(hidden)] - fn begin_pass_span(&self, pass: &mut P, name: Cow<'static, str>); - - #[doc(hidden)] - fn end_pass_span(&self, pass: &mut P); -} - -/// Guard returned by [`RecordDiagnostics::time_span`]. -/// -/// Will panic on drop unless [`TimeSpanGuard::end`] is called. -pub struct TimeSpanGuard<'a, R: ?Sized, E> { - recorder: &'a R, - marker: PhantomData, -} - -impl TimeSpanGuard<'_, R, E> { - /// End the span. You have to provide the same encoder which was used to begin the span. - pub fn end(self, encoder: &mut E) { - self.recorder.end_time_span(encoder); - core::mem::forget(self); - } -} - -impl Drop for TimeSpanGuard<'_, R, E> { - fn drop(&mut self) { - panic!("TimeSpanScope::end was never called") - } -} - -/// Guard returned by [`RecordDiagnostics::pass_span`]. -/// -/// Will panic on drop unless [`PassSpanGuard::end`] is called. -pub struct PassSpanGuard<'a, R: ?Sized, P> { - recorder: &'a R, - name: Cow<'static, str>, - marker: PhantomData

, -} - -impl PassSpanGuard<'_, R, P> { - /// End the span. You have to provide the same pass which was used to begin the span. - pub fn end(self, pass: &mut P) { - self.recorder.end_pass_span(pass); - core::mem::forget(self); - } -} - -impl Drop for PassSpanGuard<'_, R, P> { - fn drop(&mut self) { - panic!("PassSpanGuard::end was never called for {}", self.name) - } -} - -impl RecordDiagnostics for Option> { - fn begin_time_span(&self, encoder: &mut E, name: Cow<'static, str>) { - if let Some(recorder) = &self { - recorder.begin_time_span(encoder, name); - } - } - - fn end_time_span(&self, encoder: &mut E) { - if let Some(recorder) = &self { - recorder.end_time_span(encoder); - } - } - - fn begin_pass_span(&self, pass: &mut P, name: Cow<'static, str>) { - if let Some(recorder) = &self { - recorder.begin_pass_span(pass, name); - } - } - - fn end_pass_span(&self, pass: &mut P) { - if let Some(recorder) = &self { - recorder.end_pass_span(pass); - } - } -} diff --git a/third_party/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs b/third_party/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs deleted file mode 100644 index 347bae7..0000000 --- a/third_party/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs +++ /dev/null @@ -1,77 +0,0 @@ -use core::{any::type_name, marker::PhantomData}; - -use bevy_app::{Plugin, PreUpdate}; -use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic}; -use bevy_ecs::{resource::Resource, system::Res}; -use bevy_platform::sync::atomic::{AtomicUsize, Ordering}; - -use crate::{ - render_asset::{RenderAsset, RenderAssets}, - Extract, ExtractSchedule, RenderApp, -}; - -pub struct RenderAssetDiagnosticPlugin { - suffix: &'static str, - _phantom: PhantomData, -} - -impl RenderAssetDiagnosticPlugin { - pub fn new(suffix: &'static str) -> Self { - Self { - suffix, - _phantom: PhantomData, - } - } - - pub fn render_asset_diagnostic_path() -> DiagnosticPath { - DiagnosticPath::from_components(["render_asset", type_name::()]) - } -} - -impl Plugin for RenderAssetDiagnosticPlugin { - fn build(&self, app: &mut bevy_app::App) { - app.register_diagnostic( - Diagnostic::new(Self::render_asset_diagnostic_path()).with_suffix(self.suffix), - ) - .init_resource::>() - .add_systems(PreUpdate, add_render_asset_measurement::); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_systems(ExtractSchedule, measure_render_asset::); - } - } -} - -#[derive(Debug, Resource)] -struct RenderAssetMeasurements { - assets: AtomicUsize, - _phantom: PhantomData, -} - -impl Default for RenderAssetMeasurements { - fn default() -> Self { - Self { - assets: AtomicUsize::default(), - _phantom: PhantomData, - } - } -} - -fn add_render_asset_measurement( - mut diagnostics: Diagnostics, - measurements: Res>, -) { - diagnostics.add_measurement( - &RenderAssetDiagnosticPlugin::::render_asset_diagnostic_path(), - || measurements.assets.load(Ordering::Relaxed) as f64, - ); -} - -fn measure_render_asset( - measurements: Extract>>, - assets: Res>, -) { - measurements - .assets - .store(assets.iter().count(), Ordering::Relaxed); -} diff --git a/third_party/bevy_render/src/diagnostic/tracy_gpu.rs b/third_party/bevy_render/src/diagnostic/tracy_gpu.rs deleted file mode 100644 index 488e415..0000000 --- a/third_party/bevy_render/src/diagnostic/tracy_gpu.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::renderer::{RenderAdapterInfo, RenderDevice, RenderQueue}; -use tracy_client::{Client, GpuContext, GpuContextType}; -use wgpu::{ - Backend, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, MapMode, PollType, - QuerySetDescriptor, QueryType, QUERY_SIZE, -}; - -pub fn new_tracy_gpu_context( - adapter_info: &RenderAdapterInfo, - device: &RenderDevice, - queue: &RenderQueue, -) -> GpuContext { - let tracy_gpu_backend = match adapter_info.backend { - Backend::Vulkan => GpuContextType::Vulkan, - Backend::Dx12 => GpuContextType::Direct3D12, - Backend::Gl => GpuContextType::OpenGL, - Backend::Metal | Backend::BrowserWebGpu | Backend::Noop => GpuContextType::Invalid, - }; - - let tracy_client = Client::running().unwrap(); - tracy_client - .new_gpu_context( - Some("RenderQueue"), - tracy_gpu_backend, - initial_timestamp(device, queue), - queue.get_timestamp_period(), - ) - .unwrap() -} - -// Code copied from https://github.com/Wumpf/wgpu-profiler/blob/f9de342a62cb75f50904a98d11dd2bbeb40ceab8/src/tracy.rs -fn initial_timestamp(device: &RenderDevice, queue: &RenderQueue) -> i64 { - let query_set = device.wgpu_device().create_query_set(&QuerySetDescriptor { - label: None, - ty: QueryType::Timestamp, - count: 1, - }); - - let resolve_buffer = device.create_buffer(&BufferDescriptor { - label: None, - size: QUERY_SIZE as _, - usage: BufferUsages::QUERY_RESOLVE | BufferUsages::COPY_SRC, - mapped_at_creation: false, - }); - - let map_buffer = device.create_buffer(&BufferDescriptor { - label: None, - size: QUERY_SIZE as _, - usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let mut timestamp_encoder = device.create_command_encoder(&CommandEncoderDescriptor::default()); - timestamp_encoder.write_timestamp(&query_set, 0); - timestamp_encoder.resolve_query_set(&query_set, 0..1, &resolve_buffer, 0); - // Workaround for https://github.com/gfx-rs/wgpu/issues/6406 - // TODO when that bug is fixed, merge these encoders together again - let mut copy_encoder = device.create_command_encoder(&CommandEncoderDescriptor::default()); - copy_encoder.copy_buffer_to_buffer(&resolve_buffer, 0, &map_buffer, 0, Some(QUERY_SIZE as _)); - queue.submit([timestamp_encoder.finish(), copy_encoder.finish()]); - - map_buffer.slice(..).map_async(MapMode::Read, |_| ()); - device - .poll(PollType::wait_indefinitely()) - .expect("Failed to poll device for map async"); - - let view = map_buffer.slice(..).get_mapped_range(); - i64::from_le_bytes((*view).try_into().unwrap()) -} diff --git a/third_party/bevy_render/src/erased_render_asset.rs b/third_party/bevy_render/src/erased_render_asset.rs deleted file mode 100644 index 9c56ba4..0000000 --- a/third_party/bevy_render/src/erased_render_asset.rs +++ /dev/null @@ -1,427 +0,0 @@ -use crate::{ - render_resource::AsBindGroupError, ExtractSchedule, MainWorld, Render, RenderApp, - RenderSystems, Res, -}; -use bevy_app::{App, Plugin, SubApp}; -use bevy_asset::RenderAssetUsages; -use bevy_asset::{Asset, AssetEvent, AssetId, Assets, UntypedAssetId}; -use bevy_ecs::{ - prelude::{Commands, IntoScheduleConfigs, MessageReader, ResMut, Resource}, - schedule::{ScheduleConfigs, SystemSet}, - system::{ScheduleSystem, StaticSystemParam, SystemParam, SystemParamItem, SystemState}, - world::{FromWorld, Mut}, -}; -use bevy_platform::collections::{HashMap, HashSet}; -use bevy_render::render_asset::RenderAssetBytesPerFrameLimiter; -use core::marker::PhantomData; -use thiserror::Error; -use tracing::{debug, error}; - -#[derive(Debug, Error)] -pub enum PrepareAssetError { - #[error("Failed to prepare asset")] - RetryNextUpdate(E), - #[error("Failed to build bind group: {0}")] - AsBindGroupError(AsBindGroupError), -} - -/// The system set during which we extract modified assets to the render world. -#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] -pub struct AssetExtractionSystems; - -/// Describes how an asset gets extracted and prepared for rendering. -/// -/// In the [`ExtractSchedule`] step the [`ErasedRenderAsset::SourceAsset`] is transferred -/// from the "main world" into the "render world". -/// -/// After that in the [`RenderSystems::PrepareAssets`] step the extracted asset -/// is transformed into its GPU-representation of type [`ErasedRenderAsset`]. -pub trait ErasedRenderAsset: Send + Sync + 'static { - /// The representation of the asset in the "main world". - type SourceAsset: Asset + Clone; - /// The target representation of the asset in the "render world". - type ErasedAsset: Send + Sync + 'static + Sized; - - /// Specifies all ECS data required by [`ErasedRenderAsset::prepare_asset`]. - /// - /// For convenience use the [`lifetimeless`](bevy_ecs::system::lifetimeless) [`SystemParam`]. - type Param: SystemParam; - - /// Whether or not to unload the asset after extracting it to the render world. - #[inline] - fn asset_usage(_source_asset: &Self::SourceAsset) -> RenderAssetUsages { - RenderAssetUsages::default() - } - - /// Size of the data the asset will upload to the gpu. Specifying a return value - /// will allow the asset to be throttled via [`RenderAssetBytesPerFrameLimiter`]. - #[inline] - #[expect( - unused_variables, - reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." - )] - fn byte_len(erased_asset: &Self::SourceAsset) -> Option { - None - } - - /// Prepares the [`ErasedRenderAsset::SourceAsset`] for the GPU by transforming it into a [`ErasedRenderAsset`]. - /// - /// ECS data may be accessed via `param`. - fn prepare_asset( - source_asset: Self::SourceAsset, - asset_id: AssetId, - param: &mut SystemParamItem, - ) -> Result>; - - /// Called whenever the [`ErasedRenderAsset::SourceAsset`] has been removed. - /// - /// You can implement this method if you need to access ECS data (via - /// `_param`) in order to perform cleanup tasks when the asset is removed. - /// - /// The default implementation does nothing. - fn unload_asset( - _source_asset: AssetId, - _param: &mut SystemParamItem, - ) { - } -} - -/// This plugin extracts the changed assets from the "app world" into the "render world" -/// and prepares them for the GPU. They can then be accessed from the [`ErasedRenderAssets`] resource. -/// -/// Therefore it sets up the [`ExtractSchedule`] and -/// [`RenderSystems::PrepareAssets`] steps for the specified [`ErasedRenderAsset`]. -/// -/// The `AFTER` generic parameter can be used to specify that `A::prepare_asset` should not be run until -/// `prepare_assets::` has completed. This allows the `prepare_asset` function to depend on another -/// prepared [`ErasedRenderAsset`], for example `Mesh::prepare_asset` relies on `ErasedRenderAssets::` for morph -/// targets, so the plugin is created as `ErasedRenderAssetPlugin::::default()`. -pub struct ErasedRenderAssetPlugin< - A: ErasedRenderAsset, - AFTER: ErasedRenderAssetDependency + 'static = (), -> { - phantom: PhantomData (A, AFTER)>, -} - -impl Default - for ErasedRenderAssetPlugin -{ - fn default() -> Self { - Self { - phantom: Default::default(), - } - } -} - -impl Plugin - for ErasedRenderAssetPlugin -{ - fn build(&self, app: &mut App) { - app.init_resource::>(); - } - - fn finish(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::>() - .init_resource::>() - .init_resource::>() - .add_systems( - ExtractSchedule, - extract_erased_render_asset::.in_set(AssetExtractionSystems), - ); - AFTER::register_system( - render_app, - prepare_erased_assets::.in_set(RenderSystems::PrepareAssets), - ); - } - } -} - -// helper to allow specifying dependencies between render assets -pub trait ErasedRenderAssetDependency { - fn register_system(render_app: &mut SubApp, system: ScheduleConfigs); -} - -impl ErasedRenderAssetDependency for () { - fn register_system(render_app: &mut SubApp, system: ScheduleConfigs) { - render_app.add_systems(Render, system); - } -} - -impl ErasedRenderAssetDependency for A { - fn register_system(render_app: &mut SubApp, system: ScheduleConfigs) { - render_app.add_systems(Render, system.after(prepare_erased_assets::)); - } -} - -/// Temporarily stores the extracted and removed assets of the current frame. -#[derive(Resource)] -pub struct ExtractedAssets { - /// The assets extracted this frame. - /// - /// These are assets that were either added or modified this frame. - pub extracted: Vec<(AssetId, A::SourceAsset)>, - - /// IDs of the assets that were removed this frame. - /// - /// These assets will not be present in [`ExtractedAssets::extracted`]. - pub removed: HashSet>, - - /// IDs of the assets that were modified this frame. - pub modified: HashSet>, - - /// IDs of the assets that were added this frame. - pub added: HashSet>, -} - -impl Default for ExtractedAssets { - fn default() -> Self { - Self { - extracted: Default::default(), - removed: Default::default(), - modified: Default::default(), - added: Default::default(), - } - } -} - -/// Stores all GPU representations ([`ErasedRenderAsset`]) -/// of [`ErasedRenderAsset::SourceAsset`] as long as they exist. -#[derive(Resource)] -pub struct ErasedRenderAssets(HashMap); - -impl Default for ErasedRenderAssets { - fn default() -> Self { - Self(Default::default()) - } -} - -impl ErasedRenderAssets { - pub fn get(&self, id: impl Into) -> Option<&ERA> { - self.0.get(&id.into()) - } - - pub fn get_mut(&mut self, id: impl Into) -> Option<&mut ERA> { - self.0.get_mut(&id.into()) - } - - pub fn insert(&mut self, id: impl Into, value: ERA) -> Option { - self.0.insert(id.into(), value) - } - - pub fn remove(&mut self, id: impl Into) -> Option { - self.0.remove(&id.into()) - } - - pub fn iter(&self) -> impl Iterator { - self.0.iter().map(|(k, v)| (*k, v)) - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.0.iter_mut().map(|(k, v)| (*k, v)) - } -} - -#[derive(Resource)] -struct CachedExtractErasedRenderAssetSystemState { - state: SystemState<( - MessageReader<'static, 'static, AssetEvent>, - ResMut<'static, Assets>, - )>, -} - -impl FromWorld for CachedExtractErasedRenderAssetSystemState { - fn from_world(world: &mut bevy_ecs::world::World) -> Self { - Self { - state: SystemState::new(world), - } - } -} - -/// This system extracts all created or modified assets of the corresponding [`ErasedRenderAsset::SourceAsset`] type -/// into the "render world". -pub(crate) fn extract_erased_render_asset( - mut commands: Commands, - mut main_world: ResMut, -) { - main_world.resource_scope( - |world, mut cached_state: Mut>| { - let (mut events, mut assets) = cached_state.state.get_mut(world); - - let mut needs_extracting = >::default(); - let mut removed = >::default(); - let mut modified = >::default(); - - for event in events.read() { - #[expect( - clippy::match_same_arms, - reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon." - )] - match event { - AssetEvent::Added { id } => { - needs_extracting.insert(*id); - } - AssetEvent::Modified { id } => { - needs_extracting.insert(*id); - modified.insert(*id); - } - AssetEvent::Removed { .. } => { - // We don't care that the asset was removed from Assets in the main world. - // An asset is only removed from ErasedRenderAssets when its last handle is dropped (AssetEvent::Unused). - } - AssetEvent::Unused { id } => { - needs_extracting.remove(id); - modified.remove(id); - removed.insert(*id); - } - AssetEvent::LoadedWithDependencies { .. } => { - // TODO: handle this - } - } - } - - let mut extracted_assets = Vec::new(); - let mut added = >::default(); - for id in needs_extracting.drain() { - if let Some(asset) = assets.get(id) { - let asset_usage = A::asset_usage(asset); - if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) { - if asset_usage == RenderAssetUsages::RENDER_WORLD { - if let Some(asset) = assets.remove(id) { - extracted_assets.push((id, asset)); - added.insert(id); - } - } else { - extracted_assets.push((id, asset.clone())); - added.insert(id); - } - } - } - } - - commands.insert_resource(ExtractedAssets:: { - extracted: extracted_assets, - removed, - modified, - added, - }); - cached_state.state.apply(world); - }, - ); -} - -// TODO: consider storing inside system? -/// All assets that should be prepared next frame. -#[derive(Resource)] -pub struct PrepareNextFrameAssets { - assets: Vec<(AssetId, A::SourceAsset)>, -} - -impl Default for PrepareNextFrameAssets { - fn default() -> Self { - Self { - assets: Default::default(), - } - } -} - -/// This system prepares all assets of the corresponding [`ErasedRenderAsset::SourceAsset`] type -/// which where extracted this frame for the GPU. -pub fn prepare_erased_assets( - mut extracted_assets: ResMut>, - mut render_assets: ResMut>, - mut prepare_next_frame: ResMut>, - param: StaticSystemParam<::Param>, - bpf: Res, -) { - let mut wrote_asset_count = 0; - - let mut param = param.into_inner(); - let queued_assets = core::mem::take(&mut prepare_next_frame.assets); - for (id, extracted_asset) in queued_assets { - if extracted_assets.removed.contains(&id) || extracted_assets.added.contains(&id) { - // skip previous frame's assets that have been removed or updated - continue; - } - - let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) { - // we could check if available bytes > byte_len here, but we want to make some - // forward progress even if the asset is larger than the max bytes per frame. - // this way we always write at least one (sized) asset per frame. - // in future we could also consider partial asset uploads. - if bpf.exhausted() { - prepare_next_frame.assets.push((id, extracted_asset)); - continue; - } - size - } else { - 0 - }; - - match A::prepare_asset(extracted_asset, id, &mut param) { - Ok(prepared_asset) => { - render_assets.insert(id, prepared_asset); - bpf.write_bytes(write_bytes); - wrote_asset_count += 1; - } - Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { - prepare_next_frame.assets.push((id, extracted_asset)); - } - Err(PrepareAssetError::AsBindGroupError(e)) => { - error!( - "{} Bind group construction failed: {e}", - core::any::type_name::() - ); - } - } - } - - for removed in extracted_assets.removed.drain() { - render_assets.remove(removed); - A::unload_asset(removed, &mut param); - } - - for (id, extracted_asset) in extracted_assets.extracted.drain(..) { - // we remove previous here to ensure that if we are updating the asset then - // any users will not see the old asset after a new asset is extracted, - // even if the new asset is not yet ready or we are out of bytes to write. - render_assets.remove(id); - - let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) { - if bpf.exhausted() { - prepare_next_frame.assets.push((id, extracted_asset)); - continue; - } - size - } else { - 0 - }; - - match A::prepare_asset(extracted_asset, id, &mut param) { - Ok(prepared_asset) => { - render_assets.insert(id, prepared_asset); - bpf.write_bytes(write_bytes); - wrote_asset_count += 1; - } - Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { - prepare_next_frame.assets.push((id, extracted_asset)); - } - Err(PrepareAssetError::AsBindGroupError(e)) => { - error!( - "{} Bind group construction failed: {e}", - core::any::type_name::() - ); - } - } - } - - if bpf.exhausted() && !prepare_next_frame.assets.is_empty() { - debug!( - "{} write budget exhausted with {} assets remaining (wrote {})", - core::any::type_name::(), - prepare_next_frame.assets.len(), - wrote_asset_count - ); - } -} diff --git a/third_party/bevy_render/src/experimental/mod.rs b/third_party/bevy_render/src/experimental/mod.rs deleted file mode 100644 index 40bb6cf..0000000 --- a/third_party/bevy_render/src/experimental/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Experimental rendering features. -//! -//! Experimental features are features with known problems, but are included -//! nonetheless for testing purposes. - -pub mod occlusion_culling; diff --git a/third_party/bevy_render/src/experimental/occlusion_culling/mesh_preprocess_types.wgsl b/third_party/bevy_render/src/experimental/occlusion_culling/mesh_preprocess_types.wgsl deleted file mode 100644 index a597fb0..0000000 --- a/third_party/bevy_render/src/experimental/occlusion_culling/mesh_preprocess_types.wgsl +++ /dev/null @@ -1,69 +0,0 @@ -// Types needed for GPU mesh uniform building. - -#define_import_path bevy_pbr::mesh_preprocess_types - -// Per-frame data that the CPU supplies to the GPU. -struct MeshInput { - // The model transform. - world_from_local: mat3x4, - // The lightmap UV rect, packed into 64 bits. - lightmap_uv_rect: vec2, - // Various flags. - flags: u32, - previous_input_index: u32, - first_vertex_index: u32, - first_index_index: u32, - index_count: u32, - current_skin_index: u32, - // Low 16 bits: index of the material inside the bind group data. - // High 16 bits: index of the lightmap in the binding array. - material_and_lightmap_bind_group_slot: u32, - timestamp: u32, - // User supplied index to identify the mesh instance - tag: u32, - pad: u32, -} - -// The `wgpu` indirect parameters structure. This is a union of two structures. -// For more information, see the corresponding comment in -// `gpu_preprocessing.rs`. -struct IndirectParametersIndexed { - // `vertex_count` or `index_count`. - index_count: u32, - // `instance_count` in both structures. - instance_count: u32, - // `first_vertex` or `first_index`. - first_index: u32, - // `base_vertex` or `first_instance`. - base_vertex: u32, - // A read-only copy of `instance_index`. - first_instance: u32, -} - -struct IndirectParametersNonIndexed { - vertex_count: u32, - instance_count: u32, - base_vertex: u32, - first_instance: u32, -} - -struct IndirectParametersCpuMetadata { - base_output_index: u32, - batch_set_index: u32, -} - -struct IndirectParametersGpuMetadata { - mesh_index: u32, -#ifdef WRITE_INDIRECT_PARAMETERS_METADATA - early_instance_count: atomic, - late_instance_count: atomic, -#else // WRITE_INDIRECT_PARAMETERS_METADATA - early_instance_count: u32, - late_instance_count: u32, -#endif // WRITE_INDIRECT_PARAMETERS_METADATA -} - -struct IndirectBatchSet { - indirect_parameters_count: atomic, - indirect_parameters_base: u32, -} diff --git a/third_party/bevy_render/src/experimental/occlusion_culling/mod.rs b/third_party/bevy_render/src/experimental/occlusion_culling/mod.rs deleted file mode 100644 index ff76b33..0000000 --- a/third_party/bevy_render/src/experimental/occlusion_culling/mod.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! GPU occlusion culling. -//! -//! See [`OcclusionCulling`] for a detailed description of occlusion culling in -//! Bevy. - -use bevy_app::{App, Plugin}; -use bevy_ecs::{component::Component, entity::Entity, prelude::ReflectComponent}; -use bevy_reflect::{prelude::ReflectDefault, Reflect}; -use bevy_shader::load_shader_library; - -use crate::{extract_component::ExtractComponent, render_resource::TextureView}; - -/// Enables GPU occlusion culling. -/// -/// See [`OcclusionCulling`] for a detailed description of occlusion culling in -/// Bevy. -pub struct OcclusionCullingPlugin; - -impl Plugin for OcclusionCullingPlugin { - fn build(&self, app: &mut App) { - load_shader_library!(app, "mesh_preprocess_types.wgsl"); - } -} - -/// Add this component to a view in order to enable experimental GPU occlusion -/// culling. -/// -/// *Bevy's occlusion culling is currently marked as experimental.* There are -/// known issues whereby, in rare circumstances, occlusion culling can result in -/// meshes being culled that shouldn't be (i.e. meshes that turn invisible). -/// Please try it out and report issues. -/// -/// *Occlusion culling* allows Bevy to avoid rendering objects that are fully -/// behind other opaque or alpha tested objects. This is different from, and -/// complements, depth fragment rejection as the `DepthPrepass` enables. While -/// depth rejection allows Bevy to avoid rendering *pixels* that are behind -/// other objects, the GPU still has to examine those pixels to reject them, -/// which requires transforming the vertices of the objects and performing -/// skinning if the objects were skinned. Occlusion culling allows the GPU to go -/// a step further, avoiding even transforming the vertices of objects that it -/// can quickly prove to be behind other objects. -/// -/// Occlusion culling inherently has some overhead, because Bevy must examine -/// the objects' bounding boxes, and create an acceleration structure -/// (hierarchical Z-buffer) to perform the occlusion tests. Therefore, occlusion -/// culling is disabled by default. Only enable it if you measure it to be a -/// speedup on your scene. Note that, because Bevy's occlusion culling runs on -/// the GPU and is quite efficient, it's rare for occlusion culling to result in -/// a significant slowdown. -/// -/// Occlusion culling currently requires a `DepthPrepass`. If no depth prepass -/// is present on the view, the [`OcclusionCulling`] component will be ignored. -/// Additionally, occlusion culling is currently incompatible with deferred -/// shading; including both `DeferredPrepass` and [`OcclusionCulling`] results -/// in unspecified behavior. -/// -/// The algorithm that Bevy uses is known as [*two-phase occlusion culling*]. -/// When you enable occlusion culling, Bevy splits the depth prepass into two: -/// an *early* depth prepass and a *late* depth prepass. The early depth prepass -/// renders all the meshes that were visible last frame to produce a -/// conservative approximation of the depth buffer. Then, after producing an -/// acceleration structure known as a hierarchical Z-buffer or depth pyramid, -/// Bevy tests the bounding boxes of all meshes against that depth buffer. Those -/// that can be quickly proven to be behind the geometry rendered during the -/// early depth prepass are skipped entirely. The other potentially-visible -/// meshes are rendered during the late prepass, and finally all the visible -/// meshes are rendered as usual during the opaque, transparent, etc. passes. -/// -/// Unlike other occlusion culling systems you may be familiar with, Bevy's -/// occlusion culling is fully dynamic and requires no baking step. The CPU -/// overhead is minimal. Large skinned meshes and other dynamic objects can -/// occlude other objects. -/// -/// [*two-phase occlusion culling*]: -/// https://medium.com/@mil_kru/two-pass-occlusion-culling-4100edcad501 -#[derive(Component, ExtractComponent, Clone, Copy, Default, Reflect)] -#[reflect(Component, Default, Clone)] -pub struct OcclusionCulling; - -/// A render-world component that contains resources necessary to perform -/// occlusion culling on any view other than a camera. -/// -/// Bevy automatically places this component on views created for shadow -/// mapping. You don't ordinarily need to add this component yourself. -#[derive(Clone, Component)] -pub struct OcclusionCullingSubview { - /// A texture view of the Z-buffer. - pub depth_texture_view: TextureView, - /// The size of the texture along both dimensions. - /// - /// Because [`OcclusionCullingSubview`] is only currently used for shadow - /// maps, they're guaranteed to have sizes equal to a power of two, so we - /// don't have to store the two dimensions individually here. - pub depth_texture_size: u32, -} - -/// A render-world component placed on each camera that stores references to all -/// entities other than cameras that need occlusion culling. -/// -/// Bevy automatically places this component on cameras that are drawing -/// shadows, when those shadows come from lights with occlusion culling enabled. -/// You don't ordinarily need to add this component yourself. -#[derive(Clone, Component)] -pub struct OcclusionCullingSubviewEntities(pub Vec); diff --git a/third_party/bevy_render/src/extract_component.rs b/third_party/bevy_render/src/extract_component.rs deleted file mode 100644 index ce656c3..0000000 --- a/third_party/bevy_render/src/extract_component.rs +++ /dev/null @@ -1,236 +0,0 @@ -use crate::{ - render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType}, - renderer::{RenderDevice, RenderQueue}, - sync_component::SyncComponentPlugin, - sync_world::RenderEntity, - Extract, ExtractSchedule, Render, RenderApp, RenderSystems, -}; -use bevy_app::{App, Plugin}; -use bevy_camera::visibility::ViewVisibility; -use bevy_ecs::{ - bundle::NoBundleEffect, - component::Component, - prelude::*, - query::{QueryFilter, QueryItem, ReadOnlyQueryData}, -}; -use core::{marker::PhantomData, ops::Deref}; - -pub use bevy_render_macros::ExtractComponent; - -/// Stores the index of a uniform inside of [`ComponentUniforms`]. -#[derive(Component)] -pub struct DynamicUniformIndex { - index: u32, - marker: PhantomData, -} - -impl DynamicUniformIndex { - #[inline] - pub fn index(&self) -> u32 { - self.index - } -} - -/// Describes how a component gets extracted for rendering. -/// -/// Therefore the component is transferred from the "app world" into the "render world" -/// in the [`ExtractSchedule`] step. -pub trait ExtractComponent: Component { - /// ECS [`ReadOnlyQueryData`] to fetch the components to extract. - type QueryData: ReadOnlyQueryData; - /// Filters the entities with additional constraints. - type QueryFilter: QueryFilter; - - /// The output from extraction. - /// - /// Returning `None` based on the queried item will remove the component from the entity in - /// the render world. This can be used, for example, to conditionally extract camera settings - /// in order to disable a rendering feature on the basis of those settings, without removing - /// the component from the entity in the main world. - /// - /// The output may be different from the queried component. - /// This can be useful for example if only a subset of the fields are useful - /// in the render world. - /// - /// `Out` has a [`Bundle`] trait bound instead of a [`Component`] trait bound in order to allow use cases - /// such as tuples of components as output. - type Out: Bundle; - - // TODO: https://github.com/rust-lang/rust/issues/29661 - // type Out: Component = Self; - - /// Defines how the component is transferred into the "render world". - fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option; -} - -/// This plugin prepares the components of the corresponding type for the GPU -/// by transforming them into uniforms. -/// -/// They can then be accessed from the [`ComponentUniforms`] resource. -/// For referencing the newly created uniforms a [`DynamicUniformIndex`] is inserted -/// for every processed entity. -/// -/// Therefore it sets up the [`RenderSystems::Prepare`] step -/// for the specified [`ExtractComponent`]. -pub struct UniformComponentPlugin(PhantomData C>); - -impl Default for UniformComponentPlugin { - fn default() -> Self { - Self(PhantomData) - } -} - -impl Plugin for UniformComponentPlugin { - fn build(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .insert_resource(ComponentUniforms::::default()) - .add_systems( - Render, - prepare_uniform_components::.in_set(RenderSystems::PrepareResources), - ); - } - } -} - -/// Stores all uniforms of the component type. -#[derive(Resource)] -pub struct ComponentUniforms { - uniforms: DynamicUniformBuffer, -} - -impl Deref for ComponentUniforms { - type Target = DynamicUniformBuffer; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.uniforms - } -} - -impl ComponentUniforms { - #[inline] - pub fn uniforms(&self) -> &DynamicUniformBuffer { - &self.uniforms - } -} - -impl Default for ComponentUniforms { - fn default() -> Self { - Self { - uniforms: Default::default(), - } - } -} - -/// This system prepares all components of the corresponding component type. -/// They are transformed into uniforms and stored in the [`ComponentUniforms`] resource. -fn prepare_uniform_components( - mut commands: Commands, - render_device: Res, - render_queue: Res, - mut component_uniforms: ResMut>, - components: Query<(Entity, &C)>, -) where - C: Component + ShaderType + WriteInto + Clone, -{ - let components_iter = components.iter(); - let count = components_iter.len(); - let Some(mut writer) = - component_uniforms - .uniforms - .get_writer(count, &render_device, &render_queue) - else { - return; - }; - let entities = components_iter - .map(|(entity, component)| { - ( - entity, - DynamicUniformIndex:: { - index: writer.write(component), - marker: PhantomData, - }, - ) - }) - .collect::>(); - commands.try_insert_batch(entities); -} - -/// This plugin extracts the components into the render world for synced entities. -/// -/// To do so, it sets up the [`ExtractSchedule`] step for the specified [`ExtractComponent`]. -pub struct ExtractComponentPlugin { - only_extract_visible: bool, - marker: PhantomData (C, F)>, -} - -impl Default for ExtractComponentPlugin { - fn default() -> Self { - Self { - only_extract_visible: false, - marker: PhantomData, - } - } -} - -impl ExtractComponentPlugin { - pub fn extract_visible() -> Self { - Self { - only_extract_visible: true, - marker: PhantomData, - } - } -} - -impl Plugin for ExtractComponentPlugin { - fn build(&self, app: &mut App) { - app.add_plugins(SyncComponentPlugin::::default()); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - if self.only_extract_visible { - render_app.add_systems(ExtractSchedule, extract_visible_components::); - } else { - render_app.add_systems(ExtractSchedule, extract_components::); - } - } - } -} - -/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`crate::sync_world::SyncToRenderWorld`]. -fn extract_components( - mut commands: Commands, - mut previous_len: Local, - query: Extract>, -) { - let mut values = Vec::with_capacity(*previous_len); - for (entity, query_item) in &query { - if let Some(component) = C::extract_component(query_item) { - values.push((entity, component)); - } else { - commands.entity(entity).remove::(); - } - } - *previous_len = values.len(); - commands.try_insert_batch(values); -} - -/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`crate::sync_world::SyncToRenderWorld`]. -fn extract_visible_components( - mut commands: Commands, - mut previous_len: Local, - query: Extract>, -) { - let mut values = Vec::with_capacity(*previous_len); - for (entity, view_visibility, query_item) in &query { - if view_visibility.get() { - if let Some(component) = C::extract_component(query_item) { - values.push((entity, component)); - } else { - commands.entity(entity).remove::(); - } - } - } - *previous_len = values.len(); - commands.try_insert_batch(values); -} diff --git a/third_party/bevy_render/src/extract_instances.rs b/third_party/bevy_render/src/extract_instances.rs deleted file mode 100644 index d85f8fa..0000000 --- a/third_party/bevy_render/src/extract_instances.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! Convenience logic for turning components from the main world into extracted -//! instances in the render world. -//! -//! This is essentially the same as the `extract_component` module, but -//! higher-performance because it avoids the ECS overhead. - -use core::marker::PhantomData; - -use bevy_app::{App, Plugin}; -use bevy_camera::visibility::ViewVisibility; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{ - prelude::Entity, - query::{QueryFilter, QueryItem, ReadOnlyQueryData}, - resource::Resource, - system::{Query, ResMut}, -}; - -use crate::sync_world::MainEntityHashMap; -use crate::{Extract, ExtractSchedule, RenderApp}; - -/// Describes how to extract data needed for rendering from a component or -/// components. -/// -/// Before rendering, any applicable components will be transferred from the -/// main world to the render world in the [`ExtractSchedule`] step. -/// -/// This is essentially the same as -/// [`ExtractComponent`](crate::extract_component::ExtractComponent), but -/// higher-performance because it avoids the ECS overhead. -pub trait ExtractInstance: Send + Sync + Sized + 'static { - /// ECS [`ReadOnlyQueryData`] to fetch the components to extract. - type QueryData: ReadOnlyQueryData; - /// Filters the entities with additional constraints. - type QueryFilter: QueryFilter; - - /// Defines how the component is transferred into the "render world". - fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option; -} - -/// This plugin extracts one or more components into the "render world" as -/// extracted instances. -/// -/// Therefore it sets up the [`ExtractSchedule`] step for the specified -/// [`ExtractedInstances`]. -#[derive(Default)] -pub struct ExtractInstancesPlugin -where - EI: ExtractInstance, -{ - only_extract_visible: bool, - marker: PhantomData EI>, -} - -/// Stores all extract instances of a type in the render world. -#[derive(Resource, Deref, DerefMut)] -pub struct ExtractedInstances(MainEntityHashMap) -where - EI: ExtractInstance; - -impl Default for ExtractedInstances -where - EI: ExtractInstance, -{ - fn default() -> Self { - Self(Default::default()) - } -} - -impl ExtractInstancesPlugin -where - EI: ExtractInstance, -{ - /// Creates a new [`ExtractInstancesPlugin`] that unconditionally extracts to - /// the render world, whether the entity is visible or not. - pub fn new() -> Self { - Self { - only_extract_visible: false, - marker: PhantomData, - } - } - - /// Creates a new [`ExtractInstancesPlugin`] that extracts to the render world - /// if and only if the entity it's attached to is visible. - pub fn extract_visible() -> Self { - Self { - only_extract_visible: true, - marker: PhantomData, - } - } -} - -impl Plugin for ExtractInstancesPlugin -where - EI: ExtractInstance, -{ - fn build(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::>(); - if self.only_extract_visible { - render_app.add_systems(ExtractSchedule, extract_visible::); - } else { - render_app.add_systems(ExtractSchedule, extract_all::); - } - } - } -} - -fn extract_all( - mut extracted_instances: ResMut>, - query: Extract>, -) where - EI: ExtractInstance, -{ - extracted_instances.clear(); - for (entity, other) in &query { - if let Some(extract_instance) = EI::extract(other) { - extracted_instances.insert(entity.into(), extract_instance); - } - } -} - -fn extract_visible( - mut extracted_instances: ResMut>, - query: Extract>, -) where - EI: ExtractInstance, -{ - extracted_instances.clear(); - for (entity, view_visibility, other) in &query { - if view_visibility.get() - && let Some(extract_instance) = EI::extract(other) - { - extracted_instances.insert(entity.into(), extract_instance); - } - } -} diff --git a/third_party/bevy_render/src/extract_param.rs b/third_party/bevy_render/src/extract_param.rs deleted file mode 100644 index 4d81fb9..0000000 --- a/third_party/bevy_render/src/extract_param.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::MainWorld; -use bevy_ecs::{ - change_detection::Tick, - prelude::*, - query::FilteredAccessSet, - system::{ - ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemParamValidationError, - SystemState, - }, - world::unsafe_world_cell::UnsafeWorldCell, -}; -use core::ops::{Deref, DerefMut}; - -/// A helper for accessing [`MainWorld`] content using a system parameter. -/// -/// A [`SystemParam`] adapter which applies the contained `SystemParam` to the [`World`] -/// contained in [`MainWorld`]. This parameter only works for systems run -/// during the [`ExtractSchedule`](crate::ExtractSchedule). -/// -/// This requires that the contained [`SystemParam`] does not mutate the world, as it -/// uses a read-only reference to [`MainWorld`] internally. -/// -/// ## Context -/// -/// [`ExtractSchedule`] is used to extract (move) data from the simulation world ([`MainWorld`]) to the -/// render world. The render world drives rendering each frame (generally to a `Window`). -/// This design is used to allow performing calculations related to rendering a prior frame at the same -/// time as the next frame is simulated, which increases throughput (FPS). -/// -/// [`Extract`] is used to get data from the main world during [`ExtractSchedule`]. -/// -/// ## Examples -/// -/// ``` -/// use bevy_ecs::prelude::*; -/// use bevy_render::Extract; -/// use bevy_render::sync_world::RenderEntity; -/// # #[derive(Component)] -/// // Do make sure to sync the cloud entities before extracting them. -/// # struct Cloud; -/// fn extract_clouds(mut commands: Commands, clouds: Extract>>) { -/// for cloud in &clouds { -/// commands.entity(cloud).insert(Cloud); -/// } -/// } -/// ``` -/// -/// [`ExtractSchedule`]: crate::ExtractSchedule -/// [Window]: bevy_window::Window -pub struct Extract<'w, 's, P> -where - P: ReadOnlySystemParam + 'static, -{ - item: SystemParamItem<'w, 's, P>, -} - -#[doc(hidden)] -pub struct ExtractState { - state: SystemState

, - main_world_state: as SystemParam>::State, -} - -// SAFETY: The only `World` access (`Res`) is read-only. -unsafe impl

ReadOnlySystemParam for Extract<'_, '_, P> where P: ReadOnlySystemParam {} - -// SAFETY: The only `World` access is properly registered by `Res::init_state`. -// This call will also ensure that there are no conflicts with prior params. -unsafe impl

SystemParam for Extract<'_, '_, P> -where - P: ReadOnlySystemParam, -{ - type State = ExtractState

; - type Item<'w, 's> = Extract<'w, 's, P>; - - fn init_state(world: &mut World) -> Self::State { - let mut main_world = world.resource_mut::(); - ExtractState { - state: SystemState::new(&mut main_world), - main_world_state: Res::::init_state(world), - } - } - - fn init_access( - state: &Self::State, - system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, - world: &mut World, - ) { - Res::::init_access( - &state.main_world_state, - system_meta, - component_access_set, - world, - ); - } - - #[inline] - unsafe fn validate_param( - state: &mut Self::State, - _system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> Result<(), SystemParamValidationError> { - // SAFETY: Read-only access to world data registered in `init_state`. - let result = unsafe { world.get_resource_by_id(state.main_world_state) }; - let Some(main_world) = result else { - return Err(SystemParamValidationError::invalid::( - "`MainWorld` resource does not exist", - )); - }; - // SAFETY: Type is guaranteed by `SystemState`. - let main_world: &World = unsafe { main_world.deref() }; - // SAFETY: We provide the main world on which this system state was initialized on. - unsafe { - SystemState::

::validate_param( - &mut state.state, - main_world.as_unsafe_world_cell_readonly(), - ) - } - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - // SAFETY: - // - The caller ensures that `world` is the same one that `init_state` was called with. - // - The caller ensures that no other `SystemParam`s will conflict with the accesses we have registered. - let main_world = unsafe { - Res::::get_param( - &mut state.main_world_state, - system_meta, - world, - change_tick, - ) - }; - let item = state.state.get(main_world.into_inner()); - Extract { item } - } -} - -impl<'w, 's, P> Deref for Extract<'w, 's, P> -where - P: ReadOnlySystemParam, -{ - type Target = SystemParamItem<'w, 's, P>; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.item - } -} - -impl<'w, 's, P> DerefMut for Extract<'w, 's, P> -where - P: ReadOnlySystemParam, -{ - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.item - } -} - -impl<'a, 'w, 's, P> IntoIterator for &'a Extract<'w, 's, P> -where - P: ReadOnlySystemParam, - &'a SystemParamItem<'w, 's, P>: IntoIterator, -{ - type Item = <&'a SystemParamItem<'w, 's, P> as IntoIterator>::Item; - type IntoIter = <&'a SystemParamItem<'w, 's, P> as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - (&self.item).into_iter() - } -} diff --git a/third_party/bevy_render/src/extract_resource.rs b/third_party/bevy_render/src/extract_resource.rs deleted file mode 100644 index cec8647..0000000 --- a/third_party/bevy_render/src/extract_resource.rs +++ /dev/null @@ -1,70 +0,0 @@ -use core::marker::PhantomData; - -use bevy_app::{App, Plugin}; -use bevy_ecs::prelude::*; -pub use bevy_render_macros::ExtractResource; -use bevy_utils::once; - -use crate::{Extract, ExtractSchedule, RenderApp}; - -/// Describes how a resource gets extracted for rendering. -/// -/// Therefore the resource is transferred from the "main world" into the "render world" -/// in the [`ExtractSchedule`] step. -pub trait ExtractResource: Resource { - type Source: Resource; - - /// Defines how the resource is transferred into the "render world". - fn extract_resource(source: &Self::Source) -> Self; -} - -/// This plugin extracts the resources into the "render world". -/// -/// Therefore it sets up the[`ExtractSchedule`] step -/// for the specified [`Resource`]. -pub struct ExtractResourcePlugin(PhantomData); - -impl Default for ExtractResourcePlugin { - fn default() -> Self { - Self(PhantomData) - } -} - -impl Plugin for ExtractResourcePlugin { - fn build(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_systems(ExtractSchedule, extract_resource::); - } else { - once!(tracing::error!( - "Render app did not exist when trying to add `extract_resource` for <{}>.", - core::any::type_name::() - )); - } - } -} - -/// This system extracts the resource of the corresponding [`Resource`] type -pub fn extract_resource( - mut commands: Commands, - main_resource: Extract>>, - target_resource: Option>, -) { - if let Some(main_resource) = main_resource.as_ref() { - if let Some(mut target_resource) = target_resource { - if main_resource.is_changed() { - *target_resource = R::extract_resource(main_resource); - } - } else { - #[cfg(debug_assertions)] - if !main_resource.is_added() { - once!(tracing::warn!( - "Removing resource {} from render world not expected, adding using `Commands`. - This may decrease performance", - core::any::type_name::() - )); - } - - commands.insert_resource(R::extract_resource(main_resource)); - } - } -} diff --git a/third_party/bevy_render/src/globals.rs b/third_party/bevy_render/src/globals.rs deleted file mode 100644 index 9b643d5..0000000 --- a/third_party/bevy_render/src/globals.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::{ - extract_resource::ExtractResource, - render_resource::{ShaderType, UniformBuffer}, - renderer::{RenderDevice, RenderQueue}, - Extract, ExtractSchedule, Render, RenderApp, RenderSystems, -}; -use bevy_app::{App, Plugin}; -use bevy_diagnostic::FrameCount; -use bevy_ecs::prelude::*; -use bevy_reflect::prelude::*; -use bevy_shader::load_shader_library; -use bevy_time::Time; - -pub struct GlobalsPlugin; - -impl Plugin for GlobalsPlugin { - fn build(&self, app: &mut App) { - load_shader_library!(app, "globals.wgsl"); - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .init_resource::.in_set(AssetExtractionSystems), - ); - AFTER::register_system( - render_app, - prepare_assets::.in_set(RenderSystems::PrepareAssets), - ); - } - } -} - -// helper to allow specifying dependencies between render assets -pub trait RenderAssetDependency { - fn register_system(render_app: &mut SubApp, system: ScheduleConfigs); -} - -impl RenderAssetDependency for () { - fn register_system(render_app: &mut SubApp, system: ScheduleConfigs) { - render_app.add_systems(Render, system); - } -} - -impl RenderAssetDependency for A { - fn register_system(render_app: &mut SubApp, system: ScheduleConfigs) { - render_app.add_systems(Render, system.after(prepare_assets::)); - } -} - -/// Temporarily stores the extracted and removed assets of the current frame. -#[derive(Resource)] -pub struct ExtractedAssets { - /// The assets extracted this frame. - /// - /// These are assets that were either added or modified this frame. - pub extracted: Vec<(AssetId, A::SourceAsset)>, - - /// IDs of the assets that were removed this frame. - /// - /// These assets will not be present in [`ExtractedAssets::extracted`]. - pub removed: HashSet>, - - /// IDs of the assets that were modified this frame. - pub modified: HashSet>, - - /// IDs of the assets that were added this frame. - pub added: HashSet>, -} - -impl Default for ExtractedAssets { - fn default() -> Self { - Self { - extracted: Default::default(), - removed: Default::default(), - modified: Default::default(), - added: Default::default(), - } - } -} - -/// Stores all GPU representations ([`RenderAsset`]) -/// of [`RenderAsset::SourceAsset`] as long as they exist. -#[derive(Resource)] -pub struct RenderAssets(HashMap, A>); - -impl Default for RenderAssets { - fn default() -> Self { - Self(Default::default()) - } -} - -impl RenderAssets { - pub fn get(&self, id: impl Into>) -> Option<&A> { - self.0.get(&id.into()) - } - - pub fn get_mut(&mut self, id: impl Into>) -> Option<&mut A> { - self.0.get_mut(&id.into()) - } - - pub fn insert(&mut self, id: impl Into>, value: A) -> Option { - self.0.insert(id.into(), value) - } - - pub fn remove(&mut self, id: impl Into>) -> Option { - self.0.remove(&id.into()) - } - - pub fn iter(&self) -> impl Iterator, &A)> { - self.0.iter().map(|(k, v)| (*k, v)) - } - - pub fn iter_mut(&mut self) -> impl Iterator, &mut A)> { - self.0.iter_mut().map(|(k, v)| (*k, v)) - } -} - -#[derive(Resource)] -struct CachedExtractRenderAssetSystemState { - state: SystemState<( - MessageReader<'static, 'static, AssetEvent>, - ResMut<'static, Assets>, - Option>>, - )>, -} - -impl FromWorld for CachedExtractRenderAssetSystemState { - fn from_world(world: &mut bevy_ecs::world::World) -> Self { - Self { - state: SystemState::new(world), - } - } -} - -/// This system extracts all created or modified assets of the corresponding [`RenderAsset::SourceAsset`] type -/// into the "render world". -pub(crate) fn extract_render_asset( - mut commands: Commands, - mut main_world: ResMut, -) { - main_world.resource_scope( - |world, mut cached_state: Mut>| { - let (mut events, mut assets, maybe_render_assets) = cached_state.state.get_mut(world); - - let mut needs_extracting = >::default(); - let mut removed = >::default(); - let mut modified = >::default(); - - for event in events.read() { - #[expect( - clippy::match_same_arms, - reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon." - )] - match event { - AssetEvent::Added { id } => { - needs_extracting.insert(*id); - } - AssetEvent::Modified { id } => { - needs_extracting.insert(*id); - modified.insert(*id); - } - AssetEvent::Removed { .. } => { - // We don't care that the asset was removed from Assets in the main world. - // An asset is only removed from RenderAssets when its last handle is dropped (AssetEvent::Unused). - } - AssetEvent::Unused { id } => { - needs_extracting.remove(id); - modified.remove(id); - removed.insert(*id); - } - AssetEvent::LoadedWithDependencies { .. } => { - // TODO: handle this - } - } - } - - let mut extracted_assets = Vec::new(); - let mut added = >::default(); - for id in needs_extracting.drain() { - if let Some(asset) = assets.get(id) { - let asset_usage = A::asset_usage(asset); - if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) { - if asset_usage == RenderAssetUsages::RENDER_WORLD { - if let Some(asset) = assets.get_mut_untracked(id) { - let previous_asset = maybe_render_assets.as_ref().and_then(|render_assets| render_assets.get(id)); - match A::take_gpu_data(asset, previous_asset) { - Ok(gpu_data_asset) => { - extracted_assets.push((id, gpu_data_asset)); - added.insert(id); - } - Err(e) => { - error!("{} with RenderAssetUsages == RENDER_WORLD cannot be extracted: {e}", core::any::type_name::()); - } - }; - } - } else { - extracted_assets.push((id, asset.clone())); - added.insert(id); - } - } - } - } - - commands.insert_resource(ExtractedAssets:: { - extracted: extracted_assets, - removed, - modified, - added, - }); - cached_state.state.apply(world); - }, - ); -} - -// TODO: consider storing inside system? -/// All assets that should be prepared next frame. -#[derive(Resource)] -pub struct PrepareNextFrameAssets { - assets: Vec<(AssetId, A::SourceAsset)>, -} - -impl Default for PrepareNextFrameAssets { - fn default() -> Self { - Self { - assets: Default::default(), - } - } -} - -/// This system prepares all assets of the corresponding [`RenderAsset::SourceAsset`] type -/// which where extracted this frame for the GPU. -pub fn prepare_assets( - mut extracted_assets: ResMut>, - mut render_assets: ResMut>, - mut prepare_next_frame: ResMut>, - param: StaticSystemParam<::Param>, - bpf: Res, -) { - let mut wrote_asset_count = 0; - - let mut param = param.into_inner(); - let queued_assets = core::mem::take(&mut prepare_next_frame.assets); - for (id, extracted_asset) in queued_assets { - if extracted_assets.removed.contains(&id) || extracted_assets.added.contains(&id) { - // skip previous frame's assets that have been removed or updated - continue; - } - - let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) { - // we could check if available bytes > byte_len here, but we want to make some - // forward progress even if the asset is larger than the max bytes per frame. - // this way we always write at least one (sized) asset per frame. - // in future we could also consider partial asset uploads. - if bpf.exhausted() { - prepare_next_frame.assets.push((id, extracted_asset)); - continue; - } - size - } else { - 0 - }; - - let previous_asset = render_assets.get(id); - match A::prepare_asset(extracted_asset, id, &mut param, previous_asset) { - Ok(prepared_asset) => { - render_assets.insert(id, prepared_asset); - bpf.write_bytes(write_bytes); - wrote_asset_count += 1; - } - Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { - prepare_next_frame.assets.push((id, extracted_asset)); - } - Err(PrepareAssetError::AsBindGroupError(e)) => { - error!( - "{} Bind group construction failed: {e}", - core::any::type_name::() - ); - } - } - } - - for removed in extracted_assets.removed.drain() { - render_assets.remove(removed); - A::unload_asset(removed, &mut param); - } - - for (id, extracted_asset) in extracted_assets.extracted.drain(..) { - // we remove previous here to ensure that if we are updating the asset then - // any users will not see the old asset after a new asset is extracted, - // even if the new asset is not yet ready or we are out of bytes to write. - let previous_asset = render_assets.remove(id); - - let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) { - if bpf.exhausted() { - prepare_next_frame.assets.push((id, extracted_asset)); - continue; - } - size - } else { - 0 - }; - - match A::prepare_asset(extracted_asset, id, &mut param, previous_asset.as_ref()) { - Ok(prepared_asset) => { - render_assets.insert(id, prepared_asset); - bpf.write_bytes(write_bytes); - wrote_asset_count += 1; - } - Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { - prepare_next_frame.assets.push((id, extracted_asset)); - } - Err(PrepareAssetError::AsBindGroupError(e)) => { - error!( - "{} Bind group construction failed: {e}", - core::any::type_name::() - ); - } - } - } - - if bpf.exhausted() && !prepare_next_frame.assets.is_empty() { - debug!( - "{} write budget exhausted with {} assets remaining (wrote {})", - core::any::type_name::(), - prepare_next_frame.assets.len(), - wrote_asset_count - ); - } -} - -pub fn reset_render_asset_bytes_per_frame( - mut bpf_limiter: ResMut, -) { - bpf_limiter.reset(); -} - -pub fn extract_render_asset_bytes_per_frame( - bpf: Extract>, - mut bpf_limiter: ResMut, -) { - bpf_limiter.max_bytes = bpf.max_bytes; -} - -/// A resource that defines the amount of data allowed to be transferred from CPU to GPU -/// each frame, preventing choppy frames at the cost of waiting longer for GPU assets -/// to become available. -#[derive(Resource, Default)] -pub struct RenderAssetBytesPerFrame { - pub max_bytes: Option, -} - -impl RenderAssetBytesPerFrame { - /// `max_bytes`: the number of bytes to write per frame. - /// - /// This is a soft limit: only full assets are written currently, uploading stops - /// after the first asset that exceeds the limit. - /// - /// To participate, assets should implement [`RenderAsset::byte_len`]. If the default - /// is not overridden, the assets are assumed to be small enough to upload without restriction. - pub fn new(max_bytes: usize) -> Self { - Self { - max_bytes: Some(max_bytes), - } - } -} - -/// A render-world resource that facilitates limiting the data transferred from CPU to GPU -/// each frame, preventing choppy frames at the cost of waiting longer for GPU assets -/// to become available. -#[derive(Resource, Default)] -pub struct RenderAssetBytesPerFrameLimiter { - /// Populated by [`RenderAssetBytesPerFrame`] during extraction. - pub max_bytes: Option, - /// Bytes written this frame. - pub bytes_written: AtomicUsize, -} - -impl RenderAssetBytesPerFrameLimiter { - /// Reset the available bytes. Called once per frame during extraction by [`crate::RenderPlugin`]. - pub fn reset(&mut self) { - if self.max_bytes.is_none() { - return; - } - self.bytes_written.store(0, Ordering::Relaxed); - } - - /// Check how many bytes are available for writing. - pub fn available_bytes(&self, required_bytes: usize) -> usize { - if let Some(max_bytes) = self.max_bytes { - let total_bytes = self - .bytes_written - .fetch_add(required_bytes, Ordering::Relaxed); - - // The bytes available is the inverse of the amount we overshot max_bytes - if total_bytes >= max_bytes { - required_bytes.saturating_sub(total_bytes - max_bytes) - } else { - required_bytes - } - } else { - required_bytes - } - } - - /// Decreases the available bytes for the current frame. - pub(crate) fn write_bytes(&self, bytes: usize) { - if self.max_bytes.is_some() && bytes > 0 { - self.bytes_written.fetch_add(bytes, Ordering::Relaxed); - } - } - - /// Returns `true` if there are no remaining bytes available for writing this frame. - pub(crate) fn exhausted(&self) -> bool { - if let Some(max_bytes) = self.max_bytes { - let bytes_written = self.bytes_written.load(Ordering::Relaxed); - bytes_written >= max_bytes - } else { - false - } - } -} diff --git a/third_party/bevy_render/src/render_graph/app.rs b/third_party/bevy_render/src/render_graph/app.rs deleted file mode 100644 index 879f28f..0000000 --- a/third_party/bevy_render/src/render_graph/app.rs +++ /dev/null @@ -1,174 +0,0 @@ -use bevy_app::{App, SubApp}; -use bevy_ecs::world::{FromWorld, World}; -use tracing::warn; - -use super::{IntoRenderNodeArray, Node, RenderGraph, RenderLabel, RenderSubGraph}; - -/// Adds common [`RenderGraph`] operations to [`SubApp`] (and [`App`]). -pub trait RenderGraphExt { - // Add a sub graph to the [`RenderGraph`] - fn add_render_sub_graph(&mut self, sub_graph: impl RenderSubGraph) -> &mut Self; - /// Add a [`Node`] to the [`RenderGraph`]: - /// * Create the [`Node`] using the [`FromWorld`] implementation - /// * Add it to the graph - fn add_render_graph_node( - &mut self, - sub_graph: impl RenderSubGraph, - node_label: impl RenderLabel, - ) -> &mut Self; - /// Automatically add the required node edges based on the given ordering - fn add_render_graph_edges( - &mut self, - sub_graph: impl RenderSubGraph, - edges: impl IntoRenderNodeArray, - ) -> &mut Self; - - /// Add node edge to the specified graph - fn add_render_graph_edge( - &mut self, - sub_graph: impl RenderSubGraph, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> &mut Self; -} - -impl RenderGraphExt for World { - fn add_render_graph_node( - &mut self, - sub_graph: impl RenderSubGraph, - node_label: impl RenderLabel, - ) -> &mut Self { - let sub_graph = sub_graph.intern(); - let node = T::from_world(self); - let mut render_graph = self.get_resource_mut::().expect( - "RenderGraph not found. Make sure you are using add_render_graph_node on the RenderApp", - ); - if let Some(graph) = render_graph.get_sub_graph_mut(sub_graph) { - graph.add_node(node_label, node); - } else { - warn!( - "Tried adding a render graph node to {sub_graph:?} but the sub graph doesn't exist" - ); - } - self - } - - #[track_caller] - fn add_render_graph_edges( - &mut self, - sub_graph: impl RenderSubGraph, - edges: impl IntoRenderNodeArray, - ) -> &mut Self { - let sub_graph = sub_graph.intern(); - let mut render_graph = self.get_resource_mut::().expect( - "RenderGraph not found. Make sure you are using add_render_graph_edges on the RenderApp", - ); - if let Some(graph) = render_graph.get_sub_graph_mut(sub_graph) { - graph.add_node_edges(edges); - } else { - warn!( - "Tried adding render graph edges to {sub_graph:?} but the sub graph doesn't exist" - ); - } - self - } - - fn add_render_graph_edge( - &mut self, - sub_graph: impl RenderSubGraph, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> &mut Self { - let sub_graph = sub_graph.intern(); - let mut render_graph = self.get_resource_mut::().expect( - "RenderGraph not found. Make sure you are using add_render_graph_edge on the RenderApp", - ); - if let Some(graph) = render_graph.get_sub_graph_mut(sub_graph) { - graph.add_node_edge(output_node, input_node); - } else { - warn!( - "Tried adding a render graph edge to {sub_graph:?} but the sub graph doesn't exist" - ); - } - self - } - - fn add_render_sub_graph(&mut self, sub_graph: impl RenderSubGraph) -> &mut Self { - let mut render_graph = self.get_resource_mut::().expect( - "RenderGraph not found. Make sure you are using add_render_sub_graph on the RenderApp", - ); - render_graph.add_sub_graph(sub_graph, RenderGraph::default()); - self - } -} - -impl RenderGraphExt for SubApp { - fn add_render_graph_node( - &mut self, - sub_graph: impl RenderSubGraph, - node_label: impl RenderLabel, - ) -> &mut Self { - World::add_render_graph_node::(self.world_mut(), sub_graph, node_label); - self - } - - fn add_render_graph_edge( - &mut self, - sub_graph: impl RenderSubGraph, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> &mut Self { - World::add_render_graph_edge(self.world_mut(), sub_graph, output_node, input_node); - self - } - - #[track_caller] - fn add_render_graph_edges( - &mut self, - sub_graph: impl RenderSubGraph, - edges: impl IntoRenderNodeArray, - ) -> &mut Self { - World::add_render_graph_edges(self.world_mut(), sub_graph, edges); - self - } - - fn add_render_sub_graph(&mut self, sub_graph: impl RenderSubGraph) -> &mut Self { - World::add_render_sub_graph(self.world_mut(), sub_graph); - self - } -} - -impl RenderGraphExt for App { - fn add_render_graph_node( - &mut self, - sub_graph: impl RenderSubGraph, - node_label: impl RenderLabel, - ) -> &mut Self { - World::add_render_graph_node::(self.world_mut(), sub_graph, node_label); - self - } - - fn add_render_graph_edge( - &mut self, - sub_graph: impl RenderSubGraph, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> &mut Self { - World::add_render_graph_edge(self.world_mut(), sub_graph, output_node, input_node); - self - } - - fn add_render_graph_edges( - &mut self, - sub_graph: impl RenderSubGraph, - edges: impl IntoRenderNodeArray, - ) -> &mut Self { - World::add_render_graph_edges(self.world_mut(), sub_graph, edges); - self - } - - fn add_render_sub_graph(&mut self, sub_graph: impl RenderSubGraph) -> &mut Self { - World::add_render_sub_graph(self.world_mut(), sub_graph); - self - } -} diff --git a/third_party/bevy_render/src/render_graph/camera_driver_node.rs b/third_party/bevy_render/src/render_graph/camera_driver_node.rs deleted file mode 100644 index 4ba4e93..0000000 --- a/third_party/bevy_render/src/render_graph/camera_driver_node.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::{ - camera::{ExtractedCamera, SortedCameras}, - render_graph::{Node, NodeRunError, RenderGraphContext}, - renderer::RenderContext, - view::ExtractedWindows, -}; -use bevy_camera::{ClearColor, NormalizedRenderTarget}; -use bevy_ecs::{entity::ContainsEntity, prelude::QueryState, world::World}; -use bevy_platform::collections::HashSet; -use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp}; - -pub struct CameraDriverNode { - cameras: QueryState<&'static ExtractedCamera>, -} - -impl CameraDriverNode { - pub fn new(world: &mut World) -> Self { - Self { - cameras: world.query(), - } - } -} - -impl Node for CameraDriverNode { - fn update(&mut self, world: &mut World) { - self.cameras.update_archetypes(world); - } - fn run( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let sorted_cameras = world.resource::(); - let windows = world.resource::(); - let mut camera_windows = >::default(); - for sorted_camera in &sorted_cameras.0 { - let Ok(camera) = self.cameras.get_manual(world, sorted_camera.entity) else { - continue; - }; - - let mut run_graph = true; - if let Some(NormalizedRenderTarget::Window(window_ref)) = camera.target { - let window_entity = window_ref.entity(); - if windows - .windows - .get(&window_entity) - .is_some_and(|w| w.physical_width > 0 && w.physical_height > 0) - { - camera_windows.insert(window_entity); - } else { - // The window doesn't exist anymore or zero-sized so we don't need to run the graph - run_graph = false; - } - } - if run_graph { - graph.run_sub_graph( - camera.render_graph, - vec![], - Some(sorted_camera.entity), - Some(format!( - "Camera {} ({})", - sorted_camera.order, sorted_camera.entity - )), - )?; - } - } - - let clear_color_global = world.resource::(); - - // wgpu (and some backends) require doing work for swap chains if you call `get_current_texture()` and `present()` - // This ensures that Bevy doesn't crash, even when there are no cameras (and therefore no work submitted). - for (id, window) in world.resource::().iter() { - if camera_windows.contains(id) && render_context.has_commands() { - continue; - } - - let Some(swap_chain_texture) = &window.swap_chain_texture_view else { - continue; - }; - - #[cfg(feature = "trace")] - let _span = tracing::info_span!("no_camera_clear_pass").entered(); - let pass_descriptor = RenderPassDescriptor { - label: Some("no_camera_clear_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view: swap_chain_texture, - depth_slice: None, - resolve_target: None, - ops: Operations { - load: LoadOp::Clear(clear_color_global.to_linear().into()), - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; - - render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - } - - Ok(()) - } -} diff --git a/third_party/bevy_render/src/render_graph/context.rs b/third_party/bevy_render/src/render_graph/context.rs deleted file mode 100644 index b1f35ed..0000000 --- a/third_party/bevy_render/src/render_graph/context.rs +++ /dev/null @@ -1,286 +0,0 @@ -use crate::{ - render_graph::{NodeState, RenderGraph, SlotInfos, SlotLabel, SlotType, SlotValue}, - render_resource::{Buffer, Sampler, TextureView}, -}; -use alloc::borrow::Cow; -use bevy_ecs::{entity::Entity, intern::Interned}; -use thiserror::Error; - -use super::{InternedRenderSubGraph, RenderLabel, RenderSubGraph}; - -/// A command that signals the graph runner to run the sub graph corresponding to the `sub_graph` -/// with the specified `inputs` next. -pub struct RunSubGraph { - pub sub_graph: InternedRenderSubGraph, - pub inputs: Vec, - pub view_entity: Option, - pub debug_group: Option, -} - -/// The context with all graph information required to run a [`Node`](super::Node). -/// This context is created for each node by the render graph runner. -/// -/// The slot input can be read from here and the outputs must be written back to the context for -/// passing them onto the next node. -/// -/// Sub graphs can be queued for running by adding a [`RunSubGraph`] command to the context. -/// After the node has finished running the graph runner is responsible for executing the sub graphs. -pub struct RenderGraphContext<'a> { - graph: &'a RenderGraph, - node: &'a NodeState, - inputs: &'a [SlotValue], - outputs: &'a mut [Option], - run_sub_graphs: Vec, - /// The `view_entity` associated with the render graph being executed - /// This is optional because you aren't required to have a `view_entity` for a node. - /// For example, compute shader nodes don't have one. - /// It should always be set when the [`RenderGraph`] is running on a View. - view_entity: Option, -} - -impl<'a> RenderGraphContext<'a> { - /// Creates a new render graph context for the `node`. - pub fn new( - graph: &'a RenderGraph, - node: &'a NodeState, - inputs: &'a [SlotValue], - outputs: &'a mut [Option], - ) -> Self { - Self { - graph, - node, - inputs, - outputs, - run_sub_graphs: Vec::new(), - view_entity: None, - } - } - - /// Returns the input slot values for the node. - #[inline] - pub fn inputs(&self) -> &[SlotValue] { - self.inputs - } - - /// Returns the [`SlotInfos`] of the inputs. - pub fn input_info(&self) -> &SlotInfos { - &self.node.input_slots - } - - /// Returns the [`SlotInfos`] of the outputs. - pub fn output_info(&self) -> &SlotInfos { - &self.node.output_slots - } - - /// Retrieves the input slot value referenced by the `label`. - pub fn get_input(&self, label: impl Into) -> Result<&SlotValue, InputSlotError> { - let label = label.into(); - let index = self - .input_info() - .get_slot_index(label.clone()) - .ok_or(InputSlotError::InvalidSlot(label))?; - Ok(&self.inputs[index]) - } - - // TODO: should this return an Arc or a reference? - /// Retrieves the input slot value referenced by the `label` as a [`TextureView`]. - pub fn get_input_texture( - &self, - label: impl Into, - ) -> Result<&TextureView, InputSlotError> { - let label = label.into(); - match self.get_input(label.clone())? { - SlotValue::TextureView(value) => Ok(value), - value => Err(InputSlotError::MismatchedSlotType { - label, - actual: value.slot_type(), - expected: SlotType::TextureView, - }), - } - } - - /// Retrieves the input slot value referenced by the `label` as a [`Sampler`]. - pub fn get_input_sampler( - &self, - label: impl Into, - ) -> Result<&Sampler, InputSlotError> { - let label = label.into(); - match self.get_input(label.clone())? { - SlotValue::Sampler(value) => Ok(value), - value => Err(InputSlotError::MismatchedSlotType { - label, - actual: value.slot_type(), - expected: SlotType::Sampler, - }), - } - } - - /// Retrieves the input slot value referenced by the `label` as a [`Buffer`]. - pub fn get_input_buffer(&self, label: impl Into) -> Result<&Buffer, InputSlotError> { - let label = label.into(); - match self.get_input(label.clone())? { - SlotValue::Buffer(value) => Ok(value), - value => Err(InputSlotError::MismatchedSlotType { - label, - actual: value.slot_type(), - expected: SlotType::Buffer, - }), - } - } - - /// Retrieves the input slot value referenced by the `label` as an [`Entity`]. - pub fn get_input_entity(&self, label: impl Into) -> Result { - let label = label.into(); - match self.get_input(label.clone())? { - SlotValue::Entity(value) => Ok(*value), - value => Err(InputSlotError::MismatchedSlotType { - label, - actual: value.slot_type(), - expected: SlotType::Entity, - }), - } - } - - /// Sets the output slot value referenced by the `label`. - pub fn set_output( - &mut self, - label: impl Into, - value: impl Into, - ) -> Result<(), OutputSlotError> { - let label = label.into(); - let value = value.into(); - let slot_index = self - .output_info() - .get_slot_index(label.clone()) - .ok_or_else(|| OutputSlotError::InvalidSlot(label.clone()))?; - let slot = self - .output_info() - .get_slot(slot_index) - .expect("slot is valid"); - if value.slot_type() != slot.slot_type { - return Err(OutputSlotError::MismatchedSlotType { - label, - actual: slot.slot_type, - expected: value.slot_type(), - }); - } - self.outputs[slot_index] = Some(value); - Ok(()) - } - - pub fn view_entity(&self) -> Entity { - self.view_entity.unwrap() - } - - pub fn get_view_entity(&self) -> Option { - self.view_entity - } - - pub fn set_view_entity(&mut self, view_entity: Entity) { - self.view_entity = Some(view_entity); - } - - /// Queues up a sub graph for execution after the node has finished running. - pub fn run_sub_graph( - &mut self, - name: impl RenderSubGraph, - inputs: Vec, - view_entity: Option, - debug_group: Option, - ) -> Result<(), RunSubGraphError> { - let name = name.intern(); - let sub_graph = self - .graph - .get_sub_graph(name) - .ok_or(RunSubGraphError::MissingSubGraph(name))?; - if let Some(input_node) = sub_graph.get_input_node() { - for (i, input_slot) in input_node.input_slots.iter().enumerate() { - if let Some(input_value) = inputs.get(i) { - if input_slot.slot_type != input_value.slot_type() { - return Err(RunSubGraphError::MismatchedInputSlotType { - graph_name: name, - slot_index: i, - actual: input_value.slot_type(), - expected: input_slot.slot_type, - label: input_slot.name.clone().into(), - }); - } - } else { - return Err(RunSubGraphError::MissingInput { - slot_index: i, - slot_name: input_slot.name.clone(), - graph_name: name, - }); - } - } - } else if !inputs.is_empty() { - return Err(RunSubGraphError::SubGraphHasNoInputs(name)); - } - - self.run_sub_graphs.push(RunSubGraph { - sub_graph: name, - inputs, - view_entity, - debug_group, - }); - - Ok(()) - } - - /// Returns a human-readable label for this node, for debugging purposes. - pub fn label(&self) -> Interned { - self.node.label - } - - /// Finishes the context for this [`Node`](super::Node) by - /// returning the sub graphs to run next. - pub fn finish(self) -> Vec { - self.run_sub_graphs - } -} - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum RunSubGraphError { - #[error("attempted to run sub-graph `{0:?}`, but it does not exist")] - MissingSubGraph(InternedRenderSubGraph), - #[error("attempted to pass inputs to sub-graph `{0:?}`, which has no input slots")] - SubGraphHasNoInputs(InternedRenderSubGraph), - #[error("sub graph (name: `{graph_name:?}`) could not be run because slot `{slot_name}` at index {slot_index} has no value")] - MissingInput { - slot_index: usize, - slot_name: Cow<'static, str>, - graph_name: InternedRenderSubGraph, - }, - #[error("attempted to use the wrong type for input slot")] - MismatchedInputSlotType { - graph_name: InternedRenderSubGraph, - slot_index: usize, - label: SlotLabel, - expected: SlotType, - actual: SlotType, - }, -} - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum OutputSlotError { - #[error("output slot `{0:?}` does not exist")] - InvalidSlot(SlotLabel), - #[error("attempted to output a value of type `{actual}` to output slot `{label:?}`, which has type `{expected}`")] - MismatchedSlotType { - label: SlotLabel, - expected: SlotType, - actual: SlotType, - }, -} - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum InputSlotError { - #[error("input slot `{0:?}` does not exist")] - InvalidSlot(SlotLabel), - #[error("attempted to retrieve a value of type `{actual}` from input slot `{label:?}`, which has type `{expected}`")] - MismatchedSlotType { - label: SlotLabel, - expected: SlotType, - actual: SlotType, - }, -} diff --git a/third_party/bevy_render/src/render_graph/edge.rs b/third_party/bevy_render/src/render_graph/edge.rs deleted file mode 100644 index 199b7e8..0000000 --- a/third_party/bevy_render/src/render_graph/edge.rs +++ /dev/null @@ -1,57 +0,0 @@ -use super::InternedRenderLabel; - -/// An edge, which connects two [`Nodes`](super::Node) in -/// a [`RenderGraph`](crate::render_graph::RenderGraph). -/// -/// They are used to describe the ordering (which node has to run first) -/// and may be of two kinds: [`NodeEdge`](Self::NodeEdge) and [`SlotEdge`](Self::SlotEdge). -/// -/// Edges are added via the [`RenderGraph::add_node_edge`] and the -/// [`RenderGraph::add_slot_edge`] methods. -/// -/// The former simply states that the `output_node` has to be run before the `input_node`, -/// while the later connects an output slot of the `output_node` -/// with an input slot of the `input_node` to pass additional data along. -/// For more information see [`SlotType`](super::SlotType). -/// -/// [`RenderGraph::add_node_edge`]: crate::render_graph::RenderGraph::add_node_edge -/// [`RenderGraph::add_slot_edge`]: crate::render_graph::RenderGraph::add_slot_edge -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Edge { - /// An edge describing to ordering of both nodes (`output_node` before `input_node`) - /// and connecting the output slot at the `output_index` of the `output_node` - /// with the slot at the `input_index` of the `input_node`. - SlotEdge { - input_node: InternedRenderLabel, - input_index: usize, - output_node: InternedRenderLabel, - output_index: usize, - }, - /// An edge describing to ordering of both nodes (`output_node` before `input_node`). - NodeEdge { - input_node: InternedRenderLabel, - output_node: InternedRenderLabel, - }, -} - -impl Edge { - /// Returns the id of the `input_node`. - pub fn get_input_node(&self) -> InternedRenderLabel { - match self { - Edge::SlotEdge { input_node, .. } | Edge::NodeEdge { input_node, .. } => *input_node, - } - } - - /// Returns the id of the `output_node`. - pub fn get_output_node(&self) -> InternedRenderLabel { - match self { - Edge::SlotEdge { output_node, .. } | Edge::NodeEdge { output_node, .. } => *output_node, - } - } -} - -#[derive(PartialEq, Eq)] -pub enum EdgeExistence { - Exists, - DoesNotExist, -} diff --git a/third_party/bevy_render/src/render_graph/graph.rs b/third_party/bevy_render/src/render_graph/graph.rs deleted file mode 100644 index b7f8328..0000000 --- a/third_party/bevy_render/src/render_graph/graph.rs +++ /dev/null @@ -1,918 +0,0 @@ -use crate::{ - render_graph::{ - Edge, Node, NodeRunError, NodeState, RenderGraphContext, RenderGraphError, RenderLabel, - SlotInfo, SlotLabel, - }, - renderer::RenderContext, -}; -use bevy_ecs::{define_label, intern::Interned, prelude::World, resource::Resource}; -use bevy_platform::collections::HashMap; -use core::fmt::Debug; - -use super::{EdgeExistence, InternedRenderLabel, IntoRenderNodeArray}; - -pub use bevy_render_macros::RenderSubGraph; - -define_label!( - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(RenderSubGraph)]`" - )] - /// A strongly-typed class of labels used to identify a [`SubGraph`] in a render graph. - RenderSubGraph, - RENDER_SUB_GRAPH_INTERNER -); - -/// A shorthand for `Interned`. -pub type InternedRenderSubGraph = Interned; - -/// The render graph configures the modular and re-usable render logic. -/// -/// It is a retained and stateless (nodes themselves may have their own internal state) structure, -/// which can not be modified while it is executed by the graph runner. -/// -/// The render graph runner is responsible for executing the entire graph each frame. -/// It will execute each node in the graph in the correct order, based on the edges between the nodes. -/// -/// It consists of three main components: [`Nodes`](Node), [`Edges`](Edge) -/// and [`Slots`](super::SlotType). -/// -/// Nodes are responsible for generating draw calls and operating on input and output slots. -/// Edges specify the order of execution for nodes and connect input and output slots together. -/// Slots describe the render resources created or used by the nodes. -/// -/// Additionally a render graph can contain multiple sub graphs, which are run by the -/// corresponding nodes. Every render graph can have its own optional input node. -/// -/// ## Example -/// Here is a simple render graph example with two nodes connected by a node edge. -/// ```ignore -/// # TODO: Remove when #10645 is fixed -/// # use bevy_app::prelude::*; -/// # use bevy_ecs::prelude::World; -/// # use bevy_render::render_graph::{RenderGraph, RenderLabel, Node, RenderGraphContext, NodeRunError}; -/// # use bevy_render::renderer::RenderContext; -/// # -/// #[derive(RenderLabel)] -/// enum Labels { -/// A, -/// B, -/// } -/// -/// # struct MyNode; -/// # -/// # impl Node for MyNode { -/// # fn run(&self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, world: &World) -> Result<(), NodeRunError> { -/// # unimplemented!() -/// # } -/// # } -/// # -/// let mut graph = RenderGraph::default(); -/// graph.add_node(Labels::A, MyNode); -/// graph.add_node(Labels::B, MyNode); -/// graph.add_node_edge(Labels::B, Labels::A); -/// ``` -#[derive(Resource, Default)] -pub struct RenderGraph { - nodes: HashMap, - sub_graphs: HashMap, -} - -/// The label for the input node of a graph. Used to connect other nodes to it. -#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] -pub struct GraphInput; - -impl RenderGraph { - /// Updates all nodes and sub graphs of the render graph. Should be called before executing it. - pub fn update(&mut self, world: &mut World) { - for node in self.nodes.values_mut() { - node.node.update(world); - } - - for sub_graph in self.sub_graphs.values_mut() { - sub_graph.update(world); - } - } - - /// Creates an [`GraphInputNode`] with the specified slots if not already present. - pub fn set_input(&mut self, inputs: Vec) { - assert!( - matches!( - self.get_node_state(GraphInput), - Err(RenderGraphError::InvalidNode(_)) - ), - "Graph already has an input node" - ); - - self.add_node(GraphInput, GraphInputNode { inputs }); - } - - /// Returns the [`NodeState`] of the input node of this graph. - /// - /// # See also - /// - /// - [`input_node`](Self::input_node) for an unchecked version. - #[inline] - pub fn get_input_node(&self) -> Option<&NodeState> { - self.get_node_state(GraphInput).ok() - } - - /// Returns the [`NodeState`] of the input node of this graph. - /// - /// # Panics - /// - /// Panics if there is no input node set. - /// - /// # See also - /// - /// - [`get_input_node`](Self::get_input_node) for a version which returns an [`Option`] instead. - #[inline] - pub fn input_node(&self) -> &NodeState { - self.get_input_node().unwrap() - } - - /// Adds the `node` with the `label` to the graph. - /// If the label is already present replaces it instead. - pub fn add_node(&mut self, label: impl RenderLabel, node: T) - where - T: Node, - { - let label = label.intern(); - let node_state = NodeState::new(label, node); - self.nodes.insert(label, node_state); - } - - /// Add `node_edge`s based on the order of the given `edges` array. - /// - /// Defining an edge that already exists is not considered an error with this api. - /// It simply won't create a new edge. - #[track_caller] - pub fn add_node_edges(&mut self, edges: impl IntoRenderNodeArray) { - for window in edges.into_array().windows(2) { - let [a, b] = window else { - break; - }; - if let Err(err) = self.try_add_node_edge(*a, *b) { - match err { - // Already existing edges are very easy to produce with this api - // and shouldn't cause a panic - RenderGraphError::EdgeAlreadyExists(_) => {} - _ => panic!("{err}"), - } - } - } - } - - /// Removes the `node` with the `label` from the graph. - /// If the label does not exist, nothing happens. - pub fn remove_node(&mut self, label: impl RenderLabel) -> Result<(), RenderGraphError> { - let label = label.intern(); - if let Some(node_state) = self.nodes.remove(&label) { - // Remove all edges from other nodes to this one. Note that as we're removing this - // node, we don't need to remove its input edges - for input_edge in node_state.edges.input_edges() { - match input_edge { - Edge::SlotEdge { output_node, .. } - | Edge::NodeEdge { - input_node: _, - output_node, - } => { - if let Ok(output_node) = self.get_node_state_mut(*output_node) { - output_node.edges.remove_output_edge(input_edge.clone())?; - } - } - } - } - // Remove all edges from this node to other nodes. Note that as we're removing this - // node, we don't need to remove its output edges - for output_edge in node_state.edges.output_edges() { - match output_edge { - Edge::SlotEdge { - output_node: _, - output_index: _, - input_node, - input_index: _, - } - | Edge::NodeEdge { - output_node: _, - input_node, - } => { - if let Ok(input_node) = self.get_node_state_mut(*input_node) { - input_node.edges.remove_input_edge(output_edge.clone())?; - } - } - } - } - } - - Ok(()) - } - - /// Retrieves the [`NodeState`] referenced by the `label`. - pub fn get_node_state(&self, label: impl RenderLabel) -> Result<&NodeState, RenderGraphError> { - let label = label.intern(); - self.nodes - .get(&label) - .ok_or(RenderGraphError::InvalidNode(label)) - } - - /// Retrieves the [`NodeState`] referenced by the `label` mutably. - pub fn get_node_state_mut( - &mut self, - label: impl RenderLabel, - ) -> Result<&mut NodeState, RenderGraphError> { - let label = label.intern(); - self.nodes - .get_mut(&label) - .ok_or(RenderGraphError::InvalidNode(label)) - } - - /// Retrieves the [`Node`] referenced by the `label`. - pub fn get_node(&self, label: impl RenderLabel) -> Result<&T, RenderGraphError> - where - T: Node, - { - self.get_node_state(label).and_then(|n| n.node()) - } - - /// Retrieves the [`Node`] referenced by the `label` mutably. - pub fn get_node_mut(&mut self, label: impl RenderLabel) -> Result<&mut T, RenderGraphError> - where - T: Node, - { - self.get_node_state_mut(label).and_then(|n| n.node_mut()) - } - - /// Adds the [`Edge::SlotEdge`] to the graph. This guarantees that the `output_node` - /// is run before the `input_node` and also connects the `output_slot` to the `input_slot`. - /// - /// Fails if any invalid [`RenderLabel`]s or [`SlotLabel`]s are given. - /// - /// # See also - /// - /// - [`add_slot_edge`](Self::add_slot_edge) for an infallible version. - pub fn try_add_slot_edge( - &mut self, - output_node: impl RenderLabel, - output_slot: impl Into, - input_node: impl RenderLabel, - input_slot: impl Into, - ) -> Result<(), RenderGraphError> { - let output_slot = output_slot.into(); - let input_slot = input_slot.into(); - - let output_node = output_node.intern(); - let input_node = input_node.intern(); - - let output_index = self - .get_node_state(output_node)? - .output_slots - .get_slot_index(output_slot.clone()) - .ok_or(RenderGraphError::InvalidOutputNodeSlot(output_slot))?; - let input_index = self - .get_node_state(input_node)? - .input_slots - .get_slot_index(input_slot.clone()) - .ok_or(RenderGraphError::InvalidInputNodeSlot(input_slot))?; - - let edge = Edge::SlotEdge { - output_node, - output_index, - input_node, - input_index, - }; - - self.validate_edge(&edge, EdgeExistence::DoesNotExist)?; - - { - let output_node = self.get_node_state_mut(output_node)?; - output_node.edges.add_output_edge(edge.clone())?; - } - let input_node = self.get_node_state_mut(input_node)?; - input_node.edges.add_input_edge(edge)?; - - Ok(()) - } - - /// Adds the [`Edge::SlotEdge`] to the graph. This guarantees that the `output_node` - /// is run before the `input_node` and also connects the `output_slot` to the `input_slot`. - /// - /// # Panics - /// - /// Any invalid [`RenderLabel`]s or [`SlotLabel`]s are given. - /// - /// # See also - /// - /// - [`try_add_slot_edge`](Self::try_add_slot_edge) for a fallible version. - pub fn add_slot_edge( - &mut self, - output_node: impl RenderLabel, - output_slot: impl Into, - input_node: impl RenderLabel, - input_slot: impl Into, - ) { - self.try_add_slot_edge(output_node, output_slot, input_node, input_slot) - .unwrap(); - } - - /// Removes the [`Edge::SlotEdge`] from the graph. If any nodes or slots do not exist then - /// nothing happens. - pub fn remove_slot_edge( - &mut self, - output_node: impl RenderLabel, - output_slot: impl Into, - input_node: impl RenderLabel, - input_slot: impl Into, - ) -> Result<(), RenderGraphError> { - let output_slot = output_slot.into(); - let input_slot = input_slot.into(); - - let output_node = output_node.intern(); - let input_node = input_node.intern(); - - let output_index = self - .get_node_state(output_node)? - .output_slots - .get_slot_index(output_slot.clone()) - .ok_or(RenderGraphError::InvalidOutputNodeSlot(output_slot))?; - let input_index = self - .get_node_state(input_node)? - .input_slots - .get_slot_index(input_slot.clone()) - .ok_or(RenderGraphError::InvalidInputNodeSlot(input_slot))?; - - let edge = Edge::SlotEdge { - output_node, - output_index, - input_node, - input_index, - }; - - self.validate_edge(&edge, EdgeExistence::Exists)?; - - { - let output_node = self.get_node_state_mut(output_node)?; - output_node.edges.remove_output_edge(edge.clone())?; - } - let input_node = self.get_node_state_mut(input_node)?; - input_node.edges.remove_input_edge(edge)?; - - Ok(()) - } - - /// Adds the [`Edge::NodeEdge`] to the graph. This guarantees that the `output_node` - /// is run before the `input_node`. - /// - /// Fails if any invalid [`RenderLabel`] is given. - /// - /// # See also - /// - /// - [`add_node_edge`](Self::add_node_edge) for an infallible version. - pub fn try_add_node_edge( - &mut self, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> Result<(), RenderGraphError> { - let output_node = output_node.intern(); - let input_node = input_node.intern(); - - let edge = Edge::NodeEdge { - output_node, - input_node, - }; - - self.validate_edge(&edge, EdgeExistence::DoesNotExist)?; - - { - let output_node = self.get_node_state_mut(output_node)?; - output_node.edges.add_output_edge(edge.clone())?; - } - let input_node = self.get_node_state_mut(input_node)?; - input_node.edges.add_input_edge(edge)?; - - Ok(()) - } - - /// Adds the [`Edge::NodeEdge`] to the graph. This guarantees that the `output_node` - /// is run before the `input_node`. - /// - /// # Panics - /// - /// Panics if any invalid [`RenderLabel`] is given. - /// - /// # See also - /// - /// - [`try_add_node_edge`](Self::try_add_node_edge) for a fallible version. - pub fn add_node_edge(&mut self, output_node: impl RenderLabel, input_node: impl RenderLabel) { - self.try_add_node_edge(output_node, input_node).unwrap(); - } - - /// Removes the [`Edge::NodeEdge`] from the graph. If either node does not exist then nothing - /// happens. - pub fn remove_node_edge( - &mut self, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> Result<(), RenderGraphError> { - let output_node = output_node.intern(); - let input_node = input_node.intern(); - - let edge = Edge::NodeEdge { - output_node, - input_node, - }; - - self.validate_edge(&edge, EdgeExistence::Exists)?; - - { - let output_node = self.get_node_state_mut(output_node)?; - output_node.edges.remove_output_edge(edge.clone())?; - } - let input_node = self.get_node_state_mut(input_node)?; - input_node.edges.remove_input_edge(edge)?; - - Ok(()) - } - - /// Verifies that the edge existence is as expected and - /// checks that slot edges are connected correctly. - pub fn validate_edge( - &mut self, - edge: &Edge, - should_exist: EdgeExistence, - ) -> Result<(), RenderGraphError> { - if should_exist == EdgeExistence::Exists && !self.has_edge(edge) { - return Err(RenderGraphError::EdgeDoesNotExist(edge.clone())); - } else if should_exist == EdgeExistence::DoesNotExist && self.has_edge(edge) { - return Err(RenderGraphError::EdgeAlreadyExists(edge.clone())); - } - - match *edge { - Edge::SlotEdge { - output_node, - output_index, - input_node, - input_index, - } => { - let output_node_state = self.get_node_state(output_node)?; - let input_node_state = self.get_node_state(input_node)?; - - let output_slot = output_node_state - .output_slots - .get_slot(output_index) - .ok_or(RenderGraphError::InvalidOutputNodeSlot(SlotLabel::Index( - output_index, - )))?; - let input_slot = input_node_state.input_slots.get_slot(input_index).ok_or( - RenderGraphError::InvalidInputNodeSlot(SlotLabel::Index(input_index)), - )?; - - if let Some(Edge::SlotEdge { - output_node: current_output_node, - .. - }) = input_node_state.edges.input_edges().iter().find(|e| { - if let Edge::SlotEdge { - input_index: current_input_index, - .. - } = e - { - input_index == *current_input_index - } else { - false - } - }) && should_exist == EdgeExistence::DoesNotExist - { - return Err(RenderGraphError::NodeInputSlotAlreadyOccupied { - node: input_node, - input_slot: input_index, - occupied_by_node: *current_output_node, - }); - } - - if output_slot.slot_type != input_slot.slot_type { - return Err(RenderGraphError::MismatchedNodeSlots { - output_node, - output_slot: output_index, - input_node, - input_slot: input_index, - }); - } - } - Edge::NodeEdge { .. } => { /* nothing to validate here */ } - } - - Ok(()) - } - - /// Checks whether the `edge` already exists in the graph. - pub fn has_edge(&self, edge: &Edge) -> bool { - let output_node_state = self.get_node_state(edge.get_output_node()); - let input_node_state = self.get_node_state(edge.get_input_node()); - if let Ok(output_node_state) = output_node_state - && output_node_state.edges.output_edges().contains(edge) - && let Ok(input_node_state) = input_node_state - && input_node_state.edges.input_edges().contains(edge) - { - return true; - } - - false - } - - /// Returns an iterator over the [`NodeStates`](NodeState). - pub fn iter_nodes(&self) -> impl Iterator { - self.nodes.values() - } - - /// Returns an iterator over the [`NodeStates`](NodeState), that allows modifying each value. - pub fn iter_nodes_mut(&mut self) -> impl Iterator { - self.nodes.values_mut() - } - - /// Returns an iterator over the sub graphs. - pub fn iter_sub_graphs(&self) -> impl Iterator { - self.sub_graphs.iter().map(|(name, graph)| (*name, graph)) - } - - /// Returns an iterator over the sub graphs, that allows modifying each value. - pub fn iter_sub_graphs_mut( - &mut self, - ) -> impl Iterator { - self.sub_graphs - .iter_mut() - .map(|(name, graph)| (*name, graph)) - } - - /// Returns an iterator over a tuple of the input edges and the corresponding output nodes - /// for the node referenced by the label. - pub fn iter_node_inputs( - &self, - label: impl RenderLabel, - ) -> Result, RenderGraphError> { - let node = self.get_node_state(label)?; - Ok(node - .edges - .input_edges() - .iter() - .map(|edge| (edge, edge.get_output_node())) - .map(move |(edge, output_node)| (edge, self.get_node_state(output_node).unwrap()))) - } - - /// Returns an iterator over a tuple of the output edges and the corresponding input nodes - /// for the node referenced by the label. - pub fn iter_node_outputs( - &self, - label: impl RenderLabel, - ) -> Result, RenderGraphError> { - let node = self.get_node_state(label)?; - Ok(node - .edges - .output_edges() - .iter() - .map(|edge| (edge, edge.get_input_node())) - .map(move |(edge, input_node)| (edge, self.get_node_state(input_node).unwrap()))) - } - - /// Adds the `sub_graph` with the `label` to the graph. - /// If the label is already present replaces it instead. - pub fn add_sub_graph(&mut self, label: impl RenderSubGraph, sub_graph: RenderGraph) { - self.sub_graphs.insert(label.intern(), sub_graph); - } - - /// Removes the `sub_graph` with the `label` from the graph. - /// If the label does not exist then nothing happens. - pub fn remove_sub_graph(&mut self, label: impl RenderSubGraph) { - self.sub_graphs.remove(&label.intern()); - } - - /// Retrieves the sub graph corresponding to the `label`. - pub fn get_sub_graph(&self, label: impl RenderSubGraph) -> Option<&RenderGraph> { - self.sub_graphs.get(&label.intern()) - } - - /// Retrieves the sub graph corresponding to the `label` mutably. - pub fn get_sub_graph_mut(&mut self, label: impl RenderSubGraph) -> Option<&mut RenderGraph> { - self.sub_graphs.get_mut(&label.intern()) - } - - /// Retrieves the sub graph corresponding to the `label`. - /// - /// # Panics - /// - /// Panics if any invalid subgraph label is given. - /// - /// # See also - /// - /// - [`get_sub_graph`](Self::get_sub_graph) for a fallible version. - pub fn sub_graph(&self, label: impl RenderSubGraph) -> &RenderGraph { - let label = label.intern(); - self.sub_graphs - .get(&label) - .unwrap_or_else(|| panic!("Subgraph {label:?} not found")) - } - - /// Retrieves the sub graph corresponding to the `label` mutably. - /// - /// # Panics - /// - /// Panics if any invalid subgraph label is given. - /// - /// # See also - /// - /// - [`get_sub_graph_mut`](Self::get_sub_graph_mut) for a fallible version. - pub fn sub_graph_mut(&mut self, label: impl RenderSubGraph) -> &mut RenderGraph { - let label = label.intern(); - self.sub_graphs - .get_mut(&label) - .unwrap_or_else(|| panic!("Subgraph {label:?} not found")) - } -} - -impl Debug for RenderGraph { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - for node in self.iter_nodes() { - writeln!(f, "{:?}", node.label)?; - writeln!(f, " in: {:?}", node.input_slots)?; - writeln!(f, " out: {:?}", node.output_slots)?; - } - - Ok(()) - } -} - -/// A [`Node`] which acts as an entry point for a [`RenderGraph`] with custom inputs. -/// It has the same input and output slots and simply copies them over when run. -pub struct GraphInputNode { - inputs: Vec, -} - -impl Node for GraphInputNode { - fn input(&self) -> Vec { - self.inputs.clone() - } - - fn output(&self) -> Vec { - self.inputs.clone() - } - - fn run( - &self, - graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - _world: &World, - ) -> Result<(), NodeRunError> { - for i in 0..graph.inputs().len() { - let input = graph.inputs()[i].clone(); - graph.set_output(i, input)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - render_graph::{ - node::IntoRenderNodeArray, Edge, InternedRenderLabel, Node, NodeRunError, RenderGraph, - RenderGraphContext, RenderGraphError, RenderLabel, SlotInfo, SlotType, - }, - renderer::RenderContext, - }; - use bevy_ecs::world::{FromWorld, World}; - use bevy_platform::collections::HashSet; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - enum TestLabel { - A, - B, - C, - D, - } - - #[derive(Debug)] - struct TestNode { - inputs: Vec, - outputs: Vec, - } - - impl TestNode { - pub fn new(inputs: usize, outputs: usize) -> Self { - TestNode { - inputs: (0..inputs) - .map(|i| SlotInfo::new(format!("in_{i}"), SlotType::TextureView)) - .collect(), - outputs: (0..outputs) - .map(|i| SlotInfo::new(format!("out_{i}"), SlotType::TextureView)) - .collect(), - } - } - } - - impl Node for TestNode { - fn input(&self) -> Vec { - self.inputs.clone() - } - - fn output(&self) -> Vec { - self.outputs.clone() - } - - fn run( - &self, - _: &mut RenderGraphContext, - _: &mut RenderContext, - _: &World, - ) -> Result<(), NodeRunError> { - Ok(()) - } - } - - fn input_nodes(label: impl RenderLabel, graph: &RenderGraph) -> HashSet { - graph - .iter_node_inputs(label) - .unwrap() - .map(|(_edge, node)| node.label) - .collect::>() - } - - fn output_nodes(label: impl RenderLabel, graph: &RenderGraph) -> HashSet { - graph - .iter_node_outputs(label) - .unwrap() - .map(|(_edge, node)| node.label) - .collect::>() - } - - #[test] - fn test_graph_edges() { - let mut graph = RenderGraph::default(); - graph.add_node(TestLabel::A, TestNode::new(0, 1)); - graph.add_node(TestLabel::B, TestNode::new(0, 1)); - graph.add_node(TestLabel::C, TestNode::new(1, 1)); - graph.add_node(TestLabel::D, TestNode::new(1, 0)); - - graph.add_slot_edge(TestLabel::A, "out_0", TestLabel::C, "in_0"); - graph.add_node_edge(TestLabel::B, TestLabel::C); - graph.add_slot_edge(TestLabel::C, 0, TestLabel::D, 0); - - assert!( - input_nodes(TestLabel::A, &graph).is_empty(), - "A has no inputs" - ); - assert_eq!( - output_nodes(TestLabel::A, &graph), - HashSet::from_iter((TestLabel::C,).into_array()), - "A outputs to C" - ); - - assert!( - input_nodes(TestLabel::B, &graph).is_empty(), - "B has no inputs" - ); - assert_eq!( - output_nodes(TestLabel::B, &graph), - HashSet::from_iter((TestLabel::C,).into_array()), - "B outputs to C" - ); - - assert_eq!( - input_nodes(TestLabel::C, &graph), - HashSet::from_iter((TestLabel::A, TestLabel::B).into_array()), - "A and B input to C" - ); - assert_eq!( - output_nodes(TestLabel::C, &graph), - HashSet::from_iter((TestLabel::D,).into_array()), - "C outputs to D" - ); - - assert_eq!( - input_nodes(TestLabel::D, &graph), - HashSet::from_iter((TestLabel::C,).into_array()), - "C inputs to D" - ); - assert!( - output_nodes(TestLabel::D, &graph).is_empty(), - "D has no outputs" - ); - } - - #[test] - fn test_get_node_typed() { - struct MyNode { - value: usize, - } - - impl Node for MyNode { - fn run( - &self, - _: &mut RenderGraphContext, - _: &mut RenderContext, - _: &World, - ) -> Result<(), NodeRunError> { - Ok(()) - } - } - - let mut graph = RenderGraph::default(); - - graph.add_node(TestLabel::A, MyNode { value: 42 }); - - let node: &MyNode = graph.get_node(TestLabel::A).unwrap(); - assert_eq!(node.value, 42, "node value matches"); - - let result: Result<&TestNode, RenderGraphError> = graph.get_node(TestLabel::A); - assert_eq!( - result.unwrap_err(), - RenderGraphError::WrongNodeType, - "expect a wrong node type error" - ); - } - - #[test] - fn test_slot_already_occupied() { - let mut graph = RenderGraph::default(); - - graph.add_node(TestLabel::A, TestNode::new(0, 1)); - graph.add_node(TestLabel::B, TestNode::new(0, 1)); - graph.add_node(TestLabel::C, TestNode::new(1, 1)); - - graph.add_slot_edge(TestLabel::A, 0, TestLabel::C, 0); - assert_eq!( - graph.try_add_slot_edge(TestLabel::B, 0, TestLabel::C, 0), - Err(RenderGraphError::NodeInputSlotAlreadyOccupied { - node: TestLabel::C.intern(), - input_slot: 0, - occupied_by_node: TestLabel::A.intern(), - }), - "Adding to a slot that is already occupied should return an error" - ); - } - - #[test] - fn test_edge_already_exists() { - let mut graph = RenderGraph::default(); - - graph.add_node(TestLabel::A, TestNode::new(0, 1)); - graph.add_node(TestLabel::B, TestNode::new(1, 0)); - - graph.add_slot_edge(TestLabel::A, 0, TestLabel::B, 0); - assert_eq!( - graph.try_add_slot_edge(TestLabel::A, 0, TestLabel::B, 0), - Err(RenderGraphError::EdgeAlreadyExists(Edge::SlotEdge { - output_node: TestLabel::A.intern(), - output_index: 0, - input_node: TestLabel::B.intern(), - input_index: 0, - })), - "Adding to a duplicate edge should return an error" - ); - } - - #[test] - fn test_add_node_edges() { - struct SimpleNode; - impl Node for SimpleNode { - fn run( - &self, - _graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - _world: &World, - ) -> Result<(), NodeRunError> { - Ok(()) - } - } - impl FromWorld for SimpleNode { - fn from_world(_world: &mut World) -> Self { - Self - } - } - - let mut graph = RenderGraph::default(); - graph.add_node(TestLabel::A, SimpleNode); - graph.add_node(TestLabel::B, SimpleNode); - graph.add_node(TestLabel::C, SimpleNode); - - graph.add_node_edges((TestLabel::A, TestLabel::B, TestLabel::C)); - - assert_eq!( - output_nodes(TestLabel::A, &graph), - HashSet::from_iter((TestLabel::B,).into_array()), - "A -> B" - ); - assert_eq!( - input_nodes(TestLabel::B, &graph), - HashSet::from_iter((TestLabel::A,).into_array()), - "A -> B" - ); - assert_eq!( - output_nodes(TestLabel::B, &graph), - HashSet::from_iter((TestLabel::C,).into_array()), - "B -> C" - ); - assert_eq!( - input_nodes(TestLabel::C, &graph), - HashSet::from_iter((TestLabel::B,).into_array()), - "B -> C" - ); - } -} diff --git a/third_party/bevy_render/src/render_graph/mod.rs b/third_party/bevy_render/src/render_graph/mod.rs deleted file mode 100644 index 6f98a30..0000000 --- a/third_party/bevy_render/src/render_graph/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -mod app; -mod camera_driver_node; -mod context; -mod edge; -mod graph; -mod node; -mod node_slot; - -pub use app::*; -pub use camera_driver_node::*; -pub use context::*; -pub use edge::*; -pub use graph::*; -pub use node::*; -pub use node_slot::*; - -use thiserror::Error; - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum RenderGraphError { - #[error("node {0:?} does not exist")] - InvalidNode(InternedRenderLabel), - #[error("output node slot does not exist")] - InvalidOutputNodeSlot(SlotLabel), - #[error("input node slot does not exist")] - InvalidInputNodeSlot(SlotLabel), - #[error("node does not match the given type")] - WrongNodeType, - #[error("attempted to connect output slot {output_slot} from node {output_node:?} to incompatible input slot {input_slot} from node {input_node:?}")] - MismatchedNodeSlots { - output_node: InternedRenderLabel, - output_slot: usize, - input_node: InternedRenderLabel, - input_slot: usize, - }, - #[error("attempted to add an edge that already exists")] - EdgeAlreadyExists(Edge), - #[error("attempted to remove an edge that does not exist")] - EdgeDoesNotExist(Edge), - #[error("node {node:?} has an unconnected input slot {input_slot}")] - UnconnectedNodeInputSlot { - node: InternedRenderLabel, - input_slot: usize, - }, - #[error("node {node:?} has an unconnected output slot {output_slot}")] - UnconnectedNodeOutputSlot { - node: InternedRenderLabel, - output_slot: usize, - }, - #[error("node {node:?} input slot {input_slot} already occupied by {occupied_by_node:?}")] - NodeInputSlotAlreadyOccupied { - node: InternedRenderLabel, - input_slot: usize, - occupied_by_node: InternedRenderLabel, - }, -} diff --git a/third_party/bevy_render/src/render_graph/node.rs b/third_party/bevy_render/src/render_graph/node.rs deleted file mode 100644 index bfc007b..0000000 --- a/third_party/bevy_render/src/render_graph/node.rs +++ /dev/null @@ -1,426 +0,0 @@ -use crate::{ - render_graph::{ - Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError, - RunSubGraphError, SlotInfo, SlotInfos, - }, - render_phase::DrawError, - renderer::RenderContext, -}; -pub use bevy_ecs::label::DynEq; -use bevy_ecs::{ - define_label, - intern::Interned, - query::{QueryItem, QueryState, ReadOnlyQueryData}, - world::{FromWorld, World}, -}; -use core::fmt::Debug; -use downcast_rs::{impl_downcast, Downcast}; -use thiserror::Error; -use variadics_please::all_tuples_with_size; - -pub use bevy_render_macros::RenderLabel; - -use super::{InternedRenderSubGraph, RenderSubGraph}; - -define_label!( - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(RenderLabel)]`" - )] - /// A strongly-typed class of labels used to identify a [`Node`] in a render graph. - RenderLabel, - RENDER_LABEL_INTERNER -); - -/// A shorthand for `Interned`. -pub type InternedRenderLabel = Interned; - -pub trait IntoRenderNodeArray { - fn into_array(self) -> [InternedRenderLabel; N]; -} - -impl IntoRenderNodeArray for Vec { - fn into_array(self) -> [InternedRenderLabel; N] { - self.try_into().unwrap() - } -} - -macro_rules! impl_render_label_tuples { - ($N: expr, $(#[$meta:meta])* $(($T: ident, $I: ident)),*) => { - $(#[$meta])* - impl<$($T: RenderLabel),*> IntoRenderNodeArray<$N> for ($($T,)*) { - #[inline] - fn into_array(self) -> [InternedRenderLabel; $N] { - let ($($I,)*) = self; - [$($I.intern(), )*] - } - } - } -} - -all_tuples_with_size!( - #[doc(fake_variadic)] - impl_render_label_tuples, - 1, - 32, - T, - l -); - -/// A render node that can be added to a [`RenderGraph`](super::RenderGraph). -/// -/// Nodes are the fundamental part of the graph and used to extend its functionality, by -/// generating draw calls and/or running subgraphs. -/// They are added via the `render_graph::add_node(my_node)` method. -/// -/// To determine their position in the graph and ensure that all required dependencies (inputs) -/// are already executed, [`Edges`](Edge) are used. -/// -/// A node can produce outputs used as dependencies by other nodes. -/// Those inputs and outputs are called slots and are the default way of passing render data -/// inside the graph. For more information see [`SlotType`](super::SlotType). -pub trait Node: Downcast + Send + Sync + 'static { - /// Specifies the required input slots for this node. - /// They will then be available during the run method inside the [`RenderGraphContext`]. - fn input(&self) -> Vec { - Vec::new() - } - - /// Specifies the produced output slots for this node. - /// They can then be passed one inside [`RenderGraphContext`] during the run method. - fn output(&self) -> Vec { - Vec::new() - } - - /// Updates internal node state using the current render [`World`] prior to the run method. - fn update(&mut self, _world: &mut World) {} - - /// Runs the graph node logic, issues draw calls, updates the output slots and - /// optionally queues up subgraphs for execution. The graph data, input and output values are - /// passed via the [`RenderGraphContext`]. - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError>; -} - -impl_downcast!(Node); - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum NodeRunError { - #[error("encountered an input slot error")] - InputSlotError(#[from] InputSlotError), - #[error("encountered an output slot error")] - OutputSlotError(#[from] OutputSlotError), - #[error("encountered an error when running a sub-graph")] - RunSubGraphError(#[from] RunSubGraphError), - #[error("encountered an error when executing draw command")] - DrawError(#[from] DrawError), -} - -/// A collection of input and output [`Edges`](Edge) for a [`Node`]. -#[derive(Debug)] -pub struct Edges { - label: InternedRenderLabel, - input_edges: Vec, - output_edges: Vec, -} - -impl Edges { - /// Returns all "input edges" (edges going "in") for this node . - #[inline] - pub fn input_edges(&self) -> &[Edge] { - &self.input_edges - } - - /// Returns all "output edges" (edges going "out") for this node . - #[inline] - pub fn output_edges(&self) -> &[Edge] { - &self.output_edges - } - - /// Returns this node's label. - #[inline] - pub fn label(&self) -> InternedRenderLabel { - self.label - } - - /// Adds an edge to the `input_edges` if it does not already exist. - pub(crate) fn add_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { - if self.has_input_edge(&edge) { - return Err(RenderGraphError::EdgeAlreadyExists(edge)); - } - self.input_edges.push(edge); - Ok(()) - } - - /// Removes an edge from the `input_edges` if it exists. - pub(crate) fn remove_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { - if let Some(index) = self.input_edges.iter().position(|e| *e == edge) { - self.input_edges.swap_remove(index); - Ok(()) - } else { - Err(RenderGraphError::EdgeDoesNotExist(edge)) - } - } - - /// Adds an edge to the `output_edges` if it does not already exist. - pub(crate) fn add_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { - if self.has_output_edge(&edge) { - return Err(RenderGraphError::EdgeAlreadyExists(edge)); - } - self.output_edges.push(edge); - Ok(()) - } - - /// Removes an edge from the `output_edges` if it exists. - pub(crate) fn remove_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { - if let Some(index) = self.output_edges.iter().position(|e| *e == edge) { - self.output_edges.swap_remove(index); - Ok(()) - } else { - Err(RenderGraphError::EdgeDoesNotExist(edge)) - } - } - - /// Checks whether the input edge already exists. - pub fn has_input_edge(&self, edge: &Edge) -> bool { - self.input_edges.contains(edge) - } - - /// Checks whether the output edge already exists. - pub fn has_output_edge(&self, edge: &Edge) -> bool { - self.output_edges.contains(edge) - } - - /// Searches the `input_edges` for a [`Edge::SlotEdge`], - /// which `input_index` matches the `index`; - pub fn get_input_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> { - self.input_edges - .iter() - .find(|e| { - if let Edge::SlotEdge { input_index, .. } = e { - *input_index == index - } else { - false - } - }) - .ok_or(RenderGraphError::UnconnectedNodeInputSlot { - input_slot: index, - node: self.label, - }) - } - - /// Searches the `output_edges` for a [`Edge::SlotEdge`], - /// which `output_index` matches the `index`; - pub fn get_output_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> { - self.output_edges - .iter() - .find(|e| { - if let Edge::SlotEdge { output_index, .. } = e { - *output_index == index - } else { - false - } - }) - .ok_or(RenderGraphError::UnconnectedNodeOutputSlot { - output_slot: index, - node: self.label, - }) - } -} - -/// The internal representation of a [`Node`], with all data required -/// by the [`RenderGraph`](super::RenderGraph). -/// -/// The `input_slots` and `output_slots` are provided by the `node`. -pub struct NodeState { - pub label: InternedRenderLabel, - /// The name of the type that implements [`Node`]. - pub type_name: &'static str, - pub node: Box, - pub input_slots: SlotInfos, - pub output_slots: SlotInfos, - pub edges: Edges, -} - -impl Debug for NodeState { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - writeln!(f, "{:?} ({})", self.label, self.type_name) - } -} - -impl NodeState { - /// Creates an [`NodeState`] without edges, but the `input_slots` and `output_slots` - /// are provided by the `node`. - pub fn new(label: InternedRenderLabel, node: T) -> Self - where - T: Node, - { - NodeState { - label, - input_slots: node.input().into(), - output_slots: node.output().into(), - node: Box::new(node), - type_name: core::any::type_name::(), - edges: Edges { - label, - input_edges: Vec::new(), - output_edges: Vec::new(), - }, - } - } - - /// Retrieves the [`Node`]. - pub fn node(&self) -> Result<&T, RenderGraphError> - where - T: Node, - { - self.node - .downcast_ref::() - .ok_or(RenderGraphError::WrongNodeType) - } - - /// Retrieves the [`Node`] mutably. - pub fn node_mut(&mut self) -> Result<&mut T, RenderGraphError> - where - T: Node, - { - self.node - .downcast_mut::() - .ok_or(RenderGraphError::WrongNodeType) - } - - /// Validates that each input slot corresponds to an input edge. - pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> { - for i in 0..self.input_slots.len() { - self.edges.get_input_slot_edge(i)?; - } - - Ok(()) - } - - /// Validates that each output slot corresponds to an output edge. - pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> { - for i in 0..self.output_slots.len() { - self.edges.get_output_slot_edge(i)?; - } - - Ok(()) - } -} - -/// A [`Node`] without any inputs, outputs and subgraphs, which does nothing when run. -/// Used (as a label) to bundle multiple dependencies into one inside -/// the [`RenderGraph`](super::RenderGraph). -#[derive(Default)] -pub struct EmptyNode; - -impl Node for EmptyNode { - fn run( - &self, - _graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - _world: &World, - ) -> Result<(), NodeRunError> { - Ok(()) - } -} - -/// A [`RenderGraph`](super::RenderGraph) [`Node`] that runs the configured subgraph once. -/// This makes it easier to insert sub-graph runs into a graph. -pub struct RunGraphOnViewNode { - sub_graph: InternedRenderSubGraph, -} - -impl RunGraphOnViewNode { - pub fn new(sub_graph: T) -> Self { - Self { - sub_graph: sub_graph.intern(), - } - } -} - -impl Node for RunGraphOnViewNode { - fn run( - &self, - graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - _world: &World, - ) -> Result<(), NodeRunError> { - graph.run_sub_graph(self.sub_graph, vec![], Some(graph.view_entity()), None)?; - Ok(()) - } -} - -/// This trait should be used instead of the [`Node`] trait when making a render node that runs on a view. -/// -/// It is intended to be used with [`ViewNodeRunner`] -pub trait ViewNode { - /// The query that will be used on the view entity. - /// It is guaranteed to run on the view entity, so there's no need for a filter - type ViewQuery: ReadOnlyQueryData; - - /// Updates internal node state using the current render [`World`] prior to the run method. - fn update(&mut self, _world: &mut World) {} - - /// Runs the graph node logic, issues draw calls, updates the output slots and - /// optionally queues up subgraphs for execution. The graph data, input and output values are - /// passed via the [`RenderGraphContext`]. - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - view_query: QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError>; -} - -/// This [`Node`] can be used to run any [`ViewNode`]. -/// It will take care of updating the view query in `update()` and running the query in `run()`. -/// -/// This [`Node`] exists to help reduce boilerplate when making a render node that runs on a view. -pub struct ViewNodeRunner { - view_query: QueryState, - node: N, -} - -impl ViewNodeRunner { - pub fn new(node: N, world: &mut World) -> Self { - Self { - view_query: world.query_filtered(), - node, - } - } -} - -impl FromWorld for ViewNodeRunner { - fn from_world(world: &mut World) -> Self { - Self::new(N::from_world(world), world) - } -} - -impl Node for ViewNodeRunner -where - T: ViewNode + Send + Sync + 'static, -{ - fn update(&mut self, world: &mut World) { - self.view_query.update_archetypes(world); - self.node.update(world); - } - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let Ok(view) = self.view_query.get_manual(world, graph.view_entity()) else { - return Ok(()); - }; - - ViewNode::run(&self.node, graph, render_context, view, world)?; - Ok(()) - } -} diff --git a/third_party/bevy_render/src/render_graph/node_slot.rs b/third_party/bevy_render/src/render_graph/node_slot.rs deleted file mode 100644 index eb11b36..0000000 --- a/third_party/bevy_render/src/render_graph/node_slot.rs +++ /dev/null @@ -1,165 +0,0 @@ -use alloc::borrow::Cow; -use bevy_ecs::entity::Entity; -use core::fmt; -use derive_more::derive::From; - -use crate::render_resource::{Buffer, Sampler, TextureView}; - -/// A value passed between render [`Nodes`](super::Node). -/// Corresponds to the [`SlotType`] specified in the [`RenderGraph`](super::RenderGraph). -/// -/// Slots can have four different types of values: -/// [`Buffer`], [`TextureView`], [`Sampler`] and [`Entity`]. -/// -/// These values do not contain the actual render data, but only the ids to retrieve them. -#[derive(Debug, Clone, From)] -pub enum SlotValue { - /// A GPU-accessible [`Buffer`]. - Buffer(Buffer), - /// A [`TextureView`] describes a texture used in a pipeline. - TextureView(TextureView), - /// A texture [`Sampler`] defines how a pipeline will sample from a [`TextureView`]. - Sampler(Sampler), - /// An entity from the ECS. - Entity(Entity), -} - -impl SlotValue { - /// Returns the [`SlotType`] of this value. - pub fn slot_type(&self) -> SlotType { - match self { - SlotValue::Buffer(_) => SlotType::Buffer, - SlotValue::TextureView(_) => SlotType::TextureView, - SlotValue::Sampler(_) => SlotType::Sampler, - SlotValue::Entity(_) => SlotType::Entity, - } - } -} - -/// Describes the render resources created (output) or used (input) by -/// the render [`Nodes`](super::Node). -/// -/// This should not be confused with [`SlotValue`], which actually contains the passed data. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum SlotType { - /// A GPU-accessible [`Buffer`]. - Buffer, - /// A [`TextureView`] describes a texture used in a pipeline. - TextureView, - /// A texture [`Sampler`] defines how a pipeline will sample from a [`TextureView`]. - Sampler, - /// An entity from the ECS. - Entity, -} - -impl fmt::Display for SlotType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - SlotType::Buffer => "Buffer", - SlotType::TextureView => "TextureView", - SlotType::Sampler => "Sampler", - SlotType::Entity => "Entity", - }; - - f.write_str(s) - } -} - -/// A [`SlotLabel`] is used to reference a slot by either its name or index -/// inside the [`RenderGraph`](super::RenderGraph). -#[derive(Debug, Clone, Eq, PartialEq, From)] -pub enum SlotLabel { - Index(usize), - Name(Cow<'static, str>), -} - -impl From<&SlotLabel> for SlotLabel { - fn from(value: &SlotLabel) -> Self { - value.clone() - } -} - -impl From for SlotLabel { - fn from(value: String) -> Self { - SlotLabel::Name(value.into()) - } -} - -impl From<&'static str> for SlotLabel { - fn from(value: &'static str) -> Self { - SlotLabel::Name(value.into()) - } -} - -/// The internal representation of a slot, which specifies its [`SlotType`] and name. -#[derive(Clone, Debug)] -pub struct SlotInfo { - pub name: Cow<'static, str>, - pub slot_type: SlotType, -} - -impl SlotInfo { - pub fn new(name: impl Into>, slot_type: SlotType) -> Self { - SlotInfo { - name: name.into(), - slot_type, - } - } -} - -/// A collection of input or output [`SlotInfos`](SlotInfo) for -/// a [`NodeState`](super::NodeState). -#[derive(Default, Debug)] -pub struct SlotInfos { - slots: Vec, -} - -impl> From for SlotInfos { - fn from(slots: T) -> Self { - SlotInfos { - slots: slots.into_iter().collect(), - } - } -} - -impl SlotInfos { - /// Returns the count of slots. - #[inline] - pub fn len(&self) -> usize { - self.slots.len() - } - - /// Returns true if there are no slots. - #[inline] - pub fn is_empty(&self) -> bool { - self.slots.is_empty() - } - - /// Retrieves the [`SlotInfo`] for the provided label. - pub fn get_slot(&self, label: impl Into) -> Option<&SlotInfo> { - let label = label.into(); - let index = self.get_slot_index(label)?; - self.slots.get(index) - } - - /// Retrieves the [`SlotInfo`] for the provided label mutably. - pub fn get_slot_mut(&mut self, label: impl Into) -> Option<&mut SlotInfo> { - let label = label.into(); - let index = self.get_slot_index(label)?; - self.slots.get_mut(index) - } - - /// Retrieves the index (inside input or output slots) of the slot for the provided label. - pub fn get_slot_index(&self, label: impl Into) -> Option { - let label = label.into(); - match label { - SlotLabel::Index(index) => Some(index), - SlotLabel::Name(ref name) => self.slots.iter().position(|s| s.name == *name), - } - } - - /// Returns an iterator over the slot infos. - pub fn iter(&self) -> impl Iterator { - self.slots.iter() - } -} diff --git a/third_party/bevy_render/src/render_phase/draw.rs b/third_party/bevy_render/src/render_phase/draw.rs deleted file mode 100644 index 494074d..0000000 --- a/third_party/bevy_render/src/render_phase/draw.rs +++ /dev/null @@ -1,396 +0,0 @@ -use crate::render_phase::{PhaseItem, TrackedRenderPass}; -use bevy_app::{App, SubApp}; -use bevy_ecs::{ - entity::Entity, - query::{QueryEntityError, QueryState, ROQueryItem, ReadOnlyQueryData}, - resource::Resource, - system::{ReadOnlySystemParam, SystemParam, SystemParamItem, SystemState}, - world::World, -}; -use bevy_utils::TypeIdMap; -use core::{any::TypeId, fmt::Debug, hash::Hash}; -use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use thiserror::Error; -use variadics_please::all_tuples; - -/// A draw function used to draw [`PhaseItem`]s. -/// -/// The draw function can retrieve and query the required ECS data from the render world. -/// -/// This trait can either be implemented directly or implicitly composed out of multiple modular -/// [`RenderCommand`]s. For more details and an example see the [`RenderCommand`] documentation. -pub trait Draw: Send + Sync + 'static { - /// Prepares the draw function to be used. This is called once and only once before the phase - /// begins. There may be zero or more [`draw`](Draw::draw) calls following a call to this function. - /// Implementing this is optional. - #[expect( - unused_variables, - reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." - )] - fn prepare(&mut self, world: &'_ World) {} - - /// Draws a [`PhaseItem`] by issuing zero or more `draw` calls via the [`TrackedRenderPass`]. - fn draw<'w>( - &mut self, - world: &'w World, - pass: &mut TrackedRenderPass<'w>, - view: Entity, - item: &P, - ) -> Result<(), DrawError>; -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum DrawError { - #[error("Failed to execute render command {0:?}")] - RenderCommandFailure(&'static str), - #[error("Failed to get execute view query")] - InvalidViewQuery, - #[error("View entity not found")] - ViewEntityNotFound, -} - -// TODO: make this generic? -/// An identifier for a [`Draw`] function stored in [`DrawFunctions`]. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct DrawFunctionId(u32); - -/// Stores all [`Draw`] functions for the [`PhaseItem`] type. -/// -/// For retrieval, the [`Draw`] functions are mapped to their respective [`TypeId`]s. -pub struct DrawFunctionsInternal { - pub draw_functions: Vec>>, - pub indices: TypeIdMap, -} - -impl DrawFunctionsInternal

{ - /// Prepares all draw function. This is called once and only once before the phase begins. - pub fn prepare(&mut self, world: &World) { - for function in &mut self.draw_functions { - function.prepare(world); - } - } - - /// Adds the [`Draw`] function and maps it to its own type. - pub fn add>(&mut self, draw_function: T) -> DrawFunctionId { - self.add_with::(draw_function) - } - - /// Adds the [`Draw`] function and maps it to the type `T` - pub fn add_with>(&mut self, draw_function: D) -> DrawFunctionId { - let id = DrawFunctionId(self.draw_functions.len().try_into().unwrap()); - self.draw_functions.push(Box::new(draw_function)); - self.indices.insert(TypeId::of::(), id); - id - } - - /// Retrieves the [`Draw`] function corresponding to the `id` mutably. - pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw

> { - self.draw_functions.get_mut(id.0 as usize).map(|f| &mut **f) - } - - /// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`. - pub fn get_id(&self) -> Option { - self.indices.get(&TypeId::of::()).copied() - } - - /// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`. - /// - /// Fallible wrapper for [`Self::get_id()`] - /// - /// ## Panics - /// If the id doesn't exist, this function will panic. - pub fn id(&self) -> DrawFunctionId { - self.get_id::().unwrap_or_else(|| { - panic!( - "Draw function {} not found for {}", - core::any::type_name::(), - core::any::type_name::

() - ) - }) - } -} - -/// Stores all draw functions for the [`PhaseItem`] type hidden behind a reader-writer lock. -/// -/// To access them the [`DrawFunctions::read`] and [`DrawFunctions::write`] methods are used. -#[derive(Resource)] -pub struct DrawFunctions { - internal: RwLock>, -} - -impl Default for DrawFunctions

{ - fn default() -> Self { - Self { - internal: RwLock::new(DrawFunctionsInternal { - draw_functions: Vec::new(), - indices: Default::default(), - }), - } - } -} - -impl DrawFunctions

{ - /// Accesses the draw functions in read mode. - pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal

> { - self.internal.read().unwrap_or_else(PoisonError::into_inner) - } - - /// Accesses the draw functions in write mode. - pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal

> { - self.internal - .write() - .unwrap_or_else(PoisonError::into_inner) - } -} - -/// [`RenderCommand`]s are modular standardized pieces of render logic that can be composed into -/// [`Draw`] functions. -/// -/// To turn a stateless render command into a usable draw function it has to be wrapped by a -/// [`RenderCommandState`]. -/// This is done automatically when registering a render command as a [`Draw`] function via the -/// [`AddRenderCommand::add_render_command`] method. -/// -/// Compared to the draw function the required ECS data is fetched automatically -/// (by the [`RenderCommandState`]) from the render world. -/// Therefore the three types [`Param`](RenderCommand::Param), -/// [`ViewQuery`](RenderCommand::ViewQuery) and -/// [`ItemQuery`](RenderCommand::ItemQuery) are used. -/// They specify which information is required to execute the render command. -/// -/// Multiple render commands can be combined together by wrapping them in a tuple. -/// -/// # Example -/// -/// The `DrawMaterial` draw function is created from the following render command -/// tuple. Const generics are used to set specific bind group locations: -/// -/// ``` -/// # use bevy_render::render_phase::SetItemPipeline; -/// # struct SetMeshViewBindGroup; -/// # struct SetMeshViewBindingArrayBindGroup; -/// # struct SetMeshBindGroup; -/// # struct SetMaterialBindGroup(std::marker::PhantomData); -/// # struct DrawMesh; -/// pub type DrawMaterial = ( -/// SetItemPipeline, -/// SetMeshViewBindGroup<0>, -/// SetMeshViewBindingArrayBindGroup<1>, -/// SetMeshBindGroup<2>, -/// SetMaterialBindGroup, -/// DrawMesh, -/// ); -/// ``` -pub trait RenderCommand { - /// Specifies the general ECS data (e.g. resources) required by [`RenderCommand::render`]. - /// - /// When fetching resources, note that, due to lifetime limitations of the `Deref` trait, - /// [`SRes::into_inner`] must be called on each [`SRes`] reference in the - /// [`RenderCommand::render`] method, instead of being automatically dereferenced as is the - /// case in normal `systems`. - /// - /// All parameters have to be read only. - /// - /// [`SRes`]: bevy_ecs::system::lifetimeless::SRes - /// [`SRes::into_inner`]: bevy_ecs::system::lifetimeless::SRes::into_inner - type Param: SystemParam + 'static; - /// Specifies the ECS data of the view entity required by [`RenderCommand::render`]. - /// - /// The view entity refers to the camera, or shadow-casting light, etc. from which the phase - /// item will be rendered from. - /// All components have to be accessed read only. - type ViewQuery: ReadOnlyQueryData; - /// Specifies the ECS data of the item entity required by [`RenderCommand::render`]. - /// - /// The item is the entity that will be rendered for the corresponding view. - /// All components have to be accessed read only. - /// - /// For efficiency reasons, Bevy doesn't always extract entities to the - /// render world; for instance, entities that simply consist of meshes are - /// often not extracted. If the entity doesn't exist in the render world, - /// the supplied query data will be `None`. - type ItemQuery: ReadOnlyQueryData; - - /// Renders a [`PhaseItem`] by recording commands (e.g. setting pipelines, binding bind groups, - /// issuing draw calls, etc.) via the [`TrackedRenderPass`]. - fn render<'w>( - item: &P, - view: ROQueryItem<'w, '_, Self::ViewQuery>, - entity: Option>, - param: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult; -} - -/// The result of a [`RenderCommand`]. -#[derive(Debug)] -pub enum RenderCommandResult { - Success, - Skip, - Failure(&'static str), -} - -macro_rules! render_command_tuple_impl { - ($(#[$meta:meta])* $(($name: ident, $view: ident, $entity: ident)),*) => { - $(#[$meta])* - impl),*> RenderCommand

for ($($name,)*) { - type Param = ($($name::Param,)*); - type ViewQuery = ($($name::ViewQuery,)*); - type ItemQuery = ($($name::ItemQuery,)*); - - #[expect( - clippy::allow_attributes, - reason = "We are in a macro; as such, `non_snake_case` may not always lint." - )] - #[allow( - non_snake_case, - reason = "Parameter and variable names are provided by the macro invocation, not by us." - )] - fn render<'w>( - _item: &P, - ($($view,)*): ROQueryItem<'w, '_, Self::ViewQuery>, - maybe_entities: Option>, - ($($name,)*): SystemParamItem<'w, '_, Self::Param>, - _pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - match maybe_entities { - None => { - $( - match $name::render(_item, $view, None, $name, _pass) { - RenderCommandResult::Skip => return RenderCommandResult::Skip, - RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason), - _ => {}, - } - )* - } - Some(($($entity,)*)) => { - $( - match $name::render(_item, $view, Some($entity), $name, _pass) { - RenderCommandResult::Skip => return RenderCommandResult::Skip, - RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason), - _ => {}, - } - )* - } - } - RenderCommandResult::Success - } - } - }; -} - -all_tuples!( - #[doc(fake_variadic)] - render_command_tuple_impl, - 0, - 15, - C, - V, - E -); - -/// Wraps a [`RenderCommand`] into a state so that it can be used as a [`Draw`] function. -/// -/// The [`RenderCommand::Param`], [`RenderCommand::ViewQuery`] and -/// [`RenderCommand::ItemQuery`] are fetched from the ECS and passed to the command. -pub struct RenderCommandState> { - state: SystemState, - view: QueryState, - entity: QueryState, -} - -impl> RenderCommandState { - /// Creates a new [`RenderCommandState`] for the [`RenderCommand`]. - pub fn new(world: &mut World) -> Self { - Self { - state: SystemState::new(world), - view: world.query(), - entity: world.query(), - } - } -} - -impl + Send + Sync + 'static> Draw

for RenderCommandState -where - C::Param: ReadOnlySystemParam, -{ - /// Prepares the render command to be used. This is called once and only once before the phase - /// begins. There may be zero or more [`draw`](RenderCommandState::draw) calls following a call to this function. - fn prepare(&mut self, world: &'_ World) { - self.view.update_archetypes(world); - self.entity.update_archetypes(world); - } - - /// Fetches the ECS parameters for the wrapped [`RenderCommand`] and then renders it. - fn draw<'w>( - &mut self, - world: &'w World, - pass: &mut TrackedRenderPass<'w>, - view: Entity, - item: &P, - ) -> Result<(), DrawError> { - let param = self.state.get(world); - let view = match self.view.get_manual(world, view) { - Ok(view) => view, - Err(err) => match err { - QueryEntityError::NotSpawned(_) => return Err(DrawError::ViewEntityNotFound), - QueryEntityError::QueryDoesNotMatch(_, _) - | QueryEntityError::AliasedMutability(_) => { - return Err(DrawError::InvalidViewQuery) - } - }, - }; - - let entity = self.entity.get_manual(world, item.entity()).ok(); - match C::render(item, view, entity, param, pass) { - RenderCommandResult::Success | RenderCommandResult::Skip => Ok(()), - RenderCommandResult::Failure(reason) => Err(DrawError::RenderCommandFailure(reason)), - } - } -} - -/// Registers a [`RenderCommand`] as a [`Draw`] function. -/// They are stored inside the [`DrawFunctions`] resource of the app. -pub trait AddRenderCommand { - /// Adds the [`RenderCommand`] for the specified render phase to the app. - fn add_render_command + Send + Sync + 'static>( - &mut self, - ) -> &mut Self - where - C::Param: ReadOnlySystemParam; -} - -impl AddRenderCommand for SubApp { - fn add_render_command + Send + Sync + 'static>( - &mut self, - ) -> &mut Self - where - C::Param: ReadOnlySystemParam, - { - let draw_function = RenderCommandState::::new(self.world_mut()); - let draw_functions = self - .world() - .get_resource::>() - .unwrap_or_else(|| { - panic!( - "DrawFunctions<{}> must be added to the world as a resource \ - before adding render commands to it", - core::any::type_name::

(), - ); - }); - draw_functions.write().add_with::(draw_function); - self - } -} - -impl AddRenderCommand for App { - fn add_render_command + Send + Sync + 'static>( - &mut self, - ) -> &mut Self - where - C::Param: ReadOnlySystemParam, - { - SubApp::add_render_command::(self.main_mut()); - self - } -} diff --git a/third_party/bevy_render/src/render_phase/draw_state.rs b/third_party/bevy_render/src/render_phase/draw_state.rs deleted file mode 100644 index 167d493..0000000 --- a/third_party/bevy_render/src/render_phase/draw_state.rs +++ /dev/null @@ -1,671 +0,0 @@ -use crate::{ - diagnostic::internal::{Pass, PassKind, WritePipelineStatistics, WriteTimestamp}, - render_resource::{ - BindGroup, BindGroupId, Buffer, BufferId, BufferSlice, RenderPipeline, RenderPipelineId, - ShaderStages, - }, - renderer::RenderDevice, -}; -use bevy_camera::Viewport; -use bevy_color::LinearRgba; -use bevy_utils::default; -use core::ops::Range; -use wgpu::{IndexFormat, QuerySet, RenderPass}; - -#[cfg(feature = "detailed_trace")] -use tracing::trace; - -type BufferSliceKey = (BufferId, wgpu::BufferAddress, wgpu::BufferSize); - -/// Tracks the state of a [`TrackedRenderPass`]. -/// -/// This is used to skip redundant operations on the [`TrackedRenderPass`] (e.g. setting an already -/// set pipeline, binding an already bound bind group). These operations can otherwise be fairly -/// costly due to IO to the GPU, so deduplicating these calls results in a speedup. -#[derive(Debug, Default)] -struct DrawState { - pipeline: Option, - bind_groups: Vec<(Option, Vec)>, - /// List of vertex buffers by [`BufferId`], offset, and size. See [`DrawState::buffer_slice_key`] - vertex_buffers: Vec>, - index_buffer: Option<(BufferSliceKey, IndexFormat)>, - - /// Stores whether this state is populated or empty for quick state invalidation - stores_state: bool, -} - -impl DrawState { - /// Marks the `pipeline` as bound. - fn set_pipeline(&mut self, pipeline: RenderPipelineId) { - // TODO: do these need to be cleared? - // self.bind_groups.clear(); - // self.vertex_buffers.clear(); - // self.index_buffer = None; - self.pipeline = Some(pipeline); - self.stores_state = true; - } - - /// Checks, whether the `pipeline` is already bound. - fn is_pipeline_set(&self, pipeline: RenderPipelineId) -> bool { - self.pipeline == Some(pipeline) - } - - /// Marks the `bind_group` as bound to the `index`. - fn set_bind_group(&mut self, index: usize, bind_group: BindGroupId, dynamic_indices: &[u32]) { - let group = &mut self.bind_groups[index]; - group.0 = Some(bind_group); - group.1.clear(); - group.1.extend(dynamic_indices); - self.stores_state = true; - } - - /// Checks, whether the `bind_group` is already bound to the `index`. - fn is_bind_group_set( - &self, - index: usize, - bind_group: BindGroupId, - dynamic_indices: &[u32], - ) -> bool { - if let Some(current_bind_group) = self.bind_groups.get(index) { - current_bind_group.0 == Some(bind_group) && dynamic_indices == current_bind_group.1 - } else { - false - } - } - - /// Marks the vertex `buffer` as bound to the `index`. - fn set_vertex_buffer(&mut self, index: usize, buffer_slice: BufferSlice) { - self.vertex_buffers[index] = Some(self.buffer_slice_key(&buffer_slice)); - self.stores_state = true; - } - - /// Checks, whether the vertex `buffer` is already bound to the `index`. - fn is_vertex_buffer_set(&self, index: usize, buffer_slice: &BufferSlice) -> bool { - if let Some(current) = self.vertex_buffers.get(index) { - *current == Some(self.buffer_slice_key(buffer_slice)) - } else { - false - } - } - - /// Returns the value used for checking whether `BufferSlice`s are equivalent. - fn buffer_slice_key(&self, buffer_slice: &BufferSlice) -> BufferSliceKey { - ( - buffer_slice.id(), - buffer_slice.offset(), - buffer_slice.size(), - ) - } - - /// Marks the index `buffer` as bound. - fn set_index_buffer(&mut self, buffer_slice: &BufferSlice, index_format: IndexFormat) { - self.index_buffer = Some((self.buffer_slice_key(buffer_slice), index_format)); - self.stores_state = true; - } - - /// Checks, whether the index `buffer` is already bound. - fn is_index_buffer_set(&self, buffer: &BufferSlice, index_format: IndexFormat) -> bool { - self.index_buffer == Some((self.buffer_slice_key(buffer), index_format)) - } - - /// Resets tracking state - pub fn reset_tracking(&mut self) { - if !self.stores_state { - return; - } - self.pipeline = None; - self.bind_groups.iter_mut().for_each(|val| { - val.0 = None; - val.1.clear(); - }); - self.vertex_buffers.iter_mut().for_each(|val| { - *val = None; - }); - self.index_buffer = None; - self.stores_state = false; - } -} - -/// A [`RenderPass`], which tracks the current pipeline state to skip redundant operations. -/// -/// It is used to set the current [`RenderPipeline`], [`BindGroup`]s and [`Buffer`]s. -/// After all requirements are specified, draw calls can be issued. -pub struct TrackedRenderPass<'a> { - pass: RenderPass<'a>, - state: DrawState, -} - -impl<'a> TrackedRenderPass<'a> { - /// Tracks the supplied render pass. - pub fn new(device: &RenderDevice, pass: RenderPass<'a>) -> Self { - let limits = device.limits(); - let max_bind_groups = limits.max_bind_groups as usize; - let max_vertex_buffers = limits.max_vertex_buffers as usize; - Self { - state: DrawState { - bind_groups: vec![(None, Vec::new()); max_bind_groups], - vertex_buffers: vec![None; max_vertex_buffers], - ..default() - }, - pass, - } - } - - /// Returns the wgpu [`RenderPass`]. - /// - /// Function invalidates internal tracking state, - /// some redundant pipeline operations may not be skipped. - pub fn wgpu_pass(&mut self) -> &mut RenderPass<'a> { - self.state.reset_tracking(); - &mut self.pass - } - - /// Sets the active [`RenderPipeline`]. - /// - /// Subsequent draw calls will exhibit the behavior defined by the `pipeline`. - pub fn set_render_pipeline(&mut self, pipeline: &'a RenderPipeline) { - #[cfg(feature = "detailed_trace")] - trace!("set pipeline: {:?}", pipeline); - if self.state.is_pipeline_set(pipeline.id()) { - return; - } - self.pass.set_pipeline(pipeline); - self.state.set_pipeline(pipeline.id()); - } - - /// Sets the active bind group for a given bind group index. The bind group layout - /// in the active pipeline when any `draw()` function is called must match the layout of - /// this bind group. - /// - /// If the bind group have dynamic offsets, provide them in binding order. - /// These offsets have to be aligned to [`WgpuLimits::min_uniform_buffer_offset_alignment`](crate::settings::WgpuLimits::min_uniform_buffer_offset_alignment) - /// or [`WgpuLimits::min_storage_buffer_offset_alignment`](crate::settings::WgpuLimits::min_storage_buffer_offset_alignment) appropriately. - pub fn set_bind_group( - &mut self, - index: usize, - bind_group: &'a BindGroup, - dynamic_uniform_indices: &[u32], - ) { - if self - .state - .is_bind_group_set(index, bind_group.id(), dynamic_uniform_indices) - { - #[cfg(feature = "detailed_trace")] - trace!( - "set bind_group {} (already set): {:?} ({:?})", - index, - bind_group, - dynamic_uniform_indices - ); - return; - } - #[cfg(feature = "detailed_trace")] - trace!( - "set bind_group {}: {:?} ({:?})", - index, - bind_group, - dynamic_uniform_indices - ); - - self.pass - .set_bind_group(index as u32, bind_group, dynamic_uniform_indices); - self.state - .set_bind_group(index, bind_group.id(), dynamic_uniform_indices); - } - - /// Assign a vertex buffer to a slot. - /// - /// Subsequent calls to [`draw`] and [`draw_indexed`] on this - /// [`TrackedRenderPass`] will use `buffer` as one of the source vertex buffers. - /// - /// The `slot_index` refers to the index of the matching descriptor in - /// [`VertexState::buffers`](crate::render_resource::VertexState::buffers). - /// - /// [`draw`]: TrackedRenderPass::draw - /// [`draw_indexed`]: TrackedRenderPass::draw_indexed - pub fn set_vertex_buffer(&mut self, slot_index: usize, buffer_slice: BufferSlice<'a>) { - if self.state.is_vertex_buffer_set(slot_index, &buffer_slice) { - #[cfg(feature = "detailed_trace")] - trace!( - "set vertex buffer {} (already set): {:?} (offset = {}, size = {})", - slot_index, - buffer_slice.id(), - buffer_slice.offset(), - buffer_slice.size(), - ); - return; - } - #[cfg(feature = "detailed_trace")] - trace!( - "set vertex buffer {}: {:?} (offset = {}, size = {})", - slot_index, - buffer_slice.id(), - buffer_slice.offset(), - buffer_slice.size(), - ); - - self.pass - .set_vertex_buffer(slot_index as u32, *buffer_slice); - self.state.set_vertex_buffer(slot_index, buffer_slice); - } - - /// Sets the active index buffer. - /// - /// Subsequent calls to [`TrackedRenderPass::draw_indexed`] will use the buffer referenced by - /// `buffer_slice` as the source index buffer. - pub fn set_index_buffer(&mut self, buffer_slice: BufferSlice<'a>, index_format: IndexFormat) { - let already_set = self.state.is_index_buffer_set(&buffer_slice, index_format); - #[cfg(feature = "detailed_trace")] - trace!( - "set index buffer{}: {:?} (offset = {}, size = {})", - if already_set { " (already set)" } else { "" }, - buffer_slice.id(), - buffer_slice.offset(), - buffer_slice.size(), - ); - if already_set { - return; - } - self.pass.set_index_buffer(*buffer_slice, index_format); - self.state.set_index_buffer(&buffer_slice, index_format); - } - - /// Draws primitives from the active vertex buffer(s). - /// - /// The active vertex buffer(s) can be set with [`TrackedRenderPass::set_vertex_buffer`]. - pub fn draw(&mut self, vertices: Range, instances: Range) { - #[cfg(feature = "detailed_trace")] - trace!("draw: {:?} {:?}", vertices, instances); - self.pass.draw(vertices, instances); - } - - /// Draws indexed primitives using the active index buffer and the active vertex buffer(s). - /// - /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the - /// active vertex buffer(s) can be set with [`TrackedRenderPass::set_vertex_buffer`]. - pub fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { - #[cfg(feature = "detailed_trace")] - trace!( - "draw indexed: {:?} {} {:?}", - indices, - base_vertex, - instances - ); - self.pass.draw_indexed(indices, base_vertex, instances); - } - - /// Draws primitives from the active vertex buffer(s) based on the contents of the - /// `indirect_buffer`. - /// - /// The active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. - /// - /// The structure expected in `indirect_buffer` is the following: - /// - /// ``` - /// #[repr(C)] - /// struct DrawIndirect { - /// vertex_count: u32, // The number of vertices to draw. - /// instance_count: u32, // The number of instances to draw. - /// first_vertex: u32, // The Index of the first vertex to draw. - /// first_instance: u32, // The instance ID of the first instance to draw. - /// // has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`] is enabled. - /// } - /// ``` - pub fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: u64) { - #[cfg(feature = "detailed_trace")] - trace!("draw indirect: {:?} {}", indirect_buffer, indirect_offset); - self.pass.draw_indirect(indirect_buffer, indirect_offset); - } - - /// Draws indexed primitives using the active index buffer and the active vertex buffers, - /// based on the contents of the `indirect_buffer`. - /// - /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the - /// active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. - /// - /// The structure expected in `indirect_buffer` is the following: - /// - /// ``` - /// #[repr(C)] - /// struct DrawIndexedIndirect { - /// vertex_count: u32, // The number of vertices to draw. - /// instance_count: u32, // The number of instances to draw. - /// first_index: u32, // The base index within the index buffer. - /// vertex_offset: i32, // The value added to the vertex index before indexing into the vertex buffer. - /// first_instance: u32, // The instance ID of the first instance to draw. - /// // has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`] is enabled. - /// } - /// ``` - pub fn draw_indexed_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: u64) { - #[cfg(feature = "detailed_trace")] - trace!( - "draw indexed indirect: {:?} {}", - indirect_buffer, - indirect_offset - ); - self.pass - .draw_indexed_indirect(indirect_buffer, indirect_offset); - } - - /// Dispatches multiple draw calls from the active vertex buffer(s) based on the contents of the - /// `indirect_buffer`.`count` draw calls are issued. - /// - /// The active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. - /// - /// `indirect_buffer` should contain `count` tightly packed elements of the following structure: - /// - /// ``` - /// #[repr(C)] - /// struct DrawIndirect { - /// vertex_count: u32, // The number of vertices to draw. - /// instance_count: u32, // The number of instances to draw. - /// first_vertex: u32, // The Index of the first vertex to draw. - /// first_instance: u32, // The instance ID of the first instance to draw. - /// // has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`] is enabled. - /// } - /// ``` - pub fn multi_draw_indirect( - &mut self, - indirect_buffer: &'a Buffer, - indirect_offset: u64, - count: u32, - ) { - #[cfg(feature = "detailed_trace")] - trace!( - "multi draw indirect: {:?} {}, {}x", - indirect_buffer, - indirect_offset, - count - ); - self.pass - .multi_draw_indirect(indirect_buffer, indirect_offset, count); - } - - /// Dispatches multiple draw calls from the active vertex buffer(s) based on the contents of - /// the `indirect_buffer`. - /// The count buffer is read to determine how many draws to issue. - /// - /// The indirect buffer must be long enough to account for `max_count` draws, however only - /// `count` elements will be read, where `count` is the value read from `count_buffer` capped - /// at `max_count`. - /// - /// The active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. - /// - /// `indirect_buffer` should contain `count` tightly packed elements of the following structure: - /// - /// ``` - /// #[repr(C)] - /// struct DrawIndirect { - /// vertex_count: u32, // The number of vertices to draw. - /// instance_count: u32, // The number of instances to draw. - /// first_vertex: u32, // The Index of the first vertex to draw. - /// first_instance: u32, // The instance ID of the first instance to draw. - /// // has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`] is enabled. - /// } - /// ``` - pub fn multi_draw_indirect_count( - &mut self, - indirect_buffer: &'a Buffer, - indirect_offset: u64, - count_buffer: &'a Buffer, - count_offset: u64, - max_count: u32, - ) { - #[cfg(feature = "detailed_trace")] - trace!( - "multi draw indirect count: {:?} {}, ({:?} {})x, max {}x", - indirect_buffer, - indirect_offset, - count_buffer, - count_offset, - max_count - ); - self.pass.multi_draw_indirect_count( - indirect_buffer, - indirect_offset, - count_buffer, - count_offset, - max_count, - ); - } - - /// Dispatches multiple draw calls from the active index buffer and the active vertex buffers, - /// based on the contents of the `indirect_buffer`. `count` draw calls are issued. - /// - /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the - /// active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. - /// - /// `indirect_buffer` should contain `count` tightly packed elements of the following structure: - /// - /// ``` - /// #[repr(C)] - /// struct DrawIndexedIndirect { - /// vertex_count: u32, // The number of vertices to draw. - /// instance_count: u32, // The number of instances to draw. - /// first_index: u32, // The base index within the index buffer. - /// vertex_offset: i32, // The value added to the vertex index before indexing into the vertex buffer. - /// first_instance: u32, // The instance ID of the first instance to draw. - /// // has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`] is enabled. - /// } - /// ``` - pub fn multi_draw_indexed_indirect( - &mut self, - indirect_buffer: &'a Buffer, - indirect_offset: u64, - count: u32, - ) { - #[cfg(feature = "detailed_trace")] - trace!( - "multi draw indexed indirect: {:?} {}, {}x", - indirect_buffer, - indirect_offset, - count - ); - self.pass - .multi_draw_indexed_indirect(indirect_buffer, indirect_offset, count); - } - - /// Dispatches multiple draw calls from the active index buffer and the active vertex buffers, - /// based on the contents of the `indirect_buffer`. - /// The count buffer is read to determine how many draws to issue. - /// - /// The indirect buffer must be long enough to account for `max_count` draws, however only - /// `count` elements will be read, where `count` is the value read from `count_buffer` capped - /// at `max_count`. - /// - /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the - /// active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. - /// - /// `indirect_buffer` should contain `count` tightly packed elements of the following structure: - /// - /// ``` - /// #[repr(C)] - /// struct DrawIndexedIndirect { - /// vertex_count: u32, // The number of vertices to draw. - /// instance_count: u32, // The number of instances to draw. - /// first_index: u32, // The base index within the index buffer. - /// vertex_offset: i32, // The value added to the vertex index before indexing into the vertex buffer. - /// first_instance: u32, // The instance ID of the first instance to draw. - /// // has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`] is enabled. - /// } - /// ``` - pub fn multi_draw_indexed_indirect_count( - &mut self, - indirect_buffer: &'a Buffer, - indirect_offset: u64, - count_buffer: &'a Buffer, - count_offset: u64, - max_count: u32, - ) { - #[cfg(feature = "detailed_trace")] - trace!( - "multi draw indexed indirect count: {:?} {}, ({:?} {})x, max {}x", - indirect_buffer, - indirect_offset, - count_buffer, - count_offset, - max_count - ); - self.pass.multi_draw_indexed_indirect_count( - indirect_buffer, - indirect_offset, - count_buffer, - count_offset, - max_count, - ); - } - - /// Sets the stencil reference. - /// - /// Subsequent stencil tests will test against this value. - pub fn set_stencil_reference(&mut self, reference: u32) { - #[cfg(feature = "detailed_trace")] - trace!("set stencil reference: {}", reference); - self.pass.set_stencil_reference(reference); - } - - /// Sets the scissor region. - /// - /// Subsequent draw calls will discard any fragments that fall outside this region. - pub fn set_scissor_rect(&mut self, x: u32, y: u32, width: u32, height: u32) { - #[cfg(feature = "detailed_trace")] - trace!("set_scissor_rect: {} {} {} {}", x, y, width, height); - self.pass.set_scissor_rect(x, y, width, height); - } - - /// Set push constant data. - /// - /// `Features::PUSH_CONSTANTS` must be enabled on the device in order to call these functions. - pub fn set_push_constants(&mut self, stages: ShaderStages, offset: u32, data: &[u8]) { - #[cfg(feature = "detailed_trace")] - trace!( - "set push constants: {:?} offset: {} data.len: {}", - stages, - offset, - data.len() - ); - self.pass.set_push_constants(stages, offset, data); - } - - /// Set the rendering viewport. - /// - /// Subsequent draw calls will be projected into that viewport. - pub fn set_viewport( - &mut self, - x: f32, - y: f32, - width: f32, - height: f32, - min_depth: f32, - max_depth: f32, - ) { - #[cfg(feature = "detailed_trace")] - trace!( - "set viewport: {} {} {} {} {} {}", - x, - y, - width, - height, - min_depth, - max_depth - ); - self.pass - .set_viewport(x, y, width, height, min_depth, max_depth); - } - - /// Set the rendering viewport to the given camera [`Viewport`]. - /// - /// Subsequent draw calls will be projected into that viewport. - pub fn set_camera_viewport(&mut self, viewport: &Viewport) { - self.set_viewport( - viewport.physical_position.x as f32, - viewport.physical_position.y as f32, - viewport.physical_size.x as f32, - viewport.physical_size.y as f32, - viewport.depth.start, - viewport.depth.end, - ); - } - - /// Insert a single debug marker. - /// - /// This is a GPU debugging feature. This has no effect on the rendering itself. - pub fn insert_debug_marker(&mut self, label: &str) { - #[cfg(feature = "detailed_trace")] - trace!("insert debug marker: {}", label); - self.pass.insert_debug_marker(label); - } - - /// Start a new debug group. - /// - /// Push a new debug group over the internal stack. Subsequent render commands and debug - /// markers are grouped into this new group, until [`pop_debug_group`] is called. - /// - /// ``` - /// # fn example(mut pass: bevy_render::render_phase::TrackedRenderPass<'static>) { - /// pass.push_debug_group("Render the car"); - /// // [setup pipeline etc...] - /// pass.draw(0..64, 0..1); - /// pass.pop_debug_group(); - /// # } - /// ``` - /// - /// Note that [`push_debug_group`] and [`pop_debug_group`] must always be called in pairs. - /// - /// This is a GPU debugging feature. This has no effect on the rendering itself. - /// - /// [`push_debug_group`]: TrackedRenderPass::push_debug_group - /// [`pop_debug_group`]: TrackedRenderPass::pop_debug_group - pub fn push_debug_group(&mut self, label: &str) { - #[cfg(feature = "detailed_trace")] - trace!("push_debug_group marker: {}", label); - self.pass.push_debug_group(label); - } - - /// End the current debug group. - /// - /// Subsequent render commands and debug markers are not grouped anymore in - /// this group, but in the previous one (if any) or the default top-level one - /// if the debug group was the last one on the stack. - /// - /// Note that [`push_debug_group`] and [`pop_debug_group`] must always be called in pairs. - /// - /// This is a GPU debugging feature. This has no effect on the rendering itself. - /// - /// [`push_debug_group`]: TrackedRenderPass::push_debug_group - /// [`pop_debug_group`]: TrackedRenderPass::pop_debug_group - pub fn pop_debug_group(&mut self) { - #[cfg(feature = "detailed_trace")] - trace!("pop_debug_group"); - self.pass.pop_debug_group(); - } - - /// Sets the blend color as used by some of the blending modes. - /// - /// Subsequent blending tests will test against this value. - pub fn set_blend_constant(&mut self, color: LinearRgba) { - #[cfg(feature = "detailed_trace")] - trace!("set blend constant: {:?}", color); - self.pass.set_blend_constant(wgpu::Color::from(color)); - } -} - -impl WriteTimestamp for TrackedRenderPass<'_> { - fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) { - self.pass.write_timestamp(query_set, index); - } -} - -impl WritePipelineStatistics for TrackedRenderPass<'_> { - fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32) { - self.pass.begin_pipeline_statistics_query(query_set, index); - } - - fn end_pipeline_statistics_query(&mut self) { - self.pass.end_pipeline_statistics_query(); - } -} - -impl Pass for TrackedRenderPass<'_> { - const KIND: PassKind = PassKind::Render; -} diff --git a/third_party/bevy_render/src/render_phase/mod.rs b/third_party/bevy_render/src/render_phase/mod.rs deleted file mode 100644 index ea9deb5..0000000 --- a/third_party/bevy_render/src/render_phase/mod.rs +++ /dev/null @@ -1,1911 +0,0 @@ -//! The modular rendering abstraction responsible for queuing, preparing, sorting and drawing -//! entities as part of separate render phases. -//! -//! In Bevy each view (camera, or shadow-casting light, etc.) has one or multiple render phases -//! (e.g. opaque, transparent, shadow, etc). -//! They are used to queue entities for rendering. -//! Multiple phases might be required due to different sorting/batching behaviors -//! (e.g. opaque: front to back, transparent: back to front) or because one phase depends on -//! the rendered texture of the previous phase (e.g. for screen-space reflections). -//! -//! To draw an entity, a corresponding [`PhaseItem`] has to be added to one or multiple of these -//! render phases for each view that it is visible in. -//! This must be done in the [`RenderSystems::Queue`]. -//! After that the render phase sorts them in the [`RenderSystems::PhaseSort`]. -//! Finally the items are rendered using a single [`TrackedRenderPass`], during -//! the [`RenderSystems::Render`]. -//! -//! Therefore each phase item is assigned a [`Draw`] function. -//! These set up the state of the [`TrackedRenderPass`] (i.e. select the -//! [`RenderPipeline`](crate::render_resource::RenderPipeline), configure the -//! [`BindGroup`](crate::render_resource::BindGroup)s, etc.) and then issue a draw call, -//! for the corresponding item. -//! -//! The [`Draw`] function trait can either be implemented directly or such a function can be -//! created by composing multiple [`RenderCommand`]s. - -mod draw; -mod draw_state; -mod rangefinder; - -use bevy_app::{App, Plugin}; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::change_detection::Tick; -use bevy_ecs::entity::EntityHash; -use bevy_platform::collections::{hash_map::Entry, HashMap}; -use bevy_utils::default; -pub use draw::*; -pub use draw_state::*; -use encase::{internal::WriteInto, ShaderSize}; -use fixedbitset::{Block, FixedBitSet}; -use indexmap::IndexMap; -use nonmax::NonMaxU32; -pub use rangefinder::*; -use wgpu::Features; - -use crate::batching::gpu_preprocessing::{ - GpuPreprocessingMode, GpuPreprocessingSupport, PhaseBatchedInstanceBuffers, - PhaseIndirectParametersBuffers, -}; -use crate::renderer::RenderDevice; -use crate::sync_world::{MainEntity, MainEntityHashMap}; -use crate::view::RetainedViewEntity; -use crate::RenderDebugFlags; -use crate::{ - batching::{ - self, - gpu_preprocessing::{self, BatchedInstanceBuffers}, - no_gpu_preprocessing::{self, BatchedInstanceBuffer}, - GetFullBatchData, - }, - render_resource::{CachedRenderPipelineId, GpuArrayBufferIndex, PipelineCache}, - Render, RenderApp, RenderSystems, -}; -use bevy_ecs::intern::Interned; -use bevy_ecs::{ - define_label, - prelude::*, - system::{lifetimeless::SRes, SystemParamItem}, -}; -use bevy_render::renderer::RenderAdapterInfo; -pub use bevy_render_macros::ShaderLabel; -use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, ops::Range, slice::SliceIndex}; -use smallvec::SmallVec; -use tracing::warn; - -define_label!( - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(ShaderLabel)]`" - )] - /// Labels used to uniquely identify types of material shaders - ShaderLabel, - SHADER_LABEL_INTERNER -); - -/// A shorthand for `Interned`. -pub type InternedShaderLabel = Interned; - -pub use bevy_render_macros::DrawFunctionLabel; - -define_label!( - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(DrawFunctionLabel)]`" - )] - /// Labels used to uniquely identify types of material shaders - DrawFunctionLabel, - DRAW_FUNCTION_LABEL_INTERNER -); - -pub type InternedDrawFunctionLabel = Interned; - -/// Stores the rendering instructions for a single phase that uses bins in all -/// views. -/// -/// They're cleared out every frame, but storing them in a resource like this -/// allows us to reuse allocations. -#[derive(Resource, Deref, DerefMut)] -pub struct ViewBinnedRenderPhases(pub HashMap>) -where - BPI: BinnedPhaseItem; - -/// A collection of all rendering instructions, that will be executed by the GPU, for a -/// single render phase for a single view. -/// -/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases. -/// They are used to queue entities for rendering. -/// Multiple phases might be required due to different sorting/batching behaviors -/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on -/// the rendered texture of the previous phase (e.g. for screen-space reflections). -/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`]. -/// The render pass might be reused for multiple phases to reduce GPU overhead. -/// -/// This flavor of render phase is used for phases in which the ordering is less -/// critical: for example, `Opaque3d`. It's generally faster than the -/// alternative [`SortedRenderPhase`]. -pub struct BinnedRenderPhase -where - BPI: BinnedPhaseItem, -{ - /// The multidrawable bins. - /// - /// Each batch set key maps to a *batch set*, which in this case is a set of - /// meshes that can be drawn together in one multidraw call. Each batch set - /// is subdivided into *bins*, each of which represents a particular mesh. - /// Each bin contains the entity IDs of instances of that mesh. - /// - /// So, for example, if there are two cubes and a sphere present in the - /// scene, we would generally have one batch set containing two bins, - /// assuming that the cubes and sphere meshes are allocated together and use - /// the same pipeline. The first bin, corresponding to the cubes, will have - /// two entities in it. The second bin, corresponding to the sphere, will - /// have one entity in it. - pub multidrawable_meshes: IndexMap>, - - /// The bins corresponding to batchable items that aren't multidrawable. - /// - /// For multidrawable entities, use `multidrawable_meshes`; for - /// unbatchable entities, use `unbatchable_values`. - pub batchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>, - - /// The unbatchable bins. - /// - /// Each entity here is rendered in a separate drawcall. - pub unbatchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>, - - /// Items in the bin that aren't meshes at all. - /// - /// Bevy itself doesn't place anything in this list, but plugins or your app - /// can in order to execute custom drawing commands. Draw functions for each - /// entity are simply called in order at rendering time. - /// - /// See the `custom_phase_item` example for an example of how to use this. - pub non_mesh_items: IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>, - - /// Information on each batch set. - /// - /// A *batch set* is a set of entities that will be batched together unless - /// we're on a platform that doesn't support storage buffers (e.g. WebGL 2) - /// and differing dynamic uniform indices force us to break batches. On - /// platforms that support storage buffers, a batch set always consists of - /// at most one batch. - /// - /// Multidrawable entities come first, then batchable entities, then - /// unbatchable entities. - pub(crate) batch_sets: BinnedRenderPhaseBatchSets, - - /// The batch and bin key for each entity. - /// - /// We retain these so that, when the entity changes, - /// [`Self::sweep_old_entities`] can quickly find the bin it was located in - /// and remove it. - cached_entity_bin_keys: IndexMap, EntityHash>, - - /// The set of indices in [`Self::cached_entity_bin_keys`] that are - /// confirmed to be up to date. - /// - /// Note that each bit in this bit set refers to an *index* in the - /// [`IndexMap`] (i.e. a bucket in the hash table). They aren't entity IDs. - valid_cached_entity_bin_keys: FixedBitSet, - - /// The set of entities that changed bins this frame. - /// - /// An entity will only be present in this list if it was in one bin on the - /// previous frame and is in a new bin on this frame. Each list entry - /// specifies the bin the entity used to be in. We use this in order to - /// remove the entity from the old bin during - /// [`BinnedRenderPhase::sweep_old_entities`]. - entities_that_changed_bins: Vec>, - /// The gpu preprocessing mode configured for the view this phase is associated - /// with. - gpu_preprocessing_mode: GpuPreprocessingMode, -} - -/// All entities that share a mesh and a material and can be batched as part of -/// a [`BinnedRenderPhase`]. -#[derive(Default)] -pub struct RenderBin { - /// A list of the entities in each bin, along with their cached - /// [`InputUniformIndex`]. - entities: IndexMap, -} - -/// Information that we track about an entity that was in one bin on the -/// previous frame and is in a different bin this frame. -struct EntityThatChangedBins -where - BPI: BinnedPhaseItem, -{ - /// The entity. - main_entity: MainEntity, - /// The key that identifies the bin that this entity used to be in. - old_cached_binned_entity: CachedBinnedEntity, -} - -/// Information that we keep about an entity currently within a bin. -pub struct CachedBinnedEntity -where - BPI: BinnedPhaseItem, -{ - /// Information that we use to identify a cached entity in a bin. - pub cached_bin_key: Option>, - /// The last modified tick of the entity. - /// - /// We use this to detect when the entity needs to be invalidated. - pub change_tick: Tick, -} - -/// Information that we use to identify a cached entity in a bin. -pub struct CachedBinKey -where - BPI: BinnedPhaseItem, -{ - /// The key of the batch set containing the entity. - pub batch_set_key: BPI::BatchSetKey, - /// The key of the bin containing the entity. - pub bin_key: BPI::BinKey, - /// The type of render phase that we use to render the entity: multidraw, - /// plain batch, etc. - pub phase_type: BinnedRenderPhaseType, -} - -impl Clone for CachedBinnedEntity -where - BPI: BinnedPhaseItem, -{ - fn clone(&self) -> Self { - CachedBinnedEntity { - cached_bin_key: self.cached_bin_key.clone(), - change_tick: self.change_tick, - } - } -} - -impl Clone for CachedBinKey -where - BPI: BinnedPhaseItem, -{ - fn clone(&self) -> Self { - CachedBinKey { - batch_set_key: self.batch_set_key.clone(), - bin_key: self.bin_key.clone(), - phase_type: self.phase_type, - } - } -} - -impl PartialEq for CachedBinKey -where - BPI: BinnedPhaseItem, -{ - fn eq(&self, other: &Self) -> bool { - self.batch_set_key == other.batch_set_key - && self.bin_key == other.bin_key - && self.phase_type == other.phase_type - } -} - -/// How we store and render the batch sets. -/// -/// Each one of these corresponds to a [`GpuPreprocessingMode`]. -pub enum BinnedRenderPhaseBatchSets { - /// Batches are grouped into batch sets based on dynamic uniforms. - /// - /// This corresponds to [`GpuPreprocessingMode::None`]. - DynamicUniforms(Vec>), - - /// Batches are never grouped into batch sets. - /// - /// This corresponds to [`GpuPreprocessingMode::PreprocessingOnly`]. - Direct(Vec), - - /// Batches are grouped together into batch sets based on their ability to - /// be multi-drawn together. - /// - /// This corresponds to [`GpuPreprocessingMode::Culling`]. - MultidrawIndirect(Vec>), -} - -/// A group of entities that will be batched together into a single multi-draw -/// call. -pub struct BinnedRenderPhaseBatchSet { - /// The first batch in this batch set. - pub(crate) first_batch: BinnedRenderPhaseBatch, - /// The key of the bin that the first batch corresponds to. - pub(crate) bin_key: BK, - /// The number of batches. - pub(crate) batch_count: u32, - /// The index of the batch set in the GPU buffer. - pub(crate) index: u32, -} - -impl BinnedRenderPhaseBatchSets { - fn clear(&mut self) { - match *self { - BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut vec) => vec.clear(), - BinnedRenderPhaseBatchSets::Direct(ref mut vec) => vec.clear(), - BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => vec.clear(), - } - } -} - -/// Information about a single batch of entities rendered using binned phase -/// items. -#[derive(Debug)] -pub struct BinnedRenderPhaseBatch { - /// An entity that's *representative* of this batch. - /// - /// Bevy uses this to fetch the mesh. It can be any entity in the batch. - pub representative_entity: (Entity, MainEntity), - /// The range of instance indices in this batch. - pub instance_range: Range, - - /// The dynamic offset of the batch. - /// - /// Note that dynamic offsets are only used on platforms that don't support - /// storage buffers. - pub extra_index: PhaseItemExtraIndex, -} - -/// Information about the unbatchable entities in a bin. -pub struct UnbatchableBinnedEntities { - /// The entities. - pub entities: MainEntityHashMap, - - /// The GPU array buffer indices of each unbatchable binned entity. - pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet, -} - -/// Information about [`BinnedRenderPhaseType::NonMesh`] entities. -pub struct NonMeshEntities { - /// The entities. - pub entities: MainEntityHashMap, -} - -/// Stores instance indices and dynamic offsets for unbatchable entities in a -/// binned render phase. -/// -/// This is conceptually `Vec`, but it -/// avoids the overhead of storing dynamic offsets on platforms that support -/// them. In other words, this allows a fast path that avoids allocation on -/// platforms that aren't WebGL 2. -#[derive(Default)] - -pub(crate) enum UnbatchableBinnedEntityIndexSet { - /// There are no unbatchable entities in this bin (yet). - #[default] - NoEntities, - - /// The instances for all unbatchable entities in this bin are contiguous, - /// and there are no dynamic uniforms. - /// - /// This is the typical case on platforms other than WebGL 2. We special - /// case this to avoid allocation on those platforms. - Sparse { - /// The range of indices. - instance_range: Range, - /// The index of the first indirect instance parameters. - /// - /// The other indices immediately follow these. - first_indirect_parameters_index: Option, - }, - - /// Dynamic uniforms are present for unbatchable entities in this bin. - /// - /// We fall back to this on WebGL 2. - Dense(Vec), -} - -/// The instance index and dynamic offset (if present) for an unbatchable entity. -/// -/// This is only useful on platforms that don't support storage buffers. -#[derive(Clone)] -pub(crate) struct UnbatchableBinnedEntityIndices { - /// The instance index. - pub(crate) instance_index: u32, - /// The [`PhaseItemExtraIndex`], if present. - pub(crate) extra_index: PhaseItemExtraIndex, -} - -/// Identifies the list within [`BinnedRenderPhase`] that a phase item is to be -/// placed in. -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum BinnedRenderPhaseType { - /// The item is a mesh that's eligible for multi-draw indirect rendering and - /// can be batched with other meshes of the same type. - MultidrawableMesh, - - /// The item is a mesh that can be batched with other meshes of the same type and - /// drawn in a single draw call. - BatchableMesh, - - /// The item is a mesh that's eligible for indirect rendering, but can't be - /// batched with other meshes of the same type. - UnbatchableMesh, - - /// The item isn't a mesh at all. - /// - /// Bevy will simply invoke the drawing commands for such items one after - /// another, with no further processing. - /// - /// The engine itself doesn't enqueue any items of this type, but it's - /// available for use in your application and/or plugins. - NonMesh, -} - -impl From> for UnbatchableBinnedEntityIndices -where - T: Clone + ShaderSize + WriteInto, -{ - fn from(value: GpuArrayBufferIndex) -> Self { - UnbatchableBinnedEntityIndices { - instance_index: value.index, - extra_index: PhaseItemExtraIndex::maybe_dynamic_offset(value.dynamic_offset), - } - } -} - -impl Default for ViewBinnedRenderPhases -where - BPI: BinnedPhaseItem, -{ - fn default() -> Self { - Self(default()) - } -} - -impl ViewBinnedRenderPhases -where - BPI: BinnedPhaseItem, -{ - pub fn prepare_for_new_frame( - &mut self, - retained_view_entity: RetainedViewEntity, - gpu_preprocessing: GpuPreprocessingMode, - ) { - match self.entry(retained_view_entity) { - Entry::Occupied(mut entry) => entry.get_mut().prepare_for_new_frame(), - Entry::Vacant(entry) => { - entry.insert(BinnedRenderPhase::::new(gpu_preprocessing)); - } - } - } -} - -/// The index of the uniform describing this object in the GPU buffer, when GPU -/// preprocessing is enabled. -/// -/// For example, for 3D meshes, this is the index of the `MeshInputUniform` in -/// the buffer. -/// -/// This field is ignored if GPU preprocessing isn't in use, such as (currently) -/// in the case of 2D meshes. In that case, it can be safely set to -/// [`core::default::Default::default`]. -#[derive(Clone, Copy, PartialEq, Default, Deref, DerefMut)] -#[repr(transparent)] -pub struct InputUniformIndex(pub u32); - -impl BinnedRenderPhase -where - BPI: BinnedPhaseItem, -{ - /// Bins a new entity. - /// - /// The `phase_type` parameter specifies whether the entity is a - /// preprocessable mesh and whether it can be binned with meshes of the same - /// type. - pub fn add( - &mut self, - batch_set_key: BPI::BatchSetKey, - bin_key: BPI::BinKey, - (entity, main_entity): (Entity, MainEntity), - input_uniform_index: InputUniformIndex, - mut phase_type: BinnedRenderPhaseType, - change_tick: Tick, - ) { - // If the user has overridden indirect drawing for this view, we need to - // force the phase type to be batchable instead. - if self.gpu_preprocessing_mode == GpuPreprocessingMode::PreprocessingOnly - && phase_type == BinnedRenderPhaseType::MultidrawableMesh - { - phase_type = BinnedRenderPhaseType::BatchableMesh; - } - - match phase_type { - BinnedRenderPhaseType::MultidrawableMesh => { - match self.multidrawable_meshes.entry(batch_set_key.clone()) { - indexmap::map::Entry::Occupied(mut entry) => { - entry - .get_mut() - .entry(bin_key.clone()) - .or_default() - .insert(main_entity, input_uniform_index); - } - indexmap::map::Entry::Vacant(entry) => { - let mut new_batch_set = IndexMap::default(); - new_batch_set.insert( - bin_key.clone(), - RenderBin::from_entity(main_entity, input_uniform_index), - ); - entry.insert(new_batch_set); - } - } - } - - BinnedRenderPhaseType::BatchableMesh => { - match self - .batchable_meshes - .entry((batch_set_key.clone(), bin_key.clone()).clone()) - { - indexmap::map::Entry::Occupied(mut entry) => { - entry.get_mut().insert(main_entity, input_uniform_index); - } - indexmap::map::Entry::Vacant(entry) => { - entry.insert(RenderBin::from_entity(main_entity, input_uniform_index)); - } - } - } - - BinnedRenderPhaseType::UnbatchableMesh => { - match self - .unbatchable_meshes - .entry((batch_set_key.clone(), bin_key.clone())) - { - indexmap::map::Entry::Occupied(mut entry) => { - entry.get_mut().entities.insert(main_entity, entity); - } - indexmap::map::Entry::Vacant(entry) => { - let mut entities = MainEntityHashMap::default(); - entities.insert(main_entity, entity); - entry.insert(UnbatchableBinnedEntities { - entities, - buffer_indices: default(), - }); - } - } - } - - BinnedRenderPhaseType::NonMesh => { - // We don't process these items further. - match self - .non_mesh_items - .entry((batch_set_key.clone(), bin_key.clone()).clone()) - { - indexmap::map::Entry::Occupied(mut entry) => { - entry.get_mut().entities.insert(main_entity, entity); - } - indexmap::map::Entry::Vacant(entry) => { - let mut entities = MainEntityHashMap::default(); - entities.insert(main_entity, entity); - entry.insert(NonMeshEntities { entities }); - } - } - } - } - - // Update the cache. - self.update_cache( - main_entity, - Some(CachedBinKey { - batch_set_key, - bin_key, - phase_type, - }), - change_tick, - ); - } - - /// Inserts an entity into the cache with the given change tick. - pub fn update_cache( - &mut self, - main_entity: MainEntity, - cached_bin_key: Option>, - change_tick: Tick, - ) { - let new_cached_binned_entity = CachedBinnedEntity { - cached_bin_key, - change_tick, - }; - - let (index, old_cached_binned_entity) = self - .cached_entity_bin_keys - .insert_full(main_entity, new_cached_binned_entity.clone()); - - // If the entity changed bins, record its old bin so that we can remove - // the entity from it. - if let Some(old_cached_binned_entity) = old_cached_binned_entity - && old_cached_binned_entity.cached_bin_key != new_cached_binned_entity.cached_bin_key - { - self.entities_that_changed_bins.push(EntityThatChangedBins { - main_entity, - old_cached_binned_entity, - }); - } - - // Mark the entity as valid. - self.valid_cached_entity_bin_keys.grow_and_insert(index); - } - - /// Encodes the GPU commands needed to render all entities in this phase. - pub fn render<'w>( - &self, - render_pass: &mut TrackedRenderPass<'w>, - world: &'w World, - view: Entity, - ) -> Result<(), DrawError> { - { - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - draw_functions.prepare(world); - // Make sure to drop the reader-writer lock here to avoid recursive - // locks. - } - - self.render_batchable_meshes(render_pass, world, view)?; - self.render_unbatchable_meshes(render_pass, world, view)?; - self.render_non_meshes(render_pass, world, view)?; - - Ok(()) - } - - /// Renders all batchable meshes queued in this phase. - fn render_batchable_meshes<'w>( - &self, - render_pass: &mut TrackedRenderPass<'w>, - world: &'w World, - view: Entity, - ) -> Result<(), DrawError> { - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - - let render_device = world.resource::(); - let render_adapter_info = world.resource::(); - let multi_draw_indirect_count_supported = render_device - .features() - .contains(Features::MULTI_DRAW_INDIRECT_COUNT) - // TODO: https://github.com/gfx-rs/wgpu/issues/7974 - && !matches!(render_adapter_info.backend, wgpu::Backend::Dx12); - - match self.batch_sets { - BinnedRenderPhaseBatchSets::DynamicUniforms(ref batch_sets) => { - debug_assert_eq!(self.batchable_meshes.len(), batch_sets.len()); - - for ((batch_set_key, bin_key), batch_set) in - self.batchable_meshes.keys().zip(batch_sets.iter()) - { - for batch in batch_set { - let binned_phase_item = BPI::new( - batch_set_key.clone(), - bin_key.clone(), - batch.representative_entity, - batch.instance_range.clone(), - batch.extra_index.clone(), - ); - - // Fetch the draw function. - let Some(draw_function) = - draw_functions.get_mut(binned_phase_item.draw_function()) - else { - continue; - }; - - draw_function.draw(world, render_pass, view, &binned_phase_item)?; - } - } - } - - BinnedRenderPhaseBatchSets::Direct(ref batch_set) => { - for (batch, (batch_set_key, bin_key)) in - batch_set.iter().zip(self.batchable_meshes.keys()) - { - let binned_phase_item = BPI::new( - batch_set_key.clone(), - bin_key.clone(), - batch.representative_entity, - batch.instance_range.clone(), - batch.extra_index.clone(), - ); - - // Fetch the draw function. - let Some(draw_function) = - draw_functions.get_mut(binned_phase_item.draw_function()) - else { - continue; - }; - - draw_function.draw(world, render_pass, view, &binned_phase_item)?; - } - } - - BinnedRenderPhaseBatchSets::MultidrawIndirect(ref batch_sets) => { - for (batch_set_key, batch_set) in self - .multidrawable_meshes - .keys() - .chain( - self.batchable_meshes - .keys() - .map(|(batch_set_key, _)| batch_set_key), - ) - .zip(batch_sets.iter()) - { - let batch = &batch_set.first_batch; - - let batch_set_index = if multi_draw_indirect_count_supported { - NonMaxU32::new(batch_set.index) - } else { - None - }; - - let binned_phase_item = BPI::new( - batch_set_key.clone(), - batch_set.bin_key.clone(), - batch.representative_entity, - batch.instance_range.clone(), - match batch.extra_index { - PhaseItemExtraIndex::None => PhaseItemExtraIndex::None, - PhaseItemExtraIndex::DynamicOffset(ref dynamic_offset) => { - PhaseItemExtraIndex::DynamicOffset(*dynamic_offset) - } - PhaseItemExtraIndex::IndirectParametersIndex { ref range, .. } => { - PhaseItemExtraIndex::IndirectParametersIndex { - range: range.start..(range.start + batch_set.batch_count), - batch_set_index, - } - } - }, - ); - - // Fetch the draw function. - let Some(draw_function) = - draw_functions.get_mut(binned_phase_item.draw_function()) - else { - continue; - }; - - draw_function.draw(world, render_pass, view, &binned_phase_item)?; - } - } - } - - Ok(()) - } - - /// Renders all unbatchable meshes queued in this phase. - fn render_unbatchable_meshes<'w>( - &self, - render_pass: &mut TrackedRenderPass<'w>, - world: &'w World, - view: Entity, - ) -> Result<(), DrawError> { - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - - for (batch_set_key, bin_key) in self.unbatchable_meshes.keys() { - let unbatchable_entities = - &self.unbatchable_meshes[&(batch_set_key.clone(), bin_key.clone())]; - for (entity_index, entity) in unbatchable_entities.entities.iter().enumerate() { - let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices { - UnbatchableBinnedEntityIndexSet::NoEntities => { - // Shouldn't happen… - continue; - } - UnbatchableBinnedEntityIndexSet::Sparse { - instance_range, - first_indirect_parameters_index, - } => UnbatchableBinnedEntityIndices { - instance_index: instance_range.start + entity_index as u32, - extra_index: match first_indirect_parameters_index { - None => PhaseItemExtraIndex::None, - Some(first_indirect_parameters_index) => { - let first_indirect_parameters_index_for_entity = - u32::from(*first_indirect_parameters_index) - + entity_index as u32; - PhaseItemExtraIndex::IndirectParametersIndex { - range: first_indirect_parameters_index_for_entity - ..(first_indirect_parameters_index_for_entity + 1), - batch_set_index: None, - } - } - }, - }, - UnbatchableBinnedEntityIndexSet::Dense(dynamic_offsets) => { - dynamic_offsets[entity_index].clone() - } - }; - - let binned_phase_item = BPI::new( - batch_set_key.clone(), - bin_key.clone(), - (*entity.1, *entity.0), - unbatchable_dynamic_offset.instance_index - ..(unbatchable_dynamic_offset.instance_index + 1), - unbatchable_dynamic_offset.extra_index, - ); - - // Fetch the draw function. - let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function()) - else { - continue; - }; - - draw_function.draw(world, render_pass, view, &binned_phase_item)?; - } - } - Ok(()) - } - - /// Renders all objects of type [`BinnedRenderPhaseType::NonMesh`]. - /// - /// These will have been added by plugins or the application. - fn render_non_meshes<'w>( - &self, - render_pass: &mut TrackedRenderPass<'w>, - world: &'w World, - view: Entity, - ) -> Result<(), DrawError> { - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - - for ((batch_set_key, bin_key), non_mesh_entities) in &self.non_mesh_items { - for (main_entity, entity) in non_mesh_entities.entities.iter() { - // Come up with a fake batch range and extra index. The draw - // function is expected to manage any sort of batching logic itself. - let binned_phase_item = BPI::new( - batch_set_key.clone(), - bin_key.clone(), - (*entity, *main_entity), - 0..1, - PhaseItemExtraIndex::None, - ); - - let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function()) - else { - continue; - }; - - draw_function.draw(world, render_pass, view, &binned_phase_item)?; - } - } - - Ok(()) - } - - pub fn is_empty(&self) -> bool { - self.multidrawable_meshes.is_empty() - && self.batchable_meshes.is_empty() - && self.unbatchable_meshes.is_empty() - && self.non_mesh_items.is_empty() - } - - pub fn prepare_for_new_frame(&mut self) { - self.batch_sets.clear(); - - self.valid_cached_entity_bin_keys.clear(); - self.valid_cached_entity_bin_keys - .grow(self.cached_entity_bin_keys.len()); - self.valid_cached_entity_bin_keys - .set_range(self.cached_entity_bin_keys.len().., true); - - self.entities_that_changed_bins.clear(); - - for unbatchable_bin in self.unbatchable_meshes.values_mut() { - unbatchable_bin.buffer_indices.clear(); - } - } - - /// Checks to see whether the entity is in a bin and returns true if it's - /// both in a bin and up to date. - /// - /// If this function returns true, we also add the entry to the - /// `valid_cached_entity_bin_keys` list. - pub fn validate_cached_entity( - &mut self, - visible_entity: MainEntity, - current_change_tick: Tick, - ) -> bool { - if let indexmap::map::Entry::Occupied(entry) = - self.cached_entity_bin_keys.entry(visible_entity) - && entry.get().change_tick == current_change_tick - { - self.valid_cached_entity_bin_keys.insert(entry.index()); - return true; - } - - false - } - - /// Removes all entities not marked as clean from the bins. - /// - /// During `queue_material_meshes`, we process all visible entities and mark - /// each as clean as we come to it. Then, in [`sweep_old_entities`], we call - /// this method, which removes entities that aren't marked as clean from the - /// bins. - pub fn sweep_old_entities(&mut self) { - // Search for entities not marked as valid. We have to do this in - // reverse order because `swap_remove_index` will potentially invalidate - // all indices after the one we remove. - for index in ReverseFixedBitSetZeroesIterator::new(&self.valid_cached_entity_bin_keys) { - let Some((entity, cached_binned_entity)) = - self.cached_entity_bin_keys.swap_remove_index(index) - else { - continue; - }; - - if let Some(ref cached_bin_key) = cached_binned_entity.cached_bin_key { - remove_entity_from_bin( - entity, - cached_bin_key, - &mut self.multidrawable_meshes, - &mut self.batchable_meshes, - &mut self.unbatchable_meshes, - &mut self.non_mesh_items, - ); - } - } - - // If an entity changed bins, we need to remove it from its old bin. - for entity_that_changed_bins in self.entities_that_changed_bins.drain(..) { - let Some(ref old_cached_bin_key) = entity_that_changed_bins - .old_cached_binned_entity - .cached_bin_key - else { - continue; - }; - remove_entity_from_bin( - entity_that_changed_bins.main_entity, - old_cached_bin_key, - &mut self.multidrawable_meshes, - &mut self.batchable_meshes, - &mut self.unbatchable_meshes, - &mut self.non_mesh_items, - ); - } - } -} - -/// Removes an entity from a bin. -/// -/// If this makes the bin empty, this function removes the bin as well. -/// -/// This is a standalone function instead of a method on [`BinnedRenderPhase`] -/// for borrow check reasons. -fn remove_entity_from_bin( - entity: MainEntity, - entity_bin_key: &CachedBinKey, - multidrawable_meshes: &mut IndexMap>, - batchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>, - unbatchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>, - non_mesh_items: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>, -) where - BPI: BinnedPhaseItem, -{ - match entity_bin_key.phase_type { - BinnedRenderPhaseType::MultidrawableMesh => { - if let indexmap::map::Entry::Occupied(mut batch_set_entry) = - multidrawable_meshes.entry(entity_bin_key.batch_set_key.clone()) - { - if let indexmap::map::Entry::Occupied(mut bin_entry) = batch_set_entry - .get_mut() - .entry(entity_bin_key.bin_key.clone()) - { - bin_entry.get_mut().remove(entity); - - // If the bin is now empty, remove the bin. - if bin_entry.get_mut().is_empty() { - bin_entry.swap_remove(); - } - } - - // If the batch set is now empty, remove it. This will perturb - // the order, but that's OK because we're going to sort the bin - // afterwards. - if batch_set_entry.get_mut().is_empty() { - batch_set_entry.swap_remove(); - } - } - } - - BinnedRenderPhaseType::BatchableMesh => { - if let indexmap::map::Entry::Occupied(mut bin_entry) = batchable_meshes.entry(( - entity_bin_key.batch_set_key.clone(), - entity_bin_key.bin_key.clone(), - )) { - bin_entry.get_mut().remove(entity); - - // If the bin is now empty, remove the bin. - if bin_entry.get_mut().is_empty() { - bin_entry.swap_remove(); - } - } - } - - BinnedRenderPhaseType::UnbatchableMesh => { - if let indexmap::map::Entry::Occupied(mut bin_entry) = unbatchable_meshes.entry(( - entity_bin_key.batch_set_key.clone(), - entity_bin_key.bin_key.clone(), - )) { - bin_entry.get_mut().entities.remove(&entity); - - // If the bin is now empty, remove the bin. - if bin_entry.get_mut().entities.is_empty() { - bin_entry.swap_remove(); - } - } - } - - BinnedRenderPhaseType::NonMesh => { - if let indexmap::map::Entry::Occupied(mut bin_entry) = non_mesh_items.entry(( - entity_bin_key.batch_set_key.clone(), - entity_bin_key.bin_key.clone(), - )) { - bin_entry.get_mut().entities.remove(&entity); - - // If the bin is now empty, remove the bin. - if bin_entry.get_mut().entities.is_empty() { - bin_entry.swap_remove(); - } - } - } - } -} - -impl BinnedRenderPhase -where - BPI: BinnedPhaseItem, -{ - fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self { - Self { - multidrawable_meshes: IndexMap::default(), - batchable_meshes: IndexMap::default(), - unbatchable_meshes: IndexMap::default(), - non_mesh_items: IndexMap::default(), - batch_sets: match gpu_preprocessing { - GpuPreprocessingMode::Culling => { - BinnedRenderPhaseBatchSets::MultidrawIndirect(vec![]) - } - GpuPreprocessingMode::PreprocessingOnly => { - BinnedRenderPhaseBatchSets::Direct(vec![]) - } - GpuPreprocessingMode::None => BinnedRenderPhaseBatchSets::DynamicUniforms(vec![]), - }, - cached_entity_bin_keys: IndexMap::default(), - valid_cached_entity_bin_keys: FixedBitSet::new(), - entities_that_changed_bins: vec![], - gpu_preprocessing_mode: gpu_preprocessing, - } - } -} - -impl UnbatchableBinnedEntityIndexSet { - /// Returns the [`UnbatchableBinnedEntityIndices`] for the given entity. - fn indices_for_entity_index( - &self, - entity_index: u32, - ) -> Option { - match self { - UnbatchableBinnedEntityIndexSet::NoEntities => None, - UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. } - if entity_index >= instance_range.len() as u32 => - { - None - } - UnbatchableBinnedEntityIndexSet::Sparse { - instance_range, - first_indirect_parameters_index: None, - } => Some(UnbatchableBinnedEntityIndices { - instance_index: instance_range.start + entity_index, - extra_index: PhaseItemExtraIndex::None, - }), - UnbatchableBinnedEntityIndexSet::Sparse { - instance_range, - first_indirect_parameters_index: Some(first_indirect_parameters_index), - } => { - let first_indirect_parameters_index_for_this_batch = - u32::from(*first_indirect_parameters_index) + entity_index; - Some(UnbatchableBinnedEntityIndices { - instance_index: instance_range.start + entity_index, - extra_index: PhaseItemExtraIndex::IndirectParametersIndex { - range: first_indirect_parameters_index_for_this_batch - ..(first_indirect_parameters_index_for_this_batch + 1), - batch_set_index: None, - }, - }) - } - UnbatchableBinnedEntityIndexSet::Dense(indices) => { - indices.get(entity_index as usize).cloned() - } - } - } -} - -/// A convenient abstraction for adding all the systems necessary for a binned -/// render phase to the render app. -/// -/// This is the version used when the pipeline supports GPU preprocessing: e.g. -/// 3D PBR meshes. -pub struct BinnedRenderPhasePlugin -where - BPI: BinnedPhaseItem, - GFBD: GetFullBatchData, -{ - /// Debugging flags that can optionally be set when constructing the renderer. - pub debug_flags: RenderDebugFlags, - phantom: PhantomData<(BPI, GFBD)>, -} - -impl BinnedRenderPhasePlugin -where - BPI: BinnedPhaseItem, - GFBD: GetFullBatchData, -{ - pub fn new(debug_flags: RenderDebugFlags) -> Self { - Self { - debug_flags, - phantom: PhantomData, - } - } -} - -impl Plugin for BinnedRenderPhasePlugin -where - BPI: BinnedPhaseItem, - GFBD: GetFullBatchData + Sync + Send + 'static, -{ - fn build(&self, app: &mut App) { - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - render_app - .init_resource::>() - .init_resource::>() - .insert_resource(PhaseIndirectParametersBuffers::::new( - self.debug_flags - .contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS), - )) - .add_systems( - Render, - ( - batching::sort_binned_render_phase::.in_set(RenderSystems::PhaseSort), - ( - no_gpu_preprocessing::batch_and_prepare_binned_render_phase:: - .run_if(resource_exists::>), - gpu_preprocessing::batch_and_prepare_binned_render_phase:: - .run_if( - resource_exists::< - BatchedInstanceBuffers, - >, - ), - ) - .in_set(RenderSystems::PrepareResources), - sweep_old_entities::.in_set(RenderSystems::QueueSweep), - gpu_preprocessing::collect_buffers_for_phase:: - .run_if( - resource_exists::< - BatchedInstanceBuffers, - >, - ) - .in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers), - ), - ); - } -} - -/// Stores the rendering instructions for a single phase that sorts items in all -/// views. -/// -/// They're cleared out every frame, but storing them in a resource like this -/// allows us to reuse allocations. -#[derive(Resource, Deref, DerefMut)] -pub struct ViewSortedRenderPhases(pub HashMap>) -where - SPI: SortedPhaseItem; - -impl Default for ViewSortedRenderPhases -where - SPI: SortedPhaseItem, -{ - fn default() -> Self { - Self(default()) - } -} - -impl ViewSortedRenderPhases -where - SPI: SortedPhaseItem, -{ - pub fn insert_or_clear(&mut self, retained_view_entity: RetainedViewEntity) { - match self.entry(retained_view_entity) { - Entry::Occupied(mut entry) => entry.get_mut().clear(), - Entry::Vacant(entry) => { - entry.insert(default()); - } - } - } -} - -/// A convenient abstraction for adding all the systems necessary for a sorted -/// render phase to the render app. -/// -/// This is the version used when the pipeline supports GPU preprocessing: e.g. -/// 3D PBR meshes. -pub struct SortedRenderPhasePlugin -where - SPI: SortedPhaseItem, - GFBD: GetFullBatchData, -{ - /// Debugging flags that can optionally be set when constructing the renderer. - pub debug_flags: RenderDebugFlags, - phantom: PhantomData<(SPI, GFBD)>, -} - -impl SortedRenderPhasePlugin -where - SPI: SortedPhaseItem, - GFBD: GetFullBatchData, -{ - pub fn new(debug_flags: RenderDebugFlags) -> Self { - Self { - debug_flags, - phantom: PhantomData, - } - } -} - -impl Plugin for SortedRenderPhasePlugin -where - SPI: SortedPhaseItem + CachedRenderPipelinePhaseItem, - GFBD: GetFullBatchData + Sync + Send + 'static, -{ - fn build(&self, app: &mut App) { - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - render_app - .init_resource::>() - .init_resource::>() - .insert_resource(PhaseIndirectParametersBuffers::::new( - self.debug_flags - .contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS), - )) - .add_systems( - Render, - ( - ( - no_gpu_preprocessing::batch_and_prepare_sorted_render_phase:: - .run_if(resource_exists::>), - gpu_preprocessing::batch_and_prepare_sorted_render_phase:: - .run_if( - resource_exists::< - BatchedInstanceBuffers, - >, - ), - ) - .in_set(RenderSystems::PrepareResources), - gpu_preprocessing::collect_buffers_for_phase:: - .run_if( - resource_exists::< - BatchedInstanceBuffers, - >, - ) - .in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers), - ), - ); - } -} - -impl UnbatchableBinnedEntityIndexSet { - /// Adds a new entity to the list of unbatchable binned entities. - pub fn add(&mut self, indices: UnbatchableBinnedEntityIndices) { - match self { - UnbatchableBinnedEntityIndexSet::NoEntities => { - match indices.extra_index { - PhaseItemExtraIndex::DynamicOffset(_) => { - // This is the first entity we've seen, and we don't have - // compute shaders. Initialize an array. - *self = UnbatchableBinnedEntityIndexSet::Dense(vec![indices]); - } - PhaseItemExtraIndex::None => { - // This is the first entity we've seen, and we have compute - // shaders. Initialize the fast path. - *self = UnbatchableBinnedEntityIndexSet::Sparse { - instance_range: indices.instance_index..indices.instance_index + 1, - first_indirect_parameters_index: None, - } - } - PhaseItemExtraIndex::IndirectParametersIndex { - range: ref indirect_parameters_index, - .. - } => { - // This is the first entity we've seen, and we have compute - // shaders. Initialize the fast path. - *self = UnbatchableBinnedEntityIndexSet::Sparse { - instance_range: indices.instance_index..indices.instance_index + 1, - first_indirect_parameters_index: NonMaxU32::new( - indirect_parameters_index.start, - ), - } - } - } - } - - UnbatchableBinnedEntityIndexSet::Sparse { - instance_range, - first_indirect_parameters_index, - } if instance_range.end == indices.instance_index - && ((first_indirect_parameters_index.is_none() - && indices.extra_index == PhaseItemExtraIndex::None) - || first_indirect_parameters_index.is_some_and( - |first_indirect_parameters_index| match indices.extra_index { - PhaseItemExtraIndex::IndirectParametersIndex { - range: ref this_range, - .. - } => { - u32::from(first_indirect_parameters_index) + instance_range.end - - instance_range.start - == this_range.start - } - PhaseItemExtraIndex::DynamicOffset(_) | PhaseItemExtraIndex::None => { - false - } - }, - )) => - { - // This is the normal case on non-WebGL 2. - instance_range.end += 1; - } - - UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. } => { - // We thought we were in non-WebGL 2 mode, but we got a dynamic - // offset or non-contiguous index anyway. This shouldn't happen, - // but let's go ahead and do the sensible thing anyhow: demote - // the compressed `NoDynamicOffsets` field to the full - // `DynamicOffsets` array. - warn!( - "Unbatchable binned entity index set was demoted from sparse to dense. \ - This is a bug in the renderer. Please report it.", - ); - let new_dynamic_offsets = (0..instance_range.len() as u32) - .flat_map(|entity_index| self.indices_for_entity_index(entity_index)) - .chain(iter::once(indices)) - .collect(); - *self = UnbatchableBinnedEntityIndexSet::Dense(new_dynamic_offsets); - } - - UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => { - dense_indices.push(indices); - } - } - } - - /// Clears the unbatchable binned entity index set. - fn clear(&mut self) { - match self { - UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => dense_indices.clear(), - UnbatchableBinnedEntityIndexSet::Sparse { .. } => { - *self = UnbatchableBinnedEntityIndexSet::NoEntities; - } - _ => {} - } - } -} - -/// A collection of all items to be rendered that will be encoded to GPU -/// commands for a single render phase for a single view. -/// -/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases. -/// They are used to queue entities for rendering. -/// Multiple phases might be required due to different sorting/batching behaviors -/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on -/// the rendered texture of the previous phase (e.g. for screen-space reflections). -/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`]. -/// The render pass might be reused for multiple phases to reduce GPU overhead. -/// -/// This flavor of render phase is used only for meshes that need to be sorted -/// back-to-front, such as transparent meshes. For items that don't need strict -/// sorting, [`BinnedRenderPhase`] is preferred, for performance. -pub struct SortedRenderPhase -where - I: SortedPhaseItem, -{ - /// The items within this [`SortedRenderPhase`]. - pub items: Vec, -} - -impl Default for SortedRenderPhase -where - I: SortedPhaseItem, -{ - fn default() -> Self { - Self { items: Vec::new() } - } -} - -impl SortedRenderPhase -where - I: SortedPhaseItem, -{ - /// Adds a [`PhaseItem`] to this render phase. - #[inline] - pub fn add(&mut self, item: I) { - self.items.push(item); - } - - /// Removes all [`PhaseItem`]s from this render phase. - #[inline] - pub fn clear(&mut self) { - self.items.clear(); - } - - /// Sorts all of its [`PhaseItem`]s. - pub fn sort(&mut self) { - I::sort(&mut self.items); - } - - /// An [`Iterator`] through the associated [`Entity`] for each [`PhaseItem`] in order. - #[inline] - pub fn iter_entities(&'_ self) -> impl Iterator + '_ { - self.items.iter().map(PhaseItem::entity) - } - - /// Renders all of its [`PhaseItem`]s using their corresponding draw functions. - pub fn render<'w>( - &self, - render_pass: &mut TrackedRenderPass<'w>, - world: &'w World, - view: Entity, - ) -> Result<(), DrawError> { - self.render_range(render_pass, world, view, ..) - } - - /// Renders all [`PhaseItem`]s in the provided `range` (based on their index in `self.items`) using their corresponding draw functions. - pub fn render_range<'w>( - &self, - render_pass: &mut TrackedRenderPass<'w>, - world: &'w World, - view: Entity, - range: impl SliceIndex<[I], Output = [I]>, - ) -> Result<(), DrawError> { - let items = self - .items - .get(range) - .expect("`Range` provided to `render_range()` is out of bounds"); - - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - draw_functions.prepare(world); - - let mut index = 0; - while index < items.len() { - let item = &items[index]; - let batch_range = item.batch_range(); - if batch_range.is_empty() { - index += 1; - } else { - let draw_function = draw_functions.get_mut(item.draw_function()).unwrap(); - draw_function.draw(world, render_pass, view, item)?; - index += batch_range.len(); - } - } - Ok(()) - } -} - -/// An item (entity of the render world) which will be drawn to a texture or the screen, -/// as part of a render phase. -/// -/// The data required for rendering an entity is extracted from the main world in the -/// [`ExtractSchedule`](crate::ExtractSchedule). -/// Then it has to be queued up for rendering during the [`RenderSystems::Queue`], -/// by adding a corresponding phase item to a render phase. -/// Afterwards it will be possibly sorted and rendered automatically in the -/// [`RenderSystems::PhaseSort`] and [`RenderSystems::Render`], respectively. -/// -/// `PhaseItem`s come in two flavors: [`BinnedPhaseItem`]s and -/// [`SortedPhaseItem`]s. -/// -/// * Binned phase items have a `BinKey` which specifies what bin they're to be -/// placed in. All items in the same bin are eligible to be batched together. -/// The `BinKey`s are sorted, but the individual bin items aren't. Binned phase -/// items are good for opaque meshes, in which the order of rendering isn't -/// important. Generally, binned phase items are faster than sorted phase items. -/// -/// * Sorted phase items, on the other hand, are placed into one large buffer -/// and then sorted all at once. This is needed for transparent meshes, which -/// have to be sorted back-to-front to render with the painter's algorithm. -/// These types of phase items are generally slower than binned phase items. -pub trait PhaseItem: Sized + Send + Sync + 'static { - /// Whether or not this `PhaseItem` should be subjected to automatic batching. (Default: `true`) - const AUTOMATIC_BATCHING: bool = true; - - /// The corresponding entity that will be drawn. - /// - /// This is used to fetch the render data of the entity, required by the draw function, - /// from the render world . - fn entity(&self) -> Entity; - - /// The main world entity represented by this `PhaseItem`. - fn main_entity(&self) -> MainEntity; - - /// Specifies the [`Draw`] function used to render the item. - fn draw_function(&self) -> DrawFunctionId; - - /// The range of instances that the batch covers. After doing a batched draw, batch range - /// length phase items will be skipped. This design is to avoid having to restructure the - /// render phase unnecessarily. - fn batch_range(&self) -> &Range; - fn batch_range_mut(&mut self) -> &mut Range; - - /// Returns the [`PhaseItemExtraIndex`]. - /// - /// If present, this is either a dynamic offset or an indirect parameters - /// index. - fn extra_index(&self) -> PhaseItemExtraIndex; - - /// Returns a pair of mutable references to both the batch range and extra - /// index. - fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range, &mut PhaseItemExtraIndex); -} - -/// The "extra index" associated with some [`PhaseItem`]s, alongside the -/// indirect instance index. -/// -/// Sometimes phase items require another index in addition to the range of -/// instances they already have. These can be: -/// -/// * The *dynamic offset*: a `wgpu` dynamic offset into the uniform buffer of -/// instance data. This is used on platforms that don't support storage -/// buffers, to work around uniform buffer size limitations. -/// -/// * The *indirect parameters index*: an index into the buffer that specifies -/// the indirect parameters for this [`PhaseItem`]'s drawcall. This is used when -/// indirect mode is on (as used for GPU culling). -/// -/// Note that our indirect draw functionality requires storage buffers, so it's -/// impossible to have both a dynamic offset and an indirect parameters index. -/// This convenient fact allows us to pack both indices into a single `u32`. -#[derive(Clone, PartialEq, Eq, Hash, Debug)] -pub enum PhaseItemExtraIndex { - /// No extra index is present. - None, - /// A `wgpu` dynamic offset into the uniform buffer of instance data. This - /// is used on platforms that don't support storage buffers, to work around - /// uniform buffer size limitations. - DynamicOffset(u32), - /// An index into the buffer that specifies the indirect parameters for this - /// [`PhaseItem`]'s drawcall. This is used when indirect mode is on (as used - /// for GPU culling). - IndirectParametersIndex { - /// The range of indirect parameters within the indirect parameters array. - /// - /// If we're using `multi_draw_indirect_count`, this specifies the - /// maximum range of indirect parameters within that array. If batches - /// are ultimately culled out on the GPU, the actual number of draw - /// commands might be lower than the length of this range. - range: Range, - /// If `multi_draw_indirect_count` is in use, and this phase item is - /// part of a batch set, specifies the index of the batch set that this - /// phase item is a part of. - /// - /// If `multi_draw_indirect_count` isn't in use, or this phase item - /// isn't part of a batch set, this is `None`. - batch_set_index: Option, - }, -} - -impl PhaseItemExtraIndex { - /// Returns either an indirect parameters index or - /// [`PhaseItemExtraIndex::None`], as appropriate. - pub fn maybe_indirect_parameters_index( - indirect_parameters_index: Option, - ) -> PhaseItemExtraIndex { - match indirect_parameters_index { - Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex { - range: u32::from(indirect_parameters_index) - ..(u32::from(indirect_parameters_index) + 1), - batch_set_index: None, - }, - None => PhaseItemExtraIndex::None, - } - } - - /// Returns either a dynamic offset index or [`PhaseItemExtraIndex::None`], - /// as appropriate. - pub fn maybe_dynamic_offset(dynamic_offset: Option) -> PhaseItemExtraIndex { - match dynamic_offset { - Some(dynamic_offset) => PhaseItemExtraIndex::DynamicOffset(dynamic_offset.into()), - None => PhaseItemExtraIndex::None, - } - } -} - -/// Represents phase items that are placed into bins. The `BinKey` specifies -/// which bin they're to be placed in. Bin keys are sorted, and items within the -/// same bin are eligible to be batched together. The elements within the bins -/// aren't themselves sorted. -/// -/// An example of a binned phase item is `Opaque3d`, for which the rendering -/// order isn't critical. -pub trait BinnedPhaseItem: PhaseItem { - /// The key used for binning [`PhaseItem`]s into bins. Order the members of - /// [`BinnedPhaseItem::BinKey`] by the order of binding for best - /// performance. For example, pipeline id, draw function id, mesh asset id, - /// lowest variable bind group id such as the material bind group id, and - /// its dynamic offsets if any, next bind group and offsets, etc. This - /// reduces the need for rebinding between bins and improves performance. - type BinKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash; - - /// The key used to combine batches into batch sets. - /// - /// A *batch set* is a set of meshes that can potentially be multi-drawn - /// together. - type BatchSetKey: PhaseItemBatchSetKey; - - /// Creates a new binned phase item from the key and per-entity data. - /// - /// Unlike [`SortedPhaseItem`]s, this is generally called "just in time" - /// before rendering. The resulting phase item isn't stored in any data - /// structures, resulting in significant memory savings. - fn new( - batch_set_key: Self::BatchSetKey, - bin_key: Self::BinKey, - representative_entity: (Entity, MainEntity), - batch_range: Range, - extra_index: PhaseItemExtraIndex, - ) -> Self; -} - -/// A key used to combine batches into batch sets. -/// -/// A *batch set* is a set of meshes that can potentially be multi-drawn -/// together. -pub trait PhaseItemBatchSetKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash { - /// Returns true if this batch set key describes indexed meshes or false if - /// it describes non-indexed meshes. - /// - /// Bevy uses this in order to determine which kind of indirect draw - /// parameters to use, if indirect drawing is enabled. - fn indexed(&self) -> bool; -} - -/// Represents phase items that must be sorted. The `SortKey` specifies the -/// order that these items are drawn in. These are placed into a single array, -/// and the array as a whole is then sorted. -/// -/// An example of a sorted phase item is `Transparent3d`, which must be sorted -/// back to front in order to correctly render with the painter's algorithm. -pub trait SortedPhaseItem: PhaseItem { - /// The type used for ordering the items. The smallest values are drawn first. - /// This order can be calculated using the [`ViewRangefinder3d`], - /// based on the view-space `Z` value of the corresponding view matrix. - type SortKey: Ord; - - /// Determines the order in which the items are drawn. - fn sort_key(&self) -> Self::SortKey; - - /// Sorts a slice of phase items into render order. Generally if the same type - /// is batched this should use a stable sort like [`slice::sort_by_key`]. - /// In almost all other cases, this should not be altered from the default, - /// which uses an unstable sort, as this provides the best balance of CPU and GPU - /// performance. - /// - /// Implementers can optionally not sort the list at all. This is generally advisable if and - /// only if the renderer supports a depth prepass, which is by default not supported by - /// the rest of Bevy's first party rendering crates. Even then, this may have a negative - /// impact on GPU-side performance due to overdraw. - /// - /// It's advised to always profile for performance changes when changing this implementation. - #[inline] - fn sort(items: &mut [Self]) { - items.sort_unstable_by_key(Self::sort_key); - } - - /// Whether this phase item targets indexed meshes (those with both vertex - /// and index buffers as opposed to just vertex buffers). - /// - /// Bevy needs this information in order to properly group phase items - /// together for multi-draw indirect, because the GPU layout of indirect - /// commands differs between indexed and non-indexed meshes. - /// - /// If you're implementing a custom phase item that doesn't describe a mesh, - /// you can safely return false here. - fn indexed(&self) -> bool; -} - -/// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline, -/// cached in the [`PipelineCache`]. -/// -/// You can use the [`SetItemPipeline`] render command to set the pipeline for this item. -pub trait CachedRenderPipelinePhaseItem: PhaseItem { - /// The id of the render pipeline, cached in the [`PipelineCache`], that will be used to draw - /// this phase item. - fn cached_pipeline(&self) -> CachedRenderPipelineId; -} - -/// A [`RenderCommand`] that sets the pipeline for the [`CachedRenderPipelinePhaseItem`]. -pub struct SetItemPipeline; - -impl RenderCommand

for SetItemPipeline { - type Param = SRes; - type ViewQuery = (); - type ItemQuery = (); - #[inline] - fn render<'w>( - item: &P, - _view: (), - _entity: Option<()>, - pipeline_cache: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - if let Some(pipeline) = pipeline_cache - .into_inner() - .get_render_pipeline(item.cached_pipeline()) - { - pass.set_render_pipeline(pipeline); - RenderCommandResult::Success - } else { - RenderCommandResult::Skip - } - } -} - -/// This system sorts the [`PhaseItem`]s of all [`SortedRenderPhase`]s of this -/// type. -pub fn sort_phase_system(mut render_phases: ResMut>) -where - I: SortedPhaseItem, -{ - for phase in render_phases.values_mut() { - phase.sort(); - } -} - -/// Removes entities that became invisible or changed phases from the bins. -/// -/// This must run after queuing. -pub fn sweep_old_entities(mut render_phases: ResMut>) -where - BPI: BinnedPhaseItem, -{ - for phase in render_phases.0.values_mut() { - phase.sweep_old_entities(); - } -} - -impl BinnedRenderPhaseType { - pub fn mesh( - batchable: bool, - gpu_preprocessing_support: &GpuPreprocessingSupport, - ) -> BinnedRenderPhaseType { - match (batchable, gpu_preprocessing_support.max_supported_mode) { - (true, GpuPreprocessingMode::Culling) => BinnedRenderPhaseType::MultidrawableMesh, - (true, _) => BinnedRenderPhaseType::BatchableMesh, - (false, _) => BinnedRenderPhaseType::UnbatchableMesh, - } - } -} - -impl RenderBin { - /// Creates a [`RenderBin`] containing a single entity. - fn from_entity(entity: MainEntity, uniform_index: InputUniformIndex) -> RenderBin { - let mut entities = IndexMap::default(); - entities.insert(entity, uniform_index); - RenderBin { entities } - } - - /// Inserts an entity into the bin. - fn insert(&mut self, entity: MainEntity, uniform_index: InputUniformIndex) { - self.entities.insert(entity, uniform_index); - } - - /// Removes an entity from the bin. - fn remove(&mut self, entity_to_remove: MainEntity) { - self.entities.swap_remove(&entity_to_remove); - } - - /// Returns true if the bin contains no entities. - fn is_empty(&self) -> bool { - self.entities.is_empty() - } - - /// Returns the [`IndexMap`] containing all the entities in the bin, along - /// with the cached [`InputUniformIndex`] of each. - #[inline] - pub fn entities(&self) -> &IndexMap { - &self.entities - } -} - -/// An iterator that efficiently finds the indices of all zero bits in a -/// [`FixedBitSet`] and returns them in reverse order. -/// -/// [`FixedBitSet`] doesn't natively offer this functionality, so we have to -/// implement it ourselves. -#[derive(Debug)] -struct ReverseFixedBitSetZeroesIterator<'a> { - /// The bit set. - bitset: &'a FixedBitSet, - /// The next bit index we're going to scan when [`Iterator::next`] is - /// called. - bit_index: isize, -} - -impl<'a> ReverseFixedBitSetZeroesIterator<'a> { - fn new(bitset: &'a FixedBitSet) -> ReverseFixedBitSetZeroesIterator<'a> { - ReverseFixedBitSetZeroesIterator { - bitset, - bit_index: (bitset.len() as isize) - 1, - } - } -} - -impl<'a> Iterator for ReverseFixedBitSetZeroesIterator<'a> { - type Item = usize; - - fn next(&mut self) -> Option { - while self.bit_index >= 0 { - // Unpack the bit index into block and bit. - let block_index = self.bit_index / (Block::BITS as isize); - let bit_pos = self.bit_index % (Block::BITS as isize); - - // Grab the block. Mask off all bits above the one we're scanning - // from by setting them all to 1. - let mut block = self.bitset.as_slice()[block_index as usize]; - if bit_pos + 1 < (Block::BITS as isize) { - block |= (!0) << (bit_pos + 1); - } - - // Search for the next unset bit. Note that the `leading_ones` - // function counts from the MSB to the LSB, so we need to flip it to - // get the bit number. - let pos = (Block::BITS as isize) - (block.leading_ones() as isize) - 1; - - // If we found an unset bit, return it. - if pos != -1 { - let result = block_index * (Block::BITS as isize) + pos; - self.bit_index = result - 1; - return Some(result as usize); - } - - // Otherwise, go to the previous block. - self.bit_index = block_index * (Block::BITS as isize) - 1; - } - - None - } -} - -#[cfg(test)] -mod test { - use super::ReverseFixedBitSetZeroesIterator; - use fixedbitset::FixedBitSet; - use proptest::{collection::vec, prop_assert_eq, proptest}; - - proptest! { - #[test] - fn reverse_fixed_bit_set_zeroes_iterator( - bits in vec(0usize..1024usize, 0usize..1024usize), - size in 0usize..1024usize, - ) { - // Build a random bit set. - let mut bitset = FixedBitSet::new(); - bitset.grow(size); - for bit in bits { - if bit < size { - bitset.set(bit, true); - } - } - - // Iterate over the bit set backwards in a naive way, and check that - // that iteration sequence corresponds to the optimized one. - let mut iter = ReverseFixedBitSetZeroesIterator::new(&bitset); - for bit_index in (0..size).rev() { - if !bitset.contains(bit_index) { - prop_assert_eq!(iter.next(), Some(bit_index)); - } - } - - prop_assert_eq!(iter.next(), None); - } - } -} diff --git a/third_party/bevy_render/src/render_phase/rangefinder.rs b/third_party/bevy_render/src/render_phase/rangefinder.rs deleted file mode 100644 index bc4fa30..0000000 --- a/third_party/bevy_render/src/render_phase/rangefinder.rs +++ /dev/null @@ -1,39 +0,0 @@ -use bevy_math::{Affine3A, Mat4, Vec3, Vec4}; - -/// A distance calculator for the draw order of [`PhaseItem`](crate::render_phase::PhaseItem)s. -pub struct ViewRangefinder3d { - view_from_world_row_2: Vec4, -} - -impl ViewRangefinder3d { - /// Creates a 3D rangefinder for a view matrix. - pub fn from_world_from_view(world_from_view: &Affine3A) -> ViewRangefinder3d { - let view_from_world = world_from_view.inverse(); - - ViewRangefinder3d { - view_from_world_row_2: Mat4::from(view_from_world).row(2), - } - } - - /// Calculates the distance, or view-space `Z` value, for the given world-space `position`. - #[inline] - pub fn distance(&self, position: &Vec3) -> f32 { - // NOTE: row 2 of the inverse view matrix dotted with the world-space position - // gives the z component of the point in view-space - self.view_from_world_row_2.dot(position.extend(1.0)) - } -} - -#[cfg(test)] -mod tests { - use super::ViewRangefinder3d; - use bevy_math::{Affine3A, Vec3}; - - #[test] - fn distance() { - let view_matrix = Affine3A::from_translation(Vec3::new(0.0, 0.0, -1.0)); - let rangefinder = ViewRangefinder3d::from_world_from_view(&view_matrix); - assert_eq!(rangefinder.distance(&Vec3::new(0., 0., 0.)), 1.0); - assert_eq!(rangefinder.distance(&Vec3::new(0., 0., 1.)), 2.0); - } -} diff --git a/third_party/bevy_render/src/render_resource/batched_uniform_buffer.rs b/third_party/bevy_render/src/render_resource/batched_uniform_buffer.rs deleted file mode 100644 index ad36839..0000000 --- a/third_party/bevy_render/src/render_resource/batched_uniform_buffer.rs +++ /dev/null @@ -1,157 +0,0 @@ -use super::{GpuArrayBufferIndex, GpuArrayBufferable}; -use crate::{ - render_resource::DynamicUniformBuffer, - renderer::{RenderDevice, RenderQueue}, -}; -use core::{marker::PhantomData, num::NonZero}; -use encase::{ - private::{ArrayMetadata, BufferMut, Metadata, RuntimeSizedArray, WriteInto, Writer}, - ShaderType, -}; -use nonmax::NonMaxU32; -use wgpu::{BindingResource, Limits}; - -// 1MB else we will make really large arrays on macOS which reports very large -// `max_uniform_buffer_binding_size`. On macOS this ends up being the minimum -// size of the uniform buffer as well as the size of each chunk of data at a -// dynamic offset. -#[cfg(any( - not(feature = "webgl"), - not(target_arch = "wasm32"), - feature = "webgpu" -))] -const MAX_REASONABLE_UNIFORM_BUFFER_BINDING_SIZE: u32 = 1 << 20; - -// WebGL2 quirk: using uniform buffers larger than 4KB will cause extremely -// long shader compilation times, so the limit needs to be lower on WebGL2. -// This is due to older shader compilers/GPUs that don't support dynamically -// indexing uniform buffers, and instead emulate it with large switch statements -// over buffer indices that take a long time to compile. -#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] -const MAX_REASONABLE_UNIFORM_BUFFER_BINDING_SIZE: u32 = 1 << 12; - -/// Similar to [`DynamicUniformBuffer`], except every N elements (depending on size) -/// are grouped into a batch as an `array` in WGSL. -/// -/// This reduces the number of rebindings required due to having to pass dynamic -/// offsets to bind group commands, and if indices into the array can be passed -/// in via other means, it enables batching of draw commands. -pub struct BatchedUniformBuffer { - // Batches of fixed-size arrays of T are written to this buffer so that - // each batch in a fixed-size array can be bound at a dynamic offset. - uniforms: DynamicUniformBuffer>>, - // A batch of T are gathered into this `MaxCapacityArray` until it is full, - // then it is written into the `DynamicUniformBuffer`, cleared, and new T - // are gathered here, and so on for each batch. - temp: MaxCapacityArray>, - current_offset: u32, - dynamic_offset_alignment: u32, -} - -impl BatchedUniformBuffer { - pub fn batch_size(limits: &Limits) -> usize { - (limits - .max_uniform_buffer_binding_size - .min(MAX_REASONABLE_UNIFORM_BUFFER_BINDING_SIZE) as u64 - / T::min_size().get()) as usize - } - - pub fn new(limits: &Limits) -> Self { - let capacity = Self::batch_size(limits); - let alignment = limits.min_uniform_buffer_offset_alignment; - - Self { - uniforms: DynamicUniformBuffer::new_with_alignment(alignment as u64), - temp: MaxCapacityArray(Vec::with_capacity(capacity), capacity), - current_offset: 0, - dynamic_offset_alignment: alignment, - } - } - - #[inline] - pub fn size(&self) -> NonZero { - self.temp.size() - } - - pub fn clear(&mut self) { - self.uniforms.clear(); - self.current_offset = 0; - self.temp.0.clear(); - } - - pub fn push(&mut self, component: T) -> GpuArrayBufferIndex { - let result = GpuArrayBufferIndex { - index: self.temp.0.len() as u32, - dynamic_offset: NonMaxU32::new(self.current_offset), - element_type: PhantomData, - }; - self.temp.0.push(component); - if self.temp.0.len() == self.temp.1 { - self.flush(); - } - result - } - - pub fn flush(&mut self) { - self.uniforms.push(&self.temp); - - self.current_offset += - align_to_next(self.temp.size().get(), self.dynamic_offset_alignment as u64) as u32; - - self.temp.0.clear(); - } - - pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { - if !self.temp.0.is_empty() { - self.flush(); - } - self.uniforms.write_buffer(device, queue); - } - - #[inline] - pub fn binding(&self) -> Option> { - let mut binding = self.uniforms.binding(); - if let Some(BindingResource::Buffer(binding)) = &mut binding { - // MaxCapacityArray is runtime-sized so can't use T::min_size() - binding.size = Some(self.size()); - } - binding - } -} - -#[inline] -fn align_to_next(value: u64, alignment: u64) -> u64 { - debug_assert!(alignment.is_power_of_two()); - ((value - 1) | (alignment - 1)) + 1 -} - -// ---------------------------------------------------------------------------- -// MaxCapacityArray was implemented by Teodor Tanasoaia for encase. It was -// copied here as it was not yet included in an encase release and it is -// unclear if it is the correct long-term solution for encase. - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] -struct MaxCapacityArray(T, usize); - -impl ShaderType for MaxCapacityArray -where - T: ShaderType, -{ - type ExtraMetadata = ArrayMetadata; - - const METADATA: Metadata = T::METADATA; - - fn size(&self) -> NonZero { - Self::METADATA.stride().mul(self.1.max(1) as u64).0 - } -} - -impl WriteInto for MaxCapacityArray -where - T: WriteInto + RuntimeSizedArray, -{ - fn write_into(&self, writer: &mut Writer) { - debug_assert!(self.0.len() <= self.1); - self.0.write_into(writer); - } -} diff --git a/third_party/bevy_render/src/render_resource/bind_group.rs b/third_party/bevy_render/src/render_resource/bind_group.rs deleted file mode 100644 index fe750e7..0000000 --- a/third_party/bevy_render/src/render_resource/bind_group.rs +++ /dev/null @@ -1,740 +0,0 @@ -use crate::{ - define_atomic_id, - render_asset::RenderAssets, - render_resource::{BindGroupLayout, Buffer, PipelineCache, Sampler, TextureView}, - renderer::{RenderDevice, WgpuWrapper}, - texture::GpuImage, -}; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::system::{SystemParam, SystemParamItem}; -use bevy_render::render_resource::BindGroupLayoutDescriptor; -pub use bevy_render_macros::AsBindGroup; -use core::ops::Deref; -use encase::ShaderType; -use thiserror::Error; -use wgpu::{ - BindGroupEntry, BindGroupLayoutEntry, BindingResource, SamplerBindingType, TextureViewDimension, -}; - -use super::{BindlessDescriptor, BindlessSlabResourceLimit}; - -define_atomic_id!(BindGroupId); - -/// Bind groups are responsible for binding render resources (e.g. buffers, textures, samplers) -/// to a [`TrackedRenderPass`](crate::render_phase::TrackedRenderPass). -/// This makes them accessible in the pipeline (shaders) as uniforms. -/// -/// This is a lightweight thread-safe wrapper around wgpu's own [`BindGroup`](wgpu::BindGroup), -/// which can be cloned as needed to workaround lifetime management issues. It may be converted -/// from and dereferences to wgpu's [`BindGroup`](wgpu::BindGroup). -/// -/// Can be created via [`RenderDevice::create_bind_group`](RenderDevice::create_bind_group). -#[derive(Clone, Debug)] -pub struct BindGroup { - id: BindGroupId, - value: WgpuWrapper, -} - -impl BindGroup { - /// Returns the [`BindGroupId`] representing the unique ID of the bind group. - #[inline] - pub fn id(&self) -> BindGroupId { - self.id - } -} - -impl PartialEq for BindGroup { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Eq for BindGroup {} - -impl core::hash::Hash for BindGroup { - fn hash(&self, state: &mut H) { - self.id.0.hash(state); - } -} - -impl From for BindGroup { - fn from(value: wgpu::BindGroup) -> Self { - BindGroup { - id: BindGroupId::new(), - value: WgpuWrapper::new(value), - } - } -} - -impl<'a> From<&'a BindGroup> for Option<&'a wgpu::BindGroup> { - fn from(value: &'a BindGroup) -> Self { - Some(value.deref()) - } -} - -impl<'a> From<&'a mut BindGroup> for Option<&'a wgpu::BindGroup> { - fn from(value: &'a mut BindGroup) -> Self { - Some(&*value) - } -} - -impl Deref for BindGroup { - type Target = wgpu::BindGroup; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} - -/// Converts a value to a [`BindGroup`] with a given [`BindGroupLayout`], which can then be used in Bevy shaders. -/// This trait can be derived (and generally should be). Read on for details and examples. -/// -/// This is an opinionated trait that is intended to make it easy to generically -/// convert a type into a [`BindGroup`]. It provides access to specific render resources, -/// such as [`RenderAssets`] and [`crate::texture::FallbackImage`]. If a type has a [`Handle`](bevy_asset::Handle), -/// these can be used to retrieve the corresponding [`Texture`](crate::render_resource::Texture) resource. -/// -/// [`AsBindGroup::as_bind_group`] is intended to be called once, then the result cached somewhere. It is generally -/// ok to do "expensive" work here, such as creating a [`Buffer`] for a uniform. -/// -/// If for some reason a [`BindGroup`] cannot be created yet (for example, the [`Texture`](crate::render_resource::Texture) -/// for an [`Image`](bevy_image::Image) hasn't loaded yet), just return [`AsBindGroupError::RetryNextUpdate`], which signals that the caller -/// should retry again later. -/// -/// # Deriving -/// -/// This trait can be derived. Field attributes like `uniform` and `texture` are used to define which fields should be bindings, -/// what their binding type is, and what index they should be bound at: -/// -/// ``` -/// # use bevy_render::render_resource::*; -/// # use bevy_image::Image; -/// # use bevy_color::LinearRgba; -/// # use bevy_asset::Handle; -/// # use bevy_render::storage::ShaderStorageBuffer; -/// -/// #[derive(AsBindGroup)] -/// struct CoolMaterial { -/// #[uniform(0)] -/// color: LinearRgba, -/// #[texture(1)] -/// #[sampler(2)] -/// color_texture: Handle, -/// #[storage(3, read_only)] -/// storage_buffer: Handle, -/// #[storage(4, read_only, buffer)] -/// raw_buffer: Buffer, -/// #[storage_texture(5)] -/// storage_texture: Handle, -/// } -/// ``` -/// -/// In WGSL shaders, the binding would look like this: -/// -/// ```wgsl -/// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var color: vec4; -/// @group(#{MATERIAL_BIND_GROUP}) @binding(1) var color_texture: texture_2d; -/// @group(#{MATERIAL_BIND_GROUP}) @binding(2) var color_sampler: sampler; -/// @group(#{MATERIAL_BIND_GROUP}) @binding(3) var storage_buffer: array; -/// @group(#{MATERIAL_BIND_GROUP}) @binding(4) var raw_buffer: array; -/// @group(#{MATERIAL_BIND_GROUP}) @binding(5) var storage_texture: texture_storage_2d; -/// ``` -/// Note that the "group" index is determined by the usage context. It is not defined in [`AsBindGroup`]. For example, in Bevy material bind groups -/// are generally bound to group 2. -/// -/// The following field-level attributes are supported: -/// -/// ## `uniform(BINDING_INDEX)` -/// -/// * The field will be converted to a shader-compatible type using the [`ShaderType`] trait, written to a [`Buffer`], and bound as a uniform. -/// [`ShaderType`] is implemented for most math types already, such as [`f32`], [`Vec4`](bevy_math::Vec4), and -/// [`LinearRgba`](bevy_color::LinearRgba). It can also be derived for custom structs. -/// -/// ## `texture(BINDING_INDEX, arguments)` -/// -/// * This field's [`Handle`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture) -/// GPU resource, which will be bound as a texture in shaders. The field will be assumed to implement [`Into>>`]. In practice, -/// most fields should be a [`Handle`](bevy_asset::Handle) or [`Option>`]. If the value of an [`Option>`] is -/// [`None`], the [`crate::texture::FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute -/// (with a different binding index) if a binding of the sampler for the [`Image`](bevy_image::Image) is also required. -/// -/// | Arguments | Values | Default | -/// |-----------------------|-------------------------------------------------------------------------|----------------------| -/// | `dimension` = "..." | `"1d"`, `"2d"`, `"2d_array"`, `"3d"`, `"cube"`, `"cube_array"` | `"2d"` | -/// | `sample_type` = "..." | `"float"`, `"depth"`, `"s_int"` or `"u_int"` | `"float"` | -/// | `filterable` = ... | `true`, `false` | `true` | -/// | `multisampled` = ... | `true`, `false` | `false` | -/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` | -/// -/// ## `storage_texture(BINDING_INDEX, arguments)` -/// -/// * This field's [`Handle`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture) -/// GPU resource, which will be bound as a storage texture in shaders. The field will be assumed to implement [`Into>>`]. In practice, -/// most fields should be a [`Handle`](bevy_asset::Handle) or [`Option>`]. If the value of an [`Option>`] is -/// [`None`], the [`crate::texture::FallbackImage`] resource will be used instead. -/// -/// | Arguments | Values | Default | -/// |------------------------|--------------------------------------------------------------------------------------------|---------------| -/// | `dimension` = "..." | `"1d"`, `"2d"`, `"2d_array"`, `"3d"`, `"cube"`, `"cube_array"` | `"2d"` | -/// | `image_format` = ... | any member of [`TextureFormat`](crate::render_resource::TextureFormat) | `Rgba8Unorm` | -/// | `access` = ... | any member of [`StorageTextureAccess`](crate::render_resource::StorageTextureAccess) | `ReadWrite` | -/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `compute` | -/// -/// ## `sampler(BINDING_INDEX, arguments)` -/// -/// * This field's [`Handle`](bevy_asset::Handle) will be used to look up the matching [`Sampler`] GPU -/// resource, which will be bound as a sampler in shaders. The field will be assumed to implement [`Into>>`]. In practice, -/// most fields should be a [`Handle`](bevy_asset::Handle) or [`Option>`]. If the value of an [`Option>`] is -/// [`None`], the [`crate::texture::FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `texture` binding attribute -/// (with a different binding index) if a binding of the texture for the [`Image`](bevy_image::Image) is also required. -/// -/// | Arguments | Values | Default | -/// |------------------------|-------------------------------------------------------------------------|------------------------| -/// | `sampler_type` = "..." | `"filtering"`, `"non_filtering"`, `"comparison"`. | `"filtering"` | -/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` | -/// -/// ## `storage(BINDING_INDEX, arguments)` -/// -/// * The field's [`Handle`](bevy_asset::Handle) will be used to look -/// up the matching [`Buffer`] GPU resource, which will be bound as a storage -/// buffer in shaders. If the `storage` attribute is used, the field is expected -/// a raw buffer, and the buffer will be bound as a storage buffer in shaders. -/// In bindless mode, `binding_array()` argument that specifies the binding -/// number of the resulting storage buffer binding array must be present. -/// -/// | Arguments | Values | Default | -/// |------------------------|-------------------------------------------------------------------------|------------------------| -/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` | -/// | `read_only` | if present then value is true, otherwise false | `false` | -/// | `buffer` | if present then the field will be assumed to be a raw wgpu buffer | | -/// | `binding_array(...)` | the binding number of the binding array, for bindless mode | bindless mode disabled | -/// -/// Note that fields without field-level binding attributes will be ignored. -/// ``` -/// # use bevy_render::{render_resource::AsBindGroup}; -/// # use bevy_color::LinearRgba; -/// # use bevy_asset::Handle; -/// #[derive(AsBindGroup)] -/// struct CoolMaterial { -/// #[uniform(0)] -/// color: LinearRgba, -/// this_field_is_ignored: String, -/// } -/// ``` -/// -/// As mentioned above, [`Option>`] is also supported: -/// ``` -/// # use bevy_asset::Handle; -/// # use bevy_color::LinearRgba; -/// # use bevy_image::Image; -/// # use bevy_render::render_resource::AsBindGroup; -/// #[derive(AsBindGroup)] -/// struct CoolMaterial { -/// #[uniform(0)] -/// color: LinearRgba, -/// #[texture(1)] -/// #[sampler(2)] -/// color_texture: Option>, -/// } -/// ``` -/// This is useful if you want a texture to be optional. When the value is [`None`], the [`crate::texture::FallbackImage`] will be used for the binding instead, which defaults -/// to "pure white". -/// -/// Field uniforms with the same index will be combined into a single binding: -/// ``` -/// # use bevy_render::{render_resource::AsBindGroup}; -/// # use bevy_color::LinearRgba; -/// #[derive(AsBindGroup)] -/// struct CoolMaterial { -/// #[uniform(0)] -/// color: LinearRgba, -/// #[uniform(0)] -/// roughness: f32, -/// } -/// ``` -/// -/// In WGSL shaders, the binding would look like this: -/// ```wgsl -/// struct CoolMaterial { -/// color: vec4, -/// roughness: f32, -/// }; -/// -/// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var material: CoolMaterial; -/// ``` -/// -/// Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes: -/// ## `uniform(BINDING_INDEX, ConvertedShaderType)` -/// -/// * This also creates a [`Buffer`] using [`ShaderType`] and binds it as a -/// uniform, much like the field-level `uniform` attribute. The difference is -/// that the entire [`AsBindGroup`] value is converted to `ConvertedShaderType`, -/// which must implement [`ShaderType`], instead of a specific field -/// implementing [`ShaderType`]. This is useful if more complicated conversion -/// logic is required, or when using bindless mode (see below). The conversion -/// is done using the [`AsBindGroupShaderType`] trait, -/// which is automatically implemented if `&Self` implements -/// [`Into`]. Outside of bindless mode, only use -/// [`AsBindGroupShaderType`] if access to resources like -/// [`RenderAssets`] is required. -/// -/// * In bindless mode (see `bindless(COUNT)`), this attribute becomes -/// `uniform(BINDLESS_INDEX, ConvertedShaderType, -/// binding_array(BINDING_INDEX))`. The resulting uniform buffers will be -/// available in the shader as a binding array at the given `BINDING_INDEX`. The -/// `BINDLESS_INDEX` specifies the offset of the buffer in the bindless index -/// table. -/// -/// For example, suppose that the material slot is stored in a variable named -/// `slot`, the bindless index table is named `material_indices`, and that the -/// first field (index 0) of the bindless index table type is named -/// `material`. Then specifying `#[uniform(0, StandardMaterialUniform, -/// binding_array(10)]` will create a binding array buffer declared in the -/// shader as `var material_array: -/// binding_array` and accessible as -/// `material_array[material_indices[slot].material]`. -/// -/// ## `data(BINDING_INDEX, ConvertedShaderType, binding_array(BINDING_INDEX))` -/// -/// * This is very similar to `uniform(BINDING_INDEX, ConvertedShaderType, -/// binding_array(BINDING_INDEX)` and in fact is identical if bindless mode -/// isn't being used. The difference is that, in bindless mode, the `data` -/// attribute produces a single buffer containing an array, not an array of -/// buffers. For example, suppose you had the following declaration: -/// -/// ```ignore -/// #[uniform(0, StandardMaterialUniform, binding_array(10))] -/// struct StandardMaterial { ... } -/// ``` -/// -/// In bindless mode, this will produce a binding matching the following WGSL -/// declaration: -/// -/// ```wgsl -/// @group(#{MATERIAL_BIND_GROUP}) @binding(10) var material_array: binding_array; -/// ``` -/// -/// On the other hand, if you write this declaration: -/// -/// ```ignore -/// #[data(0, StandardMaterialUniform, binding_array(10))] -/// struct StandardMaterial { ... } -/// ``` -/// -/// Then Bevy produces a binding that matches this WGSL declaration instead: -/// -/// ```wgsl -/// @group(#{MATERIAL_BIND_GROUP}) @binding(10) var material_array: array; -/// ``` -/// -/// * Just as with the structure-level `uniform` attribute, Bevy converts the -/// entire [`AsBindGroup`] to `ConvertedShaderType`, using the -/// [`AsBindGroupShaderType`] trait. -/// -/// * In non-bindless mode, the structure-level `data` attribute is the same as -/// the structure-level `uniform` attribute and produces a single uniform buffer -/// in the shader. The above example would result in a binding that looks like -/// this in WGSL in non-bindless mode: -/// -/// ```wgsl -/// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var material: StandardMaterial; -/// ``` -/// -/// * For efficiency reasons, `data` is generally preferred over `uniform` -/// unless you need to place your data in individual buffers. -/// -/// ## `bind_group_data(DataType)` -/// -/// * The [`AsBindGroup`] type will be converted to some `DataType` using [`Into`] and stored -/// as [`AsBindGroup::Data`] as part of the [`AsBindGroup::as_bind_group`] call. This is useful if data needs to be stored alongside -/// the generated bind group, such as a unique identifier for a material's bind group. The most common use case for this attribute -/// is "shader pipeline specialization". See [`SpecializedRenderPipeline`](crate::render_resource::SpecializedRenderPipeline). -/// -/// ## `bindless` -/// -/// * This switch enables *bindless resources*, which changes the way Bevy -/// supplies resources (textures, and samplers) to the shader. When bindless -/// resources are enabled, and the current platform supports them, Bevy will -/// allocate textures, and samplers into *binding arrays*, separated based on -/// type and will supply your shader with indices into those arrays. -/// * Bindless textures and samplers are placed into the appropriate global -/// array defined in `bevy_render::bindless` (`bindless.wgsl`). -/// * Bevy doesn't currently support bindless buffers, except for those created -/// with the `uniform(BINDLESS_INDEX, ConvertedShaderType, -/// binding_array(BINDING_INDEX))` attribute. If you need to include a buffer in -/// your object, and you can't create the data in that buffer with the `uniform` -/// attribute, consider a non-bindless object instead. -/// * If bindless mode is enabled, the `BINDLESS` definition will be -/// available. Because not all platforms support bindless resources, you -/// should check for the presence of this definition via `#ifdef` and fall -/// back to standard bindings if it isn't present. -/// * By default, in bindless mode, binding 0 becomes the *bindless index -/// table*, which is an array of structures, each of which contains as many -/// fields of type `u32` as the highest binding number in the structure -/// annotated with `#[derive(AsBindGroup)]`. Again by default, the *i*th field -/// of the bindless index table contains the index of the resource with binding -/// *i* within the appropriate binding array. -/// * In the case of materials, the index of the applicable table within the -/// bindless index table list corresponding to the mesh currently being drawn -/// can be retrieved with -/// `mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu`. -/// * You can limit the size of the bindless slabs to N resources with the -/// `limit(N)` declaration. For example, `#[bindless(limit(16))]` ensures that -/// each slab will have no more than 16 total resources in it. If you don't -/// specify a limit, Bevy automatically picks a reasonable one for the current -/// platform. -/// * The `index_table(range(M..N), binding(B))` declaration allows you to -/// customize the layout of the bindless index table. This is useful for -/// materials that are composed of multiple bind groups, such as -/// `ExtendedMaterial`. In such cases, there will be multiple bindless index -/// tables, so they can't both be assigned to binding 0 or their bindings will -/// conflict. -/// - The `binding(B)` attribute of the `index_table` attribute allows you to -/// customize the binding (`@binding(B)`, in the shader) at which the index -/// table will be bound. -/// - The `range(M, N)` attribute of the `index_table` attribute allows you to -/// change the mapping from the field index in the bindless index table to the -/// bindless index. Instead of the field at index $i$ being mapped to the -/// bindless index $i$, with the `range(M, N)` attribute the field at index -/// $i$ in the bindless index table is mapped to the bindless index $i$ + M. -/// The size of the index table will be set to N - M. Note that this may -/// result in the table being too small to contain all the bindless bindings. -/// * The purpose of bindless mode is to improve performance by reducing -/// state changes. By grouping resources together into binding arrays, Bevy -/// doesn't have to modify GPU state as often, decreasing API and driver -/// overhead. -/// * See the `shaders/shader_material_bindless` example for an example of how -/// to use bindless mode. See the `shaders/extended_material_bindless` example -/// for a more exotic example of bindless mode that demonstrates the -/// `index_table` attribute. -/// * The following diagram illustrates how bindless mode works using a subset -/// of `StandardMaterial`: -/// -/// ```text -/// Shader Bindings Sampler Binding Array -/// +----+-----------------------------+ +-----------+-----------+-----+ -/// +---| 0 | material_indices | +->| sampler 0 | sampler 1 | ... | -/// | +----+-----------------------------+ | +-----------+-----------+-----+ -/// | | 1 | bindless_samplers_filtering +--+ ^ -/// | +----+-----------------------------+ +-------------------------------+ -/// | | .. | ... | | -/// | +----+-----------------------------+ Texture Binding Array | -/// | | 5 | bindless_textures_2d +--+ +-----------+-----------+-----+ | -/// | +----+-----------------------------+ +->| texture 0 | texture 1 | ... | | -/// | | .. | ... | +-----------+-----------+-----+ | -/// | +----+-----------------------------+ ^ | -/// | + 10 | material_array +--+ +---------------------------+ | -/// | +----+-----------------------------+ | | | -/// | | Buffer Binding Array | | -/// | | +----------+----------+-----+ | | -/// | +->| buffer 0 | buffer 1 | ... | | | -/// | Material Bindless Indices +----------+----------+-----+ | | -/// | +----+-----------------------------+ ^ | | -/// +-->| 0 | material +----------+ | | -/// +----+-----------------------------+ | | -/// | 1 | base_color_texture +---------------------------------------+ | -/// +----+-----------------------------+ | -/// | 2 | base_color_sampler +-------------------------------------------+ -/// +----+-----------------------------+ -/// | .. | ... | -/// +----+-----------------------------+ -/// ``` -/// -/// The previous `CoolMaterial` example illustrating "combining multiple field-level uniform attributes with the same binding index" can -/// also be equivalently represented with a single struct-level uniform attribute: -/// ``` -/// # use bevy_render::{render_resource::{AsBindGroup, ShaderType}}; -/// # use bevy_color::LinearRgba; -/// #[derive(AsBindGroup)] -/// #[uniform(0, CoolMaterialUniform)] -/// struct CoolMaterial { -/// color: LinearRgba, -/// roughness: f32, -/// } -/// -/// #[derive(ShaderType)] -/// struct CoolMaterialUniform { -/// color: LinearRgba, -/// roughness: f32, -/// } -/// -/// impl From<&CoolMaterial> for CoolMaterialUniform { -/// fn from(material: &CoolMaterial) -> CoolMaterialUniform { -/// CoolMaterialUniform { -/// color: material.color, -/// roughness: material.roughness, -/// } -/// } -/// } -/// ``` -/// -/// Setting `bind_group_data` looks like this: -/// ``` -/// # use bevy_render::{render_resource::AsBindGroup}; -/// # use bevy_color::LinearRgba; -/// #[derive(AsBindGroup)] -/// #[bind_group_data(CoolMaterialKey)] -/// struct CoolMaterial { -/// #[uniform(0)] -/// color: LinearRgba, -/// is_shaded: bool, -/// } -/// -/// // Materials keys are intended to be small, cheap to hash, and -/// // uniquely identify a specific material permutation. -/// #[repr(C)] -/// #[derive(Copy, Clone, Hash, Eq, PartialEq)] -/// struct CoolMaterialKey { -/// is_shaded: bool, -/// } -/// -/// impl From<&CoolMaterial> for CoolMaterialKey { -/// fn from(material: &CoolMaterial) -> CoolMaterialKey { -/// CoolMaterialKey { -/// is_shaded: material.is_shaded, -/// } -/// } -/// } -/// ``` -pub trait AsBindGroup { - /// Data that will be stored alongside the "prepared" bind group. - type Data: Send + Sync; - - type Param: SystemParam + 'static; - - /// The number of slots per bind group, if bindless mode is enabled. - /// - /// If this bind group doesn't use bindless, then this will be `None`. - /// - /// Note that the *actual* slot count may be different from this value, due - /// to platform limitations. For example, if bindless resources aren't - /// supported on this platform, the actual slot count will be 1. - fn bindless_slot_count() -> Option { - None - } - - /// True if the hardware *actually* supports bindless textures for this - /// type, taking the device and driver capabilities into account. - /// - /// If this type doesn't use bindless textures, then the return value from - /// this function is meaningless. - fn bindless_supported(_: &RenderDevice) -> bool { - true - } - - /// label - fn label() -> &'static str; - - /// Creates a bind group for `self` matching the layout defined in [`AsBindGroup::bind_group_layout`]. - fn as_bind_group( - &self, - layout_descriptor: &BindGroupLayoutDescriptor, - render_device: &RenderDevice, - pipeline_cache: &PipelineCache, - param: &mut SystemParamItem<'_, '_, Self::Param>, - ) -> Result { - let layout = &pipeline_cache.get_bind_group_layout(layout_descriptor); - - let UnpreparedBindGroup { bindings } = - Self::unprepared_bind_group(self, layout, render_device, param, false)?; - - let entries = bindings - .iter() - .map(|(index, binding)| BindGroupEntry { - binding: *index, - resource: binding.get_binding(), - }) - .collect::>(); - - let bind_group = render_device.create_bind_group(Self::label(), layout, &entries); - - Ok(PreparedBindGroup { - bindings, - bind_group, - }) - } - - fn bind_group_data(&self) -> Self::Data; - - /// Returns a vec of (binding index, `OwnedBindingResource`). - /// - /// In cases where `OwnedBindingResource` is not available (as for bindless - /// texture arrays currently), an implementor may return - /// `AsBindGroupError::CreateBindGroupDirectly` from this function and - /// instead define `as_bind_group` directly. This may prevent certain - /// features, such as bindless mode, from working correctly. - /// - /// Set `force_no_bindless` to true to require that bindless textures *not* - /// be used. `ExtendedMaterial` uses this in order to ensure that the base - /// material doesn't use bindless mode if the extension doesn't. - fn unprepared_bind_group( - &self, - layout: &BindGroupLayout, - render_device: &RenderDevice, - param: &mut SystemParamItem<'_, '_, Self::Param>, - force_no_bindless: bool, - ) -> Result; - - /// Creates the bind group layout matching all bind groups returned by - /// [`AsBindGroup::as_bind_group`] - fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout - where - Self: Sized, - { - render_device.create_bind_group_layout( - Self::label(), - &Self::bind_group_layout_entries(render_device, false), - ) - } - - /// Creates the bind group layout descriptor matching all bind groups returned by - /// [`AsBindGroup::as_bind_group`] - /// TODO: we only need `RenderDevice` to determine if bindless is supported - fn bind_group_layout_descriptor(render_device: &RenderDevice) -> BindGroupLayoutDescriptor - where - Self: Sized, - { - BindGroupLayoutDescriptor { - label: Self::label().into(), - entries: Self::bind_group_layout_entries(render_device, false), - } - } - - /// Returns a vec of bind group layout entries. - /// - /// Set `force_no_bindless` to true to require that bindless textures *not* - /// be used. `ExtendedMaterial` uses this in order to ensure that the base - /// material doesn't use bindless mode if the extension doesn't. - fn bind_group_layout_entries( - render_device: &RenderDevice, - force_no_bindless: bool, - ) -> Vec - where - Self: Sized; - - fn bindless_descriptor() -> Option { - None - } -} - -/// An error that occurs during [`AsBindGroup::as_bind_group`] calls. -#[derive(Debug, Error)] -pub enum AsBindGroupError { - /// The bind group could not be generated. Try again next frame. - #[error("The bind group could not be generated")] - RetryNextUpdate, - #[error("Create the bind group via `as_bind_group()` instead")] - CreateBindGroupDirectly, - #[error("At binding index {0}, the provided image sampler `{1}` does not match the required sampler type(s) `{2}`.")] - InvalidSamplerType(u32, String, String), -} - -/// A prepared bind group returned as a result of [`AsBindGroup::as_bind_group`]. -pub struct PreparedBindGroup { - pub bindings: BindingResources, - pub bind_group: BindGroup, -} - -/// a map containing `OwnedBindingResource`s, keyed by the target binding index -pub struct UnpreparedBindGroup { - pub bindings: BindingResources, -} - -/// A pair of binding index and binding resource, used as part of -/// [`PreparedBindGroup`] and [`UnpreparedBindGroup`]. -#[derive(Deref, DerefMut)] -pub struct BindingResources(pub Vec<(u32, OwnedBindingResource)>); - -/// An owned binding resource of any type (ex: a [`Buffer`], [`TextureView`], etc). -/// This is used by types like [`PreparedBindGroup`] to hold a single list of all -/// render resources used by bindings. -#[derive(Debug)] -pub enum OwnedBindingResource { - Buffer(Buffer), - TextureView(TextureViewDimension, TextureView), - Sampler(SamplerBindingType, Sampler), - Data(OwnedData), -} - -/// Data that will be copied into a GPU buffer. -/// -/// This corresponds to the `#[data]` attribute in `AsBindGroup`. -#[derive(Debug, Deref, DerefMut)] -pub struct OwnedData(pub Vec); - -impl OwnedBindingResource { - /// Creates a [`BindingResource`] reference to this - /// [`OwnedBindingResource`]. - /// - /// Note that this operation panics if passed a - /// [`OwnedBindingResource::Data`], because [`OwnedData`] doesn't itself - /// correspond to any binding and instead requires the - /// `MaterialBindGroupAllocator` to pack it into a buffer. - pub fn get_binding(&self) -> BindingResource<'_> { - match self { - OwnedBindingResource::Buffer(buffer) => buffer.as_entire_binding(), - OwnedBindingResource::TextureView(_, view) => BindingResource::TextureView(view), - OwnedBindingResource::Sampler(_, sampler) => BindingResource::Sampler(sampler), - OwnedBindingResource::Data(_) => panic!("`OwnedData` has no binding resource"), - } - } -} - -/// Converts a value to a [`ShaderType`] for use in a bind group. -/// -/// This is automatically implemented for references that implement [`Into`]. -/// Generally normal [`Into`] / [`From`] impls should be preferred, but -/// sometimes additional runtime metadata is required. -/// This exists largely to make some [`AsBindGroup`] use cases easier. -pub trait AsBindGroupShaderType { - /// Return the `T` [`ShaderType`] for `self`. When used in [`AsBindGroup`] - /// derives, it is safe to assume that all images in `self` exist. - fn as_bind_group_shader_type(&self, images: &RenderAssets) -> T; -} - -impl AsBindGroupShaderType for T -where - for<'a> &'a T: Into, -{ - #[inline] - fn as_bind_group_shader_type(&self, _images: &RenderAssets) -> U { - self.into() - } -} - -#[cfg(test)] -mod test { - use super::*; - use bevy_asset::Handle; - use bevy_image::Image; - - #[test] - fn texture_visibility() { - #[expect( - dead_code, - reason = "This is a derive macro compilation test. It will not be constructed." - )] - #[derive(AsBindGroup)] - pub struct TextureVisibilityTest { - #[texture(0, visibility(all))] - pub all: Handle, - #[texture(1, visibility(none))] - pub none: Handle, - #[texture(2, visibility(fragment))] - pub fragment: Handle, - #[texture(3, visibility(vertex))] - pub vertex: Handle, - #[texture(4, visibility(compute))] - pub compute: Handle, - #[texture(5, visibility(vertex, fragment))] - pub vertex_fragment: Handle, - #[texture(6, visibility(vertex, compute))] - pub vertex_compute: Handle, - #[texture(7, visibility(fragment, compute))] - pub fragment_compute: Handle, - #[texture(8, visibility(vertex, fragment, compute))] - pub vertex_fragment_compute: Handle, - } - } -} diff --git a/third_party/bevy_render/src/render_resource/bind_group_entries.rs b/third_party/bevy_render/src/render_resource/bind_group_entries.rs deleted file mode 100644 index 274aa11..0000000 --- a/third_party/bevy_render/src/render_resource/bind_group_entries.rs +++ /dev/null @@ -1,322 +0,0 @@ -use variadics_please::all_tuples_with_size; -use wgpu::{BindGroupEntry, BindingResource}; - -use super::{Sampler, TextureView}; - -/// Helper for constructing bindgroups. -/// -/// Allows constructing the descriptor's entries as: -/// ```ignore (render_device cannot be easily accessed) -/// render_device.create_bind_group( -/// "my_bind_group", -/// &my_layout, -/// &BindGroupEntries::with_indices(( -/// (2, &my_sampler), -/// (3, my_uniform), -/// )), -/// ); -/// ``` -/// -/// instead of -/// -/// ```ignore (render_device cannot be easily accessed) -/// render_device.create_bind_group( -/// "my_bind_group", -/// &my_layout, -/// &[ -/// BindGroupEntry { -/// binding: 2, -/// resource: BindingResource::Sampler(&my_sampler), -/// }, -/// BindGroupEntry { -/// binding: 3, -/// resource: my_uniform, -/// }, -/// ], -/// ); -/// ``` -/// -/// or -/// -/// ```ignore (render_device cannot be easily accessed) -/// render_device.create_bind_group( -/// "my_bind_group", -/// &my_layout, -/// &BindGroupEntries::sequential(( -/// &my_sampler, -/// my_uniform, -/// )), -/// ); -/// ``` -/// -/// instead of -/// -/// ```ignore (render_device cannot be easily accessed) -/// render_device.create_bind_group( -/// "my_bind_group", -/// &my_layout, -/// &[ -/// BindGroupEntry { -/// binding: 0, -/// resource: BindingResource::Sampler(&my_sampler), -/// }, -/// BindGroupEntry { -/// binding: 1, -/// resource: my_uniform, -/// }, -/// ], -/// ); -/// ``` -/// -/// or -/// -/// ```ignore (render_device cannot be easily accessed) -/// render_device.create_bind_group( -/// "my_bind_group", -/// &my_layout, -/// &BindGroupEntries::single(my_uniform), -/// ); -/// ``` -/// -/// instead of -/// -/// ```ignore (render_device cannot be easily accessed) -/// render_device.create_bind_group( -/// "my_bind_group", -/// &my_layout, -/// &[ -/// BindGroupEntry { -/// binding: 0, -/// resource: my_uniform, -/// }, -/// ], -/// ); -/// ``` -pub struct BindGroupEntries<'b, const N: usize = 1> { - entries: [BindGroupEntry<'b>; N], -} - -impl<'b, const N: usize> BindGroupEntries<'b, N> { - #[inline] - pub fn sequential(resources: impl IntoBindingArray<'b, N>) -> Self { - let mut i = 0; - Self { - entries: resources.into_array().map(|resource| { - let binding = i; - i += 1; - BindGroupEntry { binding, resource } - }), - } - } - - #[inline] - pub fn with_indices(indexed_resources: impl IntoIndexedBindingArray<'b, N>) -> Self { - Self { - entries: indexed_resources - .into_array() - .map(|(binding, resource)| BindGroupEntry { binding, resource }), - } - } -} - -impl<'b> BindGroupEntries<'b, 1> { - pub fn single(resource: impl IntoBinding<'b>) -> [BindGroupEntry<'b>; 1] { - [BindGroupEntry { - binding: 0, - resource: resource.into_binding(), - }] - } -} - -impl<'b, const N: usize> core::ops::Deref for BindGroupEntries<'b, N> { - type Target = [BindGroupEntry<'b>]; - - fn deref(&self) -> &[BindGroupEntry<'b>] { - &self.entries - } -} - -pub trait IntoBinding<'a> { - fn into_binding(self) -> BindingResource<'a>; -} - -impl<'a> IntoBinding<'a> for &'a TextureView { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - BindingResource::TextureView(self) - } -} - -impl<'a> IntoBinding<'a> for &'a wgpu::TextureView { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - BindingResource::TextureView(self) - } -} - -impl<'a> IntoBinding<'a> for &'a [&'a wgpu::TextureView] { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - BindingResource::TextureViewArray(self) - } -} - -impl<'a> IntoBinding<'a> for &'a Sampler { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - BindingResource::Sampler(self) - } -} - -impl<'a> IntoBinding<'a> for &'a [&'a wgpu::Sampler] { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - BindingResource::SamplerArray(self) - } -} - -impl<'a> IntoBinding<'a> for BindingResource<'a> { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - self - } -} - -impl<'a> IntoBinding<'a> for wgpu::BufferBinding<'a> { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - BindingResource::Buffer(self) - } -} - -impl<'a> IntoBinding<'a> for &'a [wgpu::BufferBinding<'a>] { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - BindingResource::BufferArray(self) - } -} - -pub trait IntoBindingArray<'b, const N: usize> { - fn into_array(self) -> [BindingResource<'b>; N]; -} - -macro_rules! impl_to_binding_slice { - ($N: expr, $(#[$meta:meta])* $(($T: ident, $I: ident)),*) => { - $(#[$meta])* - impl<'b, $($T: IntoBinding<'b>),*> IntoBindingArray<'b, $N> for ($($T,)*) { - #[inline] - fn into_array(self) -> [BindingResource<'b>; $N] { - let ($($I,)*) = self; - [$($I.into_binding(), )*] - } - } - } -} - -all_tuples_with_size!( - #[doc(fake_variadic)] - impl_to_binding_slice, - 1, - 32, - T, - s -); - -pub trait IntoIndexedBindingArray<'b, const N: usize> { - fn into_array(self) -> [(u32, BindingResource<'b>); N]; -} - -macro_rules! impl_to_indexed_binding_slice { - ($N: expr, $(($T: ident, $S: ident, $I: ident)),*) => { - impl<'b, $($T: IntoBinding<'b>),*> IntoIndexedBindingArray<'b, $N> for ($((u32, $T),)*) { - #[inline] - fn into_array(self) -> [(u32, BindingResource<'b>); $N] { - let ($(($S, $I),)*) = self; - [$(($S, $I.into_binding())), *] - } - } - } -} - -all_tuples_with_size!(impl_to_indexed_binding_slice, 1, 32, T, n, s); - -pub struct DynamicBindGroupEntries<'b> { - entries: Vec>, -} - -impl<'b> Default for DynamicBindGroupEntries<'b> { - fn default() -> Self { - Self::new() - } -} - -impl<'b> DynamicBindGroupEntries<'b> { - pub fn sequential(entries: impl IntoBindingArray<'b, N>) -> Self { - Self { - entries: entries - .into_array() - .into_iter() - .enumerate() - .map(|(ix, resource)| BindGroupEntry { - binding: ix as u32, - resource, - }) - .collect(), - } - } - - pub fn extend_sequential( - mut self, - entries: impl IntoBindingArray<'b, N>, - ) -> Self { - let start = self.entries.last().unwrap().binding + 1; - self.entries.extend( - entries - .into_array() - .into_iter() - .enumerate() - .map(|(ix, resource)| BindGroupEntry { - binding: start + ix as u32, - resource, - }), - ); - self - } - - pub fn new_with_indices(entries: impl IntoIndexedBindingArray<'b, N>) -> Self { - Self { - entries: entries - .into_array() - .into_iter() - .map(|(binding, resource)| BindGroupEntry { binding, resource }) - .collect(), - } - } - - pub fn new() -> Self { - Self { - entries: Vec::new(), - } - } - - pub fn extend_with_indices( - mut self, - entries: impl IntoIndexedBindingArray<'b, N>, - ) -> Self { - self.entries.extend( - entries - .into_array() - .into_iter() - .map(|(binding, resource)| BindGroupEntry { binding, resource }), - ); - self - } -} - -impl<'b> core::ops::Deref for DynamicBindGroupEntries<'b> { - type Target = [BindGroupEntry<'b>]; - - fn deref(&self) -> &[BindGroupEntry<'b>] { - &self.entries - } -} diff --git a/third_party/bevy_render/src/render_resource/bind_group_layout.rs b/third_party/bevy_render/src/render_resource/bind_group_layout.rs deleted file mode 100644 index 3b8cc00..0000000 --- a/third_party/bevy_render/src/render_resource/bind_group_layout.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::{define_atomic_id, renderer::WgpuWrapper}; -use core::ops::Deref; - -define_atomic_id!(BindGroupLayoutId); - -/// Bind group layouts define the interface of resources (e.g. buffers, textures, samplers) -/// for a shader. The actual resource binding is done via a [`BindGroup`](super::BindGroup). -/// -/// This is a lightweight thread-safe wrapper around wgpu's own [`BindGroupLayout`](wgpu::BindGroupLayout), -/// which can be cloned as needed to workaround lifetime management issues. It may be converted -/// from and dereferences to wgpu's [`BindGroupLayout`](wgpu::BindGroupLayout). -/// -/// Can be created via [`RenderDevice::create_bind_group_layout`](crate::renderer::RenderDevice::create_bind_group_layout). -#[derive(Clone, Debug)] -pub struct BindGroupLayout { - id: BindGroupLayoutId, - value: WgpuWrapper, -} - -impl PartialEq for BindGroupLayout { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Eq for BindGroupLayout {} - -impl core::hash::Hash for BindGroupLayout { - fn hash(&self, state: &mut H) { - self.id.0.hash(state); - } -} - -impl BindGroupLayout { - /// Returns the [`BindGroupLayoutId`] representing the unique ID of the bind group layout. - #[inline] - pub fn id(&self) -> BindGroupLayoutId { - self.id - } - - #[inline] - pub fn value(&self) -> &wgpu::BindGroupLayout { - &self.value - } -} - -impl From for BindGroupLayout { - fn from(value: wgpu::BindGroupLayout) -> Self { - BindGroupLayout { - id: BindGroupLayoutId::new(), - value: WgpuWrapper::new(value), - } - } -} - -impl Deref for BindGroupLayout { - type Target = wgpu::BindGroupLayout; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} diff --git a/third_party/bevy_render/src/render_resource/bind_group_layout_entries.rs b/third_party/bevy_render/src/render_resource/bind_group_layout_entries.rs deleted file mode 100644 index 17630b7..0000000 --- a/third_party/bevy_render/src/render_resource/bind_group_layout_entries.rs +++ /dev/null @@ -1,592 +0,0 @@ -use core::num::NonZero; -use variadics_please::all_tuples_with_size; -use wgpu::{BindGroupLayoutEntry, BindingType, ShaderStages}; - -/// Helper for constructing bind group layouts. -/// -/// Allows constructing the layout's entries as: -/// ```ignore (render_device cannot be easily accessed) -/// let layout = render_device.create_bind_group_layout( -/// "my_bind_group_layout", -/// &BindGroupLayoutEntries::with_indices( -/// // The layout entries will only be visible in the fragment stage -/// ShaderStages::FRAGMENT, -/// ( -/// // Screen texture -/// (2, texture_2d(TextureSampleType::Float { filterable: true })), -/// // Sampler -/// (3, sampler(SamplerBindingType::Filtering)), -/// ), -/// ), -/// ); -/// ``` -/// -/// instead of -/// -/// ```ignore (render_device cannot be easily accessed) -/// let layout = render_device.create_bind_group_layout( -/// "my_bind_group_layout", -/// &[ -/// // Screen texture -/// BindGroupLayoutEntry { -/// binding: 2, -/// visibility: ShaderStages::FRAGMENT, -/// ty: BindingType::Texture { -/// sample_type: TextureSampleType::Float { filterable: true }, -/// view_dimension: TextureViewDimension::D2, -/// multisampled: false, -/// }, -/// count: None, -/// }, -/// // Sampler -/// BindGroupLayoutEntry { -/// binding: 3, -/// visibility: ShaderStages::FRAGMENT, -/// ty: BindingType::Sampler(SamplerBindingType::Filtering), -/// count: None, -/// }, -/// ], -/// ); -/// ``` -/// -/// or -/// -/// ```ignore (render_device cannot be easily accessed) -/// render_device.create_bind_group_layout( -/// "my_bind_group_layout", -/// &BindGroupLayoutEntries::sequential( -/// ShaderStages::FRAGMENT, -/// ( -/// // Screen texture -/// texture_2d(TextureSampleType::Float { filterable: true }), -/// // Sampler -/// sampler(SamplerBindingType::Filtering), -/// ), -/// ), -/// ); -/// ``` -/// -/// instead of -/// -/// ```ignore (render_device cannot be easily accessed) -/// let layout = render_device.create_bind_group_layout( -/// "my_bind_group_layout", -/// &[ -/// // Screen texture -/// BindGroupLayoutEntry { -/// binding: 0, -/// visibility: ShaderStages::FRAGMENT, -/// ty: BindingType::Texture { -/// sample_type: TextureSampleType::Float { filterable: true }, -/// view_dimension: TextureViewDimension::D2, -/// multisampled: false, -/// }, -/// count: None, -/// }, -/// // Sampler -/// BindGroupLayoutEntry { -/// binding: 1, -/// visibility: ShaderStages::FRAGMENT, -/// ty: BindingType::Sampler(SamplerBindingType::Filtering), -/// count: None, -/// }, -/// ], -/// ); -/// ``` -/// -/// or -/// -/// ```ignore (render_device cannot be easily accessed) -/// render_device.create_bind_group_layout( -/// "my_bind_group_layout", -/// &BindGroupLayoutEntries::single( -/// ShaderStages::FRAGMENT, -/// texture_2d(TextureSampleType::Float { filterable: true }), -/// ), -/// ); -/// ``` -/// -/// instead of -/// -/// ```ignore (render_device cannot be easily accessed) -/// let layout = render_device.create_bind_group_layout( -/// "my_bind_group_layout", -/// &[ -/// BindGroupLayoutEntry { -/// binding: 0, -/// visibility: ShaderStages::FRAGMENT, -/// ty: BindingType::Texture { -/// sample_type: TextureSampleType::Float { filterable: true }, -/// view_dimension: TextureViewDimension::D2, -/// multisampled: false, -/// }, -/// count: None, -/// }, -/// ], -/// ); -/// ``` - -#[derive(Clone, Copy)] -pub struct BindGroupLayoutEntryBuilder { - ty: BindingType, - visibility: Option, - count: Option>, -} - -impl BindGroupLayoutEntryBuilder { - pub fn visibility(mut self, visibility: ShaderStages) -> Self { - self.visibility = Some(visibility); - self - } - - pub fn count(mut self, count: NonZero) -> Self { - self.count = Some(count); - self - } - - pub fn build(&self, binding: u32, default_visibility: ShaderStages) -> BindGroupLayoutEntry { - BindGroupLayoutEntry { - binding, - ty: self.ty, - visibility: self.visibility.unwrap_or(default_visibility), - count: self.count, - } - } -} - -pub struct BindGroupLayoutEntries { - entries: [BindGroupLayoutEntry; N], -} - -impl BindGroupLayoutEntries { - #[inline] - pub fn sequential( - default_visibility: ShaderStages, - entries_ext: impl IntoBindGroupLayoutEntryBuilderArray, - ) -> Self { - let mut i = 0; - Self { - entries: entries_ext.into_array().map(|entry| { - let binding = i; - i += 1; - entry.build(binding, default_visibility) - }), - } - } - - #[inline] - pub fn with_indices( - default_visibility: ShaderStages, - indexed_entries: impl IntoIndexedBindGroupLayoutEntryBuilderArray, - ) -> Self { - Self { - entries: indexed_entries - .into_array() - .map(|(binding, entry)| entry.build(binding, default_visibility)), - } - } -} - -impl BindGroupLayoutEntries<1> { - pub fn single( - visibility: ShaderStages, - resource: impl IntoBindGroupLayoutEntryBuilder, - ) -> [BindGroupLayoutEntry; 1] { - [resource - .into_bind_group_layout_entry_builder() - .build(0, visibility)] - } -} - -impl core::ops::Deref for BindGroupLayoutEntries { - type Target = [BindGroupLayoutEntry]; - fn deref(&self) -> &[BindGroupLayoutEntry] { - &self.entries - } -} - -pub trait IntoBindGroupLayoutEntryBuilder { - fn into_bind_group_layout_entry_builder(self) -> BindGroupLayoutEntryBuilder; -} - -impl IntoBindGroupLayoutEntryBuilder for BindingType { - fn into_bind_group_layout_entry_builder(self) -> BindGroupLayoutEntryBuilder { - BindGroupLayoutEntryBuilder { - ty: self, - visibility: None, - count: None, - } - } -} - -impl IntoBindGroupLayoutEntryBuilder for BindGroupLayoutEntry { - fn into_bind_group_layout_entry_builder(self) -> BindGroupLayoutEntryBuilder { - if self.binding != u32::MAX { - tracing::warn!("The BindGroupLayoutEntries api ignores the binding index when converting a raw wgpu::BindGroupLayoutEntry. You can ignore this warning by setting it to u32::MAX."); - } - BindGroupLayoutEntryBuilder { - ty: self.ty, - visibility: Some(self.visibility), - count: self.count, - } - } -} - -impl IntoBindGroupLayoutEntryBuilder for BindGroupLayoutEntryBuilder { - fn into_bind_group_layout_entry_builder(self) -> BindGroupLayoutEntryBuilder { - self - } -} - -pub trait IntoBindGroupLayoutEntryBuilderArray { - fn into_array(self) -> [BindGroupLayoutEntryBuilder; N]; -} -macro_rules! impl_to_binding_type_slice { - ($N: expr, $(#[$meta:meta])* $(($T: ident, $I: ident)),*) => { - $(#[$meta])* - impl<$($T: IntoBindGroupLayoutEntryBuilder),*> IntoBindGroupLayoutEntryBuilderArray<$N> for ($($T,)*) { - #[inline] - fn into_array(self) -> [BindGroupLayoutEntryBuilder; $N] { - let ($($I,)*) = self; - [$($I.into_bind_group_layout_entry_builder(), )*] - } - } - } -} -all_tuples_with_size!( - #[doc(fake_variadic)] - impl_to_binding_type_slice, - 1, - 32, - T, - s -); - -pub trait IntoIndexedBindGroupLayoutEntryBuilderArray { - fn into_array(self) -> [(u32, BindGroupLayoutEntryBuilder); N]; -} -macro_rules! impl_to_indexed_binding_type_slice { - ($N: expr, $(($T: ident, $S: ident, $I: ident)),*) => { - impl<$($T: IntoBindGroupLayoutEntryBuilder),*> IntoIndexedBindGroupLayoutEntryBuilderArray<$N> for ($((u32, $T),)*) { - #[inline] - fn into_array(self) -> [(u32, BindGroupLayoutEntryBuilder); $N] { - let ($(($S, $I),)*) = self; - [$(($S, $I.into_bind_group_layout_entry_builder())), *] - } - } - } -} -all_tuples_with_size!(impl_to_indexed_binding_type_slice, 1, 32, T, n, s); - -impl IntoBindGroupLayoutEntryBuilderArray for [BindGroupLayoutEntry; N] { - fn into_array(self) -> [BindGroupLayoutEntryBuilder; N] { - self.map(IntoBindGroupLayoutEntryBuilder::into_bind_group_layout_entry_builder) - } -} - -pub struct DynamicBindGroupLayoutEntries { - default_visibility: ShaderStages, - entries: Vec, -} - -impl DynamicBindGroupLayoutEntries { - pub fn sequential( - default_visibility: ShaderStages, - entries: impl IntoBindGroupLayoutEntryBuilderArray, - ) -> Self { - Self { - default_visibility, - entries: entries - .into_array() - .into_iter() - .enumerate() - .map(|(ix, resource)| resource.build(ix as u32, default_visibility)) - .collect(), - } - } - - pub fn extend_sequential( - mut self, - entries: impl IntoBindGroupLayoutEntryBuilderArray, - ) -> Self { - let start = self.entries.last().unwrap().binding + 1; - self.entries.extend( - entries - .into_array() - .into_iter() - .enumerate() - .map(|(ix, resource)| resource.build(start + ix as u32, self.default_visibility)), - ); - self - } - - pub fn new_with_indices( - default_visibility: ShaderStages, - entries: impl IntoIndexedBindGroupLayoutEntryBuilderArray, - ) -> Self { - Self { - default_visibility, - entries: entries - .into_array() - .into_iter() - .map(|(binding, resource)| resource.build(binding, default_visibility)) - .collect(), - } - } - - pub fn new(default_visibility: ShaderStages) -> Self { - Self { - default_visibility, - entries: Vec::new(), - } - } - - pub fn extend_with_indices( - mut self, - entries: impl IntoIndexedBindGroupLayoutEntryBuilderArray, - ) -> Self { - self.entries.extend( - entries - .into_array() - .into_iter() - .map(|(binding, resource)| resource.build(binding, self.default_visibility)), - ); - self - } -} - -impl core::ops::Deref for DynamicBindGroupLayoutEntries { - type Target = [BindGroupLayoutEntry]; - - fn deref(&self) -> &[BindGroupLayoutEntry] { - &self.entries - } -} - -pub mod binding_types { - use crate::render_resource::{ - BufferBindingType, SamplerBindingType, TextureSampleType, TextureViewDimension, - }; - use core::num::NonZero; - use encase::ShaderType; - use wgpu::{StorageTextureAccess, TextureFormat}; - - use super::*; - - pub fn storage_buffer(has_dynamic_offset: bool) -> BindGroupLayoutEntryBuilder { - storage_buffer_sized(has_dynamic_offset, Some(T::min_size())) - } - - pub fn storage_buffer_sized( - has_dynamic_offset: bool, - min_binding_size: Option>, - ) -> BindGroupLayoutEntryBuilder { - BindingType::Buffer { - ty: BufferBindingType::Storage { read_only: false }, - has_dynamic_offset, - min_binding_size, - } - .into_bind_group_layout_entry_builder() - } - - pub fn storage_buffer_read_only( - has_dynamic_offset: bool, - ) -> BindGroupLayoutEntryBuilder { - storage_buffer_read_only_sized(has_dynamic_offset, Some(T::min_size())) - } - - pub fn storage_buffer_read_only_sized( - has_dynamic_offset: bool, - min_binding_size: Option>, - ) -> BindGroupLayoutEntryBuilder { - BindingType::Buffer { - ty: BufferBindingType::Storage { read_only: true }, - has_dynamic_offset, - min_binding_size, - } - .into_bind_group_layout_entry_builder() - } - - pub fn uniform_buffer(has_dynamic_offset: bool) -> BindGroupLayoutEntryBuilder { - uniform_buffer_sized(has_dynamic_offset, Some(T::min_size())) - } - - pub fn uniform_buffer_sized( - has_dynamic_offset: bool, - min_binding_size: Option>, - ) -> BindGroupLayoutEntryBuilder { - BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset, - min_binding_size, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_1d(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::D1, - multisampled: false, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_2d(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::D2, - multisampled: false, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_2d_multisampled(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::D2, - multisampled: true, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_2d_array(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::D2Array, - multisampled: false, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_2d_array_multisampled( - sample_type: TextureSampleType, - ) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::D2Array, - multisampled: true, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_depth_2d() -> BindGroupLayoutEntryBuilder { - texture_2d(TextureSampleType::Depth).into_bind_group_layout_entry_builder() - } - - pub fn texture_depth_2d_multisampled() -> BindGroupLayoutEntryBuilder { - texture_2d_multisampled(TextureSampleType::Depth).into_bind_group_layout_entry_builder() - } - - pub fn texture_cube(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::Cube, - multisampled: false, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_cube_multisampled( - sample_type: TextureSampleType, - ) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::Cube, - multisampled: true, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_cube_array(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::CubeArray, - multisampled: false, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_cube_array_multisampled( - sample_type: TextureSampleType, - ) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::CubeArray, - multisampled: true, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_3d(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::D3, - multisampled: false, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_3d_multisampled(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder { - BindingType::Texture { - sample_type, - view_dimension: TextureViewDimension::D3, - multisampled: true, - } - .into_bind_group_layout_entry_builder() - } - - pub fn sampler(sampler_binding_type: SamplerBindingType) -> BindGroupLayoutEntryBuilder { - BindingType::Sampler(sampler_binding_type).into_bind_group_layout_entry_builder() - } - - pub fn texture_storage_2d( - format: TextureFormat, - access: StorageTextureAccess, - ) -> BindGroupLayoutEntryBuilder { - BindingType::StorageTexture { - access, - format, - view_dimension: TextureViewDimension::D2, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_storage_2d_array( - format: TextureFormat, - access: StorageTextureAccess, - ) -> BindGroupLayoutEntryBuilder { - BindingType::StorageTexture { - access, - format, - view_dimension: TextureViewDimension::D2Array, - } - .into_bind_group_layout_entry_builder() - } - - pub fn texture_storage_3d( - format: TextureFormat, - access: StorageTextureAccess, - ) -> BindGroupLayoutEntryBuilder { - BindingType::StorageTexture { - access, - format, - view_dimension: TextureViewDimension::D3, - } - .into_bind_group_layout_entry_builder() - } - - pub fn acceleration_structure() -> BindGroupLayoutEntryBuilder { - BindingType::AccelerationStructure { - vertex_return: false, - } - .into_bind_group_layout_entry_builder() - } - - pub fn acceleration_structure_vertex_return() -> BindGroupLayoutEntryBuilder { - BindingType::AccelerationStructure { - vertex_return: true, - } - .into_bind_group_layout_entry_builder() - } -} diff --git a/third_party/bevy_render/src/render_resource/bindless.rs b/third_party/bevy_render/src/render_resource/bindless.rs deleted file mode 100644 index dc3bf00..0000000 --- a/third_party/bevy_render/src/render_resource/bindless.rs +++ /dev/null @@ -1,374 +0,0 @@ -//! Types and functions relating to bindless resources. - -use alloc::borrow::Cow; -use core::{ - num::{NonZeroU32, NonZeroU64}, - ops::Range, -}; - -use bevy_derive::{Deref, DerefMut}; -use wgpu::{ - BindGroupLayoutEntry, SamplerBindingType, ShaderStages, TextureSampleType, TextureViewDimension, -}; - -use crate::render_resource::binding_types::storage_buffer_read_only_sized; - -use super::binding_types::{ - sampler, texture_1d, texture_2d, texture_2d_array, texture_3d, texture_cube, texture_cube_array, -}; - -/// The default value for the number of resources that can be stored in a slab -/// on this platform. -/// -/// See the documentation for [`BindlessSlabResourceLimit`] for more -/// information. -#[cfg(any(target_os = "macos", target_os = "ios"))] -pub const AUTO_BINDLESS_SLAB_RESOURCE_LIMIT: u32 = 64; -/// The default value for the number of resources that can be stored in a slab -/// on this platform. -/// -/// See the documentation for [`BindlessSlabResourceLimit`] for more -/// information. -#[cfg(not(any(target_os = "macos", target_os = "ios")))] -pub const AUTO_BINDLESS_SLAB_RESOURCE_LIMIT: u32 = 2048; - -/// The binding numbers for the built-in binding arrays of each bindless -/// resource type. -/// -/// In the case of materials, the material allocator manages these binding -/// arrays. -/// -/// `bindless.wgsl` contains declarations of these arrays for use in your -/// shaders. If you change these, make sure to update that file as well. -pub static BINDING_NUMBERS: [(BindlessResourceType, BindingNumber); 9] = [ - (BindlessResourceType::SamplerFiltering, BindingNumber(1)), - (BindlessResourceType::SamplerNonFiltering, BindingNumber(2)), - (BindlessResourceType::SamplerComparison, BindingNumber(3)), - (BindlessResourceType::Texture1d, BindingNumber(4)), - (BindlessResourceType::Texture2d, BindingNumber(5)), - (BindlessResourceType::Texture2dArray, BindingNumber(6)), - (BindlessResourceType::Texture3d, BindingNumber(7)), - (BindlessResourceType::TextureCube, BindingNumber(8)), - (BindlessResourceType::TextureCubeArray, BindingNumber(9)), -]; - -/// The maximum number of resources that can be stored in a slab. -/// -/// This limit primarily exists in order to work around `wgpu` performance -/// problems involving large numbers of bindless resources. Also, some -/// platforms, such as Metal, currently enforce limits on the number of -/// resources in use. -/// -/// This corresponds to `LIMIT` in the `#[bindless(LIMIT)]` attribute when -/// deriving [`crate::render_resource::AsBindGroup`]. -#[derive(Clone, Copy, Default, PartialEq, Debug)] -pub enum BindlessSlabResourceLimit { - /// Allows the renderer to choose a reasonable value for the resource limit - /// based on the platform. - /// - /// This value has been tuned, so you should default to this value unless - /// you have special platform-specific considerations that prevent you from - /// using it. - #[default] - Auto, - - /// A custom value for the resource limit. - /// - /// Bevy will allocate no more than this number of resources in a slab, - /// unless exceeding this value is necessary in order to allocate at all - /// (i.e. unless the number of bindless resources in your bind group exceeds - /// this value), in which case Bevy can exceed it. - Custom(u32), -} - -/// Information about the bindless resources in this object. -/// -/// The material bind group allocator uses this descriptor in order to create -/// and maintain bind groups. The fields within this bindless descriptor are -/// [`Cow`]s in order to support both the common case in which the fields are -/// simply `static` constants and the more unusual case in which the fields are -/// dynamically generated efficiently. An example of the latter case is -/// `ExtendedMaterial`, which needs to assemble a bindless descriptor from those -/// of the base material and the material extension at runtime. -/// -/// This structure will only be present if this object is bindless. -pub struct BindlessDescriptor { - /// The bindless resource types that this object uses, in order of bindless - /// index. - /// - /// The resource assigned to binding index 0 will be at index 0, the - /// resource assigned to binding index will be at index 1 in this array, and - /// so on. Unused binding indices are set to [`BindlessResourceType::None`]. - pub resources: Cow<'static, [BindlessResourceType]>, - /// The [`BindlessBufferDescriptor`] for each bindless buffer that this - /// object uses. - /// - /// The order of this array is irrelevant. - pub buffers: Cow<'static, [BindlessBufferDescriptor]>, - /// The [`BindlessIndexTableDescriptor`]s describing each bindless index - /// table. - /// - /// This list must be sorted by the first bindless index. - pub index_tables: Cow<'static, [BindlessIndexTableDescriptor]>, -} - -/// The type of potentially-bindless resource. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub enum BindlessResourceType { - /// No bindless resource. - /// - /// This is used as a placeholder to fill holes in the - /// [`BindlessDescriptor::resources`] list. - None, - /// A storage buffer. - Buffer, - /// A filtering sampler. - SamplerFiltering, - /// A non-filtering sampler (nearest neighbor). - SamplerNonFiltering, - /// A comparison sampler (typically used for shadow maps). - SamplerComparison, - /// A 1D texture. - Texture1d, - /// A 2D texture. - Texture2d, - /// A 2D texture array. - /// - /// Note that this differs from a binding array. 2D texture arrays must all - /// have the same size and format. - Texture2dArray, - /// A 3D texture. - Texture3d, - /// A cubemap texture. - TextureCube, - /// A cubemap texture array. - /// - /// Note that this differs from a binding array. Cubemap texture arrays must - /// all have the same size and format. - TextureCubeArray, - /// Multiple instances of plain old data concatenated into a single buffer. - /// - /// This corresponds to the `#[data]` declaration in - /// [`crate::render_resource::AsBindGroup`]. - /// - /// Note that this resource doesn't itself map to a GPU-level binding - /// resource and instead depends on the `MaterialBindGroupAllocator` to - /// create a binding resource for it. - DataBuffer, -} - -/// Describes a bindless buffer. -/// -/// Unlike samplers and textures, each buffer in a bind group gets its own -/// unique bind group entry. That is, there isn't any `bindless_buffers` binding -/// array to go along with `bindless_textures_2d`, -/// `bindless_samplers_filtering`, etc. Therefore, this descriptor contains two -/// indices: the *binding number* and the *bindless index*. The binding number -/// is the `@binding` number used in the shader, while the bindless index is the -/// index of the buffer in the bindless index table (which is itself -/// conventionally bound to binding number 0). -/// -/// When declaring the buffer in a derived implementation -/// [`crate::render_resource::AsBindGroup`] with syntax like -/// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform, -/// bindless(BINDING_NUMBER)]`, the bindless index is `BINDLESS_INDEX`, and the -/// binding number is `BINDING_NUMBER`. Note the order. -#[derive(Clone, Copy, Debug)] -pub struct BindlessBufferDescriptor { - /// The actual binding number of the buffer. - /// - /// This is declared with `@binding` in WGSL. When deriving - /// [`crate::render_resource::AsBindGroup`], this is the `BINDING_NUMBER` in - /// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform, - /// bindless(BINDING_NUMBER)]`. - pub binding_number: BindingNumber, - /// The index of the buffer in the bindless index table. - /// - /// In the shader, this is the index into the table bound to binding 0. When - /// deriving [`crate::render_resource::AsBindGroup`], this is the - /// `BINDLESS_INDEX` in `#[uniform(BINDLESS_INDEX, StandardMaterialUniform, - /// bindless(BINDING_NUMBER)]`. - pub bindless_index: BindlessIndex, - /// The size of the buffer in bytes, if known. - pub size: Option, -} - -/// Describes the layout of the bindless index table, which maps bindless -/// indices to indices within the binding arrays. -#[derive(Clone)] -pub struct BindlessIndexTableDescriptor { - /// The range of bindless indices that this descriptor covers. - pub indices: Range, - /// The binding at which the index table itself will be bound. - /// - /// By default, this is binding 0, but it can be changed with the - /// `#[bindless(index_table(binding(B)))]` attribute. - pub binding_number: BindingNumber, -} - -/// The index of the actual binding in the bind group. -/// -/// This is the value specified in WGSL as `@binding`. -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Deref, DerefMut)] -pub struct BindingNumber(pub u32); - -/// The index in the bindless index table. -/// -/// This table is conventionally bound to binding number 0. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Debug, Deref, DerefMut)] -pub struct BindlessIndex(pub u32); - -/// Creates the bind group layout entries common to all shaders that use -/// bindless bind groups. -/// -/// `bindless_resource_count` specifies the total number of bindless resources. -/// `bindless_slab_resource_limit` specifies the resolved -/// [`BindlessSlabResourceLimit`] value. -pub fn create_bindless_bind_group_layout_entries( - bindless_index_table_length: u32, - bindless_slab_resource_limit: u32, - bindless_index_table_binding_number: BindingNumber, -) -> Vec { - let bindless_slab_resource_limit = - NonZeroU32::new(bindless_slab_resource_limit).expect("Bindless slot count must be nonzero"); - - // The maximum size of a binding array is the - // `bindless_slab_resource_limit`, which would occur if all of the bindless - // resources were of the same type. So we create our binding arrays with - // that size. - - vec![ - // Start with the bindless index table, bound to binding number 0. - storage_buffer_read_only_sized( - false, - NonZeroU64::new(bindless_index_table_length as u64 * size_of::() as u64), - ) - .build( - *bindless_index_table_binding_number, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - // Continue with the common bindless resource arrays. - sampler(SamplerBindingType::Filtering) - .count(bindless_slab_resource_limit) - .build( - 1, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - sampler(SamplerBindingType::NonFiltering) - .count(bindless_slab_resource_limit) - .build( - 2, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - sampler(SamplerBindingType::Comparison) - .count(bindless_slab_resource_limit) - .build( - 3, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - texture_1d(TextureSampleType::Float { filterable: true }) - .count(bindless_slab_resource_limit) - .build( - 4, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - texture_2d(TextureSampleType::Float { filterable: true }) - .count(bindless_slab_resource_limit) - .build( - 5, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - texture_2d_array(TextureSampleType::Float { filterable: true }) - .count(bindless_slab_resource_limit) - .build( - 6, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - texture_3d(TextureSampleType::Float { filterable: true }) - .count(bindless_slab_resource_limit) - .build( - 7, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - texture_cube(TextureSampleType::Float { filterable: true }) - .count(bindless_slab_resource_limit) - .build( - 8, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - texture_cube_array(TextureSampleType::Float { filterable: true }) - .count(bindless_slab_resource_limit) - .build( - 9, - ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE, - ), - ] -} - -impl BindlessSlabResourceLimit { - /// Determines the actual bindless slab resource limit on this platform. - pub fn resolve(&self) -> u32 { - match *self { - BindlessSlabResourceLimit::Auto => AUTO_BINDLESS_SLAB_RESOURCE_LIMIT, - BindlessSlabResourceLimit::Custom(limit) => limit, - } - } -} - -impl BindlessResourceType { - /// Returns the binding number for the common array of this resource type. - /// - /// For example, if you pass `BindlessResourceType::Texture2d`, this will - /// return 5, in order to match the `@group(2) @binding(5) var - /// bindless_textures_2d: binding_array>` declaration in - /// `bindless.wgsl`. - /// - /// Not all resource types have fixed binding numbers. If you call - /// [`Self::binding_number`] on such a resource type, it returns `None`. - /// - /// Note that this returns a static reference to the binding number, not the - /// binding number itself. This is to conform to an idiosyncratic API in - /// `wgpu` whereby binding numbers for binding arrays are taken by `&u32` - /// *reference*, not by `u32` value. - pub fn binding_number(&self) -> Option<&'static BindingNumber> { - match BINDING_NUMBERS.binary_search_by_key(self, |(key, _)| *key) { - Ok(binding_number) => Some(&BINDING_NUMBERS[binding_number].1), - Err(_) => None, - } - } -} - -impl From for BindlessResourceType { - fn from(texture_view_dimension: TextureViewDimension) -> Self { - match texture_view_dimension { - TextureViewDimension::D1 => BindlessResourceType::Texture1d, - TextureViewDimension::D2 => BindlessResourceType::Texture2d, - TextureViewDimension::D2Array => BindlessResourceType::Texture2dArray, - TextureViewDimension::Cube => BindlessResourceType::TextureCube, - TextureViewDimension::CubeArray => BindlessResourceType::TextureCubeArray, - TextureViewDimension::D3 => BindlessResourceType::Texture3d, - } - } -} - -impl From for BindlessResourceType { - fn from(sampler_binding_type: SamplerBindingType) -> Self { - match sampler_binding_type { - SamplerBindingType::Filtering => BindlessResourceType::SamplerFiltering, - SamplerBindingType::NonFiltering => BindlessResourceType::SamplerNonFiltering, - SamplerBindingType::Comparison => BindlessResourceType::SamplerComparison, - } - } -} - -impl From for BindlessIndex { - fn from(value: u32) -> Self { - Self(value) - } -} - -impl From for BindingNumber { - fn from(value: u32) -> Self { - Self(value) - } -} diff --git a/third_party/bevy_render/src/render_resource/buffer.rs b/third_party/bevy_render/src/render_resource/buffer.rs deleted file mode 100644 index 2a3620b..0000000 --- a/third_party/bevy_render/src/render_resource/buffer.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::define_atomic_id; -use crate::renderer::WgpuWrapper; -use core::ops::{Deref, RangeBounds}; - -define_atomic_id!(BufferId); - -#[derive(Clone, Debug)] -pub struct Buffer { - id: BufferId, - value: WgpuWrapper, -} - -impl Buffer { - #[inline] - pub fn id(&self) -> BufferId { - self.id - } - - pub fn slice(&self, bounds: impl RangeBounds) -> BufferSlice<'_> { - BufferSlice { - id: self.id, - value: self.value.slice(bounds), - } - } - - #[inline] - pub fn unmap(&self) { - self.value.unmap(); - } -} - -impl From for Buffer { - fn from(value: wgpu::Buffer) -> Self { - Buffer { - id: BufferId::new(), - value: WgpuWrapper::new(value), - } - } -} - -impl Deref for Buffer { - type Target = wgpu::Buffer; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} - -#[derive(Clone, Debug)] -pub struct BufferSlice<'a> { - id: BufferId, - value: wgpu::BufferSlice<'a>, -} - -impl<'a> BufferSlice<'a> { - #[inline] - pub fn id(&self) -> BufferId { - self.id - } -} - -impl<'a> Deref for BufferSlice<'a> { - type Target = wgpu::BufferSlice<'a>; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} diff --git a/third_party/bevy_render/src/render_resource/buffer_vec.rs b/third_party/bevy_render/src/render_resource/buffer_vec.rs deleted file mode 100644 index acd6a15..0000000 --- a/third_party/bevy_render/src/render_resource/buffer_vec.rs +++ /dev/null @@ -1,587 +0,0 @@ -use core::{iter, marker::PhantomData}; - -use crate::{ - render_resource::Buffer, - renderer::{RenderDevice, RenderQueue}, -}; -use bytemuck::{must_cast_slice, NoUninit}; -use encase::{ - internal::{WriteInto, Writer}, - ShaderType, -}; -use thiserror::Error; -use wgpu::{BindingResource, BufferAddress, BufferUsages}; - -use super::GpuArrayBufferable; - -/// A structure for storing raw bytes that have already been properly formatted -/// for use by the GPU. -/// -/// "Properly formatted" means that item data already meets the alignment and padding -/// requirements for how it will be used on the GPU. The item type must implement [`NoUninit`] -/// for its data representation to be directly copyable. -/// -/// Index, vertex, and instance-rate vertex buffers have no alignment nor padding requirements and -/// so this helper type is a good choice for them. -/// -/// The contained data is stored in system RAM. Calling [`reserve`](RawBufferVec::reserve) -/// allocates VRAM from the [`RenderDevice`]. -/// [`write_buffer`](RawBufferVec::write_buffer) queues copying of the data -/// from system RAM to VRAM. -/// -/// Other options for storing GPU-accessible data are: -/// * [`BufferVec`] -/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) -/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) -/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) -/// * [`StorageBuffer`](crate::render_resource::StorageBuffer) -/// * [`Texture`](crate::render_resource::Texture) -/// * [`UniformBuffer`](crate::render_resource::UniformBuffer) -pub struct RawBufferVec { - values: Vec, - buffer: Option, - capacity: usize, - item_size: usize, - buffer_usage: BufferUsages, - label: Option, - changed: bool, -} - -impl RawBufferVec { - /// Creates a new [`RawBufferVec`] with the given [`BufferUsages`]. - pub const fn new(buffer_usage: BufferUsages) -> Self { - Self { - values: Vec::new(), - buffer: None, - capacity: 0, - item_size: size_of::(), - buffer_usage, - label: None, - changed: false, - } - } - - /// Returns a handle to the buffer, if the data has been uploaded. - #[inline] - pub fn buffer(&self) -> Option<&Buffer> { - self.buffer.as_ref() - } - - /// Returns the binding for the buffer if the data has been uploaded. - #[inline] - pub fn binding(&self) -> Option> { - Some(BindingResource::Buffer( - self.buffer()?.as_entire_buffer_binding(), - )) - } - - /// Returns the amount of space that the GPU will use before reallocating. - #[inline] - pub fn capacity(&self) -> usize { - self.capacity - } - - /// Returns the number of items that have been pushed to this buffer. - #[inline] - pub fn len(&self) -> usize { - self.values.len() - } - - /// Returns true if the buffer is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.values.is_empty() - } - - /// Adds a new value and returns its index. - pub fn push(&mut self, value: T) -> usize { - let index = self.values.len(); - self.values.push(value); - index - } - - pub fn append(&mut self, other: &mut RawBufferVec) { - self.values.append(&mut other.values); - } - - /// Returns the value at the given index. - pub fn get(&self, index: u32) -> Option<&T> { - self.values.get(index as usize) - } - - /// Sets the value at the given index. - /// - /// The index must be less than [`RawBufferVec::len`]. - pub fn set(&mut self, index: u32, value: T) { - self.values[index as usize] = value; - } - - /// Preallocates space for `count` elements in the internal CPU-side buffer. - /// - /// Unlike [`RawBufferVec::reserve`], this doesn't have any effect on the GPU buffer. - pub fn reserve_internal(&mut self, count: usize) { - self.values.reserve(count); - } - - /// Changes the debugging label of the buffer. - /// - /// The next time the buffer is updated (via [`reserve`](Self::reserve)), Bevy will inform - /// the driver of the new label. - pub fn set_label(&mut self, label: Option<&str>) { - let label = label.map(str::to_string); - - if label != self.label { - self.changed = true; - } - - self.label = label; - } - - /// Returns the label - pub fn get_label(&self) -> Option<&str> { - self.label.as_deref() - } - - /// Creates a [`Buffer`] on the [`RenderDevice`] with size - /// at least `size_of::() * capacity`, unless a such a buffer already exists. - /// - /// If a [`Buffer`] exists, but is too small, references to it will be discarded, - /// and a new [`Buffer`] will be created. Any previously created [`Buffer`]s - /// that are no longer referenced will be deleted by the [`RenderDevice`] - /// once it is done using them (typically 1-2 frames). - /// - /// In addition to any [`BufferUsages`] provided when - /// the `RawBufferVec` was created, the buffer on the [`RenderDevice`] - /// is marked as [`BufferUsages::COPY_DST`](BufferUsages). - pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) { - let size = self.item_size * capacity; - if capacity > self.capacity || (self.changed && size > 0) { - self.capacity = capacity; - self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor { - label: self.label.as_deref(), - size: size as BufferAddress, - usage: BufferUsages::COPY_DST | self.buffer_usage, - mapped_at_creation: false, - })); - self.changed = false; - } - } - - /// Queues writing of data from system RAM to VRAM using the [`RenderDevice`] - /// and the provided [`RenderQueue`]. - /// - /// Before queuing the write, a [`reserve`](RawBufferVec::reserve) operation - /// is executed. - pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { - if self.values.is_empty() { - return; - } - self.reserve(self.values.len(), device); - if let Some(buffer) = &self.buffer { - let range = 0..self.item_size * self.values.len(); - let bytes: &[u8] = must_cast_slice(&self.values); - queue.write_buffer(buffer, 0, &bytes[range]); - } - } - - /// Queues writing of data from system RAM to VRAM using the [`RenderDevice`] - /// and the provided [`RenderQueue`]. - /// - /// If the buffer is not initialized on the GPU or the range is bigger than the capacity it will - /// return an error. You'll need to either reserve a new buffer which will lose data on the GPU - /// or create a new buffer and copy the old data to it. - /// - /// This will only write the data contained in the given range. It is useful if you only want - /// to update a part of the buffer. - pub fn write_buffer_range( - &mut self, - render_queue: &RenderQueue, - range: core::ops::Range, - ) -> Result<(), WriteBufferRangeError> { - if self.values.is_empty() { - return Err(WriteBufferRangeError::NoValuesToUpload); - } - if range.end > self.item_size * self.capacity { - return Err(WriteBufferRangeError::RangeBiggerThanBuffer); - } - if let Some(buffer) = &self.buffer { - // Cast only the bytes we need to write - let bytes: &[u8] = must_cast_slice(&self.values[range.start..range.end]); - render_queue.write_buffer(buffer, (range.start * self.item_size) as u64, bytes); - Ok(()) - } else { - Err(WriteBufferRangeError::BufferNotInitialized) - } - } - - /// Reduces the length of the buffer. - pub fn truncate(&mut self, len: usize) { - self.values.truncate(len); - } - - /// Removes all elements from the buffer. - pub fn clear(&mut self) { - self.values.clear(); - } - - /// Removes and returns the last element in the buffer. - pub fn pop(&mut self) -> Option { - self.values.pop() - } - - pub fn values(&self) -> &Vec { - &self.values - } - - pub fn values_mut(&mut self) -> &mut Vec { - &mut self.values - } -} - -impl RawBufferVec -where - T: NoUninit + Default, -{ - pub fn grow_set(&mut self, index: u32, value: T) { - while index as usize + 1 > self.len() { - self.values.push(T::default()); - } - self.values[index as usize] = value; - } -} - -impl Extend for RawBufferVec { - #[inline] - fn extend>(&mut self, iter: I) { - self.values.extend(iter); - } -} - -/// Like [`RawBufferVec`], but doesn't require that the data type `T` be -/// [`NoUninit`]. -/// -/// This is a high-performance data structure that you should use whenever -/// possible if your data is more complex than is suitable for [`RawBufferVec`]. -/// The [`ShaderType`] trait from the `encase` library is used to ensure that -/// the data is correctly aligned for use by the GPU. -/// -/// For performance reasons, unlike [`RawBufferVec`], this type doesn't allow -/// CPU access to the data after it's been added via [`BufferVec::push`]. If you -/// need CPU access to the data, consider another type, such as -/// [`StorageBuffer`][super::StorageBuffer]. -/// -/// Other options for storing GPU-accessible data are: -/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) -/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) -/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) -/// * [`RawBufferVec`] -/// * [`StorageBuffer`](crate::render_resource::StorageBuffer) -/// * [`Texture`](crate::render_resource::Texture) -/// * [`UniformBuffer`](crate::render_resource::UniformBuffer) -pub struct BufferVec -where - T: ShaderType + WriteInto, -{ - data: Vec, - buffer: Option, - capacity: usize, - buffer_usage: BufferUsages, - label: Option, - label_changed: bool, - phantom: PhantomData, -} - -impl BufferVec -where - T: ShaderType + WriteInto, -{ - /// Creates a new [`BufferVec`] with the given [`BufferUsages`]. - pub const fn new(buffer_usage: BufferUsages) -> Self { - Self { - data: vec![], - buffer: None, - capacity: 0, - buffer_usage, - label: None, - label_changed: false, - phantom: PhantomData, - } - } - - /// Returns a handle to the buffer, if the data has been uploaded. - #[inline] - pub fn buffer(&self) -> Option<&Buffer> { - self.buffer.as_ref() - } - - /// Returns the binding for the buffer if the data has been uploaded. - #[inline] - pub fn binding(&self) -> Option> { - Some(BindingResource::Buffer( - self.buffer()?.as_entire_buffer_binding(), - )) - } - - /// Returns the amount of space that the GPU will use before reallocating. - #[inline] - pub fn capacity(&self) -> usize { - self.capacity - } - - /// Returns the number of items that have been pushed to this buffer. - #[inline] - pub fn len(&self) -> usize { - self.data.len() / u64::from(T::min_size()) as usize - } - - /// Returns true if the buffer is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.data.is_empty() - } - - /// Adds a new value and returns its index. - pub fn push(&mut self, value: T) -> usize { - let element_size = u64::from(T::min_size()) as usize; - let offset = self.data.len(); - - // TODO: Consider using unsafe code to push uninitialized, to prevent - // the zeroing. It shows up in profiles. - self.data.extend(iter::repeat_n(0, element_size)); - - // Take a slice of the new data for `write_into` to use. This is - // important: it hoists the bounds check up here so that the compiler - // can eliminate all the bounds checks that `write_into` will emit. - let mut dest = &mut self.data[offset..(offset + element_size)]; - value.write_into(&mut Writer::new(&value, &mut dest, 0).unwrap()); - - offset / u64::from(T::min_size()) as usize - } - - /// Changes the debugging label of the buffer. - /// - /// The next time the buffer is updated (via [`Self::reserve`]), Bevy will inform - /// the driver of the new label. - pub fn set_label(&mut self, label: Option<&str>) { - let label = label.map(str::to_string); - - if label != self.label { - self.label_changed = true; - } - - self.label = label; - } - - /// Returns the label - pub fn get_label(&self) -> Option<&str> { - self.label.as_deref() - } - - /// Creates a [`Buffer`] on the [`RenderDevice`] with size - /// at least `size_of::() * capacity`, unless such a buffer already exists. - /// - /// If a [`Buffer`] exists, but is too small, references to it will be discarded, - /// and a new [`Buffer`] will be created. Any previously created [`Buffer`]s - /// that are no longer referenced will be deleted by the [`RenderDevice`] - /// once it is done using them (typically 1-2 frames). - /// - /// In addition to any [`BufferUsages`] provided when - /// the `BufferVec` was created, the buffer on the [`RenderDevice`] - /// is marked as [`BufferUsages::COPY_DST`](BufferUsages). - pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) { - if capacity <= self.capacity && !self.label_changed { - return; - } - - self.capacity = capacity; - let size = u64::from(T::min_size()) as usize * capacity; - self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor { - label: self.label.as_deref(), - size: size as BufferAddress, - usage: BufferUsages::COPY_DST | self.buffer_usage, - mapped_at_creation: false, - })); - self.label_changed = false; - } - - /// Queues writing of data from system RAM to VRAM using the [`RenderDevice`] - /// and the provided [`RenderQueue`]. - /// - /// Before queuing the write, a [`reserve`](BufferVec::reserve) operation is - /// executed. - pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { - if self.data.is_empty() { - return; - } - - self.reserve(self.data.len() / u64::from(T::min_size()) as usize, device); - - let Some(buffer) = &self.buffer else { return }; - queue.write_buffer(buffer, 0, &self.data); - } - - /// Queues writing of data from system RAM to VRAM using the [`RenderDevice`] - /// and the provided [`RenderQueue`]. - /// - /// If the buffer is not initialized on the GPU or the range is bigger than the capacity it will - /// return an error. You'll need to either reserve a new buffer which will lose data on the GPU - /// or create a new buffer and copy the old data to it. - /// - /// This will only write the data contained in the given range. It is useful if you only want - /// to update a part of the buffer. - pub fn write_buffer_range( - &mut self, - render_queue: &RenderQueue, - range: core::ops::Range, - ) -> Result<(), WriteBufferRangeError> { - if self.data.is_empty() { - return Err(WriteBufferRangeError::NoValuesToUpload); - } - let item_size = u64::from(T::min_size()) as usize; - if range.end > item_size * self.capacity { - return Err(WriteBufferRangeError::RangeBiggerThanBuffer); - } - if let Some(buffer) = &self.buffer { - let bytes = &self.data[range.start..range.end]; - render_queue.write_buffer(buffer, (range.start * item_size) as u64, bytes); - Ok(()) - } else { - Err(WriteBufferRangeError::BufferNotInitialized) - } - } - - /// Reduces the length of the buffer. - pub fn truncate(&mut self, len: usize) { - self.data.truncate(u64::from(T::min_size()) as usize * len); - } - - /// Removes all elements from the buffer. - pub fn clear(&mut self) { - self.data.clear(); - } -} - -/// Like a [`BufferVec`], but only reserves space on the GPU for elements -/// instead of initializing them CPU-side. -/// -/// This type is useful when you're accumulating "output slots" for a GPU -/// compute shader to write into. -/// -/// The type `T` need not be [`NoUninit`], unlike [`RawBufferVec`]; it only has to -/// be [`GpuArrayBufferable`]. -pub struct UninitBufferVec -where - T: GpuArrayBufferable, -{ - buffer: Option, - len: usize, - capacity: usize, - item_size: usize, - buffer_usage: BufferUsages, - label: Option, - label_changed: bool, - phantom: PhantomData, -} - -impl UninitBufferVec -where - T: GpuArrayBufferable, -{ - /// Creates a new [`UninitBufferVec`] with the given [`BufferUsages`]. - pub const fn new(buffer_usage: BufferUsages) -> Self { - Self { - len: 0, - buffer: None, - capacity: 0, - item_size: size_of::(), - buffer_usage, - label: None, - label_changed: false, - phantom: PhantomData, - } - } - - /// Returns the buffer, if allocated. - #[inline] - pub fn buffer(&self) -> Option<&Buffer> { - self.buffer.as_ref() - } - - /// Returns the binding for the buffer if the data has been uploaded. - #[inline] - pub fn binding(&self) -> Option> { - Some(BindingResource::Buffer( - self.buffer()?.as_entire_buffer_binding(), - )) - } - - /// Reserves space for one more element in the buffer and returns its index. - pub fn add(&mut self) -> usize { - self.add_multiple(1) - } - - /// Reserves space for the given number of elements in the buffer and - /// returns the index of the first one. - pub fn add_multiple(&mut self, count: usize) -> usize { - let index = self.len; - self.len += count; - index - } - - /// Returns true if no elements have been added to this [`UninitBufferVec`]. - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - /// Removes all elements from the buffer. - pub fn clear(&mut self) { - self.len = 0; - } - - /// Returns the length of the buffer. - pub fn len(&self) -> usize { - self.len - } - - /// Materializes the buffer on the GPU with space for `capacity` elements. - /// - /// If the buffer is already big enough, this function doesn't reallocate - /// the buffer. - pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) { - if capacity <= self.capacity && !self.label_changed { - return; - } - - self.capacity = capacity; - let size = self.item_size * capacity; - self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor { - label: self.label.as_deref(), - size: size as BufferAddress, - usage: BufferUsages::COPY_DST | self.buffer_usage, - mapped_at_creation: false, - })); - - self.label_changed = false; - } - - /// Materializes the buffer on the GPU, with an appropriate size for the - /// elements that have been pushed so far. - pub fn write_buffer(&mut self, device: &RenderDevice) { - if !self.is_empty() { - self.reserve(self.len, device); - } - } -} - -/// Error returned when `write_buffer_range` fails -/// -/// See [`RawBufferVec::write_buffer_range`] [`BufferVec::write_buffer_range`] -#[derive(Debug, Eq, PartialEq, Copy, Clone, Error)] -pub enum WriteBufferRangeError { - #[error("the range is bigger than the capacity of the buffer")] - RangeBiggerThanBuffer, - #[error("the gpu buffer is not initialized")] - BufferNotInitialized, - #[error("there are no values to upload")] - NoValuesToUpload, -} diff --git a/third_party/bevy_render/src/render_resource/gpu_array_buffer.rs b/third_party/bevy_render/src/render_resource/gpu_array_buffer.rs deleted file mode 100644 index c999033..0000000 --- a/third_party/bevy_render/src/render_resource/gpu_array_buffer.rs +++ /dev/null @@ -1,116 +0,0 @@ -use super::{ - binding_types::{storage_buffer_read_only, uniform_buffer_sized}, - BindGroupLayoutEntryBuilder, BufferVec, -}; -use crate::{ - render_resource::batched_uniform_buffer::BatchedUniformBuffer, - renderer::{RenderDevice, RenderQueue}, -}; -use bevy_ecs::{prelude::Component, resource::Resource}; -use core::marker::PhantomData; -use encase::{private::WriteInto, ShaderSize, ShaderType}; -use nonmax::NonMaxU32; -use wgpu::{BindingResource, BufferUsages, Limits}; - -/// Trait for types able to go in a [`GpuArrayBuffer`]. -pub trait GpuArrayBufferable: ShaderType + ShaderSize + WriteInto + Clone {} - -impl GpuArrayBufferable for T {} - -/// Stores an array of elements to be transferred to the GPU and made accessible to shaders as a read-only array. -/// -/// On platforms that support storage buffers, this is equivalent to -/// [`BufferVec`]. Otherwise, this falls back to a dynamic offset -/// uniform buffer with the largest array of T that fits within a uniform buffer -/// binding (within reasonable limits). -/// -/// Other options for storing GPU-accessible data are: -/// * [`BufferVec`] -/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) -/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) -/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) -/// * [`StorageBuffer`](crate::render_resource::StorageBuffer) -/// * [`Texture`](crate::render_resource::Texture) -/// * [`UniformBuffer`](crate::render_resource::UniformBuffer) -#[derive(Resource)] -pub enum GpuArrayBuffer { - Uniform(BatchedUniformBuffer), - Storage(BufferVec), -} - -impl GpuArrayBuffer { - pub fn new(limits: &Limits) -> Self { - if limits.max_storage_buffers_per_shader_stage == 0 { - GpuArrayBuffer::Uniform(BatchedUniformBuffer::new(limits)) - } else { - GpuArrayBuffer::Storage(BufferVec::new(BufferUsages::STORAGE)) - } - } - - pub fn clear(&mut self) { - match self { - GpuArrayBuffer::Uniform(buffer) => buffer.clear(), - GpuArrayBuffer::Storage(buffer) => buffer.clear(), - } - } - - pub fn push(&mut self, value: T) -> GpuArrayBufferIndex { - match self { - GpuArrayBuffer::Uniform(buffer) => buffer.push(value), - GpuArrayBuffer::Storage(buffer) => { - let index = buffer.push(value) as u32; - GpuArrayBufferIndex { - index, - dynamic_offset: None, - element_type: PhantomData, - } - } - } - } - - pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { - match self { - GpuArrayBuffer::Uniform(buffer) => buffer.write_buffer(device, queue), - GpuArrayBuffer::Storage(buffer) => buffer.write_buffer(device, queue), - } - } - - pub fn binding_layout(limits: &Limits) -> BindGroupLayoutEntryBuilder { - if limits.max_storage_buffers_per_shader_stage == 0 { - uniform_buffer_sized( - true, - // BatchedUniformBuffer uses a MaxCapacityArray that is runtime-sized, so we use - // None here and let wgpu figure out the size. - None, - ) - } else { - storage_buffer_read_only::(false) - } - } - - pub fn binding(&self) -> Option> { - match self { - GpuArrayBuffer::Uniform(buffer) => buffer.binding(), - GpuArrayBuffer::Storage(buffer) => buffer.binding(), - } - } - - pub fn batch_size(limits: &Limits) -> Option { - if limits.max_storage_buffers_per_shader_stage == 0 { - Some(BatchedUniformBuffer::::batch_size(limits) as u32) - } else { - None - } - } -} - -/// An index into a [`GpuArrayBuffer`] for a given element. -#[derive(Component, Clone)] -pub struct GpuArrayBufferIndex { - /// The index to use in a shader into the array. - pub index: u32, - /// The dynamic offset to use when setting the bind group in a pass. - /// Only used on platforms that don't support storage buffers. - pub dynamic_offset: Option, - pub element_type: PhantomData, -} diff --git a/third_party/bevy_render/src/render_resource/mod.rs b/third_party/bevy_render/src/render_resource/mod.rs deleted file mode 100644 index 0d431b5..0000000 --- a/third_party/bevy_render/src/render_resource/mod.rs +++ /dev/null @@ -1,74 +0,0 @@ -mod batched_uniform_buffer; -mod bind_group; -mod bind_group_entries; -mod bind_group_layout; -mod bind_group_layout_entries; -mod bindless; -mod buffer; -mod buffer_vec; -mod gpu_array_buffer; -mod pipeline; -mod pipeline_cache; -mod pipeline_specializer; -pub mod resource_macros; -mod specializer; -mod storage_buffer; -mod texture; -mod uniform_buffer; - -pub use bind_group::*; -pub use bind_group_entries::*; -pub use bind_group_layout::*; -pub use bind_group_layout_entries::*; -pub use bindless::*; -pub use buffer::*; -pub use buffer_vec::*; -pub use gpu_array_buffer::*; -pub use pipeline::*; -pub use pipeline_cache::*; -pub use pipeline_specializer::*; -pub use specializer::*; -pub use storage_buffer::*; -pub use texture::*; -pub use uniform_buffer::*; - -// TODO: decide where re-exports should go -pub use wgpu::{ - util::{ - BufferInitDescriptor, DispatchIndirectArgs, DrawIndexedIndirectArgs, DrawIndirectArgs, - TextureDataOrder, - }, - AccelerationStructureFlags, AccelerationStructureGeometryFlags, - AccelerationStructureUpdateMode, AdapterInfo as WgpuAdapterInfo, AddressMode, AstcBlock, - AstcChannel, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutEntry, BindingResource, - BindingType, Blas, BlasBuildEntry, BlasGeometries, BlasGeometrySizeDescriptors, - BlasTriangleGeometry, BlasTriangleGeometrySizeDescriptor, BlendComponent, BlendFactor, - BlendOperation, BlendState, BufferAddress, BufferAsyncError, BufferBinding, BufferBindingType, - BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites, CommandEncoder, - CommandEncoderDescriptor, CompareFunction, ComputePass, ComputePassDescriptor, - ComputePipelineDescriptor as RawComputePipelineDescriptor, CreateBlasDescriptor, - CreateTlasDescriptor, DepthBiasState, DepthStencilState, DownlevelFlags, Extent3d, Face, - Features as WgpuFeatures, FilterMode, FragmentState as RawFragmentState, FrontFace, - ImageSubresourceRange, IndexFormat, Limits as WgpuLimits, LoadOp, MapMode, MultisampleState, - Operations, Origin3d, PipelineCompilationOptions, PipelineLayout, PipelineLayoutDescriptor, - PollType, PolygonMode, PrimitiveState, PrimitiveTopology, PushConstantRange, - RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, - RenderPipelineDescriptor as RawRenderPipelineDescriptor, Sampler as WgpuSampler, - SamplerBindingType, SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, ShaderSource, - ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, StoreOp, - TexelCopyBufferInfo, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, - TextureDescriptor, TextureDimension, TextureFormat, TextureFormatFeatureFlags, - TextureFormatFeatures, TextureSampleType, TextureUsages, TextureView as WgpuTextureView, - TextureViewDescriptor, TextureViewDimension, Tlas, TlasInstance, VertexAttribute, - VertexBufferLayout as RawVertexBufferLayout, VertexFormat, VertexState as RawVertexState, - VertexStepMode, COPY_BUFFER_ALIGNMENT, -}; - -pub mod encase { - pub use bevy_encase_derive::ShaderType; - pub use encase::*; -} - -pub use self::encase::{ShaderSize, ShaderType}; - -pub use naga::ShaderStage; diff --git a/third_party/bevy_render/src/render_resource/pipeline.rs b/third_party/bevy_render/src/render_resource/pipeline.rs deleted file mode 100644 index f3976f6..0000000 --- a/third_party/bevy_render/src/render_resource/pipeline.rs +++ /dev/null @@ -1,199 +0,0 @@ -use crate::define_atomic_id; -use crate::renderer::WgpuWrapper; -use alloc::borrow::Cow; -use bevy_asset::Handle; -use bevy_mesh::VertexBufferLayout; -use bevy_shader::{Shader, ShaderDefVal}; -use core::iter; -use core::ops::Deref; -use thiserror::Error; -use wgpu::{ - BindGroupLayoutEntry, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, - PushConstantRange, -}; - -define_atomic_id!(RenderPipelineId); - -/// A [`RenderPipeline`] represents a graphics pipeline and its stages (shaders), bindings and vertex buffers. -/// -/// May be converted from and dereferences to a wgpu [`RenderPipeline`](wgpu::RenderPipeline). -/// Can be created via [`RenderDevice::create_render_pipeline`](crate::renderer::RenderDevice::create_render_pipeline). -#[derive(Clone, Debug)] -pub struct RenderPipeline { - id: RenderPipelineId, - value: WgpuWrapper, -} - -impl RenderPipeline { - #[inline] - pub fn id(&self) -> RenderPipelineId { - self.id - } -} - -impl From for RenderPipeline { - fn from(value: wgpu::RenderPipeline) -> Self { - RenderPipeline { - id: RenderPipelineId::new(), - value: WgpuWrapper::new(value), - } - } -} - -impl Deref for RenderPipeline { - type Target = wgpu::RenderPipeline; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} - -define_atomic_id!(ComputePipelineId); - -/// A [`ComputePipeline`] represents a compute pipeline and its single shader stage. -/// -/// May be converted from and dereferences to a wgpu [`ComputePipeline`](wgpu::ComputePipeline). -/// Can be created via [`RenderDevice::create_compute_pipeline`](crate::renderer::RenderDevice::create_compute_pipeline). -#[derive(Clone, Debug)] -pub struct ComputePipeline { - id: ComputePipelineId, - value: WgpuWrapper, -} - -impl ComputePipeline { - /// Returns the [`ComputePipelineId`]. - #[inline] - pub fn id(&self) -> ComputePipelineId { - self.id - } -} - -impl From for ComputePipeline { - fn from(value: wgpu::ComputePipeline) -> Self { - ComputePipeline { - id: ComputePipelineId::new(), - value: WgpuWrapper::new(value), - } - } -} - -impl Deref for ComputePipeline { - type Target = wgpu::ComputePipeline; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] -pub struct BindGroupLayoutDescriptor { - /// Debug label of the bind group layout descriptor. This will show up in graphics debuggers for easy identification. - pub label: Cow<'static, str>, - pub entries: Vec, -} - -impl BindGroupLayoutDescriptor { - pub fn new(label: impl Into>, entries: &[BindGroupLayoutEntry]) -> Self { - Self { - label: label.into(), - entries: entries.into(), - } - } -} - -/// Describes a render (graphics) pipeline. -#[derive(Clone, Debug, PartialEq, Default)] -pub struct RenderPipelineDescriptor { - /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. - pub label: Option>, - /// The layout of bind groups for this pipeline. - pub layout: Vec, - /// The push constant ranges for this pipeline. - /// Supply an empty vector if the pipeline doesn't use push constants. - pub push_constant_ranges: Vec, - /// The compiled vertex stage, its entry point, and the input buffers layout. - pub vertex: VertexState, - /// The properties of the pipeline at the primitive assembly and rasterization level. - pub primitive: PrimitiveState, - /// The effect of draw calls on the depth and stencil aspects of the output target, if any. - pub depth_stencil: Option, - /// The multi-sampling properties of the pipeline. - pub multisample: MultisampleState, - /// The compiled fragment stage, its entry point, and the color targets. - pub fragment: Option, - /// Whether to zero-initialize workgroup memory by default. If you're not sure, set this to true. - /// If this is false, reading from workgroup variables before writing to them will result in garbage values. - pub zero_initialize_workgroup_memory: bool, -} - -#[derive(Copy, Clone, Debug, Error)] -#[error("RenderPipelineDescriptor has no FragmentState configured")] -pub struct NoFragmentStateError; - -impl RenderPipelineDescriptor { - pub fn fragment_mut(&mut self) -> Result<&mut FragmentState, NoFragmentStateError> { - self.fragment.as_mut().ok_or(NoFragmentStateError) - } - - pub fn set_layout(&mut self, index: usize, layout: BindGroupLayoutDescriptor) { - filling_set_at(&mut self.layout, index, bevy_utils::default(), layout); - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Default)] -pub struct VertexState { - /// The compiled shader module for this stage. - pub shader: Handle, - pub shader_defs: Vec, - /// The name of the entry point in the compiled shader, or `None` if the default entry point - /// is used. - pub entry_point: Option>, - /// The format of any vertex buffers used with this pipeline. - pub buffers: Vec, -} - -/// Describes the fragment process in a render pipeline. -#[derive(Clone, Debug, PartialEq, Eq, Default)] -pub struct FragmentState { - /// The compiled shader module for this stage. - pub shader: Handle, - pub shader_defs: Vec, - /// The name of the entry point in the compiled shader, or `None` if the default entry point - /// is used. - pub entry_point: Option>, - /// The color state of the render targets. - pub targets: Vec>, -} - -impl FragmentState { - pub fn set_target(&mut self, index: usize, target: ColorTargetState) { - filling_set_at(&mut self.targets, index, None, Some(target)); - } -} - -/// Describes a compute pipeline. -#[derive(Clone, Debug, PartialEq, Eq, Default)] -pub struct ComputePipelineDescriptor { - pub label: Option>, - pub layout: Vec, - pub push_constant_ranges: Vec, - /// The compiled shader module for this stage. - pub shader: Handle, - pub shader_defs: Vec, - /// The name of the entry point in the compiled shader, or `None` if the default entry point - /// is used. - pub entry_point: Option>, - /// Whether to zero-initialize workgroup memory by default. If you're not sure, set this to true. - /// If this is false, reading from workgroup variables before writing to them will result in garbage values. - pub zero_initialize_workgroup_memory: bool, -} - -// utility function to set a value at the specified index, extending with -// a filler value if the index is out of bounds. -fn filling_set_at(vec: &mut Vec, index: usize, filler: T, value: T) { - let num_to_fill = (index + 1).saturating_sub(vec.len()); - vec.extend(iter::repeat_n(filler, num_to_fill)); - vec[index] = value; -} diff --git a/third_party/bevy_render/src/render_resource/pipeline_cache.rs b/third_party/bevy_render/src/render_resource/pipeline_cache.rs deleted file mode 100644 index e8b6b29..0000000 --- a/third_party/bevy_render/src/render_resource/pipeline_cache.rs +++ /dev/null @@ -1,882 +0,0 @@ -use crate::{ - render_resource::*, - renderer::{RenderAdapter, RenderDevice, WgpuWrapper}, - Extract, -}; -use alloc::{borrow::Cow, sync::Arc}; -use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; -use bevy_ecs::{ - message::MessageReader, - resource::Resource, - system::{Res, ResMut}, -}; -use bevy_platform::collections::{HashMap, HashSet}; -use bevy_shader::{ - CachedPipelineId, PipelineCacheError, Shader, ShaderCache, ShaderCacheSource, ShaderDefVal, - ValidateShader, -}; -use bevy_tasks::Task; -use bevy_utils::default; -use core::{future::Future, hash::Hash, mem}; -use std::sync::{Mutex, PoisonError}; -use tracing::error; -use wgpu::{PipelineCompilationOptions, VertexBufferLayout as RawVertexBufferLayout}; - -/// A descriptor for a [`Pipeline`]. -/// -/// Used to store a heterogenous collection of render and compute pipeline descriptors together. -#[derive(Debug)] -pub enum PipelineDescriptor { - RenderPipelineDescriptor(Box), - ComputePipelineDescriptor(Box), -} - -/// A pipeline defining the data layout and shader logic for a specific GPU task. -/// -/// Used to store a heterogenous collection of render and compute pipelines together. -#[derive(Debug)] -pub enum Pipeline { - RenderPipeline(RenderPipeline), - ComputePipeline(ComputePipeline), -} - -/// Index of a cached render pipeline in a [`PipelineCache`]. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] -pub struct CachedRenderPipelineId(CachedPipelineId); - -impl CachedRenderPipelineId { - /// An invalid cached render pipeline index, often used to initialize a variable. - pub const INVALID: Self = CachedRenderPipelineId(usize::MAX); - - #[inline] - pub fn id(&self) -> usize { - self.0 - } -} - -/// Index of a cached compute pipeline in a [`PipelineCache`]. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub struct CachedComputePipelineId(CachedPipelineId); - -impl CachedComputePipelineId { - /// An invalid cached compute pipeline index, often used to initialize a variable. - pub const INVALID: Self = CachedComputePipelineId(usize::MAX); - - #[inline] - pub fn id(&self) -> usize { - self.0 - } -} - -pub struct CachedPipeline { - pub descriptor: PipelineDescriptor, - pub state: CachedPipelineState, -} - -/// State of a cached pipeline inserted into a [`PipelineCache`]. -#[cfg_attr( - not(target_arch = "wasm32"), - expect( - clippy::large_enum_variant, - reason = "See https://github.com/bevyengine/bevy/issues/19220" - ) -)] -#[derive(Debug)] -pub enum CachedPipelineState { - /// The pipeline GPU object is queued for creation. - Queued, - /// The pipeline GPU object is being created. - Creating(Task>), - /// The pipeline GPU object was created successfully and is available (allocated on the GPU). - Ok(Pipeline), - /// An error occurred while trying to create the pipeline GPU object. - Err(PipelineCacheError), -} - -impl CachedPipelineState { - /// Convenience method to "unwrap" a pipeline state into its underlying GPU object. - /// - /// # Returns - /// - /// The method returns the allocated pipeline GPU object. - /// - /// # Panics - /// - /// This method panics if the pipeline GPU object is not available, either because it is - /// pending creation or because an error occurred while attempting to create GPU object. - pub fn unwrap(&self) -> &Pipeline { - match self { - CachedPipelineState::Ok(pipeline) => pipeline, - CachedPipelineState::Queued => { - panic!("Pipeline has not been compiled yet. It is still in the 'Queued' state.") - } - CachedPipelineState::Creating(..) => { - panic!("Pipeline has not been compiled yet. It is still in the 'Creating' state.") - } - CachedPipelineState::Err(err) => panic!("{}", err), - } - } -} - -type LayoutCacheKey = (Vec, Vec); -#[derive(Default)] -struct LayoutCache { - layouts: HashMap>>, -} - -impl LayoutCache { - fn get( - &mut self, - render_device: &RenderDevice, - bind_group_layouts: &[BindGroupLayout], - push_constant_ranges: Vec, - ) -> Arc> { - let bind_group_ids = bind_group_layouts.iter().map(BindGroupLayout::id).collect(); - self.layouts - .entry((bind_group_ids, push_constant_ranges)) - .or_insert_with_key(|(_, push_constant_ranges)| { - let bind_group_layouts = bind_group_layouts - .iter() - .map(BindGroupLayout::value) - .collect::>(); - Arc::new(WgpuWrapper::new(render_device.create_pipeline_layout( - &PipelineLayoutDescriptor { - bind_group_layouts: &bind_group_layouts, - push_constant_ranges, - ..default() - }, - ))) - }) - .clone() - } -} - -#[expect( - clippy::result_large_err, - reason = "See https://github.com/bevyengine/bevy/issues/19220" -)] -fn load_module( - render_device: &RenderDevice, - shader_source: ShaderCacheSource, - validate_shader: &ValidateShader, -) -> Result, PipelineCacheError> { - let shader_source = match shader_source { - #[cfg(feature = "shader_format_spirv")] - ShaderCacheSource::SpirV(data) => wgpu::util::make_spirv(data), - #[cfg(not(feature = "shader_format_spirv"))] - ShaderCacheSource::SpirV(_) => { - unimplemented!("Enable feature \"shader_format_spirv\" to use SPIR-V shaders") - } - ShaderCacheSource::Wgsl(src) => ShaderSource::Wgsl(Cow::Owned(src)), - #[cfg(not(feature = "decoupled_naga"))] - ShaderCacheSource::Naga(src) => ShaderSource::Naga(Cow::Owned(src)), - }; - let module_descriptor = ShaderModuleDescriptor { - label: None, - source: shader_source, - }; - - render_device - .wgpu_device() - .push_error_scope(wgpu::ErrorFilter::Validation); - - let shader_module = WgpuWrapper::new(match validate_shader { - ValidateShader::Enabled => { - render_device.create_and_validate_shader_module(module_descriptor) - } - // SAFETY: we are interfacing with shader code, which may contain undefined behavior, - // such as indexing out of bounds. - // The checks required are prohibitively expensive and a poor default for game engines. - ValidateShader::Disabled => unsafe { - render_device.create_shader_module(module_descriptor) - }, - }); - - let error = render_device.wgpu_device().pop_error_scope(); - - // `now_or_never` will return Some if the future is ready and None otherwise. - // On native platforms, wgpu will yield the error immediately while on wasm it may take longer since the browser APIs are asynchronous. - // So to keep the complexity of the ShaderCache low, we will only catch this error early on native platforms, - // and on wasm the error will be handled by wgpu and crash the application. - if let Some(Some(wgpu::Error::Validation { description, .. })) = - bevy_tasks::futures::now_or_never(error) - { - return Err(PipelineCacheError::CreateShaderModule(description)); - } - - Ok(shader_module) -} - -#[derive(Default)] -struct BindGroupLayoutCache { - bgls: HashMap, -} - -impl BindGroupLayoutCache { - fn get( - &mut self, - render_device: &RenderDevice, - descriptor: BindGroupLayoutDescriptor, - ) -> BindGroupLayout { - self.bgls - .entry(descriptor) - .or_insert_with_key(|descriptor| { - render_device - .create_bind_group_layout(descriptor.label.as_ref(), &descriptor.entries) - }) - .clone() - } -} - -/// Cache for render and compute pipelines. -/// -/// The cache stores existing render and compute pipelines allocated on the GPU, as well as -/// pending creation. Pipelines inserted into the cache are identified by a unique ID, which -/// can be used to retrieve the actual GPU object once it's ready. The creation of the GPU -/// pipeline object is deferred to the [`RenderSystems::Render`] step, just before the render -/// graph starts being processed, as this requires access to the GPU. -/// -/// Note that the cache does not perform automatic deduplication of identical pipelines. It is -/// up to the user not to insert the same pipeline twice to avoid wasting GPU resources. -/// -/// [`RenderSystems::Render`]: crate::RenderSystems::Render -#[derive(Resource)] -pub struct PipelineCache { - layout_cache: Arc>, - bindgroup_layout_cache: Arc>, - shader_cache: Arc, RenderDevice>>>, - device: RenderDevice, - pipelines: Vec, - waiting_pipelines: HashSet, - new_pipelines: Mutex>, - global_shader_defs: Vec, - /// If `true`, disables asynchronous pipeline compilation. - /// This has no effect on macOS, wasm, or without the `multi_threaded` feature. - synchronous_pipeline_compilation: bool, -} - -impl PipelineCache { - /// Returns an iterator over the pipelines in the pipeline cache. - pub fn pipelines(&self) -> impl Iterator { - self.pipelines.iter() - } - - /// Returns a iterator of the IDs of all currently waiting pipelines. - pub fn waiting_pipelines(&self) -> impl Iterator + '_ { - self.waiting_pipelines.iter().copied() - } - - /// Create a new pipeline cache associated with the given render device. - pub fn new( - device: RenderDevice, - render_adapter: RenderAdapter, - synchronous_pipeline_compilation: bool, - ) -> Self { - let mut global_shader_defs = Vec::new(); - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - { - global_shader_defs.push("NO_ARRAY_TEXTURES_SUPPORT".into()); - global_shader_defs.push("NO_CUBE_ARRAY_TEXTURES_SUPPORT".into()); - global_shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into()); - } - - if cfg!(target_abi = "sim") { - global_shader_defs.push("NO_CUBE_ARRAY_TEXTURES_SUPPORT".into()); - } - - global_shader_defs.push(ShaderDefVal::UInt( - String::from("AVAILABLE_STORAGE_BUFFER_BINDINGS"), - device.limits().max_storage_buffers_per_shader_stage, - )); - - Self { - shader_cache: Arc::new(Mutex::new(ShaderCache::new( - device.features(), - render_adapter.get_downlevel_capabilities().flags, - load_module, - ))), - device, - layout_cache: default(), - bindgroup_layout_cache: default(), - waiting_pipelines: default(), - new_pipelines: default(), - pipelines: default(), - global_shader_defs, - synchronous_pipeline_compilation, - } - } - - /// Get the state of a cached render pipeline. - /// - /// See [`PipelineCache::queue_render_pipeline()`]. - #[inline] - pub fn get_render_pipeline_state(&self, id: CachedRenderPipelineId) -> &CachedPipelineState { - // If the pipeline id isn't in `pipelines`, it's queued in `new_pipelines` - self.pipelines - .get(id.0) - .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state) - } - - /// Get the state of a cached compute pipeline. - /// - /// See [`PipelineCache::queue_compute_pipeline()`]. - #[inline] - pub fn get_compute_pipeline_state(&self, id: CachedComputePipelineId) -> &CachedPipelineState { - // If the pipeline id isn't in `pipelines`, it's queued in `new_pipelines` - self.pipelines - .get(id.0) - .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state) - } - - /// Get the render pipeline descriptor a cached render pipeline was inserted from. - /// - /// See [`PipelineCache::queue_render_pipeline()`]. - /// - /// **Note**: Be careful calling this method. It will panic if called with a pipeline that - /// has been queued but has not yet been processed by [`PipelineCache::process_queue()`]. - #[inline] - pub fn get_render_pipeline_descriptor( - &self, - id: CachedRenderPipelineId, - ) -> &RenderPipelineDescriptor { - match &self.pipelines[id.0].descriptor { - PipelineDescriptor::RenderPipelineDescriptor(descriptor) => descriptor, - PipelineDescriptor::ComputePipelineDescriptor(_) => unreachable!(), - } - } - - /// Get the compute pipeline descriptor a cached render pipeline was inserted from. - /// - /// See [`PipelineCache::queue_compute_pipeline()`]. - /// - /// **Note**: Be careful calling this method. It will panic if called with a pipeline that - /// has been queued but has not yet been processed by [`PipelineCache::process_queue()`]. - #[inline] - pub fn get_compute_pipeline_descriptor( - &self, - id: CachedComputePipelineId, - ) -> &ComputePipelineDescriptor { - match &self.pipelines[id.0].descriptor { - PipelineDescriptor::RenderPipelineDescriptor(_) => unreachable!(), - PipelineDescriptor::ComputePipelineDescriptor(descriptor) => descriptor, - } - } - - /// Try to retrieve a render pipeline GPU object from a cached ID. - /// - /// # Returns - /// - /// This method returns a successfully created render pipeline if any, or `None` if the pipeline - /// was not created yet or if there was an error during creation. You can check the actual creation - /// state with [`PipelineCache::get_render_pipeline_state()`]. - #[inline] - pub fn get_render_pipeline(&self, id: CachedRenderPipelineId) -> Option<&RenderPipeline> { - if let CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) = - &self.pipelines.get(id.0)?.state - { - Some(pipeline) - } else { - None - } - } - - /// Wait for a render pipeline to finish compiling. - #[inline] - pub fn block_on_render_pipeline(&mut self, id: CachedRenderPipelineId) { - if self.pipelines.len() <= id.0 { - self.process_queue(); - } - - let state = &mut self.pipelines[id.0].state; - if let CachedPipelineState::Creating(task) = state { - *state = match bevy_tasks::block_on(task) { - Ok(p) => CachedPipelineState::Ok(p), - Err(e) => CachedPipelineState::Err(e), - }; - } - } - - /// Try to retrieve a compute pipeline GPU object from a cached ID. - /// - /// # Returns - /// - /// This method returns a successfully created compute pipeline if any, or `None` if the pipeline - /// was not created yet or if there was an error during creation. You can check the actual creation - /// state with [`PipelineCache::get_compute_pipeline_state()`]. - #[inline] - pub fn get_compute_pipeline(&self, id: CachedComputePipelineId) -> Option<&ComputePipeline> { - if let CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) = - &self.pipelines.get(id.0)?.state - { - Some(pipeline) - } else { - None - } - } - - /// Insert a render pipeline into the cache, and queue its creation. - /// - /// The pipeline is always inserted and queued for creation. There is no attempt to deduplicate it with - /// an already cached pipeline. - /// - /// # Returns - /// - /// This method returns the unique render shader ID of the cached pipeline, which can be used to query - /// the caching state with [`get_render_pipeline_state()`] and to retrieve the created GPU pipeline once - /// it's ready with [`get_render_pipeline()`]. - /// - /// [`get_render_pipeline_state()`]: PipelineCache::get_render_pipeline_state - /// [`get_render_pipeline()`]: PipelineCache::get_render_pipeline - pub fn queue_render_pipeline( - &self, - descriptor: RenderPipelineDescriptor, - ) -> CachedRenderPipelineId { - let mut new_pipelines = self - .new_pipelines - .lock() - .unwrap_or_else(PoisonError::into_inner); - let id = CachedRenderPipelineId(self.pipelines.len() + new_pipelines.len()); - new_pipelines.push(CachedPipeline { - descriptor: PipelineDescriptor::RenderPipelineDescriptor(Box::new(descriptor)), - state: CachedPipelineState::Queued, - }); - id - } - - /// Insert a compute pipeline into the cache, and queue its creation. - /// - /// The pipeline is always inserted and queued for creation. There is no attempt to deduplicate it with - /// an already cached pipeline. - /// - /// # Returns - /// - /// This method returns the unique compute shader ID of the cached pipeline, which can be used to query - /// the caching state with [`get_compute_pipeline_state()`] and to retrieve the created GPU pipeline once - /// it's ready with [`get_compute_pipeline()`]. - /// - /// [`get_compute_pipeline_state()`]: PipelineCache::get_compute_pipeline_state - /// [`get_compute_pipeline()`]: PipelineCache::get_compute_pipeline - pub fn queue_compute_pipeline( - &self, - descriptor: ComputePipelineDescriptor, - ) -> CachedComputePipelineId { - let mut new_pipelines = self - .new_pipelines - .lock() - .unwrap_or_else(PoisonError::into_inner); - let id = CachedComputePipelineId(self.pipelines.len() + new_pipelines.len()); - new_pipelines.push(CachedPipeline { - descriptor: PipelineDescriptor::ComputePipelineDescriptor(Box::new(descriptor)), - state: CachedPipelineState::Queued, - }); - id - } - - pub fn get_bind_group_layout( - &self, - bind_group_layout_descriptor: &BindGroupLayoutDescriptor, - ) -> BindGroupLayout { - self.bindgroup_layout_cache - .lock() - .unwrap() - .get(&self.device, bind_group_layout_descriptor.clone()) - } - - /// Inserts a [`Shader`] into this cache with the provided [`AssetId`]. - pub fn set_shader(&mut self, id: AssetId, shader: Shader) { - let mut shader_cache = self.shader_cache.lock().unwrap(); - let pipelines_to_queue = shader_cache.set_shader(id, shader); - for cached_pipeline in pipelines_to_queue { - self.pipelines[cached_pipeline].state = CachedPipelineState::Queued; - self.waiting_pipelines.insert(cached_pipeline); - } - } - - /// Removes a [`Shader`] from this cache if it exists. - pub fn remove_shader(&mut self, shader: AssetId) { - let mut shader_cache = self.shader_cache.lock().unwrap(); - let pipelines_to_queue = shader_cache.remove(shader); - for cached_pipeline in pipelines_to_queue { - self.pipelines[cached_pipeline].state = CachedPipelineState::Queued; - self.waiting_pipelines.insert(cached_pipeline); - } - } - - fn start_create_render_pipeline( - &mut self, - id: CachedPipelineId, - descriptor: RenderPipelineDescriptor, - ) -> CachedPipelineState { - let device = self.device.clone(); - let shader_cache = self.shader_cache.clone(); - let layout_cache = self.layout_cache.clone(); - let mut bindgroup_layout_cache = self.bindgroup_layout_cache.lock().unwrap(); - let bind_group_layout = descriptor - .layout - .iter() - .map(|bind_group_layout_descriptor| { - bindgroup_layout_cache.get(&self.device, bind_group_layout_descriptor.clone()) - }) - .collect::>(); - - create_pipeline_task( - async move { - let mut shader_cache = shader_cache.lock().unwrap(); - let mut layout_cache = layout_cache.lock().unwrap(); - - let vertex_module = match shader_cache.get( - &device, - id, - descriptor.vertex.shader.id(), - &descriptor.vertex.shader_defs, - ) { - Ok(module) => module, - Err(err) => return Err(err), - }; - - let fragment_module = match &descriptor.fragment { - Some(fragment) => { - match shader_cache.get( - &device, - id, - fragment.shader.id(), - &fragment.shader_defs, - ) { - Ok(module) => Some(module), - Err(err) => return Err(err), - } - } - None => None, - }; - - let layout = - if descriptor.layout.is_empty() && descriptor.push_constant_ranges.is_empty() { - None - } else { - Some(layout_cache.get( - &device, - &bind_group_layout, - descriptor.push_constant_ranges.to_vec(), - )) - }; - - drop((shader_cache, layout_cache)); - - let vertex_buffer_layouts = descriptor - .vertex - .buffers - .iter() - .map(|layout| RawVertexBufferLayout { - array_stride: layout.array_stride, - attributes: &layout.attributes, - step_mode: layout.step_mode, - }) - .collect::>(); - - let fragment_data = descriptor.fragment.as_ref().map(|fragment| { - ( - fragment_module.unwrap(), - fragment.entry_point.as_deref(), - fragment.targets.as_slice(), - ) - }); - - // TODO: Expose the rest of this somehow - let compilation_options = PipelineCompilationOptions { - constants: &[], - zero_initialize_workgroup_memory: descriptor.zero_initialize_workgroup_memory, - }; - - let descriptor = RawRenderPipelineDescriptor { - multiview: None, - depth_stencil: descriptor.depth_stencil.clone(), - label: descriptor.label.as_deref(), - layout: layout.as_ref().map(|layout| -> &PipelineLayout { layout }), - multisample: descriptor.multisample, - primitive: descriptor.primitive, - vertex: RawVertexState { - buffers: &vertex_buffer_layouts, - entry_point: descriptor.vertex.entry_point.as_deref(), - module: &vertex_module, - // TODO: Should this be the same as the fragment compilation options? - compilation_options: compilation_options.clone(), - }, - fragment: fragment_data - .as_ref() - .map(|(module, entry_point, targets)| RawFragmentState { - entry_point: entry_point.as_deref(), - module, - targets, - // TODO: Should this be the same as the vertex compilation options? - compilation_options, - }), - cache: None, - }; - - Ok(Pipeline::RenderPipeline( - device.create_render_pipeline(&descriptor), - )) - }, - self.synchronous_pipeline_compilation, - ) - } - - fn start_create_compute_pipeline( - &mut self, - id: CachedPipelineId, - descriptor: ComputePipelineDescriptor, - ) -> CachedPipelineState { - let device = self.device.clone(); - let shader_cache = self.shader_cache.clone(); - let layout_cache = self.layout_cache.clone(); - let mut bindgroup_layout_cache = self.bindgroup_layout_cache.lock().unwrap(); - let bind_group_layout = descriptor - .layout - .iter() - .map(|bind_group_layout_descriptor| { - bindgroup_layout_cache.get(&self.device, bind_group_layout_descriptor.clone()) - }) - .collect::>(); - - create_pipeline_task( - async move { - let mut shader_cache = shader_cache.lock().unwrap(); - let mut layout_cache = layout_cache.lock().unwrap(); - - let compute_module = match shader_cache.get( - &device, - id, - descriptor.shader.id(), - &descriptor.shader_defs, - ) { - Ok(module) => module, - Err(err) => return Err(err), - }; - - let layout = - if descriptor.layout.is_empty() && descriptor.push_constant_ranges.is_empty() { - None - } else { - Some(layout_cache.get( - &device, - &bind_group_layout, - descriptor.push_constant_ranges.to_vec(), - )) - }; - - drop((shader_cache, layout_cache)); - - let descriptor = RawComputePipelineDescriptor { - label: descriptor.label.as_deref(), - layout: layout.as_ref().map(|layout| -> &PipelineLayout { layout }), - module: &compute_module, - entry_point: descriptor.entry_point.as_deref(), - // TODO: Expose the rest of this somehow - compilation_options: PipelineCompilationOptions { - constants: &[], - zero_initialize_workgroup_memory: descriptor - .zero_initialize_workgroup_memory, - }, - cache: None, - }; - - Ok(Pipeline::ComputePipeline( - device.create_compute_pipeline(&descriptor), - )) - }, - self.synchronous_pipeline_compilation, - ) - } - - /// Process the pipeline queue and create all pending pipelines if possible. - /// - /// This is generally called automatically during the [`RenderSystems::Render`] step, but can - /// be called manually to force creation at a different time. - /// - /// [`RenderSystems::Render`]: crate::RenderSystems::Render - pub fn process_queue(&mut self) { - let mut waiting_pipelines = mem::take(&mut self.waiting_pipelines); - let mut pipelines = mem::take(&mut self.pipelines); - - { - let mut new_pipelines = self - .new_pipelines - .lock() - .unwrap_or_else(PoisonError::into_inner); - for new_pipeline in new_pipelines.drain(..) { - let id = pipelines.len(); - pipelines.push(new_pipeline); - waiting_pipelines.insert(id); - } - } - - for id in waiting_pipelines { - self.process_pipeline(&mut pipelines[id], id); - } - - self.pipelines = pipelines; - } - - fn process_pipeline(&mut self, cached_pipeline: &mut CachedPipeline, id: usize) { - match &mut cached_pipeline.state { - CachedPipelineState::Queued => { - cached_pipeline.state = match &cached_pipeline.descriptor { - PipelineDescriptor::RenderPipelineDescriptor(descriptor) => { - self.start_create_render_pipeline(id, *descriptor.clone()) - } - PipelineDescriptor::ComputePipelineDescriptor(descriptor) => { - self.start_create_compute_pipeline(id, *descriptor.clone()) - } - }; - } - - CachedPipelineState::Creating(task) => match bevy_tasks::futures::check_ready(task) { - Some(Ok(pipeline)) => { - cached_pipeline.state = CachedPipelineState::Ok(pipeline); - return; - } - Some(Err(err)) => cached_pipeline.state = CachedPipelineState::Err(err), - _ => (), - }, - - CachedPipelineState::Err(err) => match err { - // Retry - PipelineCacheError::ShaderNotLoaded(_) - | PipelineCacheError::ShaderImportNotYetAvailable => { - cached_pipeline.state = CachedPipelineState::Queued; - } - - // Shader could not be processed ... retrying won't help - PipelineCacheError::ProcessShaderError(err) => { - let error_detail = - err.emit_to_string(&self.shader_cache.lock().unwrap().composer); - if std::env::var("VERBOSE_SHADER_ERROR") - .is_ok_and(|v| !(v.is_empty() || v == "0" || v == "false")) - { - error!("{}", pipeline_error_context(cached_pipeline)); - } - error!("failed to process shader error:\n{}", error_detail); - return; - } - PipelineCacheError::CreateShaderModule(description) => { - error!("failed to create shader module: {}", description); - return; - } - }, - - CachedPipelineState::Ok(_) => return, - } - - // Retry - self.waiting_pipelines.insert(id); - } - - pub(crate) fn process_pipeline_queue_system(mut cache: ResMut) { - cache.process_queue(); - } - - pub(crate) fn extract_shaders( - mut cache: ResMut, - shaders: Extract>>, - mut events: Extract>>, - ) { - for event in events.read() { - #[expect( - clippy::match_same_arms, - reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon." - )] - match event { - // PERF: Instead of blocking waiting for the shader cache lock, try again next frame if the lock is currently held - AssetEvent::Added { id } | AssetEvent::Modified { id } => { - if let Some(shader) = shaders.get(*id) { - let mut shader = shader.clone(); - shader.shader_defs.extend(cache.global_shader_defs.clone()); - - cache.set_shader(*id, shader); - } - } - AssetEvent::Removed { id } => cache.remove_shader(*id), - AssetEvent::Unused { .. } => {} - AssetEvent::LoadedWithDependencies { .. } => { - // TODO: handle this - } - } - } - } -} - -fn pipeline_error_context(cached_pipeline: &CachedPipeline) -> String { - fn format( - shader: &Handle, - entry: &Option>, - shader_defs: &[ShaderDefVal], - ) -> String { - let source = match shader.path() { - Some(path) => path.path().to_string_lossy().to_string(), - None => String::new(), - }; - let entry = match entry { - Some(entry) => entry.to_string(), - None => String::new(), - }; - let shader_defs = shader_defs - .iter() - .flat_map(|def| match def { - ShaderDefVal::Bool(k, v) if *v => Some(k.to_string()), - ShaderDefVal::Int(k, v) => Some(format!("{k} = {v}")), - ShaderDefVal::UInt(k, v) => Some(format!("{k} = {v}")), - _ => None, - }) - .collect::>() - .join(", "); - format!("{source}:{entry}\nshader defs: {shader_defs}") - } - match &cached_pipeline.descriptor { - PipelineDescriptor::RenderPipelineDescriptor(desc) => { - let vert = &desc.vertex; - let vert_str = format(&vert.shader, &vert.entry_point, &vert.shader_defs); - let Some(frag) = desc.fragment.as_ref() else { - return vert_str; - }; - let frag_str = format(&frag.shader, &frag.entry_point, &frag.shader_defs); - format!("vertex {vert_str}\nfragment {frag_str}") - } - PipelineDescriptor::ComputePipelineDescriptor(desc) => { - format(&desc.shader, &desc.entry_point, &desc.shader_defs) - } - } -} - -#[cfg(all( - not(target_arch = "wasm32"), - not(target_os = "macos"), - feature = "multi_threaded" -))] -fn create_pipeline_task( - task: impl Future> + Send + 'static, - sync: bool, -) -> CachedPipelineState { - if !sync { - return CachedPipelineState::Creating(bevy_tasks::AsyncComputeTaskPool::get().spawn(task)); - } - - match bevy_tasks::block_on(task) { - Ok(pipeline) => CachedPipelineState::Ok(pipeline), - Err(err) => CachedPipelineState::Err(err), - } -} - -#[cfg(any( - target_arch = "wasm32", - target_os = "macos", - not(feature = "multi_threaded") -))] -fn create_pipeline_task( - task: impl Future> + Send + 'static, - _sync: bool, -) -> CachedPipelineState { - match bevy_tasks::block_on(task) { - Ok(pipeline) => CachedPipelineState::Ok(pipeline), - Err(err) => CachedPipelineState::Err(err), - } -} diff --git a/third_party/bevy_render/src/render_resource/pipeline_specializer.rs b/third_party/bevy_render/src/render_resource/pipeline_specializer.rs deleted file mode 100644 index f2e84a2..0000000 --- a/third_party/bevy_render/src/render_resource/pipeline_specializer.rs +++ /dev/null @@ -1,259 +0,0 @@ -use crate::render_resource::{ - CachedComputePipelineId, CachedRenderPipelineId, ComputePipelineDescriptor, PipelineCache, - RenderPipelineDescriptor, -}; -use bevy_ecs::resource::Resource; -use bevy_mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError, VertexBufferLayout}; -use bevy_platform::{ - collections::{ - hash_map::{Entry, RawEntryMut, VacantEntry}, - HashMap, - }, - hash::FixedHasher, -}; -use bevy_utils::default; -use core::{fmt::Debug, hash::Hash}; -use thiserror::Error; -use tracing::error; - -/// A trait that allows constructing different variants of a render pipeline from a key. -/// -/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key -/// contains no data then you don't need to specialize. For example, if you are using the -/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute, -/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and -/// store its ID. -/// -/// See [`SpecializedRenderPipelines`] for more info. -pub trait SpecializedRenderPipeline { - /// The key that defines each "variant" of the render pipeline. - type Key: Clone + Hash + PartialEq + Eq; - - /// Construct a new render pipeline based on the provided key. - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor; -} - -/// A convenience cache for creating different variants of a render pipeline based on some key. -/// -/// Some render pipelines may need to be configured differently depending on the exact situation. -/// This cache allows constructing different render pipelines for each situation based on a key, -/// making it easy to A) construct the necessary pipelines, and B) reuse already constructed -/// pipelines. -/// -/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key -/// contains no data then you don't need to specialize. For example, if you are using the -/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute, -/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and -/// store its ID. -#[derive(Resource)] -pub struct SpecializedRenderPipelines { - cache: HashMap, -} - -impl Default for SpecializedRenderPipelines { - fn default() -> Self { - Self { cache: default() } - } -} - -impl SpecializedRenderPipelines { - /// Get or create a specialized instance of the pipeline corresponding to `key`. - pub fn specialize( - &mut self, - cache: &PipelineCache, - pipeline_specializer: &S, - key: S::Key, - ) -> CachedRenderPipelineId { - *self.cache.entry(key.clone()).or_insert_with(|| { - let descriptor = pipeline_specializer.specialize(key); - cache.queue_render_pipeline(descriptor) - }) - } -} - -/// A trait that allows constructing different variants of a compute pipeline from a key. -/// -/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key -/// contains no data then you don't need to specialize. For example, if you are using the -/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute, -/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and -/// store its ID. -/// -/// See [`SpecializedComputePipelines`] for more info. -pub trait SpecializedComputePipeline { - /// The key that defines each "variant" of the compute pipeline. - type Key: Clone + Hash + PartialEq + Eq; - - /// Construct a new compute pipeline based on the provided key. - fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor; -} - -/// A convenience cache for creating different variants of a compute pipeline based on some key. -/// -/// Some compute pipelines may need to be configured differently depending on the exact situation. -/// This cache allows constructing different compute pipelines for each situation based on a key, -/// making it easy to A) construct the necessary pipelines, and B) reuse already constructed -/// pipelines. -/// -/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key -/// contains no data then you don't need to specialize. For example, if you are using the -/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute, -/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and -/// store its ID. -#[derive(Resource)] -pub struct SpecializedComputePipelines { - cache: HashMap, -} - -impl Default for SpecializedComputePipelines { - fn default() -> Self { - Self { cache: default() } - } -} - -impl SpecializedComputePipelines { - /// Get or create a specialized instance of the pipeline corresponding to `key`. - pub fn specialize( - &mut self, - cache: &PipelineCache, - specialize_pipeline: &S, - key: S::Key, - ) -> CachedComputePipelineId { - *self.cache.entry(key.clone()).or_insert_with(|| { - let descriptor = specialize_pipeline.specialize(key); - cache.queue_compute_pipeline(descriptor) - }) - } -} - -/// A trait that allows constructing different variants of a render pipeline from a key and the -/// particular mesh's vertex buffer layout. -/// -/// See [`SpecializedMeshPipelines`] for more info. -pub trait SpecializedMeshPipeline { - /// The key that defines each "variant" of the render pipeline. - type Key: Clone + Hash + PartialEq + Eq; - - /// Construct a new render pipeline based on the provided key and vertex layout. - /// - /// The returned pipeline descriptor should have a single vertex buffer, which is derived from - /// `layout`. - fn specialize( - &self, - key: Self::Key, - layout: &MeshVertexBufferLayoutRef, - ) -> Result; -} - -/// A cache of different variants of a render pipeline based on a key and the particular mesh's -/// vertex buffer layout. -#[derive(Resource)] -pub struct SpecializedMeshPipelines { - mesh_layout_cache: HashMap<(MeshVertexBufferLayoutRef, S::Key), CachedRenderPipelineId>, - vertex_layout_cache: VertexLayoutCache, -} - -type VertexLayoutCache = HashMap< - VertexBufferLayout, - HashMap<::Key, CachedRenderPipelineId>, ->; - -impl Default for SpecializedMeshPipelines { - fn default() -> Self { - Self { - mesh_layout_cache: Default::default(), - vertex_layout_cache: Default::default(), - } - } -} - -impl SpecializedMeshPipelines { - /// Construct a new render pipeline based on the provided key and the mesh's vertex buffer - /// layout. - #[inline] - pub fn specialize( - &mut self, - cache: &PipelineCache, - pipeline_specializer: &S, - key: S::Key, - layout: &MeshVertexBufferLayoutRef, - ) -> Result { - return match self.mesh_layout_cache.entry((layout.clone(), key.clone())) { - Entry::Occupied(entry) => Ok(*entry.into_mut()), - Entry::Vacant(entry) => specialize_slow( - &mut self.vertex_layout_cache, - cache, - pipeline_specializer, - key, - layout, - entry, - ), - }; - - #[cold] - fn specialize_slow( - vertex_layout_cache: &mut VertexLayoutCache, - cache: &PipelineCache, - specialize_pipeline: &S, - key: S::Key, - layout: &MeshVertexBufferLayoutRef, - entry: VacantEntry< - (MeshVertexBufferLayoutRef, S::Key), - CachedRenderPipelineId, - FixedHasher, - >, - ) -> Result - where - S: SpecializedMeshPipeline, - { - let descriptor = specialize_pipeline - .specialize(key.clone(), layout) - .map_err(|mut err| { - { - let SpecializedMeshPipelineError::MissingVertexAttribute(err) = &mut err; - err.pipeline_type = Some(core::any::type_name::()); - } - err - })?; - // Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout - // We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them - let layout_map = match vertex_layout_cache - .raw_entry_mut() - .from_key(&descriptor.vertex.buffers[0]) - { - RawEntryMut::Occupied(entry) => entry.into_mut(), - RawEntryMut::Vacant(entry) => { - entry - .insert(descriptor.vertex.buffers[0].clone(), Default::default()) - .1 - } - }; - Ok(*entry.insert(match layout_map.entry(key) { - Entry::Occupied(entry) => { - if cfg!(debug_assertions) { - let stored_descriptor = cache.get_render_pipeline_descriptor(*entry.get()); - if stored_descriptor != &descriptor { - error!( - "The cached pipeline descriptor for {} is not \ - equal to the generated descriptor for the given key. \ - This means the SpecializePipeline implementation uses \ - unused' MeshVertexBufferLayout information to specialize \ - the pipeline. This is not allowed because it would invalidate \ - the pipeline cache.", - core::any::type_name::() - ); - } - } - *entry.into_mut() - } - Entry::Vacant(entry) => *entry.insert(cache.queue_render_pipeline(descriptor)), - })) - } - } -} - -#[derive(Error, Debug)] -pub enum SpecializedMeshPipelineError { - #[error(transparent)] - MissingVertexAttribute(#[from] MissingVertexAttributeError), -} diff --git a/third_party/bevy_render/src/render_resource/resource_macros.rs b/third_party/bevy_render/src/render_resource/resource_macros.rs deleted file mode 100644 index 6cdf3b6..0000000 --- a/third_party/bevy_render/src/render_resource/resource_macros.rs +++ /dev/null @@ -1,39 +0,0 @@ -#[macro_export] -macro_rules! define_atomic_id { - ($atomic_id_type:ident) => { - #[derive(Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)] - pub struct $atomic_id_type(core::num::NonZero); - - impl $atomic_id_type { - #[expect( - clippy::new_without_default, - reason = "Implementing the `Default` trait on atomic IDs would imply that two `::default()` equal each other. By only implementing `new()`, we indicate that each atomic ID created will be unique." - )] - pub fn new() -> Self { - use core::sync::atomic::{AtomicU32, Ordering}; - - static COUNTER: AtomicU32 = AtomicU32::new(1); - - let counter = COUNTER.fetch_add(1, Ordering::Relaxed); - Self(core::num::NonZero::::new(counter).unwrap_or_else(|| { - panic!( - "The system ran out of unique `{}`s.", - stringify!($atomic_id_type) - ); - })) - } - } - - impl From<$atomic_id_type> for core::num::NonZero { - fn from(value: $atomic_id_type) -> Self { - value.0 - } - } - - impl From> for $atomic_id_type { - fn from(value: core::num::NonZero) -> Self { - Self(value) - } - } - }; -} diff --git a/third_party/bevy_render/src/render_resource/specializer.rs b/third_party/bevy_render/src/render_resource/specializer.rs deleted file mode 100644 index 2dc5257..0000000 --- a/third_party/bevy_render/src/render_resource/specializer.rs +++ /dev/null @@ -1,353 +0,0 @@ -use super::{ - CachedComputePipelineId, CachedRenderPipelineId, ComputePipeline, ComputePipelineDescriptor, - PipelineCache, RenderPipeline, RenderPipelineDescriptor, -}; -use bevy_ecs::error::BevyError; -use bevy_platform::{ - collections::{ - hash_map::{Entry, VacantEntry}, - HashMap, - }, - hash::FixedHasher, -}; -use core::{hash::Hash, marker::PhantomData}; -use tracing::error; -use variadics_please::all_tuples; - -pub use bevy_render_macros::{Specializer, SpecializerKey}; - -/// Defines a type that is able to be "specialized" and cached by creating and transforming -/// its descriptor type. This is implemented for [`RenderPipeline`] and [`ComputePipeline`], and -/// likely will not have much utility for other types. -/// -/// See docs on [`Specializer`] for more info. -pub trait Specializable { - type Descriptor: PartialEq + Clone + Send + Sync; - type CachedId: Clone + Send + Sync; - fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId; - fn get_descriptor(pipeline_cache: &PipelineCache, id: Self::CachedId) -> &Self::Descriptor; -} - -impl Specializable for RenderPipeline { - type Descriptor = RenderPipelineDescriptor; - type CachedId = CachedRenderPipelineId; - - fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId { - pipeline_cache.queue_render_pipeline(descriptor) - } - - fn get_descriptor( - pipeline_cache: &PipelineCache, - id: CachedRenderPipelineId, - ) -> &Self::Descriptor { - pipeline_cache.get_render_pipeline_descriptor(id) - } -} - -impl Specializable for ComputePipeline { - type Descriptor = ComputePipelineDescriptor; - - type CachedId = CachedComputePipelineId; - - fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId { - pipeline_cache.queue_compute_pipeline(descriptor) - } - - fn get_descriptor( - pipeline_cache: &PipelineCache, - id: CachedComputePipelineId, - ) -> &Self::Descriptor { - pipeline_cache.get_compute_pipeline_descriptor(id) - } -} - -/// Defines a type capable of "specializing" values of a type T. -/// -/// Specialization is the process of generating variants of a type T -/// from small hashable keys, and specializers themselves can be -/// thought of as [pure functions] from the key type to `T`, that -/// [memoize] their results based on the key. -/// -///

-/// -/// Since compiling render and compute pipelines can be so slow, -/// specialization allows a Bevy app to detect when it would compile -/// a duplicate pipeline and reuse what's already in the cache. While -/// pipelines could all be memoized hashing each whole descriptor, this -/// would be much slower and could still create duplicates. In contrast, -/// memoizing groups of *related* pipelines based on a small hashable -/// key is much faster. See the docs on [`SpecializerKey`] for more info. -/// -/// ## Composing Specializers -/// -/// This trait can be derived with `#[derive(Specializer)]` for structs whose -/// fields all implement [`Specializer`]. This allows for composing multiple -/// specializers together, and makes encapsulation and separating concerns -/// between specializers much nicer. One could make individual specializers -/// for common operations and place them in entirely separate modules, then -/// compose them together with a single `#[derive]` -/// -/// ```rust -/// # use bevy_ecs::error::BevyError; -/// # use bevy_render::render_resource::Specializer; -/// # use bevy_render::render_resource::SpecializerKey; -/// # use bevy_render::render_resource::RenderPipeline; -/// # use bevy_render::render_resource::RenderPipelineDescriptor; -/// struct A; -/// struct B; -/// #[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)] -/// struct BKey { contrived_number: u32 }; -/// -/// impl Specializer for A { -/// type Key = (); -/// -/// fn specialize( -/// &self, -/// key: (), -/// descriptor: &mut RenderPipelineDescriptor -/// ) -> Result<(), BevyError> { -/// # let _ = descriptor; -/// // mutate the descriptor here -/// Ok(key) -/// } -/// } -/// -/// impl Specializer for B { -/// type Key = BKey; -/// -/// fn specialize( -/// &self, -/// key: BKey, -/// descriptor: &mut RenderPipelineDescriptor -/// ) -> Result { -/// # let _ = descriptor; -/// // mutate the descriptor here -/// Ok(key) -/// } -/// } -/// -/// #[derive(Specializer)] -/// #[specialize(RenderPipeline)] -/// struct C { -/// #[key(default)] -/// a: A, -/// b: B, -/// } -/// -/// /* -/// The generated implementation: -/// impl Specializer for C { -/// type Key = BKey; -/// fn specialize( -/// &self, -/// key: Self::Key, -/// descriptor: &mut RenderPipelineDescriptor -/// ) -> Result, BevyError> { -/// let _ = self.a.specialize((), descriptor); -/// let key = self.b.specialize(key, descriptor); -/// Ok(key) -/// } -/// } -/// */ -/// ``` -/// -/// The key type for a composed specializer will be a tuple of the keys -/// of each field, and their specialization logic will be applied in field -/// order. Since derive macros can't have generic parameters, the derive macro -/// requires an additional `#[specialize(..targets)]` attribute to specify a -/// list of types to target for the implementation. `#[specialize(all)]` is -/// also allowed, and will generate a fully generic implementation at the cost -/// of slightly worse error messages. -/// -/// Additionally, each field can optionally take a `#[key]` attribute to -/// specify a "key override". This will hide that field's key from being -/// exposed by the wrapper, and always use the value given by the attribute. -/// Values for this attribute may either be `default` which will use the key's -/// [`Default`] implementation, or a valid rust expression of the key type. -/// -/// [pure functions]: https://en.wikipedia.org/wiki/Pure_function -/// [memoize]: https://en.wikipedia.org/wiki/Memoization -pub trait Specializer: Send + Sync + 'static { - type Key: SpecializerKey; - fn specialize( - &self, - key: Self::Key, - descriptor: &mut T::Descriptor, - ) -> Result, BevyError>; -} - -// TODO: update docs for `SpecializerKey` with a more concrete example -// once we've migrated mesh layout specialization - -/// Defines a type that is able to be used as a key for [`Specializer`]s -/// -///
-/// Most types should implement this trait with the included derive macro.
-/// This generates a "canonical" key type, with IS_CANONICAL = true, and Canonical = Self -///
-/// -/// ## What's a "canonical" key? -/// -/// The specialization API memoizes pipelines based on the hash of each key, but this -/// can still produce duplicates. For example, if one used a list of vertex attributes -/// as a key, even if all the same attributes were present they could be in any order. -/// In each case, though the keys would be "different" they would produce the same -/// pipeline. -/// -/// To address this, during specialization keys are processed into a [canonical] -/// (or "standard") form that represents the actual descriptor that was produced. -/// In the previous example, that would be the final `VertexBufferLayout` contained -/// by the pipeline descriptor. This new key is used by [`Variants`] to -/// perform additional checks for duplicates, but only if required. If a key is -/// canonical from the start, then there's no need. -/// -/// For implementors: the main property of a canonical key is that if two keys hash -/// differently, they should nearly always produce different descriptors. -/// -/// [canonical]: https://en.wikipedia.org/wiki/Canonicalization -pub trait SpecializerKey: Clone + Hash + Eq { - /// Denotes whether this key is canonical or not. This should only be `true` - /// if and only if `Canonical = Self`. - const IS_CANONICAL: bool; - - /// The canonical key type to convert this into during specialization. - type Canonical: Hash + Eq; -} - -pub type Canonical = ::Canonical; - -impl Specializer for () { - type Key = (); - - fn specialize( - &self, - _key: Self::Key, - _descriptor: &mut T::Descriptor, - ) -> Result<(), BevyError> { - Ok(()) - } -} - -impl Specializer for PhantomData { - type Key = (); - - fn specialize( - &self, - _key: Self::Key, - _descriptor: &mut T::Descriptor, - ) -> Result<(), BevyError> { - Ok(()) - } -} - -macro_rules! impl_specialization_key_tuple { - ($(#[$meta:meta])* $($T:ident),*) => { - $(#[$meta])* - impl <$($T: SpecializerKey),*> SpecializerKey for ($($T,)*) { - const IS_CANONICAL: bool = true $(&& <$T as SpecializerKey>::IS_CANONICAL)*; - type Canonical = ($(Canonical<$T>,)*); - } - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_specialization_key_tuple, - 0, - 12, - T -); - -/// A cache for variants of a resource type created by a specializer. -/// At most one resource will be created for each key. -pub struct Variants> { - specializer: S, - base_descriptor: T::Descriptor, - primary_cache: HashMap, - secondary_cache: HashMap, T::CachedId>, -} - -impl> Variants { - /// Creates a new [`Variants`] from a [`Specializer`] and a base descriptor. - #[inline] - pub fn new(specializer: S, base_descriptor: T::Descriptor) -> Self { - Self { - specializer, - base_descriptor, - primary_cache: Default::default(), - secondary_cache: Default::default(), - } - } - - /// Specializes a resource given the [`Specializer`]'s key type. - #[inline] - pub fn specialize( - &mut self, - pipeline_cache: &PipelineCache, - key: S::Key, - ) -> Result { - let entry = self.primary_cache.entry(key.clone()); - match entry { - Entry::Occupied(entry) => Ok(entry.get().clone()), - Entry::Vacant(entry) => Self::specialize_slow( - &self.specializer, - self.base_descriptor.clone(), - pipeline_cache, - key, - entry, - &mut self.secondary_cache, - ), - } - } - - #[cold] - fn specialize_slow( - specializer: &S, - base_descriptor: T::Descriptor, - pipeline_cache: &PipelineCache, - key: S::Key, - primary_entry: VacantEntry, - secondary_cache: &mut HashMap, T::CachedId>, - ) -> Result { - let mut descriptor = base_descriptor.clone(); - let canonical_key = specializer.specialize(key.clone(), &mut descriptor)?; - - // if the whole key is canonical, the secondary cache isn't needed. - if ::IS_CANONICAL { - return Ok(primary_entry - .insert(::queue(pipeline_cache, descriptor)) - .clone()); - } - - let id = match secondary_cache.entry(canonical_key) { - Entry::Occupied(entry) => { - if cfg!(debug_assertions) { - let stored_descriptor = - ::get_descriptor(pipeline_cache, entry.get().clone()); - if &descriptor != stored_descriptor { - error!( - "Invalid Specializer<{}> impl for {}: the cached descriptor \ - is not equal to the generated descriptor for the given key. \ - This means the Specializer implementation uses unused information \ - from the key to specialize the pipeline. This is not allowed \ - because it would invalidate the cache.", - core::any::type_name::(), - core::any::type_name::() - ); - } - } - entry.into_mut().clone() - } - Entry::Vacant(entry) => entry - .insert(::queue(pipeline_cache, descriptor)) - .clone(), - }; - - primary_entry.insert(id.clone()); - Ok(id) - } -} diff --git a/third_party/bevy_render/src/render_resource/storage_buffer.rs b/third_party/bevy_render/src/render_resource/storage_buffer.rs deleted file mode 100644 index dd76f46..0000000 --- a/third_party/bevy_render/src/render_resource/storage_buffer.rs +++ /dev/null @@ -1,285 +0,0 @@ -use core::marker::PhantomData; - -use super::Buffer; -use crate::renderer::{RenderDevice, RenderQueue}; -use encase::{ - internal::WriteInto, DynamicStorageBuffer as DynamicStorageBufferWrapper, ShaderType, - StorageBuffer as StorageBufferWrapper, -}; -use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferSize, BufferUsages}; - -use super::IntoBinding; - -/// Stores data to be transferred to the GPU and made accessible to shaders as a storage buffer. -/// -/// Storage buffers can be made available to shaders in some combination of read/write mode, and can store large amounts of data. -/// Note however that WebGL2 does not support storage buffers, so consider alternative options in this case. -/// -/// Storage buffers can store runtime-sized arrays, but only if they are the last field in a structure. -/// -/// The contained data is stored in system RAM. [`write_buffer`](StorageBuffer::write_buffer) queues -/// copying of the data from system RAM to VRAM. Storage buffers must conform to [std430 alignment/padding requirements], which -/// is automatically enforced by this structure. -/// -/// Other options for storing GPU-accessible data are: -/// * [`BufferVec`](crate::render_resource::BufferVec) -/// * [`DynamicStorageBuffer`] -/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) -/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) -/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) -/// * [`Texture`](crate::render_resource::Texture) -/// * [`UniformBuffer`](crate::render_resource::UniformBuffer) -/// -/// [std430 alignment/padding requirements]: https://www.w3.org/TR/WGSL/#address-spaces-storage -pub struct StorageBuffer { - value: T, - scratch: StorageBufferWrapper>, - buffer: Option, - label: Option, - changed: bool, - buffer_usage: BufferUsages, - last_written_size: Option, -} - -impl From for StorageBuffer { - fn from(value: T) -> Self { - Self { - value, - scratch: StorageBufferWrapper::new(Vec::new()), - buffer: None, - label: None, - changed: false, - buffer_usage: BufferUsages::COPY_DST | BufferUsages::STORAGE, - last_written_size: None, - } - } -} - -impl Default for StorageBuffer { - fn default() -> Self { - Self { - value: T::default(), - scratch: StorageBufferWrapper::new(Vec::new()), - buffer: None, - label: None, - changed: false, - buffer_usage: BufferUsages::COPY_DST | BufferUsages::STORAGE, - last_written_size: None, - } - } -} - -impl StorageBuffer { - #[inline] - pub fn buffer(&self) -> Option<&Buffer> { - self.buffer.as_ref() - } - - #[inline] - pub fn binding(&self) -> Option> { - Some(BindingResource::Buffer(BufferBinding { - buffer: self.buffer()?, - offset: 0, - size: self.last_written_size, - })) - } - - pub fn set(&mut self, value: T) { - self.value = value; - } - - pub fn get(&self) -> &T { - &self.value - } - - pub fn get_mut(&mut self) -> &mut T { - &mut self.value - } - - pub fn set_label(&mut self, label: Option<&str>) { - let label = label.map(str::to_string); - - if label != self.label { - self.changed = true; - } - - self.label = label; - } - - pub fn get_label(&self) -> Option<&str> { - self.label.as_deref() - } - - /// Add more [`BufferUsages`] to the buffer. - /// - /// This method only allows addition of flags to the default usage flags. - /// - /// The default values for buffer usage are `BufferUsages::COPY_DST` and `BufferUsages::STORAGE`. - pub fn add_usages(&mut self, usage: BufferUsages) { - self.buffer_usage |= usage; - self.changed = true; - } - - /// Queues writing of data from system RAM to VRAM using the [`RenderDevice`] - /// and the provided [`RenderQueue`]. - /// - /// If there is no GPU-side buffer allocated to hold the data currently stored, or if a GPU-side buffer previously - /// allocated does not have enough capacity, a new GPU-side buffer is created. - pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { - self.scratch.write(&self.value).unwrap(); - - let capacity = self.buffer.as_deref().map(wgpu::Buffer::size).unwrap_or(0); - let size = self.scratch.as_ref().len() as u64; - - if capacity < size || self.changed { - self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor { - label: self.label.as_deref(), - usage: self.buffer_usage, - contents: self.scratch.as_ref(), - })); - self.changed = false; - } else if let Some(buffer) = &self.buffer { - queue.write_buffer(buffer, 0, self.scratch.as_ref()); - } - - self.last_written_size = BufferSize::new(size); - } -} - -impl<'a, T: ShaderType + WriteInto> IntoBinding<'a> for &'a StorageBuffer { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - self.binding().expect("Failed to get buffer") - } -} - -/// Stores data to be transferred to the GPU and made accessible to shaders as a dynamic storage buffer. -/// -/// This is just a [`StorageBuffer`], but also allows you to set dynamic offsets. -/// -/// Dynamic storage buffers can be made available to shaders in some combination of read/write mode, and can store large amounts -/// of data. Note however that WebGL2 does not support storage buffers, so consider alternative options in this case. Dynamic -/// storage buffers support multiple separate bindings at dynamic byte offsets and so have a -/// [`push`](DynamicStorageBuffer::push) method. -/// -/// The contained data is stored in system RAM. [`write_buffer`](DynamicStorageBuffer::write_buffer) -/// queues copying of the data from system RAM to VRAM. The data within a storage buffer binding must conform to -/// [std430 alignment/padding requirements]. `DynamicStorageBuffer` takes care of serializing the inner type to conform to -/// these requirements. Each item [`push`](DynamicStorageBuffer::push)ed into this structure -/// will additionally be aligned to meet dynamic offset alignment requirements. -/// -/// Other options for storing GPU-accessible data are: -/// * [`BufferVec`](crate::render_resource::BufferVec) -/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) -/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) -/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) -/// * [`StorageBuffer`] -/// * [`Texture`](crate::render_resource::Texture) -/// * [`UniformBuffer`](crate::render_resource::UniformBuffer) -/// -/// [std430 alignment/padding requirements]: https://www.w3.org/TR/WGSL/#address-spaces-storage -pub struct DynamicStorageBuffer { - scratch: DynamicStorageBufferWrapper>, - buffer: Option, - label: Option, - changed: bool, - buffer_usage: BufferUsages, - last_written_size: Option, - _marker: PhantomData T>, -} - -impl Default for DynamicStorageBuffer { - fn default() -> Self { - Self { - scratch: DynamicStorageBufferWrapper::new(Vec::new()), - buffer: None, - label: None, - changed: false, - buffer_usage: BufferUsages::COPY_DST | BufferUsages::STORAGE, - last_written_size: None, - _marker: PhantomData, - } - } -} - -impl DynamicStorageBuffer { - #[inline] - pub fn buffer(&self) -> Option<&Buffer> { - self.buffer.as_ref() - } - - #[inline] - pub fn binding(&self) -> Option> { - Some(BindingResource::Buffer(BufferBinding { - buffer: self.buffer()?, - offset: 0, - size: self.last_written_size, - })) - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.scratch.as_ref().is_empty() - } - - #[inline] - pub fn push(&mut self, value: T) -> u32 { - self.scratch.write(&value).unwrap() as u32 - } - - pub fn set_label(&mut self, label: Option<&str>) { - let label = label.map(str::to_string); - - if label != self.label { - self.changed = true; - } - - self.label = label; - } - - pub fn get_label(&self) -> Option<&str> { - self.label.as_deref() - } - - /// Add more [`BufferUsages`] to the buffer. - /// - /// This method only allows addition of flags to the default usage flags. - /// - /// The default values for buffer usage are `BufferUsages::COPY_DST` and `BufferUsages::STORAGE`. - pub fn add_usages(&mut self, usage: BufferUsages) { - self.buffer_usage |= usage; - self.changed = true; - } - - #[inline] - pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { - let capacity = self.buffer.as_deref().map(wgpu::Buffer::size).unwrap_or(0); - let size = self.scratch.as_ref().len() as u64; - - if capacity < size || (self.changed && size > 0) { - self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor { - label: self.label.as_deref(), - usage: self.buffer_usage, - contents: self.scratch.as_ref(), - })); - self.changed = false; - } else if let Some(buffer) = &self.buffer { - queue.write_buffer(buffer, 0, self.scratch.as_ref()); - } - - self.last_written_size = BufferSize::new(size); - } - - #[inline] - pub fn clear(&mut self) { - self.scratch.as_mut().clear(); - self.scratch.set_offset(0); - } -} - -impl<'a, T: ShaderType + WriteInto> IntoBinding<'a> for &'a DynamicStorageBuffer { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - self.binding().expect("Failed to get buffer") - } -} diff --git a/third_party/bevy_render/src/render_resource/texture.rs b/third_party/bevy_render/src/render_resource/texture.rs deleted file mode 100644 index bedbbcd..0000000 --- a/third_party/bevy_render/src/render_resource/texture.rs +++ /dev/null @@ -1,166 +0,0 @@ -use crate::define_atomic_id; -use crate::renderer::WgpuWrapper; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::resource::Resource; -use core::ops::Deref; - -define_atomic_id!(TextureId); - -/// A GPU-accessible texture. -/// -/// May be converted from and dereferences to a wgpu [`Texture`](wgpu::Texture). -/// Can be created via [`RenderDevice::create_texture`](crate::renderer::RenderDevice::create_texture). -/// -/// Other options for storing GPU-accessible data are: -/// * [`BufferVec`](crate::render_resource::BufferVec) -/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) -/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) -/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) -/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) -/// * [`StorageBuffer`](crate::render_resource::StorageBuffer) -/// * [`UniformBuffer`](crate::render_resource::UniformBuffer) -#[derive(Clone, Debug)] -pub struct Texture { - id: TextureId, - value: WgpuWrapper, -} - -impl Texture { - /// Returns the [`TextureId`]. - #[inline] - pub fn id(&self) -> TextureId { - self.id - } - - /// Creates a view of this texture. - pub fn create_view(&self, desc: &wgpu::TextureViewDescriptor) -> TextureView { - TextureView::from(self.value.create_view(desc)) - } -} - -impl From for Texture { - fn from(value: wgpu::Texture) -> Self { - Texture { - id: TextureId::new(), - value: WgpuWrapper::new(value), - } - } -} - -impl Deref for Texture { - type Target = wgpu::Texture; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} - -define_atomic_id!(TextureViewId); - -/// Describes a [`Texture`] with its associated metadata required by a pipeline or [`BindGroup`](super::BindGroup). -#[derive(Clone, Debug)] -pub struct TextureView { - id: TextureViewId, - value: WgpuWrapper, -} - -pub struct SurfaceTexture { - value: WgpuWrapper, -} - -impl SurfaceTexture { - pub fn present(self) { - self.value.into_inner().present(); - } -} - -impl TextureView { - /// Returns the [`TextureViewId`]. - #[inline] - pub fn id(&self) -> TextureViewId { - self.id - } -} - -impl From for TextureView { - fn from(value: wgpu::TextureView) -> Self { - TextureView { - id: TextureViewId::new(), - value: WgpuWrapper::new(value), - } - } -} - -impl From for SurfaceTexture { - fn from(value: wgpu::SurfaceTexture) -> Self { - SurfaceTexture { - value: WgpuWrapper::new(value), - } - } -} - -impl Deref for TextureView { - type Target = wgpu::TextureView; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} - -impl Deref for SurfaceTexture { - type Target = wgpu::SurfaceTexture; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} - -define_atomic_id!(SamplerId); - -/// A Sampler defines how a pipeline will sample from a [`TextureView`]. -/// They define image filters (including anisotropy) and address (wrapping) modes, among other things. -/// -/// May be converted from and dereferences to a wgpu [`Sampler`](wgpu::Sampler). -/// Can be created via [`RenderDevice::create_sampler`](crate::renderer::RenderDevice::create_sampler). -#[derive(Clone, Debug)] -pub struct Sampler { - id: SamplerId, - value: WgpuWrapper, -} - -impl Sampler { - /// Returns the [`SamplerId`]. - #[inline] - pub fn id(&self) -> SamplerId { - self.id - } -} - -impl From for Sampler { - fn from(value: wgpu::Sampler) -> Self { - Sampler { - id: SamplerId::new(), - value: WgpuWrapper::new(value), - } - } -} - -impl Deref for Sampler { - type Target = wgpu::Sampler; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} - -/// A rendering resource for the default image sampler which is set during renderer -/// initialization. -/// -/// The [`ImagePlugin`](bevy_image::ImagePlugin) can be set during app initialization to change the default -/// image sampler. -#[derive(Resource, Debug, Clone, Deref, DerefMut)] -pub struct DefaultImageSampler(pub(crate) Sampler); diff --git a/third_party/bevy_render/src/render_resource/uniform_buffer.rs b/third_party/bevy_render/src/render_resource/uniform_buffer.rs deleted file mode 100644 index 0fe9350..0000000 --- a/third_party/bevy_render/src/render_resource/uniform_buffer.rs +++ /dev/null @@ -1,402 +0,0 @@ -use core::{marker::PhantomData, num::NonZero}; - -use crate::{ - render_resource::Buffer, - renderer::{RenderDevice, RenderQueue}, -}; -use encase::{ - internal::{AlignmentValue, BufferMut, WriteInto}, - DynamicUniformBuffer as DynamicUniformBufferWrapper, ShaderType, - UniformBuffer as UniformBufferWrapper, -}; -use wgpu::{ - util::BufferInitDescriptor, BindingResource, BufferBinding, BufferDescriptor, BufferUsages, -}; - -use super::IntoBinding; - -/// Stores data to be transferred to the GPU and made accessible to shaders as a uniform buffer. -/// -/// Uniform buffers are available to shaders on a read-only basis. Uniform buffers are commonly used to make available to shaders -/// parameters that are constant during shader execution, and are best used for data that is relatively small in size as they are -/// only guaranteed to support up to 16kB per binding. -/// -/// The contained data is stored in system RAM. [`write_buffer`](UniformBuffer::write_buffer) queues -/// copying of the data from system RAM to VRAM. Data in uniform buffers must follow [std140 alignment/padding requirements], -/// which is automatically enforced by this structure. Per the WGPU spec, uniform buffers cannot store runtime-sized array -/// (vectors), or structures with fields that are vectors. -/// -/// Other options for storing GPU-accessible data are: -/// * [`BufferVec`](crate::render_resource::BufferVec) -/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) -/// * [`DynamicUniformBuffer`] -/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) -/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) -/// * [`StorageBuffer`](crate::render_resource::StorageBuffer) -/// * [`Texture`](crate::render_resource::Texture) -/// -/// [std140 alignment/padding requirements]: https://www.w3.org/TR/WGSL/#address-spaces-uniform -pub struct UniformBuffer { - value: T, - scratch: UniformBufferWrapper>, - buffer: Option, - label: Option, - changed: bool, - buffer_usage: BufferUsages, -} - -impl From for UniformBuffer { - fn from(value: T) -> Self { - Self { - value, - scratch: UniformBufferWrapper::new(Vec::new()), - buffer: None, - label: None, - changed: false, - buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM, - } - } -} - -impl Default for UniformBuffer { - fn default() -> Self { - Self { - value: T::default(), - scratch: UniformBufferWrapper::new(Vec::new()), - buffer: None, - label: None, - changed: false, - buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM, - } - } -} - -impl UniformBuffer { - #[inline] - pub fn buffer(&self) -> Option<&Buffer> { - self.buffer.as_ref() - } - - #[inline] - pub fn binding(&self) -> Option> { - Some(BindingResource::Buffer( - self.buffer()?.as_entire_buffer_binding(), - )) - } - - /// Set the data the buffer stores. - pub fn set(&mut self, value: T) { - self.value = value; - } - - pub fn get(&self) -> &T { - &self.value - } - - pub fn get_mut(&mut self) -> &mut T { - &mut self.value - } - - pub fn set_label(&mut self, label: Option<&str>) { - let label = label.map(str::to_string); - - if label != self.label { - self.changed = true; - } - - self.label = label; - } - - pub fn get_label(&self) -> Option<&str> { - self.label.as_deref() - } - - /// Add more [`BufferUsages`] to the buffer. - /// - /// This method only allows addition of flags to the default usage flags. - /// - /// The default values for buffer usage are `BufferUsages::COPY_DST` and `BufferUsages::UNIFORM`. - pub fn add_usages(&mut self, usage: BufferUsages) { - self.buffer_usage |= usage; - self.changed = true; - } - - /// Queues writing of data from system RAM to VRAM using the [`RenderDevice`] - /// and the provided [`RenderQueue`], if a GPU-side backing buffer already exists. - /// - /// If a GPU-side buffer does not already exist for this data, such a buffer is initialized with currently - /// available data. - pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { - self.scratch.write(&self.value).unwrap(); - - if self.changed || self.buffer.is_none() { - self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor { - label: self.label.as_deref(), - usage: self.buffer_usage, - contents: self.scratch.as_ref(), - })); - self.changed = false; - } else if let Some(buffer) = &self.buffer { - queue.write_buffer(buffer, 0, self.scratch.as_ref()); - } - } -} - -impl<'a, T: ShaderType + WriteInto> IntoBinding<'a> for &'a UniformBuffer { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - self.buffer() - .expect("Failed to get buffer") - .as_entire_buffer_binding() - .into_binding() - } -} - -/// Stores data to be transferred to the GPU and made accessible to shaders as a dynamic uniform buffer. -/// -/// Dynamic uniform buffers are available to shaders on a read-only basis. Dynamic uniform buffers are commonly used to make -/// available to shaders runtime-sized arrays of parameters that are otherwise constant during shader execution, and are best -/// suited to data that is relatively small in size as they are only guaranteed to support up to 16kB per binding. -/// -/// The contained data is stored in system RAM. [`write_buffer`](DynamicUniformBuffer::write_buffer) queues -/// copying of the data from system RAM to VRAM. Data in uniform buffers must follow [std140 alignment/padding requirements], -/// which is automatically enforced by this structure. Per the WGPU spec, uniform buffers cannot store runtime-sized array -/// (vectors), or structures with fields that are vectors. -/// -/// Other options for storing GPU-accessible data are: -/// * [`BufferVec`](crate::render_resource::BufferVec) -/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) -/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) -/// * [`RawBufferVec`](crate::render_resource::RawBufferVec) -/// * [`StorageBuffer`](crate::render_resource::StorageBuffer) -/// * [`Texture`](crate::render_resource::Texture) -/// * [`UniformBuffer`] -/// -/// [std140 alignment/padding requirements]: https://www.w3.org/TR/WGSL/#address-spaces-uniform -pub struct DynamicUniformBuffer { - scratch: DynamicUniformBufferWrapper>, - buffer: Option, - label: Option, - changed: bool, - buffer_usage: BufferUsages, - _marker: PhantomData T>, -} - -impl Default for DynamicUniformBuffer { - fn default() -> Self { - Self { - scratch: DynamicUniformBufferWrapper::new(Vec::new()), - buffer: None, - label: None, - changed: false, - buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM, - _marker: PhantomData, - } - } -} - -impl DynamicUniformBuffer { - pub fn new_with_alignment(alignment: u64) -> Self { - Self { - scratch: DynamicUniformBufferWrapper::new_with_alignment(Vec::new(), alignment), - buffer: None, - label: None, - changed: false, - buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM, - _marker: PhantomData, - } - } - - #[inline] - pub fn buffer(&self) -> Option<&Buffer> { - self.buffer.as_ref() - } - - #[inline] - pub fn binding(&self) -> Option> { - Some(BindingResource::Buffer(BufferBinding { - buffer: self.buffer()?, - offset: 0, - size: Some(T::min_size()), - })) - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.scratch.as_ref().is_empty() - } - - /// Push data into the `DynamicUniformBuffer`'s internal vector (residing on system RAM). - #[inline] - pub fn push(&mut self, value: &T) -> u32 { - self.scratch.write(value).unwrap() as u32 - } - - pub fn set_label(&mut self, label: Option<&str>) { - let label = label.map(str::to_string); - - if label != self.label { - self.changed = true; - } - - self.label = label; - } - - pub fn get_label(&self) -> Option<&str> { - self.label.as_deref() - } - - /// Add more [`BufferUsages`] to the buffer. - /// - /// This method only allows addition of flags to the default usage flags. - /// - /// The default values for buffer usage are `BufferUsages::COPY_DST` and `BufferUsages::UNIFORM`. - pub fn add_usages(&mut self, usage: BufferUsages) { - self.buffer_usage |= usage; - self.changed = true; - } - - /// Creates a writer that can be used to directly write elements into the target buffer. - /// - /// This method uses less memory and performs fewer memory copies using over [`push`] and [`write_buffer`]. - /// - /// `max_count` *must* be greater than or equal to the number of elements that are to be written to the buffer, or - /// the writer will panic while writing. Dropping the writer will schedule the buffer write into the provided - /// [`RenderQueue`]. - /// - /// If there is no GPU-side buffer allocated to hold the data currently stored, or if a GPU-side buffer previously - /// allocated does not have enough capacity to hold `max_count` elements, a new GPU-side buffer is created. - /// - /// Returns `None` if there is no allocated GPU-side buffer, and `max_count` is 0. - /// - /// [`push`]: Self::push - /// [`write_buffer`]: Self::write_buffer - #[inline] - pub fn get_writer<'a>( - &'a mut self, - max_count: usize, - device: &RenderDevice, - queue: &'a RenderQueue, - ) -> Option> { - let alignment = if cfg!(target_abi = "sim") { - // On iOS simulator on silicon macs, metal validation check that the host OS alignment - // is respected, but the device reports the correct value for iOS, which is smaller. - // Use the larger value. - // See https://github.com/gfx-rs/wgpu/issues/7057 - remove if it's not needed anymore. - AlignmentValue::new(256) - } else { - AlignmentValue::new(device.limits().min_uniform_buffer_offset_alignment as u64) - }; - - let mut capacity = self.buffer.as_deref().map(wgpu::Buffer::size).unwrap_or(0); - let size = alignment - .round_up(T::min_size().get()) - .checked_mul(max_count as u64) - .unwrap(); - - if capacity < size || (self.changed && size > 0) { - let buffer = device.create_buffer(&BufferDescriptor { - label: self.label.as_deref(), - usage: self.buffer_usage, - size, - mapped_at_creation: false, - }); - capacity = buffer.size(); - self.buffer = Some(buffer); - self.changed = false; - } - - if let Some(buffer) = self.buffer.as_deref() { - let buffer_view = queue - .write_buffer_with(buffer, 0, NonZero::::new(buffer.size())?) - .unwrap(); - Some(DynamicUniformBufferWriter { - buffer: encase::DynamicUniformBuffer::new_with_alignment( - QueueWriteBufferViewWrapper { - capacity: capacity as usize, - buffer_view, - }, - alignment.get(), - ), - _marker: PhantomData, - }) - } else { - None - } - } - - /// Queues writing of data from system RAM to VRAM using the [`RenderDevice`] - /// and the provided [`RenderQueue`]. - /// - /// If there is no GPU-side buffer allocated to hold the data currently stored, or if a GPU-side buffer previously - /// allocated does not have enough capacity, a new GPU-side buffer is created. - #[inline] - pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { - let capacity = self.buffer.as_deref().map(wgpu::Buffer::size).unwrap_or(0); - let size = self.scratch.as_ref().len() as u64; - - if capacity < size || (self.changed && size > 0) { - self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor { - label: self.label.as_deref(), - usage: self.buffer_usage, - contents: self.scratch.as_ref(), - })); - self.changed = false; - } else if let Some(buffer) = &self.buffer { - queue.write_buffer(buffer, 0, self.scratch.as_ref()); - } - } - - #[inline] - pub fn clear(&mut self) { - self.scratch.as_mut().clear(); - self.scratch.set_offset(0); - } -} - -/// A writer that can be used to directly write elements into the target buffer. -/// -/// For more information, see [`DynamicUniformBuffer::get_writer`]. -pub struct DynamicUniformBufferWriter { - buffer: encase::DynamicUniformBuffer, - _marker: PhantomData T>, -} - -impl DynamicUniformBufferWriter { - pub fn write(&mut self, value: &T) -> u32 { - self.buffer.write(value).unwrap() as u32 - } -} - -/// A wrapper to work around the orphan rule so that [`wgpu::QueueWriteBufferView`] can implement -/// [`BufferMut`]. -struct QueueWriteBufferViewWrapper { - buffer_view: wgpu::QueueWriteBufferView, - // Must be kept separately and cannot be retrieved from buffer_view, as the read-only access will - // invoke a panic. - capacity: usize, -} - -impl BufferMut for QueueWriteBufferViewWrapper { - #[inline] - fn capacity(&self) -> usize { - self.capacity - } - - #[inline] - fn write(&mut self, offset: usize, val: &[u8; N]) { - self.buffer_view.write(offset, val); - } - - #[inline] - fn write_slice(&mut self, offset: usize, val: &[u8]) { - self.buffer_view.write_slice(offset, val); - } -} - -impl<'a, T: ShaderType + WriteInto> IntoBinding<'a> for &'a DynamicUniformBuffer { - #[inline] - fn into_binding(self) -> BindingResource<'a> { - self.binding().unwrap() - } -} diff --git a/third_party/bevy_render/src/renderer/graph_runner.rs b/third_party/bevy_render/src/renderer/graph_runner.rs deleted file mode 100644 index c547372..0000000 --- a/third_party/bevy_render/src/renderer/graph_runner.rs +++ /dev/null @@ -1,292 +0,0 @@ -use bevy_ecs::{prelude::Entity, world::World}; -use bevy_platform::collections::HashMap; -#[cfg(feature = "trace")] -use tracing::info_span; - -use alloc::{borrow::Cow, collections::VecDeque}; -use smallvec::{smallvec, SmallVec}; -use thiserror::Error; - -use crate::{ - diagnostic::internal::{DiagnosticsRecorder, RenderDiagnosticsMutex}, - render_graph::{ - Edge, InternedRenderLabel, InternedRenderSubGraph, NodeRunError, NodeState, RenderGraph, - RenderGraphContext, SlotLabel, SlotType, SlotValue, - }, - renderer::{RenderContext, RenderDevice}, -}; - -/// The [`RenderGraphRunner`] is responsible for executing a [`RenderGraph`]. -/// -/// It will run all nodes in the graph sequentially in the correct order (defined by the edges). -/// Each [`Node`](crate::render_graph::Node) can run any arbitrary code, but will generally -/// either send directly a [`CommandBuffer`] or a task that will asynchronously generate a [`CommandBuffer`] -/// -/// After running the graph, the [`RenderGraphRunner`] will execute in parallel all the tasks to get -/// an ordered list of [`CommandBuffer`]s to execute. These [`CommandBuffer`] will be submitted to the GPU -/// sequentially in the order that the tasks were submitted. (which is the order of the [`RenderGraph`]) -/// -/// [`CommandBuffer`]: wgpu::CommandBuffer -pub(crate) struct RenderGraphRunner; - -#[derive(Error, Debug)] -pub enum RenderGraphRunnerError { - #[error(transparent)] - NodeRunError(#[from] NodeRunError), - #[error("node output slot not set (index {slot_index}, name {slot_name})")] - EmptyNodeOutputSlot { - type_name: &'static str, - slot_index: usize, - slot_name: Cow<'static, str>, - }, - #[error("graph '{sub_graph:?}' could not be run because slot '{slot_name}' at index {slot_index} has no value")] - MissingInput { - slot_index: usize, - slot_name: Cow<'static, str>, - sub_graph: Option, - }, - #[error("attempted to use the wrong type for input slot")] - MismatchedInputSlotType { - slot_index: usize, - label: SlotLabel, - expected: SlotType, - actual: SlotType, - }, - #[error( - "node (name: '{node_name:?}') has {slot_count} input slots, but was provided {value_count} values" - )] - MismatchedInputCount { - node_name: InternedRenderLabel, - slot_count: usize, - value_count: usize, - }, -} - -impl RenderGraphRunner { - pub fn run( - graph: &RenderGraph, - render_device: RenderDevice, - mut diagnostics_recorder: Option, - queue: &wgpu::Queue, - world: &World, - finalizer: impl FnOnce(&mut wgpu::CommandEncoder), - ) -> Result, RenderGraphRunnerError> { - if let Some(recorder) = &mut diagnostics_recorder { - recorder.begin_frame(); - } - - let mut render_context = RenderContext::new(render_device, diagnostics_recorder); - Self::run_graph(graph, None, &mut render_context, world, &[], None, None)?; - finalizer(render_context.command_encoder()); - - let (render_device, mut diagnostics_recorder) = { - let (commands, render_device, diagnostics_recorder) = render_context.finish(); - - #[cfg(feature = "trace")] - let _span = info_span!("submit_graph_commands").entered(); - queue.submit(commands); - - (render_device, diagnostics_recorder) - }; - - if let Some(recorder) = &mut diagnostics_recorder { - let render_diagnostics_mutex = world.resource::().0.clone(); - recorder.finish_frame(&render_device, move |diagnostics| { - *render_diagnostics_mutex.lock().expect("lock poisoned") = Some(diagnostics); - }); - } - - Ok(diagnostics_recorder) - } - - /// Runs the [`RenderGraph`] and all its sub-graphs sequentially, making sure that all nodes are - /// run in the correct order. (a node only runs when all its dependencies have finished running) - fn run_graph<'w>( - graph: &RenderGraph, - sub_graph: Option, - render_context: &mut RenderContext<'w>, - world: &'w World, - inputs: &[SlotValue], - view_entity: Option, - debug_group: Option, - ) -> Result<(), RenderGraphRunnerError> { - let mut node_outputs: HashMap> = - HashMap::default(); - #[cfg(feature = "trace")] - let span = if let Some(render_label) = &sub_graph { - let name = format!("{render_label:?}"); - if let Some(debug_group) = debug_group.as_ref() { - info_span!("run_graph", name = name, debug_group = debug_group) - } else { - info_span!("run_graph", name = name) - } - } else { - info_span!("run_graph", name = "main_graph") - }; - #[cfg(feature = "trace")] - let _guard = span.enter(); - - if let Some(debug_group) = debug_group.as_ref() { - // wgpu 27 changed the debug_group validation which makes it impossible to have - // a debug_group that spans multiple command encoders. - // - // - // - // For now, we use a debug_marker as a workaround - render_context - .command_encoder() - .insert_debug_marker(&format!("Start {debug_group}")); - } - - // Queue up nodes without inputs, which can be run immediately - let mut node_queue: VecDeque<&NodeState> = graph - .iter_nodes() - .filter(|node| node.input_slots.is_empty()) - .collect(); - - // pass inputs into the graph - if let Some(input_node) = graph.get_input_node() { - let mut input_values: SmallVec<[SlotValue; 4]> = SmallVec::new(); - for (i, input_slot) in input_node.input_slots.iter().enumerate() { - if let Some(input_value) = inputs.get(i) { - if input_slot.slot_type != input_value.slot_type() { - return Err(RenderGraphRunnerError::MismatchedInputSlotType { - slot_index: i, - actual: input_value.slot_type(), - expected: input_slot.slot_type, - label: input_slot.name.clone().into(), - }); - } - input_values.push(input_value.clone()); - } else { - return Err(RenderGraphRunnerError::MissingInput { - slot_index: i, - slot_name: input_slot.name.clone(), - sub_graph, - }); - } - } - - node_outputs.insert(input_node.label, input_values); - - for (_, node_state) in graph - .iter_node_outputs(input_node.label) - .expect("node exists") - { - node_queue.push_front(node_state); - } - } - - 'handle_node: while let Some(node_state) = node_queue.pop_back() { - // skip nodes that are already processed - if node_outputs.contains_key(&node_state.label) { - continue; - } - - let mut slot_indices_and_inputs: SmallVec<[(usize, SlotValue); 4]> = SmallVec::new(); - // check if all dependencies have finished running - for (edge, input_node) in graph - .iter_node_inputs(node_state.label) - .expect("node is in graph") - { - match edge { - Edge::SlotEdge { - output_index, - input_index, - .. - } => { - if let Some(outputs) = node_outputs.get(&input_node.label) { - slot_indices_and_inputs - .push((*input_index, outputs[*output_index].clone())); - } else { - node_queue.push_front(node_state); - continue 'handle_node; - } - } - Edge::NodeEdge { .. } => { - if !node_outputs.contains_key(&input_node.label) { - node_queue.push_front(node_state); - continue 'handle_node; - } - } - } - } - - // construct final sorted input list - slot_indices_and_inputs.sort_by_key(|(index, _)| *index); - let inputs: SmallVec<[SlotValue; 4]> = slot_indices_and_inputs - .into_iter() - .map(|(_, value)| value) - .collect(); - - if inputs.len() != node_state.input_slots.len() { - return Err(RenderGraphRunnerError::MismatchedInputCount { - node_name: node_state.label, - slot_count: node_state.input_slots.len(), - value_count: inputs.len(), - }); - } - - let mut outputs: SmallVec<[Option; 4]> = - smallvec![None; node_state.output_slots.len()]; - { - let mut context = RenderGraphContext::new(graph, node_state, &inputs, &mut outputs); - if let Some(view_entity) = view_entity { - context.set_view_entity(view_entity); - } - - { - #[cfg(feature = "trace")] - let _span = info_span!("node", name = node_state.type_name).entered(); - - node_state.node.run(&mut context, render_context, world)?; - } - - for run_sub_graph in context.finish() { - let sub_graph = graph - .get_sub_graph(run_sub_graph.sub_graph) - .expect("sub graph exists because it was validated when queued."); - Self::run_graph( - sub_graph, - Some(run_sub_graph.sub_graph), - render_context, - world, - &run_sub_graph.inputs, - run_sub_graph.view_entity, - run_sub_graph.debug_group, - )?; - } - } - - let mut values: SmallVec<[SlotValue; 4]> = SmallVec::new(); - for (i, output) in outputs.into_iter().enumerate() { - if let Some(value) = output { - values.push(value); - } else { - let empty_slot = node_state.output_slots.get_slot(i).unwrap(); - return Err(RenderGraphRunnerError::EmptyNodeOutputSlot { - type_name: node_state.type_name, - slot_index: i, - slot_name: empty_slot.name.clone(), - }); - } - } - node_outputs.insert(node_state.label, values); - - for (_, node_state) in graph - .iter_node_outputs(node_state.label) - .expect("node exists") - { - node_queue.push_front(node_state); - } - } - - if let Some(debug_group) = debug_group { - render_context - .command_encoder() - .insert_debug_marker(&format!("End {debug_group}")); - } - - Ok(()) - } -} diff --git a/third_party/bevy_render/src/renderer/mod.rs b/third_party/bevy_render/src/renderer/mod.rs deleted file mode 100644 index e5ab90f..0000000 --- a/third_party/bevy_render/src/renderer/mod.rs +++ /dev/null @@ -1,689 +0,0 @@ -mod graph_runner; -#[cfg(feature = "raw_vulkan_init")] -pub mod raw_vulkan_init; -mod render_device; -mod wgpu_wrapper; - -pub use graph_runner::*; -pub use render_device::*; -pub use wgpu_wrapper::WgpuWrapper; - -use crate::{ - diagnostic::{internal::DiagnosticsRecorder, RecordDiagnostics}, - render_graph::RenderGraph, - render_phase::TrackedRenderPass, - render_resource::RenderPassDescriptor, - settings::{RenderResources, WgpuSettings, WgpuSettingsPriority}, - view::{ExtractedWindows, ViewTarget}, -}; -use alloc::sync::Arc; -use bevy_camera::NormalizedRenderTarget; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{prelude::*, system::SystemState}; -use bevy_platform::time::Instant; -use bevy_render::camera::ExtractedCamera; -use bevy_time::TimeSender; -use bevy_window::RawHandleWrapperHolder; -use tracing::{debug, error, info, info_span, warn}; -use wgpu::{ - Adapter, AdapterInfo, Backends, CommandBuffer, CommandEncoder, DeviceType, Instance, Queue, - RequestAdapterOptions, Trace, -}; - -/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame. -pub fn render_system( - world: &mut World, - state: &mut SystemState>, -) { - world.resource_scope(|world, mut graph: Mut| { - graph.update(world); - }); - - let diagnostics_recorder = world.remove_resource::(); - - let graph = world.resource::(); - let render_device = world.resource::(); - let render_queue = world.resource::(); - - let res = RenderGraphRunner::run( - graph, - render_device.clone(), // TODO: is this clone really necessary? - diagnostics_recorder, - &render_queue.0, - world, - |encoder| { - crate::view::screenshot::submit_screenshot_commands(world, encoder); - crate::gpu_readback::submit_readback_commands(world, encoder); - }, - ); - - match res { - Ok(Some(diagnostics_recorder)) => { - world.insert_resource(diagnostics_recorder); - } - Ok(None) => {} - Err(e) => { - error!("Error running render graph:"); - { - let mut src: &dyn core::error::Error = &e; - loop { - error!("> {}", src); - match src.source() { - Some(s) => src = s, - None => break, - } - } - } - - panic!("Error running render graph: {e}"); - } - } - - { - let _span = info_span!("present_frames").entered(); - - world.resource_scope(|world, mut windows: Mut| { - let views = state.get(world); - for window in windows.values_mut() { - let view_needs_present = views.iter().any(|(view_target, camera)| { - matches!( - camera.target, - Some(NormalizedRenderTarget::Window(w)) if w.entity() == window.entity - ) && view_target.needs_present() - }); - - if view_needs_present || window.needs_initial_present { - window.present(); - window.needs_initial_present = false; - } - } - }); - - #[cfg(feature = "tracing-tracy")] - tracing::event!( - tracing::Level::INFO, - message = "finished frame", - tracy.frame_mark = true - ); - } - - crate::view::screenshot::collect_screenshots(world); - - // update the time and send it to the app world - let time_sender = world.resource::(); - if let Err(error) = time_sender.0.try_send(Instant::now()) { - match error { - bevy_time::TrySendError::Full(_) => { - panic!("The TimeSender channel should always be empty during render. You might need to add the bevy::core::time_system to your app."); - } - bevy_time::TrySendError::Disconnected(_) => { - // ignore disconnected errors, the main world probably just got dropped during shutdown - } - } - } -} - -/// This queue is used to enqueue tasks for the GPU to execute asynchronously. -#[derive(Resource, Clone, Deref, DerefMut)] -pub struct RenderQueue(pub Arc>); - -/// The handle to the physical device being used for rendering. -/// See [`Adapter`] for more info. -#[derive(Resource, Clone, Debug, Deref, DerefMut)] -pub struct RenderAdapter(pub Arc>); - -/// The GPU instance is used to initialize the [`RenderQueue`] and [`RenderDevice`], -/// as well as to create [`WindowSurfaces`](crate::view::window::WindowSurfaces). -#[derive(Resource, Clone, Deref, DerefMut)] -pub struct RenderInstance(pub Arc>); - -/// The [`AdapterInfo`] of the adapter in use by the renderer. -#[derive(Resource, Clone, Deref, DerefMut)] -pub struct RenderAdapterInfo(pub WgpuWrapper); - -const GPU_NOT_FOUND_ERROR_MESSAGE: &str = if cfg!(target_os = "linux") { - "Unable to find a GPU! Make sure you have installed required drivers! For extra information, see: https://github.com/bevyengine/bevy/blob/latest/docs/linux_dependencies.md" -} else { - "Unable to find a GPU! Make sure you have installed required drivers!" -}; - -#[cfg(not(target_family = "wasm"))] -fn find_adapter_by_name( - instance: &Instance, - options: &WgpuSettings, - compatible_surface: Option<&wgpu::Surface<'_>>, - adapter_name: &str, -) -> Option { - for adapter in - instance.enumerate_adapters(options.backends.expect( - "The `backends` field of `WgpuSettings` must be set to use a specific adapter.", - )) - { - tracing::trace!("Checking adapter: {:?}", adapter.get_info()); - let info = adapter.get_info(); - if let Some(surface) = compatible_surface - && !adapter.is_surface_supported(surface) - { - continue; - } - - if info - .name - .to_lowercase() - .contains(&adapter_name.to_lowercase()) - { - return Some(adapter); - } - } - None -} - -/// Initializes the renderer by retrieving and preparing the GPU instance, device and queue -/// for the specified backend. -pub async fn initialize_renderer( - backends: Backends, - primary_window: Option, - options: &WgpuSettings, - #[cfg(feature = "raw_vulkan_init")] - raw_vulkan_init_settings: raw_vulkan_init::RawVulkanInitSettings, -) -> RenderResources { - let instance_descriptor = wgpu::InstanceDescriptor { - backends, - flags: options.instance_flags, - memory_budget_thresholds: options.instance_memory_budget_thresholds, - backend_options: wgpu::BackendOptions { - gl: wgpu::GlBackendOptions { - gles_minor_version: options.gles3_minor_version, - fence_behavior: wgpu::GlFenceBehavior::Normal, - }, - dx12: wgpu::Dx12BackendOptions { - shader_compiler: options.dx12_shader_compiler.clone(), - presentation_system: wgpu::wgt::Dx12SwapchainKind::from_env().unwrap_or_default(), - latency_waitable_object: wgpu::wgt::Dx12UseFrameLatencyWaitableObject::from_env() - .unwrap_or_default(), - }, - noop: wgpu::NoopBackendOptions { enable: false }, - }, - }; - - #[cfg(not(feature = "raw_vulkan_init"))] - let instance = Instance::new(&instance_descriptor); - #[cfg(feature = "raw_vulkan_init")] - let mut additional_vulkan_features = raw_vulkan_init::AdditionalVulkanFeatures::default(); - #[cfg(feature = "raw_vulkan_init")] - let instance = raw_vulkan_init::create_raw_vulkan_instance( - &instance_descriptor, - &raw_vulkan_init_settings, - &mut additional_vulkan_features, - ); - - let surface = primary_window.and_then(|wrapper| { - let maybe_handle = wrapper - .0 - .lock() - .expect("Couldn't get the window handle in time for renderer initialization"); - if let Some(wrapper) = maybe_handle.as_ref() { - // SAFETY: Plugins should be set up on the main thread. - let handle = unsafe { wrapper.get_handle() }; - Some( - instance - .create_surface(handle) - .expect("Failed to create wgpu surface"), - ) - } else { - None - } - }); - - let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER") - .map_or(options.force_fallback_adapter, |v| { - !(v.is_empty() || v == "0" || v == "false") - }); - - let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME") - .as_deref() - .map_or(options.adapter_name.clone(), |x| Some(x.to_lowercase())); - - let request_adapter_options = RequestAdapterOptions { - power_preference: options.power_preference, - compatible_surface: surface.as_ref(), - force_fallback_adapter, - }; - - #[cfg(not(target_family = "wasm"))] - let mut selected_adapter = desired_adapter_name.and_then(|adapter_name| { - find_adapter_by_name( - &instance, - options, - request_adapter_options.compatible_surface, - &adapter_name, - ) - }); - #[cfg(target_family = "wasm")] - let mut selected_adapter = None; - - #[cfg(target_family = "wasm")] - if desired_adapter_name.is_some() { - warn!("Choosing an adapter is not supported on wasm."); - } - - if selected_adapter.is_none() { - debug!( - "Searching for adapter with options: {:?}", - request_adapter_options - ); - selected_adapter = instance - .request_adapter(&request_adapter_options) - .await - .ok(); - } - - let adapter = selected_adapter.expect(GPU_NOT_FOUND_ERROR_MESSAGE); - let adapter_info = adapter.get_info(); - info!("{:?}", adapter_info); - - if adapter_info.device_type == DeviceType::Cpu { - warn!( - "The selected adapter is using a driver that only supports software rendering. \ - This is likely to be very slow. See https://bevy.org/learn/errors/b0006/" - ); - } - - // Maybe get features and limits based on what is supported by the adapter/backend - let mut features = wgpu::Features::empty(); - let mut limits = options.limits.clone(); - if matches!(options.priority, WgpuSettingsPriority::Functionality) { - features = adapter.features(); - if adapter_info.device_type == DeviceType::DiscreteGpu { - // `MAPPABLE_PRIMARY_BUFFERS` can have a significant, negative performance impact for - // discrete GPUs due to having to transfer data across the PCI-E bus and so it - // should not be automatically enabled in this case. It is however beneficial for - // integrated GPUs. - features.remove(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS); - } - - limits = adapter.limits(); - } - - // Enforce the disabled features - if let Some(disabled_features) = options.disabled_features { - features.remove(disabled_features); - } - // NOTE: |= is used here to ensure that any explicitly-enabled features are respected. - features |= options.features; - - // Enforce the limit constraints - if let Some(constrained_limits) = options.constrained_limits.as_ref() { - // NOTE: Respect the configured limits as an 'upper bound'. This means for 'max' limits, we - // take the minimum of the calculated limits according to the adapter/backend and the - // specified max_limits. For 'min' limits, take the maximum instead. This is intended to - // err on the side of being conservative. We can't claim 'higher' limits that are supported - // but we can constrain to 'lower' limits. - limits = wgpu::Limits { - max_texture_dimension_1d: limits - .max_texture_dimension_1d - .min(constrained_limits.max_texture_dimension_1d), - max_texture_dimension_2d: limits - .max_texture_dimension_2d - .min(constrained_limits.max_texture_dimension_2d), - max_texture_dimension_3d: limits - .max_texture_dimension_3d - .min(constrained_limits.max_texture_dimension_3d), - max_texture_array_layers: limits - .max_texture_array_layers - .min(constrained_limits.max_texture_array_layers), - max_bind_groups: limits - .max_bind_groups - .min(constrained_limits.max_bind_groups), - max_dynamic_uniform_buffers_per_pipeline_layout: limits - .max_dynamic_uniform_buffers_per_pipeline_layout - .min(constrained_limits.max_dynamic_uniform_buffers_per_pipeline_layout), - max_dynamic_storage_buffers_per_pipeline_layout: limits - .max_dynamic_storage_buffers_per_pipeline_layout - .min(constrained_limits.max_dynamic_storage_buffers_per_pipeline_layout), - max_sampled_textures_per_shader_stage: limits - .max_sampled_textures_per_shader_stage - .min(constrained_limits.max_sampled_textures_per_shader_stage), - max_samplers_per_shader_stage: limits - .max_samplers_per_shader_stage - .min(constrained_limits.max_samplers_per_shader_stage), - max_storage_buffers_per_shader_stage: limits - .max_storage_buffers_per_shader_stage - .min(constrained_limits.max_storage_buffers_per_shader_stage), - max_storage_textures_per_shader_stage: limits - .max_storage_textures_per_shader_stage - .min(constrained_limits.max_storage_textures_per_shader_stage), - max_uniform_buffers_per_shader_stage: limits - .max_uniform_buffers_per_shader_stage - .min(constrained_limits.max_uniform_buffers_per_shader_stage), - max_binding_array_elements_per_shader_stage: limits - .max_binding_array_elements_per_shader_stage - .min(constrained_limits.max_binding_array_elements_per_shader_stage), - max_binding_array_sampler_elements_per_shader_stage: limits - .max_binding_array_sampler_elements_per_shader_stage - .min(constrained_limits.max_binding_array_sampler_elements_per_shader_stage), - max_uniform_buffer_binding_size: limits - .max_uniform_buffer_binding_size - .min(constrained_limits.max_uniform_buffer_binding_size), - max_storage_buffer_binding_size: limits - .max_storage_buffer_binding_size - .min(constrained_limits.max_storage_buffer_binding_size), - max_vertex_buffers: limits - .max_vertex_buffers - .min(constrained_limits.max_vertex_buffers), - max_vertex_attributes: limits - .max_vertex_attributes - .min(constrained_limits.max_vertex_attributes), - max_vertex_buffer_array_stride: limits - .max_vertex_buffer_array_stride - .min(constrained_limits.max_vertex_buffer_array_stride), - max_push_constant_size: limits - .max_push_constant_size - .min(constrained_limits.max_push_constant_size), - min_uniform_buffer_offset_alignment: limits - .min_uniform_buffer_offset_alignment - .max(constrained_limits.min_uniform_buffer_offset_alignment), - min_storage_buffer_offset_alignment: limits - .min_storage_buffer_offset_alignment - .max(constrained_limits.min_storage_buffer_offset_alignment), - max_inter_stage_shader_components: limits - .max_inter_stage_shader_components - .min(constrained_limits.max_inter_stage_shader_components), - max_compute_workgroup_storage_size: limits - .max_compute_workgroup_storage_size - .min(constrained_limits.max_compute_workgroup_storage_size), - max_compute_invocations_per_workgroup: limits - .max_compute_invocations_per_workgroup - .min(constrained_limits.max_compute_invocations_per_workgroup), - max_compute_workgroup_size_x: limits - .max_compute_workgroup_size_x - .min(constrained_limits.max_compute_workgroup_size_x), - max_compute_workgroup_size_y: limits - .max_compute_workgroup_size_y - .min(constrained_limits.max_compute_workgroup_size_y), - max_compute_workgroup_size_z: limits - .max_compute_workgroup_size_z - .min(constrained_limits.max_compute_workgroup_size_z), - max_compute_workgroups_per_dimension: limits - .max_compute_workgroups_per_dimension - .min(constrained_limits.max_compute_workgroups_per_dimension), - max_buffer_size: limits - .max_buffer_size - .min(constrained_limits.max_buffer_size), - max_bindings_per_bind_group: limits - .max_bindings_per_bind_group - .min(constrained_limits.max_bindings_per_bind_group), - max_non_sampler_bindings: limits - .max_non_sampler_bindings - .min(constrained_limits.max_non_sampler_bindings), - max_blas_primitive_count: limits - .max_blas_primitive_count - .min(constrained_limits.max_blas_primitive_count), - max_blas_geometry_count: limits - .max_blas_geometry_count - .min(constrained_limits.max_blas_geometry_count), - max_tlas_instance_count: limits - .max_tlas_instance_count - .min(constrained_limits.max_tlas_instance_count), - max_color_attachments: limits - .max_color_attachments - .min(constrained_limits.max_color_attachments), - max_color_attachment_bytes_per_sample: limits - .max_color_attachment_bytes_per_sample - .min(constrained_limits.max_color_attachment_bytes_per_sample), - min_subgroup_size: limits - .min_subgroup_size - .max(constrained_limits.min_subgroup_size), - max_subgroup_size: limits - .max_subgroup_size - .min(constrained_limits.max_subgroup_size), - max_acceleration_structures_per_shader_stage: limits - .max_acceleration_structures_per_shader_stage - .min(constrained_limits.max_acceleration_structures_per_shader_stage), - max_task_workgroup_total_count: limits - .max_task_workgroup_total_count - .min(constrained_limits.max_task_workgroup_total_count), - max_task_workgroups_per_dimension: limits - .max_task_workgroups_per_dimension - .min(constrained_limits.max_task_workgroups_per_dimension), - max_mesh_output_layers: limits - .max_mesh_output_layers - .min(constrained_limits.max_mesh_output_layers), - max_mesh_multiview_count: limits - .max_mesh_multiview_count - .min(constrained_limits.max_mesh_multiview_count), - }; - } - - let device_descriptor = wgpu::DeviceDescriptor { - label: options.device_label.as_ref().map(AsRef::as_ref), - required_features: features, - required_limits: limits, - // SAFETY: TODO, see https://github.com/bevyengine/bevy/issues/22082 - experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() }, - memory_hints: options.memory_hints.clone(), - // See https://github.com/gfx-rs/wgpu/issues/5974 - trace: Trace::Off, - }; - - #[cfg(not(feature = "raw_vulkan_init"))] - let (device, queue) = adapter.request_device(&device_descriptor).await.unwrap(); - - #[cfg(feature = "raw_vulkan_init")] - let (device, queue) = raw_vulkan_init::create_raw_device( - &adapter, - &device_descriptor, - &raw_vulkan_init_settings, - &mut additional_vulkan_features, - ) - .await - .unwrap(); - - debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); - debug!("Configured wgpu adapter Features: {:#?}", device.features()); - - RenderResources( - RenderDevice::from(device), - RenderQueue(Arc::new(WgpuWrapper::new(queue))), - RenderAdapterInfo(WgpuWrapper::new(adapter_info)), - RenderAdapter(Arc::new(WgpuWrapper::new(adapter))), - RenderInstance(Arc::new(WgpuWrapper::new(instance))), - #[cfg(feature = "raw_vulkan_init")] - additional_vulkan_features, - ) -} - -/// The context with all information required to interact with the GPU. -/// -/// The [`RenderDevice`] is used to create render resources and the -/// [`CommandEncoder`] is used to record a series of GPU operations. -pub struct RenderContext<'w> { - render_device: RenderDevice, - command_encoder: Option, - command_buffer_queue: Vec>, - diagnostics_recorder: Option>, -} - -impl<'w> RenderContext<'w> { - /// Creates a new [`RenderContext`] from a [`RenderDevice`]. - pub fn new( - render_device: RenderDevice, - diagnostics_recorder: Option, - ) -> Self { - Self { - render_device, - command_encoder: None, - command_buffer_queue: Vec::new(), - diagnostics_recorder: diagnostics_recorder.map(Arc::new), - } - } - - /// Gets the underlying [`RenderDevice`]. - pub fn render_device(&self) -> &RenderDevice { - &self.render_device - } - - /// Gets the diagnostics recorder, used to track elapsed time and pipeline statistics - /// of various render and compute passes. - pub fn diagnostic_recorder(&self) -> impl RecordDiagnostics + use<> { - self.diagnostics_recorder.clone() - } - - /// Gets the current [`CommandEncoder`]. - pub fn command_encoder(&mut self) -> &mut CommandEncoder { - self.command_encoder.get_or_insert_with(|| { - self.render_device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()) - }) - } - - pub(crate) fn has_commands(&mut self) -> bool { - self.command_encoder.is_some() || !self.command_buffer_queue.is_empty() - } - - /// Creates a new [`TrackedRenderPass`] for the context, - /// configured using the provided `descriptor`. - pub fn begin_tracked_render_pass<'a>( - &'a mut self, - descriptor: RenderPassDescriptor<'_>, - ) -> TrackedRenderPass<'a> { - // Cannot use command_encoder() as we need to split the borrow on self - let command_encoder = self.command_encoder.get_or_insert_with(|| { - self.render_device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()) - }); - - let render_pass = command_encoder.begin_render_pass(&descriptor); - TrackedRenderPass::new(&self.render_device, render_pass) - } - - /// Append a [`CommandBuffer`] to the command buffer queue. - /// - /// If present, this will flush the currently unflushed [`CommandEncoder`] - /// into a [`CommandBuffer`] into the queue before appending the provided - /// buffer. - pub fn add_command_buffer(&mut self, command_buffer: CommandBuffer) { - self.flush_encoder(); - - self.command_buffer_queue - .push(QueuedCommandBuffer::Ready(command_buffer)); - } - - /// Append a function that will generate a [`CommandBuffer`] to the - /// command buffer queue, to be ran later. - /// - /// If present, this will flush the currently unflushed [`CommandEncoder`] - /// into a [`CommandBuffer`] into the queue before appending the provided - /// buffer. - pub fn add_command_buffer_generation_task( - &mut self, - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - task: impl FnOnce(RenderDevice) -> CommandBuffer + 'w + Send, - #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] - task: impl FnOnce(RenderDevice) -> CommandBuffer + 'w, - ) { - self.flush_encoder(); - - self.command_buffer_queue - .push(QueuedCommandBuffer::Task(Box::new(task))); - } - - /// Finalizes and returns the queue of [`CommandBuffer`]s. - /// - /// This function will wait until all command buffer generation tasks are complete - /// by running them in parallel (where supported). - /// - /// The [`CommandBuffer`]s will be returned in the order that they were added. - pub fn finish( - mut self, - ) -> ( - Vec, - RenderDevice, - Option, - ) { - self.flush_encoder(); - - let mut command_buffers = Vec::with_capacity(self.command_buffer_queue.len()); - - #[cfg(feature = "trace")] - let _command_buffer_generation_tasks_span = - info_span!("command_buffer_generation_tasks").entered(); - - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - { - let mut task_based_command_buffers = - bevy_tasks::ComputeTaskPool::get().scope(|task_pool| { - for (i, queued_command_buffer) in - self.command_buffer_queue.into_iter().enumerate() - { - match queued_command_buffer { - QueuedCommandBuffer::Ready(command_buffer) => { - command_buffers.push((i, command_buffer)); - } - QueuedCommandBuffer::Task(command_buffer_generation_task) => { - let render_device = self.render_device.clone(); - task_pool.spawn(async move { - (i, command_buffer_generation_task(render_device)) - }); - } - } - } - }); - command_buffers.append(&mut task_based_command_buffers); - } - - #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] - for (i, queued_command_buffer) in self.command_buffer_queue.into_iter().enumerate() { - match queued_command_buffer { - QueuedCommandBuffer::Ready(command_buffer) => { - command_buffers.push((i, command_buffer)); - } - QueuedCommandBuffer::Task(command_buffer_generation_task) => { - let render_device = self.render_device.clone(); - command_buffers.push((i, command_buffer_generation_task(render_device))); - } - } - } - - #[cfg(feature = "trace")] - drop(_command_buffer_generation_tasks_span); - - command_buffers.sort_unstable_by_key(|(i, _)| *i); - - let mut command_buffers = command_buffers - .into_iter() - .map(|(_, cb)| cb) - .collect::>(); - - let mut diagnostics_recorder = self.diagnostics_recorder.take().map(|v| { - Arc::try_unwrap(v) - .ok() - .expect("diagnostic recorder shouldn't be held longer than necessary") - }); - - if let Some(recorder) = &mut diagnostics_recorder { - let mut command_encoder = self - .render_device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - recorder.resolve(&mut command_encoder); - command_buffers.push(command_encoder.finish()); - } - - (command_buffers, self.render_device, diagnostics_recorder) - } - - fn flush_encoder(&mut self) { - if let Some(encoder) = self.command_encoder.take() { - self.command_buffer_queue - .push(QueuedCommandBuffer::Ready(encoder.finish())); - } - } -} - -enum QueuedCommandBuffer<'w> { - Ready(CommandBuffer), - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - Task(Box CommandBuffer + 'w + Send>), - #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] - Task(Box CommandBuffer + 'w>), -} diff --git a/third_party/bevy_render/src/renderer/raw_vulkan_init.rs b/third_party/bevy_render/src/renderer/raw_vulkan_init.rs deleted file mode 100644 index 9730569..0000000 --- a/third_party/bevy_render/src/renderer/raw_vulkan_init.rs +++ /dev/null @@ -1,148 +0,0 @@ -use alloc::sync::Arc; -use bevy_ecs::resource::Resource; -use bevy_platform::collections::HashSet; -use core::any::{Any, TypeId}; -use thiserror::Error; -use wgpu::{ - hal::api::Vulkan, Adapter, Device, DeviceDescriptor, Instance, InstanceDescriptor, Queue, -}; - -/// When the `raw_vulkan_init` feature is enabled, these settings will be used to configure the raw vulkan instance. -#[derive(Resource, Default, Clone)] -pub struct RawVulkanInitSettings { - // SAFETY: this must remain private to ensure that registering callbacks is unsafe - create_instance_callbacks: Vec< - Arc< - dyn Fn( - &mut wgpu::hal::vulkan::CreateInstanceCallbackArgs, - &mut AdditionalVulkanFeatures, - ) + Send - + Sync, - >, - >, - // SAFETY: this must remain private to ensure that registering callbacks is unsafe - create_device_callbacks: Vec< - Arc< - dyn Fn( - &mut wgpu::hal::vulkan::CreateDeviceCallbackArgs, - &wgpu::hal::vulkan::Adapter, - &mut AdditionalVulkanFeatures, - ) + Send - + Sync, - >, - >, -} - -impl RawVulkanInitSettings { - /// Adds a new Vulkan create instance callback. See [`wgpu::hal::vulkan::Instance::init_with_callback`] for details. - /// - /// # Safety - /// - Callback must not remove features. - /// - Callback must not change anything to what the instance does not support. - pub unsafe fn add_create_instance_callback( - &mut self, - callback: impl Fn(&mut wgpu::hal::vulkan::CreateInstanceCallbackArgs, &mut AdditionalVulkanFeatures) - + Send - + Sync - + 'static, - ) { - self.create_instance_callbacks.push(Arc::new(callback)); - } - - /// Adds a new Vulkan create device callback. See [`wgpu::hal::vulkan::Adapter::open_with_callback`] for details. - /// - /// # Safety - /// - Callback must not remove features. - /// - Callback must not change anything to what the device does not support. - pub unsafe fn add_create_device_callback( - &mut self, - callback: impl Fn( - &mut wgpu::hal::vulkan::CreateDeviceCallbackArgs, - &wgpu::hal::vulkan::Adapter, - &mut AdditionalVulkanFeatures, - ) + Send - + Sync - + 'static, - ) { - self.create_device_callbacks.push(Arc::new(callback)); - } -} - -pub(crate) fn create_raw_vulkan_instance( - instance_descriptor: &InstanceDescriptor, - settings: &RawVulkanInitSettings, - additional_features: &mut AdditionalVulkanFeatures, -) -> Instance { - // SAFETY: Registering callbacks is unsafe. Callback authors promise not to remove features - // or change the instance to something it does not support - unsafe { - wgpu::hal::vulkan::Instance::init_with_callback( - &wgpu::hal::InstanceDescriptor { - name: "wgpu", - flags: instance_descriptor.flags, - memory_budget_thresholds: instance_descriptor.memory_budget_thresholds, - backend_options: instance_descriptor.backend_options.clone(), - }, - Some(Box::new(|mut args| { - for callback in &settings.create_instance_callbacks { - (callback)(&mut args, additional_features); - } - })), - ) - .map(|raw_instance| Instance::from_hal::(raw_instance)) - .unwrap_or_else(|_| Instance::new(instance_descriptor)) - } -} - -pub(crate) async fn create_raw_device( - adapter: &Adapter, - device_descriptor: &DeviceDescriptor<'_>, - settings: &RawVulkanInitSettings, - additional_features: &mut AdditionalVulkanFeatures, -) -> Result<(Device, Queue), CreateRawVulkanDeviceError> { - // SAFETY: Registering callbacks is unsafe. Callback authors promise not to remove features - // or change the adapter to something it does not support - unsafe { - let Some(raw_adapter) = adapter.as_hal::() else { - return Ok(adapter.request_device(device_descriptor).await?); - }; - let open_device = raw_adapter.open_with_callback( - device_descriptor.required_features, - &device_descriptor.memory_hints, - Some(Box::new(|mut args| { - for callback in &settings.create_device_callbacks { - (callback)(&mut args, &raw_adapter, additional_features); - } - })), - )?; - - Ok(adapter.create_device_from_hal::(open_device, device_descriptor)?) - } -} - -#[derive(Error, Debug)] -pub(crate) enum CreateRawVulkanDeviceError { - #[error(transparent)] - RequestDeviceError(#[from] wgpu::RequestDeviceError), - #[error(transparent)] - DeviceError(#[from] wgpu::hal::DeviceError), -} - -/// A list of additional Vulkan features that are supported by the current wgpu instance / adapter. This is populated -/// by callbacks defined in [`RawVulkanInitSettings`] -#[derive(Resource, Default, Clone)] -pub struct AdditionalVulkanFeatures(HashSet); - -impl AdditionalVulkanFeatures { - pub fn insert(&mut self) { - self.0.insert(TypeId::of::()); - } - - pub fn has(&self) -> bool { - self.0.contains(&TypeId::of::()) - } - - pub fn remove(&mut self) { - self.0.remove(&TypeId::of::()); - } -} diff --git a/third_party/bevy_render/src/renderer/render_device.rs b/third_party/bevy_render/src/renderer/render_device.rs deleted file mode 100644 index f0334a9..0000000 --- a/third_party/bevy_render/src/renderer/render_device.rs +++ /dev/null @@ -1,310 +0,0 @@ -use super::RenderQueue; -use crate::render_resource::{ - BindGroup, BindGroupLayout, Buffer, ComputePipeline, RawRenderPipelineDescriptor, - RenderPipeline, Sampler, Texture, -}; -use crate::renderer::WgpuWrapper; -use bevy_ecs::resource::Resource; -use wgpu::{ - util::DeviceExt, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, - BindGroupLayoutEntry, BufferAsyncError, BufferBindingType, PollError, PollStatus, -}; - -/// This GPU device is responsible for the creation of most rendering and compute resources. -#[derive(Resource, Clone)] -pub struct RenderDevice { - device: WgpuWrapper, -} - -impl From for RenderDevice { - fn from(device: wgpu::Device) -> Self { - Self::new(WgpuWrapper::new(device)) - } -} - -impl RenderDevice { - pub fn new(device: WgpuWrapper) -> Self { - Self { device } - } - - /// List all [`Features`](wgpu::Features) that may be used with this device. - /// - /// Functions may panic if you use unsupported features. - #[inline] - pub fn features(&self) -> wgpu::Features { - self.device.features() - } - - /// List all [`Limits`](wgpu::Limits) that were requested of this device. - /// - /// If any of these limits are exceeded, functions may panic. - #[inline] - pub fn limits(&self) -> wgpu::Limits { - self.device.limits() - } - - /// Creates a [`ShaderModule`](wgpu::ShaderModule) from either SPIR-V or WGSL source code. - /// - /// # Safety - /// - /// Creates a shader module with user-customizable runtime checks which allows shaders to - /// perform operations which can lead to undefined behavior like indexing out of bounds, - /// To avoid UB, ensure any unchecked shaders are sound! - /// This method should never be called for user-supplied shaders. - #[inline] - pub unsafe fn create_shader_module( - &self, - desc: wgpu::ShaderModuleDescriptor, - ) -> wgpu::ShaderModule { - #[cfg(feature = "spirv_shader_passthrough")] - match &desc.source { - wgpu::ShaderSource::SpirV(source) - if self - .features() - .contains(wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS) => - { - // SAFETY: - // This call passes binary data to the backend as-is and can potentially result in a driver crash or bogus behavior. - // No attempt is made to ensure that data is valid SPIR-V. - unsafe { - self.device.create_shader_module_passthrough( - wgpu::ShaderModuleDescriptorPassthrough { - label: desc.label, - spirv: Some(source.clone()), - ..Default::default() - }, - ) - } - } - // SAFETY: - // - // This call passes binary data to the backend as-is and can potentially result in a driver crash or bogus behavior. - // No attempt is made to ensure that data is valid SPIR-V. - _ => unsafe { - self.device - .create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked()) - }, - } - #[cfg(not(feature = "spirv_shader_passthrough"))] - // SAFETY: the caller is responsible for upholding the safety requirements - unsafe { - self.device - .create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked()) - } - } - - /// Creates and validates a [`ShaderModule`](wgpu::ShaderModule) from either SPIR-V or WGSL source code. - /// - /// See [`ValidateShader`](bevy_shader::ValidateShader) for more information on the tradeoffs involved with shader validation. - #[inline] - pub fn create_and_validate_shader_module( - &self, - desc: wgpu::ShaderModuleDescriptor, - ) -> wgpu::ShaderModule { - #[cfg(feature = "spirv_shader_passthrough")] - match &desc.source { - wgpu::ShaderSource::SpirV(_source) => panic!("no safety checks are performed for spirv shaders. use `create_shader_module` instead"), - _ => self.device.create_shader_module(desc), - } - #[cfg(not(feature = "spirv_shader_passthrough"))] - self.device.create_shader_module(desc) - } - - /// Check for resource cleanups and mapping callbacks. - /// - /// Return `true` if the queue is empty, or `false` if there are more queue - /// submissions still in flight. (Note that, unless access to the [`wgpu::Queue`] is - /// coordinated somehow, this information could be out of date by the time - /// the caller receives it. `Queue`s can be shared between threads, so - /// other threads could submit new work at any time.) - /// - /// no-op on the web, device is automatically polled. - #[inline] - pub fn poll(&self, maintain: wgpu::PollType) -> Result { - self.device.poll(maintain) - } - - /// Creates an empty [`CommandEncoder`](wgpu::CommandEncoder). - #[inline] - pub fn create_command_encoder( - &self, - desc: &wgpu::CommandEncoderDescriptor, - ) -> wgpu::CommandEncoder { - self.device.create_command_encoder(desc) - } - - /// Creates an empty [`RenderBundleEncoder`](wgpu::RenderBundleEncoder). - #[inline] - pub fn create_render_bundle_encoder( - &self, - desc: &wgpu::RenderBundleEncoderDescriptor, - ) -> wgpu::RenderBundleEncoder<'_> { - self.device.create_render_bundle_encoder(desc) - } - - /// Creates a new [`BindGroup`](wgpu::BindGroup). - #[inline] - pub fn create_bind_group<'a>( - &self, - label: impl Into>, - layout: &'a BindGroupLayout, - entries: &'a [BindGroupEntry<'a>], - ) -> BindGroup { - let wgpu_bind_group = self.device.create_bind_group(&BindGroupDescriptor { - label: label.into(), - layout, - entries, - }); - BindGroup::from(wgpu_bind_group) - } - - /// Creates a [`BindGroupLayout`](wgpu::BindGroupLayout). - #[inline] - pub fn create_bind_group_layout<'a>( - &self, - label: impl Into>, - entries: &'a [BindGroupLayoutEntry], - ) -> BindGroupLayout { - BindGroupLayout::from( - self.device - .create_bind_group_layout(&BindGroupLayoutDescriptor { - label: label.into(), - entries, - }), - ) - } - - /// Creates a [`PipelineLayout`](wgpu::PipelineLayout). - #[inline] - pub fn create_pipeline_layout( - &self, - desc: &wgpu::PipelineLayoutDescriptor, - ) -> wgpu::PipelineLayout { - self.device.create_pipeline_layout(desc) - } - - /// Creates a [`RenderPipeline`]. - #[inline] - pub fn create_render_pipeline(&self, desc: &RawRenderPipelineDescriptor) -> RenderPipeline { - let wgpu_render_pipeline = self.device.create_render_pipeline(desc); - RenderPipeline::from(wgpu_render_pipeline) - } - - /// Creates a [`ComputePipeline`]. - #[inline] - pub fn create_compute_pipeline( - &self, - desc: &wgpu::ComputePipelineDescriptor, - ) -> ComputePipeline { - let wgpu_compute_pipeline = self.device.create_compute_pipeline(desc); - ComputePipeline::from(wgpu_compute_pipeline) - } - - /// Creates a [`Buffer`]. - pub fn create_buffer(&self, desc: &wgpu::BufferDescriptor) -> Buffer { - let wgpu_buffer = self.device.create_buffer(desc); - Buffer::from(wgpu_buffer) - } - - /// Creates a [`Buffer`] and initializes it with the specified data. - pub fn create_buffer_with_data(&self, desc: &wgpu::util::BufferInitDescriptor) -> Buffer { - let wgpu_buffer = self.device.create_buffer_init(desc); - Buffer::from(wgpu_buffer) - } - - /// Creates a new [`Texture`] and initializes it with the specified data. - /// - /// `desc` specifies the general format of the texture. - /// `data` is the raw data. - pub fn create_texture_with_data( - &self, - render_queue: &RenderQueue, - desc: &wgpu::TextureDescriptor, - order: wgpu::util::TextureDataOrder, - data: &[u8], - ) -> Texture { - let wgpu_texture = - self.device - .create_texture_with_data(render_queue.as_ref(), desc, order, data); - Texture::from(wgpu_texture) - } - - /// Creates a new [`Texture`]. - /// - /// `desc` specifies the general format of the texture. - pub fn create_texture(&self, desc: &wgpu::TextureDescriptor) -> Texture { - let wgpu_texture = self.device.create_texture(desc); - Texture::from(wgpu_texture) - } - - /// Creates a new [`Sampler`]. - /// - /// `desc` specifies the behavior of the sampler. - pub fn create_sampler(&self, desc: &wgpu::SamplerDescriptor) -> Sampler { - let wgpu_sampler = self.device.create_sampler(desc); - Sampler::from(wgpu_sampler) - } - - /// Initializes [`Surface`](wgpu::Surface) for presentation. - /// - /// # Panics - /// - /// - A old [`SurfaceTexture`](wgpu::SurfaceTexture) is still alive referencing an old surface. - /// - Texture format requested is unsupported on the surface. - pub fn configure_surface(&self, surface: &wgpu::Surface, config: &wgpu::SurfaceConfiguration) { - surface.configure(&self.device, config); - } - - /// Returns the wgpu [`Device`](wgpu::Device). - pub fn wgpu_device(&self) -> &wgpu::Device { - &self.device - } - - pub fn map_buffer( - &self, - buffer: &wgpu::BufferSlice, - map_mode: wgpu::MapMode, - callback: impl FnOnce(Result<(), BufferAsyncError>) + Send + 'static, - ) { - buffer.map_async(map_mode, callback); - } - - // Rounds up `row_bytes` to be a multiple of [`wgpu::COPY_BYTES_PER_ROW_ALIGNMENT`]. - pub const fn align_copy_bytes_per_row(row_bytes: usize) -> usize { - let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; - - // If row_bytes is aligned calculate a value just under the next aligned value. - // Otherwise calculate a value greater than the next aligned value. - let over_aligned = row_bytes + align - 1; - - // Round the number *down* to the nearest aligned value. - (over_aligned / align) * align - } - - pub fn get_supported_read_only_binding_type( - &self, - buffers_per_shader_stage: u32, - ) -> BufferBindingType { - if self.limits().max_storage_buffers_per_shader_stage >= buffers_per_shader_stage { - BufferBindingType::Storage { read_only: true } - } else { - BufferBindingType::Uniform - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn align_copy_bytes_per_row() { - // Test for https://github.com/bevyengine/bevy/issues/16992 - let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; - - assert_eq!(RenderDevice::align_copy_bytes_per_row(0), 0); - assert_eq!(RenderDevice::align_copy_bytes_per_row(1), align); - assert_eq!(RenderDevice::align_copy_bytes_per_row(align + 1), align * 2); - assert_eq!(RenderDevice::align_copy_bytes_per_row(align), align); - } -} diff --git a/third_party/bevy_render/src/renderer/wgpu_wrapper.rs b/third_party/bevy_render/src/renderer/wgpu_wrapper.rs deleted file mode 100644 index 272d0dd..0000000 --- a/third_party/bevy_render/src/renderer/wgpu_wrapper.rs +++ /dev/null @@ -1,50 +0,0 @@ -/// A wrapper to safely make `wgpu` types Send / Sync on web with atomics enabled. -/// -/// On web with `atomics` enabled the inner value can only be accessed -/// or dropped on the `wgpu` thread or else a panic will occur. -/// On other platforms the wrapper simply contains the wrapped value. -#[derive(Debug, Clone)] -pub struct WgpuWrapper( - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] T, - #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] send_wrapper::SendWrapper, -); - -// SAFETY: SendWrapper is always Send + Sync. -#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] -#[expect(unsafe_code, reason = "Blanket-impl Send requires unsafe.")] -unsafe impl Send for WgpuWrapper {} -#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] -#[expect(unsafe_code, reason = "Blanket-impl Sync requires unsafe.")] -unsafe impl Sync for WgpuWrapper {} - -impl WgpuWrapper { - /// Constructs a new instance of `WgpuWrapper` which will wrap the specified value. - pub fn new(t: T) -> Self { - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - return Self(t); - #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] - return Self(send_wrapper::SendWrapper::new(t)); - } - - /// Unwraps the value. - pub fn into_inner(self) -> T { - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - return self.0; - #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] - return self.0.take(); - } -} - -impl core::ops::Deref for WgpuWrapper { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl core::ops::DerefMut for WgpuWrapper { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/third_party/bevy_render/src/settings.rs b/third_party/bevy_render/src/settings.rs deleted file mode 100644 index c3b284e..0000000 --- a/third_party/bevy_render/src/settings.rs +++ /dev/null @@ -1,226 +0,0 @@ -use crate::renderer::{ - RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue, -}; -use alloc::borrow::Cow; - -pub use wgpu::{ - Backends, Dx12Compiler, Features as WgpuFeatures, Gles3MinorVersion, InstanceFlags, - Limits as WgpuLimits, MemoryHints, PowerPreference, -}; -use wgpu::{DxcShaderModel, MemoryBudgetThresholds}; - -/// Configures the priority used when automatically configuring the features/limits of `wgpu`. -#[derive(Clone)] -pub enum WgpuSettingsPriority { - /// WebGPU default features and limits - Compatibility, - /// The maximum supported features and limits of the adapter and backend - Functionality, - /// WebGPU default limits plus additional constraints in order to be compatible with WebGL2 - WebGL2, -} - -/// Provides configuration for renderer initialization. Use [`RenderDevice::features`](RenderDevice::features), -/// [`RenderDevice::limits`](RenderDevice::limits), and the [`RenderAdapterInfo`] -/// resource to get runtime information about the actual adapter, backend, features, and limits. -/// NOTE: [`Backends::DX12`](Backends::DX12), [`Backends::METAL`](Backends::METAL), and -/// [`Backends::VULKAN`](Backends::VULKAN) are enabled by default for non-web and the best choice -/// is automatically selected. Web using the `webgl` feature uses [`Backends::GL`](Backends::GL). -/// NOTE: If you want to use [`Backends::GL`](Backends::GL) in a native app on `Windows` and/or `macOS`, you must -/// use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle) and enable the `gles` feature. This is -/// because wgpu requires EGL to create a GL context without a window and only ANGLE supports that. -#[derive(Clone)] -pub struct WgpuSettings { - pub device_label: Option>, - pub backends: Option, - pub power_preference: PowerPreference, - pub priority: WgpuSettingsPriority, - /// The features to ensure are enabled regardless of what the adapter/backend supports. - /// Setting these explicitly may cause renderer initialization to fail. - pub features: WgpuFeatures, - /// The features to ensure are disabled regardless of what the adapter/backend supports - pub disabled_features: Option, - /// The imposed limits. - pub limits: WgpuLimits, - /// The constraints on limits allowed regardless of what the adapter/backend supports - pub constrained_limits: Option, - /// The shader compiler to use for the DX12 backend. - pub dx12_shader_compiler: Dx12Compiler, - /// Allows you to choose which minor version of GLES3 to use (3.0, 3.1, 3.2, or automatic) - /// This only applies when using ANGLE and the GL backend. - pub gles3_minor_version: Gles3MinorVersion, - /// These are for controlling WGPU's debug information to eg. enable validation and shader debug info in release builds. - pub instance_flags: InstanceFlags, - /// This hints to the WGPU device about the preferred memory allocation strategy. - pub memory_hints: MemoryHints, - /// The thresholds for device memory budget. - pub instance_memory_budget_thresholds: MemoryBudgetThresholds, - /// If true, will force wgpu to use a software renderer, if available. - pub force_fallback_adapter: bool, - /// The name of the adapter to use. - pub adapter_name: Option, -} - -impl Default for WgpuSettings { - fn default() -> Self { - let default_backends = if cfg!(all( - feature = "webgl", - target_arch = "wasm32", - not(feature = "webgpu") - )) { - Backends::GL - } else if cfg!(all(feature = "webgpu", target_arch = "wasm32")) { - Backends::BROWSER_WEBGPU - } else { - Backends::all() - }; - - let backends = Some(Backends::from_env().unwrap_or(default_backends)); - - let power_preference = - PowerPreference::from_env().unwrap_or(PowerPreference::HighPerformance); - - let priority = settings_priority_from_env().unwrap_or(WgpuSettingsPriority::Functionality); - - let limits = if cfg!(all( - feature = "webgl", - target_arch = "wasm32", - not(feature = "webgpu") - )) || matches!(priority, WgpuSettingsPriority::WebGL2) - { - wgpu::Limits::downlevel_webgl2_defaults() - } else { - #[expect(clippy::allow_attributes, reason = "`unused_mut` is not always linted")] - #[allow( - unused_mut, - reason = "This variable needs to be mutable if the `ci_limits` feature is enabled" - )] - let mut limits = wgpu::Limits::default(); - #[cfg(feature = "ci_limits")] - { - limits.max_storage_textures_per_shader_stage = 4; - limits.max_texture_dimension_3d = 1024; - } - limits - }; - - let dx12_shader_compiler = - Dx12Compiler::from_env().unwrap_or(if cfg!(feature = "statically-linked-dxc") { - Dx12Compiler::StaticDxc - } else { - let dxc = "dxcompiler.dll"; - - if cfg!(target_os = "windows") && std::fs::metadata(dxc).is_ok() { - Dx12Compiler::DynamicDxc { - dxc_path: String::from(dxc), - max_shader_model: DxcShaderModel::V6_7, - } - } else { - Dx12Compiler::Fxc - } - }); - - let gles3_minor_version = Gles3MinorVersion::from_env().unwrap_or_default(); - - let instance_flags = InstanceFlags::default().with_env(); - - Self { - device_label: Default::default(), - backends, - power_preference, - priority, - features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, - disabled_features: None, - limits, - constrained_limits: None, - dx12_shader_compiler, - gles3_minor_version, - instance_flags, - memory_hints: MemoryHints::default(), - instance_memory_budget_thresholds: MemoryBudgetThresholds::default(), - force_fallback_adapter: false, - adapter_name: None, - } - } -} - -#[derive(Clone)] -pub struct RenderResources( - pub RenderDevice, - pub RenderQueue, - pub RenderAdapterInfo, - pub RenderAdapter, - pub RenderInstance, - #[cfg(feature = "raw_vulkan_init")] - pub crate::renderer::raw_vulkan_init::AdditionalVulkanFeatures, -); - -/// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin). -#[expect( - clippy::large_enum_variant, - reason = "See https://github.com/bevyengine/bevy/issues/19220" -)] -pub enum RenderCreation { - /// Allows renderer resource initialization to happen outside of the rendering plugin. - Manual(RenderResources), - /// Lets the rendering plugin create resources itself. - Automatic(WgpuSettings), -} - -impl RenderCreation { - /// Function to create a [`RenderCreation::Manual`] variant. - pub fn manual( - device: RenderDevice, - queue: RenderQueue, - adapter_info: RenderAdapterInfo, - adapter: RenderAdapter, - instance: RenderInstance, - #[cfg(feature = "raw_vulkan_init")] - additional_vulkan_features: crate::renderer::raw_vulkan_init::AdditionalVulkanFeatures, - ) -> Self { - RenderResources( - device, - queue, - adapter_info, - adapter, - instance, - #[cfg(feature = "raw_vulkan_init")] - additional_vulkan_features, - ) - .into() - } -} - -impl From for RenderCreation { - fn from(value: RenderResources) -> Self { - Self::Manual(value) - } -} - -impl Default for RenderCreation { - fn default() -> Self { - Self::Automatic(Default::default()) - } -} - -impl From for RenderCreation { - fn from(value: WgpuSettings) -> Self { - Self::Automatic(value) - } -} - -/// Get a features/limits priority from the environment variable `WGPU_SETTINGS_PRIO` -pub fn settings_priority_from_env() -> Option { - Some( - match std::env::var("WGPU_SETTINGS_PRIO") - .as_deref() - .map(str::to_lowercase) - .as_deref() - { - Ok("compatibility") => WgpuSettingsPriority::Compatibility, - Ok("functionality") => WgpuSettingsPriority::Functionality, - Ok("webgl2") => WgpuSettingsPriority::WebGL2, - _ => return None, - }, - ) -} diff --git a/third_party/bevy_render/src/storage.rs b/third_party/bevy_render/src/storage.rs deleted file mode 100644 index 0362add..0000000 --- a/third_party/bevy_render/src/storage.rs +++ /dev/null @@ -1,158 +0,0 @@ -use crate::{ - render_asset::{AssetExtractionError, PrepareAssetError, RenderAsset, RenderAssetPlugin}, - render_resource::{Buffer, BufferUsages}, - renderer::RenderDevice, -}; -use bevy_app::{App, Plugin}; -use bevy_asset::{Asset, AssetApp, AssetId, RenderAssetUsages}; -use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; -use bevy_reflect::{prelude::ReflectDefault, Reflect}; -use bevy_utils::default; -use encase::{internal::WriteInto, ShaderType}; -use wgpu::util::BufferInitDescriptor; - -/// Adds [`ShaderStorageBuffer`] as an asset that is extracted and uploaded to the GPU. -#[derive(Default)] -pub struct StoragePlugin; - -impl Plugin for StoragePlugin { - fn build(&self, app: &mut App) { - app.add_plugins(RenderAssetPlugin::::default()) - .init_asset::() - .register_asset_reflect::(); - } -} - -/// A storage buffer that is prepared as a [`RenderAsset`] and uploaded to the GPU. -#[derive(Asset, Reflect, Debug, Clone)] -#[reflect(opaque)] -#[reflect(Default, Debug, Clone)] -pub struct ShaderStorageBuffer { - /// Optional data used to initialize the buffer. - pub data: Option>, - /// The buffer description used to create the buffer. - pub buffer_description: wgpu::BufferDescriptor<'static>, - /// The asset usage of the storage buffer. - pub asset_usage: RenderAssetUsages, -} - -impl Default for ShaderStorageBuffer { - fn default() -> Self { - Self { - data: None, - buffer_description: wgpu::BufferDescriptor { - label: None, - size: 0, - usage: BufferUsages::STORAGE, - mapped_at_creation: false, - }, - asset_usage: RenderAssetUsages::default(), - } - } -} - -impl ShaderStorageBuffer { - /// Creates a new storage buffer with the given data and asset usage. - pub fn new(data: &[u8], asset_usage: RenderAssetUsages) -> Self { - let mut storage = ShaderStorageBuffer { - data: Some(data.to_vec()), - ..default() - }; - storage.asset_usage = asset_usage; - storage - } - - /// Creates a new storage buffer with the given size and asset usage. - pub fn with_size(size: usize, asset_usage: RenderAssetUsages) -> Self { - let mut storage = ShaderStorageBuffer { - data: None, - ..default() - }; - storage.buffer_description.size = size as u64; - storage.buffer_description.mapped_at_creation = false; - storage.asset_usage = asset_usage; - storage - } - - /// Sets the data of the storage buffer to the given [`ShaderType`]. - pub fn set_data(&mut self, value: T) - where - T: ShaderType + WriteInto, - { - let size = value.size().get() as usize; - let mut wrapper = encase::StorageBuffer::>::new(Vec::with_capacity(size)); - wrapper.write(&value).unwrap(); - self.data = Some(wrapper.into_inner()); - } -} - -impl From for ShaderStorageBuffer -where - T: ShaderType + WriteInto, -{ - fn from(value: T) -> Self { - let size = value.size().get() as usize; - let mut wrapper = encase::StorageBuffer::>::new(Vec::with_capacity(size)); - wrapper.write(&value).unwrap(); - Self::new(wrapper.as_ref(), RenderAssetUsages::default()) - } -} - -/// A storage buffer that is prepared as a [`RenderAsset`] and uploaded to the GPU. -pub struct GpuShaderStorageBuffer { - pub buffer: Buffer, - pub had_data: bool, -} - -impl RenderAsset for GpuShaderStorageBuffer { - type SourceAsset = ShaderStorageBuffer; - type Param = SRes; - - fn asset_usage(source_asset: &Self::SourceAsset) -> RenderAssetUsages { - source_asset.asset_usage - } - - fn take_gpu_data( - source: &mut Self::SourceAsset, - previous_gpu_asset: Option<&Self>, - ) -> Result { - let data = source.data.take(); - - let valid_upload = data.is_some() || previous_gpu_asset.is_none_or(|prev| !prev.had_data); - - valid_upload - .then(|| Self::SourceAsset { - data, - ..source.clone() - }) - .ok_or(AssetExtractionError::AlreadyExtracted) - } - - fn prepare_asset( - source_asset: Self::SourceAsset, - _: AssetId, - render_device: &mut SystemParamItem, - _: Option<&Self>, - ) -> Result> { - match source_asset.data { - Some(data) => { - let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { - label: source_asset.buffer_description.label, - contents: &data, - usage: source_asset.buffer_description.usage, - }); - Ok(GpuShaderStorageBuffer { - buffer, - had_data: true, - }) - } - None => { - let buffer = render_device.create_buffer(&source_asset.buffer_description); - Ok(GpuShaderStorageBuffer { - buffer, - had_data: false, - }) - } - } - } -} diff --git a/third_party/bevy_render/src/sync_component.rs b/third_party/bevy_render/src/sync_component.rs deleted file mode 100644 index dd7eca1..0000000 --- a/third_party/bevy_render/src/sync_component.rs +++ /dev/null @@ -1,42 +0,0 @@ -use core::marker::PhantomData; - -use bevy_app::{App, Plugin}; -use bevy_ecs::component::Component; - -use crate::sync_world::{EntityRecord, PendingSyncEntity, SyncToRenderWorld}; - -/// Plugin that registers a component for automatic sync to the render world. See [`SyncWorldPlugin`] for more information. -/// -/// This plugin is automatically added by [`ExtractComponentPlugin`], and only needs to be added for manual extraction implementations. -/// -/// # Implementation details -/// -/// It adds [`SyncToRenderWorld`] as a required component to make the [`SyncWorldPlugin`] aware of the component, and -/// handles cleanup of the component in the render world when it is removed from an entity. -/// -/// # Warning -/// When the component is removed from the main world entity, all components are removed from the entity in the render world. -/// This is done in order to handle components with custom extraction logic and derived state. -/// -/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin -/// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin -pub struct SyncComponentPlugin(PhantomData); - -impl Default for SyncComponentPlugin { - fn default() -> Self { - Self(PhantomData) - } -} - -impl Plugin for SyncComponentPlugin { - fn build(&self, app: &mut App) { - app.register_required_components::(); - - app.world_mut() - .register_component_hooks::() - .on_remove(|mut world, context| { - let mut pending = world.resource_mut::(); - pending.push(EntityRecord::ComponentRemoved(context.entity)); - }); - } -} diff --git a/third_party/bevy_render/src/sync_world.rs b/third_party/bevy_render/src/sync_world.rs deleted file mode 100644 index 6a1a1fa..0000000 --- a/third_party/bevy_render/src/sync_world.rs +++ /dev/null @@ -1,602 +0,0 @@ -use bevy_app::Plugin; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{ - component::Component, - entity::{ContainsEntity, Entity, EntityEquivalent, EntityHash}, - lifecycle::{Add, Remove}, - observer::On, - query::With, - reflect::ReflectComponent, - resource::Resource, - system::{Local, Query, ResMut, SystemState}, - world::{Mut, World}, -}; -use bevy_platform::collections::{HashMap, HashSet}; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; - -/// A plugin that synchronizes entities with [`SyncToRenderWorld`] between the main world and the render world. -/// -/// All entities with the [`SyncToRenderWorld`] component are kept in sync. It -/// is automatically added as a required component by [`ExtractComponentPlugin`] -/// and [`SyncComponentPlugin`], so it doesn't need to be added manually when -/// spawning or as a required component when either of these plugins are used. -/// -/// # Implementation -/// -/// Bevy's renderer is architected independently from the main app. -/// It operates in its own separate ECS [`World`], so the renderer logic can run in parallel with the main world logic. -/// This is called "Pipelined Rendering", see [`PipelinedRenderingPlugin`] for more information. -/// -/// [`SyncWorldPlugin`] is the first thing that runs every frame and it maintains an entity-to-entity mapping -/// between the main world and the render world. -/// It does so by spawning and despawning entities in the render world, to match spawned and despawned entities in the main world. -/// The link between synced entities is maintained by the [`RenderEntity`] and [`MainEntity`] components. -/// -/// The [`RenderEntity`] contains the corresponding render world entity of a main world entity, while [`MainEntity`] contains -/// the corresponding main world entity of a render world entity. -/// For convenience, [`QueryData`](bevy_ecs::query::QueryData) implementations are provided for both components: -/// adding [`MainEntity`] to a query (without a `&`) will return the corresponding main world [`Entity`], -/// and adding [`RenderEntity`] will return the corresponding render world [`Entity`]. -/// If you have access to the component itself, the underlying entities can be accessed by calling `.id()`. -/// -/// Synchronization is necessary preparation for extraction ([`ExtractSchedule`](crate::ExtractSchedule)), which copies over component data from the main -/// to the render world for these entities. -/// -/// ```text -/// |--------------------------------------------------------------------| -/// | | | Main world update | -/// | sync | extract |---------------------------------------------------| -/// | | | Render world update | -/// |--------------------------------------------------------------------| -/// ``` -/// -/// An example for synchronized main entities 1v1 and 18v1 -/// -/// ```text -/// |---------------------------Main World------------------------------| -/// | Entity | Component | -/// |-------------------------------------------------------------------| -/// | ID: 1v1 | PointLight | RenderEntity(ID: 3V1) | SyncToRenderWorld | -/// | ID: 18v1 | PointLight | RenderEntity(ID: 5V1) | SyncToRenderWorld | -/// |-------------------------------------------------------------------| -/// -/// |----------Render World-----------| -/// | Entity | Component | -/// |---------------------------------| -/// | ID: 3v1 | MainEntity(ID: 1V1) | -/// | ID: 5v1 | MainEntity(ID: 18V1) | -/// |---------------------------------| -/// -/// ``` -/// -/// Note that this effectively establishes a link between the main world entity and the render world entity. -/// Not every entity needs to be synchronized, however; only entities with the [`SyncToRenderWorld`] component are synced. -/// Adding [`SyncToRenderWorld`] to a main world component will establish such a link. -/// Once a synchronized main entity is despawned, its corresponding render entity will be automatically -/// despawned in the next `sync`. -/// -/// The sync step does not copy any of component data between worlds, since its often not necessary to transfer over all -/// the components of a main world entity. -/// The render world probably cares about a `Position` component, but not a `Velocity` component. -/// The extraction happens in its own step, independently from, and after synchronization. -/// -/// Moreover, [`SyncWorldPlugin`] only synchronizes *entities*. [`RenderAsset`](crate::render_asset::RenderAsset)s like meshes and textures are handled -/// differently. -/// -/// [`PipelinedRenderingPlugin`]: crate::pipelined_rendering::PipelinedRenderingPlugin -/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin -/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin -#[derive(Default)] -pub struct SyncWorldPlugin; - -impl Plugin for SyncWorldPlugin { - fn build(&self, app: &mut bevy_app::App) { - app.init_resource::(); - app.add_observer( - |add: On, mut pending: ResMut| { - pending.push(EntityRecord::Added(add.entity)); - }, - ); - app.add_observer( - |remove: On, - mut pending: ResMut, - query: Query<&RenderEntity>| { - if let Ok(e) = query.get(remove.entity) { - pending.push(EntityRecord::Removed(*e)); - }; - }, - ); - } -} -/// Marker component that indicates that its entity needs to be synchronized to the render world. -/// -/// This component is automatically added as a required component by [`ExtractComponentPlugin`] and [`SyncComponentPlugin`]. -/// For more information see [`SyncWorldPlugin`]. -/// -/// NOTE: This component should persist throughout the entity's entire lifecycle. -/// If this component is removed from its entity, the entity will be despawned. -/// -/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin -/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin -#[derive(Component, Copy, Clone, Debug, Default, Reflect)] -#[reflect(Component, Default, Clone)] -#[component(storage = "SparseSet")] -pub struct SyncToRenderWorld; - -/// Component added on the main world entities that are synced to the Render World in order to keep track of the corresponding render world entity. -/// -/// Can also be used as a newtype wrapper for render world entities. -#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, Reflect)] -#[component(clone_behavior = Ignore)] -#[reflect(Component, Clone)] -pub struct RenderEntity(Entity); -impl RenderEntity { - #[inline] - pub fn id(&self) -> Entity { - self.0 - } -} - -impl From for RenderEntity { - fn from(entity: Entity) -> Self { - RenderEntity(entity) - } -} - -impl ContainsEntity for RenderEntity { - fn entity(&self) -> Entity { - self.id() - } -} - -// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. -unsafe impl EntityEquivalent for RenderEntity {} - -/// Component added on the render world entities to keep track of the corresponding main world entity. -/// -/// Can also be used as a newtype wrapper for main world entities. -#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Reflect)] -#[reflect(Component, Clone)] -pub struct MainEntity(Entity); -impl MainEntity { - #[inline] - pub fn id(&self) -> Entity { - self.0 - } -} - -impl From for MainEntity { - fn from(entity: Entity) -> Self { - MainEntity(entity) - } -} - -impl ContainsEntity for MainEntity { - fn entity(&self) -> Entity { - self.id() - } -} - -// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. -unsafe impl EntityEquivalent for MainEntity {} - -/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing with a [`MainEntity`]. -pub type MainEntityHashMap = HashMap; - -/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing with a [`MainEntity`].. -pub type MainEntityHashSet = HashSet; - -/// Marker component that indicates that its entity needs to be despawned at the end of the frame. -#[derive(Component, Copy, Clone, Debug, Default, Reflect)] -#[reflect(Component, Default, Clone)] -pub struct TemporaryRenderEntity; - -/// A record enum to what entities with [`SyncToRenderWorld`] have been added or removed. -#[derive(Debug)] -pub(crate) enum EntityRecord { - /// When an entity is spawned on the main world, notify the render world so that it can spawn a corresponding - /// entity. This contains the main world entity. - Added(Entity), - /// When an entity is despawned on the main world, notify the render world so that the corresponding entity can be - /// despawned. This contains the render world entity. - Removed(RenderEntity), - /// When a component is removed from an entity, notify the render world so that the corresponding component can be - /// removed. This contains the main world entity. - ComponentRemoved(Entity), -} - -// Entity Record in MainWorld pending to Sync -#[derive(Resource, Default, Deref, DerefMut)] -pub(crate) struct PendingSyncEntity { - records: Vec, -} - -pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut World) { - main_world.resource_scope(|world, mut pending: Mut| { - // TODO : batching record - for record in pending.drain(..) { - match record { - EntityRecord::Added(e) => { - if let Ok(mut main_entity) = world.get_entity_mut(e) { - match main_entity.entry::() { - bevy_ecs::world::ComponentEntry::Occupied(_) => { - panic!("Attempting to synchronize an entity that has already been synchronized!"); - } - bevy_ecs::world::ComponentEntry::Vacant(entry) => { - let id = render_world.spawn(MainEntity(e)).id(); - - entry.insert(RenderEntity(id)); - } - }; - } - } - EntityRecord::Removed(render_entity) => { - if let Ok(ec) = render_world.get_entity_mut(render_entity.id()) { - ec.despawn(); - }; - } - EntityRecord::ComponentRemoved(main_entity) => { - let Some(mut render_entity) = world.get_mut::(main_entity) else { - continue; - }; - if let Ok(render_world_entity) = render_world.get_entity_mut(render_entity.id()) { - // In order to handle components that extract to derived components, we clear the entity - // and let the extraction system re-add the components. - render_world_entity.despawn(); - - let id = render_world.spawn(MainEntity(main_entity)).id(); - render_entity.0 = id; - } - }, - } - } - }); -} - -pub(crate) fn despawn_temporary_render_entities( - world: &mut World, - state: &mut SystemState>>, - mut local: Local>, -) { - let query = state.get(world); - - local.extend(query.iter()); - - // Ensure next frame allocation keeps order - local.sort_unstable_by_key(|e| e.index()); - for e in local.drain(..).rev() { - world.despawn(e); - } -} - -/// This module exists to keep the complex unsafe code out of the main module. -/// -/// The implementations for both [`MainEntity`] and [`RenderEntity`] should stay in sync, -/// and are based off of the `&T` implementation in `bevy_ecs`. -mod render_entities_world_query_impls { - use super::{MainEntity, RenderEntity}; - - use bevy_ecs::{ - archetype::Archetype, - change_detection::Tick, - component::{ComponentId, Components}, - entity::Entity, - query::{ - ArchetypeQueryData, FilteredAccess, QueryData, ReadOnlyQueryData, - ReleaseStateQueryData, WorldQuery, - }, - storage::{Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, World}, - }; - - /// SAFETY: defers completely to `&RenderEntity` implementation, - /// and then only modifies the output safely. - unsafe impl WorldQuery for RenderEntity { - type Fetch<'w> = <&'static RenderEntity as WorldQuery>::Fetch<'w>; - type State = <&'static RenderEntity as WorldQuery>::State; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>( - fetch: Self::Fetch<'wlong>, - ) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - component_id: &'s ComponentId, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. - unsafe { - <&RenderEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) - } - } - - const IS_DENSE: bool = <&'static RenderEntity as WorldQuery>::IS_DENSE; - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - component_id: &'s ComponentId, - archetype: &'w Archetype, - table: &'w Table, - ) { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. - unsafe { - <&RenderEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table); - } - } - - #[inline] - unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w>, - &component_id: &'s ComponentId, - table: &'w Table, - ) { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. - unsafe { <&RenderEntity as WorldQuery>::set_table(fetch, &component_id, table) } - } - - fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { - <&RenderEntity as WorldQuery>::update_component_access(&component_id, access); - } - - fn init_state(world: &mut World) -> ComponentId { - <&RenderEntity as WorldQuery>::init_state(world) - } - - fn get_state(components: &Components) -> Option { - <&RenderEntity as WorldQuery>::get_state(components) - } - - fn matches_component_set( - &state: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - <&RenderEntity as WorldQuery>::matches_component_set(&state, set_contains_id) - } - } - - // SAFETY: Component access of Self::ReadOnly is a subset of Self. - // Self::ReadOnly matches exactly the same archetypes/tables as Self. - unsafe impl QueryData for RenderEntity { - const IS_READ_ONLY: bool = true; - const IS_ARCHETYPAL: bool = <&MainEntity as QueryData>::IS_ARCHETYPAL; - type ReadOnly = RenderEntity; - type Item<'w, 's> = Entity; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Option> { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. - let component = - unsafe { <&RenderEntity as QueryData>::fetch(state, fetch, entity, table_row) }; - component.map(RenderEntity::id) - } - - fn iter_access( - state: &Self::State, - ) -> impl Iterator> { - <&RenderEntity as QueryData>::iter_access(state) - } - } - - // SAFETY: the underlying `Entity` is copied, and no mutable access is provided. - unsafe impl ReadOnlyQueryData for RenderEntity {} - - impl ArchetypeQueryData for RenderEntity {} - - impl ReleaseStateQueryData for RenderEntity { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } - } - - /// SAFETY: defers completely to `&RenderEntity` implementation, - /// and then only modifies the output safely. - unsafe impl WorldQuery for MainEntity { - type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>; - type State = <&'static MainEntity as WorldQuery>::State; - - fn shrink_fetch<'wlong: 'wshort, 'wshort>( - fetch: Self::Fetch<'wlong>, - ) -> Self::Fetch<'wshort> { - fetch - } - - #[inline] - unsafe fn init_fetch<'w, 's>( - world: UnsafeWorldCell<'w>, - component_id: &'s ComponentId, - last_run: Tick, - this_run: Tick, - ) -> Self::Fetch<'w> { - // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. - unsafe { - <&MainEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) - } - } - - const IS_DENSE: bool = <&'static MainEntity as WorldQuery>::IS_DENSE; - - #[inline] - unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w>, - component_id: &ComponentId, - archetype: &'w Archetype, - table: &'w Table, - ) { - // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. - unsafe { - <&MainEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table); - } - } - - #[inline] - unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w>, - &component_id: &'s ComponentId, - table: &'w Table, - ) { - // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. - unsafe { <&MainEntity as WorldQuery>::set_table(fetch, &component_id, table) } - } - - fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { - <&MainEntity as WorldQuery>::update_component_access(&component_id, access); - } - - fn init_state(world: &mut World) -> ComponentId { - <&MainEntity as WorldQuery>::init_state(world) - } - - fn get_state(components: &Components) -> Option { - <&MainEntity as WorldQuery>::get_state(components) - } - - fn matches_component_set( - &state: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - <&MainEntity as WorldQuery>::matches_component_set(&state, set_contains_id) - } - } - - // SAFETY: Component access of Self::ReadOnly is a subset of Self. - // Self::ReadOnly matches exactly the same archetypes/tables as Self. - unsafe impl QueryData for MainEntity { - const IS_READ_ONLY: bool = true; - const IS_ARCHETYPAL: bool = <&MainEntity as QueryData>::IS_ARCHETYPAL; - type ReadOnly = MainEntity; - type Item<'w, 's> = Entity; - - fn shrink<'wlong: 'wshort, 'wshort, 's>( - item: Self::Item<'wlong, 's>, - ) -> Self::Item<'wshort, 's> { - item - } - - #[inline(always)] - unsafe fn fetch<'w, 's>( - state: &'s Self::State, - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Option> { - // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. - let component = - unsafe { <&MainEntity as QueryData>::fetch(state, fetch, entity, table_row) }; - component.map(MainEntity::id) - } - - fn iter_access( - state: &Self::State, - ) -> impl Iterator> { - <&MainEntity as QueryData>::iter_access(state) - } - } - - // SAFETY: the underlying `Entity` is copied, and no mutable access is provided. - unsafe impl ReadOnlyQueryData for MainEntity {} - - impl ArchetypeQueryData for MainEntity {} - - impl ReleaseStateQueryData for MainEntity { - fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { - item - } - } -} - -#[cfg(test)] -mod tests { - use bevy_ecs::{ - component::Component, - entity::Entity, - lifecycle::{Add, Remove}, - observer::On, - query::With, - system::{Query, ResMut}, - world::World, - }; - - use super::{ - entity_sync_system, EntityRecord, MainEntity, PendingSyncEntity, RenderEntity, - SyncToRenderWorld, - }; - - #[derive(Component)] - struct RenderDataComponent; - - #[test] - fn sync_world() { - let mut main_world = World::new(); - let mut render_world = World::new(); - main_world.init_resource::(); - - main_world.add_observer( - |add: On, mut pending: ResMut| { - pending.push(EntityRecord::Added(add.entity)); - }, - ); - main_world.add_observer( - |remove: On, - mut pending: ResMut, - query: Query<&RenderEntity>| { - if let Ok(e) = query.get(remove.entity) { - pending.push(EntityRecord::Removed(*e)); - }; - }, - ); - - // spawn some empty entities for test - for _ in 0..99 { - main_world.spawn_empty(); - } - - // spawn - let main_entity = main_world - .spawn(RenderDataComponent) - // indicates that its entity needs to be synchronized to the render world - .insert(SyncToRenderWorld) - .id(); - - entity_sync_system(&mut main_world, &mut render_world); - - let mut q = render_world.query_filtered::>(); - - // Only one synchronized entity - assert!(q.iter(&render_world).count() == 1); - - let render_entity = q.single(&render_world).unwrap(); - let render_entity_component = main_world.get::(main_entity).unwrap(); - - assert!(render_entity_component.id() == render_entity); - - let main_entity_component = render_world - .get::(render_entity_component.id()) - .unwrap(); - - assert!(main_entity_component.id() == main_entity); - - // despawn - main_world.despawn(main_entity); - - entity_sync_system(&mut main_world, &mut render_world); - - // Only one synchronized entity - assert!(q.iter(&render_world).count() == 0); - } -} diff --git a/third_party/bevy_render/src/texture/fallback_image.rs b/third_party/bevy_render/src/texture/fallback_image.rs deleted file mode 100644 index 738c84b..0000000 --- a/third_party/bevy_render/src/texture/fallback_image.rs +++ /dev/null @@ -1,274 +0,0 @@ -use crate::{ - render_resource::*, - renderer::{RenderDevice, RenderQueue}, - texture::{DefaultImageSampler, GpuImage}, -}; -use bevy_asset::RenderAssetUsages; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{ - prelude::{FromWorld, Res, ResMut}, - resource::Resource, - system::SystemParam, -}; -use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo}; -use bevy_platform::collections::HashMap; - -/// A [`RenderApp`](crate::RenderApp) resource that contains the default "fallback image", -/// which can be used in situations where an image was not explicitly defined. The most common -/// use case is [`AsBindGroup`] implementations (such as materials) that support optional textures. -/// -/// Defaults to a 1x1 fully opaque white texture, (1.0, 1.0, 1.0, 1.0) which makes multiplying -/// it with other colors a no-op. -#[derive(Resource)] -pub struct FallbackImage { - /// Fallback image for [`TextureViewDimension::D1`]. - pub d1: GpuImage, - /// Fallback image for [`TextureViewDimension::D2`]. - pub d2: GpuImage, - /// Fallback image for [`TextureViewDimension::D2Array`]. - pub d2_array: GpuImage, - /// Fallback image for [`TextureViewDimension::Cube`]. - pub cube: GpuImage, - /// Fallback image for [`TextureViewDimension::CubeArray`]. - pub cube_array: GpuImage, - /// Fallback image for [`TextureViewDimension::D3`]. - pub d3: GpuImage, -} - -impl FallbackImage { - /// Returns the appropriate fallback image for the given texture dimension. - pub fn get(&self, texture_dimension: TextureViewDimension) -> &GpuImage { - match texture_dimension { - TextureViewDimension::D1 => &self.d1, - TextureViewDimension::D2 => &self.d2, - TextureViewDimension::D2Array => &self.d2_array, - TextureViewDimension::Cube => &self.cube, - TextureViewDimension::CubeArray => &self.cube_array, - TextureViewDimension::D3 => &self.d3, - } - } -} - -/// A [`RenderApp`](crate::RenderApp) resource that contains a _zero-filled_ "fallback image", -/// which can be used in place of [`FallbackImage`], when a fully transparent or black fallback -/// is required instead of fully opaque white. -/// -/// Defaults to a 1x1 fully transparent black texture, (0.0, 0.0, 0.0, 0.0) which makes adding -/// or alpha-blending it to other colors a no-op. -#[derive(Resource, Deref)] -pub struct FallbackImageZero(GpuImage); - -/// A [`RenderApp`](crate::RenderApp) resource that contains a "cubemap fallback image", -/// which can be used in situations where an image was not explicitly defined. The most common -/// use case is [`AsBindGroup`] implementations (such as materials) that support optional textures. -#[derive(Resource, Deref)] -pub struct FallbackImageCubemap(GpuImage); - -fn fallback_image_new( - render_device: &RenderDevice, - render_queue: &RenderQueue, - default_sampler: &DefaultImageSampler, - format: TextureFormat, - dimension: TextureViewDimension, - samples: u32, - value: u8, -) -> GpuImage { - // TODO make this configurable per channel - - let extents = Extent3d { - width: 1, - height: 1, - depth_or_array_layers: match dimension { - TextureViewDimension::Cube | TextureViewDimension::CubeArray => 6, - _ => 1, - }, - }; - - // We can't create textures with data when it's a depth texture or when using multiple samples - let create_texture_with_data = !format.is_depth_stencil_format() && samples == 1; - - let image_dimension = dimension.compatible_texture_dimension(); - let mut image = if create_texture_with_data { - let data = vec![value; format.pixel_size().unwrap_or(0)]; - Image::new_fill( - extents, - image_dimension, - &data, - format, - RenderAssetUsages::RENDER_WORLD, - ) - } else { - let mut image = Image::default_uninit(); - image.texture_descriptor.dimension = TextureDimension::D2; - image.texture_descriptor.size = extents; - image.texture_descriptor.format = format; - image - }; - image.texture_descriptor.sample_count = samples; - if image_dimension == TextureDimension::D2 { - image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT; - } - - let texture = if create_texture_with_data { - render_device.create_texture_with_data( - render_queue, - &image.texture_descriptor, - TextureDataOrder::default(), - &image.data.expect("Image has no data"), - ) - } else { - render_device.create_texture(&image.texture_descriptor) - }; - - let texture_view = texture.create_view(&TextureViewDescriptor { - dimension: Some(dimension), - array_layer_count: Some(extents.depth_or_array_layers), - ..TextureViewDescriptor::default() - }); - let sampler = match image.sampler { - ImageSampler::Default => (**default_sampler).clone(), - ImageSampler::Descriptor(ref descriptor) => { - render_device.create_sampler(&descriptor.as_wgpu()) - } - }; - GpuImage { - texture, - texture_view, - texture_format: image.texture_descriptor.format, - texture_view_format: image.texture_view_descriptor.and_then(|v| v.format), - sampler, - size: image.texture_descriptor.size, - mip_level_count: image.texture_descriptor.mip_level_count, - had_data: true, - } -} - -impl FromWorld for FallbackImage { - fn from_world(world: &mut bevy_ecs::prelude::World) -> Self { - let render_device = world.resource::(); - let render_queue = world.resource::(); - let default_sampler = world.resource::(); - Self { - d1: fallback_image_new( - render_device, - render_queue, - default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::D1, - 1, - 255, - ), - d2: fallback_image_new( - render_device, - render_queue, - default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::D2, - 1, - 255, - ), - d2_array: fallback_image_new( - render_device, - render_queue, - default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::D2Array, - 1, - 255, - ), - cube: fallback_image_new( - render_device, - render_queue, - default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::Cube, - 1, - 255, - ), - cube_array: fallback_image_new( - render_device, - render_queue, - default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::CubeArray, - 1, - 255, - ), - d3: fallback_image_new( - render_device, - render_queue, - default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::D3, - 1, - 255, - ), - } - } -} - -impl FromWorld for FallbackImageZero { - fn from_world(world: &mut bevy_ecs::prelude::World) -> Self { - let render_device = world.resource::(); - let render_queue = world.resource::(); - let default_sampler = world.resource::(); - Self(fallback_image_new( - render_device, - render_queue, - default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::D2, - 1, - 0, - )) - } -} - -impl FromWorld for FallbackImageCubemap { - fn from_world(world: &mut bevy_ecs::prelude::World) -> Self { - let render_device = world.resource::(); - let render_queue = world.resource::(); - let default_sampler = world.resource::(); - Self(fallback_image_new( - render_device, - render_queue, - default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::Cube, - 1, - 255, - )) - } -} - -/// A Cache of fallback textures that uses the sample count and `TextureFormat` as a key -/// -/// # WARNING -/// Images using MSAA with sample count > 1 are not initialized with data, therefore, -/// you shouldn't sample them before writing data to them first. -#[derive(Resource, Deref, DerefMut, Default)] -pub struct FallbackImageFormatMsaaCache(HashMap<(u32, TextureFormat), GpuImage>); - -#[derive(SystemParam)] -pub struct FallbackImageMsaa<'w> { - cache: ResMut<'w, FallbackImageFormatMsaaCache>, - render_device: Res<'w, RenderDevice>, - render_queue: Res<'w, RenderQueue>, - default_sampler: Res<'w, DefaultImageSampler>, -} - -impl<'w> FallbackImageMsaa<'w> { - pub fn image_for_samplecount(&mut self, sample_count: u32, format: TextureFormat) -> &GpuImage { - self.cache.entry((sample_count, format)).or_insert_with(|| { - fallback_image_new( - &self.render_device, - &self.render_queue, - &self.default_sampler, - format, - TextureViewDimension::D2, - sample_count, - 255, - ) - }) - } -} diff --git a/third_party/bevy_render/src/texture/gpu_image.rs b/third_party/bevy_render/src/texture/gpu_image.rs deleted file mode 100644 index 43483fc..0000000 --- a/third_party/bevy_render/src/texture/gpu_image.rs +++ /dev/null @@ -1,153 +0,0 @@ -use crate::{ - render_asset::{AssetExtractionError, PrepareAssetError, RenderAsset}, - render_resource::{DefaultImageSampler, Sampler, Texture, TextureView}, - renderer::{RenderDevice, RenderQueue}, -}; -use bevy_asset::{AssetId, RenderAssetUsages}; -use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; -use bevy_image::{Image, ImageSampler}; -use bevy_math::{AspectRatio, UVec2}; -use tracing::warn; -use wgpu::{Extent3d, TextureFormat, TextureViewDescriptor}; - -/// The GPU-representation of an [`Image`]. -/// Consists of the [`Texture`], its [`TextureView`] and the corresponding [`Sampler`], and the texture's size. -#[derive(Debug, Clone)] -pub struct GpuImage { - pub texture: Texture, - pub texture_view: TextureView, - pub texture_format: TextureFormat, - pub texture_view_format: Option, - pub sampler: Sampler, - pub size: Extent3d, - pub mip_level_count: u32, - pub had_data: bool, -} - -impl RenderAsset for GpuImage { - type SourceAsset = Image; - type Param = ( - SRes, - SRes, - SRes, - ); - - #[inline] - fn asset_usage(image: &Self::SourceAsset) -> RenderAssetUsages { - image.asset_usage - } - - fn take_gpu_data( - source: &mut Self::SourceAsset, - previous_gpu_asset: Option<&Self>, - ) -> Result { - let data = source.data.take(); - - // check if this image originally had data and no longer does, that implies it - // has already been extracted - let valid_upload = data.is_some() || previous_gpu_asset.is_none_or(|prev| !prev.had_data); - - valid_upload - .then(|| Self::SourceAsset { - data, - ..source.clone() - }) - .ok_or(AssetExtractionError::AlreadyExtracted) - } - - #[inline] - fn byte_len(image: &Self::SourceAsset) -> Option { - image.data.as_ref().map(Vec::len) - } - - /// Converts the extracted image into a [`GpuImage`]. - fn prepare_asset( - image: Self::SourceAsset, - _: AssetId, - (render_device, render_queue, default_sampler): &mut SystemParamItem, - previous_asset: Option<&Self>, - ) -> Result> { - let had_data = image.data.is_some(); - let texture = if let Some(ref data) = image.data { - render_device.create_texture_with_data( - render_queue, - &image.texture_descriptor, - image.data_order, - data, - ) - } else { - let new_texture = render_device.create_texture(&image.texture_descriptor); - if image.copy_on_resize { - if let Some(previous) = previous_asset { - let mut command_encoder = - render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("copy_image_on_resize"), - }); - let copy_size = Extent3d { - width: image.texture_descriptor.size.width.min(previous.size.width), - height: image - .texture_descriptor - .size - .height - .min(previous.size.height), - depth_or_array_layers: image - .texture_descriptor - .size - .depth_or_array_layers - .min(previous.size.depth_or_array_layers), - }; - - command_encoder.copy_texture_to_texture( - previous.texture.as_image_copy(), - new_texture.as_image_copy(), - copy_size, - ); - render_queue.submit([command_encoder.finish()]); - } else { - warn!("No previous asset to copy from for image: {:?}", image); - } - } - new_texture - }; - - let texture_view = texture.create_view( - image - .texture_view_descriptor - .as_ref() - .unwrap_or(&TextureViewDescriptor::default()), - ); - let sampler = match image.sampler { - ImageSampler::Default => (***default_sampler).clone(), - ImageSampler::Descriptor(descriptor) => { - render_device.create_sampler(&descriptor.as_wgpu()) - } - }; - - Ok(GpuImage { - texture, - texture_view, - texture_format: image.texture_descriptor.format, - texture_view_format: image.texture_view_descriptor.and_then(|v| v.format), - sampler, - size: image.texture_descriptor.size, - mip_level_count: image.texture_descriptor.mip_level_count, - had_data, - }) - } -} - -impl GpuImage { - /// Returns the aspect ratio (width / height) of a 2D image. - #[inline] - pub fn aspect_ratio(&self) -> AspectRatio { - AspectRatio::try_from_pixels(self.size.width, self.size.height).expect( - "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values", - ) - } - - /// Returns the size of a 2D image. - #[inline] - pub fn size_2d(&self) -> UVec2 { - UVec2::new(self.size.width, self.size.height) - } -} diff --git a/third_party/bevy_render/src/texture/manual_texture_view.rs b/third_party/bevy_render/src/texture/manual_texture_view.rs deleted file mode 100644 index a9f3a3a..0000000 --- a/third_party/bevy_render/src/texture/manual_texture_view.rs +++ /dev/null @@ -1,68 +0,0 @@ -use bevy_camera::ManualTextureViewHandle; -use bevy_ecs::{prelude::Component, resource::Resource}; -use bevy_image::BevyDefault; -use bevy_math::UVec2; -use bevy_platform::collections::HashMap; -use bevy_render_macros::ExtractResource; -use wgpu::TextureFormat; - -use crate::render_resource::TextureView; - -/// A manually managed [`TextureView`] for use as a [`bevy_camera::RenderTarget`]. -#[derive(Debug, Clone, Component)] -pub struct ManualTextureView { - pub texture_view: TextureView, - pub size: UVec2, - pub view_format: TextureFormat, -} - -impl ManualTextureView { - pub fn with_default_format(texture_view: TextureView, size: UVec2) -> Self { - Self { - texture_view, - size, - view_format: TextureFormat::bevy_default(), - } - } -} - -/// Resource that stores manually managed [`ManualTextureView`]s for use as a [`RenderTarget`](bevy_camera::RenderTarget). -/// This type dereferences to a `HashMap`. -/// To add a new texture view, pick a new [`ManualTextureViewHandle`] and insert it into the map. -/// Then, to render to the view, set a [`Camera`](bevy_camera::Camera)s `target` to `RenderTarget::TextureView(handle)`. -/// ```ignore -/// # use bevy_ecs::prelude::*; -/// # let mut world = World::default(); -/// # world.insert_resource(ManualTextureViews::default()); -/// # let texture_view = todo!(); -/// let manual_views = world.resource_mut::(); -/// let manual_view = ManualTextureView::with_default_format(texture_view, UVec2::new(1024, 1024)); -/// -/// // Choose an unused handle value; it's likely only you are inserting manual views. -/// const MANUAL_VIEW_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle::new(42); -/// manual_views.insert(MANUAL_VIEW_HANDLE, manual_view); -/// -/// // Now you can spawn a Camera that renders to the manual view: -/// # use bevy_camera::{Camera, RenderTarget}; -/// world.spawn(Camera { -/// target: RenderTarget::TextureView(MANUAL_VIEW_HANDLE), -/// ..Default::default() -/// }); -/// ``` -/// Bevy will then use the `ManualTextureViews` resource to find your texture view and render to it. -#[derive(Default, Clone, Resource, ExtractResource)] -pub struct ManualTextureViews(HashMap); - -impl core::ops::Deref for ManualTextureViews { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl core::ops::DerefMut for ManualTextureViews { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/third_party/bevy_render/src/texture/mod.rs b/third_party/bevy_render/src/texture/mod.rs deleted file mode 100644 index 3d451b5..0000000 --- a/third_party/bevy_render/src/texture/mod.rs +++ /dev/null @@ -1,73 +0,0 @@ -mod fallback_image; -mod gpu_image; -mod manual_texture_view; -mod texture_attachment; -mod texture_cache; - -pub use crate::render_resource::DefaultImageSampler; -use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats, ImageLoader, ImagePlugin}; -pub use fallback_image::*; -pub use gpu_image::*; -pub use manual_texture_view::*; -pub use texture_attachment::*; -pub use texture_cache::*; - -use crate::{ - extract_resource::ExtractResourcePlugin, render_asset::RenderAssetPlugin, - renderer::RenderDevice, Render, RenderApp, RenderSystems, -}; -use bevy_app::{App, Plugin}; -use bevy_asset::AssetApp; -use bevy_ecs::prelude::*; -use tracing::warn; - -#[derive(Default)] -pub struct TexturePlugin; - -impl Plugin for TexturePlugin { - fn build(&self, app: &mut App) { - app.add_plugins(( - RenderAssetPlugin::::default(), - ExtractResourcePlugin::::default(), - )) - .init_resource::(); - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::().add_systems( - Render, - update_texture_cache_system.in_set(RenderSystems::Cleanup), - ); - } - } - - fn finish(&self, app: &mut App) { - if !ImageLoader::SUPPORTED_FORMATS.is_empty() { - let supported_compressed_formats = if let Some(resource) = - app.world().get_resource::() - { - resource.0 - } else { - warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \ - RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend."); - CompressedImageFormats::NONE - }; - - app.register_asset_loader(ImageLoader::new(supported_compressed_formats)); - } - let default_sampler = app.get_added_plugins::()[0] - .default_sampler - .clone(); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - let default_sampler = { - let device = render_app.world().resource::(); - device.create_sampler(&default_sampler.as_wgpu()) - }; - render_app - .insert_resource(DefaultImageSampler(default_sampler)) - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::(); - } - } -} diff --git a/third_party/bevy_render/src/texture/texture_attachment.rs b/third_party/bevy_render/src/texture/texture_attachment.rs deleted file mode 100644 index 3e4fbb8..0000000 --- a/third_party/bevy_render/src/texture/texture_attachment.rs +++ /dev/null @@ -1,172 +0,0 @@ -use super::CachedTexture; -use crate::render_resource::{TextureFormat, TextureView}; -use alloc::sync::Arc; -use bevy_color::LinearRgba; -use core::sync::atomic::{AtomicBool, Ordering}; -use wgpu::{ - LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp, -}; - -/// A wrapper for a [`CachedTexture`] that is used as a [`RenderPassColorAttachment`]. -#[derive(Clone)] -pub struct ColorAttachment { - pub texture: CachedTexture, - pub resolve_target: Option, - pub previous_frame_texture: Option, - clear_color: Option, - is_first_call: Arc, -} - -impl ColorAttachment { - pub fn new( - texture: CachedTexture, - resolve_target: Option, - previous_frame_texture: Option, - clear_color: Option, - ) -> Self { - Self { - texture, - resolve_target, - previous_frame_texture, - clear_color, - is_first_call: Arc::new(AtomicBool::new(true)), - } - } - - /// Get this texture view as an attachment. The attachment will be cleared with a value of - /// `clear_color` if this is the first time calling this function, otherwise it will be loaded. - /// - /// The returned attachment will always have writing enabled (`store: StoreOp::Load`). - pub fn get_attachment(&self) -> RenderPassColorAttachment<'_> { - if let Some(resolve_target) = self.resolve_target.as_ref() { - let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst); - - RenderPassColorAttachment { - view: &resolve_target.default_view, - depth_slice: None, - resolve_target: Some(&self.texture.default_view), - ops: Operations { - load: match (self.clear_color, first_call) { - (Some(clear_color), true) => LoadOp::Clear(clear_color.into()), - (None, _) | (Some(_), false) => LoadOp::Load, - }, - store: StoreOp::Store, - }, - } - } else { - self.get_unsampled_attachment() - } - } - - /// Get this texture view as an attachment, without the resolve target. The attachment will be cleared with - /// a value of `clear_color` if this is the first time calling this function, otherwise it will be loaded. - /// - /// The returned attachment will always have writing enabled (`store: StoreOp::Load`). - pub fn get_unsampled_attachment(&self) -> RenderPassColorAttachment<'_> { - let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst); - - RenderPassColorAttachment { - view: &self.texture.default_view, - depth_slice: None, - resolve_target: None, - ops: Operations { - load: match (self.clear_color, first_call) { - (Some(clear_color), true) => LoadOp::Clear(clear_color.into()), - (None, _) | (Some(_), false) => LoadOp::Load, - }, - store: StoreOp::Store, - }, - } - } - - pub(crate) fn mark_as_cleared(&self) { - self.is_first_call.store(false, Ordering::SeqCst); - } -} - -/// A wrapper for a [`TextureView`] that is used as a depth-only [`RenderPassDepthStencilAttachment`]. -#[derive(Clone)] -pub struct DepthAttachment { - pub view: TextureView, - clear_value: Option, - is_first_call: Arc, -} - -impl DepthAttachment { - pub fn new(view: TextureView, clear_value: Option) -> Self { - Self { - view, - clear_value, - is_first_call: Arc::new(AtomicBool::new(clear_value.is_some())), - } - } - - /// Get this texture view as an attachment. The attachment will be cleared with a value of - /// `clear_value` if this is the first time calling this function with `store` == [`StoreOp::Store`], - /// and a clear value was provided, otherwise it will be loaded. - pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment<'_> { - let first_call = self - .is_first_call - .fetch_and(store != StoreOp::Store, Ordering::SeqCst); - - RenderPassDepthStencilAttachment { - view: &self.view, - depth_ops: Some(Operations { - load: if first_call { - // If first_call is true, then a clear value will always have been provided in the constructor - LoadOp::Clear(self.clear_value.unwrap()) - } else { - LoadOp::Load - }, - store, - }), - stencil_ops: None, - } - } -} - -/// A wrapper for a [`TextureView`] that is used as a [`RenderPassColorAttachment`] for a view -/// target's final output texture. -#[derive(Clone)] -pub struct OutputColorAttachment { - pub view: TextureView, - pub view_format: TextureFormat, - is_first_call: Arc, -} - -impl OutputColorAttachment { - pub fn new(view: TextureView, view_format: TextureFormat) -> Self { - Self { - view, - view_format, - is_first_call: Arc::new(AtomicBool::new(true)), - } - } - - /// Get this texture view as an attachment. The attachment will be cleared with a value of - /// the provided `clear_color` if this is the first time calling this function, otherwise it - /// will be loaded. - pub fn get_attachment(&self, clear_color: Option) -> RenderPassColorAttachment<'_> { - let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst); - - RenderPassColorAttachment { - view: &self.view, - depth_slice: None, - resolve_target: None, - ops: Operations { - load: match (clear_color, first_call) { - (Some(clear_color), true) => LoadOp::Clear(clear_color.into()), - (None, _) | (Some(_), false) => LoadOp::Load, - }, - store: StoreOp::Store, - }, - } - } - - /// Returns `true` if this attachment has been written to by a render pass. - // we re-use is_first_call atomic to track usage, which assumes that calls to get_attachment - // are always consumed by a render pass that writes to the attachment - pub fn needs_present(&self) -> bool { - !self.is_first_call.load(Ordering::SeqCst) - } -} diff --git a/third_party/bevy_render/src/texture/texture_cache.rs b/third_party/bevy_render/src/texture/texture_cache.rs deleted file mode 100644 index ca1ef9b..0000000 --- a/third_party/bevy_render/src/texture/texture_cache.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::{ - render_resource::{Texture, TextureView}, - renderer::RenderDevice, -}; -use bevy_ecs::{prelude::ResMut, resource::Resource}; -use bevy_platform::collections::{hash_map::Entry, HashMap}; -use wgpu::{TextureDescriptor, TextureViewDescriptor}; - -/// The internal representation of a [`CachedTexture`] used to track whether it was recently used -/// and is currently taken. -struct CachedTextureMeta { - texture: Texture, - default_view: TextureView, - taken: bool, - frames_since_last_use: usize, -} - -/// A cached GPU [`Texture`] with corresponding [`TextureView`]. -/// -/// This is useful for textures that are created repeatedly (each frame) in the rendering process -/// to reduce the amount of GPU memory allocations. -#[derive(Clone)] -pub struct CachedTexture { - pub texture: Texture, - pub default_view: TextureView, -} - -/// This resource caches textures that are created repeatedly in the rendering process and -/// are only required for one frame. -#[derive(Resource, Default)] -pub struct TextureCache { - textures: HashMap, Vec>, -} - -impl TextureCache { - /// Retrieves a texture that matches the `descriptor`. If no matching one is found a new - /// [`CachedTexture`] is created. - pub fn get( - &mut self, - render_device: &RenderDevice, - descriptor: TextureDescriptor<'static>, - ) -> CachedTexture { - match self.textures.entry(descriptor) { - Entry::Occupied(mut entry) => { - for texture in entry.get_mut().iter_mut() { - if !texture.taken { - texture.frames_since_last_use = 0; - texture.taken = true; - return CachedTexture { - texture: texture.texture.clone(), - default_view: texture.default_view.clone(), - }; - } - } - - let texture = render_device.create_texture(&entry.key().clone()); - let default_view = texture.create_view(&TextureViewDescriptor::default()); - entry.get_mut().push(CachedTextureMeta { - texture: texture.clone(), - default_view: default_view.clone(), - frames_since_last_use: 0, - taken: true, - }); - CachedTexture { - texture, - default_view, - } - } - Entry::Vacant(entry) => { - let texture = render_device.create_texture(entry.key()); - let default_view = texture.create_view(&TextureViewDescriptor::default()); - entry.insert(vec![CachedTextureMeta { - texture: texture.clone(), - default_view: default_view.clone(), - taken: true, - frames_since_last_use: 0, - }]); - CachedTexture { - texture, - default_view, - } - } - } - } - - /// Returns `true` if the texture cache contains no textures. - pub fn is_empty(&self) -> bool { - self.textures.is_empty() - } - - /// Updates the cache and only retains recently used textures. - pub fn update(&mut self) { - self.textures.retain(|_, textures| { - for texture in textures.iter_mut() { - texture.frames_since_last_use += 1; - texture.taken = false; - } - - textures.retain(|texture| texture.frames_since_last_use < 3); - !textures.is_empty() - }); - } -} - -/// Updates the [`TextureCache`] to only retains recently used textures. -pub fn update_texture_cache_system(mut texture_cache: ResMut) { - texture_cache.update(); -} diff --git a/third_party/bevy_render/src/view/mod.rs b/third_party/bevy_render/src/view/mod.rs deleted file mode 100644 index 1bc5a32..0000000 --- a/third_party/bevy_render/src/view/mod.rs +++ /dev/null @@ -1,1172 +0,0 @@ -pub mod visibility; -pub mod window; - -use bevy_camera::{ - primitives::Frustum, CameraMainTextureUsages, ClearColor, ClearColorConfig, Exposure, - MainPassResolutionOverride, NormalizedRenderTarget, -}; -use bevy_diagnostic::FrameCount; -pub use visibility::*; -pub use window::*; - -use crate::{ - camera::{ExtractedCamera, MipBias, NormalizedRenderTargetExt as _, TemporalJitter}, - experimental::occlusion_culling::OcclusionCulling, - extract_component::ExtractComponentPlugin, - render_asset::RenderAssets, - render_phase::ViewRangefinder3d, - render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, - renderer::{RenderDevice, RenderQueue}, - sync_world::MainEntity, - texture::{ - CachedTexture, ColorAttachment, DepthAttachment, GpuImage, ManualTextureViews, - OutputColorAttachment, TextureCache, - }, - Render, RenderApp, RenderSystems, -}; -use alloc::sync::Arc; -use bevy_app::{App, Plugin}; -use bevy_color::LinearRgba; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::prelude::*; -use bevy_image::{BevyDefault as _, ToExtents}; -use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles}; -use bevy_platform::collections::{hash_map::Entry, HashMap}; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render_macros::ExtractComponent; -use bevy_shader::load_shader_library; -use bevy_transform::components::GlobalTransform; -use core::{ - ops::Range, - sync::atomic::{AtomicUsize, Ordering}, -}; -use wgpu::{ - BufferUsages, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp, - TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, -}; - -/// The matrix that converts from the RGB to the LMS color space. -/// -/// To derive this, first we convert from RGB to [CIE 1931 XYZ]: -/// -/// ```text -/// ⎡ X ⎤ ⎡ 0.490 0.310 0.200 ⎤ ⎡ R ⎤ -/// ⎢ Y ⎥ = ⎢ 0.177 0.812 0.011 ⎥ ⎢ G ⎥ -/// ⎣ Z ⎦ ⎣ 0.000 0.010 0.990 ⎦ ⎣ B ⎦ -/// ``` -/// -/// Then we convert to LMS according to the [CAM16 standard matrix]: -/// -/// ```text -/// ⎡ L ⎤ ⎡ 0.401 0.650 -0.051 ⎤ ⎡ X ⎤ -/// ⎢ M ⎥ = ⎢ -0.250 1.204 0.046 ⎥ ⎢ Y ⎥ -/// ⎣ S ⎦ ⎣ -0.002 0.049 0.953 ⎦ ⎣ Z ⎦ -/// ``` -/// -/// The resulting matrix is just the concatenation of these two matrices, to do -/// the conversion in one step. -/// -/// [CIE 1931 XYZ]: https://en.wikipedia.org/wiki/CIE_1931_color_space -/// [CAM16 standard matrix]: https://en.wikipedia.org/wiki/LMS_color_space -static RGB_TO_LMS: Mat3 = mat3( - vec3(0.311692, 0.0905138, 0.00764433), - vec3(0.652085, 0.901341, 0.0486554), - vec3(0.0362225, 0.00814478, 0.943700), -); - -/// The inverse of the [`RGB_TO_LMS`] matrix, converting from the LMS color -/// space back to RGB. -static LMS_TO_RGB: Mat3 = mat3( - vec3(4.06305, -0.40791, -0.0118812), - vec3(-2.93241, 1.40437, -0.0486532), - vec3(-0.130646, 0.00353630, 1.0605344), -); - -/// The [CIE 1931] *xy* chromaticity coordinates of the [D65 white point]. -/// -/// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space -/// [D65 white point]: https://en.wikipedia.org/wiki/Standard_illuminant#D65_values -static D65_XY: Vec2 = vec2(0.31272, 0.32903); - -/// The [D65 white point] in [LMS color space]. -/// -/// [LMS color space]: https://en.wikipedia.org/wiki/LMS_color_space -/// [D65 white point]: https://en.wikipedia.org/wiki/Standard_illuminant#D65_values -static D65_LMS: Vec3 = vec3(0.975538, 1.01648, 1.08475); - -pub struct ViewPlugin; - -impl Plugin for ViewPlugin { - fn build(&self, app: &mut App) { - load_shader_library!(app, "view.wgsl"); - - app - // NOTE: windows.is_changed() handles cases where a window was resized - .add_plugins(( - ExtractComponentPlugin::::default(), - ExtractComponentPlugin::::default(), - ExtractComponentPlugin::::default(), - RenderVisibilityRangePlugin, - )); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_systems( - Render, - ( - // `TextureView`s need to be dropped before reconfiguring window surfaces. - clear_view_attachments - .in_set(RenderSystems::ManageViews) - .before(create_surfaces), - cleanup_view_targets_for_resize - .in_set(RenderSystems::ManageViews) - .before(create_surfaces), - prepare_view_attachments - .in_set(RenderSystems::ManageViews) - .before(prepare_view_targets) - .after(prepare_windows), - prepare_view_targets - .in_set(RenderSystems::ManageViews) - .after(prepare_windows) - .after(crate::render_asset::prepare_assets::) - .ambiguous_with(crate::camera::sort_cameras), // doesn't use `sorted_camera_index_for_target` - prepare_view_uniforms.in_set(RenderSystems::PrepareResources), - ), - ); - } - } - - fn finish(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .init_resource::(); - } - } -} - -/// Component for configuring the number of samples for [Multi-Sample Anti-Aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing) -/// for a [`Camera`](bevy_camera::Camera). -/// -/// Defaults to 4 samples. A higher number of samples results in smoother edges. -/// -/// Some advanced rendering features may require that MSAA is disabled. -/// -/// Note that the web currently only supports 1 or 4 samples. -#[derive( - Component, - Default, - Clone, - Copy, - ExtractComponent, - Reflect, - PartialEq, - PartialOrd, - Eq, - Hash, - Debug, -)] -#[reflect(Component, Default, PartialEq, Hash, Debug)] -pub enum Msaa { - Off = 1, - Sample2 = 2, - #[default] - Sample4 = 4, - Sample8 = 8, -} - -impl Msaa { - #[inline] - pub fn samples(&self) -> u32 { - *self as u32 - } - - pub fn from_samples(samples: u32) -> Self { - match samples { - 1 => Msaa::Off, - 2 => Msaa::Sample2, - 4 => Msaa::Sample4, - 8 => Msaa::Sample8, - _ => panic!("Unsupported MSAA sample count: {samples}"), - } - } -} - -/// If this component is added to a camera, the camera will use an intermediate "high dynamic range" render texture. -/// This allows rendering with a wider range of lighting values. However, this does *not* affect -/// whether the camera will render with hdr display output (which bevy does not support currently) -/// and only affects the intermediate render texture. -#[derive( - Component, Default, Copy, Clone, ExtractComponent, Reflect, PartialEq, Eq, Hash, Debug, -)] -#[reflect(Component, Default, PartialEq, Hash, Debug)] -pub struct Hdr; - -/// An identifier for a view that is stable across frames. -/// -/// We can't use [`Entity`] for this because render world entities aren't -/// stable, and we can't use just [`MainEntity`] because some main world views -/// extract to multiple render world views. For example, a directional light -/// extracts to one render world view per cascade, and a point light extracts to -/// one render world view per cubemap face. So we pair the main entity with an -/// *auxiliary entity* and a *subview index*, which *together* uniquely identify -/// a view in the render world in a way that's stable from frame to frame. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct RetainedViewEntity { - /// The main entity that this view corresponds to. - pub main_entity: MainEntity, - - /// Another entity associated with the view entity. - /// - /// This is currently used for shadow cascades. If there are multiple - /// cameras, each camera needs to have its own set of shadow cascades. Thus - /// the light and subview index aren't themselves enough to uniquely - /// identify a shadow cascade: we need the camera that the cascade is - /// associated with as well. This entity stores that camera. - /// - /// If not present, this will be `MainEntity(Entity::PLACEHOLDER)`. - pub auxiliary_entity: MainEntity, - - /// The index of the view corresponding to the entity. - /// - /// For example, for point lights that cast shadows, this is the index of - /// the cubemap face (0 through 5 inclusive). For directional lights, this - /// is the index of the cascade. - pub subview_index: u32, -} - -impl RetainedViewEntity { - /// Creates a new [`RetainedViewEntity`] from the given main world entity, - /// auxiliary main world entity, and subview index. - /// - /// See [`RetainedViewEntity::subview_index`] for an explanation of what - /// `auxiliary_entity` and `subview_index` are. - pub fn new( - main_entity: MainEntity, - auxiliary_entity: Option, - subview_index: u32, - ) -> Self { - Self { - main_entity, - auxiliary_entity: auxiliary_entity.unwrap_or(Entity::PLACEHOLDER.into()), - subview_index, - } - } -} - -/// Describes a camera in the render world. -/// -/// Each entity in the main world can potentially extract to multiple subviews, -/// each of which has a [`RetainedViewEntity::subview_index`]. For instance, 3D -/// cameras extract to both a 3D camera subview with index 0 and a special UI -/// subview with index 1. Likewise, point lights with shadows extract to 6 -/// subviews, one for each side of the shadow cubemap. -#[derive(Component)] -pub struct ExtractedView { - /// The entity in the main world corresponding to this render world view. - pub retained_view_entity: RetainedViewEntity, - /// Typically a column-major right-handed projection matrix, one of either: - /// - /// Perspective (infinite reverse z) - /// ```text - /// f = 1 / tan(fov_y_radians / 2) - /// - /// ⎡ f / aspect 0 0 0 ⎤ - /// ⎢ 0 f 0 0 ⎥ - /// ⎢ 0 0 0 near ⎥ - /// ⎣ 0 0 -1 0 ⎦ - /// ``` - /// - /// Orthographic - /// ```text - /// w = right - left - /// h = top - bottom - /// d = far - near - /// cw = -right - left - /// ch = -top - bottom - /// - /// ⎡ 2 / w 0 0 cw / w ⎤ - /// ⎢ 0 2 / h 0 ch / h ⎥ - /// ⎢ 0 0 1 / d far / d ⎥ - /// ⎣ 0 0 0 1 ⎦ - /// ``` - /// - /// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic - /// - /// Glam matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]` - /// - /// Custom projections are also possible however. - pub clip_from_view: Mat4, - pub world_from_view: GlobalTransform, - // The view-projection matrix. When provided it is used instead of deriving it from - // `projection` and `transform` fields, which can be helpful in cases where numerical - // stability matters and there is a more direct way to derive the view-projection matrix. - pub clip_from_world: Option, - pub hdr: bool, - // uvec4(origin.x, origin.y, width, height) - pub viewport: UVec4, - pub color_grading: ColorGrading, - - /// Whether to switch culling mode so that materials that request backface - /// culling cull front faces, and vice versa. - /// - /// This is typically used for cameras that mirror the world that they - /// render across a plane, because doing that flips the winding of each - /// polygon. - /// - /// This setting doesn't affect materials that disable backface culling. - pub invert_culling: bool, -} - -impl ExtractedView { - /// Creates a 3D rangefinder for a view - pub fn rangefinder3d(&self) -> ViewRangefinder3d { - ViewRangefinder3d::from_world_from_view(&self.world_from_view.affine()) - } -} - -/// Configures filmic color grading parameters to adjust the image appearance. -/// -/// Color grading is applied just before tonemapping for a given -/// [`Camera`](bevy_camera::Camera) entity, with the sole exception of the -/// `post_saturation` value in [`ColorGradingGlobal`], which is applied after -/// tonemapping. -#[derive(Component, Reflect, Debug, Default, Clone)] -#[reflect(Component, Default, Debug, Clone)] -pub struct ColorGrading { - /// Filmic color grading values applied to the image as a whole (as opposed - /// to individual sections, like shadows and highlights). - pub global: ColorGradingGlobal, - - /// Color grading values that are applied to the darker parts of the image. - /// - /// The cutoff points can be customized with the - /// [`ColorGradingGlobal::midtones_range`] field. - pub shadows: ColorGradingSection, - - /// Color grading values that are applied to the parts of the image with - /// intermediate brightness. - /// - /// The cutoff points can be customized with the - /// [`ColorGradingGlobal::midtones_range`] field. - pub midtones: ColorGradingSection, - - /// Color grading values that are applied to the lighter parts of the image. - /// - /// The cutoff points can be customized with the - /// [`ColorGradingGlobal::midtones_range`] field. - pub highlights: ColorGradingSection, -} - -/// Filmic color grading values applied to the image as a whole (as opposed to -/// individual sections, like shadows and highlights). -#[derive(Clone, Debug, Reflect)] -#[reflect(Default, Clone)] -pub struct ColorGradingGlobal { - /// Exposure value (EV) offset, measured in stops. - pub exposure: f32, - - /// An adjustment made to the [CIE 1931] chromaticity *x* value. - /// - /// Positive values make the colors redder. Negative values make the colors - /// bluer. This has no effect on luminance (brightness). - /// - /// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space - pub temperature: f32, - - /// An adjustment made to the [CIE 1931] chromaticity *y* value. - /// - /// Positive values make the colors more magenta. Negative values make the - /// colors greener. This has no effect on luminance (brightness). - /// - /// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space - pub tint: f32, - - /// An adjustment to the [hue], in radians. - /// - /// Adjusting this value changes the perceived colors in the image: red to - /// yellow to green to blue, etc. It has no effect on the saturation or - /// brightness of the colors. - /// - /// [hue]: https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation - pub hue: f32, - - /// Saturation adjustment applied after tonemapping. - /// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image - /// with luminance defined by ITU-R BT.709 - /// Values above 1.0 increase saturation. - pub post_saturation: f32, - - /// The luminance (brightness) ranges that are considered part of the - /// "midtones" of the image. - /// - /// This affects which [`ColorGradingSection`]s apply to which colors. Note - /// that the sections smoothly blend into one another, to avoid abrupt - /// transitions. - /// - /// The default value is 0.2 to 0.7. - pub midtones_range: Range, -} - -/// The [`ColorGrading`] structure, packed into the most efficient form for the -/// GPU. -#[derive(Clone, Copy, Debug, ShaderType)] -pub struct ColorGradingUniform { - pub balance: Mat3, - pub saturation: Vec3, - pub contrast: Vec3, - pub gamma: Vec3, - pub gain: Vec3, - pub lift: Vec3, - pub midtone_range: Vec2, - pub exposure: f32, - pub hue: f32, - pub post_saturation: f32, -} - -/// A section of color grading values that can be selectively applied to -/// shadows, midtones, and highlights. -#[derive(Reflect, Debug, Copy, Clone, PartialEq)] -#[reflect(Clone, PartialEq)] -pub struct ColorGradingSection { - /// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image - /// with luminance defined by ITU-R BT.709. - /// Values above 1.0 increase saturation. - pub saturation: f32, - - /// Adjusts the range of colors. - /// - /// A value of 1.0 applies no changes. Values below 1.0 move the colors more - /// toward a neutral gray. Values above 1.0 spread the colors out away from - /// the neutral gray. - pub contrast: f32, - - /// A nonlinear luminance adjustment, mainly affecting the high end of the - /// range. - /// - /// This is the *n* exponent in the standard [ASC CDL] formula for color - /// correction: - /// - /// ```text - /// out = (i × s + o)ⁿ - /// ``` - /// - /// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function - pub gamma: f32, - - /// A linear luminance adjustment, mainly affecting the middle part of the - /// range. - /// - /// This is the *s* factor in the standard [ASC CDL] formula for color - /// correction: - /// - /// ```text - /// out = (i × s + o)ⁿ - /// ``` - /// - /// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function - pub gain: f32, - - /// A fixed luminance adjustment, mainly affecting the lower part of the - /// range. - /// - /// This is the *o* term in the standard [ASC CDL] formula for color - /// correction: - /// - /// ```text - /// out = (i × s + o)ⁿ - /// ``` - /// - /// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function - pub lift: f32, -} - -impl Default for ColorGradingGlobal { - fn default() -> Self { - Self { - exposure: 0.0, - temperature: 0.0, - tint: 0.0, - hue: 0.0, - post_saturation: 1.0, - midtones_range: 0.2..0.7, - } - } -} - -impl Default for ColorGradingSection { - fn default() -> Self { - Self { - saturation: 1.0, - contrast: 1.0, - gamma: 1.0, - gain: 1.0, - lift: 0.0, - } - } -} - -impl ColorGrading { - /// Creates a new [`ColorGrading`] instance in which shadows, midtones, and - /// highlights all have the same set of color grading values. - pub fn with_identical_sections( - global: ColorGradingGlobal, - section: ColorGradingSection, - ) -> ColorGrading { - ColorGrading { - global, - highlights: section, - midtones: section, - shadows: section, - } - } - - /// Returns an iterator that visits the shadows, midtones, and highlights - /// sections, in that order. - pub fn all_sections(&self) -> impl Iterator { - [&self.shadows, &self.midtones, &self.highlights].into_iter() - } - - /// Applies the given mutating function to the shadows, midtones, and - /// highlights sections, in that order. - /// - /// Returns an array composed of the results of such evaluation, in that - /// order. - pub fn all_sections_mut(&mut self) -> impl Iterator { - [&mut self.shadows, &mut self.midtones, &mut self.highlights].into_iter() - } -} - -#[derive(Clone, ShaderType)] -pub struct ViewUniform { - pub clip_from_world: Mat4, - pub unjittered_clip_from_world: Mat4, - pub world_from_clip: Mat4, - pub world_from_view: Mat4, - pub view_from_world: Mat4, - /// Typically a column-major right-handed projection matrix, one of either: - /// - /// Perspective (infinite reverse z) - /// ```text - /// f = 1 / tan(fov_y_radians / 2) - /// - /// ⎡ f / aspect 0 0 0 ⎤ - /// ⎢ 0 f 0 0 ⎥ - /// ⎢ 0 0 0 near ⎥ - /// ⎣ 0 0 -1 0 ⎦ - /// ``` - /// - /// Orthographic - /// ```text - /// w = right - left - /// h = top - bottom - /// d = far - near - /// cw = -right - left - /// ch = -top - bottom - /// - /// ⎡ 2 / w 0 0 cw / w ⎤ - /// ⎢ 0 2 / h 0 ch / h ⎥ - /// ⎢ 0 0 1 / d far / d ⎥ - /// ⎣ 0 0 0 1 ⎦ - /// ``` - /// - /// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic - /// - /// Glam matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]` - /// - /// Custom projections are also possible however. - pub clip_from_view: Mat4, - pub view_from_clip: Mat4, - pub world_position: Vec3, - pub exposure: f32, - // viewport(x_origin, y_origin, width, height) - pub viewport: Vec4, - pub main_pass_viewport: Vec4, - /// 6 world-space half spaces (normal: vec3, distance: f32) ordered left, right, top, bottom, near, far. - /// The normal vectors point towards the interior of the frustum. - /// A half space contains `p` if `normal.dot(p) + distance > 0.` - pub frustum: [Vec4; 6], - pub color_grading: ColorGradingUniform, - pub mip_bias: f32, - pub frame_count: u32, -} - -#[derive(Resource)] -pub struct ViewUniforms { - pub uniforms: DynamicUniformBuffer, -} - -impl FromWorld for ViewUniforms { - fn from_world(world: &mut World) -> Self { - let mut uniforms = DynamicUniformBuffer::default(); - uniforms.set_label(Some("view_uniforms_buffer")); - - let render_device = world.resource::(); - if render_device.limits().max_storage_buffers_per_shader_stage > 0 { - uniforms.add_usages(BufferUsages::STORAGE); - } - - Self { uniforms } - } -} - -#[derive(Component)] -pub struct ViewUniformOffset { - pub offset: u32, -} - -#[derive(Component, Clone)] -pub struct ViewTarget { - main_textures: MainTargetTextures, - main_texture_format: TextureFormat, - /// 0 represents `main_textures.a`, 1 represents `main_textures.b` - /// This is shared across view targets with the same render target - main_texture: Arc, - out_texture: OutputColorAttachment, -} - -/// Contains [`OutputColorAttachment`] used for each target present on any view in the current -/// frame, after being prepared by [`prepare_view_attachments`]. Users that want to override -/// the default output color attachment for a specific target can do so by adding a -/// [`OutputColorAttachment`] to this resource before [`prepare_view_targets`] is called. -#[derive(Resource, Default, Deref, DerefMut)] -pub struct ViewTargetAttachments(HashMap); - -pub struct PostProcessWrite<'a> { - pub source: &'a TextureView, - pub source_texture: &'a Texture, - pub destination: &'a TextureView, - pub destination_texture: &'a Texture, -} - -impl From for ColorGradingUniform { - fn from(component: ColorGrading) -> Self { - // Compute the balance matrix that will be used to apply the white - // balance adjustment to an RGB color. Our general approach will be to - // convert both the color and the developer-supplied white point to the - // LMS color space, apply the conversion, and then convert back. - // - // First, we start with the CIE 1931 *xy* values of the standard D65 - // illuminant: - // - // - // We then adjust them based on the developer's requested white balance. - let white_point_xy = D65_XY + vec2(-component.global.temperature, component.global.tint); - - // Convert the white point from CIE 1931 *xy* to LMS. First, we convert to XYZ: - // - // Y Y - // Y = 1 X = ─ x Z = ─ (1 - x - y) - // y y - // - // Then we convert from XYZ to LMS color space, using the CAM16 matrix - // from : - // - // ⎡ L ⎤ ⎡ 0.401 0.650 -0.051 ⎤ ⎡ X ⎤ - // ⎢ M ⎥ = ⎢ -0.250 1.204 0.046 ⎥ ⎢ Y ⎥ - // ⎣ S ⎦ ⎣ -0.002 0.049 0.953 ⎦ ⎣ Z ⎦ - // - // The following formula is just a simplification of the above. - - let white_point_lms = vec3(0.701634, 1.15856, -0.904175) - + (vec3(-0.051461, 0.045854, 0.953127) - + vec3(0.452749, -0.296122, -0.955206) * white_point_xy.x) - / white_point_xy.y; - - // Now that we're in LMS space, perform the white point scaling. - let white_point_adjustment = Mat3::from_diagonal(D65_LMS / white_point_lms); - - // Finally, combine the RGB → LMS → corrected LMS → corrected RGB - // pipeline into a single 3×3 matrix. - let balance = LMS_TO_RGB * white_point_adjustment * RGB_TO_LMS; - - Self { - balance, - saturation: vec3( - component.shadows.saturation, - component.midtones.saturation, - component.highlights.saturation, - ), - contrast: vec3( - component.shadows.contrast, - component.midtones.contrast, - component.highlights.contrast, - ), - gamma: vec3( - component.shadows.gamma, - component.midtones.gamma, - component.highlights.gamma, - ), - gain: vec3( - component.shadows.gain, - component.midtones.gain, - component.highlights.gain, - ), - lift: vec3( - component.shadows.lift, - component.midtones.lift, - component.highlights.lift, - ), - midtone_range: vec2( - component.global.midtones_range.start, - component.global.midtones_range.end, - ), - exposure: component.global.exposure, - hue: component.global.hue, - post_saturation: component.global.post_saturation, - } - } -} - -/// Add this component to a camera to disable *indirect mode*. -/// -/// Indirect mode, automatically enabled on supported hardware, allows Bevy to -/// offload transform and cull operations to the GPU, reducing CPU overhead. -/// Doing this, however, reduces the amount of control that your app has over -/// instancing decisions. In certain circumstances, you may want to disable -/// indirect drawing so that your app can manually instance meshes as it sees -/// fit. See the `custom_shader_instancing` example. -/// -/// The vast majority of applications will not need to use this component, as it -/// generally reduces rendering performance. -/// -/// Note: This component should only be added when initially spawning a camera. Adding -/// or removing after spawn can result in unspecified behavior. -#[derive(Component, Default)] -pub struct NoIndirectDrawing; - -impl ViewTarget { - pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float; - - /// Retrieve this target's main texture's color attachment. - pub fn get_color_attachment(&self) -> RenderPassColorAttachment<'_> { - if self.main_texture.load(Ordering::SeqCst) == 0 { - self.main_textures.a.get_attachment() - } else { - self.main_textures.b.get_attachment() - } - } - - /// Retrieve this target's "unsampled" main texture's color attachment. - pub fn get_unsampled_color_attachment(&self) -> RenderPassColorAttachment<'_> { - if self.main_texture.load(Ordering::SeqCst) == 0 { - self.main_textures.a.get_unsampled_attachment() - } else { - self.main_textures.b.get_unsampled_attachment() - } - } - - /// The "main" unsampled texture. - pub fn main_texture(&self) -> &Texture { - if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.a.texture.texture - } else { - &self.main_textures.b.texture.texture - } - } - - /// The _other_ "main" unsampled texture. - /// In most cases you should use [`Self::main_texture`] instead and never this. - /// The textures will naturally be swapped when [`Self::post_process_write`] is called. - /// - /// A use case for this is to be able to prepare a bind group for all main textures - /// ahead of time. - pub fn main_texture_other(&self) -> &Texture { - if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.b.texture.texture - } else { - &self.main_textures.a.texture.texture - } - } - - /// The "main" unsampled texture. - pub fn main_texture_view(&self) -> &TextureView { - if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.a.texture.default_view - } else { - &self.main_textures.b.texture.default_view - } - } - - /// The _other_ "main" unsampled texture view. - /// In most cases you should use [`Self::main_texture_view`] instead and never this. - /// The textures will naturally be swapped when [`Self::post_process_write`] is called. - /// - /// A use case for this is to be able to prepare a bind group for all main textures - /// ahead of time. - pub fn main_texture_other_view(&self) -> &TextureView { - if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.b.texture.default_view - } else { - &self.main_textures.a.texture.default_view - } - } - - /// The "main" sampled texture. - pub fn sampled_main_texture(&self) -> Option<&Texture> { - self.main_textures - .a - .resolve_target - .as_ref() - .map(|sampled| &sampled.texture) - } - - /// The "main" sampled texture view. - pub fn sampled_main_texture_view(&self) -> Option<&TextureView> { - self.main_textures - .a - .resolve_target - .as_ref() - .map(|sampled| &sampled.default_view) - } - - #[inline] - pub fn main_texture_format(&self) -> TextureFormat { - self.main_texture_format - } - - /// Returns `true` if and only if the main texture is [`Self::TEXTURE_FORMAT_HDR`] - #[inline] - pub fn is_hdr(&self) -> bool { - self.main_texture_format == ViewTarget::TEXTURE_FORMAT_HDR - } - - /// The final texture this view will render to. - #[inline] - pub fn out_texture(&self) -> &TextureView { - &self.out_texture.view - } - - pub fn out_texture_color_attachment( - &self, - clear_color: Option, - ) -> RenderPassColorAttachment<'_> { - self.out_texture.get_attachment(clear_color) - } - - /// Whether the final texture this view will render to needs to be presented. - pub fn needs_present(&self) -> bool { - self.out_texture.needs_present() - } - - /// The format of the final texture this view will render to - #[inline] - pub fn out_texture_view_format(&self) -> TextureFormat { - self.out_texture.view_format - } - - /// This will start a new "post process write", which assumes that the caller - /// will write the [`PostProcessWrite`]'s `source` to the `destination`. - /// - /// `source` is the "current" main texture. This will internally flip this - /// [`ViewTarget`]'s main texture to the `destination` texture, so the caller - /// _must_ ensure `source` is copied to `destination`, with or without modifications. - /// Failing to do so will cause the current main texture information to be lost. - pub fn post_process_write(&self) -> PostProcessWrite<'_> { - let old_is_a_main_texture = self.main_texture.fetch_xor(1, Ordering::SeqCst); - // if the old main texture is a, then the post processing must write from a to b - if old_is_a_main_texture == 0 { - self.main_textures.b.mark_as_cleared(); - PostProcessWrite { - source: &self.main_textures.a.texture.default_view, - source_texture: &self.main_textures.a.texture.texture, - destination: &self.main_textures.b.texture.default_view, - destination_texture: &self.main_textures.b.texture.texture, - } - } else { - self.main_textures.a.mark_as_cleared(); - PostProcessWrite { - source: &self.main_textures.b.texture.default_view, - source_texture: &self.main_textures.b.texture.texture, - destination: &self.main_textures.a.texture.default_view, - destination_texture: &self.main_textures.a.texture.texture, - } - } - } -} - -#[derive(Component)] -pub struct ViewDepthTexture { - pub texture: Texture, - attachment: DepthAttachment, -} - -impl ViewDepthTexture { - pub fn new(texture: CachedTexture, clear_value: Option) -> Self { - Self { - texture: texture.texture, - attachment: DepthAttachment::new(texture.default_view, clear_value), - } - } - - pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment<'_> { - self.attachment.get_attachment(store) - } - - pub fn view(&self) -> &TextureView { - &self.attachment.view - } -} - -pub fn prepare_view_uniforms( - mut commands: Commands, - render_device: Res, - render_queue: Res, - mut view_uniforms: ResMut, - views: Query<( - Entity, - Option<&ExtractedCamera>, - &ExtractedView, - Option<&Frustum>, - Option<&TemporalJitter>, - Option<&MipBias>, - Option<&MainPassResolutionOverride>, - )>, - frame_count: Res, -) { - let view_iter = views.iter(); - let view_count = view_iter.len(); - let Some(mut writer) = - view_uniforms - .uniforms - .get_writer(view_count, &render_device, &render_queue) - else { - return; - }; - for ( - entity, - extracted_camera, - extracted_view, - frustum, - temporal_jitter, - mip_bias, - resolution_override, - ) in &views - { - let viewport = extracted_view.viewport.as_vec4(); - let mut main_pass_viewport = viewport; - if let Some(resolution_override) = resolution_override { - main_pass_viewport.z = resolution_override.0.x as f32; - main_pass_viewport.w = resolution_override.0.y as f32; - } - - let unjittered_projection = extracted_view.clip_from_view; - let mut clip_from_view = unjittered_projection; - - if let Some(temporal_jitter) = temporal_jitter { - temporal_jitter.jitter_projection(&mut clip_from_view, main_pass_viewport.zw()); - } - - let view_from_clip = clip_from_view.inverse(); - let world_from_view = extracted_view.world_from_view.to_matrix(); - let view_from_world = world_from_view.inverse(); - - let clip_from_world = if temporal_jitter.is_some() { - clip_from_view * view_from_world - } else { - extracted_view - .clip_from_world - .unwrap_or_else(|| clip_from_view * view_from_world) - }; - - // Map Frustum type to shader array, 6> - let frustum = frustum - .map(|frustum| frustum.half_spaces.map(|h| h.normal_d())) - .unwrap_or([Vec4::ZERO; 6]); - - let view_uniforms = ViewUniformOffset { - offset: writer.write(&ViewUniform { - clip_from_world, - unjittered_clip_from_world: unjittered_projection * view_from_world, - world_from_clip: world_from_view * view_from_clip, - world_from_view, - view_from_world, - clip_from_view, - view_from_clip, - world_position: extracted_view.world_from_view.translation(), - exposure: extracted_camera - .map(|c| c.exposure) - .unwrap_or_else(|| Exposure::default().exposure()), - viewport, - main_pass_viewport, - frustum, - color_grading: extracted_view.color_grading.clone().into(), - mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0, - frame_count: frame_count.0, - }), - }; - - commands.entity(entity).insert(view_uniforms); - } -} - -#[derive(Clone)] -struct MainTargetTextures { - a: ColorAttachment, - b: ColorAttachment, - /// 0 represents `main_textures.a`, 1 represents `main_textures.b` - /// This is shared across view targets with the same render target - main_texture: Arc, -} - -/// Prepares the view target [`OutputColorAttachment`] for each view in the current frame. -pub fn prepare_view_attachments( - windows: Res, - images: Res>, - manual_texture_views: Res, - cameras: Query<&ExtractedCamera>, - mut view_target_attachments: ResMut, -) { - for camera in cameras.iter() { - let Some(target) = &camera.target else { - continue; - }; - - match view_target_attachments.entry(target.clone()) { - Entry::Occupied(_) => {} - Entry::Vacant(entry) => { - let Some(attachment) = target - .get_texture_view(&windows, &images, &manual_texture_views) - .cloned() - .zip(target.get_texture_view_format(&windows, &images, &manual_texture_views)) - .map(|(view, format)| OutputColorAttachment::new(view.clone(), format)) - else { - continue; - }; - entry.insert(attachment); - } - }; - } -} - -/// Clears the view target [`OutputColorAttachment`]s. -pub fn clear_view_attachments(mut view_target_attachments: ResMut) { - view_target_attachments.clear(); -} - -pub fn cleanup_view_targets_for_resize( - mut commands: Commands, - windows: Res, - cameras: Query<(Entity, &ExtractedCamera), With>, -) { - for (entity, camera) in &cameras { - if let Some(NormalizedRenderTarget::Window(window_ref)) = &camera.target - && let Some(window) = windows.get(&window_ref.entity()) - && (window.size_changed || window.present_mode_changed) - { - commands.entity(entity).remove::(); - } - } -} - -pub fn prepare_view_targets( - mut commands: Commands, - clear_color_global: Res, - render_device: Res, - mut texture_cache: ResMut, - cameras: Query<( - Entity, - &ExtractedCamera, - &ExtractedView, - &CameraMainTextureUsages, - &Msaa, - )>, - view_target_attachments: Res, -) { - let mut textures = >::default(); - for (entity, camera, view, texture_usage, msaa) in cameras.iter() { - let (Some(target_size), Some(out_attachment)) = ( - camera.physical_target_size, - camera - .target - .as_ref() - .and_then(|target| view_target_attachments.get(target)), - ) else { - // If we can't find an output attachment we need to remove the ViewTarget - // component to make sure the camera doesn't try rendering to an invalid - // output attachment. - commands.entity(entity).try_remove::(); - - continue; - }; - - let main_texture_format = if view.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - - let clear_color = match camera.clear_color { - ClearColorConfig::Custom(color) => Some(color), - ClearColorConfig::None => None, - _ => Some(clear_color_global.0), - }; - - let (a, b, sampled, main_texture) = textures - .entry((camera.target.clone(), texture_usage.0, view.hdr, msaa)) - .or_insert_with(|| { - let descriptor = TextureDescriptor { - label: None, - size: target_size.to_extents(), - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: main_texture_format, - usage: texture_usage.0, - view_formats: match main_texture_format { - TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb], - TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb], - _ => &[], - }, - }; - let a = texture_cache.get( - &render_device, - TextureDescriptor { - label: Some("main_texture_a"), - ..descriptor - }, - ); - let b = texture_cache.get( - &render_device, - TextureDescriptor { - label: Some("main_texture_b"), - ..descriptor - }, - ); - let sampled = if msaa.samples() > 1 { - let sampled = texture_cache.get( - &render_device, - TextureDescriptor { - label: Some("main_texture_sampled"), - size: target_size.to_extents(), - mip_level_count: 1, - sample_count: msaa.samples(), - dimension: TextureDimension::D2, - format: main_texture_format, - usage: TextureUsages::RENDER_ATTACHMENT, - view_formats: descriptor.view_formats, - }, - ); - Some(sampled) - } else { - None - }; - let main_texture = Arc::new(AtomicUsize::new(0)); - (a, b, sampled, main_texture) - }); - - let converted_clear_color = clear_color.map(Into::into); - - let main_textures = MainTargetTextures { - a: ColorAttachment::new(a.clone(), sampled.clone(), None, converted_clear_color), - b: ColorAttachment::new(b.clone(), sampled.clone(), None, converted_clear_color), - main_texture: main_texture.clone(), - }; - - commands.entity(entity).insert(ViewTarget { - main_texture: main_textures.main_texture.clone(), - main_textures, - main_texture_format, - out_texture: out_attachment.clone(), - }); - } -} diff --git a/third_party/bevy_render/src/view/view.wgsl b/third_party/bevy_render/src/view/view.wgsl deleted file mode 100644 index 23ded53..0000000 --- a/third_party/bevy_render/src/view/view.wgsl +++ /dev/null @@ -1,272 +0,0 @@ -#define_import_path bevy_render::view - -struct ColorGrading { - balance: mat3x3, - saturation: vec3, - contrast: vec3, - gamma: vec3, - gain: vec3, - lift: vec3, - midtone_range: vec2, - exposure: f32, - hue: f32, - post_saturation: f32, -} - -struct View { - clip_from_world: mat4x4, - unjittered_clip_from_world: mat4x4, - world_from_clip: mat4x4, - world_from_view: mat4x4, - view_from_world: mat4x4, - // Typically a column-major right-handed projection matrix, one of either: - // - // Perspective (infinite reverse z) - // ``` - // f = 1 / tan(fov_y_radians / 2) - // - // ⎡ f / aspect 0 0 0 ⎤ - // ⎢ 0 f 0 0 ⎥ - // ⎢ 0 0 0 near ⎥ - // ⎣ 0 0 -1 0 ⎦ - // ``` - // - // Orthographic - // ``` - // w = right - left - // h = top - bottom - // d = far - near - // cw = -right - left - // ch = -top - bottom - // - // ⎡ 2 / w 0 0 cw / w ⎤ - // ⎢ 0 2 / h 0 ch / h ⎥ - // ⎢ 0 0 1 / d far / d ⎥ - // ⎣ 0 0 0 1 ⎦ - // ``` - // - // `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic - // - // Wgsl matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]` - // - // Custom projections are also possible however. - clip_from_view: mat4x4, - view_from_clip: mat4x4, - world_position: vec3, - exposure: f32, - // viewport(x_origin, y_origin, width, height) - viewport: vec4, - main_pass_viewport: vec4, - // 6 world-space half spaces (normal: vec3, distance: f32) ordered left, right, top, bottom, near, far. - // The normal vectors point towards the interior of the frustum. - // A half space contains `p` if `normal.dot(p) + distance > 0.` - frustum: array, 6>, - color_grading: ColorGrading, - mip_bias: f32, - frame_count: u32, -}; - -/// World space: -/// +y is up - -/// View space: -/// -z is forward, +x is right, +y is up -/// Forward is from the camera position into the scene. -/// (0.0, 0.0, -1.0) is linear distance of 1.0 in front of the camera's view relative to the camera's rotation -/// (0.0, 1.0, 0.0) is linear distance of 1.0 above the camera's view relative to the camera's rotation - -/// NDC (normalized device coordinate): -/// https://www.w3.org/TR/webgpu/#coordinate-systems -/// (-1.0, -1.0) in NDC is located at the bottom-left corner of NDC -/// (1.0, 1.0) in NDC is located at the top-right corner of NDC -/// Z is depth where: -/// 1.0 is near clipping plane -/// Perspective projection: 0.0 is inf far away -/// Orthographic projection: 0.0 is far clipping plane - -/// Clip space: -/// This is NDC before the perspective divide, still in homogenous coordinate space. -/// Dividing a clip space point by its w component yields a point in NDC space. - -/// UV space: -/// 0.0, 0.0 is the top left -/// 1.0, 1.0 is the bottom right - - -// ----------------- -// TO WORLD -------- -// ----------------- - -/// Convert a view space position to world space -fn position_view_to_world(view_pos: vec3, world_from_view: mat4x4) -> vec3 { - let world_pos = world_from_view * vec4(view_pos, 1.0); - return world_pos.xyz; -} - -/// Convert a clip space position to world space -fn position_clip_to_world(clip_pos: vec4, world_from_clip: mat4x4) -> vec3 { - let world_pos = world_from_clip * clip_pos; - return world_pos.xyz; -} - -/// Convert a ndc space position to world space -fn position_ndc_to_world(ndc_pos: vec3, world_from_clip: mat4x4) -> vec3 { - let world_pos = world_from_clip * vec4(ndc_pos, 1.0); - return world_pos.xyz / world_pos.w; -} - -/// Convert a view space direction to world space -fn direction_view_to_world(view_dir: vec3, world_from_view: mat4x4) -> vec3 { - let world_dir = world_from_view * vec4(view_dir, 0.0); - return world_dir.xyz; -} - -/// Convert a clip space direction to world space -fn direction_clip_to_world(clip_dir: vec4, world_from_clip: mat4x4) -> vec3 { - let world_dir = world_from_clip * clip_dir; - return world_dir.xyz; -} - -// ----------------- -// TO VIEW --------- -// ----------------- - -/// Convert a world space position to view space -fn position_world_to_view(world_pos: vec3, view_from_world: mat4x4) -> vec3 { - let view_pos = view_from_world * vec4(world_pos, 1.0); - return view_pos.xyz; -} - -/// Convert a clip space position to view space -fn position_clip_to_view(clip_pos: vec4, view_from_clip: mat4x4) -> vec3 { - let view_pos = view_from_clip * clip_pos; - return view_pos.xyz; -} - -/// Convert a ndc space position to view space -fn position_ndc_to_view(ndc_pos: vec3, view_from_clip: mat4x4) -> vec3 { - let view_pos = view_from_clip * vec4(ndc_pos, 1.0); - return view_pos.xyz / view_pos.w; -} - -/// Convert a world space direction to view space -fn direction_world_to_view(world_dir: vec3, view_from_world: mat4x4) -> vec3 { - let view_dir = view_from_world * vec4(world_dir, 0.0); - return view_dir.xyz; -} - -/// Convert a clip space direction to view space -fn direction_clip_to_view(clip_dir: vec4, view_from_clip: mat4x4) -> vec3 { - let view_dir = view_from_clip * clip_dir; - return view_dir.xyz; -} - -// ----------------- -// TO CLIP --------- -// ----------------- - -/// Convert a world space position to clip space -fn position_world_to_clip(world_pos: vec3, clip_from_world: mat4x4) -> vec4 { - let clip_pos = clip_from_world * vec4(world_pos, 1.0); - return clip_pos; -} - -/// Convert a view space position to clip space -fn position_view_to_clip(view_pos: vec3, clip_from_view: mat4x4) -> vec4 { - let clip_pos = clip_from_view * vec4(view_pos, 1.0); - return clip_pos; -} - -/// Convert a world space direction to clip space -fn direction_world_to_clip(world_dir: vec3, clip_from_world: mat4x4) -> vec4 { - let clip_dir = clip_from_world * vec4(world_dir, 0.0); - return clip_dir; -} - -/// Convert a view space direction to clip space -fn direction_view_to_clip(view_dir: vec3, clip_from_view: mat4x4) -> vec4 { - let clip_dir = clip_from_view * vec4(view_dir, 0.0); - return clip_dir; -} - -// ----------------- -// TO NDC ---------- -// ----------------- - -/// Convert a world space position to ndc space -fn position_world_to_ndc(world_pos: vec3, clip_from_world: mat4x4) -> vec3 { - let ndc_pos = clip_from_world * vec4(world_pos, 1.0); - return ndc_pos.xyz / ndc_pos.w; -} - -/// Convert a view space position to ndc space -fn position_view_to_ndc(view_pos: vec3, clip_from_view: mat4x4) -> vec3 { - let ndc_pos = clip_from_view * vec4(view_pos, 1.0); - return ndc_pos.xyz / ndc_pos.w; -} - -// ----------------- -// DEPTH ----------- -// ----------------- - -/// Retrieve the perspective camera near clipping plane -fn perspective_camera_near(clip_from_view: mat4x4) -> f32 { - return clip_from_view[3][2]; -} - -/// Convert ndc depth to linear view z. -/// Note: Depth values in front of the camera will be negative as -z is forward -fn depth_ndc_to_view_z(ndc_depth: f32, clip_from_view: mat4x4, view_from_clip: mat4x4) -> f32 { -#ifdef VIEW_PROJECTION_PERSPECTIVE - return -perspective_camera_near(clip_from_view) / ndc_depth; -#else ifdef VIEW_PROJECTION_ORTHOGRAPHIC - return -(clip_from_view[3][2] - ndc_depth) / clip_from_view[2][2]; -#else - let view_pos = view_from_clip * vec4(0.0, 0.0, ndc_depth, 1.0); - return view_pos.z / view_pos.w; -#endif -} - -/// Convert linear view z to ndc depth. -/// Note: View z input should be negative for values in front of the camera as -z is forward -fn view_z_to_depth_ndc(view_z: f32, clip_from_view: mat4x4) -> f32 { -#ifdef VIEW_PROJECTION_PERSPECTIVE - return -perspective_camera_near(clip_from_view) / view_z; -#else ifdef VIEW_PROJECTION_ORTHOGRAPHIC - return clip_from_view[3][2] + view_z * clip_from_view[2][2]; -#else - let ndc_pos = clip_from_view * vec4(0.0, 0.0, view_z, 1.0); - return ndc_pos.z / ndc_pos.w; -#endif -} - -// ----------------- -// UV -------------- -// ----------------- - -/// Convert ndc space xy coordinate [-1.0 .. 1.0] to uv [0.0 .. 1.0] -fn ndc_to_uv(ndc: vec2) -> vec2 { - return ndc * vec2(0.5, -0.5) + vec2(0.5); -} - -/// Convert uv [0.0 .. 1.0] coordinate to ndc space xy [-1.0 .. 1.0] -fn uv_to_ndc(uv: vec2) -> vec2 { - return uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0); -} - -/// returns the (0.0, 0.0) .. (1.0, 1.0) position within the viewport for the current render target -/// [0 .. render target viewport size] eg. [(0.0, 0.0) .. (1280.0, 720.0)] to [(0.0, 0.0) .. (1.0, 1.0)] -fn frag_coord_to_uv(frag_coord: vec2, viewport: vec4) -> vec2 { - return (frag_coord - viewport.xy) / viewport.zw; -} - -/// Convert frag coord to ndc -fn frag_coord_to_ndc(frag_coord: vec4, viewport: vec4) -> vec3 { - return vec3(uv_to_ndc(frag_coord_to_uv(frag_coord.xy, viewport)), frag_coord.z); -} - -/// Convert ndc space xy coordinate [-1.0 .. 1.0] to [0 .. render target -/// viewport size] -fn ndc_to_frag_coord(ndc: vec2, viewport: vec4) -> vec2 { - return ndc_to_uv(ndc) * viewport.zw; -} diff --git a/third_party/bevy_render/src/view/visibility/mod.rs b/third_party/bevy_render/src/view/visibility/mod.rs deleted file mode 100644 index 3994827..0000000 --- a/third_party/bevy_render/src/view/visibility/mod.rs +++ /dev/null @@ -1,54 +0,0 @@ -use core::any::TypeId; - -use bevy_ecs::{component::Component, entity::Entity, prelude::ReflectComponent}; -use bevy_reflect::{prelude::ReflectDefault, Reflect}; -use bevy_utils::TypeIdMap; - -use crate::sync_world::MainEntity; - -mod range; -use bevy_camera::visibility::*; -pub use range::*; - -/// Collection of entities visible from the current view. -/// -/// This component is extracted from [`VisibleEntities`]. -#[derive(Clone, Component, Default, Debug, Reflect)] -#[reflect(Component, Default, Debug, Clone)] -pub struct RenderVisibleEntities { - #[reflect(ignore, clone)] - pub entities: TypeIdMap>, -} - -impl RenderVisibleEntities { - pub fn get(&self) -> &[(Entity, MainEntity)] - where - QF: 'static, - { - match self.entities.get(&TypeId::of::()) { - Some(entities) => &entities[..], - None => &[], - } - } - - pub fn iter(&self) -> impl DoubleEndedIterator - where - QF: 'static, - { - self.get::().iter() - } - - pub fn len(&self) -> usize - where - QF: 'static, - { - self.get::().len() - } - - pub fn is_empty(&self) -> bool - where - QF: 'static, - { - self.get::().is_empty() - } -} diff --git a/third_party/bevy_render/src/view/visibility/range.rs b/third_party/bevy_render/src/view/visibility/range.rs deleted file mode 100644 index 543f10f..0000000 --- a/third_party/bevy_render/src/view/visibility/range.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! Specific distances from the camera in which entities are visible, also known -//! as *hierarchical levels of detail* or *HLOD*s. - -use super::VisibilityRange; -use bevy_app::{App, Plugin}; -use bevy_ecs::{ - entity::Entity, - lifecycle::RemovedComponents, - query::Changed, - resource::Resource, - schedule::IntoScheduleConfigs as _, - system::{Query, Res, ResMut}, -}; -use bevy_math::{vec4, Vec4}; -use bevy_platform::collections::HashMap; -use bevy_utils::prelude::default; -use nonmax::NonMaxU16; -use wgpu::{BufferBindingType, BufferUsages}; - -use crate::{ - render_resource::BufferVec, - renderer::{RenderDevice, RenderQueue}, - sync_world::{MainEntity, MainEntityHashMap}, - Extract, ExtractSchedule, Render, RenderApp, RenderSystems, -}; - -/// We need at least 4 storage buffer bindings available to enable the -/// visibility range buffer. -/// -/// Even though we only use one storage buffer, the first 3 available storage -/// buffers will go to various light-related buffers. We will grab the fourth -/// buffer slot. -pub const VISIBILITY_RANGES_STORAGE_BUFFER_COUNT: u32 = 4; - -/// The size of the visibility ranges buffer in elements (not bytes) when fewer -/// than 6 storage buffers are available and we're forced to use a uniform -/// buffer instead (most notably, on WebGL 2). -const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: usize = 64; - -/// A plugin that enables [`RenderVisibilityRanges`]s, which allow entities to be -/// hidden or shown based on distance to the camera. -pub struct RenderVisibilityRangePlugin; - -impl Plugin for RenderVisibilityRangePlugin { - fn build(&self, app: &mut App) { - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - render_app - .init_resource::() - .add_systems(ExtractSchedule, extract_visibility_ranges) - .add_systems( - Render, - write_render_visibility_ranges.in_set(RenderSystems::PrepareResourcesFlush), - ); - } -} - -/// Stores information related to [`VisibilityRange`]s in the render world. -#[derive(Resource)] -pub struct RenderVisibilityRanges { - /// Information corresponding to each entity. - entities: MainEntityHashMap, - - /// Maps a [`VisibilityRange`] to its index within the `buffer`. - /// - /// This map allows us to deduplicate identical visibility ranges, which - /// saves GPU memory. - range_to_index: HashMap, - - /// The GPU buffer that stores [`VisibilityRange`]s. - /// - /// Each [`Vec4`] contains the start margin start, start margin end, end - /// margin start, and end margin end distances, in that order. - buffer: BufferVec, - - /// True if the buffer has been changed since the last frame and needs to be - /// reuploaded to the GPU. - buffer_dirty: bool, -} - -/// Per-entity information related to [`VisibilityRange`]s. -struct RenderVisibilityEntityInfo { - /// The index of the range within the GPU buffer. - buffer_index: NonMaxU16, - /// True if the range is abrupt: i.e. has no crossfade. - is_abrupt: bool, -} - -impl Default for RenderVisibilityRanges { - fn default() -> Self { - Self { - entities: default(), - range_to_index: default(), - buffer: BufferVec::new( - BufferUsages::STORAGE | BufferUsages::UNIFORM | BufferUsages::VERTEX, - ), - buffer_dirty: true, - } - } -} - -impl RenderVisibilityRanges { - /// Clears out the [`RenderVisibilityRanges`] in preparation for a new - /// frame. - fn clear(&mut self) { - self.entities.clear(); - self.range_to_index.clear(); - self.buffer.clear(); - self.buffer_dirty = true; - } - - /// Inserts a new entity into the [`RenderVisibilityRanges`]. - fn insert(&mut self, entity: MainEntity, visibility_range: &VisibilityRange) { - // Grab a slot in the GPU buffer, or take the existing one if there - // already is one. - let buffer_index = *self - .range_to_index - .entry(visibility_range.clone()) - .or_insert_with(|| { - NonMaxU16::try_from(self.buffer.push(vec4( - visibility_range.start_margin.start, - visibility_range.start_margin.end, - visibility_range.end_margin.start, - visibility_range.end_margin.end, - )) as u16) - .unwrap_or_default() - }); - - self.entities.insert( - entity, - RenderVisibilityEntityInfo { - buffer_index, - is_abrupt: visibility_range.is_abrupt(), - }, - ); - } - - /// Returns the index in the GPU buffer corresponding to the visible range - /// for the given entity. - /// - /// If the entity has no visible range, returns `None`. - #[inline] - pub fn lod_index_for_entity(&self, entity: MainEntity) -> Option { - self.entities.get(&entity).map(|info| info.buffer_index) - } - - /// Returns true if the entity has a visibility range and it isn't abrupt: - /// i.e. if it has a crossfade. - #[inline] - pub fn entity_has_crossfading_visibility_ranges(&self, entity: MainEntity) -> bool { - self.entities - .get(&entity) - .is_some_and(|info| !info.is_abrupt) - } - - /// Returns a reference to the GPU buffer that stores visibility ranges. - #[inline] - pub fn buffer(&self) -> &BufferVec { - &self.buffer - } -} - -/// Extracts all [`VisibilityRange`] components from the main world to the -/// render world and inserts them into [`RenderVisibilityRanges`]. -pub fn extract_visibility_ranges( - mut render_visibility_ranges: ResMut, - visibility_ranges_query: Extract>, - changed_ranges_query: Extract>>, - mut removed_visibility_ranges: Extract>, -) { - if changed_ranges_query.is_empty() && removed_visibility_ranges.read().next().is_none() { - return; - } - - render_visibility_ranges.clear(); - for (entity, visibility_range) in visibility_ranges_query.iter() { - render_visibility_ranges.insert(entity.into(), visibility_range); - } -} - -/// Writes the [`RenderVisibilityRanges`] table to the GPU. -pub fn write_render_visibility_ranges( - render_device: Res, - render_queue: Res, - mut render_visibility_ranges: ResMut, -) { - // If there haven't been any changes, early out. - if !render_visibility_ranges.buffer_dirty { - return; - } - - // Mess with the length of the buffer to meet API requirements if necessary. - match render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT) - { - // If we're using a uniform buffer, we must have *exactly* - // `VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE` elements. - BufferBindingType::Uniform - if render_visibility_ranges.buffer.len() > VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE => - { - render_visibility_ranges - .buffer - .truncate(VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE); - } - BufferBindingType::Uniform - if render_visibility_ranges.buffer.len() < VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE => - { - while render_visibility_ranges.buffer.len() < VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE { - render_visibility_ranges.buffer.push(default()); - } - } - - // Otherwise, if we're using a storage buffer, just ensure there's - // something in the buffer, or else it won't get allocated. - BufferBindingType::Storage { .. } if render_visibility_ranges.buffer.is_empty() => { - render_visibility_ranges.buffer.push(default()); - } - - _ => {} - } - - // Schedule the write. - render_visibility_ranges - .buffer - .write_buffer(&render_device, &render_queue); - render_visibility_ranges.buffer_dirty = false; -} diff --git a/third_party/bevy_render/src/view/window/mod.rs b/third_party/bevy_render/src/view/window/mod.rs deleted file mode 100644 index 17aa918..0000000 --- a/third_party/bevy_render/src/view/window/mod.rs +++ /dev/null @@ -1,490 +0,0 @@ -use crate::renderer::WgpuWrapper; -use crate::{ - render_resource::{SurfaceTexture, TextureView}, - renderer::{RenderAdapter, RenderDevice, RenderInstance}, - Extract, ExtractSchedule, Render, RenderApp, RenderSystems, -}; -use bevy_app::{App, Plugin}; -use bevy_ecs::{entity::EntityHashMap, prelude::*}; -use bevy_platform::collections::HashSet; -use bevy_utils::default; -use bevy_window::{ - CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosing, -}; -use core::{ - num::NonZero, - ops::{Deref, DerefMut}, -}; -use tracing::{debug, info, warn}; -use wgpu::{ - SurfaceConfiguration, SurfaceTargetUnsafe, TextureFormat, TextureUsages, TextureViewDescriptor, -}; - -pub mod screenshot; - -use screenshot::ScreenshotPlugin; - -pub struct WindowRenderPlugin; - -impl Plugin for WindowRenderPlugin { - fn build(&self, app: &mut App) { - app.add_plugins(ScreenshotPlugin); - - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .init_resource::() - .add_systems(ExtractSchedule, extract_windows) - .add_systems( - Render, - create_surfaces - .run_if(need_surface_configuration) - .before(prepare_windows), - ) - .add_systems(Render, prepare_windows.in_set(RenderSystems::ManageViews)); - } - } -} - -pub struct ExtractedWindow { - /// An entity that contains the components in [`Window`]. - pub entity: Entity, - pub handle: RawHandleWrapper, - pub physical_width: u32, - pub physical_height: u32, - pub present_mode: PresentMode, - pub desired_maximum_frame_latency: Option>, - /// Note: this will not always be the swap chain texture view. When taking a screenshot, - /// this will point to an alternative texture instead to allow for copying the render result - /// to CPU memory. - pub swap_chain_texture_view: Option, - pub swap_chain_texture: Option, - pub swap_chain_texture_format: Option, - pub swap_chain_texture_view_format: Option, - pub size_changed: bool, - pub present_mode_changed: bool, - pub alpha_mode: CompositeAlphaMode, - /// Whether this window needs an initial buffer commit. - /// - /// On Wayland, windows must present at least once before they are shown. - /// See - pub needs_initial_present: bool, -} - -impl ExtractedWindow { - fn set_swapchain_texture(&mut self, frame: wgpu::SurfaceTexture) { - self.swap_chain_texture_view_format = Some(frame.texture.format().add_srgb_suffix()); - let texture_view_descriptor = TextureViewDescriptor { - format: self.swap_chain_texture_view_format, - ..default() - }; - self.swap_chain_texture_view = Some(TextureView::from( - frame.texture.create_view(&texture_view_descriptor), - )); - self.swap_chain_texture = Some(SurfaceTexture::from(frame)); - } - - fn has_swapchain_texture(&self) -> bool { - self.swap_chain_texture_view.is_some() && self.swap_chain_texture.is_some() - } - - pub fn present(&mut self) { - if let Some(surface_texture) = self.swap_chain_texture.take() { - // TODO(clean): winit docs recommends calling pre_present_notify before this. - // though `present()` doesn't present the frame, it schedules it to be presented - // by wgpu. - // https://docs.rs/winit/0.29.9/wasm32-unknown-unknown/winit/window/struct.Window.html#method.pre_present_notify - surface_texture.present(); - } - } -} - -#[derive(Default, Resource)] -pub struct ExtractedWindows { - pub primary: Option, - pub windows: EntityHashMap, -} - -impl Deref for ExtractedWindows { - type Target = EntityHashMap; - - fn deref(&self) -> &Self::Target { - &self.windows - } -} - -impl DerefMut for ExtractedWindows { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.windows - } -} - -fn extract_windows( - mut extracted_windows: ResMut, - mut closing: Extract>, - windows: Extract)>>, - mut removed: Extract>, - mut window_surfaces: ResMut, -) { - for (entity, window, handle, primary) in windows.iter() { - if primary.is_some() { - extracted_windows.primary = Some(entity); - } - - let (new_width, new_height) = ( - window.resolution.physical_width().max(1), - window.resolution.physical_height().max(1), - ); - - let extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow { - entity, - handle: handle.clone(), - physical_width: new_width, - physical_height: new_height, - present_mode: window.present_mode, - desired_maximum_frame_latency: window.desired_maximum_frame_latency, - swap_chain_texture: None, - swap_chain_texture_view: None, - size_changed: false, - swap_chain_texture_format: None, - swap_chain_texture_view_format: None, - present_mode_changed: false, - alpha_mode: window.composite_alpha_mode, - needs_initial_present: true, - }); - - if extracted_window.swap_chain_texture.is_none() { - // If we called present on the previous swap-chain texture last update, - // then drop the swap chain frame here, otherwise we can keep it for the - // next update as an optimization. `prepare_windows` will only acquire a new - // swap chain texture if needed. - extracted_window.swap_chain_texture_view = None; - } - extracted_window.size_changed = new_width != extracted_window.physical_width - || new_height != extracted_window.physical_height; - extracted_window.present_mode_changed = - window.present_mode != extracted_window.present_mode; - - if extracted_window.size_changed { - debug!( - "Window size changed from {}x{} to {}x{}", - extracted_window.physical_width, - extracted_window.physical_height, - new_width, - new_height - ); - extracted_window.physical_width = new_width; - extracted_window.physical_height = new_height; - } - - if extracted_window.present_mode_changed { - debug!( - "Window Present Mode changed from {:?} to {:?}", - extracted_window.present_mode, window.present_mode - ); - extracted_window.present_mode = window.present_mode; - } - } - - for closing_window in closing.read() { - extracted_windows.remove(&closing_window.window); - window_surfaces.remove(&closing_window.window); - } - for removed_window in removed.read() { - extracted_windows.remove(&removed_window); - window_surfaces.remove(&removed_window); - } -} - -struct SurfaceData { - // TODO: what lifetime should this be? - surface: WgpuWrapper>, - configuration: SurfaceConfiguration, - texture_view_format: Option, -} - -#[derive(Resource, Default)] -pub struct WindowSurfaces { - surfaces: EntityHashMap, - /// List of windows that we have already called the initial `configure_surface` for - configured_windows: HashSet, -} - -impl WindowSurfaces { - fn remove(&mut self, window: &Entity) { - self.surfaces.remove(window); - self.configured_windows.remove(window); - } -} - -/// (re)configures window surfaces, and obtains a swapchain texture for rendering. -/// -/// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is -/// the performance bottleneck. This can be seen in profiles as multiple prepare-set systems all -/// taking an unusually long time to complete, and all finishing at about the same time as the -/// `prepare_windows` system. Improvements in bevy are planned to avoid this happening when it -/// should not but it will still happen as it is easy for a user to create a large GPU workload -/// relative to the GPU performance and/or CPU workload. -/// This can be caused by many reasons, but several of them are: -/// - GPU workload is more than your current GPU can manage -/// - Error / performance bug in your custom shaders -/// - wgpu was unable to detect a proper GPU hardware-accelerated device given the chosen -/// [`Backends`](crate::settings::Backends), [`WgpuLimits`](crate::settings::WgpuLimits), -/// and/or [`WgpuFeatures`](crate::settings::WgpuFeatures). For example, on Windows currently -/// `DirectX 11` is not supported by wgpu 0.12 and so if your GPU/drivers do not support Vulkan, -/// it may be that a software renderer called "Microsoft Basic Render Driver" using `DirectX 12` -/// will be chosen and performance will be very poor. This is visible in a log message that is -/// output during renderer initialization. -/// Another alternative is to try to use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle) and -/// [`Backends::GL`](crate::settings::Backends::GL) with the `gles` feature enabled if your -/// GPU/drivers support `OpenGL 4.3` / `OpenGL ES 3.0` or later. -pub fn prepare_windows( - mut windows: ResMut, - mut window_surfaces: ResMut, - render_device: Res, - #[cfg(target_os = "linux")] render_instance: Res, -) { - for window in windows.windows.values_mut() { - let window_surfaces = window_surfaces.deref_mut(); - let Some(surface_data) = window_surfaces.surfaces.get(&window.entity) else { - continue; - }; - - // We didn't present the previous frame, so we can keep using our existing swapchain texture. - if window.has_swapchain_texture() && !window.size_changed && !window.present_mode_changed { - continue; - } - - // A recurring issue is hitting `wgpu::SurfaceError::Timeout` on certain Linux - // mesa driver implementations. This seems to be a quirk of some drivers. - // We'd rather keep panicking when not on Linux mesa, because in those case, - // the `Timeout` is still probably the symptom of a degraded unrecoverable - // application state. - // see https://github.com/bevyengine/bevy/pull/5957 - // and https://github.com/gfx-rs/wgpu/issues/1218 - #[cfg(target_os = "linux")] - let may_erroneously_timeout = || { - render_instance - .enumerate_adapters(wgpu::Backends::VULKAN) - .iter() - .any(|adapter| { - let name = adapter.get_info().name; - name.starts_with("Radeon") - || name.starts_with("AMD") - || name.starts_with("Intel") - }) - }; - - let surface = &surface_data.surface; - match surface.get_current_texture() { - Ok(frame) => { - window.set_swapchain_texture(frame); - } - Err(wgpu::SurfaceError::Outdated) => { - render_device.configure_surface(surface, &surface_data.configuration); - let frame = match surface.get_current_texture() { - Ok(frame) => frame, - Err(err) => { - // This is a common occurrence on X11 and Xwayland with NVIDIA drivers - // when opening and resizing the window. - warn!("Couldn't get swap chain texture after configuring. Cause: '{err}'"); - continue; - } - }; - window.set_swapchain_texture(frame); - } - #[cfg(target_os = "linux")] - Err(wgpu::SurfaceError::Timeout) => { - if may_erroneously_timeout() { - tracing::trace!( - "Couldn't get swap chain texture. This is probably a quirk \ - of your Linux GPU driver, so it can be safely ignored." - ); - } else { - warn!( - "Couldn't get swap chain texture due to a timeout; skipping this frame." - ); - } - continue; - } - Err(err) => { - panic!("Couldn't get swap chain texture, operation unrecoverable: {err}"); - } - } - window.swap_chain_texture_format = Some(surface_data.configuration.format); - } -} - -pub fn need_surface_configuration( - windows: Res, - window_surfaces: Res, -) -> bool { - for window in windows.windows.values() { - if !window_surfaces.configured_windows.contains(&window.entity) - || window.size_changed - || window.present_mode_changed - { - return true; - } - } - false -} - -// 2 is wgpu's default/what we've been using so far. -// 1 is the minimum, but may cause lower framerates due to the cpu waiting for the gpu to finish -// all work for the previous frame before starting work on the next frame, which then means the gpu -// has to wait for the cpu to finish to start on the next frame. -const DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY: u32 = 2; - -/// Creates window surfaces. -pub fn create_surfaces( - // By accessing a NonSend resource, we tell the scheduler to put this system on the main thread, - // which is necessary for some OS's - #[cfg(any(target_os = "macos", target_os = "ios"))] _marker: bevy_ecs::system::NonSendMarker, - mut windows: ResMut, - mut window_surfaces: ResMut, - render_instance: Res, - render_adapter: Res, - render_device: Res, -) { - for window in windows.windows.values_mut() { - let data = window_surfaces - .surfaces - .entry(window.entity) - .or_insert_with(|| { - let surface_target = SurfaceTargetUnsafe::RawHandle { - raw_display_handle: window.handle.get_display_handle(), - raw_window_handle: window.handle.get_window_handle(), - }; - // SAFETY: The window handles in ExtractedWindows will always be valid objects to create surfaces on - let surface = unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - // As of wgpu 0.15, only fallible if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. - render_instance - .create_surface_unsafe(surface_target) - .expect("Failed to create wgpu surface") - }; - let caps = surface.get_capabilities(&render_adapter); - let present_mode = present_mode(window, &caps); - let formats = caps.formats; - // For future HDR output support, we'll need to request a format that supports HDR, - // but as of wgpu 0.15 that is not yet supported. - // Prefer sRGB formats for surfaces, but fall back to first available format if no sRGB formats are available. - let mut format = *formats.first().expect("No supported formats for surface"); - for available_format in formats { - // Rgba8UnormSrgb and Bgra8UnormSrgb and the only sRGB formats wgpu exposes that we can use for surfaces. - if available_format == TextureFormat::Rgba8UnormSrgb - || available_format == TextureFormat::Bgra8UnormSrgb - { - format = available_format; - break; - } - } - - let texture_view_format = if !format.is_srgb() { - Some(format.add_srgb_suffix()) - } else { - None - }; - let configuration = SurfaceConfiguration { - format, - width: window.physical_width, - height: window.physical_height, - usage: TextureUsages::RENDER_ATTACHMENT, - present_mode, - desired_maximum_frame_latency: window - .desired_maximum_frame_latency - .map(NonZero::::get) - .unwrap_or(DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY), - alpha_mode: match window.alpha_mode { - CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto, - CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque, - CompositeAlphaMode::PreMultiplied => { - wgpu::CompositeAlphaMode::PreMultiplied - } - CompositeAlphaMode::PostMultiplied => { - wgpu::CompositeAlphaMode::PostMultiplied - } - CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit, - }, - view_formats: match texture_view_format { - Some(format) => vec![format], - None => vec![], - }, - }; - - render_device.configure_surface(&surface, &configuration); - - SurfaceData { - surface: WgpuWrapper::new(surface), - configuration, - texture_view_format, - } - }); - - if window.size_changed || window.present_mode_changed { - // normally this is dropped on present but we double check here to be safe as failure to - // drop it will cause validation errors in wgpu - drop(window.swap_chain_texture.take()); - #[cfg_attr( - target_arch = "wasm32", - expect(clippy::drop_non_drop, reason = "texture views are not drop on wasm") - )] - drop(window.swap_chain_texture_view.take()); - - data.configuration.width = window.physical_width; - data.configuration.height = window.physical_height; - let caps = data.surface.get_capabilities(&render_adapter); - data.configuration.present_mode = present_mode(window, &caps); - render_device.configure_surface(&data.surface, &data.configuration); - } - - window_surfaces.configured_windows.insert(window.entity); - } -} - -fn present_mode( - window: &mut ExtractedWindow, - caps: &wgpu::SurfaceCapabilities, -) -> wgpu::PresentMode { - let present_mode = match window.present_mode { - PresentMode::Fifo => wgpu::PresentMode::Fifo, - PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed, - PresentMode::Mailbox => wgpu::PresentMode::Mailbox, - PresentMode::Immediate => wgpu::PresentMode::Immediate, - PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, - PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, - }; - let fallbacks = match present_mode { - wgpu::PresentMode::AutoVsync => { - &[wgpu::PresentMode::FifoRelaxed, wgpu::PresentMode::Fifo][..] - } - wgpu::PresentMode::AutoNoVsync => &[ - wgpu::PresentMode::Immediate, - wgpu::PresentMode::Mailbox, - wgpu::PresentMode::Fifo, - ][..], - wgpu::PresentMode::Mailbox => &[ - wgpu::PresentMode::Mailbox, - wgpu::PresentMode::Immediate, - wgpu::PresentMode::Fifo, - ][..], - // Always end in FIFO to make sure it's always supported - x => &[x, wgpu::PresentMode::Fifo][..], - }; - let new_present_mode = fallbacks - .iter() - .copied() - .find(|fallback| caps.present_modes.contains(fallback)) - .unwrap_or_else(|| { - unreachable!( - "Fallback system failed to choose present mode. \ - This is a bug. Mode: {:?}, Options: {:?}", - window.present_mode, &caps.present_modes - ); - }); - if new_present_mode != present_mode && fallbacks.contains(&present_mode) { - info!("PresentMode {present_mode:?} requested but not available. Falling back to {new_present_mode:?}"); - } - new_present_mode -} diff --git a/third_party/bevy_render/src/view/window/screenshot.rs b/third_party/bevy_render/src/view/window/screenshot.rs deleted file mode 100644 index 655b9b3..0000000 --- a/third_party/bevy_render/src/view/window/screenshot.rs +++ /dev/null @@ -1,695 +0,0 @@ -use super::ExtractedWindows; -use crate::{ - gpu_readback, - render_asset::RenderAssets, - render_resource::{ - binding_types::texture_2d, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, - BindGroupLayoutEntries, Buffer, BufferUsages, CachedRenderPipelineId, FragmentState, - PipelineCache, RenderPipelineDescriptor, SpecializedRenderPipeline, - SpecializedRenderPipelines, Texture, TextureUsages, TextureView, VertexState, - }, - renderer::RenderDevice, - texture::{GpuImage, ManualTextureViews, OutputColorAttachment}, - view::{prepare_view_attachments, prepare_view_targets, ViewTargetAttachments, WindowSurfaces}, - ExtractSchedule, MainWorld, Render, RenderApp, RenderStartup, RenderSystems, -}; -use alloc::{borrow::Cow, sync::Arc}; -use bevy_app::{First, Plugin, Update}; -use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle, RenderAssetUsages}; -use bevy_camera::{ManualTextureViewHandle, NormalizedRenderTarget, RenderTarget}; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{ - entity::EntityHashMap, message::message_update_system, prelude::*, system::SystemState, -}; -use bevy_image::{Image, TextureFormatPixelInfo, ToExtents}; -use bevy_platform::collections::HashSet; -use bevy_reflect::Reflect; -use bevy_shader::Shader; -use bevy_tasks::AsyncComputeTaskPool; -use bevy_utils::default; -use bevy_window::{PrimaryWindow, WindowRef}; -use core::ops::Deref; -use std::{ - path::Path, - sync::{ - mpsc::{Receiver, Sender}, - Mutex, - }, -}; -use tracing::{error, info, warn}; -use wgpu::{CommandEncoder, Extent3d, TextureFormat}; - -#[derive(EntityEvent, Reflect, Deref, DerefMut, Debug)] -#[reflect(Debug)] -pub struct ScreenshotCaptured { - pub entity: Entity, - #[deref] - pub image: Image, -} - -/// A component that signals to the renderer to capture a screenshot this frame. -/// -/// This component should be spawned on a new entity with an observer that will trigger -/// with [`ScreenshotCaptured`] when the screenshot is ready. -/// -/// Screenshots are captured asynchronously and may not be available immediately after the frame -/// that the component is spawned on. The observer should be used to handle the screenshot when it -/// is ready. -/// -/// Note that the screenshot entity will be despawned after the screenshot is captured and the -/// observer is triggered. -/// -/// # Usage -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_render::view::screenshot::{save_to_disk, Screenshot}; -/// -/// fn take_screenshot(mut commands: Commands) { -/// commands.spawn(Screenshot::primary_window()) -/// .observe(save_to_disk("screenshot.png")); -/// } -/// ``` -#[derive(Component, Deref, DerefMut, Reflect, Debug)] -#[reflect(Component, Debug)] -pub struct Screenshot(pub RenderTarget); - -/// A marker component that indicates that a screenshot is currently being captured. -#[derive(Component, Default)] -pub struct Capturing; - -/// A marker component that indicates that a screenshot has been captured, the image is ready, and -/// the screenshot entity can be despawned. -#[derive(Component, Default)] -pub struct Captured; - -impl Screenshot { - /// Capture a screenshot of the provided window entity. - pub fn window(window: Entity) -> Self { - Self(RenderTarget::Window(WindowRef::Entity(window))) - } - - /// Capture a screenshot of the primary window, if one exists. - pub fn primary_window() -> Self { - Self(RenderTarget::Window(WindowRef::Primary)) - } - - /// Capture a screenshot of the provided render target image. - pub fn image(image: Handle) -> Self { - Self(RenderTarget::Image(image.into())) - } - - /// Capture a screenshot of the provided manual texture view. - pub fn texture_view(texture_view: ManualTextureViewHandle) -> Self { - Self(RenderTarget::TextureView(texture_view)) - } -} - -struct ScreenshotPreparedState { - pub texture: Texture, - pub buffer: Buffer, - pub bind_group: BindGroup, - pub pipeline_id: CachedRenderPipelineId, - pub size: Extent3d, -} - -#[derive(Resource, Deref, DerefMut)] -pub struct CapturedScreenshots(pub Arc>>); - -#[derive(Resource, Deref, DerefMut, Default)] -struct RenderScreenshotTargets(EntityHashMap); - -#[derive(Resource, Deref, DerefMut, Default)] -struct RenderScreenshotsPrepared(EntityHashMap); - -#[derive(Resource, Deref, DerefMut)] -struct RenderScreenshotsSender(Sender<(Entity, Image)>); - -/// Saves the captured screenshot to disk at the provided path. -pub fn save_to_disk(path: impl AsRef) -> impl FnMut(On) { - let path = path.as_ref().to_owned(); - move |screenshot_captured| { - let img = screenshot_captured.image.clone(); - match img.try_into_dynamic() { - Ok(dyn_img) => match image::ImageFormat::from_path(&path) { - Ok(format) => { - // discard the alpha channel which stores brightness values when HDR is enabled to make sure - // the screenshot looks right - let img = dyn_img.to_rgb8(); - #[cfg(not(target_arch = "wasm32"))] - match img.save_with_format(&path, format) { - Ok(_) => info!("Screenshot saved to {}", path.display()), - Err(e) => error!("Cannot save screenshot, IO error: {e}"), - } - - #[cfg(target_arch = "wasm32")] - { - let save_screenshot = || { - use image::EncodableLayout; - use wasm_bindgen::{JsCast, JsValue}; - - let mut image_buffer = std::io::Cursor::new(Vec::new()); - img.write_to(&mut image_buffer, format) - .map_err(|e| JsValue::from_str(&format!("{e}")))?; - - let parts = js_sys::Array::of1( - &js_sys::Uint8Array::new_from_slice( - image_buffer.into_inner().as_bytes(), - ) - .into(), - ); - let blob = web_sys::Blob::new_with_u8_array_sequence(&parts)?; - let url = web_sys::Url::create_object_url_with_blob(&blob)?; - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let link = document.create_element("a")?; - link.set_attribute("href", &url)?; - link.set_attribute( - "download", - path.file_name() - .and_then(|filename| filename.to_str()) - .ok_or_else(|| JsValue::from_str("Invalid filename"))?, - )?; - let html_element = link.dyn_into::()?; - html_element.click(); - web_sys::Url::revoke_object_url(&url)?; - Ok::<(), JsValue>(()) - }; - - match (save_screenshot)() { - Ok(_) => info!("Screenshot saved to {}", path.display()), - Err(e) => error!("Cannot save screenshot, error: {e:?}"), - }; - } - } - Err(e) => error!("Cannot save screenshot, requested format not recognized: {e}"), - }, - Err(e) => error!("Cannot save screenshot, screen format cannot be understood: {e}"), - } - } -} - -fn clear_screenshots(mut commands: Commands, screenshots: Query>) { - for entity in screenshots.iter() { - commands.entity(entity).despawn(); - } -} - -pub fn trigger_screenshots( - mut commands: Commands, - captured_screenshots: ResMut, -) { - let captured_screenshots = captured_screenshots.lock().unwrap(); - while let Ok((entity, image)) = captured_screenshots.try_recv() { - commands.entity(entity).insert(Captured); - commands.trigger(ScreenshotCaptured { image, entity }); - } -} - -fn extract_screenshots( - mut targets: ResMut, - mut main_world: ResMut, - mut system_state: Local< - Option< - SystemState<( - Commands, - Query>, - Query<(Entity, &Screenshot), Without>, - )>, - >, - >, - mut seen_targets: Local>, -) { - if system_state.is_none() { - *system_state = Some(SystemState::new(&mut main_world)); - } - let system_state = system_state.as_mut().unwrap(); - let (mut commands, primary_window, screenshots) = system_state.get_mut(&mut main_world); - - targets.clear(); - seen_targets.clear(); - - let primary_window = primary_window.iter().next(); - - for (entity, screenshot) in screenshots.iter() { - let render_target = screenshot.0.clone(); - let Some(render_target) = render_target.normalize(primary_window) else { - warn!( - "Unknown render target for screenshot, skipping: {:?}", - render_target - ); - continue; - }; - if seen_targets.contains(&render_target) { - warn!( - "Duplicate render target for screenshot, skipping entity {}: {:?}", - entity, render_target - ); - // If we don't despawn the entity here, it will be captured again in the next frame - commands.entity(entity).despawn(); - continue; - } - seen_targets.insert(render_target.clone()); - targets.insert(entity, render_target); - commands.entity(entity).insert(Capturing); - } - - system_state.apply(&mut main_world); -} - -fn prepare_screenshots( - targets: Res, - mut prepared: ResMut, - window_surfaces: Res, - render_device: Res, - screenshot_pipeline: Res, - pipeline_cache: Res, - mut pipelines: ResMut>, - images: Res>, - manual_texture_views: Res, - mut view_target_attachments: ResMut, -) { - prepared.clear(); - for (entity, target) in targets.iter() { - match target { - NormalizedRenderTarget::Window(window) => { - let window = window.entity(); - let Some(surface_data) = window_surfaces.surfaces.get(&window) else { - warn!("Unknown window for screenshot, skipping: {}", window); - continue; - }; - let view_format = surface_data - .texture_view_format - .unwrap_or(surface_data.configuration.format); - let size = Extent3d { - width: surface_data.configuration.width, - height: surface_data.configuration.height, - ..default() - }; - let (texture_view, state) = prepare_screenshot_state( - size, - view_format, - &render_device, - &screenshot_pipeline, - &pipeline_cache, - &mut pipelines, - ); - prepared.insert(*entity, state); - view_target_attachments.insert( - target.clone(), - OutputColorAttachment::new(texture_view.clone(), view_format), - ); - } - NormalizedRenderTarget::Image(image) => { - let Some(gpu_image) = images.get(&image.handle) else { - warn!("Unknown image for screenshot, skipping: {:?}", image); - continue; - }; - let view_format = gpu_image - .texture_view_format - .unwrap_or(gpu_image.texture_format); - let (texture_view, state) = prepare_screenshot_state( - gpu_image.size, - view_format, - &render_device, - &screenshot_pipeline, - &pipeline_cache, - &mut pipelines, - ); - prepared.insert(*entity, state); - view_target_attachments.insert( - target.clone(), - OutputColorAttachment::new(texture_view.clone(), view_format), - ); - } - NormalizedRenderTarget::TextureView(texture_view) => { - let Some(manual_texture_view) = manual_texture_views.get(texture_view) else { - warn!( - "Unknown manual texture view for screenshot, skipping: {:?}", - texture_view - ); - continue; - }; - let view_format = manual_texture_view.view_format; - let size = manual_texture_view.size.to_extents(); - let (texture_view, state) = prepare_screenshot_state( - size, - view_format, - &render_device, - &screenshot_pipeline, - &pipeline_cache, - &mut pipelines, - ); - prepared.insert(*entity, state); - view_target_attachments.insert( - target.clone(), - OutputColorAttachment::new(texture_view.clone(), view_format), - ); - } - NormalizedRenderTarget::None { .. } => { - // Nothing to screenshot! - } - } - } -} - -fn prepare_screenshot_state( - size: Extent3d, - format: TextureFormat, - render_device: &RenderDevice, - pipeline: &ScreenshotToScreenPipeline, - pipeline_cache: &PipelineCache, - pipelines: &mut SpecializedRenderPipelines, -) -> (TextureView, ScreenshotPreparedState) { - let texture = render_device.create_texture(&wgpu::TextureDescriptor { - label: Some("screenshot-capture-rendertarget"), - size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format, - usage: TextureUsages::RENDER_ATTACHMENT - | TextureUsages::COPY_SRC - | TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }); - let texture_view = texture.create_view(&Default::default()); - let buffer = render_device.create_buffer(&wgpu::BufferDescriptor { - label: Some("screenshot-transfer-buffer"), - size: gpu_readback::get_aligned_size(size, format.pixel_size().unwrap_or(0) as u32) as u64, - usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - let bind_group = render_device.create_bind_group( - "screenshot-to-screen-bind-group", - &pipeline_cache.get_bind_group_layout(&pipeline.bind_group_layout), - &BindGroupEntries::single(&texture_view), - ); - let pipeline_id = pipelines.specialize(pipeline_cache, pipeline, format); - - ( - texture_view, - ScreenshotPreparedState { - texture, - buffer, - bind_group, - pipeline_id, - size, - }, - ) -} - -pub struct ScreenshotPlugin; - -impl Plugin for ScreenshotPlugin { - fn build(&self, app: &mut bevy_app::App) { - embedded_asset!(app, "screenshot.wgsl"); - - let (tx, rx) = std::sync::mpsc::channel(); - app.insert_resource(CapturedScreenshots(Arc::new(Mutex::new(rx)))) - .add_systems( - First, - clear_screenshots - .after(message_update_system) - .before(ApplyDeferred), - ) - .add_systems(Update, trigger_screenshots); - - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - render_app - .insert_resource(RenderScreenshotsSender(tx)) - .init_resource::() - .init_resource::() - .init_resource::>() - .add_systems(RenderStartup, init_screenshot_to_screen_pipeline) - .add_systems(ExtractSchedule, extract_screenshots.ambiguous_with_all()) - .add_systems( - Render, - prepare_screenshots - .after(prepare_view_attachments) - .before(prepare_view_targets) - .in_set(RenderSystems::ManageViews), - ); - } -} - -#[derive(Resource)] -pub struct ScreenshotToScreenPipeline { - pub bind_group_layout: BindGroupLayoutDescriptor, - pub shader: Handle, -} - -pub fn init_screenshot_to_screen_pipeline(mut commands: Commands, asset_server: Res) { - let bind_group_layout = BindGroupLayoutDescriptor::new( - "screenshot-to-screen-bgl", - &BindGroupLayoutEntries::single( - wgpu::ShaderStages::FRAGMENT, - texture_2d(wgpu::TextureSampleType::Float { filterable: false }), - ), - ); - - let shader = load_embedded_asset!(asset_server.as_ref(), "screenshot.wgsl"); - - commands.insert_resource(ScreenshotToScreenPipeline { - bind_group_layout, - shader, - }); -} - -impl SpecializedRenderPipeline for ScreenshotToScreenPipeline { - type Key = TextureFormat; - - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - RenderPipelineDescriptor { - label: Some(Cow::Borrowed("screenshot-to-screen")), - layout: vec![self.bind_group_layout.clone()], - vertex: VertexState { - shader: self.shader.clone(), - ..default() - }, - primitive: wgpu::PrimitiveState { - cull_mode: Some(wgpu::Face::Back), - ..Default::default() - }, - multisample: Default::default(), - fragment: Some(FragmentState { - shader: self.shader.clone(), - targets: vec![Some(wgpu::ColorTargetState { - format: key, - blend: None, - write_mask: wgpu::ColorWrites::ALL, - })], - ..default() - }), - ..default() - } - } -} - -pub(crate) fn submit_screenshot_commands(world: &World, encoder: &mut CommandEncoder) { - let targets = world.resource::(); - let prepared = world.resource::(); - let pipelines = world.resource::(); - let gpu_images = world.resource::>(); - let windows = world.resource::(); - let manual_texture_views = world.resource::(); - - for (entity, render_target) in targets.iter() { - match render_target { - NormalizedRenderTarget::Window(window) => { - let window = window.entity(); - let Some(window) = windows.get(&window) else { - continue; - }; - let width = window.physical_width; - let height = window.physical_height; - let Some(texture_format) = window.swap_chain_texture_view_format else { - continue; - }; - let Some(swap_chain_texture_view) = window.swap_chain_texture_view.as_ref() else { - continue; - }; - render_screenshot( - encoder, - prepared, - pipelines, - entity, - width, - height, - texture_format, - swap_chain_texture_view, - ); - } - NormalizedRenderTarget::Image(image) => { - let Some(gpu_image) = gpu_images.get(&image.handle) else { - warn!("Unknown image for screenshot, skipping: {:?}", image); - continue; - }; - let width = gpu_image.size.width; - let height = gpu_image.size.height; - let texture_format = gpu_image.texture_format; - let texture_view = gpu_image.texture_view.deref(); - render_screenshot( - encoder, - prepared, - pipelines, - entity, - width, - height, - texture_format, - texture_view, - ); - } - NormalizedRenderTarget::TextureView(texture_view) => { - let Some(texture_view) = manual_texture_views.get(texture_view) else { - warn!( - "Unknown manual texture view for screenshot, skipping: {:?}", - texture_view - ); - continue; - }; - let width = texture_view.size.x; - let height = texture_view.size.y; - let texture_format = texture_view.view_format; - let texture_view = texture_view.texture_view.deref(); - render_screenshot( - encoder, - prepared, - pipelines, - entity, - width, - height, - texture_format, - texture_view, - ); - } - NormalizedRenderTarget::None { .. } => { - // Nothing to screenshot! - } - }; - } -} - -fn render_screenshot( - encoder: &mut CommandEncoder, - prepared: &RenderScreenshotsPrepared, - pipelines: &PipelineCache, - entity: &Entity, - width: u32, - height: u32, - texture_format: TextureFormat, - texture_view: &wgpu::TextureView, -) { - if let Some(prepared_state) = &prepared.get(entity) { - let extent = Extent3d { - width, - height, - depth_or_array_layers: 1, - }; - encoder.copy_texture_to_buffer( - prepared_state.texture.as_image_copy(), - wgpu::TexelCopyBufferInfo { - buffer: &prepared_state.buffer, - layout: gpu_readback::layout_data(extent, texture_format), - }, - extent, - ); - - if let Some(pipeline) = pipelines.get_render_pipeline(prepared_state.pipeline_id) { - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("screenshot_to_screen_pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: texture_view, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - pass.set_pipeline(pipeline); - pass.set_bind_group(0, &prepared_state.bind_group, &[]); - pass.draw(0..3, 0..1); - } - } -} - -pub(crate) fn collect_screenshots(world: &mut World) { - #[cfg(feature = "trace")] - let _span = tracing::info_span!("collect_screenshots").entered(); - - let sender = world.resource::().deref().clone(); - let prepared = world.resource::(); - - for (entity, prepared) in prepared.iter() { - let entity = *entity; - let sender = sender.clone(); - let width = prepared.size.width; - let height = prepared.size.height; - let texture_format = prepared.texture.format(); - let Ok(pixel_size) = texture_format.pixel_size() else { - continue; - }; - let buffer = prepared.buffer.clone(); - - let finish = async move { - let (tx, rx) = async_channel::bounded(1); - let buffer_slice = buffer.slice(..); - // The polling for this map call is done every frame when the command queue is submitted. - buffer_slice.map_async(wgpu::MapMode::Read, move |result| { - if let Err(err) = result { - panic!("{}", err.to_string()); - } - tx.try_send(()).unwrap(); - }); - rx.recv().await.unwrap(); - let data = buffer_slice.get_mapped_range(); - // we immediately move the data to CPU memory to avoid holding the mapped view for long - let mut result = Vec::from(&*data); - drop(data); - - if result.len() != ((width * height) as usize * pixel_size) { - // Our buffer has been padded because we needed to align to a multiple of 256. - // We remove this padding here - let initial_row_bytes = width as usize * pixel_size; - let buffered_row_bytes = - gpu_readback::align_byte_size(width * pixel_size as u32) as usize; - - let mut take_offset = buffered_row_bytes; - let mut place_offset = initial_row_bytes; - for _ in 1..height { - result.copy_within(take_offset..take_offset + buffered_row_bytes, place_offset); - take_offset += buffered_row_bytes; - place_offset += initial_row_bytes; - } - result.truncate(initial_row_bytes * height as usize); - } - - if let Err(e) = sender.send(( - entity, - Image::new( - Extent3d { - width, - height, - depth_or_array_layers: 1, - }, - wgpu::TextureDimension::D2, - result, - texture_format, - RenderAssetUsages::RENDER_WORLD, - ), - )) { - error!("Failed to send screenshot: {}", e); - } - }; - - AsyncComputeTaskPool::get().spawn(finish).detach(); - } -} diff --git a/third_party/bevy_render/src/view/window/screenshot.wgsl b/third_party/bevy_render/src/view/window/screenshot.wgsl deleted file mode 100644 index 2743fa1..0000000 --- a/third_party/bevy_render/src/view/window/screenshot.wgsl +++ /dev/null @@ -1,16 +0,0 @@ -// This vertex shader will create a triangle that will cover the entire screen -// with minimal effort, avoiding the need for a vertex buffer etc. -@vertex -fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { - let x = f32((in_vertex_index & 1u) << 2u); - let y = f32((in_vertex_index & 2u) << 1u); - return vec4(x - 1.0, y - 1.0, 0.0, 1.0); -} - -@group(0) @binding(0) var t: texture_2d; - -@fragment -fn fs_main(@builtin(position) pos: vec4) -> @location(0) vec4 { - let coords = floor(pos.xy); - return textureLoad(t, vec2(coords), 0i); -} diff --git a/third_party/bevy_ufbx/.cargo-ok b/third_party/bevy_ufbx/.cargo-ok new file mode 100644 index 0000000..5f8b795 --- /dev/null +++ b/third_party/bevy_ufbx/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/third_party/bevy_ufbx/.cargo_vcs_info.json b/third_party/bevy_ufbx/.cargo_vcs_info.json new file mode 100644 index 0000000..23a9439 --- /dev/null +++ b/third_party/bevy_ufbx/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "76957e35dc448209886cd0977ab7f893e0de12c3" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/third_party/bevy_ufbx/.github/ISSUE_TEMPLATE/bug_report.md b/third_party/bevy_ufbx/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/third_party/bevy_ufbx/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/third_party/bevy_ufbx/.github/ISSUE_TEMPLATE/feature_request.md b/third_party/bevy_ufbx/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/third_party/bevy_ufbx/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/third_party/bevy_ufbx/.github/dependabot.yml b/third_party/bevy_ufbx/.github/dependabot.yml new file mode 100644 index 0000000..5251d0f --- /dev/null +++ b/third_party/bevy_ufbx/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "cargo" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/third_party/bevy_ufbx/.github/workflows/ci.yml b/third_party/bevy_ufbx/.github/workflows/ci.yml new file mode 100644 index 0000000..3de62ba --- /dev/null +++ b/third_party/bevy_ufbx/.github/workflows/ci.yml @@ -0,0 +1,107 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + # Keep binaries small. + RUSTFLAGS: "-C debuginfo=0" + +jobs: + # ── Fast compile check ──────────────────────────────────────────────────── + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-check-${{ hashFiles('**/Cargo.toml') }} + restore-keys: | + ${{ runner.os }}-cargo-check- + ${{ runner.os }}-cargo- + + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libasound2-dev \ + libudev-dev \ + libwayland-dev \ + libxkbcommon-dev + + - name: Check crate + run: cargo check + + - name: Check examples + run: cargo check --examples + + - name: Check tests + run: cargo test --no-run + + # ── Unit & integration tests ────────────────────────────────────────────── + test: + name: Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.toml') }} + restore-keys: | + ${{ runner.os }}-cargo-test- + ${{ runner.os }}-cargo- + + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libasound2-dev \ + libudev-dev \ + libwayland-dev \ + libxkbcommon-dev + + # Sparse-clone only the data/ directory from ufbx/ufbx. + # --filter=blob:none skips downloading blobs during the initial clone; + # they are fetched on demand when the sparse checkout is materialised. + - name: Clone ufbx test data + run: | + git clone \ + --filter=blob:none \ + --sparse \ + --depth 1 \ + https://github.com/ufbx/ufbx.git \ + /tmp/ufbx + cd /tmp/ufbx + git sparse-checkout set data + + - name: Run tests + env: + UFBX_TEST_DATA: /tmp/ufbx/data + run: cargo test -- --nocapture diff --git a/third_party/bevy_ufbx/.gitignore b/third_party/bevy_ufbx/.gitignore new file mode 100644 index 0000000..a9e6727 --- /dev/null +++ b/third_party/bevy_ufbx/.gitignore @@ -0,0 +1,24 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ +/target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +Cargo.lock \ No newline at end of file diff --git a/third_party/bevy_ufbx/Cargo.toml b/third_party/bevy_ufbx/Cargo.toml new file mode 100644 index 0000000..22d4071 --- /dev/null +++ b/third_party/bevy_ufbx/Cargo.toml @@ -0,0 +1,109 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2024" +name = "bevy_ufbx" +version = "0.18.1-rc.1" +authors = ["Artur "] +build = false +exclude = ["assets/"] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "FBX asset loader for Bevy using the ufbx library" +readme = "README.md" +keywords = [ + "bevy", + "fbx", + "asset", + "loader", +] +categories = [ + "game-development", + "graphics", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/FizzWizzleDazzle/bevy_ufbx" + +[lib] +name = "bevy_ufbx" +path = "src/lib.rs" + +[[example]] +name = "load_fbx" +path = "examples/load_fbx.rs" + +[[test]] +name = "basic_loading" +path = "tests/basic_loading.rs" + +[[test]] +name = "integration" +path = "tests/integration.rs" + +[[test]] +name = "label" +path = "tests/label.rs" + +[[test]] +name = "load_all_ufbx_data" +path = "tests/load_all_ufbx_data.rs" + +[[test]] +name = "loader_settings" +path = "tests/loader_settings.rs" + +[[test]] +name = "types" +path = "tests/types.rs" + +[[test]] +name = "utils" +path = "tests/utils.rs" + +[dependencies.bevy] +version = "0.19" +features = [ + "bevy_asset", + "bevy_render", + "bevy_pbr", + "bevy_world_serialization", +] +default-features = false + +[dependencies.bevy_image] +version = "0.19" + +[dependencies.bevy_material] +version = "0.19" + +[dependencies.serde] +version = "1.0" +features = ["derive"] + +[dependencies.thiserror] +version = "2.0" + +[dependencies.ufbx] +version = "0.9" + +[dev-dependencies.bevy] +version = "0.19" +default-features = true + +[dev-dependencies.serde_json] +version = "1.0.145" + +[dev-dependencies.ufbx] +version = "0.9" diff --git a/third_party/bevy_ufbx/Cargo.toml.orig b/third_party/bevy_ufbx/Cargo.toml.orig new file mode 100644 index 0000000..c4f532a --- /dev/null +++ b/third_party/bevy_ufbx/Cargo.toml.orig @@ -0,0 +1,29 @@ +[package] +name = "bevy_ufbx" +version = "0.18.1-rc.1" +edition = "2024" +authors = ["Artur "] +license = "MIT OR Apache-2.0" +repository = "https://github.com/FizzWizzleDazzle/bevy_ufbx" +description = "FBX asset loader for Bevy using the ufbx library" +keywords = ["bevy", "fbx", "asset", "loader"] +categories = ["game-development", "graphics"] +exclude = ["assets/"] + +[dependencies] +bevy = { version = "0.19", default-features = false, features = [ + "bevy_asset", + "bevy_render", + "bevy_pbr", + "bevy_world_serialization", +]} +bevy_image = "0.19" +bevy_material = "0.19" +ufbx = "0.9" +thiserror = "2.0" +serde = { version = "1.0", features = ["derive"] } + +[dev-dependencies] +bevy = { version = "0.19", default-features = true } +serde_json = "1.0.145" +ufbx = "0.9" diff --git a/third_party/bevy_render/LICENSE-APACHE b/third_party/bevy_ufbx/LICENSE-APACHE similarity index 89% rename from third_party/bevy_render/LICENSE-APACHE rename to third_party/bevy_ufbx/LICENSE-APACHE index d9a10c0..f49a4e1 100644 --- a/third_party/bevy_render/LICENSE-APACHE +++ b/third_party/bevy_ufbx/LICENSE-APACHE @@ -174,3 +174,28 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/third_party/bevy_render/LICENSE-MIT b/third_party/bevy_ufbx/LICENSE-MIT similarity index 96% rename from third_party/bevy_render/LICENSE-MIT rename to third_party/bevy_ufbx/LICENSE-MIT index 9cf1062..305f834 100644 --- a/third_party/bevy_render/LICENSE-MIT +++ b/third_party/bevy_ufbx/LICENSE-MIT @@ -1,5 +1,7 @@ MIT License +Copyright (c) 2025 FizzWizZleDazzle + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/third_party/bevy_ufbx/README.md b/third_party/bevy_ufbx/README.md new file mode 100644 index 0000000..2026b2c --- /dev/null +++ b/third_party/bevy_ufbx/README.md @@ -0,0 +1,112 @@ +# bevy_ufbx + +FBX asset loader for [Bevy](https://bevyengine.org) powered by [ufbx](https://github.com/ufbx/ufbx). + +## Bevy compatibility + +| bevy | bevy_ufbx | +|------|-----------| +| 0.18 | 0.18 | +| 0.17 | 0.17 | + +## Installation + +```toml +[dependencies] +bevy = "0.18" +bevy_ufbx = "0.18" +``` + +## Quick start + +```rust +use bevy::prelude::*; +use bevy_ufbx::FbxPlugin; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(FbxPlugin) + .add_systems(Startup, setup) + .run(); +} + +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn(Camera3d::default()); + commands.spawn(SceneRoot( + asset_server.load("character.fbx#Scene0"), + )); +} +``` + +## Loading with custom settings + +```rust +use bevy_ufbx::{Fbx, FbxLoaderSettings}; + +fn setup(asset_server: Res) { + asset_server.load_with_settings::( + "environment.fbx", + |s| { + s.load_cameras = false; + s.load_lights = false; + }, + ); +} +``` + +### `FbxLoaderSettings` fields + +| Field | Type | Default | Description | +|----------------------|----------------------|-------------------------------|---------------------------------------------| +| `load_meshes` | `RenderAssetUsages` | `RenderAssetUsages::default()` | Which worlds the mesh is available in | +| `load_materials` | `RenderAssetUsages` | `RenderAssetUsages::default()` | Which worlds the material is available in | +| `load_cameras` | `bool` | `true` | Import cameras from the FBX | +| `load_lights` | `bool` | `true` | Import lights from the FBX | +| `include_source` | `bool` | `false` | Keep raw bytes in the loaded asset | +| `convert_coordinates`| `bool` | `false` | Remap axes to Bevy's right-handed Y-up space| + +## Asset labels + +Individual sub-assets can be addressed with `#Label` path suffixes: + +| Label | Type | Description | +|-------------------|---------------------|-----------------------------------------| +| `Scene{N}` | `Scene` | Scene hierarchy (N = scene index) | +| `Mesh{N}` | `Mesh` | Triangulated mesh | +| `Material{N}` | `StandardMaterial` | PBR material | +| `Node{N}` | `FbxNode` | Transform node | +| `Skin{N}` | `FbxSkin` | Skeletal skin | +| `DefaultMaterial` | `StandardMaterial` | Fallback material when none is present | + +```rust +let scene = asset_server.load::("model.fbx#Scene0"); +let mesh = asset_server.load::("model.fbx#Mesh0"); +let material = asset_server.load::("model.fbx#Material0"); +``` + +## Supported features + +- Triangle meshes with positions, normals, and UVs +- Multi-material meshes (face groups per material) +- PBR materials (base color, metallic, roughness, normal, emission, AO) +- Texture mapping, including `.fbm` embedded texture folders +- Skeletal skinning data (bone weights / bind poses) +- Scene hierarchy (node transforms) +- Directional, point, and spot lights + +## Limitations + +- Animation curves are parsed but **not yet forwarded to Bevy's animation system** +- Cameras are not imported into Bevy camera components +- NURBS and subdivision surfaces are not supported (ufbx triangulates on load) + +## Example + +```sh +cargo run --example load_fbx -- my_model.fbx +``` + +## License + +Licensed under either of [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE) at your option. diff --git a/third_party/bevy_ufbx/examples/load_fbx.rs b/third_party/bevy_ufbx/examples/load_fbx.rs new file mode 100644 index 0000000..3f1b827 --- /dev/null +++ b/third_party/bevy_ufbx/examples/load_fbx.rs @@ -0,0 +1,92 @@ +//! Loads and displays an FBX file. +//! +//! Pass a path (relative to the `assets/` directory) as the first argument, +//! or drop a `cube.fbx` into `assets/` and run without arguments: +//! +//! ```sh +//! cargo run --example load_fbx -- spider.fbx +//! ``` +//! +//! The loaded model spins slowly so you can inspect it from all sides. +//! Use `FbxLoaderSettings` to control what gets imported – see the +//! `load_with_settings` section at the bottom of `setup`. + +use bevy::prelude::*; +use bevy_ufbx::{Fbx, FbxLoaderSettings, FbxPlugin}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(FbxPlugin) + .add_systems(Startup, setup) + .add_systems(Update, rotate) + .run(); +} + +// ── Components / resources ────────────────────────────────────────────────── + +#[derive(Component)] +struct Spinning; + +#[derive(Resource)] +struct FbxHandle(Handle); + +// ── Setup ─────────────────────────────────────────────────────────────────── + +fn setup(mut commands: Commands, asset_server: Res) { + // ── Camera ────────────────────────────────────────────────────────────── + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(4.0, 4.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y), + )); + + // ── Lighting ───────────────────────────────────────────────────────────── + commands.spawn(( + DirectionalLight { + illuminance: 10_000.0, + shadows_enabled: true, + ..default() + }, + Transform::from_rotation(Quat::from_euler( + EulerRot::XYZ, + -45_f32.to_radians(), + 45_f32.to_radians(), + 0.0, + )), + )); + + commands.insert_resource(GlobalAmbientLight { + color: Color::WHITE, + brightness: 200.0, + affects_lightmapped_meshes: false, + }); + + // ── FBX path from CLI args (defaults to "cube.fbx") ────────────────────── + let path = std::env::args() + .nth(1) + .unwrap_or_else(|| "cube.fbx".to_string()); + + // --- Default loading --- + let scene = asset_server.load(format!("{path}#Scene0")); + commands.spawn((SceneRoot(scene), Spinning)); + + // --- Custom settings (uncomment to use instead) --- + // let fbx = asset_server.load_with_settings::( + // path.clone(), + // |s| { + // s.load_cameras = false; + // s.load_lights = false; + // }, + // ); + // commands.insert_resource(FbxHandle(fbx)); + + info!("Loading '{path}' …"); +} + +// ── Systems ────────────────────────────────────────────────────────────────── + +fn rotate(time: Res