diff --git a/.github/actions/install-linux-deps/action.yml b/.github/actions/install-linux-deps/action.yml index a59e2240b6128..c494200e5a649 100644 --- a/.github/actions/install-linux-deps/action.yml +++ b/.github/actions/install-linux-deps/action.yml @@ -28,7 +28,7 @@ inputs: wayland: description: Install Wayland (libwayland-dev) required: false - default: "false" + default: "true" xkb: description: Install xkb (libxkbcommon-dev) required: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68ddb854bc228..7ad9a1ce76777 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,8 @@ name: CI +permissions: + contents: read + on: merge_group: pull_request: @@ -257,9 +260,12 @@ jobs: with: target: wasm32-unknown-unknown - name: Check wasm + env: + RUSTFLAGS: --cfg getrandom_backend="wasm_js" run: cargo check --target wasm32-unknown-unknown build-wasm-atomics: + if: ${{ false }} # Disabled temporarily due to https://github.com/rust-lang/rust/issues/145101 runs-on: ubuntu-latest timeout-minutes: 30 needs: build @@ -287,7 +293,7 @@ jobs: - name: Check wasm run: cargo check --target wasm32-unknown-unknown -Z build-std=std,panic_abort env: - RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory" + RUSTFLAGS: '-C target-feature=+atomics,+bulk-memory --cfg getrandom_backend="wasm_js"' markdownlint: runs-on: ubuntu-latest diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index ceb0f42b05444..84c6852f86eb1 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -1,14 +1,17 @@ name: Dependencies +permissions: + contents: read + on: pull_request: paths: - - '**/Cargo.toml' - - 'deny.toml' + - "**/Cargo.toml" + - "deny.toml" push: paths: - - '**/Cargo.toml' - - 'deny.toml' + - "**/Cargo.toml" + - "deny.toml" branches: - main diff --git a/.github/workflows/example-run-report.yml b/.github/workflows/example-run-report.yml index 198dee72e4586..fbde12411fc57 100644 --- a/.github/workflows/example-run-report.yml +++ b/.github/workflows/example-run-report.yml @@ -6,6 +6,7 @@ name: Example Run - PR Comments # Also requesting write permissions on PR to be able to comment permissions: pull-requests: "write" + contents: "read" on: workflow_run: @@ -59,8 +60,10 @@ jobs: path: screenshots - name: branch name id: branch-name + env: + BRANCH_NAME: ${{ github.event.workflow_run.head_branch }} run: | - echo "result=PR-$(cat PR)-${{ github.event.workflow_run.head_branch }}" >> $GITHUB_OUTPUT + echo "result=PR-$(cat PR)-$BRANCH_NAME" >> $GITHUB_OUTPUT - name: PR number id: pr-number run: | diff --git a/.github/workflows/example-run.yml b/.github/workflows/example-run.yml index d26faeee56dd9..b25468bba46df 100644 --- a/.github/workflows/example-run.yml +++ b/.github/workflows/example-run.yml @@ -1,5 +1,8 @@ name: Example Run +permissions: + contents: read + on: merge_group: pull_request: diff --git a/.github/workflows/send-screenshots-to-pixeleagle.yml b/.github/workflows/send-screenshots-to-pixeleagle.yml index ee2b5e3dd1160..3a8ac2c1393cf 100644 --- a/.github/workflows/send-screenshots-to-pixeleagle.yml +++ b/.github/workflows/send-screenshots-to-pixeleagle.yml @@ -1,5 +1,8 @@ name: Send Screenshots to Pixel Eagle +permissions: + contents: read + on: workflow_call: inputs: @@ -45,9 +48,10 @@ jobs: if: ${{ fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }} env: project: B04F67C0-C054-4A6F-92EC-F599FEC2FD1D + branch: ${{ inputs.branch }} run: | # Create a new run with its associated metadata - metadata='{"os":"${{ inputs.os }}", "commit": "${{ inputs.commit }}", "branch": "${{ inputs.branch }}"}' + metadata='{"os":"${{ inputs.os }}", "commit": "${{ inputs.commit }}", "branch": "$branch"}' run=`curl https://pixel-eagle.com/$project/runs --json "$metadata" --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }} | jq '.id'` SAVEIFS=$IFS diff --git a/.github/workflows/update-caches.yml b/.github/workflows/update-caches.yml index c086cbc7277e7..3935030eccc0f 100644 --- a/.github/workflows/update-caches.yml +++ b/.github/workflows/update-caches.yml @@ -1,5 +1,8 @@ name: Update Actions Caches +permissions: + contents: read + on: # Manually workflow_dispatch: diff --git a/.github/workflows/validation-jobs.yml b/.github/workflows/validation-jobs.yml index 47bd3fe054bbf..2e4575e384a8f 100644 --- a/.github/workflows/validation-jobs.yml +++ b/.github/workflows/validation-jobs.yml @@ -1,5 +1,8 @@ name: validation jobs +permissions: + contents: read + on: merge_group: pull_request: @@ -130,6 +133,8 @@ jobs: cd ../.. - name: First Wasm build + env: + RUSTFLAGS: --cfg getrandom_backend="wasm_js" run: | cargo build --release --example testbed_ui --target wasm32-unknown-unknown diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index b4ddffdb9dbb7..3a1cf2a76db30 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -1,10 +1,13 @@ name: Weekly beta compile test +permissions: + contents: read + on: schedule: # New versions of rust release on Thursdays. We test on Mondays to get at least 3 days of warning before all our CI breaks again. # https://forge.rust-lang.org/release/process.html#release-day-thursday - - cron: '0 12 * * 1' + - cron: "0 12 * * 1" workflow_dispatch: env: @@ -85,7 +88,7 @@ jobs: close-any-open-issues: runs-on: ubuntu-latest - needs: ['test', 'lint', 'check-compiles'] + needs: ["test", "lint", "check-compiles"] permissions: issues: write steps: @@ -106,14 +109,13 @@ jobs: COMMENT: | [Last pipeline run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) successfully completed. Closing issue. - open-issue: name: Warn that weekly CI fails runs-on: ubuntu-latest needs: [test, lint, check-compiles] permissions: issues: write - # We disable this job on forks, because + # We disable this job on forks, because # Use always() so the job doesn't get canceled if any other jobs fail if: ${{ github.repository == 'bevyengine/bevy' && always() && contains(needs.*.result, 'failure') }} steps: diff --git a/Cargo.toml b/Cargo.toml index c37f074c6c667..59f3c631f47ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,8 @@ members = [ "examples/mobile", # Examples of using Bevy on no_std platforms. "examples/no_std/*", + # Examples of compiling Bevy with automatic reflect type registration for platforms without `inventory` support. + "examples/reflection/auto_register_static", # Benchmarks "benches", # Internal tools that are not published. @@ -71,6 +73,8 @@ allow_attributes = "warn" allow_attributes_without_reason = "warn" [workspace.lints.rust] +# Strictly temporary until encase fixes dead code generation from ShaderType macros +dead_code = "allow" missing_docs = "warn" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } unsafe_code = "deny" @@ -116,6 +120,8 @@ allow_attributes = "warn" allow_attributes_without_reason = "warn" [lints.rust] +# Strictly temporary until encase fixes dead code generation from ShaderType macros +dead_code = "allow" missing_docs = "warn" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } unsafe_code = "deny" @@ -160,12 +166,14 @@ default = [ "hdr", "multi_threaded", "png", + "reflect_auto_register", "smaa_luts", "sysinfo_plugin", "tonemapping_luts", "vorbis", "webgl2", "x11", + "wayland", "debug", "zstd_rust", "experimental_bevy_feathers", @@ -552,6 +560,12 @@ reflect_functions = ["bevy_internal/reflect_functions"] # Enable documentation reflection reflect_documentation = ["bevy_internal/reflect_documentation"] +# Enable automatic reflect registration +reflect_auto_register = ["bevy_internal/reflect_auto_register"] + +# Enable automatic reflect registration without inventory. See `reflect::load_type_registrations` for more info. +reflect_auto_register_static = ["bevy_internal/reflect_auto_register_static"] + # Enable winit custom cursor support custom_cursor = ["bevy_internal/custom_cursor"] @@ -600,8 +614,8 @@ tracing = { version = "0.1", default-features = false, optional = true } bevy_dylib = { path = "crates/bevy_dylib", version = "0.17.0-dev", default-features = false, optional = true } [dev-dependencies] -rand = "0.8.0" -rand_chacha = "0.3.1" +rand = "0.9.0" +rand_chacha = "0.9.0" ron = "0.10" flate2 = "1.0" serde = { version = "1", features = ["derive"] } @@ -623,21 +637,18 @@ crossbeam-channel = "0.5.0" argh = "0.1.12" thiserror = "2.0" event-listener = "5.3.0" -hyper = { version = "1", features = ["server", "http1"] } -http-body-util = "0.1" anyhow = "1" -macro_rules_attribute = "0.2" accesskit = "0.21" nonmax = "0.5" variadics_please = "1" [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] -smol = "2" -smol-macros = "0.1" -smol-hyper = "0.1" ureq = { version = "3.0.8", features = ["json"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] +getrandom = { version = "0.3", default-features = false, features = [ + "wasm_js", +] } wasm-bindgen = { version = "0.2" } web-sys = { version = "0.3", features = ["Window"] } @@ -2787,6 +2798,17 @@ description = "Demonstrates how to create and use type data" category = "Reflection" wasm = false +[[example]] +name = "auto_register_static" +path = "examples/reflection/auto_register_static/src/lib.rs" +doc-scrape-examples = true + +[package.metadata.example.auto_register_static] +name = "Automatic types registration" +description = "Demonstrates how to set up automatic reflect types registration for platforms without `inventory` support" +category = "Reflection" +wasm = false + # Scene [[example]] name = "scene" @@ -2818,28 +2840,6 @@ A shader in its most common usage is a small program that is run by the GPU per- There are also compute shaders which are used for more general processing leveraging the GPU's parallelism. """ -[[example]] -name = "custom_vertex_attribute" -path = "examples/shader/custom_vertex_attribute.rs" -doc-scrape-examples = true - -[package.metadata.example.custom_vertex_attribute] -name = "Custom Vertex Attribute" -description = "A shader that reads a mesh's custom vertex attribute" -category = "Shaders" -wasm = true - -[[example]] -name = "custom_post_processing" -path = "examples/shader/custom_post_processing.rs" -doc-scrape-examples = true - -[package.metadata.example.custom_post_processing] -name = "Post Processing - Custom Render Pass" -description = "A custom post processing effect, using a custom render pass that runs after the main pass" -category = "Shaders" -wasm = true - [[example]] name = "shader_defs" path = "examples/shader/shader_defs.rs" @@ -2930,29 +2930,6 @@ description = "A shader that uses WESL" category = "Shaders" wasm = true -[[example]] -name = "custom_shader_instancing" -path = "examples/shader/custom_shader_instancing.rs" -doc-scrape-examples = true - -[package.metadata.example.custom_shader_instancing] -name = "Instancing" -description = "A shader that renders a mesh multiple times in one draw call using low level rendering api" -category = "Shaders" -wasm = true - -[[example]] -name = "custom_render_phase" -path = "examples/shader/custom_render_phase.rs" -doc-scrape-examples = true - -[package.metadata.example.custom_render_phase] -name = "Custom Render Phase" -description = "Shows how to make a complete render phase" -category = "Shaders" -wasm = true - - [[example]] name = "automatic_instancing" path = "examples/shader/automatic_instancing.rs" @@ -3008,17 +2985,6 @@ description = "A shader that shows how to reuse the core bevy PBR shading functi category = "Shaders" wasm = true -[[example]] -name = "texture_binding_array" -path = "examples/shader/texture_binding_array.rs" -doc-scrape-examples = true - -[package.metadata.example.texture_binding_array] -name = "Texture Binding Array (Bindless Textures)" -description = "A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures)." -category = "Shaders" -wasm = false - [[example]] name = "storage_buffer" path = "examples/shader/storage_buffer.rs" @@ -3030,9 +2996,74 @@ description = "A shader that shows how to bind a storage buffer using a custom m category = "Shaders" wasm = true +# Shaders advanced +[[package.metadata.example_category]] +name = "Shaders - advanced" +description = """ +These examples demonstrate how to use the lower level rendering apis exposed from bevy. + +These are generally simplified examples of bevy's own rendering infrastructure. +""" + +[[example]] +name = "custom_vertex_attribute" +path = "examples/shader_advanced/custom_vertex_attribute.rs" +doc-scrape-examples = true + +[package.metadata.example.custom_vertex_attribute] +name = "Custom Vertex Attribute" +description = "A shader that reads a mesh's custom vertex attribute" +category = "Shaders" +wasm = true + +[[example]] +name = "custom_post_processing" +path = "examples/shader_advanced/custom_post_processing.rs" +doc-scrape-examples = true + +[package.metadata.example.custom_post_processing] +name = "Post Processing - Custom Render Pass" +description = "A custom post processing effect, using a custom render pass that runs after the main pass" +category = "Shaders" +wasm = true + +[[example]] +name = "custom_shader_instancing" +path = "examples/shader_advanced/custom_shader_instancing.rs" +doc-scrape-examples = true + +[package.metadata.example.custom_shader_instancing] +name = "Instancing" +description = "A shader that renders a mesh multiple times in one draw call using low level rendering api" +category = "Shaders" +wasm = true + +[[example]] +name = "custom_render_phase" +path = "examples/shader_advanced/custom_render_phase.rs" +doc-scrape-examples = true + +[package.metadata.example.custom_render_phase] +name = "Custom Render Phase" +description = "Shows how to make a complete render phase" +category = "Shaders" +wasm = true + +[[example]] +name = "texture_binding_array" +path = "examples/shader_advanced/texture_binding_array.rs" +doc-scrape-examples = true + +[package.metadata.example.texture_binding_array] +name = "Texture Binding Array (Bindless Textures)" +description = "A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures)." +category = "Shaders" +wasm = false + + [[example]] name = "specialized_mesh_pipeline" -path = "examples/shader/specialized_mesh_pipeline.rs" +path = "examples/shader_advanced/specialized_mesh_pipeline.rs" doc-scrape-examples = true [package.metadata.example.specialized_mesh_pipeline] @@ -3608,6 +3639,18 @@ description = "Demonstrates how to control the relative depth (z-position) of UI category = "UI (User Interface)" wasm = true +[[example]] +name = "virtual_keyboard" +path = "examples/ui/virtual_keyboard.rs" +doc-scrape-examples = true +required-features = ["experimental_bevy_feathers"] + +[package.metadata.example.virtual_keyboard] +name = "Virtual Keyboard" +description = "Example demonstrating a virtual keyboard widget" +category = "UI (User Interface)" +wasm = true + [[example]] name = "ui_scaling" path = "examples/ui/ui_scaling.rs" @@ -4201,7 +4244,7 @@ wasm = false [[example]] name = "custom_phase_item" -path = "examples/shader/custom_phase_item.rs" +path = "examples/shader_advanced/custom_phase_item.rs" doc-scrape-examples = true [package.metadata.example.custom_phase_item] diff --git a/README.md b/README.md index db0a3d57b12d3..9e9d49a5a37ec 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,6 @@ These are generally BSD-like, but exact details vary by crate: If the README of a crate contains a 'License' header (or similar), the additional copyright notices and license terms applicable to that crate will be listed. The above licensing requirement still applies to contributions to those crates, and sections of those crates will carry those license terms. The [license](https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields) field of each crate will also reflect this. -For example, [`bevy_mikktspace`](./crates/bevy_mikktspace/README.md#license-agreement) has code under the Zlib license (as well as a copyright notice when choosing the MIT license). The [assets](assets) included in this repository (for our [examples](./examples/README.md)) typically fall under different open licenses. These will not be included in your game (unless copied in by you), and they are not distributed in the published bevy crates. diff --git a/assets/shaders/array_texture.wgsl b/assets/shaders/array_texture.wgsl index 293f3316654e4..1bc6c46c404bf 100644 --- a/assets/shaders/array_texture.wgsl +++ b/assets/shaders/array_texture.wgsl @@ -7,8 +7,8 @@ } #import bevy_core_pipeline::tonemapping::tone_mapping -@group(3) @binding(0) var my_array_texture: texture_2d_array; -@group(3) @binding(1) var my_array_texture_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var my_array_texture: texture_2d_array; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var my_array_texture_sampler: sampler; @fragment fn fragment( diff --git a/assets/shaders/automatic_instancing.wgsl b/assets/shaders/automatic_instancing.wgsl index ca2195df0d4de..320f03aa6a7ba 100644 --- a/assets/shaders/automatic_instancing.wgsl +++ b/assets/shaders/automatic_instancing.wgsl @@ -3,8 +3,8 @@ view_transformations::position_world_to_clip } -@group(3) @binding(0) var texture: texture_2d; -@group(3) @binding(1) var texture_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var texture_sampler: sampler; struct Vertex { @builtin(instance_index) instance_index: u32, diff --git a/assets/shaders/bindless_material.wgsl b/assets/shaders/bindless_material.wgsl index f7fa795d94c1c..edc463615bdd6 100644 --- a/assets/shaders/bindless_material.wgsl +++ b/assets/shaders/bindless_material.wgsl @@ -15,12 +15,12 @@ struct MaterialBindings { } #ifdef BINDLESS -@group(3) @binding(0) var materials: array; -@group(3) @binding(10) var material_color: binding_array; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var materials: array; +@group(#{MATERIAL_BIND_GROUP}) @binding(10) var material_color: binding_array; #else // BINDLESS -@group(3) @binding(0) var material_color: Color; -@group(3) @binding(1) var material_color_texture: texture_2d; -@group(3) @binding(2) var material_color_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material_color: Color; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var material_color_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(2) var material_color_sampler: sampler; #endif // BINDLESS @fragment diff --git a/assets/shaders/cubemap_unlit.wgsl b/assets/shaders/cubemap_unlit.wgsl index 81e153e408dfb..16b5cdfd30568 100644 --- a/assets/shaders/cubemap_unlit.wgsl +++ b/assets/shaders/cubemap_unlit.wgsl @@ -1,12 +1,12 @@ #import bevy_pbr::forward_io::VertexOutput #ifdef CUBEMAP_ARRAY -@group(3) @binding(0) var base_color_texture: texture_cube_array; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var base_color_texture: texture_cube_array; #else -@group(3) @binding(0) var base_color_texture: texture_cube; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var base_color_texture: texture_cube; #endif -@group(3) @binding(1) var base_color_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var base_color_sampler: sampler; @fragment fn fragment( diff --git a/assets/shaders/custom_material.wgsl b/assets/shaders/custom_material.wgsl index 7548d2223c8af..810bf16a58568 100644 --- a/assets/shaders/custom_material.wgsl +++ b/assets/shaders/custom_material.wgsl @@ -2,9 +2,9 @@ // we can import items from shader modules in the assets folder with a quoted path #import "shaders/custom_material_import.wgsl"::COLOR_MULTIPLIER -@group(3) @binding(0) var material_color: vec4; -@group(3) @binding(1) var material_color_texture: texture_2d; -@group(3) @binding(2) var material_color_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material_color: vec4; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var material_color_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(2) var material_color_sampler: sampler; @fragment fn fragment( diff --git a/assets/shaders/custom_material_2d.wgsl b/assets/shaders/custom_material_2d.wgsl index eee352493dd53..a90cd76b6cd08 100644 --- a/assets/shaders/custom_material_2d.wgsl +++ b/assets/shaders/custom_material_2d.wgsl @@ -2,9 +2,9 @@ // we can import items from shader modules in the assets folder with a quoted path #import "shaders/custom_material_import.wgsl"::COLOR_MULTIPLIER -@group(2) @binding(0) var material_color: vec4; -@group(2) @binding(1) var base_color_texture: texture_2d; -@group(2) @binding(2) var base_color_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material_color: vec4; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var base_color_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(2) var base_color_sampler: sampler; @fragment fn fragment(mesh: VertexOutput) -> @location(0) vec4 { diff --git a/assets/shaders/custom_material_screenspace_texture.wgsl b/assets/shaders/custom_material_screenspace_texture.wgsl index abad3cc15afd1..4401ccc4fa911 100644 --- a/assets/shaders/custom_material_screenspace_texture.wgsl +++ b/assets/shaders/custom_material_screenspace_texture.wgsl @@ -4,8 +4,8 @@ utils::coords_to_viewport_uv, } -@group(3) @binding(0) var texture: texture_2d; -@group(3) @binding(1) var texture_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var texture_sampler: sampler; @fragment fn fragment( diff --git a/assets/shaders/custom_vertex_attribute.wgsl b/assets/shaders/custom_vertex_attribute.wgsl index cb650c8a47a03..55a78126712b6 100644 --- a/assets/shaders/custom_vertex_attribute.wgsl +++ b/assets/shaders/custom_vertex_attribute.wgsl @@ -5,7 +5,7 @@ struct CustomMaterial { color: vec4, }; -@group(3) @binding(0) var material: CustomMaterial; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material: CustomMaterial; struct Vertex { @builtin(instance_index) instance_index: u32, diff --git a/assets/shaders/extended_material.wgsl b/assets/shaders/extended_material.wgsl index ae89f7dfeab33..71dbe7f3d5ee5 100644 --- a/assets/shaders/extended_material.wgsl +++ b/assets/shaders/extended_material.wgsl @@ -25,7 +25,7 @@ struct MyExtendedMaterial { #endif } -@group(3) @binding(100) +@group(#{MATERIAL_BIND_GROUP}) @binding(100) var my_extended_material: MyExtendedMaterial; @fragment diff --git a/assets/shaders/extended_material_bindless.wgsl b/assets/shaders/extended_material_bindless.wgsl index c9cb07e0c733e..9db5d246835b7 100644 --- a/assets/shaders/extended_material_bindless.wgsl +++ b/assets/shaders/extended_material_bindless.wgsl @@ -42,19 +42,19 @@ struct ExampleBindlessExtendedMaterial { // The indices of the bindless resources in the bindless resource arrays, for // the `ExampleBindlessExtension` fields. -@group(3) @binding(100) var example_extended_material_indices: +@group(#{MATERIAL_BIND_GROUP}) @binding(100) var example_extended_material_indices: array; // An array that holds the `ExampleBindlessExtendedMaterial` plain old data, // indexed by `ExampleBindlessExtendedMaterialIndices.material`. -@group(3) @binding(101) var example_extended_material: +@group(#{MATERIAL_BIND_GROUP}) @binding(101) var example_extended_material: array; #else // BINDLESS // In non-bindless mode, we simply use a uniform for the plain old data. -@group(3) @binding(50) var example_extended_material: ExampleBindlessExtendedMaterial; -@group(3) @binding(51) var modulate_texture: texture_2d; -@group(3) @binding(52) var modulate_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(50) var example_extended_material: ExampleBindlessExtendedMaterial; +@group(#{MATERIAL_BIND_GROUP}) @binding(51) var modulate_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(52) var modulate_sampler: sampler; #endif // BINDLESS diff --git a/assets/shaders/fallback_image_test.wgsl b/assets/shaders/fallback_image_test.wgsl index c92cbd1577e4e..50ac6b8549ad3 100644 --- a/assets/shaders/fallback_image_test.wgsl +++ b/assets/shaders/fallback_image_test.wgsl @@ -1,22 +1,22 @@ #import bevy_pbr::forward_io::VertexOutput -@group(3) @binding(0) var test_texture_1d: texture_1d; -@group(3) @binding(1) var test_texture_1d_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var test_texture_1d: texture_1d; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var test_texture_1d_sampler: sampler; -@group(3) @binding(2) var test_texture_2d: texture_2d; -@group(3) @binding(3) var test_texture_2d_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(2) var test_texture_2d: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(3) var test_texture_2d_sampler: sampler; -@group(3) @binding(4) var test_texture_2d_array: texture_2d_array; -@group(3) @binding(5) var test_texture_2d_array_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(4) var test_texture_2d_array: texture_2d_array; +@group(#{MATERIAL_BIND_GROUP}) @binding(5) var test_texture_2d_array_sampler: sampler; -@group(3) @binding(6) var test_texture_cube: texture_cube; -@group(3) @binding(7) var test_texture_cube_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(6) var test_texture_cube: texture_cube; +@group(#{MATERIAL_BIND_GROUP}) @binding(7) var test_texture_cube_sampler: sampler; -@group(3) @binding(8) var test_texture_cube_array: texture_cube_array; -@group(3) @binding(9) var test_texture_cube_array_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(8) var test_texture_cube_array: texture_cube_array; +@group(#{MATERIAL_BIND_GROUP}) @binding(9) var test_texture_cube_array_sampler: sampler; -@group(3) @binding(10) var test_texture_3d: texture_3d; -@group(3) @binding(11) var test_texture_3d_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(10) var test_texture_3d: texture_3d; +@group(#{MATERIAL_BIND_GROUP}) @binding(11) var test_texture_3d_sampler: sampler; @fragment fn fragment(in: VertexOutput) {} diff --git a/assets/shaders/irradiance_volume_voxel_visualization.wgsl b/assets/shaders/irradiance_volume_voxel_visualization.wgsl index 0e12110f3b1da..d510e75eb866f 100644 --- a/assets/shaders/irradiance_volume_voxel_visualization.wgsl +++ b/assets/shaders/irradiance_volume_voxel_visualization.wgsl @@ -12,7 +12,7 @@ struct VoxelVisualizationIrradianceVolumeInfo { intensity: f32, } -@group(3) @binding(100) +@group(#{MATERIAL_BIND_GROUP}) @binding(100) var irradiance_volume_info: VoxelVisualizationIrradianceVolumeInfo; @fragment diff --git a/assets/shaders/line_material.wgsl b/assets/shaders/line_material.wgsl index 639762a444ab4..c19fb3dc03e66 100644 --- a/assets/shaders/line_material.wgsl +++ b/assets/shaders/line_material.wgsl @@ -4,7 +4,7 @@ struct LineMaterial { color: vec4, }; -@group(3) @binding(0) var material: LineMaterial; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material: LineMaterial; @fragment fn fragment( diff --git a/assets/shaders/manual_material.wgsl b/assets/shaders/manual_material.wgsl index 557a1412c73d2..4b3b2662eb7a7 100644 --- a/assets/shaders/manual_material.wgsl +++ b/assets/shaders/manual_material.wgsl @@ -1,7 +1,7 @@ #import bevy_pbr::forward_io::VertexOutput -@group(3) @binding(0) var material_color_texture: texture_2d; -@group(3) @binding(1) var material_color_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material_color_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var material_color_sampler: sampler; @fragment fn fragment( diff --git a/assets/shaders/shader_defs.wgsl b/assets/shaders/shader_defs.wgsl index 776fc9ffe69eb..f36453c402169 100644 --- a/assets/shaders/shader_defs.wgsl +++ b/assets/shaders/shader_defs.wgsl @@ -4,7 +4,7 @@ struct CustomMaterial { color: vec4, }; -@group(3) @binding(0) var material: CustomMaterial; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material: CustomMaterial; @fragment fn fragment( diff --git a/assets/shaders/show_prepass.wgsl b/assets/shaders/show_prepass.wgsl index b1a53677ac1ea..11313c50e1df9 100644 --- a/assets/shaders/show_prepass.wgsl +++ b/assets/shaders/show_prepass.wgsl @@ -11,7 +11,7 @@ struct ShowPrepassSettings { padding_1: u32, padding_2: u32, } -@group(3) @binding(0) var settings: ShowPrepassSettings; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var settings: ShowPrepassSettings; @fragment fn fragment( diff --git a/assets/shaders/storage_buffer.wgsl b/assets/shaders/storage_buffer.wgsl index f447333cc9754..17ec47b736612 100644 --- a/assets/shaders/storage_buffer.wgsl +++ b/assets/shaders/storage_buffer.wgsl @@ -3,7 +3,7 @@ view_transformations::position_world_to_clip } -@group(3) @binding(0) var colors: array, 5>; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var colors: array, 5>; struct Vertex { @builtin(instance_index) instance_index: u32, diff --git a/assets/shaders/texture_binding_array.wgsl b/assets/shaders/texture_binding_array.wgsl index 17b94a74d3209..98ffce0f65bf1 100644 --- a/assets/shaders/texture_binding_array.wgsl +++ b/assets/shaders/texture_binding_array.wgsl @@ -1,7 +1,7 @@ #import bevy_pbr::forward_io::VertexOutput -@group(3) @binding(0) var textures: binding_array>; -@group(3) @binding(1) var nearest_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var textures: binding_array>; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var nearest_sampler: sampler; // We can also have array of samplers // var samplers: binding_array; diff --git a/assets/shaders/water_material.wgsl b/assets/shaders/water_material.wgsl index a8a9e03df4d16..bd5bd929036de 100644 --- a/assets/shaders/water_material.wgsl +++ b/assets/shaders/water_material.wgsl @@ -23,9 +23,9 @@ struct WaterSettings { @group(0) @binding(1) var globals: Globals; -@group(3) @binding(100) var water_normals_texture: texture_2d; -@group(3) @binding(101) var water_normals_sampler: sampler; -@group(3) @binding(102) var water_settings: WaterSettings; +@group(#{MATERIAL_BIND_GROUP}) @binding(100) var water_normals_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(101) var water_normals_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(102) var water_settings: WaterSettings; // Samples a single octave of noise and returns the resulting normal. fn sample_noise_octave(uv: vec2, strength: f32) -> vec3 { diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 789207d8234be..f30640fa9071b 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -23,22 +23,16 @@ bevy_picking = { path = "../crates/bevy_picking", features = [ bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] } bevy_render = { path = "../crates/bevy_render" } bevy_tasks = { path = "../crates/bevy_tasks" } -bevy_utils = { path = "../crates/bevy_utils" } bevy_platform = { path = "../crates/bevy_platform", default-features = false, features = [ "std", ] } # Other crates -glam = "0.29" -rand = "0.8" -rand_chacha = "0.3" +glam = "0.30.1" +rand = "0.9" +rand_chacha = "0.9" nonmax = { version = "0.5", default-features = false } -# Make `bevy_render` compile on Linux with x11 windowing. x11 vs. Wayland does not matter here -# because the benches do not actually open any windows. -[target.'cfg(target_os = "linux")'.dev-dependencies] -bevy_winit = { path = "../crates/bevy_winit", features = ["x11"] } - [lints.clippy] doc_markdown = "warn" manual_let_else = "warn" diff --git a/benches/benches/bevy_ecs/observers/propagation.rs b/benches/benches/bevy_ecs/observers/propagation.rs index 17306f6f22a59..2622ecdddd16c 100644 --- a/benches/benches/bevy_ecs/observers/propagation.rs +++ b/benches/benches/bevy_ecs/observers/propagation.rs @@ -66,7 +66,7 @@ pub fn event_propagation(criterion: &mut Criterion) { struct TestEvent {} fn send_events(world: &mut World, leaves: &[Entity]) { - let target = leaves.iter().choose(&mut rand::thread_rng()).unwrap(); + let target = leaves.iter().choose(&mut rand::rng()).unwrap(); (0..N_EVENTS).for_each(|_| { world.trigger_targets(TestEvent:: {}, *target); @@ -107,7 +107,7 @@ fn add_listeners_to_hierarchy( } let mut rng = deterministic_rand(); for e in nodes.iter() { - if rng.gen_bool(DENSITY as f64 / 100.0) { + if rng.random_bool(DENSITY as f64 / 100.0) { world.entity_mut(*e).observe(empty_listener::); } } diff --git a/benches/benches/bevy_ecs/world/entity_hash.rs b/benches/benches/bevy_ecs/world/entity_hash.rs index 2f92a715b4411..36d998022614d 100644 --- a/benches/benches/bevy_ecs/world/entity_hash.rs +++ b/benches/benches/bevy_ecs/world/entity_hash.rs @@ -11,9 +11,9 @@ fn make_entity(rng: &mut impl Rng, size: usize) -> Entity { // * For ids, half are in [0, size), half are unboundedly larger. // * For generations, half are in [1, 3), half are unboundedly larger. - let x: f64 = rng.r#gen(); + let x: f64 = rng.random(); let id = -(1.0 - x).log2() * (size as f64); - let x: f64 = rng.r#gen(); + let x: f64 = rng.random(); let generation = 1.0 + -(1.0 - x).log2() * 2.0; // this is not reliable, but we're internal so a hack is ok diff --git a/benches/benches/bevy_reflect/path.rs b/benches/benches/bevy_reflect/path.rs index c0d8bfe0da732..c8c82ff6ba97e 100644 --- a/benches/benches/bevy_reflect/path.rs +++ b/benches/benches/bevy_reflect/path.rs @@ -3,7 +3,7 @@ use core::{fmt::Write, hint::black_box, str, time::Duration}; use benches::bench; use bevy_reflect::ParsedPath; use criterion::{criterion_group, BatchSize, BenchmarkId, Criterion, Throughput}; -use rand::{distributions::Uniform, Rng, SeedableRng}; +use rand::{distr::Uniform, Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; criterion_group!(benches, parse_reflect_path); @@ -18,20 +18,20 @@ fn deterministic_rand() -> ChaCha8Rng { ChaCha8Rng::seed_from_u64(42) } fn random_ident(rng: &mut ChaCha8Rng, f: &mut dyn Write) { - let between = Uniform::from(b'a'..=b'z'); - let ident_size = rng.gen_range(1..128); + let between = Uniform::new_inclusive(b'a', b'z').unwrap(); + let ident_size = rng.random_range(1..128); let ident: Vec = rng.sample_iter(between).take(ident_size).collect(); let ident = str::from_utf8(&ident).unwrap(); let _ = write!(f, "{ident}"); } fn random_index(rng: &mut ChaCha8Rng, f: &mut dyn Write) { - let index = rng.gen_range(1..128); + let index = rng.random_range(1..128); let _ = write!(f, "{index}"); } fn write_random_access(rng: &mut ChaCha8Rng, f: &mut dyn Write) { - match rng.gen_range(0..4) { + match rng.random_range(0..4) { 0 => { // Access::Field f.write_char('.').unwrap(); diff --git a/crates/bevy_android/Cargo.toml b/crates/bevy_android/Cargo.toml new file mode 100644 index 0000000000000..19a41377b4f79 --- /dev/null +++ b/crates/bevy_android/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "bevy_android" +version = "0.17.0-dev" +edition = "2024" +description = "Provides android functionality for Bevy Engine." +homepage = "https://bevy.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[target.'cfg(target_os = "android")'.dependencies] +android-activity = "0.6" + +[features] +default = [] + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_android/LICENSE-APACHE b/crates/bevy_android/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_android/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_android/LICENSE-MIT b/crates/bevy_android/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_android/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +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 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_android/src/lib.rs b/crates/bevy_android/src/lib.rs new file mode 100644 index 0000000000000..5e8b500add8ea --- /dev/null +++ b/crates/bevy_android/src/lib.rs @@ -0,0 +1,10 @@ +//! Provides Android functionality for Bevy Engine. + +#[cfg(target_os = "android")] +pub use android_activity; + +/// [`AndroidApp`] provides an interface to query the application state as well as monitor events +/// (for example lifecycle and input events). +#[cfg(target_os = "android")] +pub static ANDROID_APP: std::sync::OnceLock = + std::sync::OnceLock::new(); diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index 782daf0f552bc..0e1d5866df41b 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -199,7 +199,7 @@ pub trait AnimatableProperty: Send + Sync + 'static { /// The [`EvaluatorId`] used to look up the [`AnimationCurveEvaluator`] for this [`AnimatableProperty`]. /// For a given animated property, this ID should always be the same to allow things like animation blending to occur. - fn evaluator_id(&self) -> EvaluatorId; + fn evaluator_id(&self) -> EvaluatorId<'_>; } /// A [`Component`] field that can be animated, defined by a function that reads the component and returns @@ -236,7 +236,7 @@ where Ok((self.func)(c.into_inner())) } - fn evaluator_id(&self) -> EvaluatorId { + fn evaluator_id(&self) -> EvaluatorId<'_> { EvaluatorId::ComponentField(&self.evaluator_id) } } @@ -357,7 +357,7 @@ where self.curve.domain() } - fn evaluator_id(&self) -> EvaluatorId { + fn evaluator_id(&self) -> EvaluatorId<'_> { self.property.evaluator_id() } @@ -476,7 +476,7 @@ where self.0.domain() } - fn evaluator_id(&self) -> EvaluatorId { + fn evaluator_id(&self) -> EvaluatorId<'_> { EvaluatorId::Type(TypeId::of::()) } @@ -768,7 +768,7 @@ pub trait AnimationCurve: Debug + Send + Sync + 'static { /// /// This must match the type returned by [`Self::create_evaluator`]. It must /// be a single type that doesn't depend on the type of the curve. - fn evaluator_id(&self) -> EvaluatorId; + fn evaluator_id(&self) -> EvaluatorId<'_>; /// Returns a newly-instantiated [`AnimationCurveEvaluator`] for use with /// this curve. diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 0c667417b5ae3..3c9c7d9e4c3f1 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -40,7 +40,6 @@ use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath}; use bevy_time::Time; use bevy_transform::TransformSystems; use bevy_utils::{PreHashMap, PreHashMapExt, TypeIdMap}; -use petgraph::graph::NodeIndex; use serde::{Deserialize, Serialize}; use thread_local::ThreadLocal; use tracing::{trace, warn}; @@ -60,7 +59,7 @@ pub mod prelude { use crate::{ animation_curves::AnimationCurve, graph::{AnimationGraph, AnimationGraphAssetLoader, AnimationNodeIndex}, - transition::{advance_transitions, expire_completed_transitions, AnimationTransitions}, + transition::{advance_transitions, expire_completed_transitions}, }; use alloc::sync::Arc; @@ -773,7 +772,7 @@ struct CurrentEvaluators { } impl CurrentEvaluators { - pub(crate) fn keys(&self) -> impl Iterator { + pub(crate) fn keys(&self) -> impl Iterator> { self.component_properties .keys() .map(EvaluatorId::ComponentField) @@ -1008,12 +1007,11 @@ pub fn advance_animations( if let Some(active_animation) = active_animations.get_mut(&node_index) { // Tick the animation if necessary. - if !active_animation.paused { - if let AnimationNodeType::Clip(ref clip_handle) = node.node_type { - if let Some(clip) = animation_clips.get(clip_handle) { - active_animation.update(delta_seconds, clip.duration); - } - } + if !active_animation.paused + && let AnimationNodeType::Clip(ref clip_handle) = node.node_type + && let Some(clip) = animation_clips.get(clip_handle) + { + active_animation.update(delta_seconds, clip.duration); } } } @@ -1159,21 +1157,20 @@ pub fn animate_targets( AnimationEventTarget::Node(target_id), clip, active_animation, - ) { - if !triggered_events.is_empty() { - par_commands.command_scope(move |mut commands| { - for TimedAnimationEvent { time, event } in - triggered_events.iter() - { - event.trigger( - &mut commands, - entity, - *time, - active_animation.weight, - ); - } - }); - } + ) && !triggered_events.is_empty() + { + par_commands.command_scope(move |mut commands| { + for TimedAnimationEvent { time, event } in + triggered_events.iter() + { + event.trigger( + &mut commands, + entity, + *time, + active_animation.weight, + ); + } + }); } } @@ -1234,12 +1231,6 @@ impl Plugin for AnimationPlugin { .init_asset_loader::() .register_asset_reflect::() .register_asset_reflect::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() .init_resource::() .add_systems( PostUpdate, @@ -1469,7 +1460,7 @@ impl<'a> TriggeredEvents<'a> { self.lower.is_empty() && self.upper.is_empty() } - fn iter(&self) -> TriggeredEventsIter { + fn iter(&self) -> TriggeredEventsIter<'_> { match self.direction { TriggeredEventsDir::Forward => TriggeredEventsIter::Forward(self.lower.iter()), TriggeredEventsDir::Reverse => TriggeredEventsIter::Reverse(self.lower.iter().rev()), diff --git a/crates/bevy_animation/src/transition.rs b/crates/bevy_animation/src/transition.rs index 494855970441d..42a4addfae6ec 100644 --- a/crates/bevy_animation/src/transition.rs +++ b/crates/bevy_animation/src/transition.rs @@ -81,16 +81,15 @@ impl AnimationTransitions { new_animation: AnimationNodeIndex, transition_duration: Duration, ) -> &'p mut ActiveAnimation { - if let Some(old_animation_index) = self.main_animation.replace(new_animation) { - if let Some(old_animation) = player.animation_mut(old_animation_index) { - if !old_animation.is_paused() { - self.transitions.push(AnimationTransition { - current_weight: old_animation.weight, - weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), - animation: old_animation_index, - }); - } - } + if let Some(old_animation_index) = self.main_animation.replace(new_animation) + && let Some(old_animation) = player.animation_mut(old_animation_index) + && !old_animation.is_paused() + { + self.transitions.push(AnimationTransition { + current_weight: old_animation.weight, + weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), + animation: old_animation_index, + }); } // If already transitioning away from this animation, cancel the transition. @@ -135,10 +134,10 @@ pub fn advance_transitions( remaining_weight -= animation.weight; } - if let Some(main_animation_index) = animation_transitions.main_animation { - if let Some(ref mut animation) = player.animation_mut(main_animation_index) { - animation.weight = remaining_weight; - } + if let Some(main_animation_index) = animation_transitions.main_animation + && let Some(ref mut animation) = player.animation_mut(main_animation_index) + { + animation.weight = remaining_weight; } } } diff --git a/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/mod.rs index 872175dbc741b..845f4c98dd462 100644 --- a/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/mod.rs @@ -103,7 +103,6 @@ impl Plugin for CasPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "robust_contrast_adaptive_sharpening.wgsl"); - app.register_type::(); app.add_plugins(( ExtractComponentPlugin::::default(), UniformComponentPlugin::::default(), diff --git a/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/node.rs b/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/node.rs index 663d481e887bd..c1e6ccdda02c3 100644 --- a/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/node.rs +++ b/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/node.rs @@ -3,6 +3,7 @@ use std::sync::Mutex; use crate::contrast_adaptive_sharpening::ViewCasPipeline; use bevy_ecs::prelude::*; use bevy_render::{ + diagnostic::RecordDiagnostics, extract_component::{ComponentUniforms, DynamicUniformIndex}, render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::{ @@ -66,6 +67,8 @@ impl Node for CasNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let view_target = target.post_process_write(); let source = view_target.source; let destination = view_target.destination; @@ -98,6 +101,7 @@ impl Node for CasNode { label: Some("contrast_adaptive_sharpening"), color_attachments: &[Some(RenderPassColorAttachment { view: destination, + depth_slice: None, resolve_target: None, ops: Operations::default(), })], @@ -109,11 +113,14 @@ impl Node for CasNode { let mut render_pass = render_context .command_encoder() .begin_render_pass(&pass_descriptor); + let pass_span = diagnostics.time_span(&mut render_pass, "contrast_adaptive_sharpening"); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, bind_group, &[uniform_index.index()]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_anti_aliasing/src/fxaa/mod.rs b/crates/bevy_anti_aliasing/src/fxaa/mod.rs index bba559626379b..6b67770230684 100644 --- a/crates/bevy_anti_aliasing/src/fxaa/mod.rs +++ b/crates/bevy_anti_aliasing/src/fxaa/mod.rs @@ -86,7 +86,6 @@ impl Plugin for FxaaPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "fxaa.wgsl"); - app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_anti_aliasing/src/fxaa/node.rs b/crates/bevy_anti_aliasing/src/fxaa/node.rs index a58f21d9a7746..54d2afd33e736 100644 --- a/crates/bevy_anti_aliasing/src/fxaa/node.rs +++ b/crates/bevy_anti_aliasing/src/fxaa/node.rs @@ -3,6 +3,7 @@ use std::sync::Mutex; use crate::fxaa::{CameraFxaaPipeline, Fxaa, FxaaPipeline}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ + diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ BindGroup, BindGroupEntries, Operations, PipelineCache, RenderPassColorAttachment, @@ -42,6 +43,8 @@ impl ViewNode for FxaaNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let post_process = target.post_process_write(); let source = post_process.source; let destination = post_process.destination; @@ -61,9 +64,10 @@ impl ViewNode for FxaaNode { }; let pass_descriptor = RenderPassDescriptor { - label: Some("fxaa_pass"), + label: Some("fxaa"), color_attachments: &[Some(RenderPassColorAttachment { view: destination, + depth_slice: None, resolve_target: None, ops: Operations::default(), })], @@ -75,11 +79,14 @@ impl ViewNode for FxaaNode { let mut render_pass = render_context .command_encoder() .begin_render_pass(&pass_descriptor); + let pass_span = diagnostics.pass_span(&mut render_pass, "fxaa"); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, bind_group, &[]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_anti_aliasing/src/smaa/mod.rs b/crates/bevy_anti_aliasing/src/smaa/mod.rs index 33a916e489a96..3d3913ccce4ca 100644 --- a/crates/bevy_anti_aliasing/src/smaa/mod.rs +++ b/crates/bevy_anti_aliasing/src/smaa/mod.rs @@ -53,6 +53,7 @@ use bevy_math::{vec4, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::ExtractedCamera, + diagnostic::RecordDiagnostics, extract_component::{ExtractComponent, ExtractComponentPlugin}, render_asset::RenderAssets, render_graph::{ @@ -312,8 +313,7 @@ impl Plugin for SmaaPlugin { } }; - app.add_plugins(ExtractComponentPlugin::::default()) - .register_type::(); + app.add_plugins(ExtractComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -824,6 +824,10 @@ impl ViewNode for SmaaNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + render_context.command_encoder().push_debug_group("smaa"); + let time_span = diagnostics.time_span(render_context.command_encoder(), "smaa"); + // Fetch the framebuffer textures. let postprocess = view_target.post_process_write(); let (source, destination) = (postprocess.source, postprocess.destination); @@ -864,6 +868,9 @@ impl ViewNode for SmaaNode { destination, ); + time_span.end(render_context.command_encoder()); + render_context.command_encoder().pop_debug_group(); + Ok(()) } } @@ -896,6 +903,7 @@ fn perform_edge_detection( label: Some("SMAA edge detection pass"), color_attachments: &[Some(RenderPassColorAttachment { view: &smaa_textures.edge_detection_color_texture.default_view, + depth_slice: None, resolve_target: None, ops: default(), })], @@ -951,6 +959,7 @@ fn perform_blending_weight_calculation( label: Some("SMAA blending weight calculation pass"), color_attachments: &[Some(RenderPassColorAttachment { view: &smaa_textures.blend_texture.default_view, + depth_slice: None, resolve_target: None, ops: default(), })], @@ -1007,6 +1016,7 @@ fn perform_neighborhood_blending( label: Some("SMAA neighborhood blending pass"), color_attachments: &[Some(RenderPassColorAttachment { view: destination, + depth_slice: None, resolve_target: None, ops: default(), })], diff --git a/crates/bevy_anti_aliasing/src/taa/mod.rs b/crates/bevy_anti_aliasing/src/taa/mod.rs index f182108f1082a..d32b28251bc23 100644 --- a/crates/bevy_anti_aliasing/src/taa/mod.rs +++ b/crates/bevy_anti_aliasing/src/taa/mod.rs @@ -20,6 +20,7 @@ use bevy_math::vec2; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::{ExtractedCamera, MipBias, TemporalJitter}, + diagnostic::RecordDiagnostics, prelude::{Camera, Projection}, render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_resource::{ @@ -50,8 +51,6 @@ impl Plugin for TemporalAntiAliasPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "taa.wgsl"); - app.register_type::(); - app.add_plugins(SyncComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { @@ -181,6 +180,9 @@ impl ViewNode for TemporalAntiAliasNode { ) else { return Ok(()); }; + + let diagnostics = render_context.diagnostic_recorder(); + let view_target = view_target.post_process_write(); let taa_bind_group = render_context.render_device().create_bind_group( @@ -198,15 +200,17 @@ impl ViewNode for TemporalAntiAliasNode { { let mut taa_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("taa_pass"), + label: Some("taa"), color_attachments: &[ Some(RenderPassColorAttachment { view: view_target.destination, + depth_slice: None, resolve_target: None, ops: Operations::default(), }), Some(RenderPassColorAttachment { view: &taa_history_textures.write.default_view, + depth_slice: None, resolve_target: None, ops: Operations::default(), }), @@ -215,12 +219,16 @@ impl ViewNode for TemporalAntiAliasNode { timestamp_writes: None, occlusion_query_set: None, }); + let pass_span = diagnostics.pass_span(&mut taa_pass, "taa"); + taa_pass.set_render_pipeline(taa_pipeline); taa_pass.set_bind_group(0, &taa_bind_group, &[]); if let Some(viewport) = camera.viewport.as_ref() { taa_pass.set_camera_viewport(viewport); } taa_pass.draw(0..3, 0..1); + + pass_span.end(&mut taa_pass); } Ok(()) @@ -342,16 +350,17 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut, )>(); - for (entity, camera, camera_projection, mut taa_settings) in - cameras_3d.iter_mut(&mut main_world) - { + for (entity, camera, camera_projection, taa_settings) in cameras_3d.iter_mut(&mut main_world) { let has_perspective_projection = matches!(camera_projection, Projection::Perspective(_)); let mut entity_commands = commands .get_entity(entity) .expect("Camera entity wasn't synced."); - if taa_settings.is_some() && camera.is_active && has_perspective_projection { - entity_commands.insert(taa_settings.as_deref().unwrap().clone()); - taa_settings.as_mut().unwrap().reset = false; + if let Some(mut taa_settings) = taa_settings + && camera.is_active + && has_perspective_projection + { + entity_commands.insert(taa_settings.clone()); + taa_settings.reset = false; } else { entity_commands.remove::<( TemporalAntiAliasing, diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index a0c5222b0b1a3..61c1e8f37983f 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -22,6 +22,11 @@ reflect_functions = [ "bevy_reflect/functions", "bevy_ecs/reflect_functions", ] +reflect_auto_register = [ + "bevy_reflect", + "bevy_reflect/auto_register", + "bevy_ecs/reflect_auto_register", +] # Debugging Features @@ -47,14 +52,12 @@ std = [ "bevy_ecs/std", "dep:ctrlc", "downcast-rs/std", - "bevy_tasks/std", "bevy_platform/std", ] ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. critical-section = [ - "bevy_tasks/critical-section", "bevy_ecs/critical-section", "bevy_platform/critical-section", "bevy_reflect?/critical-section", @@ -64,7 +67,6 @@ critical-section = [ ## Note this is currently only applicable on `wasm32` architectures. web = [ "bevy_platform/web", - "bevy_tasks/web", "bevy_reflect?/web", "dep:wasm-bindgen", "dep:web-sys", diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index e2755078c6535..c6c16cb5bed86 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -106,13 +106,11 @@ impl Default for App { #[cfg(feature = "bevy_reflect")] { - use bevy_ecs::observer::ObservedBy; - + #[cfg(not(feature = "reflect_auto_register"))] app.init_resource::(); - app.register_type::(); - app.register_type::(); - app.register_type::(); - app.register_type::(); + + #[cfg(feature = "reflect_auto_register")] + app.insert_resource(AppTypeRegistry::new_with_derived_types()); } #[cfg(feature = "reflect_functions")] diff --git a/crates/bevy_app/src/hotpatch.rs b/crates/bevy_app/src/hotpatch.rs index 90642a67ddaea..c5765a2874cac 100644 --- a/crates/bevy_app/src/hotpatch.rs +++ b/crates/bevy_app/src/hotpatch.rs @@ -3,6 +3,8 @@ extern crate alloc; use alloc::sync::Arc; +#[cfg(feature = "reflect_auto_register")] +use bevy_ecs::schedule::IntoScheduleConfigs; use bevy_ecs::{ change_detection::DetectChangesMut, event::EventWriter, system::ResMut, HotPatchChanges, HotPatched, @@ -44,5 +46,14 @@ impl Plugin for HotPatchPlugin { } }, ); + + #[cfg(feature = "reflect_auto_register")] + app.add_systems( + crate::First, + (move |registry: bevy_ecs::system::Res| { + registry.write().register_derived_types(); + }) + .run_if(bevy_ecs::schedule::common_conditions::on_event::), + ); } } diff --git a/crates/bevy_app/src/plugin_group.rs b/crates/bevy_app/src/plugin_group.rs index 60897d5453066..ee1c8cb44b3cc 100644 --- a/crates/bevy_app/src/plugin_group.rs +++ b/crates/bevy_app/src/plugin_group.rs @@ -517,18 +517,18 @@ impl PluginGroupBuilder { #[track_caller] pub fn finish(mut self, app: &mut App) { for ty in &self.order { - if let Some(entry) = self.plugins.remove(ty) { - if entry.enabled { - debug!("added plugin: {}", entry.plugin.name()); - if let Err(AppError::DuplicatePlugin { plugin_name }) = - app.add_boxed_plugin(entry.plugin) - { - panic!( - "Error adding plugin {} in group {}: plugin was already added in application", - plugin_name, - self.group_name - ); - } + if let Some(entry) = self.plugins.remove(ty) + && entry.enabled + { + debug!("added plugin: {}", entry.plugin.name()); + if let Err(AppError::DuplicatePlugin { plugin_name }) = + app.add_boxed_plugin(entry.plugin) + { + panic!( + "Error adding plugin {} in group {}: plugin was already added in application", + plugin_name, + self.group_name + ); } } } diff --git a/crates/bevy_app/src/propagate.rs b/crates/bevy_app/src/propagate.rs index 2cfaf6bda767e..e0a02c86f2df5 100644 --- a/crates/bevy_app/src/propagate.rs +++ b/crates/bevy_app/src/propagate.rs @@ -1,18 +1,21 @@ use alloc::vec::Vec; use core::marker::PhantomData; -use crate::{App, Plugin, Update}; +use crate::{App, Plugin}; +#[cfg(feature = "bevy_reflect")] +use bevy_ecs::reflect::ReflectComponent; use bevy_ecs::{ component::Component, entity::Entity, hierarchy::ChildOf, + intern::Interned, lifecycle::RemovedComponents, query::{Changed, Or, QueryFilter, With, Without}, - reflect::ReflectComponent, relationship::{Relationship, RelationshipTarget}, - schedule::{IntoScheduleConfigs, SystemSet}, + schedule::{IntoScheduleConfigs, ScheduleLabel, SystemSet}, system::{Commands, Local, Query}, }; +#[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; /// Plugin to automatically propagate a component value to all direct and transient relationship @@ -30,11 +33,31 @@ use bevy_reflect::Reflect; /// to reach a given target. /// Individual entities can be skipped or terminate the propagation with the [`PropagateOver`] /// and [`PropagateStop`] components. +/// +/// The schedule can be configured via [`HierarchyPropagatePlugin::new`]. +/// You should be sure to schedule your logic relative to this set: making changes +/// that modify component values before this logic, and reading the propagated +/// values after it. pub struct HierarchyPropagatePlugin< C: Component + Clone + PartialEq, F: QueryFilter = (), R: Relationship = ChildOf, ->(PhantomData (C, F, R)>); +> { + schedule: Interned, + _marker: PhantomData (C, F, R)>, +} + +impl + HierarchyPropagatePlugin +{ + /// Construct the plugin. The propagation systems will be placed in the specified schedule. + pub fn new(schedule: impl ScheduleLabel) -> Self { + Self { + schedule: schedule.intern(), + _marker: PhantomData, + } + } +} /// Causes the inner component to be added to this entity and all direct and transient relationship /// targets. A target with a [`Propagate`] component of its own will override propagation from @@ -73,14 +96,6 @@ pub struct PropagateSet { )] pub struct Inherited(pub C); -impl Default - for HierarchyPropagatePlugin -{ - fn default() -> Self { - Self(Default::default()) - } -} - impl Default for PropagateOver { fn default() -> Self { Self(Default::default()) @@ -122,7 +137,7 @@ impl, update_stopped::, @@ -288,7 +303,7 @@ mod tests { fn test_simple_propagate() { let mut app = App::new(); app.add_schedule(Schedule::new(Update)); - app.add_plugins(HierarchyPropagatePlugin::::default()); + app.add_plugins(HierarchyPropagatePlugin::::new(Update)); let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id(); let intermediate = app @@ -315,7 +330,7 @@ mod tests { fn test_reparented() { let mut app = App::new(); app.add_schedule(Schedule::new(Update)); - app.add_plugins(HierarchyPropagatePlugin::::default()); + app.add_plugins(HierarchyPropagatePlugin::::new(Update)); let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id(); let propagatee = app @@ -337,7 +352,7 @@ mod tests { fn test_reparented_with_prior() { let mut app = App::new(); app.add_schedule(Schedule::new(Update)); - app.add_plugins(HierarchyPropagatePlugin::::default()); + app.add_plugins(HierarchyPropagatePlugin::::new(Update)); let propagator_a = app.world_mut().spawn(Propagate(TestValue(1))).id(); let propagator_b = app.world_mut().spawn(Propagate(TestValue(2))).id(); @@ -371,7 +386,7 @@ mod tests { fn test_remove_orphan() { let mut app = App::new(); app.add_schedule(Schedule::new(Update)); - app.add_plugins(HierarchyPropagatePlugin::::default()); + app.add_plugins(HierarchyPropagatePlugin::::new(Update)); let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id(); let propagatee = app @@ -402,7 +417,7 @@ mod tests { fn test_remove_propagated() { let mut app = App::new(); app.add_schedule(Schedule::new(Update)); - app.add_plugins(HierarchyPropagatePlugin::::default()); + app.add_plugins(HierarchyPropagatePlugin::::new(Update)); let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id(); let propagatee = app @@ -433,7 +448,7 @@ mod tests { fn test_propagate_over() { let mut app = App::new(); app.add_schedule(Schedule::new(Update)); - app.add_plugins(HierarchyPropagatePlugin::::default()); + app.add_plugins(HierarchyPropagatePlugin::::new(Update)); let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id(); let propagate_over = app @@ -460,7 +475,7 @@ mod tests { fn test_propagate_stop() { let mut app = App::new(); app.add_schedule(Schedule::new(Update)); - app.add_plugins(HierarchyPropagatePlugin::::default()); + app.add_plugins(HierarchyPropagatePlugin::::new(Update)); let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id(); let propagate_stop = app @@ -486,7 +501,7 @@ mod tests { fn test_intermediate_override() { let mut app = App::new(); app.add_schedule(Schedule::new(Update)); - app.add_plugins(HierarchyPropagatePlugin::::default()); + app.add_plugins(HierarchyPropagatePlugin::::new(Update)); let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id(); let intermediate = app @@ -527,7 +542,9 @@ mod tests { let mut app = App::new(); app.add_schedule(Schedule::new(Update)); - app.add_plugins(HierarchyPropagatePlugin::>::default()); + app.add_plugins(HierarchyPropagatePlugin::>::new( + Update, + )); let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id(); let propagatee = app diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index edf8986130a00..2476f967280b5 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -65,7 +65,7 @@ uuid = { version = "1.13.1", default-features = false, features = [ tracing = { version = "0.1", default-features = false } [target.'cfg(target_os = "android")'.dependencies] -bevy_window = { path = "../bevy_window", version = "0.17.0-dev" } +bevy_android = { path = "../bevy_android", version = "0.17.0-dev", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] # TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption. @@ -81,9 +81,6 @@ uuid = { version = "1.13.1", default-features = false, features = ["js"] } bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [ "web", ] } -bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false, features = [ - "web", -] } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [ "web", ] } diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index b43a8625e783d..1e338ca11caf9 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -229,7 +229,7 @@ unsafe impl WorldQuery for AssetChanged { } #[inline] - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { <&A>::update_component_access(&state.asset_id, access); access.add_resource_read(state.resource_id); } diff --git a/crates/bevy_asset/src/io/android.rs b/crates/bevy_asset/src/io/android.rs index 67ca8e339a22c..fd6a71d219894 100644 --- a/crates/bevy_asset/src/io/android.rs +++ b/crates/bevy_asset/src/io/android.rs @@ -17,7 +17,7 @@ pub struct AndroidAssetReader; impl AssetReader for AndroidAssetReader { async fn read<'a>(&'a self, path: &'a Path) -> Result { - let asset_manager = bevy_window::ANDROID_APP + let asset_manager = bevy_android::ANDROID_APP .get() .expect("Bevy must be setup with the #[bevy_main] macro on Android") .asset_manager(); @@ -31,7 +31,7 @@ impl AssetReader for AndroidAssetReader { async fn read_meta<'a>(&'a self, path: &'a Path) -> Result { let meta_path = get_meta_path(path); - let asset_manager = bevy_window::ANDROID_APP + let asset_manager = bevy_android::ANDROID_APP .get() .expect("Bevy must be setup with the #[bevy_main] macro on Android") .asset_manager(); @@ -47,7 +47,7 @@ impl AssetReader for AndroidAssetReader { &'a self, path: &'a Path, ) -> Result, AssetReaderError> { - let asset_manager = bevy_window::ANDROID_APP + let asset_manager = bevy_android::ANDROID_APP .get() .expect("Bevy must be setup with the #[bevy_main] macro on Android") .asset_manager(); @@ -73,7 +73,7 @@ impl AssetReader for AndroidAssetReader { } async fn is_directory<'a>(&'a self, path: &'a Path) -> Result { - let asset_manager = bevy_window::ANDROID_APP + let asset_manager = bevy_android::ANDROID_APP .get() .expect("Bevy must be setup with the #[bevy_main] macro on Android") .asset_manager(); diff --git a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs index 06a0791a501c0..b7773cb404e2c 100644 --- a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs +++ b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs @@ -73,15 +73,15 @@ impl FilesystemEventHandler for EmbeddedEventHandler { fn handle(&mut self, absolute_paths: &[PathBuf], event: AssetSourceEvent) { if self.last_event.as_ref() != Some(&event) { - if let AssetSourceEvent::ModifiedAsset(path) = &event { - if let Ok(file) = File::open(&absolute_paths[0]) { - let mut reader = BufReader::new(file); - let mut buffer = Vec::new(); + if let AssetSourceEvent::ModifiedAsset(path) = &event + && let Ok(file) = File::open(&absolute_paths[0]) + { + let mut reader = BufReader::new(file); + let mut buffer = Vec::new(); - // Read file into vector. - if reader.read_to_end(&mut buffer).is_ok() { - self.dir.insert_asset(path, buffer); - } + // Read file into vector. + if reader.read_to_end(&mut buffer).is_ok() { + self.dir.insert_asset(path, buffer); } } self.last_event = Some(event.clone()); diff --git a/crates/bevy_asset/src/io/file/file_asset.rs b/crates/bevy_asset/src/io/file/file_asset.rs index 58ae546e7f260..290891440c4e1 100644 --- a/crates/bevy_asset/src/io/file/file_asset.rs +++ b/crates/bevy_asset/src/io/file/file_asset.rs @@ -69,10 +69,10 @@ impl AssetReader for FileAssetReader { f.ok().and_then(|dir_entry| { let path = dir_entry.path(); // filter out meta files as they are not considered assets - if let Some(ext) = path.extension().and_then(|e| e.to_str()) { - if ext.eq_ignore_ascii_case("meta") { - return None; - } + if let Some(ext) = path.extension().and_then(|e| e.to_str()) + && ext.eq_ignore_ascii_case("meta") + { + return None; } // filter out hidden files. they are not listed by default but are directly targetable if path diff --git a/crates/bevy_asset/src/io/file/mod.rs b/crates/bevy_asset/src/io/file/mod.rs index 96c43072e8f47..0ab9267fff679 100644 --- a/crates/bevy_asset/src/io/file/mod.rs +++ b/crates/bevy_asset/src/io/file/mod.rs @@ -75,14 +75,12 @@ impl FileAssetWriter { /// watching for changes. pub fn new + core::fmt::Debug>(path: P, create_root: bool) -> Self { let root_path = get_base_path().join(path.as_ref()); - if create_root { - if let Err(e) = std::fs::create_dir_all(&root_path) { - error!( - "Failed to create root directory {} for file asset writer: {}", - root_path.display(), - e - ); - } + if create_root && let Err(e) = std::fs::create_dir_all(&root_path) { + error!( + "Failed to create root directory {} for file asset writer: {}", + root_path.display(), + e + ); } Self { root_path } } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index d3c51ec9eb8f8..4769f5b3efcb3 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -422,8 +422,7 @@ impl Plugin for AssetPlugin { // and as a result has ambiguous system ordering with all other systems in `PreUpdate`. // This is virtually never a real problem: asset loading is async and so anything that interacts directly with it // needs to be robust to stochastic delays anyways. - .add_systems(PreUpdate, handle_internal_asset_events.ambiguous_with_all()) - .register_type::(); + .add_systems(PreUpdate, handle_internal_asset_events.ambiguous_with_all()); } } @@ -1636,37 +1635,37 @@ mod tests { let loaded_folders = world.resource::>(); let cool_texts = world.resource::>(); for event in reader.read(events) { - if let AssetEvent::LoadedWithDependencies { id } = event { - if *id == handle.id() { - let loaded_folder = loaded_folders.get(&handle).unwrap(); - let a_handle: Handle = - asset_server.get_handle("text/a.cool.ron").unwrap(); - let c_handle: Handle = - asset_server.get_handle("text/c.cool.ron").unwrap(); - - let mut found_a = false; - let mut found_c = false; - for asset_handle in &loaded_folder.handles { - if asset_handle.id() == a_handle.id().untyped() { - found_a = true; - } else if asset_handle.id() == c_handle.id().untyped() { - found_c = true; - } + if let AssetEvent::LoadedWithDependencies { id } = event + && *id == handle.id() + { + let loaded_folder = loaded_folders.get(&handle).unwrap(); + let a_handle: Handle = + asset_server.get_handle("text/a.cool.ron").unwrap(); + let c_handle: Handle = + asset_server.get_handle("text/c.cool.ron").unwrap(); + + let mut found_a = false; + let mut found_c = false; + for asset_handle in &loaded_folder.handles { + if asset_handle.id() == a_handle.id().untyped() { + found_a = true; + } else if asset_handle.id() == c_handle.id().untyped() { + found_c = true; } - assert!(found_a); - assert!(found_c); - assert_eq!(loaded_folder.handles.len(), 2); + } + assert!(found_a); + assert!(found_c); + assert_eq!(loaded_folder.handles.len(), 2); - let a_text = cool_texts.get(&a_handle).unwrap(); - let b_text = cool_texts.get(&a_text.dependencies[0]).unwrap(); - let c_text = cool_texts.get(&c_handle).unwrap(); + let a_text = cool_texts.get(&a_handle).unwrap(); + let b_text = cool_texts.get(&a_text.dependencies[0]).unwrap(); + let c_text = cool_texts.get(&c_handle).unwrap(); - assert_eq!("a", a_text.text); - assert_eq!("b", b_text.text); - assert_eq!("c", c_text.text); + assert_eq!("a", a_text.text); + assert_eq!("b", b_text.text); + assert_eq!("c", c_text.text); - return Some(()); - } + return Some(()); } } None diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 24405f0657d5e..8065d32b53bc5 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -371,7 +371,7 @@ impl<'a> LoadContext<'a> { /// load_context.add_loaded_labeled_asset(label, loaded_asset); /// } /// ``` - pub fn begin_labeled_asset(&self) -> LoadContext { + pub fn begin_labeled_asset(&self) -> LoadContext<'_> { LoadContext::new( self.asset_server, self.asset_path.clone(), diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index ed189a683b35d..d4e8b9a0370af 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -246,7 +246,7 @@ impl<'a> AssetPath<'a> { /// Gets the "asset source", if one was defined. If none was defined, the default source /// will be used. #[inline] - pub fn source(&self) -> &AssetSourceId { + pub fn source(&self) -> &AssetSourceId<'_> { &self.source } diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index a239d66a9b59a..7b3d36e686b02 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -642,11 +642,12 @@ impl AssetProcessor { )) .await?; } - if !contains_files && path.parent().is_some() { - if let Some(writer) = clean_empty_folders_writer { - // it is ok for this to fail as it is just a cleanup job. - let _ = writer.remove_empty_directory(&path).await; - } + if !contains_files + && path.parent().is_some() + && let Some(writer) = clean_empty_folders_writer + { + // it is ok for this to fail as it is just a cleanup job. + let _ = writer.remove_empty_directory(&path).await; } Ok(contains_files) } else { @@ -892,23 +893,22 @@ impl AssetProcessor { if let Some(current_processed_info) = infos .get(asset_path) .and_then(|i| i.processed_info.as_ref()) + && current_processed_info.hash == new_hash { - if current_processed_info.hash == new_hash { - let mut dependency_changed = false; - for current_dep_info in ¤t_processed_info.process_dependencies { - let live_hash = infos - .get(¤t_dep_info.path) - .and_then(|i| i.processed_info.as_ref()) - .map(|i| i.full_hash); - if live_hash != Some(current_dep_info.full_hash) { - dependency_changed = true; - break; - } - } - if !dependency_changed { - return Ok(ProcessResult::SkippedNotChanged); + let mut dependency_changed = false; + for current_dep_info in ¤t_processed_info.process_dependencies { + let live_hash = infos + .get(¤t_dep_info.path) + .and_then(|i| i.processed_info.as_ref()) + .map(|i| i.full_hash); + if live_hash != Some(current_dep_info.full_hash) { + dependency_changed = true; + break; } } + if !dependency_changed { + return Ok(ProcessResult::SkippedNotChanged); + } } } // Note: this lock must remain alive until all processed asset and meta writes have finished (or failed) diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index c8b96012eea72..0f3e3987f264a 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -114,7 +114,7 @@ impl<'a, A: Asset> SavedAsset<'a, A> { } /// Returns the labeled asset, if it exists and matches this type. - pub fn get_labeled(&self, label: &Q) -> Option> + pub fn get_labeled(&self, label: &Q) -> Option> where CowArc<'static, str>: Borrow, Q: ?Sized + Hash + Eq, diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index 1b3bb3cb65d68..3651cd2efebf2 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -133,13 +133,11 @@ impl AssetInfos { .get(&type_id) .ok_or(MissingHandleProviderError(type_id))?; - if watching_for_changes { - if let Some(path) = &path { - let mut without_label = path.to_owned(); - if let Some(label) = without_label.take_label() { - let labels = living_labeled_assets.entry(without_label).or_default(); - labels.insert(label.as_ref().into()); - } + if watching_for_changes && let Some(path) = &path { + let mut without_label = path.to_owned(); + if let Some(label) = without_label.take_label() { + let labels = living_labeled_assets.entry(without_label).or_default(); + labels.insert(label.as_ref().into()); } } diff --git a/crates/bevy_asset/src/server/loaders.rs b/crates/bevy_asset/src/server/loaders.rs index 08384e9efeaa4..9c13c861bd986 100644 --- a/crates/bevy_asset/src/server/loaders.rs +++ b/crates/bevy_asset/src/server/loaders.rs @@ -59,15 +59,13 @@ impl AssetLoaders { .entry((*extension).into()) .or_default(); - if !list.is_empty() { - if let Some(existing_loaders_for_type_id) = existing_loaders_for_type_id { - if list - .iter() - .any(|index| existing_loaders_for_type_id.contains(index)) - { - duplicate_extensions.push(extension); - } - } + if !list.is_empty() + && let Some(existing_loaders_for_type_id) = existing_loaders_for_type_id + && list + .iter() + .any(|index| existing_loaders_for_type_id.contains(index)) + { + duplicate_extensions.push(extension); } list.push(loader_index); @@ -125,15 +123,13 @@ impl AssetLoaders { .entry((*extension).into()) .or_default(); - if !list.is_empty() { - if let Some(existing_loaders_for_type_id) = existing_loaders_for_type_id { - if list - .iter() - .any(|index| existing_loaders_for_type_id.contains(index)) - { - duplicate_extensions.push(extension); - } - } + if !list.is_empty() + && let Some(existing_loaders_for_type_id) = existing_loaders_for_type_id + && list + .iter() + .any(|index| existing_loaders_for_type_id.contains(index)) + { + duplicate_extensions.push(extension); } list.push(loader_index); @@ -218,10 +214,10 @@ impl AssetLoaders { }; // Try the provided extension - if let Some(extension) = extension { - if let Some(&index) = try_extension(extension) { - return self.get_by_index(index); - } + if let Some(extension) = extension + && let Some(&index) = try_extension(extension) + { + return self.get_by_index(index); } // Try extracting the extension from the path diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index da3d59899955f..b18967e601ae8 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -846,10 +846,11 @@ impl AssetServer { } } - if !reloaded && server.data.infos.read().should_reload(&path) { - if let Err(err) = server.load_internal(None, path, true, None).await { - error!("{}", err); - } + if !reloaded + && server.data.infos.read().should_reload(&path) + && let Err(err) = server.load_internal(None, path, true, None).await + { + error!("{}", err); } }) .detach(); @@ -1286,7 +1287,7 @@ impl AssetServer { } /// Returns the path for the given `id`, if it has one. - pub fn get_path(&self, id: impl Into) -> Option { + pub fn get_path(&self, id: impl Into) -> Option> { let infos = self.data.infos.read(); let info = infos.get(id.into())?; Some(info.path.as_ref()?.clone()) diff --git a/crates/bevy_asset/src/transformer.rs b/crates/bevy_asset/src/transformer.rs index 802e3aeaa7eff..f80f44511ab4c 100644 --- a/crates/bevy_asset/src/transformer.rs +++ b/crates/bevy_asset/src/transformer.rs @@ -87,7 +87,7 @@ impl TransformedAsset { &mut self.value } /// Returns the labeled asset, if it exists and matches this type. - pub fn get_labeled(&mut self, label: &Q) -> Option> + pub fn get_labeled(&mut self, label: &Q) -> Option> where CowArc<'static, str>: Borrow, Q: ?Sized + Hash + Eq, @@ -187,7 +187,7 @@ impl<'a, A: Asset> TransformedSubAsset<'a, A> { self.value } /// Returns the labeled asset, if it exists and matches this type. - pub fn get_labeled(&mut self, label: &Q) -> Option> + pub fn get_labeled(&mut self, label: &Q) -> Option> where CowArc<'static, str>: Borrow, Q: ?Sized + Hash + Eq, diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index e3b5e02569fd2..43e7cbe05c0e0 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -80,13 +80,7 @@ pub struct AudioPlugin { impl Plugin for AudioPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .insert_resource(self.global_volume) + app.insert_resource(self.global_volume) .insert_resource(DefaultSpatialScale(self.default_spatial_scale)) .configure_sets( PostUpdate, diff --git a/crates/bevy_camera/Cargo.toml b/crates/bevy_camera/Cargo.toml index 6ed3998a82165..d6cbd46667fef 100644 --- a/crates/bevy_camera/Cargo.toml +++ b/crates/bevy_camera/Cargo.toml @@ -26,7 +26,7 @@ bevy_color = { path = "../bevy_color", version = "0.17.0-dev", features = [ bevy_window = { path = "../bevy_window", version = "0.17.0-dev" } # other -wgpu-types = { version = "25", default-features = false } +wgpu-types = { version = "26", default-features = false } serde = { version = "1", default-features = false, features = ["derive"] } thiserror = { version = "2", default-features = false } downcast-rs = { version = "2", default-features = false, features = ["std"] } diff --git a/crates/bevy_camera/src/camera.rs b/crates/bevy_camera/src/camera.rs index 1eea96307732e..0fad8f94fe626 100644 --- a/crates/bevy_camera/src/camera.rs +++ b/crates/bevy_camera/src/camera.rs @@ -80,14 +80,20 @@ impl Viewport { } } - pub fn with_override( - &self, + pub fn from_viewport_and_override( + viewport: Option<&Self>, main_pass_resolution_override: Option<&MainPassResolutionOverride>, - ) -> Self { - let mut viewport = self.clone(); + ) -> Option { + let mut viewport = viewport.cloned(); + if let Some(override_size) = main_pass_resolution_override { - viewport.physical_size = **override_size; + if viewport.is_none() { + viewport = Some(Viewport::default()); + } + + viewport.as_mut().unwrap().physical_size = **override_size; } + viewport } } @@ -101,7 +107,8 @@ impl Viewport { /// * Insert this component on a 3d camera entity in the render world. /// * The resolution override must be smaller than the camera's viewport size. /// * The resolution override is specified in physical pixels. -#[derive(Component, Reflect, Deref)] +/// * In shaders, use `View::main_pass_viewport` instead of `View::viewport`. +#[derive(Component, Reflect, Deref, Debug)] #[reflect(Component)] pub struct MainPassResolutionOverride(pub UVec2); @@ -176,7 +183,7 @@ pub struct ComputedCameraValues { pub old_sub_camera_view: Option, } -/// How much energy a `Camera3d` absorbs from incoming light. +/// How much energy a [`Camera3d`](crate::Camera3d) absorbs from incoming light. /// /// #[derive(Component, Clone, Copy, Reflect)] @@ -322,8 +329,8 @@ pub enum ViewportConversionError { /// but custom render graphs can also be defined. Inserting a [`Camera`] with no render /// graph will emit an error at runtime. /// -/// [`Camera2d`]: https://docs.rs/bevy/latest/bevy/core_pipeline/core_2d/struct.Camera2d.html -/// [`Camera3d`]: https://docs.rs/bevy/latest/bevy/core_pipeline/core_3d/struct.Camera3d.html +/// [`Camera2d`]: crate::Camera2d +/// [`Camera3d`]: crate::Camera3d #[derive(Component, Debug, Reflect, Clone)] #[reflect(Component, Default, Debug, Clone)] #[require( diff --git a/crates/bevy_camera/src/lib.rs b/crates/bevy_camera/src/lib.rs index bf0ededae8314..1ae5c8107860e 100644 --- a/crates/bevy_camera/src/lib.rs +++ b/crates/bevy_camera/src/lib.rs @@ -18,20 +18,10 @@ pub struct CameraPlugin; impl Plugin for CameraPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_resource::() - .add_plugins(( - CameraProjectionPlugin, - visibility::VisibilityPlugin, - visibility::VisibilityRangePlugin, - )); + app.init_resource::().add_plugins(( + CameraProjectionPlugin, + visibility::VisibilityPlugin, + visibility::VisibilityRangePlugin, + )); } } diff --git a/crates/bevy_camera/src/primitives.rs b/crates/bevy_camera/src/primitives.rs index 32bb557b93774..eb6246d25990c 100644 --- a/crates/bevy_camera/src/primitives.rs +++ b/crates/bevy_camera/src/primitives.rs @@ -235,7 +235,7 @@ impl HalfSpace { /// This process is called frustum culling, and entities can opt out of it using /// the [`NoFrustumCulling`] component. /// -/// The frustum component is typically added automatically for cameras, either `Camera2d` or `Camera3d`. +/// The frustum component is typically added automatically for cameras, either [`Camera2d`] or [`Camera3d`]. /// It is usually updated automatically by [`update_frusta`] from the /// [`CameraProjection`] component and [`GlobalTransform`] of the camera entity. /// @@ -244,6 +244,8 @@ impl HalfSpace { /// [`update_frusta`]: crate::visibility::update_frusta /// [`CameraProjection`]: crate::CameraProjection /// [`GlobalTransform`]: bevy_transform::components::GlobalTransform +/// [`Camera2d`]: crate::Camera2d +/// [`Camera3d`]: crate::Camera3d #[derive(Component, Clone, Copy, Debug, Default, Reflect)] #[reflect(Component, Default, Debug, Clone)] pub struct Frustum { diff --git a/crates/bevy_camera/src/projection.rs b/crates/bevy_camera/src/projection.rs index 847714208d12a..1d2bfdab27561 100644 --- a/crates/bevy_camera/src/projection.rs +++ b/crates/bevy_camera/src/projection.rs @@ -18,16 +18,12 @@ pub struct CameraProjectionPlugin; impl Plugin for CameraProjectionPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .add_systems( - PostUpdate, - crate::visibility::update_frusta - .in_set(VisibilitySystems::UpdateFrusta) - .after(TransformSystems::Propagate), - ); + app.add_systems( + PostUpdate, + crate::visibility::update_frusta + .in_set(VisibilitySystems::UpdateFrusta) + .after(TransformSystems::Propagate), + ); } } diff --git a/crates/bevy_camera/src/visibility/mod.rs b/crates/bevy_camera/src/visibility/mod.rs index 478db336e32a3..f059beab829ef 100644 --- a/crates/bevy_camera/src/visibility/mod.rs +++ b/crates/bevy_camera/src/visibility/mod.rs @@ -340,17 +340,7 @@ impl Plugin for VisibilityPlugin { fn build(&self, app: &mut bevy_app::App) { use VisibilitySystems::*; - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_required_components::() + app.register_required_components::() .register_required_components::() .register_required_components::() .register_required_components::() @@ -394,10 +384,10 @@ pub fn calculate_bounds( without_aabb: Query<(Entity, &Mesh3d), (Without, Without)>, ) { for (entity, mesh_handle) in &without_aabb { - if let Some(mesh) = meshes.get(mesh_handle) { - if let Some(aabb) = mesh.compute_aabb() { - commands.entity(entity).try_insert(aabb); - } + if let Some(mesh) = meshes.get(mesh_handle) + && let Some(aabb) = mesh.compute_aabb() + { + commands.entity(entity).try_insert(aabb); } } } @@ -590,21 +580,22 @@ pub fn check_visibility( } // If we have an aabb, do frustum culling - if !no_frustum_culling && !no_cpu_culling { - if let Some(model_aabb) = maybe_model_aabb { - let world_from_local = transform.affine(); - let model_sphere = Sphere { - center: world_from_local.transform_point3a(model_aabb.center), - radius: transform.radius_vec3a(model_aabb.half_extents), - }; - // Do quick sphere-based frustum culling - if !frustum.intersects_sphere(&model_sphere, false) { - return; - } - // Do aabb-based frustum culling - if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) { - return; - } + if !no_frustum_culling + && !no_cpu_culling + && let Some(model_aabb) = maybe_model_aabb + { + let world_from_local = transform.affine(); + let model_sphere = Sphere { + center: world_from_local.transform_point3a(model_aabb.center), + radius: transform.radius_vec3a(model_aabb.half_extents), + }; + // Do quick sphere-based frustum culling + if !frustum.intersects_sphere(&model_sphere, false) { + return; + } + // Do aabb-based frustum culling + if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) { + return; } } diff --git a/crates/bevy_camera/src/visibility/range.rs b/crates/bevy_camera/src/visibility/range.rs index b85631c9d5c75..b827dedbbf10c 100644 --- a/crates/bevy_camera/src/visibility/range.rs +++ b/crates/bevy_camera/src/visibility/range.rs @@ -30,14 +30,12 @@ pub struct VisibilityRangePlugin; impl Plugin for VisibilityRangePlugin { fn build(&self, app: &mut App) { - app.register_type::() - .init_resource::() - .add_systems( - PostUpdate, - check_visibility_ranges - .in_set(VisibilitySystems::CheckVisibility) - .before(check_visibility), - ); + app.init_resource::().add_systems( + PostUpdate, + check_visibility_ranges + .in_set(VisibilitySystems::CheckVisibility) + .before(check_visibility), + ); } } diff --git a/crates/bevy_color/Cargo.toml b/crates/bevy_color/Cargo.toml index 22ade1270900b..23df9d99b3976 100644 --- a/crates/bevy_color/Cargo.toml +++ b/crates/bevy_color/Cargo.toml @@ -20,8 +20,8 @@ serde = { version = "1.0", features = [ ], default-features = false, optional = true } thiserror = { version = "2", default-features = false } derive_more = { version = "2", default-features = false, features = ["from"] } -wgpu-types = { version = "25", default-features = false, optional = true } -encase = { version = "0.10", default-features = false, optional = true } +wgpu-types = { version = "26", default-features = false, optional = true } +encase = { version = "0.11", default-features = false, optional = true } [features] default = ["std", "bevy_reflect", "encase"] diff --git a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs index 805e67ec97c6e..773ab8b12f646 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs @@ -46,14 +46,12 @@ impl Plugin for AutoExposurePlugin { embedded_asset!(app, "auto_exposure.wgsl"); app.add_plugins(RenderAssetPlugin::::default()) - .register_type::() .init_asset::() .register_asset_reflect::(); app.world_mut() .resource_mut::>() .insert(&Handle::default(), AutoExposureCompensationCurve::default()); - app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_core_pipeline/src/auto_exposure/node.rs b/crates/bevy_core_pipeline/src/auto_exposure/node.rs index 222efe5c62bd0..c7f88dfb28d6f 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/node.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/node.rs @@ -10,6 +10,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_render::{ + diagnostic::RecordDiagnostics, globals::GlobalsBuffer, render_asset::RenderAssets, render_graph::*, @@ -98,6 +99,8 @@ impl Node for AutoExposureNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let compute_bind_group = render_context.render_device().create_bind_group( None, &pipeline.histogram_layout, @@ -122,9 +125,10 @@ impl Node for AutoExposureNode { render_context .command_encoder() .begin_compute_pass(&ComputePassDescriptor { - label: Some("auto_exposure_pass"), + label: Some("auto_exposure"), timestamp_writes: None, }); + let pass_span = diagnostics.time_span(&mut compute_pass, "auto_exposure"); compute_pass.set_bind_group(0, &compute_bind_group, &[view_uniform_offset.offset]); compute_pass.set_pipeline(histogram_pipeline); @@ -136,6 +140,8 @@ impl Node for AutoExposureNode { compute_pass.set_pipeline(average_pipeline); compute_pass.dispatch_workgroups(1, 1, 1); + pass_span.end(&mut compute_pass); + Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 0308ebe72e508..f07675fad36c7 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -49,9 +49,6 @@ impl Plugin for BloomPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "bloom.wgsl"); - app.register_type::(); - app.register_type::(); - app.register_type::(); app.add_plugins(( ExtractComponentPlugin::::default(), UniformComponentPlugin::::default(), @@ -186,6 +183,7 @@ impl ViewNode for BloomNode { label: Some("bloom_downsampling_first_pass"), color_attachments: &[Some(RenderPassColorAttachment { view, + depth_slice: None, resolve_target: None, ops: Operations::default(), })], @@ -210,6 +208,7 @@ impl ViewNode for BloomNode { label: Some("bloom_downsampling_pass"), color_attachments: &[Some(RenderPassColorAttachment { view, + depth_slice: None, resolve_target: None, ops: Operations::default(), })], @@ -234,6 +233,7 @@ impl ViewNode for BloomNode { label: Some("bloom_upsampling_pass"), color_attachments: &[Some(RenderPassColorAttachment { view, + depth_slice: None, resolve_target: None, ops: Operations { load: LoadOp::Load, diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index f051c1164cc8a..70146efd7a253 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -80,8 +80,7 @@ pub struct Core2dPlugin; impl Plugin for Core2dPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_required_components::() + app.register_required_components::() .register_required_components_with::(|| { CameraRenderGraph::new(Core2d) }) diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index c5ee7a798db2e..0ee9144a954d1 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -2,6 +2,7 @@ use crate::{ core_3d::Opaque3d, skybox::{SkyboxBindGroup, SkyboxPipelineId}, }; +use bevy_camera::Viewport; use bevy_ecs::{prelude::World, query::QueryItem}; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, @@ -91,8 +92,10 @@ impl ViewNode for MainOpaquePass3dNode { let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_3d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } // Opaque draws diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs index 393167227f8db..37d359785bfd6 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs @@ -1,9 +1,11 @@ use super::{Camera3d, ViewTransmissionTexture}; use crate::core_3d::Transmissive3d; +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_image::ToExtents; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, + diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::ViewSortedRenderPhases, render_resource::{RenderPassDescriptor, StoreOp}, @@ -52,6 +54,8 @@ impl ViewNode for MainTransmissivePass3dNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let physical_target_size = camera.physical_target_size.unwrap(); let render_pass_descriptor = RenderPassDescriptor { @@ -94,6 +98,8 @@ impl ViewNode for MainTransmissivePass3dNode { let mut render_pass = render_context.begin_tracked_render_pass(render_pass_descriptor.clone()); + let pass_span = + diagnostics.pass_span(&mut render_pass, "main_transmissive_pass_3d"); if let Some(viewport) = camera.viewport.as_ref() { render_pass.set_camera_viewport(viewport); @@ -105,18 +111,27 @@ impl ViewNode for MainTransmissivePass3dNode { { error!("Error encountered while rendering the transmissive phase {err:?}"); } + + pass_span.end(&mut render_pass); } } else { let mut render_pass = render_context.begin_tracked_render_pass(render_pass_descriptor); + let pass_span = + diagnostics.pass_span(&mut render_pass, "main_transmissive_pass_3d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = Viewport::from_viewport_and_override( + camera.viewport.as_ref(), + resolution_override, + ) { + render_pass.set_camera_viewport(&viewport); } if let Err(err) = transmissive_phase.render(&mut render_pass, world, view_entity) { error!("Error encountered while rendering the transmissive phase {err:?}"); } + + pass_span.end(&mut render_pass); } } diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs index 0c70ec23a0863..bbe14578b0903 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs @@ -1,4 +1,5 @@ use crate::core_3d::Transparent3d; +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, @@ -69,8 +70,10 @@ impl ViewNode for MainTransparentPass3dNode { let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_3d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) { diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 9fd7880869546..ad058f8579072 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -139,9 +139,7 @@ pub struct Core3dPlugin; impl Plugin for Core3dPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .register_required_components_with::(|| DebandDither::Enabled) + app.register_required_components_with::(|| DebandDither::Enabled) .register_required_components_with::(|| { CameraRenderGraph::new(Core3d) }) diff --git a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs index 9a23c0e34d752..68d1634160f8a 100644 --- a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs +++ b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs @@ -8,6 +8,7 @@ use bevy_ecs::prelude::*; use bevy_image::ToExtents; use bevy_render::{ camera::ExtractedCamera, + diagnostic::RecordDiagnostics, render_resource::{binding_types::texture_2d, *}, renderer::RenderDevice, texture::{CachedTexture, TextureCache}, @@ -77,6 +78,8 @@ impl ViewNode for CopyDeferredLightingIdNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let bind_group = render_context.render_device().create_bind_group( "copy_deferred_lighting_id_bind_group", ©_deferred_lighting_id_pipeline.layout, @@ -84,7 +87,7 @@ impl ViewNode for CopyDeferredLightingIdNode { ); let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("copy_deferred_lighting_id_pass"), + label: Some("copy_deferred_lighting_id"), color_attachments: &[], depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &deferred_lighting_id_depth_texture.texture.default_view, @@ -98,10 +101,14 @@ impl ViewNode for CopyDeferredLightingIdNode { occlusion_query_set: None, }); + let pass_span = diagnostics.pass_span(&mut render_pass, "copy_deferred_lighting_id"); + render_pass.set_render_pipeline(pipeline); render_pass.set_bind_group(0, &bind_group, &[]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index ab87fccee6e89..db983c9043567 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -1,3 +1,4 @@ +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::camera::MainPassResolutionOverride; use bevy_render::experimental::occlusion_culling::OcclusionCulling; @@ -6,6 +7,7 @@ use bevy_render::render_graph::ViewNode; use bevy_render::view::{ExtractedView, NoIndirectDrawing}; use bevy_render::{ camera::ExtractedCamera, + diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext}, render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, @@ -130,6 +132,8 @@ fn run_deferred_prepass<'w>( return Ok(()); }; + let diagnostic = render_context.diagnostic_recorder(); + let mut color_attachments = vec![]; color_attachments.push( view_prepass_textures @@ -176,6 +180,7 @@ fn run_deferred_prepass<'w>( load: bevy_render::render_resource::LoadOp::Load, store: StoreOp::Store, }, + depth_slice: None, } } #[cfg(any( @@ -221,8 +226,11 @@ fn run_deferred_prepass<'w>( occlusion_query_set: None, }); let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + let pass_span = diagnostic.pass_span(&mut render_pass, label); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } // Opaque draws @@ -247,6 +255,7 @@ fn run_deferred_prepass<'w>( } } + pass_span.end(&mut render_pass); drop(render_pass); // After rendering to the view depth texture, copy it to the prepass depth texture diff --git a/crates/bevy_core_pipeline/src/dof/mod.rs b/crates/bevy_core_pipeline/src/dof/mod.rs index 0bd5c79365d12..10ca30c8fc2c8 100644 --- a/crates/bevy_core_pipeline/src/dof/mod.rs +++ b/crates/bevy_core_pipeline/src/dof/mod.rs @@ -32,6 +32,7 @@ use bevy_math::ops; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::{ camera::{PhysicalCameraParameters, Projection}, + diagnostic::RecordDiagnostics, extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_graph::{ NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner, @@ -206,8 +207,6 @@ impl Plugin for DepthOfFieldPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "dof.wgsl"); - app.register_type::(); - app.register_type::(); app.add_plugins(UniformComponentPlugin::::default()); app.add_plugins(SyncComponentPlugin::::default()); @@ -354,6 +353,8 @@ impl ViewNode for DepthOfFieldNode { let view_uniforms = world.resource::(); let global_bind_group = world.resource::(); + let diagnostics = render_context.diagnostic_recorder(); + // We can be in either Gaussian blur or bokeh mode here. Both modes are // similar, consisting of two passes each. We factor out the information // specific to each pass into @@ -409,6 +410,7 @@ impl ViewNode for DepthOfFieldNode { let mut color_attachments: SmallVec<[_; 2]> = SmallVec::new(); color_attachments.push(Some(RenderPassColorAttachment { view: postprocess.destination, + depth_slice: None, resolve_target: None, ops: Operations { load: LoadOp::Clear(default()), @@ -429,6 +431,7 @@ impl ViewNode for DepthOfFieldNode { }; color_attachments.push(Some(RenderPassColorAttachment { view: &auxiliary_dof_texture.default_view, + depth_slice: None, resolve_target: None, ops: Operations { load: LoadOp::Clear(default()), @@ -446,6 +449,9 @@ impl ViewNode for DepthOfFieldNode { let mut render_pass = render_context .command_encoder() .begin_render_pass(&render_pass_descriptor); + let pass_span = + diagnostics.pass_span(&mut render_pass, pipeline_render_info.pass_label); + render_pass.set_pipeline(render_pipeline); // Set the per-view bind group. render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]); @@ -457,6 +463,8 @@ impl ViewNode for DepthOfFieldNode { ); // Render the full-screen pass. render_pass.draw(0..3, 0..1); + + pass_span.end(&mut render_pass); } Ok(()) diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index bc3159de18712..1b5f022428a3c 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -37,19 +37,11 @@ pub mod prelude { } use crate::{ - blit::BlitPlugin, - bloom::BloomPlugin, - core_2d::Core2dPlugin, - core_3d::Core3dPlugin, - deferred::copy_lighting_id::CopyDeferredLightingIdPlugin, - dof::DepthOfFieldPlugin, - experimental::mip_generation::MipGenerationPlugin, - motion_blur::MotionBlurPlugin, - msaa_writeback::MsaaWritebackPlugin, - post_process::PostProcessingPlugin, - prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, - tonemapping::TonemappingPlugin, - upscaling::UpscalingPlugin, + blit::BlitPlugin, bloom::BloomPlugin, core_2d::Core2dPlugin, core_3d::Core3dPlugin, + deferred::copy_lighting_id::CopyDeferredLightingIdPlugin, dof::DepthOfFieldPlugin, + experimental::mip_generation::MipGenerationPlugin, motion_blur::MotionBlurPlugin, + msaa_writeback::MsaaWritebackPlugin, post_process::PostProcessingPlugin, + tonemapping::TonemappingPlugin, upscaling::UpscalingPlugin, }; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; @@ -63,12 +55,7 @@ impl Plugin for CorePipelinePlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "fullscreen_vertex_shader/fullscreen.wgsl"); - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_resource::() - .add_plugins((Core2dPlugin, Core3dPlugin, CopyDeferredLightingIdPlugin)) + app.add_plugins((Core2dPlugin, Core3dPlugin, CopyDeferredLightingIdPlugin)) .add_plugins(( BlitPlugin, MsaaWritebackPlugin, diff --git a/crates/bevy_core_pipeline/src/motion_blur/node.rs b/crates/bevy_core_pipeline/src/motion_blur/node.rs index ade5f50d77466..00e4d4df873d5 100644 --- a/crates/bevy_core_pipeline/src/motion_blur/node.rs +++ b/crates/bevy_core_pipeline/src/motion_blur/node.rs @@ -1,5 +1,6 @@ use bevy_ecs::{query::QueryItem, world::World}; use bevy_render::{ + diagnostic::RecordDiagnostics, extract_component::ComponentUniforms, globals::GlobalsBuffer, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, @@ -59,6 +60,8 @@ impl ViewNode for MotionBlurNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let post_process = view_target.post_process_write(); let layout = if msaa.samples() == 1 { @@ -81,9 +84,10 @@ impl ViewNode for MotionBlurNode { ); let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("motion_blur_pass"), + label: Some("motion_blur"), color_attachments: &[Some(RenderPassColorAttachment { view: post_process.destination, + depth_slice: None, resolve_target: None, ops: Operations::default(), })], @@ -91,11 +95,14 @@ impl ViewNode for MotionBlurNode { timestamp_writes: None, occlusion_query_set: None, }); + let pass_span = diagnostics.pass_span(&mut render_pass, "motion_blur"); render_pass.set_render_pipeline(pipeline); render_pass.set_bind_group(0, &bind_group, &[]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/msaa_writeback.rs b/crates/bevy_core_pipeline/src/msaa_writeback.rs index 151660876aeb0..f1a833047fbb1 100644 --- a/crates/bevy_core_pipeline/src/msaa_writeback.rs +++ b/crates/bevy_core_pipeline/src/msaa_writeback.rs @@ -8,6 +8,7 @@ use bevy_color::LinearRgba; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ camera::ExtractedCamera, + diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_resource::*, renderer::RenderContext, @@ -74,6 +75,8 @@ impl ViewNode for MsaaWritebackNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + // The current "main texture" needs to be bound as an input resource, and we need the "other" // unused target to be the "resolve target" for the MSAA write. Therefore this is the same // as a post process write! @@ -87,6 +90,7 @@ impl ViewNode for MsaaWritebackNode { color_attachments: &[Some(RenderPassColorAttachment { // If MSAA is enabled, then the sampled texture will always exist view: target.sampled_main_texture_view().unwrap(), + depth_slice: None, resolve_target: Some(post_process.destination), ops: Operations { load: LoadOp::Clear(LinearRgba::BLACK.into()), @@ -104,11 +108,14 @@ impl ViewNode for MsaaWritebackNode { let mut render_pass = render_context .command_encoder() .begin_render_pass(&pass_descriptor); + let pass_span = diagnostics.pass_span(&mut render_pass, "msaa_writeback"); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, &bind_group, &[]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 9c20f4739d752..f08fa2ffd701e 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -67,13 +67,13 @@ impl Component for OrderIndependentTransparencySettings { fn on_add() -> Option { Some(|world, context| { - if let Some(value) = world.get::(context.entity) { - if value.layer_count > 32 { - warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.", + if let Some(value) = world.get::(context.entity) + && value.layer_count > 32 + { + warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.", context.caller.map(|location|format!("{location}: ")).unwrap_or_default(), value.layer_count ); - } } }) } @@ -106,8 +106,7 @@ impl Plugin for OrderIndependentTransparencyPlugin { OitResolvePlugin, )) .add_systems(Update, check_msaa) - .add_systems(Last, configure_depth_texture_usages) - .register_type::(); + .add_systems(Last, configure_depth_texture_usages); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; diff --git a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs index 650b65f494bc2..882251f072857 100644 --- a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs @@ -167,11 +167,11 @@ pub fn queue_oit_resolve_pipeline( layer_count: oit_settings.layer_count, }; - if let Some((cached_key, id)) = cached_pipeline_id.get(&e) { - if *cached_key == key { - commands.entity(e).insert(OitResolvePipelineId(*id)); - continue; - } + if let Some((cached_key, id)) = cached_pipeline_id.get(&e) + && *cached_key == key + { + commands.entity(e).insert(OitResolvePipelineId(*id)); + continue; } let desc = specialize_oit_resolve_pipeline( diff --git a/crates/bevy_core_pipeline/src/oit/resolve/node.rs b/crates/bevy_core_pipeline/src/oit/resolve/node.rs index 77352e5ecb42b..b09ad63d21962 100644 --- a/crates/bevy_core_pipeline/src/oit/resolve/node.rs +++ b/crates/bevy_core_pipeline/src/oit/resolve/node.rs @@ -1,6 +1,8 @@ +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, + diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode}, render_resource::{BindGroupEntries, PipelineCache, RenderPassDescriptor}, renderer::RenderContext, @@ -49,6 +51,8 @@ impl ViewNode for OitResolveNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let depth_bind_group = render_context.render_device().create_bind_group( "oit_resolve_depth_bind_group", &resolve_pipeline.oit_depth_bind_group_layout, @@ -56,15 +60,18 @@ impl ViewNode for OitResolveNode { ); let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("oit_resolve_pass"), + label: Some("oit_resolve"), color_attachments: &[Some(view_target.get_color_attachment())], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, }); + let pass_span = diagnostics.pass_span(&mut render_pass, "oit_resolve"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } render_pass.set_render_pipeline(pipeline); @@ -72,6 +79,8 @@ impl ViewNode for OitResolveNode { render_pass.set_bind_group(1, &depth_bind_group, &[]); render_pass.draw(0..3, 0..1); + + pass_span.end(&mut render_pass); } Ok(()) diff --git a/crates/bevy_core_pipeline/src/post_process/mod.rs b/crates/bevy_core_pipeline/src/post_process/mod.rs index 6d9a7669f2221..551caf28f1f64 100644 --- a/crates/bevy_core_pipeline/src/post_process/mod.rs +++ b/crates/bevy_core_pipeline/src/post_process/mod.rs @@ -19,6 +19,7 @@ use bevy_image::{BevyDefault, Image}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::Camera, + diagnostic::RecordDiagnostics, extract_component::{ExtractComponent, ExtractComponentPlugin}, load_shader_library, render_asset::{RenderAssetUsages, RenderAssets}, @@ -200,7 +201,6 @@ impl Plugin for PostProcessingPlugin { RenderAssetUsages::RENDER_WORLD, )); - app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { @@ -371,14 +371,17 @@ impl ViewNode for PostProcessingNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + // Use the [`PostProcessWrite`] infrastructure, since this is a // full-screen pass. let post_process = view_target.post_process_write(); let pass_descriptor = RenderPassDescriptor { - label: Some("postprocessing pass"), + label: Some("postprocessing"), color_attachments: &[Some(RenderPassColorAttachment { view: post_process.destination, + depth_slice: None, resolve_target: None, ops: Operations::default(), })], @@ -402,11 +405,14 @@ impl ViewNode for PostProcessingNode { let mut render_pass = render_context .command_encoder() .begin_render_pass(&pass_descriptor); + let pass_span = diagnostics.pass_span(&mut render_pass, "postprocessing"); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, &bind_group, &[**post_processing_uniform_buffer_offsets]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index c4cc7b1d55da5..de59578d0e4bc 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -1,3 +1,4 @@ +use bevy_camera::Viewport; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, @@ -186,8 +187,10 @@ fn run_prepass<'w>( let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); let pass_span = diagnostics.pass_span(&mut render_pass, label); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } // Opaque draws @@ -235,14 +238,14 @@ fn run_prepass<'w>( drop(render_pass); // After rendering to the view depth texture, copy it to the prepass depth texture if deferred isn't going to - if deferred_prepass.is_none() { - if let Some(prepass_depth_texture) = &view_prepass_textures.depth { - command_encoder.copy_texture_to_texture( - view_depth_texture.texture.as_image_copy(), - prepass_depth_texture.texture.texture.as_image_copy(), - view_prepass_textures.size, - ); - } + if deferred_prepass.is_none() + && let Some(prepass_depth_texture) = &view_prepass_textures.depth + { + command_encoder.copy_texture_to_texture( + view_depth_texture.texture.as_image_copy(), + prepass_depth_texture.texture.texture.as_image_copy(), + view_prepass_textures.size, + ); } command_encoder.finish() diff --git a/crates/bevy_core_pipeline/src/skybox/mod.rs b/crates/bevy_core_pipeline/src/skybox/mod.rs index f80bbbf770c22..205aad66a817e 100644 --- a/crates/bevy_core_pipeline/src/skybox/mod.rs +++ b/crates/bevy_core_pipeline/src/skybox/mod.rs @@ -45,7 +45,7 @@ impl Plugin for SkyboxPlugin { embedded_asset!(app, "skybox.wgsl"); embedded_asset!(app, "skybox_prepass.wgsl"); - app.register_type::().add_plugins(( + app.add_plugins(( ExtractComponentPlugin::::default(), UniformComponentPlugin::::default(), )); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 2eb3d267e34c5..90b7de086f318 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -82,9 +82,6 @@ impl Plugin for TonemappingPlugin { app.add_plugins(ExtractResourcePlugin::::default()); - app.register_type::(); - app.register_type::(); - app.add_plugins(( ExtractComponentPlugin::::default(), ExtractComponentPlugin::::default(), diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index 0f8f6edc49eb2..d14f1251fc4b6 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -4,6 +4,7 @@ use crate::tonemapping::{TonemappingLuts, TonemappingPipeline, ViewTonemappingPi use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ + diagnostic::RecordDiagnostics, render_asset::RenderAssets, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ @@ -60,6 +61,8 @@ impl ViewNode for TonemappingNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let post_process = target.post_process_write(); let source = post_process.source; let destination = post_process.destination; @@ -114,9 +117,10 @@ impl ViewNode for TonemappingNode { }; let pass_descriptor = RenderPassDescriptor { - label: Some("tonemapping_pass"), + label: Some("tonemapping"), color_attachments: &[Some(RenderPassColorAttachment { view: destination, + depth_slice: None, resolve_target: None, ops: Operations { load: LoadOp::Clear(Default::default()), // TODO shouldn't need to be cleared @@ -131,11 +135,14 @@ impl ViewNode for TonemappingNode { let mut render_pass = render_context .command_encoder() .begin_render_pass(&pass_descriptor); + let pass_span = diagnostics.pass_span(&mut render_pass, "tonemapping"); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, bind_group, &[view_uniform_offset.offset]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index 493a1484c6e0d..7f75f3a2d4225 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -2,6 +2,7 @@ use crate::{blit::BlitPipeline, upscaling::ViewUpscalingPipeline}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ camera::{CameraOutputMode, ClearColor, ClearColorConfig, ExtractedCamera}, + diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{BindGroup, PipelineCache, RenderPassDescriptor, TextureViewId}, renderer::RenderContext, @@ -32,6 +33,8 @@ impl ViewNode for UpscalingNode { let blit_pipeline = world.resource::(); let clear_color_global = world.resource::(); + let diagnostics = render_context.diagnostic_recorder(); + let clear_color = if let Some(camera) = camera { match camera.output_mode { CameraOutputMode::Write { clear_color, .. } => clear_color, @@ -67,7 +70,7 @@ impl ViewNode for UpscalingNode { }; let pass_descriptor = RenderPassDescriptor { - label: Some("upscaling_pass"), + label: Some("upscaling"), color_attachments: &[Some( target.out_texture_color_attachment(converted_clear_color), )], @@ -79,19 +82,22 @@ impl ViewNode for UpscalingNode { let mut render_pass = render_context .command_encoder() .begin_render_pass(&pass_descriptor); + let pass_span = diagnostics.pass_span(&mut render_pass, "upscaling"); - if let Some(camera) = camera { - if let Some(viewport) = &camera.viewport { - let size = viewport.physical_size; - let position = viewport.physical_position; - render_pass.set_scissor_rect(position.x, position.y, size.x, size.y); - } + if let Some(camera) = camera + && let Some(viewport) = &camera.viewport + { + let size = viewport.physical_size; + let position = viewport.physical_position; + render_pass.set_scissor_rect(position.x, position.y, size.x, size.y); } render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, bind_group, &[]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_core_widgets/Cargo.toml b/crates/bevy_core_widgets/Cargo.toml index 6517e70fc4314..7a090cc29e9b2 100644 --- a/crates/bevy_core_widgets/Cargo.toml +++ b/crates/bevy_core_widgets/Cargo.toml @@ -18,6 +18,7 @@ bevy_input_focus = { path = "../bevy_input_focus", version = "0.17.0-dev" } bevy_log = { path = "../bevy_log", version = "0.17.0-dev" } bevy_math = { path = "../bevy_math", version = "0.17.0-dev" } bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" } # other diff --git a/crates/bevy_core_widgets/src/callback.rs b/crates/bevy_core_widgets/src/callback.rs index c3081837a785d..a009fa324d638 100644 --- a/crates/bevy_core_widgets/src/callback.rs +++ b/crates/bevy_core_widgets/src/callback.rs @@ -1,6 +1,7 @@ use bevy_ecs::system::{Commands, IntoSystem, SystemId, SystemInput}; use bevy_ecs::template::{GetTemplate, Template}; use bevy_ecs::world::{DeferredWorld, World}; +use bevy_reflect::Reflect; use std::marker::PhantomData; /// A callback defines how we want to be notified when a widget changes state. Unlike an event @@ -29,7 +30,7 @@ use std::marker::PhantomData; /// // Later, when we want to execute the callback: /// app.world_mut().commands().notify(&callback); /// ``` -#[derive(Debug)] +#[derive(Debug, Reflect)] pub enum Callback { /// Invoke a one-shot system System(SystemId), diff --git a/crates/bevy_core_widgets/src/core_button.rs b/crates/bevy_core_widgets/src/core_button.rs index 4e1087e789a88..d9ba60921f3bf 100644 --- a/crates/bevy_core_widgets/src/core_button.rs +++ b/crates/bevy_core_widgets/src/core_button.rs @@ -35,16 +35,16 @@ fn button_on_key_event( q_state: Query<(&CoreButton, Has)>, mut commands: Commands, ) { - if let Ok((bstate, disabled)) = q_state.get(trigger.target()) { - if !disabled { - let event = &trigger.event().input; - if !event.repeat - && event.state == ButtonState::Pressed - && (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space) - { - trigger.propagate(false); - commands.notify_with(&bstate.on_activate, Activate(trigger.target())); - } + if let Ok((bstate, disabled)) = q_state.get(trigger.target()) + && !disabled + { + let event = &trigger.event().input; + if !event.repeat + && event.state == ButtonState::Pressed + && (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space) + { + trigger.propagate(false); + commands.notify_with(&bstate.on_activate, Activate(trigger.target())); } } } diff --git a/crates/bevy_core_widgets/src/core_radio.rs b/crates/bevy_core_widgets/src/core_radio.rs index ff4f10dbe3b22..4132918cc4a36 100644 --- a/crates/bevy_core_widgets/src/core_radio.rs +++ b/crates/bevy_core_widgets/src/core_radio.rs @@ -8,12 +8,14 @@ use bevy_ecs::{ component::Component, observer::On, query::With, + reflect::ReflectComponent, system::{Commands, Query}, }; use bevy_input::keyboard::{KeyCode, KeyboardInput}; use bevy_input::ButtonState; use bevy_input_focus::FocusedInput; use bevy_picking::events::{Click, Pointer}; +use bevy_reflect::Reflect; use bevy_ui::{Checkable, Checked, InteractionDisabled}; use crate::{Activate, Callback, Notify}; @@ -49,6 +51,8 @@ pub struct CoreRadioGroup { /// See / #[derive(Component, Debug, Clone, Default)] #[require(AccessibilityNode(accesskit::Node::new(Role::RadioButton)), Checkable)] +#[derive(Reflect)] +#[reflect(Component)] pub struct CoreRadio; fn radio_group_on_key_input( diff --git a/crates/bevy_core_widgets/src/core_scrollbar.rs b/crates/bevy_core_widgets/src/core_scrollbar.rs index d997f565cefd1..5f5a7087483d1 100644 --- a/crates/bevy_core_widgets/src/core_scrollbar.rs +++ b/crates/bevy_core_widgets/src/core_scrollbar.rs @@ -5,17 +5,20 @@ use bevy_ecs::{ hierarchy::{ChildOf, Children}, observer::On, query::{With, Without}, + reflect::ReflectComponent, system::{Query, Res}, }; use bevy_math::Vec2; use bevy_picking::events::{Cancel, Drag, DragEnd, DragStart, Pointer, Press}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_ui::{ ComputedNode, ComputedNodeTarget, Node, ScrollPosition, UiGlobalTransform, UiScale, Val, }; /// Used to select the orientation of a scrollbar, slider, or other oriented control. // TODO: Move this to a more central place. -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Reflect)] +#[reflect(PartialEq, Clone, Default)] pub enum ControlOrientation { /// Horizontal orientation (stretching from left to right) Horizontal, @@ -45,7 +48,8 @@ pub enum ControlOrientation { /// The application is free to position the scrollbars relative to the scrolling container however /// it wants: it can overlay them on top of the scrolling content, or use a grid layout to displace /// the content to make room for the scrollbars. -#[derive(Component, Debug)] +#[derive(Component, Debug, Reflect)] +#[reflect(Component)] pub struct CoreScrollbar { /// Entity being scrolled. pub target: Entity, @@ -62,6 +66,8 @@ pub struct CoreScrollbar { /// the scrollbar). This should be a child of the scrollbar entity. #[derive(Component, Debug)] #[require(CoreScrollbarDragState)] +#[derive(Reflect)] +#[reflect(Component)] pub struct CoreScrollbarThumb; impl CoreScrollbar { @@ -83,7 +89,8 @@ impl CoreScrollbar { /// Component used to manage the state of a scrollbar during dragging. This component is /// inserted on the thumb entity. -#[derive(Component, Default)] +#[derive(Component, Default, Reflect)] +#[reflect(Component, Default)] pub struct CoreScrollbarDragState { /// Whether the scrollbar is currently being dragged. pub dragging: bool, @@ -157,14 +164,14 @@ fn scrollbar_on_drag_start( ) { if let Ok((ChildOf(thumb_parent), mut drag)) = q_thumb.get_mut(ev.target()) { ev.propagate(false); - if let Ok(scrollbar) = q_scrollbar.get(*thumb_parent) { - if let Ok(scroll_area) = q_scroll_area.get(scrollbar.target) { - drag.dragging = true; - drag.drag_origin = match scrollbar.orientation { - ControlOrientation::Horizontal => scroll_area.x, - ControlOrientation::Vertical => scroll_area.y, - }; - } + if let Ok(scrollbar) = q_scrollbar.get(*thumb_parent) + && let Ok(scroll_area) = q_scroll_area.get(scrollbar.target) + { + drag.dragging = true; + drag.drag_origin = match scrollbar.orientation { + ControlOrientation::Horizontal => scroll_area.x, + ControlOrientation::Vertical => scroll_area.y, + }; } } } @@ -176,36 +183,34 @@ fn scrollbar_on_drag( mut q_scroll_pos: Query<(&mut ScrollPosition, &ComputedNode), Without>, ui_scale: Res, ) { - if let Ok((ChildOf(thumb_parent), drag)) = q_thumb.get_mut(ev.target()) { - if let Ok((node, scrollbar)) = q_scrollbar.get_mut(*thumb_parent) { - ev.propagate(false); - let Ok((mut scroll_pos, scroll_content)) = q_scroll_pos.get_mut(scrollbar.target) - else { - return; - }; + if let Ok((ChildOf(thumb_parent), drag)) = q_thumb.get_mut(ev.target()) + && let Ok((node, scrollbar)) = q_scrollbar.get_mut(*thumb_parent) + { + ev.propagate(false); + let Ok((mut scroll_pos, scroll_content)) = q_scroll_pos.get_mut(scrollbar.target) else { + return; + }; - if drag.dragging { - let distance = ev.event().distance / ui_scale.0; - let visible_size = scroll_content.size() * scroll_content.inverse_scale_factor; - let content_size = - scroll_content.content_size() * scroll_content.inverse_scale_factor; - let scrollbar_size = (node.size() * node.inverse_scale_factor).max(Vec2::ONE); + if drag.dragging { + let distance = ev.event().distance / ui_scale.0; + let visible_size = scroll_content.size() * scroll_content.inverse_scale_factor; + let content_size = scroll_content.content_size() * scroll_content.inverse_scale_factor; + let scrollbar_size = (node.size() * node.inverse_scale_factor).max(Vec2::ONE); - match scrollbar.orientation { - ControlOrientation::Horizontal => { - let range = (content_size.x - visible_size.x).max(0.); - scroll_pos.x = (drag.drag_origin - + (distance.x * content_size.x) / scrollbar_size.x) - .clamp(0., range); - } - ControlOrientation::Vertical => { - let range = (content_size.y - visible_size.y).max(0.); - scroll_pos.y = (drag.drag_origin - + (distance.y * content_size.y) / scrollbar_size.y) - .clamp(0., range); - } - }; - } + match scrollbar.orientation { + ControlOrientation::Horizontal => { + let range = (content_size.x - visible_size.x).max(0.); + scroll_pos.x = (drag.drag_origin + + (distance.x * content_size.x) / scrollbar_size.x) + .clamp(0., range); + } + ControlOrientation::Vertical => { + let range = (content_size.y - visible_size.y).max(0.); + scroll_pos.y = (drag.drag_origin + + (distance.y * content_size.y) / scrollbar_size.y) + .clamp(0., range); + } + }; } } } diff --git a/crates/bevy_core_widgets/src/core_slider.rs b/crates/bevy_core_widgets/src/core_slider.rs index 43a691f5b7be5..d55a8d4614372 100644 --- a/crates/bevy_core_widgets/src/core_slider.rs +++ b/crates/bevy_core_widgets/src/core_slider.rs @@ -13,6 +13,7 @@ use bevy_ecs::{ component::Component, observer::On, query::With, + reflect::ReflectComponent, system::{Commands, Query}, }; use bevy_input::keyboard::{KeyCode, KeyboardInput}; @@ -21,13 +22,15 @@ use bevy_input_focus::FocusedInput; use bevy_log::warn_once; use bevy_math::ops; use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer, Press}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_ui::{ComputedNode, ComputedNodeTarget, InteractionDisabled, UiGlobalTransform, UiScale}; use crate::{Callback, Notify, ValueChange}; use bevy_ecs::template::GetTemplate; /// Defines how the slider should behave when you click on the track (not the thumb). -#[derive(Debug, Default, PartialEq, Clone, Copy)] +#[derive(Debug, Default, PartialEq, Clone, Copy, Reflect)] +#[reflect(Clone, PartialEq, Default)] pub enum TrackClick { /// Clicking on the track lets you drag to edit the value, just like clicking on the thumb. #[default] @@ -86,7 +89,7 @@ pub struct CoreSlider { } /// Marker component that identifies which descendant element is the slider thumb. -#[derive(Component, Debug, Default)] +#[derive(Component, Debug, Default, Clone)] pub struct CoreSliderThumb; /// A component which stores the current value of the slider. @@ -182,6 +185,8 @@ impl Default for SliderRange { /// shortcuts. Defaults to 1.0. #[derive(Component, Debug, PartialEq, Clone)] #[component(immutable)] +#[derive(Reflect)] +#[reflect(Component)] pub struct SliderStep(pub f32); impl Default for SliderStep { @@ -199,7 +204,8 @@ impl Default for SliderStep { /// The value in this component represents the number of decimal places of desired precision, so a /// value of 2 would round to the nearest 1/100th. A value of -3 would round to the nearest /// thousand. -#[derive(Component, Debug, Default, Clone, Copy)] +#[derive(Component, Debug, Default, Clone, Copy, Reflect)] +#[reflect(Component, Default)] pub struct SliderPrecision(pub i32); impl SliderPrecision { @@ -210,7 +216,8 @@ impl SliderPrecision { } /// Component used to manage the state of a slider during dragging. -#[derive(Component, Default)] +#[derive(Component, Default, Reflect)] +#[reflect(Component)] pub struct CoreSliderDragState { /// Whether the slider is currently being dragged. pub dragging: bool, diff --git a/crates/bevy_derive/src/bevy_main.rs b/crates/bevy_derive/src/bevy_main.rs index 6481823ad474a..5b40a0f91f4cf 100644 --- a/crates/bevy_derive/src/bevy_main.rs +++ b/crates/bevy_derive/src/bevy_main.rs @@ -15,8 +15,8 @@ pub fn bevy_main(_attr: TokenStream, item: TokenStream) -> TokenStream { // guarantee required from the caller. #[unsafe(no_mangle)] #[cfg(target_os = "android")] - fn android_main(android_app: bevy::window::android_activity::AndroidApp) { - let _ = bevy::window::ANDROID_APP.set(android_app); + fn android_main(android_app: bevy::android::android_activity::AndroidApp) { + let _ = bevy::android::ANDROID_APP.set(android_app); main(); } diff --git a/crates/bevy_dev_tools/src/fps_overlay.rs b/crates/bevy_dev_tools/src/fps_overlay.rs index 435a0d2faf0c6..aa66c97560632 100644 --- a/crates/bevy_dev_tools/src/fps_overlay.rs +++ b/crates/bevy_dev_tools/src/fps_overlay.rs @@ -212,10 +212,10 @@ fn update_text( if *time_since_rerender >= config.refresh_interval { *time_since_rerender = Duration::ZERO; for entity in &query { - if let Some(fps) = diagnostic.get(&FrameTimeDiagnosticsPlugin::FPS) { - if let Some(value) = fps.smoothed() { - *writer.text(entity, 1) = format!("{value:.2}"); - } + if let Some(fps) = diagnostic.get(&FrameTimeDiagnosticsPlugin::FPS) + && let Some(value) = fps.smoothed() + { + *writer.text(entity, 1) = format!("{value:.2}"); } } } diff --git a/crates/bevy_dev_tools/src/frame_time_graph/mod.rs b/crates/bevy_dev_tools/src/frame_time_graph/mod.rs index 32f6c200061b9..ed79bddc27403 100644 --- a/crates/bevy_dev_tools/src/frame_time_graph/mod.rs +++ b/crates/bevy_dev_tools/src/frame_time_graph/mod.rs @@ -109,6 +109,6 @@ fn update_frame_time_values( for (_, material) in frame_time_graph_materials.iter_mut() { let buffer = buffers.get_mut(&material.values).unwrap(); - buffer.set_data(frame_times.clone().as_slice()); + buffer.set_data(frame_times.clone()); } } diff --git a/crates/bevy_diagnostic/Cargo.toml b/crates/bevy_diagnostic/Cargo.toml index a1803151fcfff..5abd1fe7a7fe7 100644 --- a/crates/bevy_diagnostic/Cargo.toml +++ b/crates/bevy_diagnostic/Cargo.toml @@ -38,7 +38,6 @@ std = [ "bevy_app/std", "bevy_platform/std", "bevy_time/std", - "bevy_tasks/std", ] ## `critical-section` provides the building blocks for synchronization primitives @@ -48,7 +47,6 @@ critical-section = [ "bevy_app/critical-section", "bevy_platform/critical-section", "bevy_time/critical-section", - "bevy_tasks/critical-section", ] [dependencies] diff --git a/crates/bevy_diagnostic/src/diagnostic.rs b/crates/bevy_diagnostic/src/diagnostic.rs index 1f67d5220aae9..dd1e3576431d6 100644 --- a/crates/bevy_diagnostic/src/diagnostic.rs +++ b/crates/bevy_diagnostic/src/diagnostic.rs @@ -149,12 +149,11 @@ impl Diagnostic { } if self.max_history_length > 1 { - if self.history.len() >= self.max_history_length { - if let Some(removed_diagnostic) = self.history.pop_front() { - if !removed_diagnostic.value.is_nan() { - self.sum -= removed_diagnostic.value; - } - } + if self.history.len() >= self.max_history_length + && let Some(removed_diagnostic) = self.history.pop_front() + && !removed_diagnostic.value.is_nan() + { + self.sum -= removed_diagnostic.value; } if measurement.value.is_finite() { @@ -273,13 +272,9 @@ impl Diagnostic { return None; } - if let Some(newest) = self.history.back() { - if let Some(oldest) = self.history.front() { - return Some(newest.time.duration_since(oldest.time)); - } - } - - None + let newest = self.history.back()?; + let oldest = self.history.front()?; + Some(newest.time.duration_since(oldest.time)) } /// Return the maximum number of elements for this diagnostic. diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index f6888ea7230c8..43435351081ae 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -128,10 +128,10 @@ impl LogDiagnosticsPlugin { ) { if let Some(filter) = &state.filter { for path in filter.iter() { - if let Some(diagnostic) = diagnostics.get(path) { - if diagnostic.is_enabled { - callback(diagnostic); - } + if let Some(diagnostic) = diagnostics.get(path) + && diagnostic.is_enabled + { + callback(diagnostic); } } } else { diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index b0c050cd8afd8..75a0910d4b96e 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -27,6 +27,7 @@ bevy_reflect = ["dep:bevy_reflect"] ## Extends reflection support to functions. reflect_functions = ["bevy_reflect", "bevy_reflect/functions"] +reflect_auto_register = ["bevy_reflect", "bevy_reflect/auto_register"] ## Enables automatic backtrace capturing in BevyError backtrace = ["std"] @@ -61,7 +62,6 @@ async_executor = ["std", "bevy_tasks/async_executor"] ## supported platforms. std = [ "bevy_reflect?/std", - "bevy_tasks/std", "bevy_utils/parallel", "bevy_utils/std", "bitflags/std", @@ -78,7 +78,6 @@ std = [ ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. critical-section = [ - "bevy_tasks/critical-section", "bevy_platform/critical-section", "bevy_reflect?/critical-section", ] @@ -132,7 +131,7 @@ concurrent-queue = { version = "2.5.0", default-features = false, features = [ ] } [dev-dependencies] -rand = "0.8" +rand = "0.9" static_assertions = "1.1.0" serde_test = "1.0" diff --git a/crates/bevy_ecs/examples/change_detection.rs b/crates/bevy_ecs/examples/change_detection.rs index 42611e57e1978..e186f9b6e3365 100644 --- a/crates/bevy_ecs/examples/change_detection.rs +++ b/crates/bevy_ecs/examples/change_detection.rs @@ -66,7 +66,7 @@ enum SimulationSystems { // The entity will start with an age of 0 frames // If an entity gets spawned, we increase the counter in the EntityCounter resource fn spawn_entities(mut commands: Commands, mut entity_counter: ResMut) { - if rand::thread_rng().gen_bool(0.6) { + if rand::rng().random_bool(0.6) { let entity_id = commands.spawn(Age::default()).id(); println!(" spawning {entity_id:?}"); entity_counter.value += 1; diff --git a/crates/bevy_ecs/examples/resources.rs b/crates/bevy_ecs/examples/resources.rs index bb079d249d147..25eddfff57209 100644 --- a/crates/bevy_ecs/examples/resources.rs +++ b/crates/bevy_ecs/examples/resources.rs @@ -37,7 +37,7 @@ struct Counter { } fn increase_counter(mut counter: ResMut) { - if rand::thread_rng().gen_bool(0.5) { + if rand::rng().random_bool(0.5) { counter.value += 1; println!(" Increased counter value"); } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 744c7fc0ccddc..ffd4aef82c613 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -455,7 +455,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { } } - fn init_access(state: &Self::State, system_meta: &mut #path::system::SystemMeta, component_access_set: &mut #path::query::FilteredAccessSet<#path::component::ComponentId>, world: &mut #path::world::World) { + fn init_access(state: &Self::State, system_meta: &mut #path::system::SystemMeta, component_access_set: &mut #path::query::FilteredAccessSet, world: &mut #path::world::World) { <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_access(&state.state, system_meta, component_access_set, world); } diff --git a/crates/bevy_ecs/macros/src/query_data.rs b/crates/bevy_ecs/macros/src/query_data.rs index 12d9c2bf1c1fa..e558e2e634985 100644 --- a/crates/bevy_ecs/macros/src/query_data.rs +++ b/crates/bevy_ecs/macros/src/query_data.rs @@ -281,8 +281,8 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { fn provide_extra_access( state: &mut Self::State, - access: &mut #path::query::Access<#path::component::ComponentId>, - available_access: &#path::query::Access<#path::component::ComponentId>, + access: &mut #path::query::Access, + available_access: &#path::query::Access, ) { #(<#field_types>::provide_extra_access(&mut state.#named_field_idents, access, available_access);)* } @@ -339,8 +339,8 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { fn provide_extra_access( state: &mut Self::State, - access: &mut #path::query::Access<#path::component::ComponentId>, - available_access: &#path::query::Access<#path::component::ComponentId>, + access: &mut #path::query::Access, + available_access: &#path::query::Access, ) { #(<#field_types>::provide_extra_access(&mut state.#named_field_idents, access, available_access);)* } diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index 5a7d164b8021d..ccd01c425ecab 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -152,7 +152,7 @@ pub(crate) fn world_query_impl( #(<#field_types>::set_table(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _table);)* } - fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { + fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess) { #( <#field_types>::update_component_access(&state.#named_field_idents, _access); )* } diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 1ba4f23db2721..af42d9c44176d 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -1051,7 +1051,7 @@ impl<'w> MutUntyped<'w> { /// Returns a [`MutUntyped`] with a smaller lifetime. /// This is useful if you have `&mut MutUntyped`, but you need a `MutUntyped`. #[inline] - pub fn reborrow(&mut self) -> MutUntyped { + pub fn reborrow(&mut self) -> MutUntyped<'_> { MutUntyped { value: self.value.reborrow(), ticks: TicksMut { diff --git a/crates/bevy_ecs/src/component/clone.rs b/crates/bevy_ecs/src/component/clone.rs index ff90255e2e6b7..3a3784350e36a 100644 --- a/crates/bevy_ecs/src/component/clone.rs +++ b/crates/bevy_ecs/src/component/clone.rs @@ -7,7 +7,7 @@ use crate::entity::{ComponentCloneCtx, SourceComponent}; pub type ComponentCloneFn = fn(&SourceComponent, &mut ComponentCloneCtx); /// The clone behavior to use when cloning or moving a [`Component`]. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default)] pub enum ComponentCloneBehavior { /// Uses the default behavior (which is passed to [`ComponentCloneBehavior::resolve`]) #[default] diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 2ba536a114b21..b2e78d79c1084 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -445,7 +445,7 @@ impl EntityCloner { /// explicitly denied, for example by using the [`deny`](EntityClonerBuilder::deny) method. /// /// Required components are not considered by denied components and must be explicitly denied as well if desired. - pub fn build_opt_out(world: &mut World) -> EntityClonerBuilder { + pub fn build_opt_out(world: &mut World) -> EntityClonerBuilder<'_, OptOut> { EntityClonerBuilder { world, filter: Default::default(), @@ -461,7 +461,7 @@ impl EntityCloner { /// Components allowed to be cloned through this builder would also allow their required components, /// which will be cloned from the source entity only if the target entity does not contain them already. /// To skip adding required components see [`without_required_components`](EntityClonerBuilder::without_required_components). - pub fn build_opt_in(world: &mut World) -> EntityClonerBuilder { + pub fn build_opt_in(world: &mut World) -> EntityClonerBuilder<'_, OptIn> { EntityClonerBuilder { world, filter: Default::default(), diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index e17b78d0f1351..125c8473cc664 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -776,7 +776,7 @@ impl Entities { clippy::unnecessary_fallible_conversions, reason = "`IdCursor::try_from` may fail on 32-bit platforms." )] - pub fn reserve_entities(&self, count: u32) -> ReserveEntitiesIterator { + pub fn reserve_entities(&self, count: u32) -> ReserveEntitiesIterator<'_> { // Use one atomic subtract to grab a range of new IDs. The range might be // entirely nonnegative, meaning all IDs come from the freelist, or entirely // negative, meaning they are all new IDs to allocate, or a mix of both. diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index 652cd1fdb28a3..d60f3ab08bdf6 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -241,7 +241,7 @@ impl DefaultQueryFilters { } /// Modifies the provided [`FilteredAccess`] to include the filters from this [`DefaultQueryFilters`]. - pub(super) fn modify_access(&self, component_access: &mut FilteredAccess) { + pub(super) fn modify_access(&self, component_access: &mut FilteredAccess) { for component_id in self.disabling_ids() { if !component_access.contains(component_id) { component_access.and_without(component_id); @@ -276,7 +276,7 @@ mod tests { filters.register_disabling_component(ComponentId::new(1)); // A component access with an unrelated component - let mut component_access = FilteredAccess::::default(); + let mut component_access = FilteredAccess::default(); component_access .access_mut() .add_component_read(ComponentId::new(2)); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index cedce43fde003..9a37baf7d4f2e 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -164,7 +164,7 @@ mod tests { use crate::{ bundle::Bundle, change_detection::Ref, - component::{Component, ComponentId}, + component::Component, entity::{Entity, EntityMapper}, entity_disabling::DefaultQueryFilters, prelude::Or, @@ -1540,7 +1540,7 @@ mod tests { world.remove_resource::(); let query = world.query_filtered::<&mut A, Changed>(); - let mut expected = FilteredAccess::::default(); + let mut expected = FilteredAccess::default(); let a_id = world.components.get_id(TypeId::of::()).unwrap(); let b_id = world.components.get_id(TypeId::of::()).unwrap(); expected.add_component_write(a_id); diff --git a/crates/bevy_ecs/src/lifecycle.rs b/crates/bevy_ecs/src/lifecycle.rs index 62a731499a13f..e45f9eefa0113 100644 --- a/crates/bevy_ecs/src/lifecycle.rs +++ b/crates/bevy_ecs/src/lifecycle.rs @@ -632,7 +632,7 @@ unsafe impl<'a> SystemParam for &'a RemovedComponentEvents { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index dd43cb090d181..40a4ee3db6a06 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -182,7 +182,7 @@ impl World { pub fn add_observer( &mut self, system: impl IntoObserverSystem, - ) -> EntityWorldMut { + ) -> EntityWorldMut<'_> { self.spawn(Observer::new(system)) } @@ -515,9 +515,6 @@ mod tests { #[derive(Component)] struct B; - #[derive(Component)] - struct C; - #[derive(Component)] #[component(storage = "SparseSet")] struct S; diff --git a/crates/bevy_ecs/src/observer/system_param.rs b/crates/bevy_ecs/src/observer/system_param.rs index 6e165ccd6df67..6637a0ce2472c 100644 --- a/crates/bevy_ecs/src/observer/system_param.rs +++ b/crates/bevy_ecs/src/observer/system_param.rs @@ -64,7 +64,7 @@ impl<'w, E, B: Bundle> On<'w, E, B> { } /// Returns a pointer to the triggered event. - pub fn event_ptr(&self) -> Ptr { + pub fn event_ptr(&self) -> Ptr<'_> { Ptr::from(&self.event) } diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 51ef320f38f7e..346c131457bb7 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -1,17 +1,16 @@ use crate::component::ComponentId; -use crate::storage::SparseSetIndex; use crate::world::World; use alloc::{format, string::String, vec, vec::Vec}; -use core::{fmt, fmt::Debug, marker::PhantomData}; +use core::{fmt, fmt::Debug}; use derive_more::From; use fixedbitset::FixedBitSet; use thiserror::Error; /// A wrapper struct to make Debug representations of [`FixedBitSet`] easier -/// to read, when used to store [`SparseSetIndex`]. +/// to read. /// /// Instead of the raw integer representation of the `FixedBitSet`, the list of -/// `T` valid for [`SparseSetIndex`] is shown. +/// indexes are shown. /// /// Normal `FixedBitSet` `Debug` output: /// ```text @@ -22,27 +21,21 @@ use thiserror::Error; /// the set. With `FormattedBitSet`, we convert the present set entries into /// what they stand for, it is much clearer what is going on: /// ```text -/// read_and_writes: [ ComponentId(5), ComponentId(7) ] +/// read_and_writes: [ 5, 7 ] /// ``` -struct FormattedBitSet<'a, T: SparseSetIndex> { +struct FormattedBitSet<'a> { bit_set: &'a FixedBitSet, - _marker: PhantomData, } -impl<'a, T: SparseSetIndex> FormattedBitSet<'a, T> { +impl<'a> FormattedBitSet<'a> { fn new(bit_set: &'a FixedBitSet) -> Self { - Self { - bit_set, - _marker: PhantomData, - } + Self { bit_set } } } -impl<'a, T: SparseSetIndex + Debug> Debug for FormattedBitSet<'a, T> { +impl<'a> Debug for FormattedBitSet<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list() - .entries(self.bit_set.ones().map(T::get_sparse_set_index)) - .finish() + f.debug_list().entries(self.bit_set.ones()).finish() } } @@ -50,8 +43,8 @@ impl<'a, T: SparseSetIndex + Debug> Debug for FormattedBitSet<'a, T> { /// /// Used internally to ensure soundness during system initialization and execution. /// See the [`is_compatible`](Access::is_compatible) and [`get_conflicts`](Access::get_conflicts) functions. -#[derive(Eq, PartialEq)] -pub struct Access { +#[derive(Eq, PartialEq, Default)] +pub struct Access { /// All accessed components, or forbidden components if /// `Self::component_read_and_writes_inverted` is set. component_read_and_writes: FixedBitSet, @@ -76,11 +69,10 @@ pub struct Access { writes_all_resources: bool, // Components that are not accessed, but whose presence in an archetype affect query results. archetypal: FixedBitSet, - marker: PhantomData, } // This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. -impl Clone for Access { +impl Clone for Access { fn clone(&self) -> Self { Self { component_read_and_writes: self.component_read_and_writes.clone(), @@ -92,7 +84,6 @@ impl Clone for Access { reads_all_resources: self.reads_all_resources, writes_all_resources: self.writes_all_resources, archetypal: self.archetypal.clone(), - marker: PhantomData, } } @@ -111,24 +102,24 @@ impl Clone for Access { } } -impl Debug for Access { +impl Debug for Access { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Access") .field( "component_read_and_writes", - &FormattedBitSet::::new(&self.component_read_and_writes), + &FormattedBitSet::new(&self.component_read_and_writes), ) .field( "component_writes", - &FormattedBitSet::::new(&self.component_writes), + &FormattedBitSet::new(&self.component_writes), ) .field( "resource_read_and_writes", - &FormattedBitSet::::new(&self.resource_read_and_writes), + &FormattedBitSet::new(&self.resource_read_and_writes), ) .field( "resource_writes", - &FormattedBitSet::::new(&self.resource_writes), + &FormattedBitSet::new(&self.resource_writes), ) .field( "component_read_and_writes_inverted", @@ -137,18 +128,12 @@ impl Debug for Access { .field("component_writes_inverted", &self.component_writes_inverted) .field("reads_all_resources", &self.reads_all_resources) .field("writes_all_resources", &self.writes_all_resources) - .field("archetypal", &FormattedBitSet::::new(&self.archetypal)) + .field("archetypal", &FormattedBitSet::new(&self.archetypal)) .finish() } } -impl Default for Access { - fn default() -> Self { - Self::new() - } -} - -impl Access { +impl Access { /// Creates an empty [`Access`] collection. pub const fn new() -> Self { Self { @@ -161,7 +146,6 @@ impl Access { resource_read_and_writes: FixedBitSet::new(), resource_writes: FixedBitSet::new(), archetypal: FixedBitSet::new(), - marker: PhantomData, } } @@ -208,30 +192,27 @@ impl Access { } /// Adds access to the component given by `index`. - pub fn add_component_read(&mut self, index: T) { - let sparse_set_index = index.sparse_set_index(); + pub fn add_component_read(&mut self, index: ComponentId) { + let sparse_set_index = index.index(); self.add_component_sparse_set_index_read(sparse_set_index); } /// Adds exclusive access to the component given by `index`. - pub fn add_component_write(&mut self, index: T) { - let sparse_set_index = index.sparse_set_index(); + pub fn add_component_write(&mut self, index: ComponentId) { + let sparse_set_index = index.index(); self.add_component_sparse_set_index_read(sparse_set_index); self.add_component_sparse_set_index_write(sparse_set_index); } /// Adds access to the resource given by `index`. - pub fn add_resource_read(&mut self, index: T) { - self.resource_read_and_writes - .grow_and_insert(index.sparse_set_index()); + pub fn add_resource_read(&mut self, index: ComponentId) { + self.resource_read_and_writes.grow_and_insert(index.index()); } /// Adds exclusive access to the resource given by `index`. - pub fn add_resource_write(&mut self, index: T) { - self.resource_read_and_writes - .grow_and_insert(index.sparse_set_index()); - self.resource_writes - .grow_and_insert(index.sparse_set_index()); + pub fn add_resource_write(&mut self, index: ComponentId) { + self.resource_read_and_writes.grow_and_insert(index.index()); + self.resource_writes.grow_and_insert(index.index()); } fn remove_component_sparse_set_index_read(&mut self, index: usize) { @@ -258,8 +239,8 @@ impl Access { /// can't replace a call to `remove_component_read` followed by a call to /// `extend` with a call to `extend` followed by a call to /// `remove_component_read`. - pub fn remove_component_read(&mut self, index: T) { - let sparse_set_index = index.sparse_set_index(); + pub fn remove_component_read(&mut self, index: ComponentId) { + let sparse_set_index = index.index(); self.remove_component_sparse_set_index_write(sparse_set_index); self.remove_component_sparse_set_index_read(sparse_set_index); } @@ -272,8 +253,8 @@ impl Access { /// can't replace a call to `remove_component_write` followed by a call to /// `extend` with a call to `extend` followed by a call to /// `remove_component_write`. - pub fn remove_component_write(&mut self, index: T) { - let sparse_set_index = index.sparse_set_index(); + pub fn remove_component_write(&mut self, index: ComponentId) { + let sparse_set_index = index.index(); self.remove_component_sparse_set_index_write(sparse_set_index); } @@ -286,16 +267,14 @@ impl Access { /// /// [`Has`]: crate::query::Has /// [`Allows`]: crate::query::filter::Allows - pub fn add_archetypal(&mut self, index: T) { - self.archetypal.grow_and_insert(index.sparse_set_index()); + pub fn add_archetypal(&mut self, index: ComponentId) { + self.archetypal.grow_and_insert(index.index()); } /// Returns `true` if this can access the component given by `index`. - pub fn has_component_read(&self, index: T) -> bool { + pub fn has_component_read(&self, index: ComponentId) -> bool { self.component_read_and_writes_inverted - ^ self - .component_read_and_writes - .contains(index.sparse_set_index()) + ^ self.component_read_and_writes.contains(index.index()) } /// Returns `true` if this can access any component. @@ -304,8 +283,8 @@ impl Access { } /// Returns `true` if this can exclusively access the component given by `index`. - pub fn has_component_write(&self, index: T) -> bool { - self.component_writes_inverted ^ self.component_writes.contains(index.sparse_set_index()) + pub fn has_component_write(&self, index: ComponentId) -> bool { + self.component_writes_inverted ^ self.component_writes.contains(index.index()) } /// Returns `true` if this accesses any component mutably. @@ -314,11 +293,8 @@ impl Access { } /// Returns `true` if this can access the resource given by `index`. - pub fn has_resource_read(&self, index: T) -> bool { - self.reads_all_resources - || self - .resource_read_and_writes - .contains(index.sparse_set_index()) + pub fn has_resource_read(&self, index: ComponentId) -> bool { + self.reads_all_resources || self.resource_read_and_writes.contains(index.index()) } /// Returns `true` if this can access any resource. @@ -327,8 +303,8 @@ impl Access { } /// Returns `true` if this can exclusively access the resource given by `index`. - pub fn has_resource_write(&self, index: T) -> bool { - self.writes_all_resources || self.resource_writes.contains(index.sparse_set_index()) + pub fn has_resource_write(&self, index: ComponentId) -> bool { + self.writes_all_resources || self.resource_writes.contains(index.index()) } /// Returns `true` if this accesses any resource mutably. @@ -354,8 +330,8 @@ impl Access { /// Currently, this is only used for [`Has`]. /// /// [`Has`]: crate::query::Has - pub fn has_archetypal(&self, index: T) -> bool { - self.archetypal.contains(index.sparse_set_index()) + pub fn has_archetypal(&self, index: ComponentId) -> bool { + self.archetypal.contains(index.index()) } /// Sets this as having access to all components (i.e. `EntityRef`). @@ -455,7 +431,7 @@ impl Access { } /// Adds all access from `other`. - pub fn extend(&mut self, other: &Access) { + pub fn extend(&mut self, other: &Access) { invertible_union_with( &mut self.component_read_and_writes, &mut self.component_read_and_writes_inverted, @@ -480,7 +456,7 @@ impl Access { /// Removes any access from `self` that would conflict with `other`. /// This removes any reads and writes for any component written by `other`, /// and removes any writes for any component read by `other`. - pub fn remove_conflicting_access(&mut self, other: &Access) { + pub fn remove_conflicting_access(&mut self, other: &Access) { invertible_difference_with( &mut self.component_read_and_writes, &mut self.component_read_and_writes_inverted, @@ -513,7 +489,7 @@ impl Access { /// /// [`Access`] instances are incompatible if one can write /// an element that the other can read or write. - pub fn is_components_compatible(&self, other: &Access) -> bool { + pub fn is_components_compatible(&self, other: &Access) -> bool { // We have a conflict if we write and they read or write, or if they // write and we read or write. for ( @@ -563,7 +539,7 @@ impl Access { /// /// [`Access`] instances are incompatible if one can write /// an element that the other can read or write. - pub fn is_resources_compatible(&self, other: &Access) -> bool { + pub fn is_resources_compatible(&self, other: &Access) -> bool { if self.writes_all_resources { return !other.has_any_resource_read(); } @@ -591,13 +567,13 @@ impl Access { /// /// [`Access`] instances are incompatible if one can write /// an element that the other can read or write. - pub fn is_compatible(&self, other: &Access) -> bool { + pub fn is_compatible(&self, other: &Access) -> bool { self.is_components_compatible(other) && self.is_resources_compatible(other) } /// Returns `true` if the set's component access is a subset of another, i.e. `other`'s component access /// contains at least all the values in `self`. - pub fn is_subset_components(&self, other: &Access) -> bool { + pub fn is_subset_components(&self, other: &Access) -> bool { for ( our_components, their_components, @@ -644,7 +620,7 @@ impl Access { /// Returns `true` if the set's resource access is a subset of another, i.e. `other`'s resource access /// contains at least all the values in `self`. - pub fn is_subset_resources(&self, other: &Access) -> bool { + pub fn is_subset_resources(&self, other: &Access) -> bool { if self.writes_all_resources { return other.writes_all_resources; } @@ -668,11 +644,11 @@ impl Access { /// Returns `true` if the set is a subset of another, i.e. `other` contains /// at least all the values in `self`. - pub fn is_subset(&self, other: &Access) -> bool { + pub fn is_subset(&self, other: &Access) -> bool { self.is_subset_components(other) && self.is_subset_resources(other) } - fn get_component_conflicts(&self, other: &Access) -> AccessConflicts { + fn get_component_conflicts(&self, other: &Access) -> AccessConflicts { let mut conflicts = FixedBitSet::new(); // We have a conflict if we write and they read or write, or if they @@ -712,7 +688,7 @@ impl Access { } /// Returns a vector of elements that the access and `other` cannot access at the same time. - pub fn get_conflicts(&self, other: &Access) -> AccessConflicts { + pub fn get_conflicts(&self, other: &Access) -> AccessConflicts { let mut conflicts = match self.get_component_conflicts(other) { AccessConflicts::All => return AccessConflicts::All, AccessConflicts::Individual(conflicts) => conflicts, @@ -751,22 +727,20 @@ impl Access { } /// Returns the indices of the resources this has access to. - pub fn resource_reads_and_writes(&self) -> impl Iterator + '_ { - self.resource_read_and_writes - .ones() - .map(T::get_sparse_set_index) + pub fn resource_reads_and_writes(&self) -> impl Iterator + '_ { + self.resource_read_and_writes.ones().map(ComponentId::new) } /// Returns the indices of the resources this has non-exclusive access to. - pub fn resource_reads(&self) -> impl Iterator + '_ { + pub fn resource_reads(&self) -> impl Iterator + '_ { self.resource_read_and_writes .difference(&self.resource_writes) - .map(T::get_sparse_set_index) + .map(ComponentId::new) } /// Returns the indices of the resources this has exclusive access to. - pub fn resource_writes(&self) -> impl Iterator + '_ { - self.resource_writes.ones().map(T::get_sparse_set_index) + pub fn resource_writes(&self) -> impl Iterator + '_ { + self.resource_writes.ones().map(ComponentId::new) } /// Returns the indices of the components that this has an archetypal access to. @@ -777,8 +751,8 @@ impl Access { /// Currently, this is only used for [`Has`]. /// /// [`Has`]: crate::query::Has - pub fn archetypal(&self) -> impl Iterator + '_ { - self.archetypal.ones().map(T::get_sparse_set_index) + pub fn archetypal(&self) -> impl Iterator + '_ { + self.archetypal.ones().map(ComponentId::new) } /// Returns an iterator over the component IDs and their [`ComponentAccessKind`]. @@ -791,11 +765,12 @@ impl Access { /// /// ```rust /// # use bevy_ecs::query::{Access, ComponentAccessKind}; - /// let mut access = Access::::default(); + /// # use bevy_ecs::component::ComponentId; + /// let mut access = Access::default(); /// - /// access.add_component_read(1); - /// access.add_component_write(2); - /// access.add_archetypal(3); + /// access.add_component_read(ComponentId::new(1)); + /// access.add_component_write(ComponentId::new(2)); + /// access.add_archetypal(ComponentId::new(3)); /// /// let result = access /// .try_iter_component_access() @@ -804,15 +779,15 @@ impl Access { /// assert_eq!( /// result, /// Ok(vec![ - /// ComponentAccessKind::Shared(1), - /// ComponentAccessKind::Exclusive(2), - /// ComponentAccessKind::Archetypal(3), + /// ComponentAccessKind::Shared(ComponentId::new(1)), + /// ComponentAccessKind::Exclusive(ComponentId::new(2)), + /// ComponentAccessKind::Archetypal(ComponentId::new(3)), /// ]), /// ); /// ``` pub fn try_iter_component_access( &self, - ) -> Result> + '_, UnboundedAccessError> { + ) -> Result + '_, UnboundedAccessError> { // component_writes_inverted is only ever true when component_read_and_writes_inverted is // also true. Therefore it is sufficient to check just component_read_and_writes_inverted. if self.component_read_and_writes_inverted { @@ -823,7 +798,7 @@ impl Access { } let reads_and_writes = self.component_read_and_writes.ones().map(|index| { - let sparse_index = T::get_sparse_set_index(index); + let sparse_index = ComponentId::new(index); if self.component_writes.contains(index) { ComponentAccessKind::Exclusive(sparse_index) @@ -839,7 +814,7 @@ impl Access { !self.component_writes.contains(index) && !self.component_read_and_writes.contains(index) }) - .map(|index| ComponentAccessKind::Archetypal(T::get_sparse_set_index(index))); + .map(|index| ComponentAccessKind::Archetypal(ComponentId::new(index))); Ok(reads_and_writes.chain(archetypal)) } @@ -909,18 +884,18 @@ pub struct UnboundedAccessError { /// Describes the level of access for a particular component as defined in an [`Access`]. #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] -pub enum ComponentAccessKind { +pub enum ComponentAccessKind { /// Archetypical access, such as `Has`. - Archetypal(T), + Archetypal(ComponentId), /// Shared access, such as `&Foo`. - Shared(T), + Shared(ComponentId), /// Exclusive access, such as `&mut Foo`. - Exclusive(T), + Exclusive(ComponentId), } -impl ComponentAccessKind { +impl ComponentAccessKind { /// Gets the index of this `ComponentAccessKind`. - pub fn index(&self) -> &T { + pub fn index(&self) -> &ComponentId { let (Self::Archetypal(value) | Self::Shared(value) | Self::Exclusive(value)) = self; value } @@ -947,16 +922,16 @@ impl ComponentAccessKind { /// /// See comments the [`WorldQuery`](super::WorldQuery) impls of [`AnyOf`](super::AnyOf)/`Option`/[`Or`](super::Or) for more information. #[derive(Debug, Eq, PartialEq)] -pub struct FilteredAccess { - pub(crate) access: Access, +pub struct FilteredAccess { + pub(crate) access: Access, pub(crate) required: FixedBitSet, // An array of filter sets to express `With` or `Without` clauses in disjunctive normal form, for example: `Or<(With, With)>`. // Filters like `(With, Or<(With, Without)>` are expanded into `Or<((With, With), (With, Without))>`. - pub(crate) filter_sets: Vec>, + pub(crate) filter_sets: Vec, } // This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. -impl Clone for FilteredAccess { +impl Clone for FilteredAccess { fn clone(&self) -> Self { Self { access: self.access.clone(), @@ -972,15 +947,15 @@ impl Clone for FilteredAccess { } } -impl Default for FilteredAccess { +impl Default for FilteredAccess { fn default() -> Self { Self::matches_everything() } } -impl From> for FilteredAccessSet { - fn from(filtered_access: FilteredAccess) -> Self { - let mut base = FilteredAccessSet::::default(); +impl From for FilteredAccessSet { + fn from(filtered_access: FilteredAccess) -> Self { + let mut base = FilteredAccessSet::default(); base.add(filtered_access); base } @@ -1026,7 +1001,7 @@ impl AccessConflicts { "{}", world .components - .get_name(ComponentId::get_sparse_set_index(index)) + .get_name(ComponentId::new(index)) .unwrap() .shortname() ) @@ -1042,13 +1017,13 @@ impl AccessConflicts { } } -impl From> for AccessConflicts { - fn from(value: Vec) -> Self { - Self::Individual(value.iter().map(T::sparse_set_index).collect()) +impl From> for AccessConflicts { + fn from(value: Vec) -> Self { + Self::Individual(value.iter().map(|c| c.index()).collect()) } } -impl FilteredAccess { +impl FilteredAccess { /// Returns a `FilteredAccess` which has no access and matches everything. /// This is the equivalent of a `TRUE` logic atom. pub fn matches_everything() -> Self { @@ -1071,51 +1046,51 @@ impl FilteredAccess { /// Returns a reference to the underlying unfiltered access. #[inline] - pub fn access(&self) -> &Access { + pub fn access(&self) -> &Access { &self.access } /// Returns a mutable reference to the underlying unfiltered access. #[inline] - pub fn access_mut(&mut self) -> &mut Access { + pub fn access_mut(&mut self) -> &mut Access { &mut self.access } /// Adds access to the component given by `index`. - pub fn add_component_read(&mut self, index: T) { - self.access.add_component_read(index.clone()); - self.add_required(index.clone()); + pub fn add_component_read(&mut self, index: ComponentId) { + self.access.add_component_read(index); + self.add_required(index); self.and_with(index); } /// Adds exclusive access to the component given by `index`. - pub fn add_component_write(&mut self, index: T) { - self.access.add_component_write(index.clone()); - self.add_required(index.clone()); + pub fn add_component_write(&mut self, index: ComponentId) { + self.access.add_component_write(index); + self.add_required(index); self.and_with(index); } /// Adds access to the resource given by `index`. - pub fn add_resource_read(&mut self, index: T) { - self.access.add_resource_read(index.clone()); + pub fn add_resource_read(&mut self, index: ComponentId) { + self.access.add_resource_read(index); } /// Adds exclusive access to the resource given by `index`. - pub fn add_resource_write(&mut self, index: T) { - self.access.add_resource_write(index.clone()); + pub fn add_resource_write(&mut self, index: ComponentId) { + self.access.add_resource_write(index); } - fn add_required(&mut self, index: T) { - self.required.grow_and_insert(index.sparse_set_index()); + fn add_required(&mut self, index: ComponentId) { + self.required.grow_and_insert(index.index()); } /// Adds a `With` filter: corresponds to a conjunction (AND) operation. /// /// Suppose we begin with `Or<(With, With)>`, which is represented by an array of two `AccessFilter` instances. /// Adding `AND With` via this method transforms it into the equivalent of `Or<((With, With), (With, With))>`. - pub fn and_with(&mut self, index: T) { + pub fn and_with(&mut self, index: ComponentId) { for filter in &mut self.filter_sets { - filter.with.grow_and_insert(index.sparse_set_index()); + filter.with.grow_and_insert(index.index()); } } @@ -1123,9 +1098,9 @@ impl FilteredAccess { /// /// Suppose we begin with `Or<(With, With)>`, which is represented by an array of two `AccessFilter` instances. /// Adding `AND Without` via this method transforms it into the equivalent of `Or<((With, Without), (With, Without))>`. - pub fn and_without(&mut self, index: T) { + pub fn and_without(&mut self, index: ComponentId) { for filter in &mut self.filter_sets { - filter.without.grow_and_insert(index.sparse_set_index()); + filter.without.grow_and_insert(index.index()); } } @@ -1134,17 +1109,17 @@ impl FilteredAccess { /// As the underlying array of filters represents a disjunction, /// where each element (`AccessFilters`) represents a conjunction, /// we can simply append to the array. - pub fn append_or(&mut self, other: &FilteredAccess) { + pub fn append_or(&mut self, other: &FilteredAccess) { self.filter_sets.append(&mut other.filter_sets.clone()); } /// Adds all of the accesses from `other` to `self`. - pub fn extend_access(&mut self, other: &FilteredAccess) { + pub fn extend_access(&mut self, other: &FilteredAccess) { self.access.extend(&other.access); } /// Returns `true` if this and `other` can be active at the same time. - pub fn is_compatible(&self, other: &FilteredAccess) -> bool { + pub fn is_compatible(&self, other: &FilteredAccess) -> bool { // Resources are read from the world rather than the filtered archetypes, // so they must be compatible even if the filters are disjoint. if !self.access.is_resources_compatible(&other.access) { @@ -1171,7 +1146,7 @@ impl FilteredAccess { } /// Returns a vector of elements that this and `other` cannot access at the same time. - pub fn get_conflicts(&self, other: &FilteredAccess) -> AccessConflicts { + pub fn get_conflicts(&self, other: &FilteredAccess) -> AccessConflicts { if !self.is_compatible(other) { // filters are disjoint, so we can just look at the unfiltered intersection return self.access.get_conflicts(&other.access); @@ -1185,7 +1160,7 @@ impl FilteredAccess { /// /// Extending `Or<(With, Without)>` with `Or<(With, Without)>` will result in /// `Or<((With, With), (With, Without), (Without, With), (Without, Without))>`. - pub fn extend(&mut self, other: &FilteredAccess) { + pub fn extend(&mut self, other: &FilteredAccess) { self.access.extend(&other.access); self.required.union_with(&other.required); @@ -1233,50 +1208,48 @@ impl FilteredAccess { /// Returns `true` if the set is a subset of another, i.e. `other` contains /// at least all the values in `self`. - pub fn is_subset(&self, other: &FilteredAccess) -> bool { + pub fn is_subset(&self, other: &FilteredAccess) -> bool { self.required.is_subset(&other.required) && self.access().is_subset(other.access()) } /// Returns the indices of the elements that this access filters for. - pub fn with_filters(&self) -> impl Iterator + '_ { + pub fn with_filters(&self) -> impl Iterator + '_ { self.filter_sets .iter() - .flat_map(|f| f.with.ones().map(T::get_sparse_set_index)) + .flat_map(|f| f.with.ones().map(ComponentId::new)) } /// Returns the indices of the elements that this access filters out. - pub fn without_filters(&self) -> impl Iterator + '_ { + pub fn without_filters(&self) -> impl Iterator + '_ { self.filter_sets .iter() - .flat_map(|f| f.without.ones().map(T::get_sparse_set_index)) + .flat_map(|f| f.without.ones().map(ComponentId::new)) } /// Returns true if the index is used by this `FilteredAccess` in filters or archetypal access. /// This includes most ways to access a component, but notably excludes `EntityRef` and `EntityMut` /// along with anything inside `Option`. - pub fn contains(&self, index: T) -> bool { - self.access().has_archetypal(index.clone()) - || self.filter_sets.iter().any(|f| { - f.with.contains(index.sparse_set_index()) - || f.without.contains(index.sparse_set_index()) - }) + pub fn contains(&self, index: ComponentId) -> bool { + self.access().has_archetypal(index) + || self + .filter_sets + .iter() + .any(|f| f.with.contains(index.index()) || f.without.contains(index.index())) } } -#[derive(Eq, PartialEq)] -pub(crate) struct AccessFilters { +#[derive(Eq, PartialEq, Default)] +pub(crate) struct AccessFilters { pub(crate) with: FixedBitSet, pub(crate) without: FixedBitSet, - _index_type: PhantomData, } // This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. -impl Clone for AccessFilters { +impl Clone for AccessFilters { fn clone(&self) -> Self { Self { with: self.with.clone(), without: self.without.clone(), - _index_type: PhantomData, } } @@ -1286,26 +1259,16 @@ impl Clone for AccessFilters { } } -impl Debug for AccessFilters { +impl Debug for AccessFilters { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AccessFilters") - .field("with", &FormattedBitSet::::new(&self.with)) - .field("without", &FormattedBitSet::::new(&self.without)) + .field("with", &FormattedBitSet::new(&self.with)) + .field("without", &FormattedBitSet::new(&self.without)) .finish() } } -impl Default for AccessFilters { - fn default() -> Self { - Self { - with: FixedBitSet::default(), - without: FixedBitSet::default(), - _index_type: PhantomData, - } - } -} - -impl AccessFilters { +impl AccessFilters { fn is_ruled_out_by(&self, other: &Self) -> bool { // Although not technically complete, we don't consider the case when `AccessFilters`'s // `without` bitset contradicts its own `with` bitset (e.g. `(With, Without)`). @@ -1323,14 +1286,14 @@ impl AccessFilters { /// It stores multiple sets of accesses. /// - A "combined" set, which is the access of all filters in this set combined. /// - The set of access of each individual filters in this set. -#[derive(Debug, PartialEq, Eq)] -pub struct FilteredAccessSet { - combined_access: Access, - filtered_accesses: Vec>, +#[derive(Debug, PartialEq, Eq, Default)] +pub struct FilteredAccessSet { + combined_access: Access, + filtered_accesses: Vec, } // This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. -impl Clone for FilteredAccessSet { +impl Clone for FilteredAccessSet { fn clone(&self) -> Self { Self { combined_access: self.combined_access.clone(), @@ -1344,7 +1307,7 @@ impl Clone for FilteredAccessSet { } } -impl FilteredAccessSet { +impl FilteredAccessSet { /// Creates an empty [`FilteredAccessSet`]. pub const fn new() -> Self { Self { @@ -1355,7 +1318,7 @@ impl FilteredAccessSet { /// Returns a reference to the unfiltered access of the entire set. #[inline] - pub fn combined_access(&self) -> &Access { + pub fn combined_access(&self) -> &Access { &self.combined_access } @@ -1371,7 +1334,7 @@ impl FilteredAccessSet { /// mutually exclusive. The fine grained phase iterates over all filters in /// the `self` set and compares it to all the filters in the `other` set, /// making sure they are all mutually compatible. - pub fn is_compatible(&self, other: &FilteredAccessSet) -> bool { + pub fn is_compatible(&self, other: &FilteredAccessSet) -> bool { if self.combined_access.is_compatible(other.combined_access()) { return true; } @@ -1386,7 +1349,7 @@ impl FilteredAccessSet { } /// Returns a vector of elements that this set and `other` cannot access at the same time. - pub fn get_conflicts(&self, other: &FilteredAccessSet) -> AccessConflicts { + pub fn get_conflicts(&self, other: &FilteredAccessSet) -> AccessConflicts { // if the unfiltered access is incompatible, must check each pair let mut conflicts = AccessConflicts::empty(); if !self.combined_access.is_compatible(other.combined_access()) { @@ -1400,7 +1363,7 @@ impl FilteredAccessSet { } /// Returns a vector of elements that this set and `other` cannot access at the same time. - pub fn get_conflicts_single(&self, filtered_access: &FilteredAccess) -> AccessConflicts { + pub fn get_conflicts_single(&self, filtered_access: &FilteredAccess) -> AccessConflicts { // if the unfiltered access is incompatible, must check each pair let mut conflicts = AccessConflicts::empty(); if !self.combined_access.is_compatible(filtered_access.access()) { @@ -1412,20 +1375,20 @@ impl FilteredAccessSet { } /// Adds the filtered access to the set. - pub fn add(&mut self, filtered_access: FilteredAccess) { + pub fn add(&mut self, filtered_access: FilteredAccess) { self.combined_access.extend(&filtered_access.access); self.filtered_accesses.push(filtered_access); } /// Adds a read access to a resource to the set. - pub fn add_unfiltered_resource_read(&mut self, index: T) { + pub fn add_unfiltered_resource_read(&mut self, index: ComponentId) { let mut filter = FilteredAccess::default(); filter.add_resource_read(index); self.add(filter); } /// Adds a write access to a resource to the set. - pub fn add_unfiltered_resource_write(&mut self, index: T) { + pub fn add_unfiltered_resource_write(&mut self, index: ComponentId) { let mut filter = FilteredAccess::default(); filter.add_resource_write(index); self.add(filter); @@ -1446,7 +1409,7 @@ impl FilteredAccessSet { } /// Adds all of the accesses from the passed set to `self`. - pub fn extend(&mut self, filtered_access_set: FilteredAccessSet) { + pub fn extend(&mut self, filtered_access_set: FilteredAccessSet) { self.combined_access .extend(&filtered_access_set.combined_access); self.filtered_accesses @@ -1474,48 +1437,44 @@ impl FilteredAccessSet { } } -impl Default for FilteredAccessSet { - fn default() -> Self { - Self::new() - } -} - #[cfg(test)] mod tests { use super::{invertible_difference_with, invertible_union_with}; - use crate::query::{ - access::AccessFilters, Access, AccessConflicts, ComponentAccessKind, FilteredAccess, - FilteredAccessSet, UnboundedAccessError, + use crate::{ + component::ComponentId, + query::{ + access::AccessFilters, Access, AccessConflicts, ComponentAccessKind, FilteredAccess, + FilteredAccessSet, UnboundedAccessError, + }, }; use alloc::{vec, vec::Vec}; - use core::marker::PhantomData; use fixedbitset::FixedBitSet; - fn create_sample_access() -> Access { - let mut access = Access::::default(); + fn create_sample_access() -> Access { + let mut access = Access::default(); - access.add_component_read(1); - access.add_component_read(2); - access.add_component_write(3); - access.add_archetypal(5); + access.add_component_read(ComponentId::new(1)); + access.add_component_read(ComponentId::new(2)); + access.add_component_write(ComponentId::new(3)); + access.add_archetypal(ComponentId::new(5)); access.read_all(); access } - fn create_sample_filtered_access() -> FilteredAccess { - let mut filtered_access = FilteredAccess::::default(); + fn create_sample_filtered_access() -> FilteredAccess { + let mut filtered_access = FilteredAccess::default(); - filtered_access.add_component_write(1); - filtered_access.add_component_read(2); - filtered_access.add_required(3); - filtered_access.and_with(4); + filtered_access.add_component_write(ComponentId::new(1)); + filtered_access.add_component_read(ComponentId::new(2)); + filtered_access.add_required(ComponentId::new(3)); + filtered_access.and_with(ComponentId::new(4)); filtered_access } - fn create_sample_access_filters() -> AccessFilters { - let mut access_filters = AccessFilters::::default(); + fn create_sample_access_filters() -> AccessFilters { + let mut access_filters = AccessFilters::default(); access_filters.with.grow_and_insert(3); access_filters.without.grow_and_insert(5); @@ -1523,11 +1482,11 @@ mod tests { access_filters } - fn create_sample_filtered_access_set() -> FilteredAccessSet { - let mut filtered_access_set = FilteredAccessSet::::default(); + fn create_sample_filtered_access_set() -> FilteredAccessSet { + let mut filtered_access_set = FilteredAccessSet::default(); - filtered_access_set.add_unfiltered_resource_read(2); - filtered_access_set.add_unfiltered_resource_write(4); + filtered_access_set.add_unfiltered_resource_read(ComponentId::new(2)); + filtered_access_set.add_unfiltered_resource_write(ComponentId::new(4)); filtered_access_set.read_all(); filtered_access_set @@ -1535,7 +1494,7 @@ mod tests { #[test] fn test_access_clone() { - let original: Access = create_sample_access(); + let original = create_sample_access(); let cloned = original.clone(); assert_eq!(original, cloned); @@ -1543,12 +1502,12 @@ mod tests { #[test] fn test_access_clone_from() { - let original: Access = create_sample_access(); - let mut cloned = Access::::default(); + let original = create_sample_access(); + let mut cloned = Access::default(); - cloned.add_component_write(7); - cloned.add_component_read(4); - cloned.add_archetypal(8); + cloned.add_component_write(ComponentId::new(7)); + cloned.add_component_read(ComponentId::new(4)); + cloned.add_archetypal(ComponentId::new(8)); cloned.write_all(); cloned.clone_from(&original); @@ -1558,7 +1517,7 @@ mod tests { #[test] fn test_filtered_access_clone() { - let original: FilteredAccess = create_sample_filtered_access(); + let original = create_sample_filtered_access(); let cloned = original.clone(); assert_eq!(original, cloned); @@ -1566,11 +1525,11 @@ mod tests { #[test] fn test_filtered_access_clone_from() { - let original: FilteredAccess = create_sample_filtered_access(); - let mut cloned = FilteredAccess::::default(); + let original = create_sample_filtered_access(); + let mut cloned = FilteredAccess::default(); - cloned.add_component_write(7); - cloned.add_component_read(4); + cloned.add_component_write(ComponentId::new(7)); + cloned.add_component_read(ComponentId::new(4)); cloned.append_or(&FilteredAccess::default()); cloned.clone_from(&original); @@ -1580,7 +1539,7 @@ mod tests { #[test] fn test_access_filters_clone() { - let original: AccessFilters = create_sample_access_filters(); + let original = create_sample_access_filters(); let cloned = original.clone(); assert_eq!(original, cloned); @@ -1588,8 +1547,8 @@ mod tests { #[test] fn test_access_filters_clone_from() { - let original: AccessFilters = create_sample_access_filters(); - let mut cloned = AccessFilters::::default(); + let original = create_sample_access_filters(); + let mut cloned = AccessFilters::default(); cloned.with.grow_and_insert(1); cloned.without.grow_and_insert(2); @@ -1601,7 +1560,7 @@ mod tests { #[test] fn test_filtered_access_set_clone() { - let original: FilteredAccessSet = create_sample_filtered_access_set(); + let original = create_sample_filtered_access_set(); let cloned = original.clone(); assert_eq!(original, cloned); @@ -1609,11 +1568,11 @@ mod tests { #[test] fn test_filtered_access_set_from() { - let original: FilteredAccessSet = create_sample_filtered_access_set(); - let mut cloned = FilteredAccessSet::::default(); + let original = create_sample_filtered_access_set(); + let mut cloned = FilteredAccessSet::default(); - cloned.add_unfiltered_resource_read(7); - cloned.add_unfiltered_resource_write(9); + cloned.add_unfiltered_resource_read(ComponentId::new(7)); + cloned.add_unfiltered_resource_write(ComponentId::new(9)); cloned.write_all(); cloned.clone_from(&original); @@ -1624,19 +1583,19 @@ mod tests { #[test] fn read_all_access_conflicts() { // read_all / single write - let mut access_a = Access::::default(); - access_a.add_component_write(0); + let mut access_a = Access::default(); + access_a.add_component_write(ComponentId::new(0)); - let mut access_b = Access::::default(); + let mut access_b = Access::default(); access_b.read_all(); assert!(!access_b.is_compatible(&access_a)); // read_all / read_all - let mut access_a = Access::::default(); + let mut access_a = Access::default(); access_a.read_all(); - let mut access_b = Access::::default(); + let mut access_b = Access::default(); access_b.read_all(); assert!(access_b.is_compatible(&access_a)); @@ -1644,92 +1603,98 @@ mod tests { #[test] fn access_get_conflicts() { - let mut access_a = Access::::default(); - access_a.add_component_read(0); - access_a.add_component_read(1); + let mut access_a = Access::default(); + access_a.add_component_read(ComponentId::new(0)); + access_a.add_component_read(ComponentId::new(1)); - let mut access_b = Access::::default(); - access_b.add_component_read(0); - access_b.add_component_write(1); + let mut access_b = Access::default(); + access_b.add_component_read(ComponentId::new(0)); + access_b.add_component_write(ComponentId::new(1)); - assert_eq!(access_a.get_conflicts(&access_b), vec![1_usize].into()); + assert_eq!( + access_a.get_conflicts(&access_b), + vec![ComponentId::new(1)].into() + ); - let mut access_c = Access::::default(); - access_c.add_component_write(0); - access_c.add_component_write(1); + let mut access_c = Access::default(); + access_c.add_component_write(ComponentId::new(0)); + access_c.add_component_write(ComponentId::new(1)); assert_eq!( access_a.get_conflicts(&access_c), - vec![0_usize, 1_usize].into() + vec![ComponentId::new(0), ComponentId::new(1)].into() ); assert_eq!( access_b.get_conflicts(&access_c), - vec![0_usize, 1_usize].into() + vec![ComponentId::new(0), ComponentId::new(1)].into() ); - let mut access_d = Access::::default(); - access_d.add_component_read(0); + let mut access_d = Access::default(); + access_d.add_component_read(ComponentId::new(0)); assert_eq!(access_d.get_conflicts(&access_a), AccessConflicts::empty()); assert_eq!(access_d.get_conflicts(&access_b), AccessConflicts::empty()); - assert_eq!(access_d.get_conflicts(&access_c), vec![0_usize].into()); + assert_eq!( + access_d.get_conflicts(&access_c), + vec![ComponentId::new(0)].into() + ); } #[test] fn filtered_combined_access() { - let mut access_a = FilteredAccessSet::::default(); - access_a.add_unfiltered_resource_read(1); + let mut access_a = FilteredAccessSet::default(); + access_a.add_unfiltered_resource_read(ComponentId::new(1)); - let mut filter_b = FilteredAccess::::default(); - filter_b.add_resource_write(1); + let mut filter_b = FilteredAccess::default(); + filter_b.add_resource_write(ComponentId::new(1)); let conflicts = access_a.get_conflicts_single(&filter_b); assert_eq!( &conflicts, - &AccessConflicts::from(vec![1_usize]), + &AccessConflicts::from(vec![ComponentId::new(1)]), "access_a: {access_a:?}, filter_b: {filter_b:?}" ); } #[test] fn filtered_access_extend() { - let mut access_a = FilteredAccess::::default(); - access_a.add_component_read(0); - access_a.add_component_read(1); - access_a.and_with(2); + let mut access_a = FilteredAccess::default(); + access_a.add_component_read(ComponentId::new(0)); + access_a.add_component_read(ComponentId::new(1)); + access_a.and_with(ComponentId::new(2)); - let mut access_b = FilteredAccess::::default(); - access_b.add_component_read(0); - access_b.add_component_write(3); - access_b.and_without(4); + let mut access_b = FilteredAccess::default(); + access_b.add_component_read(ComponentId::new(0)); + access_b.add_component_write(ComponentId::new(3)); + access_b.and_without(ComponentId::new(4)); access_a.extend(&access_b); - let mut expected = FilteredAccess::::default(); - expected.add_component_read(0); - expected.add_component_read(1); - expected.and_with(2); - expected.add_component_write(3); - expected.and_without(4); + let mut expected = FilteredAccess::default(); + expected.add_component_read(ComponentId::new(0)); + expected.add_component_read(ComponentId::new(1)); + expected.and_with(ComponentId::new(2)); + expected.add_component_write(ComponentId::new(3)); + expected.and_without(ComponentId::new(4)); assert!(access_a.eq(&expected)); } #[test] fn filtered_access_extend_or() { - let mut access_a = FilteredAccess::::default(); + let mut access_a = FilteredAccess::default(); // Exclusive access to `(&mut A, &mut B)`. - access_a.add_component_write(0); - access_a.add_component_write(1); + access_a.add_component_write(ComponentId::new(0)); + access_a.add_component_write(ComponentId::new(1)); // Filter by `With`. - let mut access_b = FilteredAccess::::default(); - access_b.and_with(2); + let mut access_b = FilteredAccess::default(); + access_b.and_with(ComponentId::new(2)); // Filter by `(With, Without)`. - let mut access_c = FilteredAccess::::default(); - access_c.and_with(3); - access_c.and_without(4); + let mut access_c = FilteredAccess::default(); + access_c.and_with(ComponentId::new(3)); + access_c.and_without(ComponentId::new(4)); // Turns `access_b` into `Or<(With, (With, Without))>`. access_b.append_or(&access_c); @@ -1740,20 +1705,18 @@ mod tests { // Construct the expected `FilteredAccess` struct. // The intention here is to test that exclusive access implied by `add_write` // forms correct normalized access structs when extended with `Or` filters. - let mut expected = FilteredAccess::::default(); - expected.add_component_write(0); - expected.add_component_write(1); + let mut expected = FilteredAccess::default(); + expected.add_component_write(ComponentId::new(0)); + expected.add_component_write(ComponentId::new(1)); // The resulted access is expected to represent `Or<((With, With, With), (With, With, With, Without))>`. expected.filter_sets = vec![ AccessFilters { with: FixedBitSet::with_capacity_and_blocks(3, [0b111]), without: FixedBitSet::default(), - _index_type: PhantomData, }, AccessFilters { with: FixedBitSet::with_capacity_and_blocks(4, [0b1011]), without: FixedBitSet::with_capacity_and_blocks(5, [0b10000]), - _index_type: PhantomData, }, ]; @@ -1762,12 +1725,12 @@ mod tests { #[test] fn try_iter_component_access_simple() { - let mut access = Access::::default(); + let mut access = Access::default(); - access.add_component_read(1); - access.add_component_read(2); - access.add_component_write(3); - access.add_archetypal(5); + access.add_component_read(ComponentId::new(1)); + access.add_component_read(ComponentId::new(2)); + access.add_component_write(ComponentId::new(3)); + access.add_archetypal(ComponentId::new(5)); let result = access .try_iter_component_access() @@ -1776,20 +1739,20 @@ mod tests { assert_eq!( result, Ok(vec![ - ComponentAccessKind::Shared(1), - ComponentAccessKind::Shared(2), - ComponentAccessKind::Exclusive(3), - ComponentAccessKind::Archetypal(5), + ComponentAccessKind::Shared(ComponentId::new(1)), + ComponentAccessKind::Shared(ComponentId::new(2)), + ComponentAccessKind::Exclusive(ComponentId::new(3)), + ComponentAccessKind::Archetypal(ComponentId::new(5)), ]), ); } #[test] fn try_iter_component_access_unbounded_write_all() { - let mut access = Access::::default(); + let mut access = Access::default(); - access.add_component_read(1); - access.add_component_read(2); + access.add_component_read(ComponentId::new(1)); + access.add_component_read(ComponentId::new(2)); access.write_all(); let result = access @@ -1807,10 +1770,10 @@ mod tests { #[test] fn try_iter_component_access_unbounded_read_all() { - let mut access = Access::::default(); + let mut access = Access::default(); - access.add_component_read(1); - access.add_component_read(2); + access.add_component_read(ComponentId::new(1)); + access.add_component_read(ComponentId::new(2)); access.read_all(); let result = access diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index b545caad8f92c..8ba34a34f9ee7 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -36,7 +36,7 @@ use super::{FilteredAccess, QueryData, QueryFilter}; /// let (entity, b) = query.single(&world).unwrap(); /// ``` pub struct QueryBuilder<'w, D: QueryData = (), F: QueryFilter = ()> { - access: FilteredAccess, + access: FilteredAccess, world: &'w mut World, or: bool, first: bool, @@ -105,7 +105,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { } /// Adds access to self's underlying [`FilteredAccess`] respecting [`Self::or`] and [`Self::and`] - pub fn extend_access(&mut self, mut access: FilteredAccess) { + pub fn extend_access(&mut self, mut access: FilteredAccess) { if self.or { if self.first { access.required.clear(); @@ -231,7 +231,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { } /// Returns a reference to the [`FilteredAccess`] that will be provided to the built [`Query`]. - pub fn access(&self) -> &FilteredAccess { + pub fn access(&self) -> &FilteredAccess { &self.access } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 57babe6f95f7b..4beb4e9a62044 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -308,8 +308,8 @@ pub unsafe trait QueryData: WorldQuery { /// Called when constructing a [`QueryLens`](crate::system::QueryLens) or calling [`QueryState::from_builder`](super::QueryState::from_builder) fn provide_extra_access( _state: &mut Self::State, - _access: &mut Access, - _available_access: &Access, + _access: &mut Access, + _available_access: &Access, ) { } @@ -391,7 +391,7 @@ unsafe impl WorldQuery for Entity { ) { } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) {} @@ -481,7 +481,7 @@ unsafe impl WorldQuery for EntityLocation { ) { } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) {} @@ -642,7 +642,7 @@ unsafe impl WorldQuery for SpawnDetails { ) { } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) {} @@ -757,7 +757,7 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { ) { } - fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { assert!( !access.access().has_any_component_write(), "EntityRef conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", @@ -860,7 +860,7 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { ) { } - fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { assert!( !access.access().has_any_component_read(), "EntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.", @@ -922,7 +922,7 @@ impl ReleaseStateQueryData for EntityMut<'_> { /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl WorldQuery for FilteredEntityRef<'_, '_> { type Fetch<'w> = EntityFetch<'w>; - type State = Access; + type State = Access; fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch @@ -960,10 +960,7 @@ unsafe impl WorldQuery for FilteredEntityRef<'_, '_> { ) { } - fn update_component_access( - state: &Self::State, - filtered_access: &mut FilteredAccess, - ) { + fn update_component_access(state: &Self::State, filtered_access: &mut FilteredAccess) { assert!( filtered_access.access().is_compatible(state), "FilteredEntityRef conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.", @@ -1002,8 +999,8 @@ unsafe impl QueryData for FilteredEntityRef<'_, '_> { #[inline] fn provide_extra_access( state: &mut Self::State, - access: &mut Access, - available_access: &Access, + access: &mut Access, + available_access: &Access, ) { // Claim any extra access that doesn't conflict with other subqueries // This is used when constructing a `QueryLens` or creating a query from a `QueryBuilder` @@ -1043,7 +1040,7 @@ unsafe impl ReadOnlyQueryData for FilteredEntityRef<'_, '_> {} /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl WorldQuery for FilteredEntityMut<'_, '_> { type Fetch<'w> = EntityFetch<'w>; - type State = Access; + type State = Access; fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch @@ -1081,10 +1078,7 @@ unsafe impl WorldQuery for FilteredEntityMut<'_, '_> { ) { } - fn update_component_access( - state: &Self::State, - filtered_access: &mut FilteredAccess, - ) { + fn update_component_access(state: &Self::State, filtered_access: &mut FilteredAccess) { assert!( filtered_access.access().is_compatible(state), "FilteredEntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.", @@ -1123,8 +1117,8 @@ unsafe impl<'a, 'b> QueryData for FilteredEntityMut<'a, 'b> { #[inline] fn provide_extra_access( state: &mut Self::State, - access: &mut Access, - available_access: &Access, + access: &mut Access, + available_access: &Access, ) { // Claim any extra access that doesn't conflict with other subqueries // This is used when constructing a `QueryLens` or creating a query from a `QueryBuilder` @@ -1164,7 +1158,7 @@ where B: Bundle, { type Fetch<'w> = EntityFetch<'w>; - type State = Access; + type State = Access; fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch @@ -1195,10 +1189,7 @@ where unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Table) {} - fn update_component_access( - state: &Self::State, - filtered_access: &mut FilteredAccess, - ) { + fn update_component_access(state: &Self::State, filtered_access: &mut FilteredAccess) { let access = filtered_access.access_mut(); assert!( access.is_compatible(state), @@ -1280,7 +1271,7 @@ where B: Bundle, { type Fetch<'w> = EntityFetch<'w>; - type State = Access; + type State = Access; fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch @@ -1311,10 +1302,7 @@ where unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Table) {} - fn update_component_access( - state: &Self::State, - filtered_access: &mut FilteredAccess, - ) { + fn update_component_access(state: &Self::State, filtered_access: &mut FilteredAccess) { let access = filtered_access.access_mut(); assert!( access.is_compatible(state), @@ -1426,7 +1414,7 @@ unsafe impl WorldQuery for &Archetype { ) { } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) {} @@ -1569,10 +1557,7 @@ unsafe impl WorldQuery for &T { unsafe { fetch.components.set_table(table_data) }; } - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { + fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { assert!( !access.access().has_component_write(component_id), "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", @@ -1755,10 +1740,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { unsafe { fetch.components.set_table(table_data) }; } - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { + fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { assert!( !access.access().has_component_write(component_id), "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", @@ -1964,10 +1946,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { unsafe { fetch.components.set_table(table_data) }; } - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { + fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { assert!( !access.access().has_component_read(component_id), "&mut {} conflicts with a previous access in this query. Mutable component access must be unique.", @@ -2111,10 +2090,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { } // NOT forwarded to `&mut T` - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { + fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { // Update component access here instead of in `<&mut T as WorldQuery>` to avoid erroneously referencing // `&mut T` in error message. assert!( @@ -2254,7 +2230,7 @@ unsafe impl WorldQuery for Option { } } - fn update_component_access(state: &T::State, access: &mut FilteredAccess) { + fn update_component_access(state: &T::State, access: &mut FilteredAccess) { // FilteredAccess::add_[write,read] adds the component to the `with` filter. // Those methods are called on `access` in `T::update_component_access`. // But in `Option`, we specifically don't filter on `T`, @@ -2438,10 +2414,7 @@ unsafe impl WorldQuery for Has { *fetch = table.has_column(*state); } - fn update_component_access( - &component_id: &Self::State, - access: &mut FilteredAccess, - ) { + fn update_component_access(&component_id: &Self::State, access: &mut FilteredAccess) { access.access_mut().add_archetypal(component_id); } @@ -2536,8 +2509,8 @@ macro_rules! impl_tuple_query_data { #[inline] fn provide_extra_access( state: &mut Self::State, - access: &mut Access, - available_access: &Access, + access: &mut Access, + available_access: &Access, ) { let ($($name,)*) = state; $($name::provide_extra_access($name, access, available_access);)* @@ -2651,7 +2624,7 @@ macro_rules! impl_anytuple_fetch { )* } - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { // update the filters (Or<(With<$name>,)>) let ($($name,)*) = state; @@ -2813,7 +2786,7 @@ unsafe impl WorldQuery for NopWorldQuery { #[inline(always)] unsafe fn set_table<'w>(_fetch: &mut (), _state: &D::State, _table: &Table) {} - fn update_component_access(_state: &D::State, _access: &mut FilteredAccess) {} + fn update_component_access(_state: &D::State, _access: &mut FilteredAccess) {} fn init_state(world: &mut World) -> Self::State { D::init_state(world) @@ -2897,7 +2870,7 @@ unsafe impl WorldQuery for PhantomData { ) { } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) -> Self::State {} @@ -3099,11 +3072,7 @@ mod tests { ) { } - fn update_component_access( - _state: &Self::State, - _access: &mut FilteredAccess, - ) { - } + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) {} diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index f9f4861b796ee..6269fc95b8db9 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -180,7 +180,7 @@ unsafe impl WorldQuery for With { unsafe fn set_table(_fetch: &mut (), _state: &ComponentId, _table: &Table) {} #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { access.and_with(id); } @@ -281,7 +281,7 @@ unsafe impl WorldQuery for Without { unsafe fn set_table(_fetch: &mut (), _state: &Self::State, _table: &Table) {} #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { access.and_without(id); } @@ -444,7 +444,7 @@ macro_rules! impl_or_query_filter { )* } - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { let ($($filter,)*) = state; let mut new_access = FilteredAccess::matches_nothing(); @@ -595,7 +595,7 @@ unsafe impl WorldQuery for Allows { unsafe fn set_table(_: &mut (), _: &ComponentId, _: &Table) {} #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { access.access_mut().add_archetypal(id); } @@ -793,7 +793,7 @@ unsafe impl WorldQuery for Added { } #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { if access.access().has_component_write(id) { panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", DebugName::type_name::()); } @@ -1020,7 +1020,7 @@ unsafe impl WorldQuery for Changed { } #[inline] - fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { if access.access().has_component_write(id) { panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", DebugName::type_name::()); } @@ -1185,7 +1185,7 @@ unsafe impl WorldQuery for Spawned { unsafe fn set_table<'w, 's>(_fetch: &mut Self::Fetch<'w>, _state: &'s (), _table: &'w Table) {} #[inline] - fn update_component_access(_state: &(), _access: &mut FilteredAccess) {} + fn update_component_access(_state: &(), _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) {} diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 94772cc9b9c66..607b239f7059b 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -851,10 +851,7 @@ mod tests { ) { } - fn update_component_access( - &component_id: &Self::State, - access: &mut FilteredAccess, - ) { + fn update_component_access(&component_id: &Self::State, access: &mut FilteredAccess) { assert!( !access.access().has_resource_write(component_id), "ReadsRData conflicts with a previous access in this query. Shared access cannot coincide with exclusive access." diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 14736f8ced91d..dd940b88c97cc 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -76,7 +76,7 @@ pub struct QueryState { /// Note that because we do a zero-cost reference conversion in `Query::as_readonly`, /// the access for a read-only query may include accesses for the original mutable version, /// but the `Query` does not have exclusive access to those components. - pub(crate) component_access: FilteredAccess, + pub(crate) component_access: FilteredAccess, // NOTE: we maintain both a bitset and a vec because iterating the vec is faster pub(super) matched_storage_ids: Vec, // Represents whether this query iteration is dense or not. When this is true @@ -145,7 +145,7 @@ impl QueryState { } /// Returns the components accessed by this query. - pub fn component_access(&self) -> &FilteredAccess { + pub fn component_access(&self) -> &FilteredAccess { &self.component_access } diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index 1c739927acf05..ec669a589a10f 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -112,7 +112,7 @@ pub unsafe trait WorldQuery { /// Used to check which queries are disjoint and can run in parallel // This does not have a default body of `{}` because 99% of cases need to add accesses // and forgetting to do so would be unsound. - fn update_component_access(state: &Self::State, access: &mut FilteredAccess); + fn update_component_access(state: &Self::State, access: &mut FilteredAccess); /// Creates and initializes a [`State`](WorldQuery::State) for this [`WorldQuery`] type. fn init_state(world: &mut World) -> Self::State; @@ -200,7 +200,7 @@ macro_rules! impl_tuple_world_query { } - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { let ($($name,)*) = state; $($name::update_component_access($name, access);)* } diff --git a/crates/bevy_ecs/src/reflect/mod.rs b/crates/bevy_ecs/src/reflect/mod.rs index 24e1449e61a8d..dce6237f1b857 100644 --- a/crates/bevy_ecs/src/reflect/mod.rs +++ b/crates/bevy_ecs/src/reflect/mod.rs @@ -47,6 +47,18 @@ impl DerefMut for AppTypeRegistry { } } +impl AppTypeRegistry { + /// Creates [`AppTypeRegistry`] and automatically registers all types deriving [`Reflect`]. + /// + /// See [`TypeRegistry::register_derived_types`] for more details. + #[cfg(feature = "reflect_auto_register")] + pub fn new_with_derived_types() -> Self { + let app_registry = AppTypeRegistry::default(); + app_registry.write().register_derived_types(); + app_registry + } +} + /// A [`Resource`] storing [`FunctionRegistry`] for /// function registrations relevant to a whole app. /// diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index 22b5ea128187b..dbbe015f6b7f5 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -642,7 +642,7 @@ impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { } /// Returns the underlying [`Commands`]. - pub fn commands(&mut self) -> Commands { + pub fn commands(&mut self) -> Commands<'_, '_> { self.commands.reborrow() } diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index 655a71caa70e6..08fdf2374c9fd 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -16,7 +16,7 @@ pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor}; use fixedbitset::FixedBitSet; use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, + component::{CheckChangeTicks, Tick}, error::{BevyError, ErrorContext, Result}, prelude::{IntoSystemSet, SystemSet}, query::FilteredAccessSet, @@ -208,7 +208,7 @@ impl System for ApplyDeferred { Ok(()) } - fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet { + fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet { FilteredAccessSet::new() } diff --git a/crates/bevy_ecs/src/schedule/node.rs b/crates/bevy_ecs/src/schedule/node.rs index 75c5c71ae8ce8..cf235f655af3a 100644 --- a/crates/bevy_ecs/src/schedule/node.rs +++ b/crates/bevy_ecs/src/schedule/node.rs @@ -10,7 +10,7 @@ use bevy_platform::collections::HashMap; use slotmap::{new_key_type, Key, KeyData, SecondaryMap, SlotMap}; use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, + component::{CheckChangeTicks, Tick}, prelude::{SystemIn, SystemSet}, query::FilteredAccessSet, schedule::{ @@ -35,7 +35,7 @@ pub struct SystemWithAccess { pub system: ScheduleSystem, /// The access returned by [`System::initialize`]. /// This will be empty if the system has not been initialized yet. - pub access: FilteredAccessSet, + pub access: FilteredAccessSet, } impl SystemWithAccess { @@ -104,7 +104,7 @@ impl System for SystemWithAccess { } #[inline] - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { + fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { self.system.initialize(world) } @@ -135,7 +135,7 @@ pub struct ConditionWithAccess { pub condition: BoxedCondition, /// The access returned by [`System::initialize`]. /// This will be empty if the system has not been initialized yet. - pub access: FilteredAccessSet, + pub access: FilteredAccessSet, } impl ConditionWithAccess { @@ -204,7 +204,7 @@ impl System for ConditionWithAccess { } #[inline] - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { + fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { self.condition.initialize(world) } diff --git a/crates/bevy_ecs/src/schedule/stepping.rs b/crates/bevy_ecs/src/schedule/stepping.rs index d765ebe060ac2..72bdd752a9a32 100644 --- a/crates/bevy_ecs/src/schedule/stepping.rs +++ b/crates/bevy_ecs/src/schedule/stepping.rs @@ -365,13 +365,11 @@ impl Stepping { /// lookup the first system for the supplied schedule index fn first_system_index_for_schedule(&self, index: usize) -> usize { - let label = match self.schedule_order.get(index) { - None => return 0, - Some(label) => label, + let Some(label) = self.schedule_order.get(index) else { + return 0; }; - let state = match self.schedule_states.get(label) { - None => return 0, - Some(state) => state, + let Some(state) = self.schedule_states.get(label) else { + return 0; }; state.first.unwrap_or(0) } diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index df608829740aa..bafd8c6ad0246 100644 --- a/crates/bevy_ecs/src/spawn.rs +++ b/crates/bevy_ecs/src/spawn.rs @@ -153,12 +153,19 @@ impl) + Send + Sync + 'static> /// Children::spawn(( /// Spawn(Name::new("Child1")), /// // This adds the already existing entities as children of Root. -/// WithRelated([child2, child3].into_iter()), +/// WithRelated::new([child2, child3]), /// )), /// )); /// ``` pub struct WithRelated(pub I); +impl WithRelated { + /// Creates a new [`WithRelated`] from a collection of entities. + pub fn new(iter: impl IntoIterator) -> Self { + Self(iter.into_iter()) + } +} + impl> SpawnableList for WithRelated { fn spawn(self, world: &mut World, entity: Entity) { world @@ -653,7 +660,7 @@ mod tests { let parent = world .spawn(( Name::new("Parent"), - Children::spawn(WithRelated([child1, child2].into_iter())), + Children::spawn(WithRelated::new([child1, child2])), )) .id(); diff --git a/crates/bevy_ecs/src/system/adapter_system.rs b/crates/bevy_ecs/src/system/adapter_system.rs index 6b1334862eb35..3bae1615e8122 100644 --- a/crates/bevy_ecs/src/system/adapter_system.rs +++ b/crates/bevy_ecs/src/system/adapter_system.rs @@ -171,10 +171,7 @@ where unsafe { self.system.validate_param_unsafe(world) } } - fn initialize( - &mut self, - world: &mut crate::prelude::World, - ) -> crate::query::FilteredAccessSet { + fn initialize(&mut self, world: &mut crate::prelude::World) -> crate::query::FilteredAccessSet { self.system.initialize(world) } diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 2d037eef16c37..a2a3d0d79857c 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -3,7 +3,7 @@ use bevy_utils::prelude::DebugName; use core::marker::PhantomData; use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, + component::{CheckChangeTicks, Tick}, prelude::World, query::FilteredAccessSet, schedule::InternedSystemSet, @@ -203,7 +203,7 @@ where unsafe { self.a.validate_param_unsafe(world) } } - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { + fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { let mut a_access = self.a.initialize(world); let b_access = self.b.initialize(world); a_access.extend(b_access); @@ -402,7 +402,7 @@ where unsafe { self.a.validate_param_unsafe(world) } } - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { + fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { let mut a_access = self.a.initialize(world); let b_access = self.b.initialize(world); a_access.extend(b_access); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index d786cdb697f3b..3f3e75c37f700 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -131,7 +131,7 @@ const _: () = { fn init_access( state: &Self::State, system_meta: &mut bevy_ecs::system::SystemMeta, - component_access_set: &mut bevy_ecs::query::FilteredAccessSet, + component_access_set: &mut bevy_ecs::query::FilteredAccessSet, world: &mut World, ) { <__StructFieldsAlias<'_, '_> as bevy_ecs::system::SystemParam>::init_access( @@ -313,7 +313,7 @@ impl<'w, 's> Commands<'w, 's> { /// - [`spawn_batch`](Self::spawn_batch) to spawn many entities /// with the same combination of components. #[track_caller] - pub fn spawn_empty(&mut self) -> EntityCommands { + pub fn spawn_empty(&mut self) -> EntityCommands<'_> { let entity = self.entities.reserve_entity(); let mut entity_commands = EntityCommands { entity, @@ -375,7 +375,7 @@ impl<'w, 's> Commands<'w, 's> { /// - [`spawn_batch`](Self::spawn_batch) to spawn many entities /// with the same combination of components. #[track_caller] - pub fn spawn(&mut self, bundle: T) -> EntityCommands { + pub fn spawn(&mut self, bundle: T) -> EntityCommands<'_> { let entity = self.entities.reserve_entity(); let mut entity_commands = EntityCommands { entity, @@ -436,7 +436,7 @@ impl<'w, 's> Commands<'w, 's> { /// - [`get_entity`](Self::get_entity) for the fallible version. #[inline] #[track_caller] - pub fn entity(&mut self, entity: Entity) -> EntityCommands { + pub fn entity(&mut self, entity: Entity) -> EntityCommands<'_> { EntityCommands { entity, commands: self.reborrow(), @@ -487,7 +487,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn get_entity( &mut self, entity: Entity, - ) -> Result { + ) -> Result, EntityDoesNotExistError> { if self.entities.contains(entity) { Ok(EntityCommands { entity, @@ -1120,7 +1120,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn add_observer( &mut self, observer: impl IntoObserverSystem, - ) -> EntityCommands { + ) -> EntityCommands<'_> { self.spawn(Observer::new(observer)) } @@ -1269,7 +1269,7 @@ impl<'a> EntityCommands<'a> { /// Returns an [`EntityCommands`] with a smaller lifetime. /// /// This is useful if you have `&mut EntityCommands` but you need `EntityCommands`. - pub fn reborrow(&mut self) -> EntityCommands { + pub fn reborrow(&mut self) -> EntityCommands<'_> { EntityCommands { entity: self.entity, commands: self.commands.reborrow(), @@ -1319,7 +1319,7 @@ impl<'a> EntityCommands<'a> { /// /// # bevy_ecs::system::assert_is_system(level_up_system); /// ``` - pub fn entry(&mut self) -> EntityEntryCommands { + pub fn entry(&mut self) -> EntityEntryCommands<'_, T> { EntityEntryCommands { entity_commands: self.reborrow(), marker: PhantomData, @@ -1982,7 +1982,7 @@ impl<'a> EntityCommands<'a> { } /// Returns the underlying [`Commands`]. - pub fn commands(&mut self) -> Commands { + pub fn commands(&mut self) -> Commands<'_, '_> { self.commands.reborrow() } @@ -2381,7 +2381,7 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { /// } /// # bevy_ecs::system::assert_is_system(level_up_system); /// ``` - pub fn entity(&mut self) -> EntityCommands { + pub fn entity(&mut self) -> EntityCommands<'_> { self.entity_commands.reborrow() } } diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 241f9955dfa4d..a9cb1104a5630 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -1,5 +1,5 @@ use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, + component::{CheckChangeTicks, Tick}, error::Result, query::FilteredAccessSet, schedule::{InternedSystemSet, SystemSet}, @@ -176,7 +176,7 @@ where } #[inline] - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { + fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX); self.param_state = Some(F::Param::init(world, &mut self.system_meta)); FilteredAccessSet::new() diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 89740a9dceebc..5ee2805772129 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -1,5 +1,5 @@ use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, + component::{CheckChangeTicks, Tick}, error::{BevyError, Result}, never::Never, prelude::FromWorld, @@ -733,7 +733,7 @@ where } #[inline] - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { + fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { if let Some(state) = &self.state { assert_eq!( state.world_id, diff --git a/crates/bevy_ecs/src/system/schedule_system.rs b/crates/bevy_ecs/src/system/schedule_system.rs index e2a853dbe9d25..150b748e88679 100644 --- a/crates/bevy_ecs/src/system/schedule_system.rs +++ b/crates/bevy_ecs/src/system/schedule_system.rs @@ -1,7 +1,7 @@ use bevy_utils::prelude::DebugName; use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, + component::{CheckChangeTicks, Tick}, error::Result, query::FilteredAccessSet, system::{input::SystemIn, BoxedSystem, RunSystemError, System, SystemInput}, @@ -90,7 +90,7 @@ where self.system.validate_param_unsafe(world) } - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { + fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { self.system.initialize(world) } @@ -187,7 +187,7 @@ where self.system.validate_param_unsafe(world) } - fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { + fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { if self.value.is_none() { self.value = Some(T::from_world(world)); } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 1f2a3ab608fba..048835f397776 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -8,7 +8,7 @@ use core::fmt::{Debug, Display}; use log::warn; use crate::{ - component::{CheckChangeTicks, ComponentId, Tick}, + component::{CheckChangeTicks, Tick}, error::BevyError, query::FilteredAccessSet, schedule::InternedSystemSet, @@ -181,7 +181,7 @@ pub trait System: Send + Sync + 'static { /// Initialize the system. /// /// Returns a [`FilteredAccessSet`] with the access required to run the system. - fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet; + fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet; /// Checks any [`Tick`]s stored on this system and wraps their value if they get too old. /// diff --git a/crates/bevy_ecs/src/system/system_name.rs b/crates/bevy_ecs/src/system/system_name.rs index e0c3c952cf4ad..0bc54a9c3cfe3 100644 --- a/crates/bevy_ecs/src/system/system_name.rs +++ b/crates/bevy_ecs/src/system/system_name.rs @@ -1,5 +1,5 @@ use crate::{ - component::{ComponentId, Tick}, + component::Tick, prelude::World, query::FilteredAccessSet, system::{ExclusiveSystemParam, ReadOnlySystemParam, SystemMeta, SystemParam}, @@ -54,7 +54,7 @@ unsafe impl SystemParam for SystemName { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index cb41a8dcb2b47..d9adab6fe34b7 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -232,7 +232,7 @@ pub unsafe trait SystemParam: Sized { fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ); @@ -343,7 +343,7 @@ unsafe impl SystemParam for Qu fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { assert_component_access_compatibility( @@ -376,8 +376,8 @@ fn assert_component_access_compatibility( system_name: &DebugName, query_type: DebugName, filter_type: DebugName, - system_access: &FilteredAccessSet, - current: &FilteredAccess, + system_access: &FilteredAccessSet, + current: &FilteredAccess, world: &World, ) { let conflicts = system_access.get_conflicts_single(current); @@ -407,7 +407,7 @@ unsafe impl<'a, 'b, D: QueryData + 'static, F: QueryFilter + 'static> SystemPara fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { Query::init_access(state, system_meta, component_access_set, world); @@ -478,7 +478,7 @@ unsafe impl SystemParam fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { Query::init_access(state, system_meta, component_access_set, world); @@ -678,7 +678,7 @@ macro_rules! impl_param_set { non_snake_case, reason = "Certain variable names are provided by the caller, not by us." )] - fn init_access(state: &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, world: &mut World) { + fn init_access(state: &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, world: &mut World) { let ($($param,)*) = state; $( // Call `init_access` on a clone of the original access set to check for conflicts @@ -765,7 +765,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { fn init_access( &component_id: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { let combined_access = component_access_set.combined_access(); @@ -842,7 +842,7 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { fn init_access( &component_id: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { let combined_access = component_access_set.combined_access(); @@ -920,7 +920,7 @@ unsafe impl SystemParam for &'_ World { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { let mut filtered_access = FilteredAccess::default(); @@ -957,7 +957,7 @@ unsafe impl<'w> SystemParam for DeferredWorld<'w> { fn init_access( _state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { assert!( @@ -1102,7 +1102,7 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } @@ -1270,7 +1270,7 @@ impl<'a, T: SystemBuffer> DerefMut for Deferred<'a, T> { impl Deferred<'_, T> { /// Returns a [`Deferred`] with a smaller lifetime. /// This is useful if you have `&mut Deferred` but need `Deferred`. - pub fn reborrow(&mut self) -> Deferred { + pub fn reborrow(&mut self) -> Deferred<'_, T> { Deferred(self.0) } } @@ -1290,7 +1290,7 @@ unsafe impl SystemParam for Deferred<'_, T> { fn init_access( _state: &Self::State, system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { system_meta.set_has_deferred(); @@ -1329,7 +1329,7 @@ unsafe impl SystemParam for NonSendMarker { fn init_access( _state: &Self::State, system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { system_meta.set_non_send(); @@ -1433,7 +1433,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { fn init_access( &component_id: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { system_meta.set_non_send(); @@ -1509,7 +1509,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { fn init_access( &component_id: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { system_meta.set_non_send(); @@ -1585,7 +1585,7 @@ unsafe impl<'a> SystemParam for &'a Archetypes { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } @@ -1614,7 +1614,7 @@ unsafe impl<'a> SystemParam for &'a Components { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } @@ -1643,7 +1643,7 @@ unsafe impl<'a> SystemParam for &'a Entities { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } @@ -1672,7 +1672,7 @@ unsafe impl<'a> SystemParam for &'a Bundles { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } @@ -1730,7 +1730,7 @@ unsafe impl SystemParam for SystemChangeTick { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } @@ -1762,7 +1762,7 @@ unsafe impl SystemParam for Option { fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { T::init_access(state, system_meta, component_access_set, world); @@ -1805,7 +1805,7 @@ unsafe impl SystemParam for Result, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { T::init_access(state, system_meta, component_access_set, world); @@ -1900,7 +1900,7 @@ unsafe impl SystemParam for If { fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { T::init_access(state, system_meta, component_access_set, world); @@ -1954,7 +1954,7 @@ unsafe impl SystemParam for Vec { fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { for state in state { @@ -2018,7 +2018,7 @@ unsafe impl SystemParam for ParamSet<'_, '_, Vec> { fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { for state in state { @@ -2124,7 +2124,7 @@ macro_rules! impl_system_param_tuple { (($($param::init_state(world),)*)) } - fn init_access(state: &Self::State, _system_meta: &mut SystemMeta, _component_access_set: &mut FilteredAccessSet, _world: &mut World) { + fn init_access(state: &Self::State, _system_meta: &mut SystemMeta, _component_access_set: &mut FilteredAccessSet, _world: &mut World) { let ($($param,)*) = state; $($param::init_access($param, _system_meta, _component_access_set, _world);)* } @@ -2301,7 +2301,7 @@ unsafe impl SystemParam for StaticSystemParam<'_, '_, fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { P::init_access(state, system_meta, component_access_set, world); @@ -2346,7 +2346,7 @@ unsafe impl SystemParam for PhantomData { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } @@ -2565,7 +2565,7 @@ trait DynParamState: Sync + Send + Any { fn init_access( &self, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ); @@ -2595,7 +2595,7 @@ impl DynParamState for ParamState { fn init_access( &self, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { T::init_access(&self.0, system_meta, component_access_set, world); @@ -2623,7 +2623,7 @@ unsafe impl SystemParam for DynSystemParam<'_, '_> { fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { state @@ -2667,7 +2667,7 @@ unsafe impl SystemParam for DynSystemParam<'_, '_> { // SAFETY: Resource ComponentId access is applied to the access. If this FilteredResources // conflicts with any prior access, a panic will occur. unsafe impl SystemParam for FilteredResources<'_, '_> { - type State = Access; + type State = Access; type Item<'world, 'state> = FilteredResources<'world, 'state>; @@ -2678,7 +2678,7 @@ unsafe impl SystemParam for FilteredResources<'_, '_> { fn init_access( access: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { let combined_access = component_access_set.combined_access(); @@ -2716,7 +2716,7 @@ unsafe impl ReadOnlySystemParam for FilteredResources<'_, '_> {} // SAFETY: Resource ComponentId access is applied to the access. If this FilteredResourcesMut // conflicts with any prior access, a panic will occur. unsafe impl SystemParam for FilteredResourcesMut<'_, '_> { - type State = Access; + type State = Access; type Item<'world, 'state> = FilteredResourcesMut<'world, 'state>; @@ -2727,7 +2727,7 @@ unsafe impl SystemParam for FilteredResourcesMut<'_, '_> { fn init_access( access: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { let combined_access = component_access_set.combined_access(); diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index cb726a1d358d7..f4412d43a3cb1 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "bevy_reflect")] -use crate::reflect::ReflectComponent; #[cfg(feature = "hotpatching")] use crate::{change_detection::DetectChanges, HotPatchChanges}; use crate::{ @@ -14,14 +12,13 @@ use crate::{ }; use alloc::boxed::Box; use bevy_ecs_macros::{Component, Resource}; -#[cfg(feature = "bevy_reflect")] -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use core::marker::PhantomData; +use bevy_utils::prelude::DebugName; +use core::{any::TypeId, marker::PhantomData}; use thiserror::Error; /// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized. #[derive(Component)] -#[require(SystemIdMarker, Internal)] +#[require(SystemIdMarker = SystemIdMarker::typed_system_id_marker::(), Internal)] pub(crate) struct RegisteredSystem { initialized: bool, system: BoxedSystem, @@ -36,11 +33,45 @@ impl RegisteredSystem { } } +#[derive(Debug, Clone)] +struct TypeIdAndName { + type_id: TypeId, + name: DebugName, +} + +impl TypeIdAndName { + fn new() -> Self { + Self { + type_id: TypeId::of::(), + name: DebugName::type_name::(), + } + } +} + +impl Default for TypeIdAndName { + fn default() -> Self { + Self { + type_id: TypeId::of::<()>(), + name: DebugName::type_name::<()>(), + } + } +} + /// Marker [`Component`](bevy_ecs::component::Component) for identifying [`SystemId`] [`Entity`]s. -#[derive(Component, Default)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Component, Default))] -pub struct SystemIdMarker; +#[derive(Debug, Default, Clone, Component)] +pub struct SystemIdMarker { + input_type_id: TypeIdAndName, + output_type_id: TypeIdAndName, +} + +impl SystemIdMarker { + fn typed_system_id_marker() -> Self { + Self { + input_type_id: TypeIdAndName::new::(), + output_type_id: TypeIdAndName::new::(), + } + } +} /// A system that has been removed from the registry. /// It contains the system and whether or not it has been initialized. @@ -344,14 +375,26 @@ impl World { .map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?; // Take ownership of system trait object - let RegisteredSystem { + let Some(RegisteredSystem { mut initialized, mut system, - } = entity - .take::>() - .ok_or(RegisteredSystemError::Recursive(id))?; + }) = entity.take::>() + else { + let Some(system_id_marker) = entity.get::() else { + return Err(RegisteredSystemError::SystemIdNotRegistered(id)); + }; + if system_id_marker.input_type_id.type_id != TypeId::of::() + || system_id_marker.output_type_id.type_id != TypeId::of::() + { + return Err(RegisteredSystemError::IncorrectType( + id, + system_id_marker.clone(), + )); + } + return Err(RegisteredSystemError::Recursive(id)); + }; - // Run the system + // Initialize the system if !initialized { system.initialize(self); initialized = true; @@ -510,6 +553,9 @@ pub enum RegisteredSystemError { /// System returned an error or failed required parameter validation. #[error("System returned error: {0}")] Failed(BevyError), + /// [`SystemId`] had different input and/or output types than [`SystemIdMarker`] + #[error("Could not get system from `{}`, entity was `SystemId<{}, {}>`", DebugName::type_name::>(), .1.input_type_id.name, .1.output_type_id.name)] + IncorrectType(SystemId, SystemIdMarker), } impl From for RegisteredSystemError { @@ -532,6 +578,11 @@ impl core::fmt::Debug for RegisteredSystemError { Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(), Self::Skipped(arg0) => f.debug_tuple("Skipped").field(arg0).finish(), Self::Failed(arg0) => f.debug_tuple("Failed").field(arg0).finish(), + Self::IncorrectType(arg0, arg1) => f + .debug_tuple("IncorrectType") + .field(arg0) + .field(arg1) + .finish(), } } } @@ -542,7 +593,10 @@ mod tests { use bevy_utils::default; - use crate::{prelude::*, system::SystemId}; + use crate::{ + prelude::*, + system::{RegisteredSystemError, SystemId}, + }; #[derive(Resource, Default, PartialEq, Debug)] struct Counter(u8); @@ -986,4 +1040,21 @@ mod tests { world.run_system_cached(system.pipe(system)).unwrap(); world.run_system_cached(system.map(|()| {})).unwrap(); } + + #[test] + fn wrong_system_type() { + fn test() -> Result<(), u8> { + Ok(()) + } + + let mut world = World::new(); + + let entity = world.register_system_cached(test).entity(); + + match world.run_system::(SystemId::from_entity(entity)) { + Ok(_) => panic!("Should fail since called `run_system` with wrong SystemId type."), + Err(RegisteredSystemError::IncorrectType(_, _)) => (), + Err(err) => panic!("Failed with wrong error. `{:?}`", err), + } + } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 3b146d2c0589c..92d2e98e5da69 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -62,13 +62,13 @@ impl<'w> From<&'w mut World> for DeferredWorld<'w> { impl<'w> DeferredWorld<'w> { /// Reborrow self as a new instance of [`DeferredWorld`] #[inline] - pub fn reborrow(&mut self) -> DeferredWorld { + pub fn reborrow(&mut self) -> DeferredWorld<'_> { DeferredWorld { world: self.world } } /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] - pub fn commands(&mut self) -> Commands { + pub fn commands(&mut self) -> Commands<'_, '_> { // SAFETY: &mut self ensure that there are no outstanding accesses to the queue let command_queue = unsafe { self.world.get_raw_command_queue() }; // SAFETY: command_queue is stored on world and always valid while the world exists @@ -81,7 +81,7 @@ impl<'w> DeferredWorld<'w> { pub fn get_mut>( &mut self, entity: Entity, - ) -> Option> { + ) -> Option> { self.get_entity_mut(entity).ok()?.into_mut() } @@ -418,7 +418,7 @@ impl<'w> DeferredWorld<'w> { /// # assert_eq!(_world.get::(e1).unwrap().0, eid); /// # assert_eq!(_world.get::(e2).unwrap().0, eid); /// ``` - pub fn entities_and_commands(&mut self) -> (EntityFetcher, Commands) { + pub fn entities_and_commands(&mut self) -> (EntityFetcher<'_>, Commands<'_, '_>) { let cell = self.as_unsafe_world_cell(); // SAFETY: `&mut self` gives mutable access to the entire world, and prevents simultaneous access. let fetcher = unsafe { EntityFetcher::new(cell) }; @@ -887,7 +887,7 @@ impl<'w> DeferredWorld<'w> { /// # Safety /// - must only be used to make non-structural ECS changes #[inline] - pub(crate) fn as_unsafe_world_cell(&mut self) -> UnsafeWorldCell { + pub(crate) fn as_unsafe_world_cell(&mut self) -> UnsafeWorldCell<'_> { self.world } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 9260301474b46..643c4f1a029c1 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -3516,7 +3516,7 @@ impl<'w, 'a, T: Component> VacantComponentEntry<'w, 'a, T> { #[derive(Clone, Copy)] pub struct FilteredEntityRef<'w, 's> { entity: UnsafeEntityCell<'w>, - access: &'s Access, + access: &'s Access, } impl<'w, 's> FilteredEntityRef<'w, 's> { @@ -3526,10 +3526,7 @@ impl<'w, 's> FilteredEntityRef<'w, 's> { /// component can exist at the same time as the returned [`FilteredEntityMut`] /// - If `access` takes any access for a component `entity` must have that component. #[inline] - pub(crate) unsafe fn new( - entity: UnsafeEntityCell<'w>, - access: &'s Access, - ) -> Self { + pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: &'s Access) -> Self { Self { entity, access } } @@ -3554,7 +3551,7 @@ impl<'w, 's> FilteredEntityRef<'w, 's> { /// Returns a reference to the underlying [`Access`]. #[inline] - pub fn access(&self) -> &Access { + pub fn access(&self) -> &Access { self.access } @@ -3834,7 +3831,7 @@ unsafe impl EntityEquivalent for FilteredEntityRef<'_, '_> {} /// ``` pub struct FilteredEntityMut<'w, 's> { entity: UnsafeEntityCell<'w>, - access: &'s Access, + access: &'s Access, } impl<'w, 's> FilteredEntityMut<'w, 's> { @@ -3846,10 +3843,7 @@ impl<'w, 's> FilteredEntityMut<'w, 's> { /// may exist at the same time as the returned [`FilteredEntityMut`] /// - If `access` takes any access for a component `entity` must have that component. #[inline] - pub(crate) unsafe fn new( - entity: UnsafeEntityCell<'w>, - access: &'s Access, - ) -> Self { + pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: &'s Access) -> Self { Self { entity, access } } @@ -3887,7 +3881,7 @@ impl<'w, 's> FilteredEntityMut<'w, 's> { /// Returns a reference to the underlying [`Access`]. #[inline] - pub fn access(&self) -> &Access { + pub fn access(&self) -> &Access { self.access } @@ -4162,7 +4156,7 @@ where B: Bundle, { entity: UnsafeEntityCell<'w>, - access: &'s Access, + access: &'s Access, phantom: PhantomData, } @@ -4172,10 +4166,7 @@ where { /// # Safety /// Other users of `UnsafeEntityCell` must only have mutable access to the components in `B`. - pub(crate) unsafe fn new( - entity: UnsafeEntityCell<'w>, - access: &'s Access, - ) -> Self { + pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: &'s Access) -> Self { Self { entity, access, @@ -4400,7 +4391,7 @@ where B: Bundle, { entity: UnsafeEntityCell<'w>, - access: &'s Access, + access: &'s Access, phantom: PhantomData, } @@ -4410,10 +4401,7 @@ where { /// # Safety /// Other users of `UnsafeEntityCell` must not have access to any components not in `B`. - pub(crate) unsafe fn new( - entity: UnsafeEntityCell<'w>, - access: &'s Access, - ) -> Self { + pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: &'s Access) -> Self { Self { entity, access, diff --git a/crates/bevy_ecs/src/world/filtered_resource.rs b/crates/bevy_ecs/src/world/filtered_resource.rs index ed3672bef95a3..668b45257ed32 100644 --- a/crates/bevy_ecs/src/world/filtered_resource.rs +++ b/crates/bevy_ecs/src/world/filtered_resource.rs @@ -117,7 +117,7 @@ use super::error::ResourceFetchError; #[derive(Clone, Copy)] pub struct FilteredResources<'w, 's> { world: UnsafeWorldCell<'w>, - access: &'s Access, + access: &'s Access, last_run: Tick, this_run: Tick, } @@ -128,7 +128,7 @@ impl<'w, 's> FilteredResources<'w, 's> { /// It is the callers responsibility to ensure that nothing else may access the any resources in the `world` in a way that conflicts with `access`. pub(crate) unsafe fn new( world: UnsafeWorldCell<'w>, - access: &'s Access, + access: &'s Access, last_run: Tick, this_run: Tick, ) -> Self { @@ -141,7 +141,7 @@ impl<'w, 's> FilteredResources<'w, 's> { } /// Returns a reference to the underlying [`Access`]. - pub fn access(&self) -> &Access { + pub fn access(&self) -> &Access { self.access } @@ -220,8 +220,8 @@ impl<'w, 's> From<&'w FilteredResourcesMut<'_, 's>> for FilteredResources<'w, 's impl<'w> From<&'w World> for FilteredResources<'w, 'static> { fn from(value: &'w World) -> Self { - const READ_ALL_RESOURCES: &Access = { - const ACCESS: Access = { + const READ_ALL_RESOURCES: &Access = { + const ACCESS: Access = { let mut access = Access::new(); access.read_all_resources(); access @@ -373,7 +373,7 @@ impl<'w> From<&'w mut World> for FilteredResources<'w, 'static> { /// ``` pub struct FilteredResourcesMut<'w, 's> { world: UnsafeWorldCell<'w>, - access: &'s Access, + access: &'s Access, last_run: Tick, this_run: Tick, } @@ -384,7 +384,7 @@ impl<'w, 's> FilteredResourcesMut<'w, 's> { /// It is the callers responsibility to ensure that nothing else may access the any resources in the `world` in a way that conflicts with `access`. pub(crate) unsafe fn new( world: UnsafeWorldCell<'w>, - access: &'s Access, + access: &'s Access, last_run: Tick, this_run: Tick, ) -> Self { @@ -409,7 +409,7 @@ impl<'w, 's> FilteredResourcesMut<'w, 's> { } /// Returns a reference to the underlying [`Access`]. - pub fn access(&self) -> &Access { + pub fn access(&self) -> &Access { self.access } @@ -510,8 +510,8 @@ impl<'w, 's> FilteredResourcesMut<'w, 's> { impl<'w> From<&'w mut World> for FilteredResourcesMut<'w, 'static> { fn from(value: &'w mut World) -> Self { - const WRITE_ALL_RESOURCES: &Access = { - const ACCESS: Access = { + const WRITE_ALL_RESOURCES: &Access = { + const ACCESS: Access = { let mut access = Access::new(); access.write_all_resources(); access @@ -538,7 +538,7 @@ impl<'w> From<&'w mut World> for FilteredResourcesMut<'w, 'static> { /// This is passed to a callback in [`FilteredResourcesParamBuilder`](crate::system::FilteredResourcesParamBuilder). pub struct FilteredResourcesBuilder<'w> { world: &'w mut World, - access: Access, + access: Access, } impl<'w> FilteredResourcesBuilder<'w> { @@ -551,7 +551,7 @@ impl<'w> FilteredResourcesBuilder<'w> { } /// Returns a reference to the underlying [`Access`]. - pub fn access(&self) -> &Access { + pub fn access(&self) -> &Access { &self.access } @@ -574,7 +574,7 @@ impl<'w> FilteredResourcesBuilder<'w> { } /// Create an [`Access`] that represents the accesses of the builder. - pub fn build(self) -> Access { + pub fn build(self) -> Access { self.access } } @@ -584,7 +584,7 @@ impl<'w> FilteredResourcesBuilder<'w> { /// This is passed to a callback in [`FilteredResourcesMutParamBuilder`](crate::system::FilteredResourcesMutParamBuilder). pub struct FilteredResourcesMutBuilder<'w> { world: &'w mut World, - access: Access, + access: Access, } impl<'w> FilteredResourcesMutBuilder<'w> { @@ -597,7 +597,7 @@ impl<'w> FilteredResourcesMutBuilder<'w> { } /// Returns a reference to the underlying [`Access`]. - pub fn access(&self) -> &Access { + pub fn access(&self) -> &Access { &self.access } @@ -638,7 +638,7 @@ impl<'w> FilteredResourcesMutBuilder<'w> { } /// Create an [`Access`] that represents the accesses of the builder. - pub fn build(self) -> Access { + pub fn build(self) -> Access { self.access } } diff --git a/crates/bevy_ecs/src/world/identifier.rs b/crates/bevy_ecs/src/world/identifier.rs index 51f9a0ee2c6f5..37e71110e55d6 100644 --- a/crates/bevy_ecs/src/world/identifier.rs +++ b/crates/bevy_ecs/src/world/identifier.rs @@ -1,5 +1,5 @@ use crate::{ - component::{ComponentId, Tick}, + component::Tick, query::FilteredAccessSet, storage::SparseSetIndex, system::{ExclusiveSystemParam, ReadOnlySystemParam, SystemMeta, SystemParam}, @@ -59,7 +59,7 @@ unsafe impl SystemParam for WorldId { fn init_access( _state: &Self::State, _system_meta: &mut SystemMeta, - _component_access_set: &mut FilteredAccessSet, + _component_access_set: &mut FilteredAccessSet, _world: &mut World, ) { } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d251206c37720..9e36484cc5141 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -232,14 +232,14 @@ impl World { /// **NOTE:** [`ComponentsQueuedRegistrator`] is easily misused. /// See its docs for important notes on when and how it should be used. #[inline] - pub fn components_queue(&self) -> ComponentsQueuedRegistrator { + pub fn components_queue(&self) -> ComponentsQueuedRegistrator<'_> { // SAFETY: These are from the same world. unsafe { ComponentsQueuedRegistrator::new(&self.components, &self.component_ids) } } /// Prepares a [`ComponentsRegistrator`] for the world. #[inline] - pub fn components_registrator(&mut self) -> ComponentsRegistrator { + pub fn components_registrator(&mut self) -> ComponentsRegistrator<'_> { // SAFETY: These are from the same world. unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.component_ids) } } @@ -271,7 +271,7 @@ impl World { /// Creates a new [`Commands`] instance that writes to the world's command queue /// Use [`World::flush`] to apply all queued commands #[inline] - pub fn commands(&mut self) -> Commands { + pub fn commands(&mut self) -> Commands<'_, '_> { // SAFETY: command_queue is stored on world and always valid while the world exists unsafe { Commands::new_raw_from_entities(self.command_queue.clone(), &self.entities) } } @@ -1047,7 +1047,7 @@ impl World { /// # assert_eq!(world.get::(e1).unwrap().0, eid); /// # assert_eq!(world.get::(e2).unwrap().0, eid); /// ``` - pub fn entities_and_commands(&mut self) -> (EntityFetcher, Commands) { + pub fn entities_and_commands(&mut self) -> (EntityFetcher<'_>, Commands<'_, '_>) { let cell = self.as_unsafe_world_cell(); // SAFETY: `&mut self` gives mutable access to the entire world, and prevents simultaneous access. let fetcher = unsafe { EntityFetcher::new(cell) }; @@ -1087,7 +1087,7 @@ impl World { /// assert_eq!(position.x, 0.0); /// ``` #[track_caller] - pub fn spawn_empty(&mut self) -> EntityWorldMut { + pub fn spawn_empty(&mut self) -> EntityWorldMut<'_> { self.flush(); let entity = self.entities.alloc(); // SAFETY: entity was just allocated @@ -1155,7 +1155,7 @@ impl World { /// assert_eq!(position.x, 2.0); /// ``` #[track_caller] - pub fn spawn(&mut self, bundle: B) -> EntityWorldMut { + pub fn spawn(&mut self, bundle: B) -> EntityWorldMut<'_> { self.spawn_with_caller(bundle, MaybeLocation::caller()) } @@ -1163,7 +1163,7 @@ impl World { &mut self, bundle: B, caller: MaybeLocation, - ) -> EntityWorldMut { + ) -> EntityWorldMut<'_> { self.flush(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); @@ -1192,7 +1192,7 @@ impl World { &mut self, entity: Entity, caller: MaybeLocation, - ) -> EntityWorldMut { + ) -> EntityWorldMut<'_> { let archetype = self.archetypes.empty_mut(); // PERF: consider avoiding allocating entities in the empty archetype unless needed let table_row = self.storages.tables[archetype.table_id()].allocate(entity); @@ -1279,7 +1279,7 @@ impl World { pub fn get_mut>( &mut self, entity: Entity, - ) -> Option> { + ) -> Option> { self.get_entity_mut(entity).ok()?.into_mut() } @@ -1980,7 +1980,7 @@ impl World { /// use [`get_resource_or_insert_with`](World::get_resource_or_insert_with). #[inline] #[track_caller] - pub fn resource_ref(&self) -> Ref { + pub fn resource_ref(&self) -> Ref<'_, R> { match self.get_resource_ref() { Some(x) => x, None => panic!( @@ -2028,7 +2028,7 @@ impl World { /// Gets a reference including change detection to the resource of the given type if it exists. #[inline] - pub fn get_resource_ref(&self) -> Option> { + pub fn get_resource_ref(&self) -> Option> { // SAFETY: // - `as_unsafe_world_cell_readonly` gives permission to access everything immutably // - `&self` ensures nothing in world is borrowed mutably diff --git a/crates/bevy_encase_derive/Cargo.toml b/crates/bevy_encase_derive/Cargo.toml index 9e05bc7a853fe..d700b7d11bfa2 100644 --- a/crates/bevy_encase_derive/Cargo.toml +++ b/crates/bevy_encase_derive/Cargo.toml @@ -13,7 +13,7 @@ proc-macro = true [dependencies] bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.17.0-dev" } -encase_derive_impl = "0.10" +encase_derive_impl = "0.11" [lints] workspace = true diff --git a/crates/bevy_feathers/Cargo.toml b/crates/bevy_feathers/Cargo.toml index 8f65f659b04e2..2d7682f0a75fd 100644 --- a/crates/bevy_feathers/Cargo.toml +++ b/crates/bevy_feathers/Cargo.toml @@ -30,14 +30,13 @@ bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [ ] } bevy_ui_render = { path = "../bevy_ui_render", version = "0.17.0-dev" } bevy_window = { path = "../bevy_window", version = "0.17.0-dev" } -bevy_winit = { path = "../bevy_winit", version = "0.17.0-dev" } # other accesskit = "0.21" [features] default = [] -custom_cursor = ["bevy_winit/custom_cursor"] +custom_cursor = ["bevy_window/custom_cursor"] [lints] workspace = true diff --git a/crates/bevy_feathers/src/alpha_pattern.rs b/crates/bevy_feathers/src/alpha_pattern.rs index 4f70346535055..b534ca9e155ac 100644 --- a/crates/bevy_feathers/src/alpha_pattern.rs +++ b/crates/bevy_feathers/src/alpha_pattern.rs @@ -1,6 +1,8 @@ use bevy_asset::{Asset, Assets}; -use bevy_ecs::{component::Component, lifecycle::HookContext, world::DeferredWorld}; -use bevy_reflect::TypePath; +use bevy_ecs::{ + component::Component, lifecycle::HookContext, reflect::ReflectComponent, world::DeferredWorld, +}; +use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath}; use bevy_render::render_resource::{AsBindGroup, ShaderRef}; use bevy_ui_render::ui_material::{MaterialNode, UiMaterial}; @@ -14,7 +16,8 @@ impl UiMaterial for AlphaPatternMaterial { } /// Marker that tells us we want to fill in the [`MaterialNode`] with the alpha material. -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Default)] #[require(MaterialNode)] #[component(on_add = on_add_alpha_pattern)] pub(crate) struct AlphaPattern; diff --git a/crates/bevy_feathers/src/controls/button.rs b/crates/bevy_feathers/src/controls/button.rs index 70b28df47790b..3010cecfdc962 100644 --- a/crates/bevy_feathers/src/controls/button.rs +++ b/crates/bevy_feathers/src/controls/button.rs @@ -5,10 +5,12 @@ use bevy_ecs::{ entity::Entity, lifecycle::RemovedComponents, query::{Added, Changed, Has, Or}, + reflect::ReflectComponent, schedule::IntoScheduleConfigs, system::{Commands, In, Query}, }; use bevy_picking::{hover::Hovered, PickingSystems}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_scene2::{prelude::*, template_value}; use bevy_ui::{AlignItems, InteractionDisabled, JustifyContent, Node, Pressed, UiRect, Val}; @@ -24,7 +26,8 @@ use bevy_input_focus::tab_navigation::TabIndex; /// Color variants for buttons. This also functions as a component used by the dynamic styling /// system to identify which entities are buttons. -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] pub enum ButtonVariant { /// The standard button appearance #[default] diff --git a/crates/bevy_feathers/src/controls/checkbox.rs b/crates/bevy_feathers/src/controls/checkbox.rs index 827cd107d0557..9cefa3e96a80f 100644 --- a/crates/bevy_feathers/src/controls/checkbox.rs +++ b/crates/bevy_feathers/src/controls/checkbox.rs @@ -6,12 +6,14 @@ use bevy_ecs::{ hierarchy::Children, lifecycle::RemovedComponents, query::{Added, Changed, Has, Or, With}, + reflect::ReflectComponent, schedule::IntoScheduleConfigs, system::{Commands, In, Query}, }; use bevy_input_focus::tab_navigation::TabIndex; use bevy_math::Rot2; use bevy_picking::{hover::Hovered, PickingSystems}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::view::Visibility; use bevy_scene2::prelude::*; use bevy_ui::{ @@ -35,15 +37,18 @@ pub struct CheckboxProps { } /// Marker for the checkbox frame (contains both checkbox and label) -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] struct CheckboxFrame; /// Marker for the checkbox outline -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] struct CheckboxOutline; /// Marker for the checkbox check mark -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] struct CheckboxMark; /// Checkbox scene function. diff --git a/crates/bevy_feathers/src/controls/color_slider.rs b/crates/bevy_feathers/src/controls/color_slider.rs new file mode 100644 index 0000000000000..1f70b2d4c9eec --- /dev/null +++ b/crates/bevy_feathers/src/controls/color_slider.rs @@ -0,0 +1,367 @@ +use core::f32::consts::PI; + +use bevy_app::{Plugin, PreUpdate}; +use bevy_asset::Handle; +use bevy_color::{Alpha, Color, Hsla}; +use bevy_core_widgets::{ + CallbackTemplate, CoreSlider, CoreSliderThumb, SliderRange, SliderValue, TrackClick, + ValueChange, +}; +use bevy_ecs::{ + bundle::Bundle, + component::Component, + entity::Entity, + hierarchy::Children, + query::{Changed, Or, With}, + schedule::IntoScheduleConfigs, + system::{In, Query}, +}; +use bevy_input_focus::tab_navigation::TabIndex; +use bevy_log::warn_once; +use bevy_picking::PickingSystems; +use bevy_scene2::{bsn, template_value, Scene}; +use bevy_ui::{ + AlignItems, BackgroundColor, BackgroundGradient, BorderColor, BorderRadius, ColorStop, Display, + FlexDirection, Gradient, InterpolationColorSpace, LinearGradient, Node, Outline, PositionType, + UiRect, UiTransform, Val, Val2, ZIndex, +}; +use bevy_ui_render::ui_material::MaterialNode; + +use crate::{ + alpha_pattern::{AlphaPattern, AlphaPatternMaterial}, + cursor::EntityCursor, + palette, + rounded_corners::RoundedCorners, +}; + +const SLIDER_HEIGHT: f32 = 16.0; +const TRACK_PADDING: f32 = 3.0; +const TRACK_RADIUS: f32 = SLIDER_HEIGHT * 0.5 - TRACK_PADDING; +const THUMB_SIZE: f32 = SLIDER_HEIGHT - 2.0; + +/// Indicates which color channel we want to edit. +#[derive(Component, Default, Clone)] +pub enum ColorChannel { + /// Editing the RGB red channel (0..=1) + #[default] + Red, + /// Editing the RGB green channel (0..=1) + Green, + /// Editing the RGB blue channel (0..=1) + Blue, + /// Editing the hue channel (0..=360) + HslHue, + /// Editing the chroma / saturation channel (0..=1) + HslSaturation, + /// Editing the luminance channel (0..=1) + HslLightness, + /// Editing the alpha channel (0..=1) + Alpha, +} + +impl ColorChannel { + /// Return the range of this color channel. + pub fn range(&self) -> SliderRange { + match self { + ColorChannel::Red + | ColorChannel::Green + | ColorChannel::Blue + | ColorChannel::Alpha + | ColorChannel::HslSaturation + | ColorChannel::HslLightness => SliderRange::new(0., 1.), + ColorChannel::HslHue => SliderRange::new(0., 360.), + } + } + + /// Return the color endpoints and midpoint of the gradient. This is determined by both the + /// channel being edited and the base color. + pub fn gradient_ends(&self, base_color: Color) -> (Color, Color, Color) { + match self { + ColorChannel::Red => { + let base_rgb = base_color.to_srgba(); + ( + Color::srgb(0.0, base_rgb.green, base_rgb.blue), + Color::srgb(0.5, base_rgb.green, base_rgb.blue), + Color::srgb(1.0, base_rgb.green, base_rgb.blue), + ) + } + + ColorChannel::Green => { + let base_rgb = base_color.to_srgba(); + ( + Color::srgb(base_rgb.red, 0.0, base_rgb.blue), + Color::srgb(base_rgb.red, 0.5, base_rgb.blue), + Color::srgb(base_rgb.red, 1.0, base_rgb.blue), + ) + } + + ColorChannel::Blue => { + let base_rgb = base_color.to_srgba(); + ( + Color::srgb(base_rgb.red, base_rgb.green, 0.0), + Color::srgb(base_rgb.red, base_rgb.green, 0.5), + Color::srgb(base_rgb.red, base_rgb.green, 1.0), + ) + } + + ColorChannel::HslHue => ( + Color::hsl(0.0 + 0.0001, 1.0, 0.5), + Color::hsl(180.0, 1.0, 0.5), + Color::hsl(360.0 - 0.0001, 1.0, 0.5), + ), + + ColorChannel::HslSaturation => { + let base_hsla: Hsla = base_color.into(); + ( + Color::hsl(base_hsla.hue, 0.0, base_hsla.lightness), + Color::hsl(base_hsla.hue, 0.5, base_hsla.lightness), + Color::hsl(base_hsla.hue, 1.0, base_hsla.lightness), + ) + } + + ColorChannel::HslLightness => { + let base_hsla: Hsla = base_color.into(); + ( + Color::hsl(base_hsla.hue, base_hsla.saturation, 0.0), + Color::hsl(base_hsla.hue, base_hsla.saturation, 0.5), + Color::hsl(base_hsla.hue, base_hsla.saturation, 1.0), + ) + } + + ColorChannel::Alpha => ( + base_color.with_alpha(0.), + base_color.with_alpha(0.5), + base_color.with_alpha(1.), + ), + } + } +} + +/// Used to store the color channels that we are not editing: the components of the color +/// that are constant for this slider. +#[derive(Component, Default, Clone)] +pub struct SliderBaseColor(pub Color); + +/// Color slider template properties, passed to [`color_slider`] function. +pub struct ColorSliderProps { + /// Slider current value + pub value: f32, + /// On-change handler + pub on_change: CallbackTemplate>>, + /// Which color component we're editing + pub channel: ColorChannel, +} + +impl Default for ColorSliderProps { + fn default() -> Self { + Self { + value: 0.0, + on_change: CallbackTemplate::Ignore, + channel: ColorChannel::Alpha, + } + } +} + +/// A color slider widget. +#[derive(Component, Default, Clone)] +#[require(SliderBaseColor(Color::WHITE))] +pub struct ColorSlider { + /// Which channel is being edited by this slider. + pub channel: ColorChannel, +} + +/// Marker for the track +#[derive(Component, Default, Clone)] +struct ColorSliderTrack; + +/// Marker for the thumb +#[derive(Component, Default, Clone)] +struct ColorSliderThumb; + +/// Spawn a new slider widget. +/// +/// # Arguments +/// +/// * `props` - construction properties for the slider. +/// * `overrides` - a bundle of components that are merged in with the normal slider components. +pub fn color_slider(props: ColorSliderProps) -> impl Scene { + let channel_range = props.channel.range(); + bsn! { + Node { + display: Display::Flex, + flex_direction: FlexDirection::Row, + height: Val::Px(SLIDER_HEIGHT), + align_items: AlignItems::Stretch, + flex_grow: 1.0, + } + CoreSlider { + on_change: {props.on_change.clone()}, + track_click: TrackClick::Snap, + } + ColorSlider { + channel: {props.channel.clone()}, + } + SliderValue({props.value}) + template_value(channel_range) + EntityCursor::System(bevy_window::SystemCursorIcon::Pointer) + TabIndex(0) + [ + // track + Node { + position_type: PositionType::Absolute, + left: Val::Px(0.), + right: Val::Px(0.), + top: Val::Px(TRACK_PADDING), + bottom: Val::Px(TRACK_PADDING), + } + template_value(RoundedCorners::All.to_border_radius(TRACK_RADIUS)) + ColorSliderTrack + AlphaPattern + MaterialNode::(Handle::default()) + [ + // Left endcap + ( + Node { + width: Val::Px({THUMB_SIZE * 0.5}), + } + template_value(RoundedCorners::Left.to_border_radius(TRACK_RADIUS)) + BackgroundColor({palette::X_AXIS}) + ), + // Track with gradient + ( + Node { + flex_grow: 1.0, + } + BackgroundGradient({vec![Gradient::Linear(LinearGradient { + angle: PI * 0.5, + stops: vec![ + ColorStop::new(Color::NONE, Val::Percent(0.)), + ColorStop::new(Color::NONE, Val::Percent(50.)), + ColorStop::new(Color::NONE, Val::Percent(100.)), + ], + color_space: InterpolationColorSpace::Srgba, + })]}) + ZIndex(1) + [ + Node { + position_type: PositionType::Absolute, + left: Val::Percent(0.), + top: Val::Percent(50.), + width: Val::Px(THUMB_SIZE), + height: Val::Px(THUMB_SIZE), + border: UiRect::all(Val::Px(2.0)), + } + CoreSliderThumb + ColorSliderThumb + BorderRadius::MAX + BorderColor::all(palette::WHITE) + Outline { + width: Val::Px(1.), + offset: Val::Px(0.), + color: palette::BLACK + } + UiTransform::from_translation(Val2::new( + Val::Percent(-50.0), + Val::Percent(-50.0), + )) + ] + ), + // Right endcap + ( + Node { + width: Val::Px({THUMB_SIZE * 0.5}), + } + template_value(RoundedCorners::Right.to_border_radius(TRACK_RADIUS)) + BackgroundColor({palette::Z_AXIS}) + ), + ] + ] + } +} + +fn update_slider_pos( + mut q_sliders: Query< + (Entity, &SliderValue, &SliderRange), + ( + With, + Or<(Changed, Changed)>, + ), + >, + q_children: Query<&Children>, + mut q_slider_thumb: Query<&mut Node, With>, +) { + for (slider_ent, value, range) in q_sliders.iter_mut() { + for child in q_children.iter_descendants(slider_ent) { + if let Ok(mut thumb_node) = q_slider_thumb.get_mut(child) { + thumb_node.left = Val::Percent(range.thumb_position(value.0) * 100.0); + } + } + } +} + +fn update_track_color( + mut q_sliders: Query<(Entity, &ColorSlider, &SliderBaseColor), Changed>, + q_children: Query<&Children>, + q_track: Query<(), With>, + mut q_background: Query<&mut BackgroundColor>, + mut q_gradient: Query<&mut BackgroundGradient>, +) { + for (slider_ent, slider, SliderBaseColor(base_color)) in q_sliders.iter_mut() { + let (start, middle, end) = slider.channel.gradient_ends(*base_color); + if let Some(track_ent) = q_children + .iter_descendants(slider_ent) + .find(|ent| q_track.contains(*ent)) + { + let Ok(track_children) = q_children.get(track_ent) else { + continue; + }; + + if let Ok(mut cap_bg) = q_background.get_mut(track_children[0]) { + cap_bg.0 = start; + } + + if let Ok(mut gradient) = q_gradient.get_mut(track_children[1]) + && let [Gradient::Linear(linear_gradient)] = &mut gradient.0[..] + { + linear_gradient.stops[0].color = start; + linear_gradient.stops[1].color = middle; + linear_gradient.stops[2].color = end; + linear_gradient.color_space = match slider.channel { + ColorChannel::Red | ColorChannel::Green | ColorChannel::Blue => { + InterpolationColorSpace::Srgba + } + ColorChannel::HslHue + | ColorChannel::HslLightness + | ColorChannel::HslSaturation => InterpolationColorSpace::Hsla, + ColorChannel::Alpha => match base_color { + Color::Srgba(_) => InterpolationColorSpace::Srgba, + Color::LinearRgba(_) => InterpolationColorSpace::LinearRgba, + Color::Oklaba(_) => InterpolationColorSpace::Oklaba, + Color::Oklcha(_) => InterpolationColorSpace::OklchaLong, + Color::Hsla(_) | Color::Hsva(_) => InterpolationColorSpace::Hsla, + _ => { + warn_once!("Unsupported color space for ColorSlider: {:?}", base_color); + InterpolationColorSpace::Srgba + } + }, + }; + } + + if let Ok(mut cap_bg) = q_background.get_mut(track_children[2]) { + cap_bg.0 = end; + } + } + } +} + +/// Plugin which registers the systems for updating the slider styles. +pub struct ColorSliderPlugin; + +impl Plugin for ColorSliderPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.add_systems( + PreUpdate, + (update_slider_pos, update_track_color).in_set(PickingSystems::Last), + ); + } +} diff --git a/crates/bevy_feathers/src/controls/color_swatch.rs b/crates/bevy_feathers/src/controls/color_swatch.rs index cda4a2a8d3708..813aed32c20e2 100644 --- a/crates/bevy_feathers/src/controls/color_swatch.rs +++ b/crates/bevy_feathers/src/controls/color_swatch.rs @@ -1,18 +1,21 @@ use bevy_color::Alpha; -use bevy_ecs::component::Component; +use bevy_ecs::{component::Component, reflect::ReflectComponent}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_scene2::{bsn, Scene}; use bevy_ui::{BackgroundColor, BorderRadius, Node, PositionType, Val}; use crate::{alpha_pattern::AlphaPattern, constants::size, palette}; /// Marker identifying a color swatch. -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] pub struct ColorSwatch; /// Marker identifying the color swatch foreground, the piece that actually displays the color /// in front of the alpha pattern. This exists so that users can reach in and change the color /// dynamically. -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] pub struct ColorSwatchFg; /// Template function to spawn a color swatch. diff --git a/crates/bevy_feathers/src/controls/mod.rs b/crates/bevy_feathers/src/controls/mod.rs index 29a31deb9309f..c4449d6a1b0a6 100644 --- a/crates/bevy_feathers/src/controls/mod.rs +++ b/crates/bevy_feathers/src/controls/mod.rs @@ -3,17 +3,23 @@ use bevy_app::Plugin; mod button; mod checkbox; +mod color_slider; mod color_swatch; mod radio; mod slider; mod toggle_switch; +mod virtual_keyboard; pub use button::{button, tool_button, ButtonPlugin, ButtonProps, ButtonVariant}; pub use checkbox::{checkbox, CheckboxPlugin, CheckboxProps}; +pub use color_slider::{ + color_slider, ColorChannel, ColorSlider, ColorSliderPlugin, ColorSliderProps, SliderBaseColor, +}; pub use color_swatch::{color_swatch, ColorSwatch, ColorSwatchFg}; pub use radio::{radio, RadioPlugin}; pub use slider::{slider, SliderPlugin, SliderProps}; pub use toggle_switch::{toggle_switch, ToggleSwitchPlugin, ToggleSwitchProps}; +pub use virtual_keyboard::virtual_keyboard; /// Plugin which registers all `bevy_feathers` controls. pub struct ControlsPlugin; @@ -23,6 +29,7 @@ impl Plugin for ControlsPlugin { app.add_plugins(( ButtonPlugin, CheckboxPlugin, + ColorSliderPlugin, RadioPlugin, SliderPlugin, ToggleSwitchPlugin, diff --git a/crates/bevy_feathers/src/controls/radio.rs b/crates/bevy_feathers/src/controls/radio.rs index 26413ae24bfb4..3049bbb3945f9 100644 --- a/crates/bevy_feathers/src/controls/radio.rs +++ b/crates/bevy_feathers/src/controls/radio.rs @@ -6,11 +6,13 @@ use bevy_ecs::{ hierarchy::Children, lifecycle::RemovedComponents, query::{Added, Changed, Has, Or, With}, + reflect::ReflectComponent, schedule::IntoScheduleConfigs, system::{Commands, Query}, }; use bevy_input_focus::tab_navigation::TabIndex; use bevy_picking::{hover::Hovered, PickingSystems}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::view::Visibility; use bevy_scene2::prelude::*; use bevy_ui::{ @@ -27,11 +29,13 @@ use crate::{ }; /// Marker for the radio outline -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] struct RadioOutline; /// Marker for the radio check mark -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] struct RadioMark; /// Radio scene function. diff --git a/crates/bevy_feathers/src/controls/slider.rs b/crates/bevy_feathers/src/controls/slider.rs index 21912240775c7..8855fe077f873 100644 --- a/crates/bevy_feathers/src/controls/slider.rs +++ b/crates/bevy_feathers/src/controls/slider.rs @@ -11,11 +11,13 @@ use bevy_ecs::{ hierarchy::Children, lifecycle::RemovedComponents, query::{Added, Changed, Has, Or, Spawned, With}, + reflect::ReflectComponent, schedule::IntoScheduleConfigs, system::{In, Query, Res}, }; use bevy_input_focus::tab_navigation::TabIndex; use bevy_picking::PickingSystems; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_scene2::{prelude::*, template_value}; use bevy_ui::{ widget::Text, AlignItems, BackgroundGradient, ColorStop, Display, FlexDirection, Gradient, @@ -55,11 +57,13 @@ impl Default for SliderProps { } } -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] struct SliderStyle; /// Marker for the text -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] struct SliderValueText; /// Slider scene function. diff --git a/crates/bevy_feathers/src/controls/toggle_switch.rs b/crates/bevy_feathers/src/controls/toggle_switch.rs index 95abb4e7aa522..673528bef75a6 100644 --- a/crates/bevy_feathers/src/controls/toggle_switch.rs +++ b/crates/bevy_feathers/src/controls/toggle_switch.rs @@ -8,12 +8,14 @@ use bevy_ecs::{ hierarchy::Children, lifecycle::RemovedComponents, query::{Added, Changed, Has, Or, With}, + reflect::ReflectComponent, schedule::IntoScheduleConfigs, system::{Commands, In, Query}, world::Mut, }; use bevy_input_focus::tab_navigation::TabIndex; use bevy_picking::{hover::Hovered, PickingSystems}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_scene2::prelude::*; use bevy_ui::{BorderRadius, Checked, InteractionDisabled, Node, PositionType, UiRect, Val}; @@ -32,11 +34,13 @@ pub struct ToggleSwitchProps { } /// Marker for the toggle switch outline -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] struct ToggleSwitchOutline; /// Marker for the toggle switch slide -#[derive(Component, Default, Clone)] +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Clone, Default)] struct ToggleSwitchSlide; /// Toggle switch scene function. diff --git a/crates/bevy_feathers/src/controls/virtual_keyboard.rs b/crates/bevy_feathers/src/controls/virtual_keyboard.rs new file mode 100644 index 0000000000000..b4981aa44bd2a --- /dev/null +++ b/crates/bevy_feathers/src/controls/virtual_keyboard.rs @@ -0,0 +1,45 @@ +use bevy_core_widgets::{Activate, CallbackTemplate}; +use bevy_ecs::{ + component::Component, + system::{In, SystemId}, + template::template, +}; +use bevy_input_focus::tab_navigation::TabGroup; +use bevy_scene2::prelude::*; +use bevy_ui::Node; +use bevy_ui::Val; +use bevy_ui::{widget::Text, FlexDirection}; + +use crate::controls::{button, ButtonProps}; + +/// Function to spawn a virtual keyboard +pub fn virtual_keyboard( + keys: impl Iterator> + Send + Sync + 'static, + on_key_press: SystemId>, +) -> impl Scene { + let children: Vec<_> = keys.map(|row| { + let children: Vec<_> = row.into_iter().map(|(label, key_id)| { + bsn! { + :button(ButtonProps { on_click: CallbackTemplate::SystemId(on_key_press), ..Default::default() }) + template(move |entity| {entity.insert(key_id.clone()); Ok(())}) + [Text::new(label.clone())] + } + }).collect(); + + bsn! { + Node { + flex_direction: FlexDirection::Row, + column_gap: Val::Px(4.), + } + [{ children }] + } + }).collect(); + bsn! { + Node { + flex_direction: FlexDirection::Column, + row_gap: Val::Px(4.), + } + TabGroup::new(0) + [ {children} ] + } +} diff --git a/crates/bevy_feathers/src/cursor.rs b/crates/bevy_feathers/src/cursor.rs index 87a0e93f23063..79b8487cf9a9a 100644 --- a/crates/bevy_feathers/src/cursor.rs +++ b/crates/bevy_feathers/src/cursor.rs @@ -5,7 +5,7 @@ use bevy_ecs::{ entity::Entity, hierarchy::ChildOf, query::{With, Without}, - reflect::ReflectComponent, + reflect::{ReflectComponent, ReflectResource}, resource::Resource, schedule::IntoScheduleConfigs, system::{Commands, Query, Res}, @@ -13,14 +13,14 @@ use bevy_ecs::{ }; use bevy_picking::{hover::HoverMap, pointer::PointerId, PickingSystems}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_window::{SystemCursorIcon, Window}; -use bevy_winit::cursor::CursorIcon; #[cfg(feature = "custom_cursor")] -use bevy_winit::cursor::CustomCursor; +use bevy_window::CustomCursor; +use bevy_window::{CursorIcon, SystemCursorIcon, Window}; /// A resource that specifies the cursor icon to be used when the mouse is not hovering over /// any other entity. This is used to set the default cursor icon for the window. -#[derive(Resource, Debug, Clone, Default)] +#[derive(Resource, Debug, Clone, Default, Reflect)] +#[reflect(Resource, Debug, Default)] pub struct DefaultCursor(pub EntityCursor); /// A component that specifies the cursor shape to be used when the pointer hovers over an entity. @@ -94,10 +94,10 @@ pub(crate) fn update_cursor( .unwrap_or(&r_default_cursor.0); for (entity, prev_cursor) in q_windows.iter() { - if let Some(prev_cursor) = prev_cursor { - if cursor.eq_cursor_icon(prev_cursor) { - continue; - } + if let Some(prev_cursor) = prev_cursor + && cursor.eq_cursor_icon(prev_cursor) + { + continue; } commands.entity(entity).insert(cursor.to_cursor_icon()); } diff --git a/crates/bevy_feathers/src/font_styles.rs b/crates/bevy_feathers/src/font_styles.rs index 9d45c6a40278f..4509946ad9091 100644 --- a/crates/bevy_feathers/src/font_styles.rs +++ b/crates/bevy_feathers/src/font_styles.rs @@ -5,14 +5,17 @@ use bevy_ecs::{ component::Component, lifecycle::Insert, observer::On, - system::{Commands, Query}, + reflect::ReflectComponent, + system::{Commands, Query, Res}, template::GetTemplate, }; +use bevy_reflect::Reflect; use bevy_text::{Font, TextFont}; /// A component which, when inserted on an entity, will load the given font and propagate it /// downward to any child text entity that has the [`ThemedText`](crate::theme::ThemedText) marker. -#[derive(Component, Clone, Debug, GetTemplate)] +#[derive(Component, Clone, Debug, Reflect, GetTemplate)] +#[reflect(Component)] pub struct InheritableFont { /// The font handle or path. pub font: Handle, diff --git a/crates/bevy_feathers/src/handle_or_path.rs b/crates/bevy_feathers/src/handle_or_path.rs index 178d2b13e876e..d6330b38d5d41 100644 --- a/crates/bevy_feathers/src/handle_or_path.rs +++ b/crates/bevy_feathers/src/handle_or_path.rs @@ -1,11 +1,12 @@ //! Provides a way to specify assets either by handle or by path. use bevy_asset::{Asset, Handle}; +use bevy_reflect::Reflect; /// Enum that represents a reference to an asset as either a [`Handle`] or a [`String`] path. /// /// This is useful for when you want to specify an asset, but don't always have convenient /// access to an asset server reference. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Reflect)] pub enum HandleOrPath { /// Specify the asset reference as a handle. Handle(Handle), diff --git a/crates/bevy_feathers/src/lib.rs b/crates/bevy_feathers/src/lib.rs index 56e7af02e20ec..17a9c27ca0b19 100644 --- a/crates/bevy_feathers/src/lib.rs +++ b/crates/bevy_feathers/src/lib.rs @@ -18,7 +18,7 @@ //! Please report issues, submit fixes and propose changes. //! Thanks for stress-testing; let's build something better together. -use bevy_app::{HierarchyPropagatePlugin, Plugin, PostUpdate}; +use bevy_app::{HierarchyPropagatePlugin, Plugin, PostUpdate, Update}; use bevy_asset::embedded_asset; use bevy_ecs::query::With; use bevy_text::{TextColor, TextFont}; @@ -64,8 +64,8 @@ impl Plugin for FeathersPlugin { app.add_plugins(( ControlsPlugin, CursorIconPlugin, - HierarchyPropagatePlugin::>::default(), - HierarchyPropagatePlugin::>::default(), + HierarchyPropagatePlugin::>::new(Update), + HierarchyPropagatePlugin::>::new(Update), UiMaterialPlugin::::default(), )); diff --git a/crates/bevy_feathers/src/theme.rs b/crates/bevy_feathers/src/theme.rs index 56f6cd3192936..78048a471028b 100644 --- a/crates/bevy_feathers/src/theme.rs +++ b/crates/bevy_feathers/src/theme.rs @@ -7,16 +7,19 @@ use bevy_ecs::{ lifecycle::Insert, observer::On, query::Changed, + reflect::{ReflectComponent, ReflectResource}, resource::Resource, system::{Commands, Query, Res}, }; use bevy_log::warn_once; use bevy_platform::collections::HashMap; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_text::TextColor; use bevy_ui::{BackgroundColor, BorderColor}; /// A collection of properties that make up a theme. -#[derive(Default, Clone)] +#[derive(Default, Clone, Reflect, Debug)] +#[reflect(Default, Debug)] pub struct ThemeProps { /// Map of design tokens to colors. pub color: HashMap, @@ -24,7 +27,8 @@ pub struct ThemeProps { } /// The currently selected user interface theme. Overwriting this resource changes the theme. -#[derive(Resource, Default)] +#[derive(Resource, Default, Reflect, Debug)] +#[reflect(Resource, Default, Debug)] pub struct UiTheme(pub ThemeProps); impl UiTheme { @@ -52,6 +56,8 @@ impl UiTheme { #[derive(Component, Clone, Copy, Default)] #[require(BackgroundColor)] #[component(immutable)] +#[derive(Reflect)] +#[reflect(Component, Clone)] pub struct ThemeBackgroundColor(pub &'static str); /// Component which causes the border color of an entity to be set based on a theme color. @@ -59,16 +65,21 @@ pub struct ThemeBackgroundColor(pub &'static str); #[derive(Component, Clone, Copy, Default)] #[require(BorderColor)] #[component(immutable)] +#[derive(Reflect)] +#[reflect(Component, Clone)] pub struct ThemeBorderColor(pub &'static str); /// Component which causes the inherited text color of an entity to be set based on a theme color. #[derive(Component, Clone, Copy, Default)] #[component(immutable)] +#[derive(Reflect)] +#[reflect(Component, Clone)] pub struct ThemeFontColor(pub &'static str); /// A marker component that is used to indicate that the text entity wants to opt-in to using /// inherited text styles. -#[derive(Component, Default, Clone)] +#[derive(Component, Reflect, Default, Clone)] +#[reflect(Component)] pub struct ThemedText; pub(crate) fn update_theme( diff --git a/crates/bevy_gizmos/src/aabb.rs b/crates/bevy_gizmos/src/aabb.rs index 4ac9e5f2ac456..97b4f3e9ebd4c 100644 --- a/crates/bevy_gizmos/src/aabb.rs +++ b/crates/bevy_gizmos/src/aabb.rs @@ -28,19 +28,17 @@ pub struct AabbGizmoPlugin; impl Plugin for AabbGizmoPlugin { fn build(&self, app: &mut bevy_app::App) { - app.register_type::() - .init_gizmo_group::() - .add_systems( - PostUpdate, - ( - draw_aabbs, - draw_all_aabbs.run_if(|config: Res| { - config.config::().1.draw_all - }), - ) - .after(bevy_render::view::VisibilitySystems::CalculateBounds) - .after(TransformSystems::Propagate), - ); + app.init_gizmo_group::().add_systems( + PostUpdate, + ( + draw_aabbs, + draw_all_aabbs.run_if(|config: Res| { + config.config::().1.draw_all + }), + ) + .after(bevy_render::view::VisibilitySystems::CalculateBounds) + .after(TransformSystems::Propagate), + ); } } /// The [`GizmoConfigGroup`] used for debug visualizations of [`Aabb`] components on entities diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index e0f139a1a8662..d13fe8240be78 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -9,7 +9,7 @@ use core::{ use bevy_color::{Color, LinearRgba}; use bevy_ecs::{ - component::{ComponentId, Tick}, + component::Tick, query::FilteredAccessSet, resource::Resource, system::{ @@ -209,7 +209,7 @@ where fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { GizmosState::::init_access( @@ -355,7 +355,7 @@ where } /// Read-only view into the buffers data. - pub fn buffer(&self) -> GizmoBufferView { + pub fn buffer(&self) -> GizmoBufferView<'_> { let GizmoBuffer { list_positions, list_colors, diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 4eeeea508d048..ca3aba088b9a4 100755 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -159,9 +159,7 @@ impl Plugin for GizmoPlugin { embedded_asset!(app, "line_joints.wgsl"); } - app.register_type::() - .register_type::() - .init_asset::() + app.init_asset::() .init_resource::() // We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist. .init_gizmo_group::(); diff --git a/crates/bevy_gizmos/src/light.rs b/crates/bevy_gizmos/src/light.rs index 1bd6ee3cacb05..4fdd784ac0ec5 100644 --- a/crates/bevy_gizmos/src/light.rs +++ b/crates/bevy_gizmos/src/light.rs @@ -116,18 +116,16 @@ pub struct LightGizmoPlugin; impl Plugin for LightGizmoPlugin { fn build(&self, app: &mut bevy_app::App) { - app.register_type::() - .init_gizmo_group::() - .add_systems( - PostUpdate, - ( - draw_lights, - draw_all_lights.run_if(|config: Res| { - config.config::().1.draw_all - }), - ) - .after(TransformSystems::Propagate), - ); + app.init_gizmo_group::().add_systems( + PostUpdate, + ( + draw_lights, + draw_all_lights.run_if(|config: Res| { + config.config::().1.draw_all + }), + ) + .after(TransformSystems::Propagate), + ); } } diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 7c4216d8897f2..c8b486f41d13d 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -26,14 +26,14 @@ bevy_color = { path = "../bevy_color", version = "0.17.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } bevy_image = { path = "../bevy_image", version = "0.17.0-dev" } +bevy_light = { path = "../bevy_light", version = "0.17.0-dev" } +bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" } bevy_math = { path = "../bevy_math", version = "0.17.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" } bevy_pbr = { path = "../bevy_pbr", version = "0.17.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } bevy_render = { path = "../bevy_render", version = "0.17.0-dev" } -bevy_scene = { path = "../bevy_scene", version = "0.17.0-dev", features = [ - "bevy_render", -] } +bevy_scene = { path = "../bevy_scene", version = "0.17.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" } bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index 6b90a4d99bcac..c53e1a6b7617e 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -208,13 +208,7 @@ impl GltfPlugin { impl Plugin for GltfPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_asset::() + app.init_asset::() .init_asset::() .init_asset::() .init_asset::() diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index 9597f8699bb84..fce607437db57 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -15,6 +15,10 @@ use bevy_asset::{ io::Reader, AssetLoadError, AssetLoader, Handle, LoadContext, ReadAssetBytesError, RenderAssetUsages, }; +use bevy_camera::{ + primitives::Aabb, visibility::Visibility, Camera, OrthographicProjection, + PerspectiveProjection, Projection, ScalingMode, +}; use bevy_color::{Color, LinearRgba}; use bevy_core_pipeline::prelude::Camera3d; use bevy_ecs::{ @@ -27,25 +31,18 @@ use bevy_image::{ CompressedImageFormats, Image, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor, ImageType, TextureError, }; +use bevy_light::{DirectionalLight, PointLight, SpotLight}; use bevy_math::{Mat4, Vec3}; use bevy_mesh::{ morph::{MeshMorphWeights, MorphAttributes, MorphTargetImage, MorphWeights}, skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, - Indices, Mesh, MeshVertexAttribute, PrimitiveTopology, VertexAttributeValues, + Indices, Mesh, Mesh3d, MeshVertexAttribute, PrimitiveTopology, VertexAttributeValues, }; #[cfg(feature = "pbr_transmission_textures")] use bevy_pbr::UvChannel; -use bevy_pbr::{ - DirectionalLight, MeshMaterial3d, PointLight, SpotLight, StandardMaterial, MAX_JOINTS, -}; +use bevy_pbr::{MeshMaterial3d, StandardMaterial, MAX_JOINTS}; use bevy_platform::collections::{HashMap, HashSet}; -use bevy_render::{ - camera::{Camera, OrthographicProjection, PerspectiveProjection, Projection, ScalingMode}, - mesh::Mesh3d, - primitives::Aabb, - render_resource::Face, - view::Visibility, -}; +use bevy_render::render_resource::Face; use bevy_scene::Scene; #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::IoTaskPool; @@ -1465,49 +1462,49 @@ fn load_node( } // create camera node - if settings.load_cameras { - if let Some(camera) = gltf_node.camera() { - let projection = match camera.projection() { - gltf::camera::Projection::Orthographic(orthographic) => { - let xmag = orthographic.xmag(); - let orthographic_projection = OrthographicProjection { - near: orthographic.znear(), - far: orthographic.zfar(), - scaling_mode: ScalingMode::FixedHorizontal { - viewport_width: xmag, - }, - ..OrthographicProjection::default_3d() - }; - Projection::Orthographic(orthographic_projection) + if settings.load_cameras + && let Some(camera) = gltf_node.camera() + { + let projection = match camera.projection() { + gltf::camera::Projection::Orthographic(orthographic) => { + let xmag = orthographic.xmag(); + let orthographic_projection = OrthographicProjection { + near: orthographic.znear(), + far: orthographic.zfar(), + scaling_mode: ScalingMode::FixedHorizontal { + viewport_width: xmag, + }, + ..OrthographicProjection::default_3d() + }; + Projection::Orthographic(orthographic_projection) + } + gltf::camera::Projection::Perspective(perspective) => { + let mut perspective_projection: PerspectiveProjection = PerspectiveProjection { + fov: perspective.yfov(), + near: perspective.znear(), + ..Default::default() + }; + if let Some(zfar) = perspective.zfar() { + perspective_projection.far = zfar; } - gltf::camera::Projection::Perspective(perspective) => { - let mut perspective_projection: PerspectiveProjection = PerspectiveProjection { - fov: perspective.yfov(), - near: perspective.znear(), - ..Default::default() - }; - if let Some(zfar) = perspective.zfar() { - perspective_projection.far = zfar; - } - if let Some(aspect_ratio) = perspective.aspect_ratio() { - perspective_projection.aspect_ratio = aspect_ratio; - } - Projection::Perspective(perspective_projection) + if let Some(aspect_ratio) = perspective.aspect_ratio() { + perspective_projection.aspect_ratio = aspect_ratio; } - }; + Projection::Perspective(perspective_projection) + } + }; - node.insert(( - Camera3d::default(), - projection, - transform, - Camera { - is_active: !*active_camera_found, - ..Default::default() - }, - )); + node.insert(( + Camera3d::default(), + projection, + transform, + Camera { + is_active: !*active_camera_found, + ..Default::default() + }, + )); - *active_camera_found = true; - } + *active_camera_found = true; } // Map node index to entity @@ -1517,161 +1514,161 @@ fn load_node( node.with_children(|parent| { // Only include meshes in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_meshes flag - if !settings.load_meshes.is_empty() { - if let Some(mesh) = gltf_node.mesh() { - // append primitives - for primitive in mesh.primitives() { - let material = primitive.material(); - let material_label = material_label(&material, is_scale_inverted).to_string(); - - // This will make sure we load the default material now since it would not have been - // added when iterating over all the gltf materials (since the default material is - // not explicitly listed in the gltf). - // It also ensures an inverted scale copy is instantiated if required. - if !root_load_context.has_labeled_asset(&material_label) - && !load_context.has_labeled_asset(&material_label) - { - load_material(&material, load_context, document, is_scale_inverted); - } + if !settings.load_meshes.is_empty() + && let Some(mesh) = gltf_node.mesh() + { + // append primitives + for primitive in mesh.primitives() { + let material = primitive.material(); + let material_label = material_label(&material, is_scale_inverted).to_string(); + + // This will make sure we load the default material now since it would not have been + // added when iterating over all the gltf materials (since the default material is + // not explicitly listed in the gltf). + // It also ensures an inverted scale copy is instantiated if required. + if !root_load_context.has_labeled_asset(&material_label) + && !load_context.has_labeled_asset(&material_label) + { + load_material(&material, load_context, document, is_scale_inverted); + } - let primitive_label = GltfAssetLabel::Primitive { - mesh: mesh.index(), - primitive: primitive.index(), - }; - let bounds = primitive.bounding_box(); - - let mut mesh_entity = parent.spawn(( - // TODO: handle missing label handle errors here? - Mesh3d(load_context.get_label_handle(primitive_label.to_string())), - MeshMaterial3d::( - load_context.get_label_handle(&material_label), - ), - )); - - let target_count = primitive.morph_targets().len(); - if target_count != 0 { - let weights = match mesh.weights() { - Some(weights) => weights.to_vec(), - None => vec![0.0; target_count], - }; + let primitive_label = GltfAssetLabel::Primitive { + mesh: mesh.index(), + primitive: primitive.index(), + }; + let bounds = primitive.bounding_box(); + + let mut mesh_entity = parent.spawn(( + // TODO: handle missing label handle errors here? + Mesh3d(load_context.get_label_handle(primitive_label.to_string())), + MeshMaterial3d::( + load_context.get_label_handle(&material_label), + ), + )); - if morph_weights.is_none() { - morph_weights = Some(weights.clone()); - } + let target_count = primitive.morph_targets().len(); + if target_count != 0 { + let weights = match mesh.weights() { + Some(weights) => weights.to_vec(), + None => vec![0.0; target_count], + }; - // unwrap: the parent's call to `MeshMorphWeights::new` - // means this code doesn't run if it returns an `Err`. - // According to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#morph-targets - // they should all have the same length. - // > All morph target accessors MUST have the same count as - // > the accessors of the original primitive. - mesh_entity.insert(MeshMorphWeights::new(weights).unwrap()); + if morph_weights.is_none() { + morph_weights = Some(weights.clone()); } - mesh_entity.insert(Aabb::from_min_max( - Vec3::from_slice(&bounds.min), - Vec3::from_slice(&bounds.max), - )); - if let Some(extras) = primitive.extras() { - mesh_entity.insert(GltfExtras { - value: extras.get().to_string(), - }); - } + // unwrap: the parent's call to `MeshMorphWeights::new` + // means this code doesn't run if it returns an `Err`. + // According to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#morph-targets + // they should all have the same length. + // > All morph target accessors MUST have the same count as + // > the accessors of the original primitive. + mesh_entity.insert(MeshMorphWeights::new(weights).unwrap()); + } + mesh_entity.insert(Aabb::from_min_max( + Vec3::from_slice(&bounds.min), + Vec3::from_slice(&bounds.max), + )); - if let Some(extras) = mesh.extras() { - mesh_entity.insert(GltfMeshExtras { - value: extras.get().to_string(), - }); - } + if let Some(extras) = primitive.extras() { + mesh_entity.insert(GltfExtras { + value: extras.get().to_string(), + }); + } - if let Some(extras) = material.extras() { - mesh_entity.insert(GltfMaterialExtras { - value: extras.get().to_string(), - }); - } + if let Some(extras) = mesh.extras() { + mesh_entity.insert(GltfMeshExtras { + value: extras.get().to_string(), + }); + } - if let Some(name) = mesh.name() { - mesh_entity.insert(GltfMeshName(name.to_string())); - } + if let Some(extras) = material.extras() { + mesh_entity.insert(GltfMaterialExtras { + value: extras.get().to_string(), + }); + } - if let Some(name) = material.name() { - mesh_entity.insert(GltfMaterialName(name.to_string())); - } + if let Some(name) = mesh.name() { + mesh_entity.insert(GltfMeshName(name.to_string())); + } - mesh_entity.insert(Name::new(primitive_name(&mesh, &material))); + if let Some(name) = material.name() { + mesh_entity.insert(GltfMaterialName(name.to_string())); + } - // Mark for adding skinned mesh - if let Some(skin) = gltf_node.skin() { - entity_to_skin_index_map.insert(mesh_entity.id(), skin.index()); - } + mesh_entity.insert(Name::new(primitive_name(&mesh, &material))); + + // Mark for adding skinned mesh + if let Some(skin) = gltf_node.skin() { + entity_to_skin_index_map.insert(mesh_entity.id(), skin.index()); } } } - if settings.load_lights { - if let Some(light) = gltf_node.light() { - match light.kind() { - gltf::khr_lights_punctual::Kind::Directional => { - let mut entity = parent.spawn(DirectionalLight { - color: Color::srgb_from_array(light.color()), - // NOTE: KHR_punctual_lights defines the intensity units for directional - // lights in lux (lm/m^2) which is what we need. - illuminance: light.intensity(), - ..Default::default() + if settings.load_lights + && let Some(light) = gltf_node.light() + { + match light.kind() { + gltf::khr_lights_punctual::Kind::Directional => { + let mut entity = parent.spawn(DirectionalLight { + color: Color::srgb_from_array(light.color()), + // NOTE: KHR_punctual_lights defines the intensity units for directional + // lights in lux (lm/m^2) which is what we need. + illuminance: light.intensity(), + ..Default::default() + }); + if let Some(name) = light.name() { + entity.insert(Name::new(name.to_string())); + } + if let Some(extras) = light.extras() { + entity.insert(GltfExtras { + value: extras.get().to_string(), }); - if let Some(name) = light.name() { - entity.insert(Name::new(name.to_string())); - } - if let Some(extras) = light.extras() { - entity.insert(GltfExtras { - value: extras.get().to_string(), - }); - } } - gltf::khr_lights_punctual::Kind::Point => { - let mut entity = parent.spawn(PointLight { - color: Color::srgb_from_array(light.color()), - // NOTE: KHR_punctual_lights defines the intensity units for point lights in - // candela (lm/sr) which is luminous intensity and we need luminous power. - // For a point light, luminous power = 4 * pi * luminous intensity - intensity: light.intensity() * core::f32::consts::PI * 4.0, - range: light.range().unwrap_or(20.0), - radius: 0.0, - ..Default::default() + } + gltf::khr_lights_punctual::Kind::Point => { + let mut entity = parent.spawn(PointLight { + color: Color::srgb_from_array(light.color()), + // NOTE: KHR_punctual_lights defines the intensity units for point lights in + // candela (lm/sr) which is luminous intensity and we need luminous power. + // For a point light, luminous power = 4 * pi * luminous intensity + intensity: light.intensity() * core::f32::consts::PI * 4.0, + range: light.range().unwrap_or(20.0), + radius: 0.0, + ..Default::default() + }); + if let Some(name) = light.name() { + entity.insert(Name::new(name.to_string())); + } + if let Some(extras) = light.extras() { + entity.insert(GltfExtras { + value: extras.get().to_string(), }); - if let Some(name) = light.name() { - entity.insert(Name::new(name.to_string())); - } - if let Some(extras) = light.extras() { - entity.insert(GltfExtras { - value: extras.get().to_string(), - }); - } } - gltf::khr_lights_punctual::Kind::Spot { - inner_cone_angle, - outer_cone_angle, - } => { - let mut entity = parent.spawn(SpotLight { - color: Color::srgb_from_array(light.color()), - // NOTE: KHR_punctual_lights defines the intensity units for spot lights in - // candela (lm/sr) which is luminous intensity and we need luminous power. - // For a spot light, we map luminous power = 4 * pi * luminous intensity - intensity: light.intensity() * core::f32::consts::PI * 4.0, - range: light.range().unwrap_or(20.0), - radius: light.range().unwrap_or(0.0), - inner_angle: inner_cone_angle, - outer_angle: outer_cone_angle, - ..Default::default() + } + gltf::khr_lights_punctual::Kind::Spot { + inner_cone_angle, + outer_cone_angle, + } => { + let mut entity = parent.spawn(SpotLight { + color: Color::srgb_from_array(light.color()), + // NOTE: KHR_punctual_lights defines the intensity units for spot lights in + // candela (lm/sr) which is luminous intensity and we need luminous power. + // For a spot light, we map luminous power = 4 * pi * luminous intensity + intensity: light.intensity() * core::f32::consts::PI * 4.0, + range: light.range().unwrap_or(20.0), + radius: light.range().unwrap_or(0.0), + inner_angle: inner_cone_angle, + outer_angle: outer_cone_angle, + ..Default::default() + }); + if let Some(name) = light.name() { + entity.insert(Name::new(name.to_string())); + } + if let Some(extras) = light.extras() { + entity.insert(GltfExtras { + value: extras.get().to_string(), }); - if let Some(name) = light.name() { - entity.insert(Name::new(name.to_string())); - } - if let Some(extras) = light.extras() { - entity.insert(GltfExtras { - value: extras.get().to_string(), - }); - } } } } @@ -1703,16 +1700,16 @@ fn load_node( }); // Only include meshes in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_meshes flag - if !settings.load_meshes.is_empty() { - if let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) { - let primitive_label = mesh.primitives().next().map(|p| GltfAssetLabel::Primitive { - mesh: mesh.index(), - primitive: p.index(), - }); - let first_mesh = - primitive_label.map(|label| load_context.get_label_handle(label.to_string())); - node.insert(MorphWeights::new(weights, first_mesh)?); - } + if !settings.load_meshes.is_empty() + && let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) + { + let primitive_label = mesh.primitives().next().map(|p| GltfAssetLabel::Primitive { + mesh: mesh.index(), + primitive: p.index(), + }); + let first_mesh = + primitive_label.map(|label| load_context.get_label_handle(label.to_string())); + node.insert(MorphWeights::new(weights, first_mesh)?); } if let Some(err) = gltf_error { diff --git a/crates/bevy_image/Cargo.toml b/crates/bevy_image/Cargo.toml index 3d33720eadfa9..4069136e2b907 100644 --- a/crates/bevy_image/Cargo.toml +++ b/crates/bevy_image/Cargo.toml @@ -70,7 +70,7 @@ image = { version = "0.25.2", default-features = false } # misc bitflags = { version = "2.3", features = ["serde"] } bytemuck = { version = "1.5" } -wgpu-types = { version = "25", default-features = false } +wgpu-types = { version = "26", default-features = false } serde = { version = "1", features = ["derive"] } thiserror = { version = "2", default-features = false } futures-lite = "2.0.1" diff --git a/crates/bevy_image/src/basis.rs b/crates/bevy_image/src/basis.rs index 553140b0024bf..396129e678634 100644 --- a/crates/bevy_image/src/basis.rs +++ b/crates/bevy_image/src/basis.rs @@ -53,12 +53,12 @@ pub fn basis_buffer_to_image( let image0_mip_level_count = transcoder.image_level_count(buffer, 0); for image_index in 0..image_count { - if let Some(image_info) = transcoder.image_info(buffer, image_index) { - if texture_type == BasisTextureType::TextureType2D - && (image_info.m_orig_width != image0_info.m_orig_width - || image_info.m_orig_height != image0_info.m_orig_height) - { - return Err(TextureError::UnsupportedTextureFormat(format!( + if let Some(image_info) = transcoder.image_info(buffer, image_index) + && texture_type == BasisTextureType::TextureType2D + && (image_info.m_orig_width != image0_info.m_orig_width + || image_info.m_orig_height != image0_info.m_orig_height) + { + return Err(TextureError::UnsupportedTextureFormat(format!( "Basis file with multiple 2D textures with different sizes not supported. Image {} {}x{}, image 0 {}x{}", image_index, image_info.m_orig_width, @@ -66,7 +66,6 @@ pub fn basis_buffer_to_image( image0_info.m_orig_width, image0_info.m_orig_height, ))); - } } let mip_level_count = transcoder.image_level_count(buffer, image_index); if mip_level_count != image0_mip_level_count { diff --git a/crates/bevy_image/src/texture_atlas.rs b/crates/bevy_image/src/texture_atlas.rs index 111d912d5c298..fcd08bd6e372a 100644 --- a/crates/bevy_image/src/texture_atlas.rs +++ b/crates/bevy_image/src/texture_atlas.rs @@ -19,8 +19,7 @@ impl Plugin for TextureAtlasPlugin { app.init_asset::(); #[cfg(feature = "bevy_reflect")] - app.register_asset_reflect::() - .register_type::(); + app.register_asset_reflect::(); } } diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 77cbe96822d1d..b546452c4e9dd 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -57,16 +57,12 @@ use mouse::{ }; use touch::{touch_screen_input_system, TouchInput, Touches}; -#[cfg(feature = "bevy_reflect")] -use gamepad::Gamepad; use gamepad::{ gamepad_connection_system, gamepad_event_processing_system, GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadButtonStateChangedEvent, GamepadConnectionEvent, GamepadEvent, GamepadRumbleRequest, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, }; -#[cfg(feature = "bevy_reflect")] -use gamepad::{GamepadAxis, GamepadButton, GamepadConnection, GamepadInput, GamepadSettings}; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; @@ -134,34 +130,6 @@ impl Plugin for InputPlugin { .add_event::() .init_resource::() .add_systems(PreUpdate, touch_screen_input_system.in_set(InputSystems)); - - #[cfg(feature = "bevy_reflect")] - { - // Register common types - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::(); - } } } diff --git a/crates/bevy_input_focus/src/directional_navigation.rs b/crates/bevy_input_focus/src/directional_navigation.rs index 2f3d64702549b..eca52febf85a6 100644 --- a/crates/bevy_input_focus/src/directional_navigation.rs +++ b/crates/bevy_input_focus/src/directional_navigation.rs @@ -36,10 +36,6 @@ pub struct DirectionalNavigationPlugin; impl Plugin for DirectionalNavigationPlugin { fn build(&self, app: &mut App) { app.init_resource::(); - - #[cfg(feature = "bevy_reflect")] - app.register_type::() - .register_type::(); } } diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 86466869d0ee8..3d1f8851350d8 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -221,11 +221,6 @@ impl Plugin for InputDispatchPlugin { ) .in_set(InputFocusSystems::Dispatch), ); - - #[cfg(feature = "bevy_reflect")] - app.register_type::() - .register_type::() - .register_type::(); } } diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index 98cf49163fc3d..e8eb6d85ce0dc 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -345,9 +345,6 @@ pub struct TabNavigationPlugin; impl Plugin for TabNavigationPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup_tab_navigation); - - #[cfg(feature = "bevy_reflect")] - app.register_type::().register_type::(); app.add_observer(acquire_focus); app.add_observer(click_to_focus); } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 6980a6ecc98d5..8f10302ca1869 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -224,7 +224,6 @@ bevy_text = ["dep:bevy_text", "bevy_image"] bevy_render = [ "dep:bevy_render", - "bevy_scene?/bevy_render", "bevy_gizmos?/bevy_render", "bevy_camera", "bevy_shader", @@ -301,11 +300,25 @@ reflect_functions = [ "bevy_ecs/reflect_functions", ] +# Enable automatic reflect registration using inventory. +reflect_auto_register = [ + "bevy_reflect/auto_register_inventory", + "bevy_app/reflect_auto_register", + "bevy_ecs/reflect_auto_register", +] + +# Enable automatic reflect registration without inventory. See `reflect::load_type_registrations` for more info. +reflect_auto_register_static = [ + "bevy_reflect/auto_register_static", + "bevy_app/reflect_auto_register", + "bevy_ecs/reflect_auto_register", +] + # Enable documentation reflection reflect_documentation = ["bevy_reflect/documentation"] -# Enable winit custom cursor support -custom_cursor = ["bevy_winit/custom_cursor"] +# Enable custom cursor support +custom_cursor = ["bevy_window/custom_cursor", "bevy_winit/custom_cursor"] # Experimental support for nodes that are ignored for UI layouting ghost_nodes = ["bevy_ui/ghost_nodes"] @@ -327,7 +340,6 @@ std = [ "bevy_state?/std", "bevy_time/std", "bevy_transform/std", - "bevy_tasks/std", "bevy_window?/std", ] @@ -344,7 +356,6 @@ critical-section = [ "bevy_reflect/critical-section", "bevy_state?/critical-section", "bevy_time/critical-section", - "bevy_tasks/critical-section", ] # Uses the `libm` maths library instead of the one provided in `std` and `core`. @@ -368,12 +379,7 @@ async_executor = [ # Enables use of browser APIs. # Note this is currently only applicable on `wasm32` architectures. -web = [ - "bevy_app/web", - "bevy_platform/web", - "bevy_reflect/web", - "bevy_tasks/web", -] +web = ["bevy_app/web", "bevy_platform/web", "bevy_reflect/web"] hotpatching = ["bevy_app/hotpatching", "bevy_ecs/hotpatching"] @@ -467,6 +473,9 @@ bevy_window = { path = "../bevy_window", optional = true, version = "0.17.0-dev" ] } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.17.0-dev", default-features = false } +[target.'cfg(target_os = "android")'.dependencies] +bevy_android = { path = "../bevy_android", version = "0.17.0-dev", default-features = false } + [lints] workspace = true diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index db9a0bcee91df..28188c93e0f18 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -16,6 +16,8 @@ pub use default_plugins::*; #[cfg(feature = "bevy_window")] pub use bevy_a11y as a11y; +#[cfg(target_os = "android")] +pub use bevy_android as android; #[cfg(feature = "bevy_animation")] pub use bevy_animation as animation; #[cfg(feature = "bevy_anti_aliasing")] diff --git a/crates/bevy_light/src/cascade.rs b/crates/bevy_light/src/cascade.rs index 0cb713a9e684c..adebf86241a56 100644 --- a/crates/bevy_light/src/cascade.rs +++ b/crates/bevy_light/src/cascade.rs @@ -225,27 +225,21 @@ pub fn build_directional_light_cascades( // users to not change any other aspects of the transform - there's no guarantee // `transform.to_matrix()` will give us a matrix with our desired properties. // Instead, we directly create a good matrix from just the rotation. - let world_from_light = Mat4::from_quat(transform.compute_transform().rotation); - let light_to_world_inverse = world_from_light.inverse(); + let world_from_light = Mat4::from_quat(transform.rotation()); + let light_to_world_inverse = world_from_light.transpose(); for (view_entity, projection, view_to_world) in views.iter().copied() { let camera_to_light_view = light_to_world_inverse * view_to_world; - let view_cascades = cascades_config - .bounds - .iter() - .enumerate() - .map(|(idx, far_bound)| { + let overlap_factor = 1.0 - cascades_config.overlap_proportion; + let far_bounds = cascades_config.bounds.iter(); + let near_bounds = [cascades_config.minimum_distance] + .into_iter() + .chain(far_bounds.clone().map(|bound| overlap_factor * bound)); + let view_cascades = near_bounds + .zip(far_bounds) + .map(|(near_bound, far_bound)| { // Negate bounds as -z is camera forward direction. - let z_near = if idx > 0 { - (1.0 - cascades_config.overlap_proportion) - * -cascades_config.bounds[idx - 1] - } else { - -cascades_config.minimum_distance - }; - let z_far = -far_bound; - - let corners = projection.get_frustum_corners(z_near, z_far); - + let corners = projection.get_frustum_corners(-near_bound, -far_bound); calculate_cascade( corners, directional_light_shadow_map.size as f32, @@ -263,6 +257,8 @@ pub fn build_directional_light_cascades( /// /// The corner vertices should be specified in the following order: /// first the bottom right, top right, top left, bottom left for the near plane, then similar for the far plane. +/// +/// See this [reference](https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf) for more details. fn calculate_cascade( frustum_corners: [Vec3A; 8], cascade_texture_size: f32, @@ -284,10 +280,9 @@ fn calculate_cascade( // as even though the lengths using corner_light_view above should be the same, precision can // introduce small but significant differences. // NOTE: The size remains the same unless the view frustum or cascade configuration is modified. - let cascade_diameter = (frustum_corners[0] - frustum_corners[6]) - .length() - .max((frustum_corners[4] - frustum_corners[6]).length()) - .ceil(); + let body_diagonal = (frustum_corners[0] - frustum_corners[6]).length_squared(); + let far_plane_diagonal = (frustum_corners[4] - frustum_corners[6]).length_squared(); + let cascade_diameter = body_diagonal.max(far_plane_diagonal).sqrt().ceil(); // NOTE: If we ensure that cascade_texture_size is a power of 2, then as we made cascade_diameter an // integer, cascade_texel_size is then an integer multiple of a power of 2 and can be @@ -302,16 +297,22 @@ fn calculate_cascade( max.z, ); - // It is critical for `world_to_cascade` to be stable. So rather than forming `cascade_to_world` + // It is critical for `cascade_from_world` to be stable. So rather than forming `world_from_cascade` // and inverting it, which risks instability due to numerical precision, we directly form - // `world_to_cascade` as the reference material suggests. - let light_to_world_transpose = world_from_light.transpose(); + // `cascade_from_world` as the reference material suggests. + let world_from_light_transpose = world_from_light.transpose(); let cascade_from_world = Mat4::from_cols( - light_to_world_transpose.x_axis, - light_to_world_transpose.y_axis, - light_to_world_transpose.z_axis, + world_from_light_transpose.x_axis, + world_from_light_transpose.y_axis, + world_from_light_transpose.z_axis, (-near_plane_center).extend(1.0), ); + let world_from_cascade = Mat4::from_cols( + world_from_light.x_axis, + world_from_light.y_axis, + world_from_light.z_axis, + world_from_light * near_plane_center.extend(1.0), + ); // Right-handed orthographic projection, centered at `near_plane_center`. // NOTE: This is different from the reference material, as we use reverse Z. @@ -325,7 +326,7 @@ fn calculate_cascade( let clip_from_world = clip_from_cascade * cascade_from_world; Cascade { - world_from_cascade: cascade_from_world.inverse(), + world_from_cascade, clip_from_cascade, clip_from_world, texel_size: cascade_texel_size, diff --git a/crates/bevy_light/src/directional_light.rs b/crates/bevy_light/src/directional_light.rs index e83c26c26b47e..39e52fb5f02e8 100644 --- a/crates/bevy_light/src/directional_light.rs +++ b/crates/bevy_light/src/directional_light.rs @@ -9,6 +9,7 @@ use bevy_ecs::prelude::*; use bevy_image::Image; use bevy_reflect::prelude::*; use bevy_transform::components::Transform; +use tracing::warn; use super::{ cascade::CascadeShadowConfig, cluster::ClusterVisibilityClass, light_consts, Cascades, @@ -182,6 +183,8 @@ pub struct DirectionalLightTexture { pub struct DirectionalLightShadowMap { // The width and height of each cascade. /// + /// Must be a power of two to avoid unstable cascade positioning. + /// /// Defaults to `2048`. pub size: usize, } @@ -192,6 +195,14 @@ impl Default for DirectionalLightShadowMap { } } +pub fn validate_shadow_map_size(mut shadow_map: ResMut) { + if shadow_map.is_changed() && !shadow_map.size.is_power_of_two() { + let new_size = shadow_map.size.next_power_of_two(); + warn!("Non-power-of-two DirectionalLightShadowMap sizes are not supported, correcting {} to {new_size}", shadow_map.size); + shadow_map.size = new_size; + } +} + pub fn update_directional_light_frusta( mut views: Query< ( diff --git a/crates/bevy_light/src/lib.rs b/crates/bevy_light/src/lib.rs index 06dc5ac7ca3eb..5ef9ad00250f3 100644 --- a/crates/bevy_light/src/lib.rs +++ b/crates/bevy_light/src/lib.rs @@ -21,8 +21,8 @@ use core::ops::DerefMut; pub mod cluster; pub use cluster::ClusteredDecal; use cluster::{ - add_clusters, assign::assign_objects_to_clusters, ClusterConfig, - GlobalVisibleClusterableObjects, VisibleClusterableObjects, + add_clusters, assign::assign_objects_to_clusters, GlobalVisibleClusterableObjects, + VisibleClusterableObjects, }; mod ambient_light; pub use ambient_light::AmbientLight; @@ -48,6 +48,8 @@ pub use directional_light::{ DirectionalLightTexture, }; +use crate::directional_light::validate_shadow_map_size; + /// Constants for operating with the light units: lumens, and lux. pub mod light_consts { /// Approximations for converting the wattage of lamps to lumens. @@ -65,6 +67,10 @@ pub mod light_consts { pub const LUMENS_PER_LED_WATTS: f32 = 90.0; pub const LUMENS_PER_INCANDESCENT_WATTS: f32 = 13.8; pub const LUMENS_PER_HALOGEN_WATTS: f32 = 19.8; + /// 1,000,000 lumens is a very large "cinema light" capable of registering brightly at Bevy's + /// default "very overcast day" exposure level. For "indoor lighting" with a lower exposure, + /// this would be way too bright. + pub const VERY_LARGE_CINEMA_LIGHT: f32 = 1_000_000.0; } /// Predefined for lux values in several locations. @@ -111,24 +117,7 @@ pub struct LightPlugin; impl Plugin for LightPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_resource::() + app.init_resource::() .init_resource::() .init_resource::() .init_resource::() @@ -145,6 +134,7 @@ impl Plugin for LightPlugin { .add_systems( PostUpdate, ( + validate_shadow_map_size.before(build_directional_light_cascades), add_clusters .in_set(SimulationLightSystems::AddClusters) .after(CameraUpdateSystems), diff --git a/crates/bevy_light/src/point_light.rs b/crates/bevy_light/src/point_light.rs index f37a386200417..39f98d448aeee 100644 --- a/crates/bevy_light/src/point_light.rs +++ b/crates/bevy_light/src/point_light.rs @@ -10,7 +10,10 @@ use bevy_math::Mat4; use bevy_reflect::prelude::*; use bevy_transform::components::{GlobalTransform, Transform}; -use crate::cluster::{ClusterVisibilityClass, GlobalVisibleClusterableObjects}; +use crate::{ + cluster::{ClusterVisibilityClass, GlobalVisibleClusterableObjects}, + light_consts, +}; /// A light that emits light in all directions from a central point. /// @@ -126,10 +129,7 @@ impl Default for PointLight { fn default() -> Self { PointLight { color: Color::WHITE, - // 1,000,000 lumens is a very large "cinema light" capable of registering brightly at Bevy's - // default "very overcast day" exposure level. For "indoor lighting" with a lower exposure, - // this would be way too bright. - intensity: 1_000_000.0, + intensity: light_consts::lumens::VERY_LARGE_CINEMA_LIGHT, range: 20.0, radius: 0.0, shadows_enabled: false, diff --git a/crates/bevy_macro_utils/Cargo.toml b/crates/bevy_macro_utils/Cargo.toml index b998ae4fd4553..4353ad2136475 100644 --- a/crates/bevy_macro_utils/Cargo.toml +++ b/crates/bevy_macro_utils/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["bevy"] syn = "2.0" quote = "1.0" proc-macro2 = "1.0" -toml_edit = { version = "0.22.7", default-features = false, features = [ +toml_edit = { version = "0.23.2", default-features = false, features = [ "parse", ] } parking_lot = { version = "0.12" } diff --git a/crates/bevy_macro_utils/src/bevy_manifest.rs b/crates/bevy_macro_utils/src/bevy_manifest.rs index b6df0e0e0f89c..378e04a44869c 100644 --- a/crates/bevy_macro_utils/src/bevy_manifest.rs +++ b/crates/bevy_macro_utils/src/bevy_manifest.rs @@ -8,12 +8,12 @@ use std::{ path::{Path, PathBuf}, time::SystemTime, }; -use toml_edit::{ImDocument, Item}; +use toml_edit::{Document, Item}; /// The path to the `Cargo.toml` file for the Bevy project. #[derive(Debug)] pub struct BevyManifest { - manifest: ImDocument>, + manifest: Document>, modified_time: SystemTime, } @@ -29,10 +29,9 @@ impl BevyManifest { if let Ok(manifest) = RwLockReadGuard::try_map(MANIFESTS.read(), |manifests| manifests.get(&manifest_path)) + && manifest.modified_time == modified_time { - if manifest.modified_time == modified_time { - return manifest; - } + return manifest; } let manifest = BevyManifest { @@ -70,11 +69,11 @@ impl BevyManifest { std::fs::metadata(cargo_manifest_path).and_then(|metadata| metadata.modified()) } - fn read_manifest(path: &Path) -> ImDocument> { + fn read_manifest(path: &Path) -> Document> { let manifest = std::fs::read_to_string(path) .unwrap_or_else(|_| panic!("Unable to read cargo manifest: {}", path.display())) .into_boxed_str(); - ImDocument::parse(manifest) + Document::parse(manifest) .unwrap_or_else(|_| panic!("Failed to parse cargo manifest: {}", path.display())) } diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 459ff6e90a545..02787ee528113 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["bevy"] rust-version = "1.85.0" [dependencies] -glam = { version = "0.29.3", default-features = false, features = ["bytemuck"] } +glam = { version = "0.30.1", default-features = false, features = ["bytemuck"] } thiserror = { version = "2", default-features = false } derive_more = { version = "2", default-features = false, features = [ "from", @@ -22,8 +22,8 @@ serde = { version = "1", default-features = false, features = [ ], optional = true } libm = { version = "0.2", optional = true } approx = { version = "0.5", default-features = false, optional = true } -rand = { version = "0.8", default-features = false, optional = true } -rand_distr = { version = "0.4.3", optional = true } +rand = { version = "0.9", default-features = false, optional = true } +rand_distr = { version = "0.5", optional = true } smallvec = { version = "1", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [ "glam", @@ -33,11 +33,16 @@ variadics_please = "1.1" [dev-dependencies] approx = "0.5" # Supply rngs for examples and tests -rand = "0.8" -rand_chacha = "0.3" +rand = "0.9" +rand_chacha = "0.9" # Enable the approx feature when testing. bevy_math = { path = ".", default-features = false, features = ["approx"] } -glam = { version = "0.29.3", default-features = false, features = ["approx"] } +glam = { version = "0.30.1", default-features = false, features = ["approx"] } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +getrandom = { version = "0.3", default-features = false, features = [ + "wasm_js", +] } [features] default = ["std", "rand", "curve"] diff --git a/crates/bevy_math/src/cubic_splines/mod.rs b/crates/bevy_math/src/cubic_splines/mod.rs index 3ea99a60b0695..6a45103393ffc 100644 --- a/crates/bevy_math/src/cubic_splines/mod.rs +++ b/crates/bevy_math/src/cubic_splines/mod.rs @@ -1036,7 +1036,7 @@ impl> CubicSegment

{ /// An iterator that returns values of `t` uniformly spaced over `0..=subdivisions`. #[inline] - fn iter_uniformly(&self, subdivisions: usize) -> impl Iterator { + pub fn iter_uniformly(&self, subdivisions: usize) -> impl Iterator { let step = 1.0 / subdivisions as f32; (0..=subdivisions).map(move |i| i as f32 * step) } @@ -1605,7 +1605,7 @@ impl> RationalCurve

{ } t -= segment.knot_span; } - return (self.segments.last().unwrap(), 1.0); + (self.segments.last().unwrap(), 1.0) } } diff --git a/crates/bevy_math/src/sampling/mesh_sampling.rs b/crates/bevy_math/src/sampling/mesh_sampling.rs index 898198dea4261..f7e2d5cf7ab6b 100644 --- a/crates/bevy_math/src/sampling/mesh_sampling.rs +++ b/crates/bevy_math/src/sampling/mesh_sampling.rs @@ -6,7 +6,10 @@ use crate::{ }; use alloc::vec::Vec; use rand::Rng; -use rand_distr::{Distribution, WeightedAliasIndex, WeightedError}; +use rand_distr::{ + weighted::{Error as WeightedError, WeightedAliasIndex}, + Distribution, +}; /// A [distribution] that caches data to allow fast sampling from a collection of triangles. /// Generally used through [`sample`] or [`sample_iter`]. @@ -19,7 +22,7 @@ use rand_distr::{Distribution, WeightedAliasIndex, WeightedError}; /// ``` /// # use bevy_math::{Vec3, primitives::*}; /// # use bevy_math::sampling::mesh_sampling::UniformMeshSampler; -/// # use rand::{SeedableRng, rngs::StdRng, distributions::Distribution}; +/// # use rand::{SeedableRng, rngs::StdRng, distr::Distribution}; /// let faces = Tetrahedron::default().faces(); /// let sampler = UniformMeshSampler::try_new(faces).unwrap(); /// let rng = StdRng::seed_from_u64(8765309); diff --git a/crates/bevy_math/src/sampling/shape_sampling.rs b/crates/bevy_math/src/sampling/shape_sampling.rs index 8e71a2654d9db..3f389ae6f68db 100644 --- a/crates/bevy_math/src/sampling/shape_sampling.rs +++ b/crates/bevy_math/src/sampling/shape_sampling.rs @@ -8,7 +8,7 @@ //! # use rand::SeedableRng; //! # use rand::rngs::StdRng; //! // Get some `Rng`: -//! let rng = &mut StdRng::from_entropy(); +//! let rng = &mut StdRng::from_os_rng(); //! // Make a circle of radius 2: //! let circle = Circle::new(2.0); //! // Get a point inside this circle uniformly at random: @@ -23,9 +23,9 @@ //! # use bevy_math::{Vec2, ShapeSample}; //! # use rand::SeedableRng; //! # use rand::rngs::StdRng; -//! # use rand::distributions::Distribution; -//! # let rng1 = StdRng::from_entropy(); -//! # let rng2 = StdRng::from_entropy(); +//! # use rand::distr::Distribution; +//! # let rng1 = StdRng::from_os_rng(); +//! # let rng2 = StdRng::from_os_rng(); //! // Use a rectangle this time: //! let rectangle = Rectangle::new(1.0, 2.0); //! // Get an iterator that spits out random interior points: @@ -42,10 +42,13 @@ use core::f32::consts::{FRAC_PI_2, PI, TAU}; use crate::{ops, primitives::*, NormedVectorSpace, ScalarField, Vec2, Vec3}; use rand::{ - distributions::{Distribution, WeightedIndex}, + distr::{ + uniform::SampleUniform, + weighted::{Weight, WeightedIndex}, + Distribution, + }, Rng, }; -use rand_distr::uniform::SampleUniform; /// Exposes methods to uniformly sample a variety of primitive shapes. pub trait ShapeSample { @@ -62,7 +65,7 @@ pub trait ShapeSample { /// let square = Rectangle::new(2.0, 2.0); /// /// // Returns a Vec2 with both x and y between -1 and 1. - /// println!("{}", square.sample_interior(&mut rand::thread_rng())); + /// println!("{}", square.sample_interior(&mut rand::rng())); /// ``` fn sample_interior(&self, rng: &mut R) -> Self::Output; @@ -77,7 +80,7 @@ pub trait ShapeSample { /// /// // Returns a Vec2 where one of the coordinates is at ±1, /// // and the other is somewhere between -1 and 1. - /// println!("{}", square.sample_boundary(&mut rand::thread_rng())); + /// println!("{}", square.sample_boundary(&mut rand::rng())); /// ``` fn sample_boundary(&self, rng: &mut R) -> Self::Output; @@ -87,9 +90,9 @@ pub trait ShapeSample { /// /// ``` /// # use bevy_math::prelude::*; - /// # use rand::distributions::Distribution; + /// # use rand::distr::Distribution; /// let square = Rectangle::new(2.0, 2.0); - /// let rng = rand::thread_rng(); + /// let rng = rand::rng(); /// /// // Iterate over points randomly drawn from `square`'s interior: /// for random_val in square.interior_dist().sample_iter(rng).take(5) { @@ -109,9 +112,9 @@ pub trait ShapeSample { /// /// ``` /// # use bevy_math::prelude::*; - /// # use rand::distributions::Distribution; + /// # use rand::distr::Distribution; /// let square = Rectangle::new(2.0, 2.0); - /// let rng = rand::thread_rng(); + /// let rng = rand::rng(); /// /// // Iterate over points randomly drawn from `square`'s boundary: /// for random_val in square.boundary_dist().sample_iter(rng).take(5) { @@ -153,15 +156,15 @@ impl ShapeSample for Circle { fn sample_interior(&self, rng: &mut R) -> Vec2 { // https://mathworld.wolfram.com/DiskPointPicking.html - let theta = rng.gen_range(0.0..TAU); - let r_squared = rng.gen_range(0.0..=(self.radius * self.radius)); + let theta = rng.random_range(0.0..TAU); + let r_squared = rng.random_range(0.0..=(self.radius * self.radius)); let r = ops::sqrt(r_squared); let (sin, cos) = ops::sin_cos(theta); Vec2::new(r * cos, r * sin) } fn sample_boundary(&self, rng: &mut R) -> Vec2 { - let theta = rng.gen_range(0.0..TAU); + let theta = rng.random_range(0.0..TAU); let (sin, cos) = ops::sin_cos(theta); Vec2::new(self.radius * cos, self.radius * sin) } @@ -171,22 +174,22 @@ impl ShapeSample for CircularSector { type Output = Vec2; fn sample_interior(&self, rng: &mut R) -> Vec2 { - let theta = rng.gen_range(-self.half_angle()..=self.half_angle()); - let r_squared = rng.gen_range(0.0..=(self.radius() * self.radius())); + let theta = rng.random_range(-self.half_angle()..=self.half_angle()); + let r_squared = rng.random_range(0.0..=(self.radius() * self.radius())); let r = ops::sqrt(r_squared); let (sin, cos) = ops::sin_cos(theta); Vec2::new(r * sin, r * cos) } fn sample_boundary(&self, rng: &mut R) -> Vec2 { - if rng.gen_range(0.0..=1.0) <= self.arc_length() / self.perimeter() { + if rng.random_range(0.0..=1.0) <= self.arc_length() / self.perimeter() { // Sample on the arc - let theta = FRAC_PI_2 + rng.gen_range(-self.half_angle()..self.half_angle()); + let theta = FRAC_PI_2 + rng.random_range(-self.half_angle()..self.half_angle()); Vec2::from_angle(theta) * self.radius() } else { // Sample on the "inner" straight lines let dir = self.radius() * Vec2::from_angle(FRAC_PI_2 + self.half_angle()); - let r: f32 = rng.gen_range(-1.0..1.0); + let r: f32 = rng.random_range(-1.0..1.0); (-r).clamp(0.0, 1.0) * dir + r.clamp(0.0, 1.0) * dir * Vec2::new(-1.0, 1.0) } } @@ -195,8 +198,8 @@ impl ShapeSample for CircularSector { /// Boundary sampling for unit-spheres #[inline] fn sample_unit_sphere_boundary(rng: &mut R) -> Vec3 { - let z = rng.gen_range(-1f32..=1f32); - let (a_sin, a_cos) = ops::sin_cos(rng.gen_range(-PI..=PI)); + let z = rng.random_range(-1f32..=1f32); + let (a_sin, a_cos) = ops::sin_cos(rng.random_range(-PI..=PI)); let c = ops::sqrt(1f32 - z * z); let x = a_sin * c; let y = a_cos * c; @@ -208,7 +211,7 @@ impl ShapeSample for Sphere { type Output = Vec3; fn sample_interior(&self, rng: &mut R) -> Vec3 { - let r_cubed = rng.gen_range(0.0..=(self.radius * self.radius * self.radius)); + let r_cubed = rng.random_range(0.0..=(self.radius * self.radius * self.radius)); let r = ops::cbrt(r_cubed); r * sample_unit_sphere_boundary(rng) @@ -227,9 +230,10 @@ impl ShapeSample for Annulus { let outer_radius = self.outer_circle.radius; // Like random sampling for a circle, radius is weighted by the square. - let r_squared = rng.gen_range((inner_radius * inner_radius)..(outer_radius * outer_radius)); + let r_squared = + rng.random_range((inner_radius * inner_radius)..(outer_radius * outer_radius)); let r = ops::sqrt(r_squared); - let theta = rng.gen_range(0.0..TAU); + let theta = rng.random_range(0.0..TAU); let (sin, cos) = ops::sin_cos(theta); Vec2::new(r * cos, r * sin) @@ -240,7 +244,7 @@ impl ShapeSample for Annulus { let inner_prob = (self.inner_circle.perimeter() / total_perimeter) as f64; // Sample from boundary circles, choosing which one by weighting by perimeter: - let inner = rng.gen_bool(inner_prob); + let inner = rng.random_bool(inner_prob); if inner { self.inner_circle.sample_boundary(rng) } else { @@ -253,16 +257,16 @@ impl ShapeSample for Rhombus { type Output = Vec2; fn sample_interior(&self, rng: &mut R) -> Vec2 { - let x: f32 = rng.gen_range(0.0..=1.0); - let y: f32 = rng.gen_range(0.0..=1.0); + let x: f32 = rng.random_range(0.0..=1.0); + let y: f32 = rng.random_range(0.0..=1.0); let unit_p = Vec2::NEG_X + x * Vec2::ONE + Vec2::new(y, -y); unit_p * self.half_diagonals } fn sample_boundary(&self, rng: &mut R) -> Vec2 { - let x: f32 = rng.gen_range(-1.0..=1.0); - let y_sign = if rng.r#gen() { -1.0 } else { 1.0 }; + let x: f32 = rng.random_range(-1.0..=1.0); + let y_sign = if rng.random() { -1.0 } else { 1.0 }; let y = (1.0 - ops::abs(x)) * y_sign; Vec2::new(x, y) * self.half_diagonals @@ -273,17 +277,17 @@ impl ShapeSample for Rectangle { type Output = Vec2; fn sample_interior(&self, rng: &mut R) -> Vec2 { - let x = rng.gen_range(-self.half_size.x..=self.half_size.x); - let y = rng.gen_range(-self.half_size.y..=self.half_size.y); + let x = rng.random_range(-self.half_size.x..=self.half_size.x); + let y = rng.random_range(-self.half_size.y..=self.half_size.y); Vec2::new(x, y) } fn sample_boundary(&self, rng: &mut R) -> Vec2 { - let primary_side = rng.gen_range(-1.0..1.0); - let other_side = if rng.r#gen() { -1.0 } else { 1.0 }; + let primary_side = rng.random_range(-1.0..1.0); + let other_side = if rng.random() { -1.0 } else { 1.0 }; if self.half_size.x + self.half_size.y > 0.0 { - if rng.gen_bool((self.half_size.x / (self.half_size.x + self.half_size.y)) as f64) { + if rng.random_bool((self.half_size.x / (self.half_size.x + self.half_size.y)) as f64) { Vec2::new(primary_side, other_side) * self.half_size } else { Vec2::new(other_side, primary_side) * self.half_size @@ -298,16 +302,16 @@ impl ShapeSample for Cuboid { type Output = Vec3; fn sample_interior(&self, rng: &mut R) -> Vec3 { - let x = rng.gen_range(-self.half_size.x..=self.half_size.x); - let y = rng.gen_range(-self.half_size.y..=self.half_size.y); - let z = rng.gen_range(-self.half_size.z..=self.half_size.z); + let x = rng.random_range(-self.half_size.x..=self.half_size.x); + let y = rng.random_range(-self.half_size.y..=self.half_size.y); + let z = rng.random_range(-self.half_size.z..=self.half_size.z); Vec3::new(x, y, z) } fn sample_boundary(&self, rng: &mut R) -> Vec3 { - let primary_side1 = rng.gen_range(-1.0..1.0); - let primary_side2 = rng.gen_range(-1.0..1.0); - let other_side = if rng.r#gen() { -1.0 } else { 1.0 }; + let primary_side1 = rng.random_range(-1.0..1.0); + let primary_side2 = rng.random_range(-1.0..1.0); + let other_side = if rng.random() { -1.0 } else { 1.0 }; if let Ok(dist) = WeightedIndex::new([ self.half_size.y * self.half_size.z, @@ -339,8 +343,8 @@ where // Generate random points on a parallelepiped and reflect so that // we can use the points that lie outside the triangle - let u = rng.gen_range(P::Scalar::ZERO..=P::Scalar::ONE); - let v = rng.gen_range(P::Scalar::ZERO..=P::Scalar::ONE); + let u = rng.random_range(P::Scalar::ZERO..=P::Scalar::ONE); + let v = rng.random_range(P::Scalar::ZERO..=P::Scalar::ONE); if u + v > P::Scalar::ONE { let u1 = P::Scalar::ONE - v; @@ -355,7 +359,7 @@ where fn sample_triangle_boundary(vertices: [P; 3], rng: &mut R) -> P where P: NormedVectorSpace, - P::Scalar: SampleUniform + PartialOrd + for<'a> ::core::ops::AddAssign<&'a P::Scalar>, + P::Scalar: Weight + SampleUniform + PartialOrd + for<'a> ::core::ops::AddAssign<&'a P::Scalar>, R: Rng + ?Sized, { let [a, b, c] = vertices; @@ -363,7 +367,7 @@ where let ac = c - a; let bc = c - b; - let t = rng.gen_range(P::Scalar::ZERO..=P::Scalar::ONE); + let t = rng.random_range(::ZERO..=P::Scalar::ONE); if let Ok(dist) = WeightedIndex::new([ab.norm(), ac.norm(), bc.norm()]) { match dist.sample(rng) { @@ -411,9 +415,9 @@ impl ShapeSample for Tetrahedron { // Generate a random point in a cube: let mut coords: [f32; 3] = [ - rng.gen_range(0.0..1.0), - rng.gen_range(0.0..1.0), - rng.gen_range(0.0..1.0), + rng.random_range(0.0..1.0), + rng.random_range(0.0..1.0), + rng.random_range(0.0..1.0), ]; // The cube is broken into six tetrahedra of the form 0 <= c_0 <= c_1 <= c_2 <= 1, @@ -465,7 +469,7 @@ impl ShapeSample for Cylinder { fn sample_interior(&self, rng: &mut R) -> Vec3 { let Vec2 { x, y: z } = self.base().sample_interior(rng); - let y = rng.gen_range(-self.half_height..=self.half_height); + let y = rng.random_range(-self.half_height..=self.half_height); Vec3::new(x, y, z) } @@ -473,16 +477,16 @@ impl ShapeSample for Cylinder { // This uses the area of the ends divided by the overall surface area (optimized) // [2 (\pi r^2)]/[2 (\pi r^2) + 2 \pi r h] = r/(r + h) if self.radius + 2.0 * self.half_height > 0.0 { - if rng.gen_bool((self.radius / (self.radius + 2.0 * self.half_height)) as f64) { + if rng.random_bool((self.radius / (self.radius + 2.0 * self.half_height)) as f64) { let Vec2 { x, y: z } = self.base().sample_interior(rng); - if rng.r#gen() { + if rng.random() { Vec3::new(x, self.half_height, z) } else { Vec3::new(x, -self.half_height, z) } } else { let Vec2 { x, y: z } = self.base().sample_boundary(rng); - let y = rng.gen_range(-self.half_height..=self.half_height); + let y = rng.random_range(-self.half_height..=self.half_height); Vec3::new(x, y, z) } } else { @@ -499,7 +503,7 @@ impl ShapeSample for Capsule2d { let capsule_area = rectangle_area + PI * self.radius * self.radius; if capsule_area > 0.0 { // Check if the random point should be inside the rectangle - if rng.gen_bool((rectangle_area / capsule_area) as f64) { + if rng.random_bool((rectangle_area / capsule_area) as f64) { self.to_inner_rectangle().sample_interior(rng) } else { let circle = Circle::new(self.radius); @@ -520,9 +524,9 @@ impl ShapeSample for Capsule2d { let rectangle_surface = 4.0 * self.half_length; let capsule_surface = rectangle_surface + TAU * self.radius; if capsule_surface > 0.0 { - if rng.gen_bool((rectangle_surface / capsule_surface) as f64) { + if rng.random_bool((rectangle_surface / capsule_surface) as f64) { let side_distance = - rng.gen_range((-2.0 * self.half_length)..=(2.0 * self.half_length)); + rng.random_range((-2.0 * self.half_length)..=(2.0 * self.half_length)); if side_distance < 0.0 { Vec2::new(self.radius, side_distance + self.half_length) } else { @@ -553,7 +557,7 @@ impl ShapeSample for Capsule3d { let capsule_vol = cylinder_vol + 4.0 / 3.0 * PI * self.radius * self.radius * self.radius; if capsule_vol > 0.0 { // Check if the random point should be inside the cylinder - if rng.gen_bool((cylinder_vol / capsule_vol) as f64) { + if rng.random_bool((cylinder_vol / capsule_vol) as f64) { self.to_cylinder().sample_interior(rng) } else { let sphere = Sphere::new(self.radius); @@ -574,9 +578,9 @@ impl ShapeSample for Capsule3d { let cylinder_surface = TAU * self.radius * 2.0 * self.half_length; let capsule_surface = cylinder_surface + 4.0 * PI * self.radius * self.radius; if capsule_surface > 0.0 { - if rng.gen_bool((cylinder_surface / capsule_surface) as f64) { + if rng.random_bool((cylinder_surface / capsule_surface) as f64) { let Vec2 { x, y: z } = Circle::new(self.radius).sample_boundary(rng); - let y = rng.gen_range(-self.half_length..=self.half_length); + let y = rng.random_range(-self.half_length..=self.half_length); Vec3::new(x, y, z) } else { let sphere = Sphere::new(self.radius); @@ -599,7 +603,7 @@ impl> ShapeSample for E fn sample_interior(&self, rng: &mut R) -> Self::Output { let base_point = self.base_shape.sample_interior(rng); - let depth = rng.gen_range(-self.half_depth..self.half_depth); + let depth = rng.random_range(-self.half_depth..self.half_depth); base_point.extend(depth) } @@ -607,7 +611,7 @@ impl> ShapeSample for E let base_area = self.base_shape.area(); let total_area = self.area(); - let random = rng.gen_range(0.0..total_area); + let random = rng.random_range(0.0..total_area); match random { x if x < base_area => self.base_shape.sample_interior(rng).extend(self.half_depth), x if x < 2. * base_area => self @@ -617,7 +621,7 @@ impl> ShapeSample for E _ => self .base_shape .sample_boundary(rng) - .extend(rng.gen_range(-self.half_depth..self.half_depth)), + .extend(rng.random_range(-self.half_depth..self.half_depth)), } } } diff --git a/crates/bevy_math/src/sampling/standard.rs b/crates/bevy_math/src/sampling/standard.rs index d4e82fdc81c14..47002f5781077 100644 --- a/crates/bevy_math/src/sampling/standard.rs +++ b/crates/bevy_math/src/sampling/standard.rs @@ -1,24 +1,24 @@ -//! This module holds local implementations of the [`Distribution`] trait for [`Standard`], which +//! This module holds local implementations of the [`Distribution`] trait for [`StandardUniform`], which //! allow certain Bevy math types (those whose values can be randomly generated without additional //! input other than an [`Rng`]) to be produced using [`rand`]'s APIs. It also holds [`FromRng`], //! an ergonomic extension to that functionality which permits the omission of type annotations. //! //! For instance: //! ``` -//! # use rand::{random, Rng, SeedableRng, rngs::StdRng, distributions::Standard}; +//! # use rand::{random, Rng, SeedableRng, rngs::StdRng, distr::StandardUniform}; //! # use bevy_math::{Dir3, sampling::FromRng}; //! let mut rng = StdRng::seed_from_u64(7313429298); //! // Random direction using thread-local rng //! let random_direction1: Dir3 = random(); //! //! // Random direction using the rng constructed above -//! let random_direction2: Dir3 = rng.r#gen(); +//! let random_direction2: Dir3 = rng.random(); //! //! // The same as the previous but with different syntax //! let random_direction3 = Dir3::from_rng(&mut rng); //! -//! // Five random directions, using Standard explicitly -//! let many_random_directions: Vec = rng.sample_iter(Standard).take(5).collect(); +//! // Five random directions, using StandardUniform explicitly +//! let many_random_directions: Vec = rng.sample_iter(StandardUniform).take(5).collect(); //! ``` use core::f32::consts::TAU; @@ -28,11 +28,11 @@ use crate::{ Dir2, Dir3, Dir3A, Quat, Rot2, ShapeSample, Vec3A, }; use rand::{ - distributions::{Distribution, Standard}, + distr::{Distribution, StandardUniform}, Rng, }; -/// Ergonomics trait for a type with a [`Standard`] distribution, allowing values to be generated +/// Ergonomics trait for a type with a [`StandardUniform`] distribution, allowing values to be generated /// uniformly from an [`Rng`] by a method in its own namespace. /// /// Example @@ -45,15 +45,15 @@ use rand::{ pub trait FromRng where Self: Sized, - Standard: Distribution, + StandardUniform: Distribution, { /// Construct a value of this type uniformly at random using `rng` as the source of randomness. fn from_rng(rng: &mut R) -> Self { - rng.r#gen() + rng.random() } } -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> Dir2 { let circle = Circle::new(1.0); @@ -64,7 +64,7 @@ impl Distribution for Standard { impl FromRng for Dir2 {} -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> Dir3 { let sphere = Sphere::new(1.0); @@ -75,7 +75,7 @@ impl Distribution for Standard { impl FromRng for Dir3 {} -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> Dir3A { let sphere = Sphere::new(1.0); @@ -86,10 +86,10 @@ impl Distribution for Standard { impl FromRng for Dir3A {} -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> Rot2 { - let angle = rng.gen_range(0.0..TAU); + let angle = rng.random_range(0.0..TAU); Rot2::radians(angle) } } diff --git a/crates/bevy_mesh/Cargo.toml b/crates/bevy_mesh/Cargo.toml index 0f37ac11417a9..94dc06427bdd6 100644 --- a/crates/bevy_mesh/Cargo.toml +++ b/crates/bevy_mesh/Cargo.toml @@ -16,7 +16,7 @@ bevy_math = { path = "../bevy_math", version = "0.17.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" } -bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.17.0-dev" } +bevy_mikktspace = { version = "0.17.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" } bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ "std", @@ -26,11 +26,11 @@ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-fea # other bitflags = { version = "2.3", features = ["serde"] } bytemuck = { version = "1.5" } -wgpu-types = { version = "25", default-features = false } +wgpu-types = { version = "26", default-features = false } serde = { version = "1", default-features = false, features = [ "derive", ], optional = true } -hexasphere = "15.0" +hexasphere = "16.0" thiserror = { version = "2", default-features = false } tracing = { version = "0.1", default-features = false, features = ["std"] } derive_more = { version = "2", default-features = false, features = ["from"] } diff --git a/crates/bevy_mesh/src/conversions.rs b/crates/bevy_mesh/src/conversions.rs index f68c2d6f74321..a4148ae24353e 100644 --- a/crates/bevy_mesh/src/conversions.rs +++ b/crates/bevy_mesh/src/conversions.rs @@ -445,9 +445,8 @@ mod tests { let buffer = vec![[0_u32; 4]; 3]; let values = VertexAttributeValues::from(buffer); let error_result: Result, _> = values.try_into(); - let error = match error_result { - Ok(..) => unreachable!(), - Err(error) => error, + let Err(error) = error_result else { + unreachable!() }; assert_eq!( error.to_string(), diff --git a/crates/bevy_mesh/src/mikktspace.rs b/crates/bevy_mesh/src/mikktspace.rs index c450a8104dbfa..b5f749fc2a524 100644 --- a/crates/bevy_mesh/src/mikktspace.rs +++ b/crates/bevy_mesh/src/mikktspace.rs @@ -47,9 +47,14 @@ impl bevy_mikktspace::Geometry for MikktspaceGeometryHelper<'_> { self.uvs[self.index(face, vert)] } - fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) { + fn set_tangent( + &mut self, + tangent_space: Option, + face: usize, + vert: usize, + ) { let idx = self.index(face, vert); - self.tangents[idx] = tangent; + self.tangents[idx] = tangent_space.unwrap_or_default().tangent_encoded(); } } @@ -65,7 +70,7 @@ pub enum GenerateTangentsError { #[error("the '{0}' vertex attribute should have {1:?} format")] InvalidVertexAttributeFormat(&'static str, VertexFormat), #[error("mesh not suitable for tangent generation")] - MikktspaceError, + MikktspaceError(#[from] bevy_mikktspace::GenerateTangentSpaceError), } pub(crate) fn generate_tangents_for_mesh( @@ -113,10 +118,7 @@ pub(crate) fn generate_tangents_for_mesh( uvs, tangents, }; - let success = bevy_mikktspace::generate_tangents(&mut mikktspace_mesh); - if !success { - return Err(GenerateTangentsError::MikktspaceError); - } + bevy_mikktspace::generate_tangents(&mut mikktspace_mesh)?; // mikktspace seems to assume left-handedness so we can flip the sign to correct for this for tangent in &mut mikktspace_mesh.tangents { diff --git a/crates/bevy_mikktspace/Cargo.toml b/crates/bevy_mikktspace/Cargo.toml deleted file mode 100644 index 82c2d86553c74..0000000000000 --- a/crates/bevy_mikktspace/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "bevy_mikktspace" -version = "0.17.0-dev" -edition = "2024" -authors = [ - "Benjamin Wasty ", - "David Harvey-Macaulay ", - "Layl Bongers ", -] -description = "Mikkelsen tangent space algorithm" -documentation = "https://docs.rs/bevy" -homepage = "https://bevy.org" -repository = "https://github.com/bevyengine/bevy" -license = "Zlib AND (MIT OR Apache-2.0)" -keywords = ["bevy", "3D", "graphics", "algorithm", "tangent"] -rust-version = "1.85.0" - -[features] -default = ["std"] - -std = ["glam/std"] -libm = ["glam/libm", "dep:libm"] - -[dependencies] -glam = { version = "0.29.3", default-features = false } -libm = { version = "0.2", default-features = false, optional = true } - -[[example]] -name = "generate" - -[lints] -workspace = true - -[package.metadata.docs.rs] -rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] -all-features = true diff --git a/crates/bevy_mikktspace/LICENSE-APACHE b/crates/bevy_mikktspace/LICENSE-APACHE deleted file mode 100644 index 1b5ec8b78e237..0000000000000 --- a/crates/bevy_mikktspace/LICENSE-APACHE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS diff --git a/crates/bevy_mikktspace/LICENSE-MIT b/crates/bevy_mikktspace/LICENSE-MIT deleted file mode 100644 index 16b0f84d02846..0000000000000 --- a/crates/bevy_mikktspace/LICENSE-MIT +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2017 The mikktspace Library Developers - -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 to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - diff --git a/crates/bevy_mikktspace/README.md b/crates/bevy_mikktspace/README.md deleted file mode 100644 index 6ad4a72a4f7d5..0000000000000 --- a/crates/bevy_mikktspace/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Bevy Mikktspace - -[![License](https://img.shields.io/badge/license-MIT%2FApache%2FZlib-blue.svg)](https://github.com/bevyengine/bevy#license) -[![Crates.io](https://img.shields.io/crates/v/bevy.svg)](https://crates.io/crates/bevy_mikktspace) -[![Downloads](https://img.shields.io/crates/d/bevy_mikktspace.svg)](https://crates.io/crates/bevy_mikktspace) -[![Docs](https://docs.rs/bevy_mikktspace/badge.svg)](https://docs.rs/bevy_mikktspace/latest/bevy_mikktspace/) -[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) - -This is a fork of [https://github.com/gltf-rs/mikktspace](https://github.com/gltf-rs/mikktspace), which in turn is a port of the Mikkelsen Tangent Space Algorithm reference implementation to Rust. It has been forked for use in the bevy game engine to be able to update math crate dependencies in lock-step with bevy releases. It is vendored in the bevy repository itself as [crates/bevy_mikktspace](https://github.com/bevyengine/bevy/tree/main/crates/bevy_mikktspace). - -Port of the [Mikkelsen Tangent Space Algorithm](https://archive.blender.org/wiki/2015/index.php/Dev:Shading/Tangent_Space_Normal_Maps/) reference implementation. - -Requires at least Rust 1.76.0. - -## Examples - -### generate - -Demonstrates generating tangents for a cube with 4 triangular faces per side. - -```sh -cargo run --example generate -``` - -## License agreement - -Licensed under either of - -* Apache License, Version 2.0 - ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) -* MIT license - ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) - -at your option. AND parts of the code are licensed under: - -* Zlib license - [https://opensource.org/licenses/Zlib](https://opensource.org/licenses/Zlib) - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. diff --git a/crates/bevy_mikktspace/examples/cube.obj b/crates/bevy_mikktspace/examples/cube.obj deleted file mode 100644 index c0cf02efda305..0000000000000 --- a/crates/bevy_mikktspace/examples/cube.obj +++ /dev/null @@ -1,114 +0,0 @@ -v 0.5 -0.5 0.5 -v 0.5 -0.5 -0.5 -v 0.5 0.5 -0.5 -v 0.5 0.5 0.5 -v 0.5 0 0 -v -0.5 0.5 0.5 -v -0.5 0.5 -0.5 -v -0.5 -0.5 -0.5 -v -0.5 -0.5 0.5 -v -0.5 0 0 -v 0.5 0.5 0.5 -v 0.5 0.5 -0.5 -v -0.5 0.5 -0.5 -v -0.5 0.5 0.5 -v 0 0.5 0 -v -0.5 -0.5 0.5 -v -0.5 -0.5 -0.5 -v 0.5 -0.5 -0.5 -v 0.5 -0.5 0.5 -v 0 -0.5 0 -v -0.5 0.5 0.5 -v -0.5 -0.5 0.5 -v 0.5 -0.5 0.5 -v 0.5 0.5 0.5 -v 0 0 0.5 -v 0.5 0.5 -0.5 -v 0.5 -0.5 -0.5 -v -0.5 -0.5 -0.5 -v -0.5 0.5 -0.5 -v 0 0 -0.5 -vn 0.57735026 -0.57735026 0.57735026 -vn 0.57735026 -0.57735026 -0.57735026 -vn 0.57735026 0.57735026 -0.57735026 -vn 0.57735026 0.57735026 0.57735026 -vn 1 0 0 -vn -0.57735026 0.57735026 0.57735026 -vn -0.57735026 0.57735026 -0.57735026 -vn -0.57735026 -0.57735026 -0.57735026 -vn -0.57735026 -0.57735026 0.57735026 -vn -1 0 0 -vn 0.57735026 0.57735026 0.57735026 -vn 0.57735026 0.57735026 -0.57735026 -vn -0.57735026 0.57735026 -0.57735026 -vn -0.57735026 0.57735026 0.57735026 -vn 0 1 0 -vn -0.57735026 -0.57735026 0.57735026 -vn -0.57735026 -0.57735026 -0.57735026 -vn 0.57735026 -0.57735026 -0.57735026 -vn 0.57735026 -0.57735026 0.57735026 -vn 0 -1 0 -vn -0.57735026 0.57735026 0.57735026 -vn -0.57735026 -0.57735026 0.57735026 -vn 0.57735026 -0.57735026 0.57735026 -vn 0.57735026 0.57735026 0.57735026 -vn 0 0 1 -vn 0.57735026 0.57735026 -0.57735026 -vn 0.57735026 -0.57735026 -0.57735026 -vn -0.57735026 -0.57735026 -0.57735026 -vn -0.57735026 0.57735026 -0.57735026 -vn 0 0 -1 -vt 0 0 -vt 0 1 -vt 1 1 -vt 1 0 -vt 0.5 0.5 -vt 1 0 -vt 1 1 -vt 0 1 -vt 0 0 -vt 0.5 0.5 -vt 0 0 -vt 0 1 -vt 0 1 -vt 0 0 -vt 0 0.5 -vt 0 0 -vt 0 1 -vt 0 1 -vt 0 0 -vt 0 0.5 -vt 0 0 -vt 0 1 -vt 1 1 -vt 1 0 -vt 0.5 0.5 -vt 1 0 -vt 1 1 -vt 0 1 -vt 0 0 -vt 0.5 0.5 -f 1/1/1 2/2/2 5/5/5 -f 2/2/2 3/3/3 5/5/5 -f 3/3/3 4/4/4 5/5/5 -f 4/4/4 1/1/1 5/5/5 -f 6/6/6 7/7/7 10/10/10 -f 7/7/7 8/8/8 10/10/10 -f 8/8/8 9/9/9 10/10/10 -f 9/9/9 6/6/6 10/10/10 -f 11/11/11 12/12/12 15/15/15 -f 12/12/12 13/13/13 15/15/15 -f 13/13/13 14/14/14 15/15/15 -f 14/14/14 11/11/11 15/15/15 -f 16/16/16 17/17/17 20/20/20 -f 17/17/17 18/18/18 20/20/20 -f 18/18/18 19/19/19 20/20/20 -f 19/19/19 16/16/16 20/20/20 -f 21/21/21 22/22/22 25/25/25 -f 22/22/22 23/23/23 25/25/25 -f 23/23/23 24/24/24 25/25/25 -f 24/24/24 21/21/21 25/25/25 -f 26/26/26 27/27/27 30/30/30 -f 27/27/27 28/28/28 30/30/30 -f 28/28/28 29/29/29 30/30/30 -f 29/29/29 26/26/26 30/30/30 diff --git a/crates/bevy_mikktspace/examples/generate.rs b/crates/bevy_mikktspace/examples/generate.rs deleted file mode 100644 index 62f6f10bfa04b..0000000000000 --- a/crates/bevy_mikktspace/examples/generate.rs +++ /dev/null @@ -1,266 +0,0 @@ -//! This example demonstrates how to generate a mesh. - -#![allow( - clippy::bool_assert_comparison, - clippy::useless_conversion, - reason = "Crate auto-generated with many non-idiomatic decisions. See #7372 for details." -)] -#![expect(clippy::print_stdout, reason = "Allowed in examples.")] - -use glam::{Vec2, Vec3}; - -type Face = [u32; 3]; - -#[derive(Debug)] -struct Vertex { - position: Vec3, - normal: Vec3, - tex_coord: Vec2, -} - -struct Mesh { - faces: Vec, - vertices: Vec, -} - -fn vertex(mesh: &Mesh, face: usize, vert: usize) -> &Vertex { - let vs: &[u32; 3] = &mesh.faces[face]; - &mesh.vertices[vs[vert] as usize] -} - -impl bevy_mikktspace::Geometry for Mesh { - fn num_faces(&self) -> usize { - self.faces.len() - } - - fn num_vertices_of_face(&self, _face: usize) -> usize { - 3 - } - - fn position(&self, face: usize, vert: usize) -> [f32; 3] { - vertex(self, face, vert).position.into() - } - - fn normal(&self, face: usize, vert: usize) -> [f32; 3] { - vertex(self, face, vert).normal.into() - } - - fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] { - vertex(self, face, vert).tex_coord.into() - } - - fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) { - println!( - "{face}-{vert}: v: {v:?}, vn: {vn:?}, vt: {vt:?}, vx: {vx:?}", - face = face, - vert = vert, - v = vertex(self, face, vert).position, - vn = vertex(self, face, vert).normal, - vt = vertex(self, face, vert).tex_coord, - vx = tangent, - ); - } -} - -fn make_cube() -> Mesh { - struct ControlPoint { - uv: [f32; 2], - dir: [f32; 3], - } - let mut faces = Vec::new(); - let mut ctl_pts = Vec::new(); - let mut vertices = Vec::new(); - - // +x plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.0], - dir: [1.0, -1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 1.0], - dir: [1.0, -1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [1.0, 1.0], - dir: [1.0, 1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [1.0, 0.0], - dir: [1.0, 1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.5, 0.5], - dir: [1.0, 0.0, 0.0], - }); - } - - // -x plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { - uv: [1.0, 0.0], - dir: [-1.0, 1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [1.0, 1.0], - dir: [-1.0, 1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 1.0], - dir: [-1.0, -1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.0], - dir: [-1.0, -1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.5, 0.5], - dir: [-1.0, 0.0, 0.0], - }); - } - - // +y plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.0], - dir: [1.0, 1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 1.0], - dir: [1.0, 1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 1.0], - dir: [-1.0, 1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.0], - dir: [-1.0, 1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.5], - dir: [0.0, 1.0, 0.0], - }); - } - - // -y plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.0], - dir: [-1.0, -1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 1.0], - dir: [-1.0, -1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 1.0], - dir: [1.0, -1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.0], - dir: [1.0, -1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.5], - dir: [0.0, -1.0, 0.0], - }); - } - - // +z plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.0], - dir: [-1.0, 1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 1.0], - dir: [-1.0, -1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [1.0, 1.0], - dir: [1.0, -1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [1.0, 0.0], - dir: [1.0, 1.0, 1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.5, 0.5], - dir: [0.0, 0.0, 1.0], - }); - } - - // -z plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { - uv: [1.0, 0.0], - dir: [1.0, 1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [1.0, 1.0], - dir: [1.0, -1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 1.0], - dir: [-1.0, -1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.0, 0.0], - dir: [-1.0, 1.0, -1.0], - }); - ctl_pts.push(ControlPoint { - uv: [0.5, 0.5], - dir: [0.0, 0.0, -1.0], - }); - } - - for pt in ctl_pts { - let p: Vec3 = pt.dir.into(); - let n: Vec3 = p.normalize(); - let t: Vec2 = pt.uv.into(); - vertices.push(Vertex { - position: (p / 2.0).into(), - normal: n.into(), - tex_coord: t.into(), - }); - } - - Mesh { faces, vertices } -} - -fn main() { - let mut cube = make_cube(); - let ret = bevy_mikktspace::generate_tangents(&mut cube); - assert_eq!(true, ret); -} diff --git a/crates/bevy_mikktspace/src/generated.rs b/crates/bevy_mikktspace/src/generated.rs deleted file mode 100644 index b246b9668d00b..0000000000000 --- a/crates/bevy_mikktspace/src/generated.rs +++ /dev/null @@ -1,1871 +0,0 @@ -//! Everything in this module is pending to be refactored, turned into idiomatic-rust, and moved to -//! other modules. - -//! The contents of this file are a combination of transpilation and human -//! modification to Morten S. Mikkelsen's original tangent space algorithm -//! implementation written in C. The original source code can be found at -//! -//! and includes the following license: -//! -//! Copyright (C) 2011 by Morten S. Mikkelsen -//! -//! This software is provided 'as-is', without any express or implied -//! warranty. In no event will the authors be held liable for any damages -//! arising from the use of this software. -//! -//! Permission is granted to anyone to use this software for any purpose, -//! including commercial applications, and to alter it and redistribute it -//! freely, subject to the following restrictions: -//! -//! 1. The origin of this software must not be misrepresented; you must not -//! claim that you wrote the original software. If you use this software -//! in a product, an acknowledgment in the product documentation would be -//! appreciated but is not required. -//! -//! 2. Altered source versions must be plainly marked as such, and must not be -//! misrepresented as being the original software. -//! -//! 3. This notice may not be removed or altered from any source distribution. - -#![allow( - clippy::all, - clippy::redundant_else, - clippy::match_same_arms, - clippy::semicolon_if_nothing_returned, - clippy::explicit_iter_loop, - clippy::map_flatten, - dead_code, - mutable_transmutes, - non_camel_case_types, - non_snake_case, - non_upper_case_globals, - unused_mut, - unused_assignments, - unused_variables, - unsafe_code -)] - -use alloc::{vec, vec::Vec}; -use core::ptr::{self, null_mut}; - -use glam::Vec3; - -use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; - -#[derive(Copy, Clone)] -pub struct STSpace { - pub vOs: Vec3, - pub fMagS: f32, - pub vOt: Vec3, - pub fMagT: f32, - pub iCounter: i32, - pub bOrient: bool, -} - -impl STSpace { - pub fn zero() -> Self { - Self { - vOs: Default::default(), - fMagS: 0.0, - vOt: Default::default(), - fMagT: 0.0, - iCounter: 0, - bOrient: false, - } - } -} - -// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the -// normal map sampler must use the exact inverse of the pixel shader transformation. -// The most efficient transformation we can possibly do in the pixel shader is -// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. -// pixel shader (fast transform out) -// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); -// where vNt is the tangent space normal. The normal map sampler must likewise use the -// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. -// sampler does (exact inverse of pixel shader): -// float3 row0 = cross(vB, vN); -// float3 row1 = cross(vN, vT); -// float3 row2 = cross(vT, vB); -// float fSign = dot(vT, row0)<0 ? -1 : 1; -// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); -// where vNout is the sampled normal in some chosen 3D space. -// -// Should you choose to reconstruct the bitangent in the pixel shader instead -// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. -// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of -// quads as your renderer then problems will occur since the interpolated tangent spaces will differ -// even though the vertex level tangent spaces match. This can be solved either by triangulating before -// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. -// However, this must be used both by the sampler and your tools/rendering pipeline. -// internal structure - -#[derive(Copy, Clone)] -pub struct STriInfo { - pub FaceNeighbors: [i32; 3], - pub AssignedGroup: [*mut SGroup; 3], - pub vOs: Vec3, - pub vOt: Vec3, - pub fMagS: f32, - pub fMagT: f32, - pub iOrgFaceNumber: i32, - pub iFlag: i32, - pub iTSpacesOffs: i32, - pub vert_num: [u8; 4], -} - -impl STriInfo { - fn zero() -> Self { - Self { - FaceNeighbors: [0, 0, 0], - AssignedGroup: [null_mut(), null_mut(), null_mut()], - vOs: Default::default(), - vOt: Default::default(), - fMagS: 0.0, - fMagT: 0.0, - iOrgFaceNumber: 0, - iFlag: 0, - iTSpacesOffs: 0, - vert_num: [0, 0, 0, 0], - } - } -} - -#[derive(Copy, Clone)] -pub struct SGroup { - pub iNrFaces: i32, - pub pFaceIndices: *mut i32, - pub iVertexRepresentative: i32, - pub bOrientPreserving: bool, -} - -impl SGroup { - fn zero() -> Self { - Self { - iNrFaces: 0, - pFaceIndices: null_mut(), - iVertexRepresentative: 0, - bOrientPreserving: false, - } - } -} - -#[derive(Clone)] -pub struct SSubGroup { - pub iNrFaces: i32, - pub pTriMembers: Vec, -} - -impl SSubGroup { - fn zero() -> Self { - Self { - iNrFaces: 0, - pTriMembers: Vec::new(), - } - } -} - -#[derive(Copy, Clone)] -pub union SEdge { - pub unnamed: unnamed, - pub array: [i32; 3], -} - -impl SEdge { - fn zero() -> Self { - Self { array: [0, 0, 0] } - } -} - -#[derive(Copy, Clone)] -pub struct unnamed { - pub i0: i32, - pub i1: i32, - pub f: i32, -} - -#[derive(Copy, Clone)] -pub struct STmpVert { - pub vert: [f32; 3], - pub index: i32, -} - -impl STmpVert { - fn zero() -> Self { - Self { - vert: [0.0, 0.0, 0.0], - index: 0, - } - } -} - -pub unsafe fn genTangSpace(geometry: &mut I, fAngularThreshold: f32) -> bool { - let mut iNrTrianglesIn = 0; - let mut f = 0; - let mut t = 0; - let mut i = 0; - let mut iNrTSPaces = 0; - let mut iTotTris = 0; - let mut iDegenTriangles = 0; - let mut iNrMaxGroups = 0; - let mut iNrActiveGroups: i32 = 0i32; - let mut index = 0; - let iNrFaces = geometry.num_faces(); - let mut bRes: bool = false; - let fThresCos = cos(fAngularThreshold.to_radians()); - f = 0; - while f < iNrFaces { - let verts = geometry.num_vertices_of_face(f); - if verts == 3 { - iNrTrianglesIn += 1 - } else if verts == 4 { - iNrTrianglesIn += 2 - } - f += 1 - } - if iNrTrianglesIn <= 0 { - return false; - } - - let mut piTriListIn = vec![0i32; 3 * iNrTrianglesIn]; - let mut pTriInfos = vec![STriInfo::zero(); iNrTrianglesIn]; - - iNrTSPaces = GenerateInitialVerticesIndexList( - &mut pTriInfos, - &mut piTriListIn, - geometry, - iNrTrianglesIn, - ); - GenerateSharedVerticesIndexList(piTriListIn.as_mut_ptr(), geometry, iNrTrianglesIn); - iTotTris = iNrTrianglesIn; - iDegenTriangles = 0; - t = 0; - while t < iTotTris as usize { - let i0 = piTriListIn[t * 3 + 0]; - let i1 = piTriListIn[t * 3 + 1]; - let i2 = piTriListIn[t * 3 + 2]; - let p0 = get_position(geometry, i0 as usize); - let p1 = get_position(geometry, i1 as usize); - let p2 = get_position(geometry, i2 as usize); - if p0 == p1 || p0 == p2 || p1 == p2 { - pTriInfos[t].iFlag |= 1i32; - iDegenTriangles += 1 - } - t += 1 - } - iNrTrianglesIn = iTotTris - iDegenTriangles; - - if iNrTrianglesIn <= 0 { - return false; - } - DegenPrologue( - pTriInfos.as_mut_ptr(), - piTriListIn.as_mut_ptr(), - iNrTrianglesIn as i32, - iTotTris as i32, - ); - InitTriInfo( - pTriInfos.as_mut_ptr(), - piTriListIn.as_ptr(), - geometry, - iNrTrianglesIn, - ); - iNrMaxGroups = iNrTrianglesIn * 3; - - let mut pGroups = vec![SGroup::zero(); iNrMaxGroups]; - let mut piGroupTrianglesBuffer = vec![0; iNrTrianglesIn * 3]; - - iNrActiveGroups = Build4RuleGroups( - pTriInfos.as_mut_ptr(), - pGroups.as_mut_ptr(), - piGroupTrianglesBuffer.as_mut_ptr(), - piTriListIn.as_ptr(), - iNrTrianglesIn as i32, - ); - - let mut psTspace = vec![ - STSpace { - vOs: Vec3::new(1.0, 0.0, 0.0), - fMagS: 1.0, - vOt: Vec3::new(0.0, 1.0, 0.0), - fMagT: 1.0, - ..STSpace::zero() - }; - iNrTSPaces - ]; - - bRes = GenerateTSpaces( - &mut psTspace, - pTriInfos.as_ptr(), - pGroups.as_ptr(), - iNrActiveGroups, - piTriListIn.as_ptr(), - fThresCos, - geometry, - ); - if !bRes { - return false; - } - DegenEpilogue( - psTspace.as_mut_ptr(), - pTriInfos.as_mut_ptr(), - piTriListIn.as_mut_ptr(), - geometry, - iNrTrianglesIn as i32, - iTotTris as i32, - ); - index = 0; - f = 0; - while f < iNrFaces { - let verts_0 = geometry.num_vertices_of_face(f); - if !(verts_0 != 3 && verts_0 != 4) { - i = 0; - while i < verts_0 { - let mut pTSpace: *const STSpace = &mut psTspace[index] as *mut STSpace; - let mut tang = Vec3::new((*pTSpace).vOs.x, (*pTSpace).vOs.y, (*pTSpace).vOs.z); - let mut bitang = Vec3::new((*pTSpace).vOt.x, (*pTSpace).vOt.y, (*pTSpace).vOt.z); - geometry.set_tangent( - tang.into(), - bitang.into(), - (*pTSpace).fMagS, - (*pTSpace).fMagT, - (*pTSpace).bOrient, - f, - i, - ); - index += 1; - i += 1 - } - } - f += 1 - } - - return true; -} -unsafe fn DegenEpilogue( - mut psTspace: *mut STSpace, - mut pTriInfos: *mut STriInfo, - mut piTriListIn: *mut i32, - geometry: &mut I, - iNrTrianglesIn: i32, - iTotTris: i32, -) { - let mut t: i32 = 0i32; - let mut i: i32 = 0i32; - t = iNrTrianglesIn; - while t < iTotTris { - let bSkip: bool = if (*pTriInfos.offset(t as isize)).iFlag & 2i32 != 0i32 { - true - } else { - false - }; - if !bSkip { - i = 0i32; - while i < 3i32 { - let index1: i32 = *piTriListIn.offset((t * 3i32 + i) as isize); - let mut bNotFound: bool = true; - let mut j: i32 = 0i32; - while bNotFound && j < 3i32 * iNrTrianglesIn { - let index2: i32 = *piTriListIn.offset(j as isize); - if index1 == index2 { - bNotFound = false - } else { - j += 1 - } - } - if !bNotFound { - let iTri: i32 = j / 3i32; - let iVert: i32 = j % 3i32; - let iSrcVert: i32 = - (*pTriInfos.offset(iTri as isize)).vert_num[iVert as usize] as i32; - let iSrcOffs: i32 = (*pTriInfos.offset(iTri as isize)).iTSpacesOffs; - let iDstVert: i32 = (*pTriInfos.offset(t as isize)).vert_num[i as usize] as i32; - let iDstOffs: i32 = (*pTriInfos.offset(t as isize)).iTSpacesOffs; - *psTspace.offset((iDstOffs + iDstVert) as isize) = - *psTspace.offset((iSrcOffs + iSrcVert) as isize) - } - i += 1 - } - } - t += 1 - } - t = 0i32; - while t < iNrTrianglesIn { - if (*pTriInfos.offset(t as isize)).iFlag & 2i32 != 0i32 { - let mut vDstP = Vec3::new(0.0, 0.0, 0.0); - let mut iOrgF: i32 = -1i32; - let mut i_0: i32 = 0i32; - let mut bNotFound_0: bool = false; - let mut pV: *mut u8 = (*pTriInfos.offset(t as isize)).vert_num.as_mut_ptr(); - let mut iFlag: i32 = 1i32 << *pV.offset(0isize) as i32 - | 1i32 << *pV.offset(1isize) as i32 - | 1i32 << *pV.offset(2isize) as i32; - let mut iMissingIndex: i32 = 0i32; - if iFlag & 2i32 == 0i32 { - iMissingIndex = 1i32 - } else if iFlag & 4i32 == 0i32 { - iMissingIndex = 2i32 - } else if iFlag & 8i32 == 0i32 { - iMissingIndex = 3i32 - } - iOrgF = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - vDstP = get_position( - geometry, - face_vert_to_index(iOrgF as usize, iMissingIndex as usize), - ); - bNotFound_0 = true; - i_0 = 0i32; - while bNotFound_0 && i_0 < 3i32 { - let iVert_0: i32 = *pV.offset(i_0 as isize) as i32; - let vSrcP = get_position( - geometry, - face_vert_to_index(iOrgF as usize, iVert_0 as usize), - ); - if vSrcP == vDstP { - let iOffs: i32 = (*pTriInfos.offset(t as isize)).iTSpacesOffs; - *psTspace.offset((iOffs + iMissingIndex) as isize) = - *psTspace.offset((iOffs + iVert_0) as isize); - bNotFound_0 = false - } else { - i_0 += 1 - } - } - } - t += 1 - } -} - -unsafe fn GenerateTSpaces( - psTspace: &mut [STSpace], - mut pTriInfos: *const STriInfo, - mut pGroups: *const SGroup, - iNrActiveGroups: i32, - mut piTriListIn: *const i32, - fThresCos: f32, - geometry: &mut I, -) -> bool { - let mut iMaxNrFaces: usize = 0; - let mut iUniqueTspaces = 0; - let mut g: i32 = 0i32; - let mut i: i32 = 0i32; - g = 0i32; - while g < iNrActiveGroups { - if iMaxNrFaces < (*pGroups.offset(g as isize)).iNrFaces as usize { - iMaxNrFaces = (*pGroups.offset(g as isize)).iNrFaces as usize - } - g += 1 - } - if iMaxNrFaces == 0 { - return true; - } - - let mut pSubGroupTspace = vec![STSpace::zero(); iMaxNrFaces]; - let mut pUniSubGroups = vec![SSubGroup::zero(); iMaxNrFaces]; - let mut pTmpMembers = vec![0i32; iMaxNrFaces]; - - iUniqueTspaces = 0; - g = 0i32; - while g < iNrActiveGroups { - let mut pGroup: *const SGroup = &*pGroups.offset(g as isize) as *const SGroup; - let mut iUniqueSubGroups = 0; - let mut s = 0; - i = 0i32; - while i < (*pGroup).iNrFaces { - let f: i32 = *(*pGroup).pFaceIndices.offset(i as isize); - let mut index: i32 = -1i32; - let mut iVertIndex: i32 = -1i32; - let mut iOF_1: i32 = -1i32; - let mut iMembers: usize = 0; - let mut j: i32 = 0i32; - let mut l: usize = 0; - let mut tmp_group: SSubGroup = SSubGroup { - iNrFaces: 0, - pTriMembers: Vec::new(), - }; - let mut bFound: bool = false; - let mut n = Vec3::new(0.0, 0.0, 0.0); - let mut vOs = Vec3::new(0.0, 0.0, 0.0); - let mut vOt = Vec3::new(0.0, 0.0, 0.0); - if (*pTriInfos.offset(f as isize)).AssignedGroup[0usize] == pGroup as *mut SGroup { - index = 0i32 - } else if (*pTriInfos.offset(f as isize)).AssignedGroup[1usize] == pGroup as *mut SGroup - { - index = 1i32 - } else if (*pTriInfos.offset(f as isize)).AssignedGroup[2usize] == pGroup as *mut SGroup - { - index = 2i32 - } - iVertIndex = *piTriListIn.offset((f * 3i32 + index) as isize); - n = get_normal(geometry, iVertIndex as usize); - let mut vOs = (*pTriInfos.offset(f as isize)).vOs - - (n.dot((*pTriInfos.offset(f as isize)).vOs) * n); - let mut vOt = (*pTriInfos.offset(f as isize)).vOt - - (n.dot((*pTriInfos.offset(f as isize)).vOt) * n); - if VNotZero(vOs) { - vOs = Normalize(vOs) - } - if VNotZero(vOt) { - vOt = Normalize(vOt) - } - iOF_1 = (*pTriInfos.offset(f as isize)).iOrgFaceNumber; - iMembers = 0; - j = 0i32; - while j < (*pGroup).iNrFaces { - let t: i32 = *(*pGroup).pFaceIndices.offset(j as isize); - let iOF_2: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - let mut vOs2 = (*pTriInfos.offset(t as isize)).vOs - - (n.dot((*pTriInfos.offset(t as isize)).vOs) * n); - let mut vOt2 = (*pTriInfos.offset(t as isize)).vOt - - (n.dot((*pTriInfos.offset(t as isize)).vOt) * n); - if VNotZero(vOs2) { - vOs2 = Normalize(vOs2) - } - if VNotZero(vOt2) { - vOt2 = Normalize(vOt2) - } - let bAny: bool = if ((*pTriInfos.offset(f as isize)).iFlag - | (*pTriInfos.offset(t as isize)).iFlag) - & 4i32 - != 0i32 - { - true - } else { - false - }; - let bSameOrgFace: bool = iOF_1 == iOF_2; - let fCosS: f32 = vOs.dot(vOs2); - let fCosT: f32 = vOt.dot(vOt2); - if bAny || bSameOrgFace || fCosS > fThresCos && fCosT > fThresCos { - let fresh0 = iMembers; - iMembers = iMembers + 1; - pTmpMembers[fresh0] = t - } - j += 1 - } - if iMembers > 1 { - let mut uSeed: u32 = 39871946i32 as u32; - QuickSort(pTmpMembers.as_mut_ptr(), 0i32, (iMembers - 1) as i32, uSeed); - } - tmp_group.iNrFaces = iMembers as i32; - tmp_group.pTriMembers = pTmpMembers.clone(); - bFound = false; - l = 0; - while l < iUniqueSubGroups && !bFound { - bFound = CompareSubGroups(&mut tmp_group, &mut pUniSubGroups[l]); - if !bFound { - l += 1 - } - } - if !bFound { - pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers as i32; - pUniSubGroups[iUniqueSubGroups].pTriMembers = tmp_group.pTriMembers.clone(); - - pSubGroupTspace[iUniqueSubGroups] = EvalTspace( - tmp_group.pTriMembers.as_mut_ptr(), - iMembers as i32, - piTriListIn, - pTriInfos, - geometry, - (*pGroup).iVertexRepresentative, - ); - iUniqueSubGroups += 1 - } - let iOffs = (*pTriInfos.offset(f as isize)).iTSpacesOffs as usize; - let iVert = (*pTriInfos.offset(f as isize)).vert_num[index as usize] as usize; - let mut pTS_out: *mut STSpace = &mut psTspace[iOffs + iVert] as *mut STSpace; - if (*pTS_out).iCounter == 1i32 { - *pTS_out = AvgTSpace(pTS_out, &mut pSubGroupTspace[l]); - (*pTS_out).iCounter = 2i32; - (*pTS_out).bOrient = (*pGroup).bOrientPreserving - } else { - *pTS_out = pSubGroupTspace[l]; - (*pTS_out).iCounter = 1i32; - (*pTS_out).bOrient = (*pGroup).bOrientPreserving - } - i += 1 - } - iUniqueTspaces += iUniqueSubGroups; - g += 1 - } - return true; -} -unsafe fn AvgTSpace(mut pTS0: *const STSpace, mut pTS1: *const STSpace) -> STSpace { - let mut ts_res: STSpace = STSpace { - vOs: Vec3::new(0.0, 0.0, 0.0), - fMagS: 0., - vOt: Vec3::new(0.0, 0.0, 0.0), - fMagT: 0., - iCounter: 0, - bOrient: false, - }; - if (*pTS0).fMagS == (*pTS1).fMagS - && (*pTS0).fMagT == (*pTS1).fMagT - && (*pTS0).vOs == (*pTS1).vOs - && (*pTS0).vOt == (*pTS1).vOt - { - ts_res.fMagS = (*pTS0).fMagS; - ts_res.fMagT = (*pTS0).fMagT; - ts_res.vOs = (*pTS0).vOs; - ts_res.vOt = (*pTS0).vOt - } else { - ts_res.fMagS = 0.5f32 * ((*pTS0).fMagS + (*pTS1).fMagS); - ts_res.fMagT = 0.5f32 * ((*pTS0).fMagT + (*pTS1).fMagT); - ts_res.vOs = (*pTS0).vOs + (*pTS1).vOs; - ts_res.vOt = (*pTS0).vOt + (*pTS1).vOt; - if VNotZero(ts_res.vOs) { - ts_res.vOs = Normalize(ts_res.vOs) - } - if VNotZero(ts_res.vOt) { - ts_res.vOt = Normalize(ts_res.vOt) - } - } - return ts_res; -} - -unsafe fn Normalize(v: Vec3) -> Vec3 { - return (1.0 / v.length()) * v; -} - -unsafe fn VNotZero(v: Vec3) -> bool { - NotZero(v.x) || NotZero(v.y) || NotZero(v.z) -} - -unsafe fn NotZero(fX: f32) -> bool { - abs(fX) > 1.17549435e-38f32 -} - -unsafe fn EvalTspace( - mut face_indices: *mut i32, - iFaces: i32, - mut piTriListIn: *const i32, - mut pTriInfos: *const STriInfo, - geometry: &mut I, - iVertexRepresentative: i32, -) -> STSpace { - let mut res: STSpace = STSpace { - vOs: Vec3::new(0.0, 0.0, 0.0), - fMagS: 0., - vOt: Vec3::new(0.0, 0.0, 0.0), - fMagT: 0., - iCounter: 0, - bOrient: false, - }; - let mut fAngleSum: f32 = 0i32 as f32; - let mut face: i32 = 0i32; - res.vOs.x = 0.0f32; - res.vOs.y = 0.0f32; - res.vOs.z = 0.0f32; - res.vOt.x = 0.0f32; - res.vOt.y = 0.0f32; - res.vOt.z = 0.0f32; - res.fMagS = 0i32 as f32; - res.fMagT = 0i32 as f32; - face = 0i32; - while face < iFaces { - let f: i32 = *face_indices.offset(face as isize); - if (*pTriInfos.offset(f as isize)).iFlag & 4i32 == 0i32 { - let mut n = Vec3::new(0.0, 0.0, 0.0); - let mut vOs = Vec3::new(0.0, 0.0, 0.0); - let mut vOt = Vec3::new(0.0, 0.0, 0.0); - let mut p0 = Vec3::new(0.0, 0.0, 0.0); - let mut p1 = Vec3::new(0.0, 0.0, 0.0); - let mut p2 = Vec3::new(0.0, 0.0, 0.0); - let mut v1 = Vec3::new(0.0, 0.0, 0.0); - let mut v2 = Vec3::new(0.0, 0.0, 0.0); - let mut fCos: f32 = 0.; - let mut fAngle: f32 = 0.; - let mut fMagS: f32 = 0.; - let mut fMagT: f32 = 0.; - let mut i: i32 = -1i32; - let mut index: i32 = -1i32; - let mut i0: i32 = -1i32; - let mut i1: i32 = -1i32; - let mut i2: i32 = -1i32; - if *piTriListIn.offset((3i32 * f + 0i32) as isize) == iVertexRepresentative { - i = 0i32 - } else if *piTriListIn.offset((3i32 * f + 1i32) as isize) == iVertexRepresentative { - i = 1i32 - } else if *piTriListIn.offset((3i32 * f + 2i32) as isize) == iVertexRepresentative { - i = 2i32 - } - index = *piTriListIn.offset((3i32 * f + i) as isize); - n = get_normal(geometry, index as usize); - let mut vOs = (*pTriInfos.offset(f as isize)).vOs - - (n.dot((*pTriInfos.offset(f as isize)).vOs) * n); - let mut vOt = (*pTriInfos.offset(f as isize)).vOt - - (n.dot((*pTriInfos.offset(f as isize)).vOt) * n); - if VNotZero(vOs) { - vOs = Normalize(vOs) - } - if VNotZero(vOt) { - vOt = Normalize(vOt) - } - i2 = *piTriListIn.offset((3i32 * f + if i < 2i32 { i + 1i32 } else { 0i32 }) as isize); - i1 = *piTriListIn.offset((3i32 * f + i) as isize); - i0 = *piTriListIn.offset((3i32 * f + if i > 0i32 { i - 1i32 } else { 2i32 }) as isize); - p0 = get_position(geometry, i0 as usize); - p1 = get_position(geometry, i1 as usize); - p2 = get_position(geometry, i2 as usize); - v1 = p0 - p1; - v2 = p2 - p1; - let mut v1 = v1 - (n.dot(v1) * n); - if VNotZero(v1) { - v1 = Normalize(v1) - } - let mut v2 = v2 - (n.dot(v2) * n); - if VNotZero(v2) { - v2 = Normalize(v2) - } - let fCos = v1.dot(v2); - - let fCos = if fCos > 1i32 as f32 { - 1i32 as f32 - } else if fCos < -1i32 as f32 { - -1i32 as f32 - } else { - fCos - }; - fAngle = acosf64(fCos as f64) as f32; - fMagS = (*pTriInfos.offset(f as isize)).fMagS; - fMagT = (*pTriInfos.offset(f as isize)).fMagT; - res.vOs = res.vOs + (fAngle * vOs); - res.vOt = res.vOt + (fAngle * vOt); - res.fMagS += fAngle * fMagS; - res.fMagT += fAngle * fMagT; - fAngleSum += fAngle - } - face += 1 - } - if VNotZero(res.vOs) { - res.vOs = Normalize(res.vOs) - } - if VNotZero(res.vOt) { - res.vOt = Normalize(res.vOt) - } - if fAngleSum > 0i32 as f32 { - res.fMagS /= fAngleSum; - res.fMagT /= fAngleSum - } - return res; -} - -unsafe fn CompareSubGroups(mut pg1: *const SSubGroup, mut pg2: *const SSubGroup) -> bool { - let mut bStillSame: bool = true; - let mut i = 0; - if (*pg1).iNrFaces != (*pg2).iNrFaces { - return false; - } - while i < (*pg1).iNrFaces as usize && bStillSame { - bStillSame = if (&(*pg1).pTriMembers)[i] == (&(*pg2).pTriMembers)[i] { - true - } else { - false - }; - if bStillSame { - i += 1 - } - } - return bStillSame; -} -unsafe fn QuickSort(mut pSortBuffer: *mut i32, mut iLeft: i32, mut iRight: i32, mut uSeed: u32) { - let mut iL: i32 = 0; - let mut iR: i32 = 0; - let mut n: i32 = 0; - let mut index: i32 = 0; - let mut iMid: i32 = 0; - let mut iTmp: i32 = 0; - - // Random - let mut t: u32 = uSeed & 31i32 as u32; - t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as u32).wrapping_sub(t)); - uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as u32); - // Random end - - iL = iLeft; - iR = iRight; - n = iR - iL + 1i32; - index = uSeed.wrapping_rem(n as u32) as i32; - iMid = *pSortBuffer.offset((index + iL) as isize); - loop { - while *pSortBuffer.offset(iL as isize) < iMid { - iL += 1 - } - while *pSortBuffer.offset(iR as isize) > iMid { - iR -= 1 - } - if iL <= iR { - iTmp = *pSortBuffer.offset(iL as isize); - *pSortBuffer.offset(iL as isize) = *pSortBuffer.offset(iR as isize); - *pSortBuffer.offset(iR as isize) = iTmp; - iL += 1; - iR -= 1 - } - if !(iL <= iR) { - break; - } - } - if iLeft < iR { - QuickSort(pSortBuffer, iLeft, iR, uSeed); - } - if iL < iRight { - QuickSort(pSortBuffer, iL, iRight, uSeed); - }; -} -unsafe fn Build4RuleGroups( - mut pTriInfos: *mut STriInfo, - mut pGroups: *mut SGroup, - mut piGroupTrianglesBuffer: *mut i32, - mut piTriListIn: *const i32, - iNrTrianglesIn: i32, -) -> i32 { - let iNrMaxGroups: i32 = iNrTrianglesIn * 3i32; - let mut iNrActiveGroups: i32 = 0i32; - let mut iOffset: i32 = 0i32; - let mut f: i32 = 0i32; - let mut i: i32 = 0i32; - f = 0i32; - while f < iNrTrianglesIn { - i = 0i32; - while i < 3i32 { - if (*pTriInfos.offset(f as isize)).iFlag & 4i32 == 0i32 - && (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize].is_null() - { - let mut bOrPre: bool = false; - let mut neigh_indexL: i32 = 0; - let mut neigh_indexR: i32 = 0; - let vert_index: i32 = *piTriListIn.offset((f * 3i32 + i) as isize); - let ref mut fresh2 = (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]; - *fresh2 = ptr::from_mut(&mut *pGroups.offset(iNrActiveGroups as isize)); - (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]) - .iVertexRepresentative = vert_index; - (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).bOrientPreserving = - (*pTriInfos.offset(f as isize)).iFlag & 8i32 != 0i32; - (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).iNrFaces = 0i32; - let ref mut fresh3 = - (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).pFaceIndices; - *fresh3 = ptr::from_mut(&mut *piGroupTrianglesBuffer.offset(iOffset as isize)); - iNrActiveGroups += 1; - AddTriToGroup((*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], f); - bOrPre = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - neigh_indexL = (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize]; - neigh_indexR = (*pTriInfos.offset(f as isize)).FaceNeighbors - [(if i > 0i32 { i - 1i32 } else { 2i32 }) as usize]; - if neigh_indexL >= 0i32 { - let bAnswer: bool = AssignRecur( - piTriListIn, - pTriInfos, - neigh_indexL, - (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], - ); - let bOrPre2: bool = - if (*pTriInfos.offset(neigh_indexL as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - let bDiff: bool = if bOrPre != bOrPre2 { true } else { false }; - } - if neigh_indexR >= 0i32 { - let bAnswer_0: bool = AssignRecur( - piTriListIn, - pTriInfos, - neigh_indexR, - (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], - ); - let bOrPre2_0: bool = - if (*pTriInfos.offset(neigh_indexR as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - let bDiff_0: bool = if bOrPre != bOrPre2_0 { true } else { false }; - } - iOffset += (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).iNrFaces - } - i += 1 - } - f += 1 - } - return iNrActiveGroups; -} -// /////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// -unsafe fn AssignRecur( - mut piTriListIn: *const i32, - mut psTriInfos: *mut STriInfo, - iMyTriIndex: i32, - mut pGroup: *mut SGroup, -) -> bool { - let mut pMyTriInfo: *mut STriInfo = - &mut *psTriInfos.offset(iMyTriIndex as isize) as *mut STriInfo; - // track down vertex - let iVertRep: i32 = (*pGroup).iVertexRepresentative; - let mut pVerts: *const i32 = - &*piTriListIn.offset((3i32 * iMyTriIndex + 0i32) as isize) as *const i32; - let mut i: i32 = -1i32; - if *pVerts.offset(0isize) == iVertRep { - i = 0i32 - } else if *pVerts.offset(1isize) == iVertRep { - i = 1i32 - } else if *pVerts.offset(2isize) == iVertRep { - i = 2i32 - } - if (*pMyTriInfo).AssignedGroup[i as usize] == pGroup { - return true; - } else { - if !(*pMyTriInfo).AssignedGroup[i as usize].is_null() { - return false; - } - } - if (*pMyTriInfo).iFlag & 4i32 != 0i32 { - if (*pMyTriInfo).AssignedGroup[0usize].is_null() - && (*pMyTriInfo).AssignedGroup[1usize].is_null() - && (*pMyTriInfo).AssignedGroup[2usize].is_null() - { - (*pMyTriInfo).iFlag &= !8i32; - (*pMyTriInfo).iFlag |= if (*pGroup).bOrientPreserving { - 8i32 - } else { - 0i32 - } - } - } - let bOrient: bool = if (*pMyTriInfo).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - if bOrient != (*pGroup).bOrientPreserving { - return false; - } - AddTriToGroup(pGroup, iMyTriIndex); - (*pMyTriInfo).AssignedGroup[i as usize] = pGroup; - let neigh_indexL: i32 = (*pMyTriInfo).FaceNeighbors[i as usize]; - let neigh_indexR: i32 = - (*pMyTriInfo).FaceNeighbors[(if i > 0i32 { i - 1i32 } else { 2i32 }) as usize]; - if neigh_indexL >= 0i32 { - AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); - } - if neigh_indexR >= 0i32 { - AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); - } - return true; -} -unsafe fn AddTriToGroup(mut pGroup: *mut SGroup, iTriIndex: i32) { - *(*pGroup).pFaceIndices.offset((*pGroup).iNrFaces as isize) = iTriIndex; - (*pGroup).iNrFaces += 1; -} -unsafe fn InitTriInfo( - mut pTriInfos: *mut STriInfo, - mut piTriListIn: *const i32, - geometry: &mut I, - iNrTrianglesIn: usize, -) { - let mut f = 0; - let mut i = 0; - let mut t = 0; - f = 0; - while f < iNrTrianglesIn { - i = 0i32; - while i < 3i32 { - (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize] = -1i32; - let ref mut fresh4 = (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]; - *fresh4 = 0 as *mut SGroup; - (*pTriInfos.offset(f as isize)).vOs.x = 0.0f32; - (*pTriInfos.offset(f as isize)).vOs.y = 0.0f32; - (*pTriInfos.offset(f as isize)).vOs.z = 0.0f32; - (*pTriInfos.offset(f as isize)).vOt.x = 0.0f32; - (*pTriInfos.offset(f as isize)).vOt.y = 0.0f32; - (*pTriInfos.offset(f as isize)).vOt.z = 0.0f32; - (*pTriInfos.offset(f as isize)).fMagS = 0i32 as f32; - (*pTriInfos.offset(f as isize)).fMagT = 0i32 as f32; - (*pTriInfos.offset(f as isize)).iFlag |= 4i32; - i += 1 - } - f += 1 - } - f = 0; - while f < iNrTrianglesIn { - let v1 = get_position(geometry, *piTriListIn.offset((f * 3 + 0) as isize) as usize); - let v2 = get_position(geometry, *piTriListIn.offset((f * 3 + 1) as isize) as usize); - let v3 = get_position(geometry, *piTriListIn.offset((f * 3 + 2) as isize) as usize); - let t1 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 0) as isize) as usize); - let t2 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 1) as isize) as usize); - let t3 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 2) as isize) as usize); - let t21x: f32 = t2.x - t1.x; - let t21y: f32 = t2.y - t1.y; - let t31x: f32 = t3.x - t1.x; - let t31y: f32 = t3.y - t1.y; - let d1 = v2 - v1; - let d2 = v3 - v1; - let fSignedAreaSTx2: f32 = t21x * t31y - t21y * t31x; - let mut vOs = (t31y * d1) - (t21y * d2); - let mut vOt = (-t31x * d1) + (t21x * d2); - (*pTriInfos.offset(f as isize)).iFlag |= if fSignedAreaSTx2 > 0i32 as f32 { - 8i32 - } else { - 0i32 - }; - if NotZero(fSignedAreaSTx2) { - let fAbsArea: f32 = abs(fSignedAreaSTx2); - let fLenOs: f32 = vOs.length(); - let fLenOt: f32 = vOt.length(); - let fS: f32 = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 == 0i32 { - -1.0f32 - } else { - 1.0f32 - }; - if NotZero(fLenOs) { - (*pTriInfos.offset(f as isize)).vOs = (fS / fLenOs) * vOs - } - if NotZero(fLenOt) { - (*pTriInfos.offset(f as isize)).vOt = (fS / fLenOt) * vOt - } - (*pTriInfos.offset(f as isize)).fMagS = fLenOs / fAbsArea; - (*pTriInfos.offset(f as isize)).fMagT = fLenOt / fAbsArea; - if NotZero((*pTriInfos.offset(f as isize)).fMagS) - && NotZero((*pTriInfos.offset(f as isize)).fMagT) - { - (*pTriInfos.offset(f as isize)).iFlag &= !4i32 - } - } - f += 1 - } - while t + 1 < iNrTrianglesIn { - let iFO_a: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - let iFO_b: i32 = (*pTriInfos.offset((t + 1) as isize)).iOrgFaceNumber; - if iFO_a == iFO_b { - let bIsDeg_a: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 != 0i32 { - true - } else { - false - }; - let bIsDeg_b: bool = if (*pTriInfos.offset((t + 1) as isize)).iFlag & 1i32 != 0i32 { - true - } else { - false - }; - if !(bIsDeg_a || bIsDeg_b) { - let bOrientA: bool = if (*pTriInfos.offset(t as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - let bOrientB: bool = if (*pTriInfos.offset((t + 1) as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - if bOrientA != bOrientB { - let mut bChooseOrientFirstTri: bool = false; - if (*pTriInfos.offset((t + 1) as isize)).iFlag & 4i32 != 0i32 { - bChooseOrientFirstTri = true - } else if CalcTexArea(geometry, piTriListIn.offset((t * 3 + 0) as isize)) - >= CalcTexArea(geometry, piTriListIn.offset(((t + 1) * 3 + 0) as isize)) - { - bChooseOrientFirstTri = true - } - let t0 = if bChooseOrientFirstTri { t } else { t + 1 }; - let t1_0 = if bChooseOrientFirstTri { t + 1 } else { t }; - (*pTriInfos.offset(t1_0 as isize)).iFlag &= !8i32; - (*pTriInfos.offset(t1_0 as isize)).iFlag |= - (*pTriInfos.offset(t0 as isize)).iFlag & 8i32 - } - } - t += 2 - } else { - t += 1 - } - } - - let mut pEdges = vec![SEdge::zero(); iNrTrianglesIn * 3]; - BuildNeighborsFast( - pTriInfos, - pEdges.as_mut_ptr(), - piTriListIn, - iNrTrianglesIn as i32, - ); -} - -unsafe fn BuildNeighborsFast( - mut pTriInfos: *mut STriInfo, - mut pEdges: *mut SEdge, - mut piTriListIn: *const i32, - iNrTrianglesIn: i32, -) { - // build array of edges - // could replace with a random seed? - let mut uSeed: u32 = 39871946i32 as u32; - let mut iEntries: i32 = 0i32; - let mut iCurStartIndex: i32 = -1i32; - let mut f: i32 = 0i32; - let mut i: i32 = 0i32; - f = 0i32; - while f < iNrTrianglesIn { - i = 0i32; - while i < 3i32 { - let i0: i32 = *piTriListIn.offset((f * 3i32 + i) as isize); - let i1: i32 = - *piTriListIn.offset((f * 3i32 + if i < 2i32 { i + 1i32 } else { 0i32 }) as isize); - (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.i0 = if i0 < i1 { i0 } else { i1 }; - (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.i1 = if !(i0 < i1) { i0 } else { i1 }; - (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.f = f; - i += 1 - } - f += 1 - } - QuickSortEdges(pEdges, 0i32, iNrTrianglesIn * 3i32 - 1i32, 0i32, uSeed); - iEntries = iNrTrianglesIn * 3i32; - iCurStartIndex = 0i32; - i = 1i32; - while i < iEntries { - if (*pEdges.offset(iCurStartIndex as isize)).unnamed.i0 - != (*pEdges.offset(i as isize)).unnamed.i0 - { - let iL: i32 = iCurStartIndex; - let iR: i32 = i - 1i32; - iCurStartIndex = i; - QuickSortEdges(pEdges, iL, iR, 1i32, uSeed); - } - i += 1 - } - iCurStartIndex = 0i32; - i = 1i32; - while i < iEntries { - if (*pEdges.offset(iCurStartIndex as isize)).unnamed.i0 - != (*pEdges.offset(i as isize)).unnamed.i0 - || (*pEdges.offset(iCurStartIndex as isize)).unnamed.i1 - != (*pEdges.offset(i as isize)).unnamed.i1 - { - let iL_0: i32 = iCurStartIndex; - let iR_0: i32 = i - 1i32; - iCurStartIndex = i; - QuickSortEdges(pEdges, iL_0, iR_0, 2i32, uSeed); - } - i += 1 - } - i = 0i32; - while i < iEntries { - let i0_0: i32 = (*pEdges.offset(i as isize)).unnamed.i0; - let i1_0: i32 = (*pEdges.offset(i as isize)).unnamed.i1; - let f_0: i32 = (*pEdges.offset(i as isize)).unnamed.f; - let mut bUnassigned_A: bool = false; - let mut i0_A: i32 = 0; - let mut i1_A: i32 = 0; - let mut edgenum_A: i32 = 0; - let mut edgenum_B: i32 = 0i32; - GetEdge( - &mut i0_A, - &mut i1_A, - &mut edgenum_A, - &*piTriListIn.offset((f_0 * 3i32) as isize), - i0_0, - i1_0, - ); - bUnassigned_A = - if (*pTriInfos.offset(f_0 as isize)).FaceNeighbors[edgenum_A as usize] == -1i32 { - true - } else { - false - }; - if bUnassigned_A { - let mut j: i32 = i + 1i32; - let mut t: i32 = 0; - let mut bNotFound: bool = true; - while j < iEntries - && i0_0 == (*pEdges.offset(j as isize)).unnamed.i0 - && i1_0 == (*pEdges.offset(j as isize)).unnamed.i1 - && bNotFound - { - let mut bUnassigned_B: bool = false; - let mut i0_B: i32 = 0; - let mut i1_B: i32 = 0; - t = (*pEdges.offset(j as isize)).unnamed.f; - GetEdge( - &mut i1_B, - &mut i0_B, - &mut edgenum_B, - &*piTriListIn.offset((t * 3i32) as isize), - (*pEdges.offset(j as isize)).unnamed.i0, - (*pEdges.offset(j as isize)).unnamed.i1, - ); - bUnassigned_B = - if (*pTriInfos.offset(t as isize)).FaceNeighbors[edgenum_B as usize] == -1i32 { - true - } else { - false - }; - if i0_A == i0_B && i1_A == i1_B && bUnassigned_B { - bNotFound = false - } else { - j += 1 - } - } - if !bNotFound { - let mut t_0: i32 = (*pEdges.offset(j as isize)).unnamed.f; - (*pTriInfos.offset(f_0 as isize)).FaceNeighbors[edgenum_A as usize] = t_0; - (*pTriInfos.offset(t_0 as isize)).FaceNeighbors[edgenum_B as usize] = f_0 - } - } - i += 1 - } -} -unsafe fn GetEdge( - mut i0_out: *mut i32, - mut i1_out: *mut i32, - mut edgenum_out: *mut i32, - mut indices: *const i32, - i0_in: i32, - i1_in: i32, -) { - *edgenum_out = -1i32; - if *indices.offset(0isize) == i0_in || *indices.offset(0isize) == i1_in { - if *indices.offset(1isize) == i0_in || *indices.offset(1isize) == i1_in { - *edgenum_out.offset(0isize) = 0i32; - *i0_out.offset(0isize) = *indices.offset(0isize); - *i1_out.offset(0isize) = *indices.offset(1isize) - } else { - *edgenum_out.offset(0isize) = 2i32; - *i0_out.offset(0isize) = *indices.offset(2isize); - *i1_out.offset(0isize) = *indices.offset(0isize) - } - } else { - *edgenum_out.offset(0isize) = 1i32; - *i0_out.offset(0isize) = *indices.offset(1isize); - *i1_out.offset(0isize) = *indices.offset(2isize) - }; -} -// /////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////// -unsafe fn QuickSortEdges( - mut pSortBuffer: *mut SEdge, - mut iLeft: i32, - mut iRight: i32, - channel: i32, - mut uSeed: u32, -) { - let mut t: u32 = 0; - let mut iL: i32 = 0; - let mut iR: i32 = 0; - let mut n: i32 = 0; - let mut index: i32 = 0; - let mut iMid: i32 = 0; - // early out - let mut sTmp: SEdge = SEdge { - unnamed: unnamed { i0: 0, i1: 0, f: 0 }, - }; - let iElems: i32 = iRight - iLeft + 1i32; - if iElems < 2i32 { - return; - } else { - if iElems == 2i32 { - if (*pSortBuffer.offset(iLeft as isize)).array[channel as usize] - > (*pSortBuffer.offset(iRight as isize)).array[channel as usize] - { - sTmp = *pSortBuffer.offset(iLeft as isize); - *pSortBuffer.offset(iLeft as isize) = *pSortBuffer.offset(iRight as isize); - *pSortBuffer.offset(iRight as isize) = sTmp - } - return; - } - } - - // Random - t = uSeed & 31i32 as u32; - t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as u32).wrapping_sub(t)); - uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as u32); - // Random end - - iL = iLeft; - iR = iRight; - n = iR - iL + 1i32; - index = uSeed.wrapping_rem(n as u32) as i32; - iMid = (*pSortBuffer.offset((index + iL) as isize)).array[channel as usize]; - loop { - while (*pSortBuffer.offset(iL as isize)).array[channel as usize] < iMid { - iL += 1 - } - while (*pSortBuffer.offset(iR as isize)).array[channel as usize] > iMid { - iR -= 1 - } - if iL <= iR { - sTmp = *pSortBuffer.offset(iL as isize); - *pSortBuffer.offset(iL as isize) = *pSortBuffer.offset(iR as isize); - *pSortBuffer.offset(iR as isize) = sTmp; - iL += 1; - iR -= 1 - } - if !(iL <= iR) { - break; - } - } - if iLeft < iR { - QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); - } - if iL < iRight { - QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); - }; -} - -// returns the texture area times 2 -unsafe fn CalcTexArea(geometry: &mut I, mut indices: *const i32) -> f32 { - let t1 = get_tex_coord(geometry, *indices.offset(0isize) as usize); - let t2 = get_tex_coord(geometry, *indices.offset(1isize) as usize); - let t3 = get_tex_coord(geometry, *indices.offset(2isize) as usize); - let t21x: f32 = t2.x - t1.x; - let t21y: f32 = t2.y - t1.y; - let t31x: f32 = t3.x - t1.x; - let t31y: f32 = t3.y - t1.y; - let fSignedAreaSTx2: f32 = t21x * t31y - t21y * t31x; - return if fSignedAreaSTx2 < 0i32 as f32 { - -fSignedAreaSTx2 - } else { - fSignedAreaSTx2 - }; -} - -// degen triangles -unsafe fn DegenPrologue( - mut pTriInfos: *mut STriInfo, - mut piTriList_out: *mut i32, - iNrTrianglesIn: i32, - iTotTris: i32, -) { - let mut iNextGoodTriangleSearchIndex: i32 = -1i32; - let mut bStillFindingGoodOnes: bool = false; - // locate quads with only one good triangle - let mut t: i32 = 0i32; - while t < iTotTris - 1i32 { - let iFO_a: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - let iFO_b: i32 = (*pTriInfos.offset((t + 1i32) as isize)).iOrgFaceNumber; - if iFO_a == iFO_b { - let bIsDeg_a: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 != 0i32 { - true - } else { - false - }; - let bIsDeg_b: bool = if (*pTriInfos.offset((t + 1i32) as isize)).iFlag & 1i32 != 0i32 { - true - } else { - false - }; - if bIsDeg_a ^ bIsDeg_b != false { - (*pTriInfos.offset(t as isize)).iFlag |= 2i32; - (*pTriInfos.offset((t + 1i32) as isize)).iFlag |= 2i32 - } - t += 2i32 - } else { - t += 1 - } - } - iNextGoodTriangleSearchIndex = 1i32; - t = 0i32; - bStillFindingGoodOnes = true; - while t < iNrTrianglesIn && bStillFindingGoodOnes { - let bIsGood: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 == 0i32 { - true - } else { - false - }; - if bIsGood { - if iNextGoodTriangleSearchIndex < t + 2i32 { - iNextGoodTriangleSearchIndex = t + 2i32 - } - } else { - let mut t0: i32 = 0; - let mut t1: i32 = 0; - let mut bJustADegenerate: bool = true; - while bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris { - let bIsGood_0: bool = - if (*pTriInfos.offset(iNextGoodTriangleSearchIndex as isize)).iFlag & 1i32 - == 0i32 - { - true - } else { - false - }; - if bIsGood_0 { - bJustADegenerate = false - } else { - iNextGoodTriangleSearchIndex += 1 - } - } - t0 = t; - t1 = iNextGoodTriangleSearchIndex; - iNextGoodTriangleSearchIndex += 1; - if !bJustADegenerate { - let mut i: i32 = 0i32; - i = 0i32; - while i < 3i32 { - let index: i32 = *piTriList_out.offset((t0 * 3i32 + i) as isize); - *piTriList_out.offset((t0 * 3i32 + i) as isize) = - *piTriList_out.offset((t1 * 3i32 + i) as isize); - *piTriList_out.offset((t1 * 3i32 + i) as isize) = index; - i += 1 - } - let tri_info: STriInfo = *pTriInfos.offset(t0 as isize); - *pTriInfos.offset(t0 as isize) = *pTriInfos.offset(t1 as isize); - *pTriInfos.offset(t1 as isize) = tri_info - } else { - bStillFindingGoodOnes = false - } - } - if bStillFindingGoodOnes { - t += 1 - } - } -} -unsafe fn GenerateSharedVerticesIndexList( - mut piTriList_in_and_out: *mut i32, - geometry: &mut I, - iNrTrianglesIn: usize, -) { - let mut i = 0; - let mut iChannel: i32 = 0i32; - let mut k = 0; - let mut e = 0; - let mut iMaxCount = 0; - let mut vMin = get_position(geometry, 0); - let mut vMax = vMin; - let mut vDim = Vec3::new(0.0, 0.0, 0.0); - let mut fMin: f32 = 0.; - let mut fMax: f32 = 0.; - i = 1; - while i < iNrTrianglesIn * 3 { - let index: i32 = *piTriList_in_and_out.offset(i as isize); - let vP = get_position(geometry, index as usize); - if vMin.x > vP.x { - vMin.x = vP.x - } else if vMax.x < vP.x { - vMax.x = vP.x - } - if vMin.y > vP.y { - vMin.y = vP.y - } else if vMax.y < vP.y { - vMax.y = vP.y - } - if vMin.z > vP.z { - vMin.z = vP.z - } else if vMax.z < vP.z { - vMax.z = vP.z - } - i += 1 - } - vDim = vMax - vMin; - iChannel = 0i32; - fMin = vMin.x; - fMax = vMax.x; - if vDim.y > vDim.x && vDim.y > vDim.z { - iChannel = 1i32; - fMin = vMin.y; - fMax = vMax.y - } else if vDim.z > vDim.x { - iChannel = 2i32; - fMin = vMin.z; - fMax = vMax.z - } - - let mut piHashTable = vec![0i32; iNrTrianglesIn * 3]; - let mut piHashOffsets = vec![0i32; g_iCells]; - let mut piHashCount = vec![0i32; g_iCells]; - let mut piHashCount2 = vec![0i32; g_iCells]; - - i = 0; - while i < iNrTrianglesIn * 3 { - let index_0: i32 = *piTriList_in_and_out.offset(i as isize); - let vP_0 = get_position(geometry, index_0 as usize); - let fVal: f32 = if iChannel == 0i32 { - vP_0.x - } else if iChannel == 1i32 { - vP_0.y - } else { - vP_0.z - }; - let iCell = FindGridCell(fMin, fMax, fVal); - piHashCount[iCell] += 1; - i += 1 - } - piHashOffsets[0] = 0i32; - k = 1; - while k < g_iCells { - piHashOffsets[k] = piHashOffsets[k - 1] + piHashCount[k - 1]; - k += 1 - } - i = 0; - while i < iNrTrianglesIn * 3 { - let index_1: i32 = *piTriList_in_and_out.offset(i as isize); - let vP_1 = get_position(geometry, index_1 as usize); - let fVal_0: f32 = if iChannel == 0i32 { - vP_1.x - } else if iChannel == 1i32 { - vP_1.y - } else { - vP_1.z - }; - let iCell_0 = FindGridCell(fMin, fMax, fVal_0); - piHashTable[(piHashOffsets[iCell_0] + piHashCount2[iCell_0]) as usize] = i as i32; - piHashCount2[iCell_0] += 1; - i += 1 - } - k = 0; - while k < g_iCells { - k += 1 - } - iMaxCount = piHashCount[0] as usize; - k = 1; - while k < g_iCells { - if iMaxCount < piHashCount[k] as usize { - iMaxCount = piHashCount[k] as usize - } - k += 1 - } - let mut pTmpVert = vec![STmpVert::zero(); iMaxCount]; - k = 0; - while k < g_iCells { - // extract table of cell k and amount of entries in it - let pTable_0 = piHashTable.as_mut_ptr().offset(piHashOffsets[k] as isize); - let iEntries = piHashCount[k] as usize; - if !(iEntries < 2) { - e = 0; - while e < iEntries { - let mut i_0: i32 = *pTable_0.offset(e as isize); - let vP_2 = get_position( - geometry, - *piTriList_in_and_out.offset(i_0 as isize) as usize, - ); - pTmpVert[e].vert[0usize] = vP_2.x; - pTmpVert[e].vert[1usize] = vP_2.y; - pTmpVert[e].vert[2usize] = vP_2.z; - pTmpVert[e].index = i_0; - e += 1 - } - MergeVertsFast( - piTriList_in_and_out, - pTmpVert.as_mut_ptr(), - geometry, - 0i32, - (iEntries - 1) as i32, - ); - } - k += 1 - } -} - -unsafe fn MergeVertsFast( - mut piTriList_in_and_out: *mut i32, - mut pTmpVert: *mut STmpVert, - geometry: &mut I, - iL_in: i32, - iR_in: i32, -) { - // make bbox - let mut c: i32 = 0i32; - let mut l: i32 = 0i32; - let mut channel: i32 = 0i32; - let mut fvMin: [f32; 3] = [0.; 3]; - let mut fvMax: [f32; 3] = [0.; 3]; - let mut dx: f32 = 0i32 as f32; - let mut dy: f32 = 0i32 as f32; - let mut dz: f32 = 0i32 as f32; - let mut fSep: f32 = 0i32 as f32; - c = 0i32; - while c < 3i32 { - fvMin[c as usize] = (*pTmpVert.offset(iL_in as isize)).vert[c as usize]; - fvMax[c as usize] = fvMin[c as usize]; - c += 1 - } - l = iL_in + 1i32; - while l <= iR_in { - c = 0i32; - while c < 3i32 { - if fvMin[c as usize] > (*pTmpVert.offset(l as isize)).vert[c as usize] { - fvMin[c as usize] = (*pTmpVert.offset(l as isize)).vert[c as usize] - } else if fvMax[c as usize] < (*pTmpVert.offset(l as isize)).vert[c as usize] { - fvMax[c as usize] = (*pTmpVert.offset(l as isize)).vert[c as usize] - } - c += 1 - } - l += 1 - } - dx = fvMax[0usize] - fvMin[0usize]; - dy = fvMax[1usize] - fvMin[1usize]; - dz = fvMax[2usize] - fvMin[2usize]; - channel = 0i32; - if dy > dx && dy > dz { - channel = 1i32 - } else if dz > dx { - channel = 2i32 - } - fSep = 0.5f32 * (fvMax[channel as usize] + fvMin[channel as usize]); - if fSep >= fvMax[channel as usize] || fSep <= fvMin[channel as usize] { - l = iL_in; - while l <= iR_in { - let mut i: i32 = (*pTmpVert.offset(l as isize)).index; - let index: i32 = *piTriList_in_and_out.offset(i as isize); - let vP = get_position(geometry, index as usize); - let vN = get_normal(geometry, index as usize); - let vT = get_tex_coord(geometry, index as usize); - let mut bNotFound: bool = true; - let mut l2: i32 = iL_in; - let mut i2rec: i32 = -1i32; - while l2 < l && bNotFound { - let i2: i32 = (*pTmpVert.offset(l2 as isize)).index; - let index2: i32 = *piTriList_in_and_out.offset(i2 as isize); - let vP2 = get_position(geometry, index2 as usize); - let vN2 = get_normal(geometry, index2 as usize); - let vT2 = get_tex_coord(geometry, index2 as usize); - i2rec = i2; - if vP.x == vP2.x - && vP.y == vP2.y - && vP.z == vP2.z - && vN.x == vN2.x - && vN.y == vN2.y - && vN.z == vN2.z - && vT.x == vT2.x - && vT.y == vT2.y - && vT.z == vT2.z - { - bNotFound = false - } else { - l2 += 1 - } - } - if !bNotFound { - *piTriList_in_and_out.offset(i as isize) = - *piTriList_in_and_out.offset(i2rec as isize) - } - l += 1 - } - } else { - let mut iL: i32 = iL_in; - let mut iR: i32 = iR_in; - while iL < iR { - let mut bReadyLeftSwap: bool = false; - let mut bReadyRightSwap: bool = false; - while !bReadyLeftSwap && iL < iR { - bReadyLeftSwap = !((*pTmpVert.offset(iL as isize)).vert[channel as usize] < fSep); - if !bReadyLeftSwap { - iL += 1 - } - } - while !bReadyRightSwap && iL < iR { - bReadyRightSwap = (*pTmpVert.offset(iR as isize)).vert[channel as usize] < fSep; - if !bReadyRightSwap { - iR -= 1 - } - } - if bReadyLeftSwap && bReadyRightSwap { - let sTmp: STmpVert = *pTmpVert.offset(iL as isize); - *pTmpVert.offset(iL as isize) = *pTmpVert.offset(iR as isize); - *pTmpVert.offset(iR as isize) = sTmp; - iL += 1; - iR -= 1 - } - } - if iL == iR { - let bReadyRightSwap_0: bool = - (*pTmpVert.offset(iR as isize)).vert[channel as usize] < fSep; - if bReadyRightSwap_0 { - iL += 1 - } else { - iR -= 1 - } - } - if iL_in < iR { - MergeVertsFast(piTriList_in_and_out, pTmpVert, geometry, iL_in, iR); - } - if iL < iR_in { - MergeVertsFast(piTriList_in_and_out, pTmpVert, geometry, iL, iR_in); - } - }; -} - -const g_iCells: usize = 2048; - -// it is IMPORTANT that this function is called to evaluate the hash since -// inlining could potentially reorder instructions and generate different -// results for the same effective input value fVal. -#[inline(never)] -unsafe fn FindGridCell(fMin: f32, fMax: f32, fVal: f32) -> usize { - let fIndex = g_iCells as f32 * ((fVal - fMin) / (fMax - fMin)); - let iIndex = fIndex as isize; - return if iIndex < g_iCells as isize { - if iIndex >= 0 { - iIndex as usize - } else { - 0 - } - } else { - g_iCells - 1 - }; -} - -unsafe fn GenerateInitialVerticesIndexList( - pTriInfos: &mut [STriInfo], - piTriList_out: &mut [i32], - geometry: &mut I, - iNrTrianglesIn: usize, -) -> usize { - let mut iTSpacesOffs: usize = 0; - let mut f = 0; - let mut t: usize = 0; - let mut iDstTriIndex = 0; - f = 0; - while f < geometry.num_faces() { - let verts = geometry.num_vertices_of_face(f); - if !(verts != 3 && verts != 4) { - pTriInfos[iDstTriIndex].iOrgFaceNumber = f as i32; - pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs as i32; - if verts == 3 { - let mut pVerts = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts[0] = 0; - pVerts[1] = 1; - pVerts[2] = 2; - piTriList_out[iDstTriIndex * 3 + 0] = face_vert_to_index(f, 0) as i32; - piTriList_out[iDstTriIndex * 3 + 1] = face_vert_to_index(f, 1) as i32; - piTriList_out[iDstTriIndex * 3 + 2] = face_vert_to_index(f, 2) as i32; - iDstTriIndex += 1 - } else { - pTriInfos[iDstTriIndex + 1].iOrgFaceNumber = f as i32; - pTriInfos[iDstTriIndex + 1].iTSpacesOffs = iTSpacesOffs as i32; - let i0 = face_vert_to_index(f, 0); - let i1 = face_vert_to_index(f, 1); - let i2 = face_vert_to_index(f, 2); - let i3 = face_vert_to_index(f, 3); - let T0 = get_tex_coord(geometry, i0); - let T1 = get_tex_coord(geometry, i1); - let T2 = get_tex_coord(geometry, i2); - let T3 = get_tex_coord(geometry, i3); - let distSQ_02: f32 = (T2 - T0).length_squared(); - let distSQ_13: f32 = (T3 - T1).length_squared(); - let mut bQuadDiagIs_02: bool = false; - if distSQ_02 < distSQ_13 { - bQuadDiagIs_02 = true - } else if distSQ_13 < distSQ_02 { - bQuadDiagIs_02 = false - } else { - let P0 = get_position(geometry, i0); - let P1 = get_position(geometry, i1); - let P2 = get_position(geometry, i2); - let P3 = get_position(geometry, i3); - let distSQ_02_0: f32 = (P2 - P0).length_squared(); - let distSQ_13_0: f32 = (P3 - P1).length_squared(); - bQuadDiagIs_02 = if distSQ_13_0 < distSQ_02_0 { - false - } else { - true - } - } - if bQuadDiagIs_02 { - let mut pVerts_A = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts_A[0] = 0; - pVerts_A[1] = 1; - pVerts_A[2] = 2; - piTriList_out[iDstTriIndex * 3 + 0] = i0 as i32; - piTriList_out[iDstTriIndex * 3 + 1] = i1 as i32; - piTriList_out[iDstTriIndex * 3 + 2] = i2 as i32; - iDstTriIndex += 1; - - let mut pVerts_B = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts_B[0] = 0; - pVerts_B[1] = 2; - pVerts_B[2] = 3; - piTriList_out[iDstTriIndex * 3 + 0] = i0 as i32; - piTriList_out[iDstTriIndex * 3 + 1] = i2 as i32; - piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; - iDstTriIndex += 1 - } else { - let mut pVerts_A_0 = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts_A_0[0] = 0; - pVerts_A_0[1] = 1; - pVerts_A_0[2] = 3; - piTriList_out[iDstTriIndex * 3 + 0] = i0 as i32; - piTriList_out[iDstTriIndex * 3 + 1] = i1 as i32; - piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; - iDstTriIndex += 1; - - let mut pVerts_B_0 = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts_B_0[0] = 1; - pVerts_B_0[1] = 2; - pVerts_B_0[2] = 3; - piTriList_out[iDstTriIndex * 3 + 0] = i1 as i32; - piTriList_out[iDstTriIndex * 3 + 1] = i2 as i32; - piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; - iDstTriIndex += 1 - } - } - iTSpacesOffs += verts - } - f += 1 - } - t = 0; - while t < iNrTrianglesIn { - pTriInfos[t].iFlag = 0; - t += 1 - } - return iTSpacesOffs; -} - -fn cos(value: f32) -> f32 { - #[cfg(feature = "std")] - { - value.cos() - } - #[cfg(all(not(feature = "std"), feature = "libm"))] - { - libm::cosf(value) - } - #[cfg(all(not(feature = "std"), not(feature = "libm")))] - { - compile_error!("Require either 'libm' or 'std' for `cos`") - } -} - -fn acos(value: f32) -> f32 { - #[cfg(feature = "std")] - { - value.acos() - } - #[cfg(all(not(feature = "std"), feature = "libm"))] - { - libm::acosf(value) - } - #[cfg(all(not(feature = "std"), not(feature = "libm")))] - { - compile_error!("Require either 'libm' or 'std' for `acos`") - } -} - -fn abs(value: f32) -> f32 { - #[cfg(feature = "std")] - { - value.abs() - } - #[cfg(all(not(feature = "std"), feature = "libm"))] - { - libm::fabsf(value) - } - #[cfg(all(not(feature = "std"), not(feature = "libm")))] - { - compile_error!("Require either 'libm' or 'std' for `abs`") - } -} - -fn acosf64(value: f64) -> f64 { - #[cfg(feature = "std")] - { - value.acos() - } - #[cfg(all(not(feature = "std"), feature = "libm"))] - { - libm::acos(value) - } - #[cfg(all(not(feature = "std"), not(feature = "libm")))] - { - compile_error!("Require either 'libm' or 'std' for `acos`") - } -} diff --git a/crates/bevy_mikktspace/src/lib.rs b/crates/bevy_mikktspace/src/lib.rs deleted file mode 100644 index 12efbf5d62a04..0000000000000 --- a/crates/bevy_mikktspace/src/lib.rs +++ /dev/null @@ -1,111 +0,0 @@ -#![allow( - clippy::allow_attributes, - clippy::allow_attributes_without_reason, - reason = "Much of the code here is still code that's been transpiled from C; we want to save 'fixing' this crate until after it's ported to safe rust." -)] -#![allow( - unsafe_op_in_unsafe_fn, - clippy::all, - clippy::undocumented_unsafe_blocks, - clippy::ptr_cast_constness -)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![doc( - html_logo_url = "https://bevy.org/assets/icon.png", - html_favicon_url = "https://bevy.org/assets/icon.png" -)] -#![no_std] - -//! An implementation of [Mikkelsen's algorithm] for tangent space generation. -//! -//! [Mikkelsen's algorithm]: http://www.mikktspace.com - -#[cfg(feature = "std")] -extern crate std; - -extern crate alloc; - -use glam::{Vec2, Vec3}; - -mod generated; - -/// The interface by which mikktspace interacts with your geometry. -pub trait Geometry { - /// Returns the number of faces. - fn num_faces(&self) -> usize; - - /// Returns the number of vertices of a face. - fn num_vertices_of_face(&self, face: usize) -> usize; - - /// Returns the position of a vertex. - fn position(&self, face: usize, vert: usize) -> [f32; 3]; - - /// Returns the normal of a vertex. - fn normal(&self, face: usize, vert: usize) -> [f32; 3]; - - /// Returns the texture coordinate of a vertex. - fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2]; - - /// Sets the generated tangent for a vertex. - /// Leave this function unimplemented if you are implementing - /// `set_tangent_encoded`. - fn set_tangent( - &mut self, - tangent: [f32; 3], - _bi_tangent: [f32; 3], - _f_mag_s: f32, - _f_mag_t: f32, - bi_tangent_preserves_orientation: bool, - face: usize, - vert: usize, - ) { - let sign = if bi_tangent_preserves_orientation { - 1.0 - } else { - -1.0 - }; - self.set_tangent_encoded([tangent[0], tangent[1], tangent[2], sign], face, vert); - } - - /// Sets the generated tangent for a vertex with its bi-tangent encoded as the 'W' (4th) - /// component in the tangent. The 'W' component marks if the bi-tangent is flipped. This - /// is called by the default implementation of `set_tangent`; therefore, this function will - /// not be called by the crate unless `set_tangent` is unimplemented. - fn set_tangent_encoded(&mut self, _tangent: [f32; 4], _face: usize, _vert: usize) {} -} - -/// Generates tangents for the input geometry. -/// -/// # Errors -/// -/// Returns `false` if the geometry is unsuitable for tangent generation including, -/// but not limited to, lack of vertices. -#[allow(unsafe_code)] -pub fn generate_tangents(geometry: &mut I) -> bool { - unsafe { generated::genTangSpace(geometry, 180.0) } -} - -fn get_position(geometry: &mut I, index: usize) -> Vec3 { - let (face, vert) = index_to_face_vert(index); - geometry.position(face, vert).into() -} - -fn get_tex_coord(geometry: &mut I, index: usize) -> Vec3 { - let (face, vert) = index_to_face_vert(index); - let tex_coord: Vec2 = geometry.tex_coord(face, vert).into(); - let val = tex_coord.extend(1.0); - val -} - -fn get_normal(geometry: &mut I, index: usize) -> Vec3 { - let (face, vert) = index_to_face_vert(index); - geometry.normal(face, vert).into() -} - -fn index_to_face_vert(index: usize) -> (usize, usize) { - (index >> 2, index & 0x3) -} - -fn face_vert_to_index(face: usize, vert: usize) -> usize { - face << 2 | vert & 0x3 -} diff --git a/crates/bevy_mikktspace/tests/regression_test.rs b/crates/bevy_mikktspace/tests/regression_test.rs deleted file mode 100644 index bd6718ad39f20..0000000000000 --- a/crates/bevy_mikktspace/tests/regression_test.rs +++ /dev/null @@ -1,887 +0,0 @@ -#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] -#![expect( - clippy::bool_assert_comparison, - clippy::semicolon_if_nothing_returned, - clippy::useless_conversion, - reason = "Crate auto-generated with many non-idiomatic decisions. See #7372 for details." -)] - -use bevy_mikktspace::{generate_tangents, Geometry}; -use glam::{Vec2, Vec3}; - -pub type Face = [u32; 3]; - -#[derive(Debug)] -struct Vertex { - position: Vec3, - normal: Vec3, - tex_coord: Vec2, -} - -#[derive(Debug, PartialEq)] -struct Result { - tangent: [f32; 3], - bi_tangent: [f32; 3], - mag_s: f32, - mag_t: f32, - bi_tangent_preserves_orientation: bool, - face: usize, - vert: usize, -} - -impl Result { - fn new( - tangent: [f32; 3], - bi_tangent: [f32; 3], - mag_s: f32, - mag_t: f32, - bi_tangent_preserves_orientation: bool, - face: usize, - vert: usize, - ) -> Self { - Self { - tangent, - bi_tangent, - mag_s, - mag_t, - bi_tangent_preserves_orientation, - face, - vert, - } - } -} - -struct Mesh { - faces: Vec, - vertices: Vec, -} - -struct Context { - mesh: Mesh, - results: Vec, -} - -fn vertex(mesh: &Mesh, face: usize, vert: usize) -> &Vertex { - let vs: &[u32; 3] = &mesh.faces[face]; - &mesh.vertices[vs[vert] as usize] -} - -impl Geometry for Context { - fn num_faces(&self) -> usize { - self.mesh.faces.len() - } - - fn num_vertices_of_face(&self, _face: usize) -> usize { - 3 - } - - fn position(&self, face: usize, vert: usize) -> [f32; 3] { - vertex(&self.mesh, face, vert).position.into() - } - - fn normal(&self, face: usize, vert: usize) -> [f32; 3] { - vertex(&self.mesh, face, vert).normal.into() - } - - fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] { - vertex(&self.mesh, face, vert).tex_coord.into() - } - - fn set_tangent( - &mut self, - tangent: [f32; 3], - bi_tangent: [f32; 3], - mag_s: f32, - mag_t: f32, - bi_tangent_preserves_orientation: bool, - face: usize, - vert: usize, - ) { - self.results.push(Result { - tangent, - bi_tangent, - mag_s, - mag_t, - bi_tangent_preserves_orientation, - face, - vert, - }) - } -} - -struct ControlPoint { - uv: [f32; 2], - dir: [f32; 3], -} - -impl ControlPoint { - fn new(uv: [f32; 2], dir: [f32; 3]) -> Self { - Self { uv, dir } - } -} - -fn make_cube() -> Mesh { - let mut faces = Vec::new(); - let mut ctl_pts = Vec::new(); - let mut vertices = Vec::new(); - - // +x plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint::new([0.0, 0.0], [1.0, -1.0, 1.0])); - ctl_pts.push(ControlPoint::new([0.0, 1.0], [1.0, -1.0, -1.0])); - ctl_pts.push(ControlPoint::new([1.0, 1.0], [1.0, 1.0, -1.0])); - ctl_pts.push(ControlPoint::new([1.0, 0.0], [1.0, 1.0, 1.0])); - ctl_pts.push(ControlPoint::new([0.5, 0.5], [1.0, 0.0, 0.0])); - } - - // -x plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint::new([1.0, 0.0], [-1.0, 1.0, 1.0])); - ctl_pts.push(ControlPoint::new([1.0, 1.0], [-1.0, 1.0, -1.0])); - ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, -1.0, -1.0])); - ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, -1.0, 1.0])); - ctl_pts.push(ControlPoint::new([0.5, 0.5], [-1.0, 0.0, 0.0])); - } - - // +y plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint::new([0.0, 0.0], [1.0, 1.0, 1.0])); - ctl_pts.push(ControlPoint::new([0.0, 1.0], [1.0, 1.0, -1.0])); - ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, 1.0, -1.0])); - ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, 1.0, 1.0])); - ctl_pts.push(ControlPoint::new([0.0, 0.5], [0.0, 1.0, 0.0])); - } - - // -y plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, -1.0, 1.0])); - ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, -1.0, -1.0])); - ctl_pts.push(ControlPoint::new([0.0, 1.0], [1.0, -1.0, -1.0])); - ctl_pts.push(ControlPoint::new([0.0, 0.0], [1.0, -1.0, 1.0])); - ctl_pts.push(ControlPoint::new([0.0, 0.5], [0.0, -1.0, 0.0])); - } - - // +z plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, 1.0, 1.0])); - ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, -1.0, 1.0])); - ctl_pts.push(ControlPoint::new([1.0, 1.0], [1.0, -1.0, 1.0])); - ctl_pts.push(ControlPoint::new([1.0, 0.0], [1.0, 1.0, 1.0])); - ctl_pts.push(ControlPoint::new([0.5, 0.5], [0.0, 0.0, 1.0])); - } - - // -z plane - { - let base = ctl_pts.len() as u32; - faces.push([base, base + 1, base + 4]); - faces.push([base + 1, base + 2, base + 4]); - faces.push([base + 2, base + 3, base + 4]); - faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint::new([1.0, 0.0], [1.0, 1.0, -1.0])); - ctl_pts.push(ControlPoint::new([1.0, 1.0], [1.0, -1.0, -1.0])); - ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, -1.0, -1.0])); - ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, 1.0, -1.0])); - ctl_pts.push(ControlPoint::new([0.5, 0.5], [0.0, 0.0, -1.0])); - } - - for pt in ctl_pts { - let p: Vec3 = pt.dir.into(); - let n: Vec3 = p.normalize(); - let t: Vec2 = pt.uv.into(); - vertices.push(Vertex { - position: (p / 2.0).into(), - normal: n.into(), - tex_coord: t.into(), - }); - } - - Mesh { faces, vertices } -} - -#[test] -fn cube_tangents_should_equal_reference_values() { - let mut context = Context { - mesh: make_cube(), - results: Vec::new(), - }; - let ret = generate_tangents(&mut context); - assert_eq!(true, ret); - - let expected_results: Vec = vec![ - Result::new( - [0.40824825, 0.81649655, 0.40824825], - [0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 0, - 0, - ), - Result::new( - [0.40824825, 0.81649655, -0.40824825], - [-0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 0, - 1, - ), - Result::new( - [0.00000000, 1.00000000, 0.00000000], - [0.00000000, 0.00000000, -1.00000000], - 1.00000000, - 1.00000000, - false, - 0, - 2, - ), - Result::new( - [0.40824825, 0.81649655, -0.40824825], - [-0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 1, - 0, - ), - Result::new( - [-0.40824825, 0.81649655, 0.40824825], - [-0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 1, - 1, - ), - Result::new( - [0.00000000, 1.00000000, 0.00000000], - [0.00000000, 0.00000000, -1.00000000], - 1.00000000, - 1.00000000, - false, - 1, - 2, - ), - Result::new( - [-0.40824825, 0.81649655, 0.40824825], - [-0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 2, - 0, - ), - Result::new( - [-0.40824825, 0.81649655, -0.40824825], - [0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 2, - 1, - ), - Result::new( - [0.00000000, 1.00000000, 0.00000000], - [0.00000000, 0.00000000, -1.00000000], - 1.00000000, - 1.00000000, - false, - 2, - 2, - ), - Result::new( - [-0.40824825, 0.81649655, -0.40824825], - [0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 3, - 0, - ), - Result::new( - [0.40824825, 0.81649655, 0.40824825], - [0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 3, - 1, - ), - Result::new( - [0.00000000, 1.00000000, 0.00000000], - [0.00000000, 0.00000000, -1.00000000], - 1.00000000, - 1.00000000, - false, - 3, - 2, - ), - Result::new( - [0.40824825, 0.81649655, -0.40824825], - [-0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 4, - 0, - ), - Result::new( - [0.40824825, 0.81649655, 0.40824825], - [0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 4, - 1, - ), - Result::new( - [0.00000000, 1.00000000, 0.00000000], - [0.00000000, 0.00000000, -1.00000000], - 1.00000000, - 1.00000000, - true, - 4, - 2, - ), - Result::new( - [0.40824825, 0.81649655, 0.40824825], - [0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 5, - 0, - ), - Result::new( - [-0.40824825, 0.81649655, -0.40824825], - [0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 5, - 1, - ), - Result::new( - [0.00000000, 1.00000000, 0.00000000], - [0.00000000, 0.00000000, -1.00000000], - 1.00000000, - 1.00000000, - true, - 5, - 2, - ), - Result::new( - [-0.40824825, 0.81649655, -0.40824825], - [0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 6, - 0, - ), - Result::new( - [-0.40824825, 0.81649655, 0.40824825], - [-0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 6, - 1, - ), - Result::new( - [0.00000000, 1.00000000, 0.00000000], - [0.00000000, 0.00000000, -1.00000000], - 1.00000000, - 1.00000000, - true, - 6, - 2, - ), - Result::new( - [-0.40824825, 0.81649655, 0.40824825], - [-0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 7, - 0, - ), - Result::new( - [0.40824825, 0.81649655, -0.40824825], - [-0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 7, - 1, - ), - Result::new( - [0.00000000, 1.00000000, 0.00000000], - [0.00000000, 0.00000000, -1.00000000], - 1.00000000, - 1.00000000, - true, - 7, - 2, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 8, - 0, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 8, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 8, - 2, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 9, - 0, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 9, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 9, - 2, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 10, - 0, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 10, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 10, - 2, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 11, - 0, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 11, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 11, - 2, - ), - Result::new( - [-0.40824825, 0.81649655, 0.40824825], - [-0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 12, - 0, - ), - Result::new( - [-0.40824825, 0.81649655, -0.40824825], - [0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - true, - 12, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 12, - 2, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 13, - 0, - ), - Result::new( - [0.40824825, 0.81649655, -0.40824825], - [-0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 13, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 13, - 2, - ), - Result::new( - [0.40824825, 0.81649655, -0.40824825], - [-0.40824825, 0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 14, - 0, - ), - Result::new( - [0.40824825, 0.81649655, 0.40824825], - [0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 14, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 14, - 2, - ), - Result::new( - [0.40824825, 0.81649655, 0.40824825], - [0.40824825, -0.40824825, -0.81649655], - 1.00000000, - 1.00000000, - false, - 15, - 0, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 15, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, 1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 15, - 2, - ), - Result::new( - [0.81649655, 0.40824825, 0.40824825], - [-0.40824825, -0.81649655, 0.40824825], - 1.00000000, - 1.00000000, - false, - 16, - 0, - ), - Result::new( - [0.81649655, -0.40824825, 0.40824825], - [0.40824825, -0.81649655, -0.40824825], - 1.00000000, - 1.00000000, - false, - 16, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, -1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 16, - 2, - ), - Result::new( - [0.81649655, -0.40824825, 0.40824825], - [0.40824825, -0.81649655, -0.40824825], - 1.00000000, - 1.00000000, - false, - 17, - 0, - ), - Result::new( - [0.81649655, 0.40824825, -0.40824825], - [-0.40824825, -0.81649655, -0.40824825], - 1.00000000, - 1.00000000, - false, - 17, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, -1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 17, - 2, - ), - Result::new( - [0.81649655, 0.40824825, -0.40824825], - [-0.40824825, -0.81649655, -0.40824825], - 1.00000000, - 1.00000000, - false, - 18, - 0, - ), - Result::new( - [0.81649655, -0.40824825, -0.40824825], - [0.40824825, -0.81649655, 0.40824825], - 1.00000000, - 1.00000000, - false, - 18, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, -1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 18, - 2, - ), - Result::new( - [0.81649655, -0.40824825, -0.40824825], - [0.40824825, -0.81649655, 0.40824825], - 1.00000000, - 1.00000000, - false, - 19, - 0, - ), - Result::new( - [0.81649655, 0.40824825, 0.40824825], - [-0.40824825, -0.81649655, 0.40824825], - 1.00000000, - 1.00000000, - false, - 19, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, -1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - false, - 19, - 2, - ), - Result::new( - [0.81649655, -0.40824825, 0.40824825], - [0.40824825, -0.81649655, -0.40824825], - 1.00000000, - 1.00000000, - true, - 20, - 0, - ), - Result::new( - [0.81649655, 0.40824825, 0.40824825], - [-0.40824825, -0.81649655, 0.40824825], - 1.00000000, - 1.00000000, - true, - 20, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, -1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - true, - 20, - 2, - ), - Result::new( - [0.81649655, 0.40824825, 0.40824825], - [-0.40824825, -0.81649655, 0.40824825], - 1.00000000, - 1.00000000, - true, - 21, - 0, - ), - Result::new( - [0.81649655, -0.40824825, -0.40824825], - [0.40824825, -0.81649655, 0.40824825], - 1.00000000, - 1.00000000, - true, - 21, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, -1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - true, - 21, - 2, - ), - Result::new( - [0.81649655, -0.40824825, -0.40824825], - [0.40824825, -0.81649655, 0.40824825], - 1.00000000, - 1.00000000, - true, - 22, - 0, - ), - Result::new( - [0.81649655, 0.40824825, -0.40824825], - [-0.40824825, -0.81649655, -0.40824825], - 1.00000000, - 1.00000000, - true, - 22, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, -1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - true, - 22, - 2, - ), - Result::new( - [0.81649655, 0.40824825, -0.40824825], - [-0.40824825, -0.81649655, -0.40824825], - 1.00000000, - 1.00000000, - true, - 23, - 0, - ), - Result::new( - [0.81649655, -0.40824825, 0.40824825], - [0.40824825, -0.81649655, -0.40824825], - 1.00000000, - 1.00000000, - true, - 23, - 1, - ), - Result::new( - [1.00000000, 0.00000000, 0.00000000], - [0.00000000, -1.00000000, 0.00000000], - 1.00000000, - 1.00000000, - true, - 23, - 2, - ), - ]; - - assert_eq!(expected_results, context.results); -} diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index ed4dabdf9621e..4b6065c76dc7b 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -92,14 +92,12 @@ impl Plugin for AtmospherePlugin { embedded_asset!(app, "aerial_view_lut.wgsl"); embedded_asset!(app, "render_sky.wgsl"); - app.register_type::() - .register_type::() - .add_plugins(( - ExtractComponentPlugin::::default(), - ExtractComponentPlugin::::default(), - UniformComponentPlugin::::default(), - UniformComponentPlugin::::default(), - )); + app.add_plugins(( + ExtractComponentPlugin::::default(), + ExtractComponentPlugin::::default(), + UniformComponentPlugin::::default(), + UniformComponentPlugin::::default(), + )); } fn finish(&self, app: &mut App) { diff --git a/crates/bevy_pbr/src/atmosphere/node.rs b/crates/bevy_pbr/src/atmosphere/node.rs index e09b27c590a04..93c1a33ae9db2 100644 --- a/crates/bevy_pbr/src/atmosphere/node.rs +++ b/crates/bevy_pbr/src/atmosphere/node.rs @@ -1,6 +1,7 @@ use bevy_ecs::{query::QueryItem, system::lifetimeless::Read, world::World}; use bevy_math::{UVec2, Vec3Swizzles}; use bevy_render::{ + diagnostic::RecordDiagnostics, extract_component::DynamicUniformIndex, render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode}, render_resource::{ComputePass, ComputePassDescriptor, PipelineCache, RenderPassDescriptor}, @@ -70,12 +71,15 @@ impl ViewNode for AtmosphereLutsNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let command_encoder = render_context.command_encoder(); let mut luts_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { - label: Some("atmosphere_luts_pass"), + label: Some("atmosphere_luts"), timestamp_writes: None, }); + let pass_span = diagnostics.time_span(&mut luts_pass, "atmosphere_luts"); fn dispatch_2d(compute_pass: &mut ComputePass, size: UVec2) { const WORKGROUP_SIZE: u32 = 16; @@ -149,6 +153,8 @@ impl ViewNode for AtmosphereLutsNode { dispatch_2d(&mut luts_pass, settings.aerial_view_lut_size.xy()); + pass_span.end(&mut luts_pass); + Ok(()) } } @@ -191,16 +197,19 @@ impl ViewNode for RenderSkyNode { return Ok(()); }; //TODO: warning + let diagnostics = render_context.diagnostic_recorder(); + let mut render_sky_pass = render_context .command_encoder() .begin_render_pass(&RenderPassDescriptor { - label: Some("render_sky_pass"), + label: Some("render_sky"), color_attachments: &[Some(view_target.get_color_attachment())], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, }); + let pass_span = diagnostics.pass_span(&mut render_sky_pass, "render_sky"); render_sky_pass.set_pipeline(render_sky_pipeline); render_sky_pass.set_bind_group( @@ -216,6 +225,8 @@ impl ViewNode for RenderSkyNode { ); render_sky_pass.draw(0..3, 0..1); + pass_span.end(&mut render_sky_pass); + Ok(()) } } diff --git a/crates/bevy_pbr/src/cluster.rs b/crates/bevy_pbr/src/cluster.rs index f3d242a512fcc..bc473a509f666 100644 --- a/crates/bevy_pbr/src/cluster.rs +++ b/crates/bevy_pbr/src/cluster.rs @@ -216,7 +216,7 @@ impl GpuClusterableObjects { } } - pub fn binding(&self) -> Option { + pub fn binding(&self) -> Option> { match self { GpuClusterableObjects::Uniform(buffer) => buffer.binding(), GpuClusterableObjects::Storage(buffer) => buffer.binding(), @@ -456,7 +456,7 @@ impl ViewClusterBindings { } } - pub fn clusterable_object_index_lists_binding(&self) -> Option { + pub fn clusterable_object_index_lists_binding(&self) -> Option> { match &self.buffers { ViewClusterBuffers::Uniform { clusterable_object_index_lists, @@ -469,7 +469,7 @@ impl ViewClusterBindings { } } - pub fn offsets_and_counts_binding(&self) -> Option { + pub fn offsets_and_counts_binding(&self) -> Option> { match &self.buffers { ViewClusterBuffers::Uniform { cluster_offsets_and_counts, diff --git a/crates/bevy_pbr/src/decal/clustered.rs b/crates/bevy_pbr/src/decal/clustered.rs index 7580f1475e92a..2eed8f741e86d 100644 --- a/crates/bevy_pbr/src/decal/clustered.rs +++ b/crates/bevy_pbr/src/decal/clustered.rs @@ -144,8 +144,7 @@ impl Plugin for ClusteredDecalPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "clustered.wgsl"); - app.add_plugins(ExtractComponentPlugin::::default()) - .register_type::(); + app.add_plugins(ExtractComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; diff --git a/crates/bevy_pbr/src/decal/forward.rs b/crates/bevy_pbr/src/decal/forward.rs index d862331be510e..f5b24169d069e 100644 --- a/crates/bevy_pbr/src/decal/forward.rs +++ b/crates/bevy_pbr/src/decal/forward.rs @@ -30,8 +30,6 @@ impl Plugin for ForwardDecalPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "forward_decal.wgsl"); - app.register_type::(); - let mesh = app.world_mut().resource_mut::>().add( Rectangle::from_size(Vec2::ONE) .mesh() diff --git a/crates/bevy_pbr/src/decal/forward_decal.wgsl b/crates/bevy_pbr/src/decal/forward_decal.wgsl index f0414bc807625..ffbb5f958b189 100644 --- a/crates/bevy_pbr/src/decal/forward_decal.wgsl +++ b/crates/bevy_pbr/src/decal/forward_decal.wgsl @@ -10,7 +10,7 @@ } #import bevy_render::maths::project_onto -@group(3) @binding(200) +@group(#{MATERIAL_BIND_GROUP}) @binding(200) var inv_depth_fade_factor: f32; struct ForwardDecalInformation { diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 3c303331ffc11..d1ca01fd1c7c5 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -21,6 +21,7 @@ use bevy_image::BevyDefault as _; use bevy_light::{EnvironmentMapLight, ShadowFilteringMethod}; use bevy_render::RenderStartup; use bevy_render::{ + diagnostic::RecordDiagnostics, extract_component::{ ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, }, @@ -177,6 +178,8 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let bind_group_2 = render_context.render_device().create_bind_group( "deferred_lighting_layout_group_2", &deferred_lighting_layout.bind_group_layout_2, @@ -184,7 +187,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { ); let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("deferred_lighting_pass"), + label: Some("deferred_lighting"), color_attachments: &[Some(target.get_color_attachment())], depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &deferred_lighting_id_depth_texture.texture.default_view, @@ -197,6 +200,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { timestamp_writes: None, occlusion_query_set: None, }); + let pass_span = diagnostics.pass_span(&mut render_pass, "deferred_lighting"); render_pass.set_render_pipeline(pipeline); render_pass.set_bind_group( @@ -215,6 +219,8 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { render_pass.set_bind_group(2, &bind_group_2, &[]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 2947927b0cec5..6af1d6dad1b03 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -223,7 +223,6 @@ impl Plugin for PbrPlugin { load_shader_library!(app, "meshlet/dummy_visibility_buffer_resolve.wgsl"); app.register_asset_reflect::() - .register_type::() .init_resource::() .add_plugins(( MeshRenderPlugin { diff --git a/crates/bevy_pbr/src/light_probe/downsample.wgsl b/crates/bevy_pbr/src/light_probe/downsample.wgsl index 57a0615c87c1d..e28c9b2f1ab72 100644 --- a/crates/bevy_pbr/src/light_probe/downsample.wgsl +++ b/crates/bevy_pbr/src/light_probe/downsample.wgsl @@ -50,22 +50,16 @@ var spd_intermediate_a: array, 16>; @workgroup_size(256, 1, 1) fn downsample_first( @builtin(workgroup_id) workgroup_id: vec3u, - @builtin(local_invocation_index) local_invocation_index: u32, -#ifdef SUBGROUP_SUPPORT - @builtin(subgroup_invocation_id) subgroup_invocation_id: u32, -#endif + @builtin(local_invocation_index) local_invocation_index: u32 ) { -#ifndef SUBGROUP_SUPPORT - let subgroup_invocation_id = 0u; -#endif let sub_xy = remap_for_wave_reduction(local_invocation_index % 64u); let x = sub_xy.x + 8u * ((local_invocation_index >> 6u) % 2u); let y = sub_xy.y + 8u * (local_invocation_index >> 7u); - spd_downsample_mips_0_1(x, y, workgroup_id.xy, local_invocation_index, constants.mips, workgroup_id.z, subgroup_invocation_id); + spd_downsample_mips_0_1(x, y, workgroup_id.xy, local_invocation_index, constants.mips, workgroup_id.z); - spd_downsample_next_four(x, y, workgroup_id.xy, local_invocation_index, 2u, constants.mips, workgroup_id.z, subgroup_invocation_id); + spd_downsample_next_four(x, y, workgroup_id.xy, local_invocation_index, 2u, constants.mips, workgroup_id.z); } // TODO: Once wgpu supports globallycoherent buffers, make it actually a single pass @@ -74,24 +68,17 @@ fn downsample_first( fn downsample_second( @builtin(workgroup_id) workgroup_id: vec3u, @builtin(local_invocation_index) local_invocation_index: u32, -#ifdef SUBGROUP_SUPPORT - @builtin(subgroup_invocation_id) subgroup_invocation_id: u32, -#endif ) { -#ifndef SUBGROUP_SUPPORT - let subgroup_invocation_id = 0u; -#endif - let sub_xy = remap_for_wave_reduction(local_invocation_index % 64u); let x = sub_xy.x + 8u * ((local_invocation_index >> 6u) % 2u); let y = sub_xy.y + 8u * (local_invocation_index >> 7u); spd_downsample_mips_6_7(x, y, constants.mips, workgroup_id.z); - spd_downsample_next_four(x, y, vec2(0u), local_invocation_index, 8u, constants.mips, workgroup_id.z, subgroup_invocation_id); + spd_downsample_next_four(x, y, vec2(0u), local_invocation_index, 8u, constants.mips, workgroup_id.z); } -fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, mips: u32, slice: u32, subgroup_invocation_id: u32) { +fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, mips: u32, slice: u32) { var v: array; var tex = (workgroup_id * 64u) + vec2(x * 2u, y * 2u); @@ -117,10 +104,10 @@ fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2u, local_invocation if mips <= 1u { return; } #ifdef SUBGROUP_SUPPORT - v[0] = spd_reduce_quad(v[0], subgroup_invocation_id); - v[1] = spd_reduce_quad(v[1], subgroup_invocation_id); - v[2] = spd_reduce_quad(v[2], subgroup_invocation_id); - v[3] = spd_reduce_quad(v[3], subgroup_invocation_id); + v[0] = spd_reduce_quad(v[0]); + v[1] = spd_reduce_quad(v[1]); + v[2] = spd_reduce_quad(v[2]); + v[3] = spd_reduce_quad(v[3]); if local_invocation_index % 4u == 0u { spd_store((workgroup_id * 16u) + vec2(x / 2u, y / 2u), v[0], 1u, slice); @@ -160,28 +147,28 @@ fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2u, local_invocation #endif } -fn spd_downsample_next_four(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32, subgroup_invocation_id: u32) { +fn spd_downsample_next_four(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32) { if mips <= base_mip { return; } workgroupBarrier(); - spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice, subgroup_invocation_id); + spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice); if mips <= base_mip + 1u { return; } workgroupBarrier(); - spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1u, slice, subgroup_invocation_id); + spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1u, slice); if mips <= base_mip + 2u { return; } workgroupBarrier(); - spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2u, slice, subgroup_invocation_id); + spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2u, slice); if mips <= base_mip + 3u { return; } workgroupBarrier(); - spd_downsample_mip_5(x, y, workgroup_id, local_invocation_index, base_mip + 3u, slice, subgroup_invocation_id); + spd_downsample_mip_5(x, y, workgroup_id, local_invocation_index, base_mip + 3u, slice); } -fn spd_downsample_mip_2(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32, subgroup_invocation_id: u32) { +fn spd_downsample_mip_2(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32) { #ifdef SUBGROUP_SUPPORT var v = spd_load_intermediate(x, y); - v = spd_reduce_quad(v, subgroup_invocation_id); + v = spd_reduce_quad(v); if local_invocation_index % 4u == 0u { spd_store((workgroup_id * 8u) + vec2(x / 2u, y / 2u), v, base_mip, slice); spd_store_intermediate(x + (y / 2u) % 2u, y, v); @@ -200,11 +187,11 @@ fn spd_downsample_mip_2(x: u32, y: u32, workgroup_id: vec2u, local_invocation_in #endif } -fn spd_downsample_mip_3(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32, subgroup_invocation_id: u32) { +fn spd_downsample_mip_3(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32) { #ifdef SUBGROUP_SUPPORT if local_invocation_index < 64u { var v = spd_load_intermediate(x * 2u + y % 2u, y * 2u); - v = spd_reduce_quad(v, subgroup_invocation_id); + v = spd_reduce_quad(v); if local_invocation_index % 4u == 0u { spd_store((workgroup_id * 4u) + vec2(x / 2u, y / 2u), v, base_mip, slice); spd_store_intermediate(x * 2u + y / 2u, y * 2u, v); @@ -224,11 +211,11 @@ fn spd_downsample_mip_3(x: u32, y: u32, workgroup_id: vec2u, local_invocation_in #endif } -fn spd_downsample_mip_4(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32, subgroup_invocation_id: u32) { +fn spd_downsample_mip_4(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32) { #ifdef SUBGROUP_SUPPORT if local_invocation_index < 16u { var v = spd_load_intermediate(x * 4u + y, y * 4u); - v = spd_reduce_quad(v, subgroup_invocation_id); + v = spd_reduce_quad(v); if local_invocation_index % 4u == 0u { spd_store((workgroup_id * 2u) + vec2(x / 2u, y / 2u), v, base_mip, slice); spd_store_intermediate(x / 2u + y, 0u, v); @@ -248,11 +235,11 @@ fn spd_downsample_mip_4(x: u32, y: u32, workgroup_id: vec2u, local_invocation_in #endif } -fn spd_downsample_mip_5(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32, subgroup_invocation_id: u32) { +fn spd_downsample_mip_5(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32) { #ifdef SUBGROUP_SUPPORT if local_invocation_index < 4u { var v = spd_load_intermediate(local_invocation_index, 0u); - v = spd_reduce_quad(v, subgroup_invocation_id); + v = spd_reduce_quad(v); if local_invocation_index % 4u == 0u { spd_store(workgroup_id, v, base_mip, slice); } @@ -436,20 +423,12 @@ fn spd_reduce_4(v0: vec4f, v1: vec4f, v2: vec4f, v3: vec4f) -> vec4f { } #ifdef SUBGROUP_SUPPORT -fn spd_reduce_quad(v: vec4f, subgroup_invocation_id: u32) -> vec4f { - let quad = subgroup_invocation_id & (~0x3u); +fn spd_reduce_quad(v: vec4f) -> vec4f { let v0 = v; - let v1 = subgroupBroadcast(v, quad | 1u); - let v2 = subgroupBroadcast(v, quad | 2u); - let v3 = subgroupBroadcast(v, quad | 3u); + let v1 = quadSwapX(v); + let v2 = quadSwapY(v); + let v3 = quadSwapDiagonal(v); return spd_reduce_4(v0, v1, v2, v3); - - // TODO: Use subgroup quad operations once wgpu supports them - // let v0 = v; - // let v1 = quadSwapX(v); - // let v2 = quadSwapY(v); - // let v3 = quadSwapDiagonal(v); - // return spd_reduce_4(v0, v1, v2, v3); } #endif diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index e6dfebd903ad1..b7b5a104da460 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -215,18 +215,16 @@ impl<'a> RenderViewEnvironmentMapBindGroupEntries<'a> { }; } - if let Some(environment_maps) = render_view_environment_maps { - if let Some(cubemap) = environment_maps.binding_index_to_textures.first() { - if let (Some(diffuse_image), Some(specular_image)) = - (images.get(cubemap.diffuse), images.get(cubemap.specular)) - { - return RenderViewEnvironmentMapBindGroupEntries::Single { - diffuse_texture_view: &diffuse_image.texture_view, - specular_texture_view: &specular_image.texture_view, - sampler: &diffuse_image.sampler, - }; - } - } + if let Some(environment_maps) = render_view_environment_maps + && let Some(cubemap) = environment_maps.binding_index_to_textures.first() + && let (Some(diffuse_image), Some(specular_image)) = + (images.get(cubemap.diffuse), images.get(cubemap.specular)) + { + return RenderViewEnvironmentMapBindGroupEntries::Single { + diffuse_texture_view: &diffuse_image.texture_view, + specular_texture_view: &specular_image.texture_view, + sampler: &diffuse_image.sampler, + }; } RenderViewEnvironmentMapBindGroupEntries::Single { @@ -280,23 +278,20 @@ impl LightProbeComponent for EnvironmentMapLight { affects_lightmapped_mesh_diffuse, .. }) = view_component - { - if let (Some(_), Some(specular_map)) = ( + && let (Some(_), Some(specular_map)) = ( image_assets.get(diffuse_map_handle), image_assets.get(specular_map_handle), - ) { - render_view_light_probes.view_light_probe_info = EnvironmentMapViewLightProbeInfo { - cubemap_index: render_view_light_probes.get_or_insert_cubemap( - &EnvironmentMapIds { - diffuse: diffuse_map_handle.id(), - specular: specular_map_handle.id(), - }, - ) as i32, - smallest_specular_mip_level: specular_map.mip_level_count - 1, - intensity: *intensity, - affects_lightmapped_mesh_diffuse: *affects_lightmapped_mesh_diffuse, - }; - } + ) + { + render_view_light_probes.view_light_probe_info = EnvironmentMapViewLightProbeInfo { + cubemap_index: render_view_light_probes.get_or_insert_cubemap(&EnvironmentMapIds { + diffuse: diffuse_map_handle.id(), + specular: specular_map_handle.id(), + }) as i32, + smallest_specular_mip_level: specular_map.mip_level_count - 1, + intensity: *intensity, + affects_lightmapped_mesh_diffuse: *affects_lightmapped_mesh_diffuse, + }; }; render_view_light_probes diff --git a/crates/bevy_pbr/src/light_probe/generate.rs b/crates/bevy_pbr/src/light_probe/generate.rs index 85213119dc625..1cb0f91a09f77 100644 --- a/crates/bevy_pbr/src/light_probe/generate.rs +++ b/crates/bevy_pbr/src/light_probe/generate.rs @@ -921,11 +921,11 @@ impl Node for DownsamplingNode { render_context .command_encoder() .begin_compute_pass(&ComputePassDescriptor { - label: Some("lightprobe_copy_pass"), + label: Some("lightprobe_copy"), timestamp_writes: None, }); - let pass_span = diagnostics.pass_span(&mut compute_pass, "lightprobe_copy_pass"); + let pass_span = diagnostics.pass_span(&mut compute_pass, "lightprobe_copy"); compute_pass.set_pipeline(copy_pipeline); compute_pass.set_bind_group(0, &bind_groups.copy, &[]); @@ -1038,12 +1038,11 @@ impl Node for FilteringNode { render_context .command_encoder() .begin_compute_pass(&ComputePassDescriptor { - label: Some("lightprobe_radiance_map_pass"), + label: Some("lightprobe_radiance_map"), timestamp_writes: None, }); - let pass_span = - diagnostics.pass_span(&mut compute_pass, "lightprobe_radiance_map_pass"); + let pass_span = diagnostics.pass_span(&mut compute_pass, "lightprobe_radiance_map"); compute_pass.set_pipeline(radiance_pipeline); @@ -1072,12 +1071,12 @@ impl Node for FilteringNode { render_context .command_encoder() .begin_compute_pass(&ComputePassDescriptor { - label: Some("lightprobe_irradiance_map_pass"), + label: Some("lightprobe_irradiance_map"), timestamp_writes: None, }); let irr_span = - diagnostics.pass_span(&mut compute_pass, "lightprobe_irradiance_map_pass"); + diagnostics.pass_span(&mut compute_pass, "lightprobe_irradiance_map"); compute_pass.set_pipeline(irradiance_pipeline); compute_pass.set_bind_group(0, &bind_groups.irradiance, &[]); diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index e1931d30b82aa..a7320792b2ef4 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -253,22 +253,18 @@ impl<'a> RenderViewIrradianceVolumeBindGroupEntries<'a> { images: &'a RenderAssets, fallback_image: &'a FallbackImage, ) -> RenderViewIrradianceVolumeBindGroupEntries<'a> { - if let Some(irradiance_volumes) = render_view_irradiance_volumes { - if let Some(irradiance_volume) = irradiance_volumes.render_light_probes.first() { - if irradiance_volume.texture_index >= 0 { - if let Some(image_id) = irradiance_volumes - .binding_index_to_textures - .get(irradiance_volume.texture_index as usize) - { - if let Some(image) = images.get(*image_id) { - return RenderViewIrradianceVolumeBindGroupEntries::Single { - texture_view: &image.texture_view, - sampler: &image.sampler, - }; - } - } - } - } + if let Some(irradiance_volumes) = render_view_irradiance_volumes + && let Some(irradiance_volume) = irradiance_volumes.render_light_probes.first() + && irradiance_volume.texture_index >= 0 + && let Some(image_id) = irradiance_volumes + .binding_index_to_textures + .get(irradiance_volume.texture_index as usize) + && let Some(image) = images.get(*image_id) + { + return RenderViewIrradianceVolumeBindGroupEntries::Single { + texture_view: &image.texture_view, + sampler: &image.sampler, + }; } RenderViewIrradianceVolumeBindGroupEntries::Single { diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index c69a6ea8f77ff..906d657d948b3 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -58,6 +58,8 @@ use core::{hash::Hash, marker::PhantomData}; use smallvec::SmallVec; use tracing::error; +pub const MATERIAL_BIND_GROUP_INDEX: usize = 3; + /// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`] /// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level /// way to render [`Mesh3d`] entities with custom shader logic. @@ -122,9 +124,9 @@ use tracing::error; /// In WGSL shaders, the material's binding would look like this: /// /// ```wgsl -/// @group(3) @binding(0) var color: vec4; -/// @group(3) @binding(1) var color_texture: texture_2d; -/// @group(3) @binding(2) var color_sampler: sampler; +/// @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; /// ``` pub trait Material: Asset + AsBindGroup + Clone + Sized { /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader @@ -451,6 +453,16 @@ impl SpecializedMeshPipeline for MaterialPipelineSpecializer { .pipeline .mesh_pipeline .specialize(key.mesh_key, layout)?; + descriptor.vertex.shader_defs.push(ShaderDefVal::UInt( + "MATERIAL_BIND_GROUP".into(), + MATERIAL_BIND_GROUP_INDEX as u32, + )); + if let Some(ref mut fragment) = descriptor.fragment { + fragment.shader_defs.push(ShaderDefVal::UInt( + "MATERIAL_BIND_GROUP".into(), + MATERIAL_BIND_GROUP_INDEX as u32, + )); + }; if let Some(vertex_shader) = self.properties.get_shader(MaterialVertexShader) { descriptor.vertex.shader = vertex_shader.clone(); } @@ -490,7 +502,7 @@ pub type DrawMaterial = ( SetMeshViewBindGroup<0>, SetMeshViewBindingArrayBindGroup<1>, SetMeshBindGroup<2>, - SetMaterialBindGroup<3>, + SetMaterialBindGroup, DrawMesh, ); diff --git a/crates/bevy_pbr/src/material_bind_groups.rs b/crates/bevy_pbr/src/material_bind_groups.rs index c984cfb85db7d..0851760bbfd03 100644 --- a/crates/bevy_pbr/src/material_bind_groups.rs +++ b/crates/bevy_pbr/src/material_bind_groups.rs @@ -478,7 +478,7 @@ impl MaterialBindGroupAllocator { } /// Returns the slab with the given index, if one exists. - pub fn get(&self, group: MaterialBindGroupIndex) -> Option { + pub fn get(&self, group: MaterialBindGroupIndex) -> Option> { match *self { MaterialBindGroupAllocator::Bindless(ref bindless_allocator) => bindless_allocator .get(group) @@ -673,7 +673,7 @@ impl MaterialBindlessIndexTable { } /// Returns the [`BindGroupEntry`] for the index table itself. - fn bind_group_entry(&self) -> BindGroupEntry { + fn bind_group_entry(&self) -> BindGroupEntry<'_> { BindGroupEntry { binding: *self.binding_number, resource: self @@ -1837,7 +1837,7 @@ impl MaterialBindGroupNonBindlessAllocator { } /// Returns a wrapper around the bind group with the given index. - fn get(&self, group: MaterialBindGroupIndex) -> Option { + fn get(&self, group: MaterialBindGroupIndex) -> Option> { self.bind_groups[group.0 as usize] .as_ref() .map(|bind_group| match bind_group { diff --git a/crates/bevy_pbr/src/meshlet/instance_manager.rs b/crates/bevy_pbr/src/meshlet/instance_manager.rs index 94d03a925a490..a210accea7ff9 100644 --- a/crates/bevy_pbr/src/meshlet/instance_manager.rs +++ b/crates/bevy_pbr/src/meshlet/instance_manager.rs @@ -282,16 +282,15 @@ pub fn queue_material_meshlet_meshes( let instance_manager = instance_manager.deref_mut(); for (i, (instance, _, _)) in instance_manager.instances.iter().enumerate() { - if let Some(material_instance) = render_material_instances.instances.get(instance) { - if let Some(material_id) = instance_manager + if let Some(material_instance) = render_material_instances.instances.get(instance) + && let Some(material_id) = instance_manager .material_id_lookup .get(&material_instance.asset_id) - { - instance_manager - .material_ids_present_in_scene - .insert(*material_id); - instance_manager.instance_material_ids.get_mut()[i] = *material_id; - } + { + instance_manager + .material_ids_present_in_scene + .insert(*material_id); + instance_manager.instance_material_ids.get_mut()[i] = *material_id; } } } diff --git a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs index 39dcb0c1690fa..60be5117e8886 100644 --- a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs +++ b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs @@ -10,6 +10,7 @@ use crate::{ MeshViewBindGroup, PrepassViewBindGroup, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, }; +use bevy_camera::Viewport; use bevy_core_pipeline::prepass::{ MotionVectorPrepass, PreviousViewUniformOffset, ViewPrepassTextures, }; @@ -19,6 +20,7 @@ use bevy_ecs::{ }; use bevy_render::{ camera::{ExtractedCamera, MainPassResolutionOverride}, + diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ LoadOp, Operations, PipelineCache, RenderPassDepthStencilAttachment, RenderPassDescriptor, @@ -88,8 +90,10 @@ impl ViewNode for MeshletMainOpaquePass3dNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("meshlet_main_opaque_pass_3d"), + label: Some("meshlet_material_opaque_3d_pass"), color_attachments: &[Some(target.get_color_attachment())], depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &meshlet_material_depth.default_view, @@ -102,8 +106,11 @@ impl ViewNode for MeshletMainOpaquePass3dNode { timestamp_writes: None, occlusion_query_set: None, }); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + let pass_span = diagnostics.pass_span(&mut render_pass, "meshlet_material_opaque_3d_pass"); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } render_pass.set_bind_group( @@ -125,18 +132,19 @@ impl ViewNode for MeshletMainOpaquePass3dNode { for (material_id, material_pipeline_id, material_bind_group) in meshlet_view_materials.iter() { - if instance_manager.material_present_in_scene(material_id) { - if let Some(material_pipeline) = + if instance_manager.material_present_in_scene(material_id) + && let Some(material_pipeline) = pipeline_cache.get_render_pipeline(*material_pipeline_id) - { - let x = *material_id * 3; - render_pass.set_render_pipeline(material_pipeline); - render_pass.set_bind_group(3, material_bind_group, &[]); - render_pass.draw(x..(x + 3), 0..1); - } + { + let x = *material_id * 3; + render_pass.set_render_pipeline(material_pipeline); + render_pass.set_bind_group(3, material_bind_group, &[]); + render_pass.draw(x..(x + 3), 0..1); } } + pass_span.end(&mut render_pass); + Ok(()) } } @@ -195,6 +203,8 @@ impl ViewNode for MeshletPrepassNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let color_attachments = vec![ view_prepass_textures .normal @@ -210,7 +220,7 @@ impl ViewNode for MeshletPrepassNode { ]; let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("meshlet_prepass"), + label: Some("meshlet_material_prepass"), color_attachments: &color_attachments, depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &meshlet_material_depth.default_view, @@ -223,8 +233,11 @@ impl ViewNode for MeshletPrepassNode { timestamp_writes: None, occlusion_query_set: None, }); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + let pass_span = diagnostics.pass_span(&mut render_pass, "meshlet_material_prepass"); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } if view_has_motion_vector_prepass { @@ -251,18 +264,19 @@ impl ViewNode for MeshletPrepassNode { for (material_id, material_pipeline_id, material_bind_group) in meshlet_view_materials.iter() { - if instance_manager.material_present_in_scene(material_id) { - if let Some(material_pipeline) = + if instance_manager.material_present_in_scene(material_id) + && let Some(material_pipeline) = pipeline_cache.get_render_pipeline(*material_pipeline_id) - { - let x = *material_id * 3; - render_pass.set_render_pipeline(material_pipeline); - render_pass.set_bind_group(2, material_bind_group, &[]); - render_pass.draw(x..(x + 3), 0..1); - } + { + let x = *material_id * 3; + render_pass.set_render_pipeline(material_pipeline); + render_pass.set_bind_group(2, material_bind_group, &[]); + render_pass.draw(x..(x + 3), 0..1); } } + pass_span.end(&mut render_pass); + Ok(()) } } @@ -340,8 +354,10 @@ impl ViewNode for MeshletDeferredGBufferPrepassNode { .map(|deferred_lighting_pass_id| deferred_lighting_pass_id.get_attachment()), ]; + let diagnostics = render_context.diagnostic_recorder(); + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("meshlet_deferred_prepass"), + label: Some("meshlet_material_deferred_prepass"), color_attachments: &color_attachments, depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &meshlet_material_depth.default_view, @@ -354,8 +370,12 @@ impl ViewNode for MeshletDeferredGBufferPrepassNode { timestamp_writes: None, occlusion_query_set: None, }); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(&viewport.with_override(resolution_override)); + let pass_span = + diagnostics.pass_span(&mut render_pass, "meshlet_material_deferred_prepass"); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); } if view_has_motion_vector_prepass { @@ -382,18 +402,19 @@ impl ViewNode for MeshletDeferredGBufferPrepassNode { for (material_id, material_pipeline_id, material_bind_group) in meshlet_view_materials.iter() { - if instance_manager.material_present_in_scene(material_id) { - if let Some(material_pipeline) = + if instance_manager.material_present_in_scene(material_id) + && let Some(material_pipeline) = pipeline_cache.get_render_pipeline(*material_pipeline_id) - { - let x = *material_id * 3; - render_pass.set_render_pipeline(material_pipeline); - render_pass.set_bind_group(2, material_bind_group, &[]); - render_pass.draw(x..(x + 3), 0..1); - } + { + let x = *material_id * 3; + render_pass.set_render_pipeline(material_pipeline); + render_pass.set_bind_group(2, material_bind_group, &[]); + render_pass.draw(x..(x + 3), 0..1); } } + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index 160097fc50070..e056853be39b5 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -14,6 +14,7 @@ use bevy_ecs::{ use bevy_math::UVec2; use bevy_render::{ camera::ExtractedCamera, + diagnostic::RecordDiagnostics, render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::*, renderer::RenderContext, @@ -104,9 +105,15 @@ impl Node for MeshletVisibilityBufferRasterPassNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + render_context .command_encoder() .push_debug_group("meshlet_visibility_buffer_raster"); + let time_span = diagnostics.time_span( + render_context.command_encoder(), + "meshlet_visibility_buffer_raster", + ); let resource_manager = world.get_resource::().unwrap(); render_context.command_encoder().clear_buffer( @@ -239,6 +246,10 @@ impl Node for MeshletVisibilityBufferRasterPassNode { "meshlet_visibility_buffer_raster: {}", shadow_view.pass_name )); + let pass_span = diagnostics.time_span( + render_context.command_encoder(), + shadow_view.pass_name.clone(), + ); clear_visibility_buffer_pass( render_context, &meshlet_view_bind_groups.clear_visibility_buffer, @@ -331,8 +342,11 @@ impl Node for MeshletVisibilityBufferRasterPassNode { downsample_depth_second_shadow_view_pipeline, ); render_context.command_encoder().pop_debug_group(); + pass_span.end(render_context.command_encoder()); } + time_span.end(render_context.command_encoder()); + Ok(()) } } @@ -595,6 +609,7 @@ fn raster_pass( }), color_attachments: &[Some(RenderPassColorAttachment { view: dummy_render_target, + depth_slice: None, resolve_target: None, ops: Operations { load: LoadOp::Clear(LinearRgba::BLACK.into()), diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 66de219a90186..267dd66f314b7 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -407,6 +407,10 @@ impl PrepassPipeline { // since that's the only time it gets called from a prepass pipeline.) shader_defs.push("PREPASS_PIPELINE".into()); + shader_defs.push(ShaderDefVal::UInt( + "MATERIAL_BIND_GROUP".into(), + crate::MATERIAL_BIND_GROUP_INDEX as u32, + )); // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. // The main limitation right now is that bind group order is hardcoded in shaders. bind_group_layouts.push( diff --git a/crates/bevy_pbr/src/render/fog.rs b/crates/bevy_pbr/src/render/fog.rs index fa091207258f2..29dfab349aa52 100644 --- a/crates/bevy_pbr/src/render/fog.rs +++ b/crates/bevy_pbr/src/render/fog.rs @@ -133,7 +133,6 @@ impl Plugin for FogPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "fog.wgsl"); - app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 52df74cc26a2b..b7b2b7df06295 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -26,17 +26,16 @@ use bevy_ecs::{ system::{lifetimeless::Read, Commands, Query, Res, ResMut}, world::{FromWorld, World}, }; -use bevy_render::batching::gpu_preprocessing::{ - GpuPreprocessingMode, IndirectParametersGpuMetadata, UntypedPhaseIndirectParametersBuffers, -}; use bevy_render::{ batching::gpu_preprocessing::{ - BatchedInstanceBuffers, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingSupport, - IndirectBatchSet, IndirectParametersBuffers, IndirectParametersCpuMetadata, - IndirectParametersIndexed, IndirectParametersNonIndexed, - LatePreprocessWorkItemIndirectParameters, PreprocessWorkItem, PreprocessWorkItemBuffers, - UntypedPhaseBatchedInstanceBuffers, + BatchedInstanceBuffers, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingMode, + GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers, + IndirectParametersCpuMetadata, IndirectParametersGpuMetadata, IndirectParametersIndexed, + IndirectParametersNonIndexed, LatePreprocessWorkItemIndirectParameters, PreprocessWorkItem, + PreprocessWorkItemBuffers, UntypedPhaseBatchedInstanceBuffers, + UntypedPhaseIndirectParametersBuffers, }, + diagnostic::RecordDiagnostics, experimental::occlusion_culling::OcclusionCulling, render_graph::{Node, NodeRunError, RenderGraphContext, RenderGraphExt}, render_resource::{ @@ -582,6 +581,8 @@ impl Node for EarlyGpuPreprocessNode { render_context: &mut RenderContext<'w>, world: &'w World, ) -> Result<(), NodeRunError> { + let diagnostics = render_context.diagnostic_recorder(); + // Grab the [`BatchedInstanceBuffers`]. let batched_instance_buffers = world.resource::>(); @@ -593,9 +594,10 @@ impl Node for EarlyGpuPreprocessNode { render_context .command_encoder() .begin_compute_pass(&ComputePassDescriptor { - label: Some("early mesh preprocessing"), + label: Some("early_mesh_preprocessing"), timestamp_writes: None, }); + let pass_span = diagnostics.time_span(&mut compute_pass, "early_mesh_preprocessing"); let mut all_views: SmallVec<[_; 8]> = SmallVec::new(); all_views.push(graph.view_entity()); @@ -771,6 +773,8 @@ impl Node for EarlyGpuPreprocessNode { } } + pass_span.end(&mut compute_pass); + Ok(()) } } @@ -818,6 +822,8 @@ impl Node for LateGpuPreprocessNode { render_context: &mut RenderContext<'w>, world: &'w World, ) -> Result<(), NodeRunError> { + let diagnostics = render_context.diagnostic_recorder(); + // Grab the [`BatchedInstanceBuffers`]. let batched_instance_buffers = world.resource::>(); @@ -829,9 +835,10 @@ impl Node for LateGpuPreprocessNode { render_context .command_encoder() .begin_compute_pass(&ComputePassDescriptor { - label: Some("late mesh preprocessing"), + label: Some("late_mesh_preprocessing"), timestamp_writes: None, }); + let pass_span = diagnostics.time_span(&mut compute_pass, "late_mesh_preprocessing"); // Run the compute passes. for (view, bind_groups, view_uniform_offset) in self.view_query.iter_manual(world) { @@ -940,6 +947,8 @@ impl Node for LateGpuPreprocessNode { } } + pass_span.end(&mut compute_pass); + Ok(()) } } @@ -967,7 +976,7 @@ impl Node for EarlyPrepassBuildIndirectParametersNode { render_context, world, &preprocess_pipelines.early_phase, - "early prepass indirect parameters building", + "early_prepass_indirect_parameters_building", ) } } @@ -995,7 +1004,7 @@ impl Node for LatePrepassBuildIndirectParametersNode { render_context, world, &preprocess_pipelines.late_phase, - "late prepass indirect parameters building", + "late_prepass_indirect_parameters_building", ) } } @@ -1017,7 +1026,7 @@ impl Node for MainBuildIndirectParametersNode { render_context, world, &preprocess_pipelines.main_phase, - "main indirect parameters building", + "main_indirect_parameters_building", ) } } @@ -1034,6 +1043,8 @@ fn run_build_indirect_parameters_node( return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let pipeline_cache = world.resource::(); let indirect_parameters_buffers = world.resource::(); @@ -1044,6 +1055,7 @@ fn run_build_indirect_parameters_node( label: Some(label), timestamp_writes: None, }); + let pass_span = diagnostics.time_span(&mut compute_pass, label); // Fetch the pipeline. let ( @@ -1063,6 +1075,7 @@ fn run_build_indirect_parameters_node( ) else { warn!("The build indirect parameters pipelines weren't ready"); + pass_span.end(&mut compute_pass); return Ok(()); }; @@ -1077,6 +1090,7 @@ fn run_build_indirect_parameters_node( ) else { // This will happen while the pipeline is being compiled and is fine. + pass_span.end(&mut compute_pass); return Ok(()); }; @@ -1148,6 +1162,8 @@ fn run_build_indirect_parameters_node( } } + pass_span.end(&mut compute_pass); + Ok(()) } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index bec2a6d1a9597..021ef48c36c33 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1254,7 +1254,7 @@ pub fn prepare_lights( ShadowView { depth_attachment, pass_name: format!( - "shadow pass point light {} {}", + "shadow_point_light_{}_{}", light_index, face_index_to_name(face_index) ), @@ -1359,7 +1359,7 @@ pub fn prepare_lights( commands.entity(view_light_entity).insert(( ShadowView { depth_attachment, - pass_name: format!("shadow pass spot light {light_index}"), + pass_name: format!("shadow_spot_light_{light_index}"), }, ExtractedView { retained_view_entity, @@ -1503,7 +1503,7 @@ pub fn prepare_lights( ShadowView { depth_attachment, pass_name: format!( - "shadow pass directional light {light_index} cascade {cascade_index}" + "shadow_directional_light_{light_index}_cascade_{cascade_index}" ), }, ExtractedView { @@ -1865,7 +1865,7 @@ pub fn queue_shadows( mut shadow_render_phases: ResMut>, gpu_preprocessing_support: Res, mesh_allocator: Res, - view_lights: Query<(Entity, &ViewLightEntities), With>, + view_lights: Query<(Entity, &ViewLightEntities, Option<&RenderLayers>), With>, view_light_entities: Query<(&LightEntity, &ExtractedView)>, point_light_entities: Query<&RenderCubemapVisibleEntities, With>, directional_light_entities: Query< @@ -1875,7 +1875,7 @@ pub fn queue_shadows( spot_light_entities: Query<&RenderVisibleMeshEntities, With>, specialized_material_pipeline_cache: Res, ) { - for (entity, view_lights) in &view_lights { + for (entity, view_lights, camera_layers) in &view_lights { for view_light_entity in view_lights.lights.iter().copied() { let Ok((light_entity, extracted_view_light)) = view_light_entities.get(view_light_entity) @@ -1925,11 +1925,6 @@ pub fn queue_shadows( continue; }; - // Skip the entity if it's cached in a bin and up to date. - if shadow_phase.validate_cached_entity(main_entity, *current_change_tick) { - continue; - } - let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity) else { continue; @@ -1941,6 +1936,23 @@ pub fn queue_shadows( continue; } + let mesh_layers = mesh_instance + .shared + .render_layers + .as_ref() + .unwrap_or_default(); + + let camera_layers = camera_layers.unwrap_or_default(); + + if !camera_layers.intersects(mesh_layers) { + continue; + } + + // Skip the entity if it's cached in a bin and up to date. + if shadow_phase.validate_cached_entity(main_entity, *current_change_tick) { + continue; + } + let Some(material_instance) = render_material_instances.instances.get(&main_entity) else { continue; diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 9795d442ddd99..e908d39e813b7 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -76,7 +76,9 @@ use bevy_render::camera::TemporalJitter; use bevy_render::prelude::Msaa; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::ExtractedView; +use bevy_render::view::RenderLayers; use bevy_render::RenderSystems::PrepareAssets; + use bytemuck::{Pod, Zeroable}; use nonmax::{NonMaxU16, NonMaxU32}; use smallvec::{smallvec, SmallVec}; @@ -720,6 +722,8 @@ pub struct RenderMeshInstanceShared { pub lightmap_slab_index: Option, /// User supplied tag to identify this mesh instance. pub tag: u32, + /// Render layers that this mesh instance belongs to. + pub render_layers: Option, } /// Information that is gathered during the parallel portion of mesh extraction @@ -811,6 +815,7 @@ impl RenderMeshInstanceShared { tag: Option<&MeshTag>, not_shadow_caster: bool, no_automatic_batching: bool, + render_layers: Option<&RenderLayers>, ) -> Self { Self::for_cpu_building( previous_transform, @@ -819,6 +824,7 @@ impl RenderMeshInstanceShared { default(), not_shadow_caster, no_automatic_batching, + render_layers, ) } @@ -830,6 +836,7 @@ impl RenderMeshInstanceShared { material_bindings_index: MaterialBindingId, not_shadow_caster: bool, no_automatic_batching: bool, + render_layers: Option<&RenderLayers>, ) -> Self { let mut mesh_instance_flags = RenderMeshInstanceFlags::empty(); mesh_instance_flags.set(RenderMeshInstanceFlags::SHADOW_CASTER, !not_shadow_caster); @@ -848,6 +855,7 @@ impl RenderMeshInstanceShared { material_bindings_index, lightmap_slab_index: None, tag: tag.map_or(0, |i| **i), + render_layers: render_layers.cloned(), } } @@ -903,7 +911,7 @@ impl RenderMeshInstances { /// Constructs [`RenderMeshQueueData`] for the given entity, if it has a /// mesh attached. - pub fn render_mesh_queue_data(&self, entity: MainEntity) -> Option { + pub fn render_mesh_queue_data(&self, entity: MainEntity) -> Option> { match *self { RenderMeshInstances::CpuBuilding(ref instances) => { instances.render_mesh_queue_data(entity) @@ -934,7 +942,7 @@ impl RenderMeshInstancesCpu { .map(|render_mesh_instance| render_mesh_instance.mesh_asset_id) } - fn render_mesh_queue_data(&self, entity: MainEntity) -> Option { + fn render_mesh_queue_data(&self, entity: MainEntity) -> Option> { self.get(&entity) .map(|render_mesh_instance| RenderMeshQueueData { shared: &render_mesh_instance.shared, @@ -958,7 +966,7 @@ impl RenderMeshInstancesGpu { .map(|render_mesh_instance| render_mesh_instance.mesh_asset_id) } - fn render_mesh_queue_data(&self, entity: MainEntity) -> Option { + fn render_mesh_queue_data(&self, entity: MainEntity) -> Option> { self.get(&entity) .map(|render_mesh_instance| RenderMeshQueueData { shared: &render_mesh_instance.shared, @@ -1313,6 +1321,7 @@ pub fn extract_meshes_for_cpu_building( Has, Has, Has, + Option<&RenderLayers>, )>, >, ) { @@ -1332,6 +1341,7 @@ pub fn extract_meshes_for_cpu_building( not_shadow_caster, no_automatic_batching, visibility_range, + render_layers, )| { if !view_visibility.get() { return; @@ -1364,6 +1374,7 @@ pub fn extract_meshes_for_cpu_building( material_bindings_index, not_shadow_caster, no_automatic_batching, + render_layers, ); let world_from_local = transform.affine(); @@ -1417,6 +1428,7 @@ type GpuMeshExtractionQuery = ( Has, Has, Has, + Option>, ); /// Extracts meshes from the main world into the render world and queues @@ -1536,6 +1548,7 @@ fn extract_mesh_for_gpu_building( not_shadow_caster, no_automatic_batching, visibility_range, + render_layers, ): ::Item<'_, '_>, render_visibility_ranges: &RenderVisibilityRanges, render_mesh_instances: &RenderMeshInstancesGpu, @@ -1566,6 +1579,7 @@ fn extract_mesh_for_gpu_building( tag, not_shadow_caster, no_automatic_batching, + render_layers, ); let lightmap_uv_rect = pack_lightmap_uv_rect(lightmap.map(|lightmap| lightmap.uv_rect)); @@ -2712,27 +2726,26 @@ pub fn prepare_mesh_bind_groups( mut render_lightmaps: ResMut, ) { // CPU mesh preprocessing path. - if let Some(cpu_batched_instance_buffer) = cpu_batched_instance_buffer { - if let Some(instance_data_binding) = cpu_batched_instance_buffer + if let Some(cpu_batched_instance_buffer) = cpu_batched_instance_buffer + && let Some(instance_data_binding) = cpu_batched_instance_buffer .into_inner() .instance_data_binding() - { - // In this path, we only have a single set of bind groups for all phases. - let cpu_preprocessing_mesh_bind_groups = prepare_mesh_bind_groups_for_phase( - instance_data_binding, - &meshes, - &mesh_pipeline, - &render_device, - &skins_uniform, - &weights_uniform, - &mut render_lightmaps, - ); + { + // In this path, we only have a single set of bind groups for all phases. + let cpu_preprocessing_mesh_bind_groups = prepare_mesh_bind_groups_for_phase( + instance_data_binding, + &meshes, + &mesh_pipeline, + &render_device, + &skins_uniform, + &weights_uniform, + &mut render_lightmaps, + ); - commands.insert_resource(MeshBindGroups::CpuPreprocessing( - cpu_preprocessing_mesh_bind_groups, - )); - return; - } + commands.insert_resource(MeshBindGroups::CpuPreprocessing( + cpu_preprocessing_mesh_bind_groups, + )); + return; } // GPU mesh preprocessing path. @@ -3022,11 +3035,11 @@ impl RenderCommand

for SetMeshBindGroup { dynamic_offsets[offset_count] = dynamic_offset; offset_count += 1; } - if let Some(current_skin_index) = current_skin_byte_offset { - if skins_use_uniform_buffers(&render_device) { - dynamic_offsets[offset_count] = current_skin_index.byte_offset; - offset_count += 1; - } + if let Some(current_skin_index) = current_skin_byte_offset + && skins_use_uniform_buffers(&render_device) + { + dynamic_offsets[offset_count] = current_skin_index.byte_offset; + offset_count += 1; } if let Some(current_morph_index) = current_morph_index { dynamic_offsets[offset_count] = current_morph_index.index; @@ -3036,11 +3049,11 @@ impl RenderCommand

for SetMeshBindGroup { // Attach motion vectors if needed. if has_motion_vector_prepass { // Attach the previous skin index for motion vector computation. - if skins_use_uniform_buffers(&render_device) { - if let Some(current_skin_byte_offset) = current_skin_byte_offset { - dynamic_offsets[offset_count] = current_skin_byte_offset.byte_offset; - offset_count += 1; - } + if skins_use_uniform_buffers(&render_device) + && let Some(current_skin_byte_offset) = current_skin_byte_offset + { + dynamic_offsets[offset_count] = current_skin_byte_offset.byte_offset; + offset_count += 1; } // Attach the previous morph index for motion vector computation. If @@ -3094,13 +3107,12 @@ impl RenderCommand

for DrawMesh { // If we're using GPU preprocessing, then we're dependent on that // compute shader having been run, which of course can only happen if // it's compiled. Otherwise, our mesh instance data won't be present. - if let Some(preprocess_pipelines) = preprocess_pipelines { - if !has_preprocess_bind_group + if let Some(preprocess_pipelines) = preprocess_pipelines + && (!has_preprocess_bind_group || !preprocess_pipelines - .pipelines_are_loaded(&pipeline_cache, &preprocessing_support) - { - return RenderCommandResult::Skip; - } + .pipelines_are_loaded(&pipeline_cache, &preprocessing_support)) + { + return RenderCommandResult::Skip; } let meshes = meshes.into_inner(); diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index 51b28389dcd0c..b8f095af72a86 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -91,7 +91,7 @@ mod entry { renderer::RenderDevice, }; - fn entry(binding: u32, size: Option, buffer: &Buffer) -> BindGroupEntry { + fn entry(binding: u32, size: Option, buffer: &Buffer) -> BindGroupEntry<'_> { BindGroupEntry { binding, resource: BindingResource::Buffer(BufferBinding { @@ -116,22 +116,25 @@ mod entry { }; entry(binding, size, buffer) } - pub(super) fn weights(binding: u32, buffer: &Buffer) -> BindGroupEntry { + pub(super) fn weights(binding: u32, buffer: &Buffer) -> BindGroupEntry<'_> { entry(binding, Some(MORPH_BUFFER_SIZE as u64), buffer) } - pub(super) fn targets(binding: u32, texture: &TextureView) -> BindGroupEntry { + pub(super) fn targets(binding: u32, texture: &TextureView) -> BindGroupEntry<'_> { BindGroupEntry { binding, resource: BindingResource::TextureView(texture), } } - pub(super) fn lightmaps_texture_view(binding: u32, texture: &TextureView) -> BindGroupEntry { + pub(super) fn lightmaps_texture_view( + binding: u32, + texture: &TextureView, + ) -> BindGroupEntry<'_> { BindGroupEntry { binding, resource: BindingResource::TextureView(texture), } } - pub(super) fn lightmaps_sampler(binding: u32, sampler: &Sampler) -> BindGroupEntry { + pub(super) fn lightmaps_sampler(binding: u32, sampler: &Sampler) -> BindGroupEntry<'_> { BindGroupEntry { binding, resource: BindingResource::Sampler(sampler), diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 0f40327dc7ea6..71f5dddf1bbfe 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -684,8 +684,8 @@ pub fn prepare_mesh_view_bind_groups( entries = entries.extend_with_indices(((24, transmission_view), (25, transmission_sampler))); - if has_oit { - if let ( + if has_oit + && let ( Some(oit_layers_binding), Some(oit_layer_ids_binding), Some(oit_settings_binding), @@ -693,13 +693,13 @@ pub fn prepare_mesh_view_bind_groups( oit_buffers.layers.binding(), oit_buffers.layer_ids.binding(), oit_buffers.settings.binding(), - ) { - entries = entries.extend_with_indices(( - (26, oit_layers_binding.clone()), - (27, oit_layer_ids_binding.clone()), - (28, oit_settings_binding.clone()), - )); - } + ) + { + entries = entries.extend_with_indices(( + (26, oit_layers_binding.clone()), + (27, oit_layer_ids_binding.clone()), + (28, oit_settings_binding.clone()), + )); } let mut entries_binding_array = DynamicBindGroupEntries::new(); diff --git a/crates/bevy_pbr/src/render/pbr_bindings.wgsl b/crates/bevy_pbr/src/render/pbr_bindings.wgsl index 373d0bdd54436..6d21c81f9e5da 100644 --- a/crates/bevy_pbr/src/render/pbr_bindings.wgsl +++ b/crates/bevy_pbr/src/render/pbr_bindings.wgsl @@ -37,53 +37,53 @@ struct StandardMaterialBindings { specular_tint_sampler: u32, // 30 } -@group(3) @binding(0) var material_indices: array; -@group(3) @binding(10) var material_array: array; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material_indices: array; +@group(#{MATERIAL_BIND_GROUP}) @binding(10) var material_array: array; #else // BINDLESS -@group(3) @binding(0) var material: StandardMaterial; -@group(3) @binding(1) var base_color_texture: texture_2d; -@group(3) @binding(2) var base_color_sampler: sampler; -@group(3) @binding(3) var emissive_texture: texture_2d; -@group(3) @binding(4) var emissive_sampler: sampler; -@group(3) @binding(5) var metallic_roughness_texture: texture_2d; -@group(3) @binding(6) var metallic_roughness_sampler: sampler; -@group(3) @binding(7) var occlusion_texture: texture_2d; -@group(3) @binding(8) var occlusion_sampler: sampler; -@group(3) @binding(9) var normal_map_texture: texture_2d; -@group(3) @binding(10) var normal_map_sampler: sampler; -@group(3) @binding(11) var depth_map_texture: texture_2d; -@group(3) @binding(12) var depth_map_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material: StandardMaterial; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var base_color_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(2) var base_color_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(3) var emissive_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(4) var emissive_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(5) var metallic_roughness_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(6) var metallic_roughness_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(7) var occlusion_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(8) var occlusion_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(9) var normal_map_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(10) var normal_map_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(11) var depth_map_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(12) var depth_map_sampler: sampler; #ifdef PBR_ANISOTROPY_TEXTURE_SUPPORTED -@group(3) @binding(13) var anisotropy_texture: texture_2d; -@group(3) @binding(14) var anisotropy_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(13) var anisotropy_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(14) var anisotropy_sampler: sampler; #endif // PBR_ANISOTROPY_TEXTURE_SUPPORTED #ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED -@group(3) @binding(15) var specular_transmission_texture: texture_2d; -@group(3) @binding(16) var specular_transmission_sampler: sampler; -@group(3) @binding(17) var thickness_texture: texture_2d; -@group(3) @binding(18) var thickness_sampler: sampler; -@group(3) @binding(19) var diffuse_transmission_texture: texture_2d; -@group(3) @binding(20) var diffuse_transmission_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(15) var specular_transmission_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(16) var specular_transmission_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(17) var thickness_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(18) var thickness_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(19) var diffuse_transmission_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(20) var diffuse_transmission_sampler: sampler; #endif // PBR_TRANSMISSION_TEXTURES_SUPPORTED #ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED -@group(3) @binding(21) var clearcoat_texture: texture_2d; -@group(3) @binding(22) var clearcoat_sampler: sampler; -@group(3) @binding(23) var clearcoat_roughness_texture: texture_2d; -@group(3) @binding(24) var clearcoat_roughness_sampler: sampler; -@group(3) @binding(25) var clearcoat_normal_texture: texture_2d; -@group(3) @binding(26) var clearcoat_normal_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(21) var clearcoat_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(22) var clearcoat_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(23) var clearcoat_roughness_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(24) var clearcoat_roughness_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(25) var clearcoat_normal_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(26) var clearcoat_normal_sampler: sampler; #endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED #ifdef PBR_SPECULAR_TEXTURES_SUPPORTED -@group(3) @binding(27) var specular_texture: texture_2d; -@group(3) @binding(28) var specular_sampler: sampler; -@group(3) @binding(29) var specular_tint_texture: texture_2d; -@group(3) @binding(30) var specular_tint_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(27) var specular_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(28) var specular_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(29) var specular_tint_texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(30) var specular_tint_sampler: sampler; #endif // PBR_SPECULAR_TEXTURES_SUPPORTED #endif // BINDLESS diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 001aa67c12387..d90ed010698bc 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -19,6 +19,7 @@ use bevy_image::ToExtents; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::{ExtractedCamera, TemporalJitter}, + diagnostic::RecordDiagnostics, extract_component::ExtractComponent, globals::{GlobalsBuffer, GlobalsUniform}, load_shader_library, @@ -52,8 +53,6 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { embedded_asset!(app, "ssao.wgsl"); embedded_asset!(app, "spatial_denoise.wgsl"); - app.register_type::(); - app.add_plugins(SyncComponentPlugin::::default()); } @@ -219,16 +218,18 @@ impl ViewNode for SsaoNode { return Ok(()); }; - render_context.command_encoder().push_debug_group("ssao"); + let diagnostics = render_context.diagnostic_recorder(); + + let command_encoder = render_context.command_encoder(); + command_encoder.push_debug_group("ssao"); + let time_span = diagnostics.time_span(command_encoder, "ssao"); { let mut preprocess_depth_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("ssao_preprocess_depth_pass"), - timestamp_writes: None, - }); + command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("ssao_preprocess_depth"), + timestamp_writes: None, + }); preprocess_depth_pass.set_pipeline(preprocess_depth_pipeline); preprocess_depth_pass.set_bind_group(0, &bind_groups.preprocess_depth_bind_group, &[]); preprocess_depth_pass.set_bind_group( @@ -244,13 +245,10 @@ impl ViewNode for SsaoNode { } { - let mut ssao_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("ssao_ssao_pass"), - timestamp_writes: None, - }); + let mut ssao_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("ssao"), + timestamp_writes: None, + }); ssao_pass.set_pipeline(ssao_pipeline); ssao_pass.set_bind_group(0, &bind_groups.ssao_bind_group, &[]); ssao_pass.set_bind_group( @@ -263,12 +261,10 @@ impl ViewNode for SsaoNode { { let mut spatial_denoise_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("ssao_spatial_denoise_pass"), - timestamp_writes: None, - }); + command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("ssao_spatial_denoise"), + timestamp_writes: None, + }); spatial_denoise_pass.set_pipeline(spatial_denoise_pipeline); spatial_denoise_pass.set_bind_group(0, &bind_groups.spatial_denoise_bind_group, &[]); spatial_denoise_pass.set_bind_group( @@ -283,7 +279,8 @@ impl ViewNode for SsaoNode { ); } - render_context.command_encoder().pop_debug_group(); + time_span.end(command_encoder); + command_encoder.pop_debug_group(); Ok(()) } } diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index a30f315269ecd..aea23f160861f 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -24,8 +24,12 @@ use bevy_ecs::{ use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ + diagnostic::RecordDiagnostics, extract_component::{ExtractComponent, ExtractComponentPlugin}, - render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, + load_shader_library, + render_graph::{ + NodeRunError, RenderGraph, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner, + }, render_resource::{ binding_types, AddressMode, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites, DynamicUniformBuffer, FilterMode, @@ -38,7 +42,6 @@ use bevy_render::{ view::{ExtractedView, Msaa, ViewTarget, ViewUniformOffset}, Render, RenderApp, RenderStartup, RenderSystems, }; -use bevy_render::{load_shader_library, render_graph::RenderGraph}; use bevy_utils::{once, prelude::default}; use tracing::info; @@ -181,8 +184,7 @@ impl Plugin for ScreenSpaceReflectionsPlugin { load_shader_library!(app, "ssr.wgsl"); load_shader_library!(app, "raymarch.wgsl"); - app.register_type::() - .add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -281,6 +283,8 @@ impl ViewNode for ScreenSpaceReflectionsNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + // Set up a standard pair of postprocessing textures. let postprocess = view_target.post_process_write(); @@ -299,9 +303,10 @@ impl ViewNode for ScreenSpaceReflectionsNode { // Build the SSR render pass. let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("SSR pass"), + label: Some("ssr"), color_attachments: &[Some(RenderPassColorAttachment { view: postprocess.destination, + depth_slice: None, resolve_target: None, ops: Operations::default(), })], @@ -309,6 +314,7 @@ impl ViewNode for ScreenSpaceReflectionsNode { timestamp_writes: None, occlusion_query_set: None, }); + let pass_span = diagnostics.pass_span(&mut render_pass, "ssr"); // Set bind groups. render_pass.set_render_pipeline(render_pipeline); @@ -330,6 +336,8 @@ impl ViewNode for ScreenSpaceReflectionsNode { render_pass.set_bind_group(2, &ssr_bind_group, &[]); render_pass.draw(0..3, 0..1); + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index a49e9b62a4f78..e7d8d33b82b28 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -20,6 +20,7 @@ use bevy_ecs::{ use bevy_image::{BevyDefault, Image}; use bevy_math::{vec4, Mat3A, Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles as _}; use bevy_render::{ + diagnostic::RecordDiagnostics, mesh::{ allocator::MeshAllocator, Mesh, MeshVertexBufferLayoutRef, RenderMesh, RenderMeshBufferInfo, }, @@ -358,6 +359,13 @@ impl ViewNode for VolumetricFogNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + render_context + .command_encoder() + .push_debug_group("volumetric_lighting"); + let time_span = + diagnostics.time_span(render_context.command_encoder(), "volumetric_lighting"); + let fog_assets = world.resource::(); let render_meshes = world.resource::>(); @@ -431,6 +439,7 @@ impl ViewNode for VolumetricFogNode { label: Some("volumetric lighting pass"), color_attachments: &[Some(RenderPassColorAttachment { view: view_target.main_texture_view(), + depth_slice: None, resolve_target: None, ops: Operations { load: LoadOp::Load, @@ -492,6 +501,9 @@ impl ViewNode for VolumetricFogNode { } } + time_span.end(render_context.command_encoder()); + render_context.command_encoder().pop_debug_group(); + Ok(()) } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 10e5c83c0139d..41b610fbfd085 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -27,7 +27,8 @@ use bevy_platform::{ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, - camera::ExtractedCamera, + camera::{extract_cameras, ExtractedCamera}, + diagnostic::RecordDiagnostics, extract_resource::ExtractResource, mesh::{ allocator::{MeshAllocator, SlabId}, @@ -51,9 +52,8 @@ use bevy_render::{ ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget, }, - Extract, Render, RenderApp, RenderDebugFlags, RenderSystems, + Extract, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, }; -use bevy_render::{camera::extract_cameras, RenderStartup}; use core::{hash::Hash, ops::Range}; use tracing::error; @@ -89,9 +89,6 @@ impl Plugin for WireframePlugin { )) .init_asset::() .init_resource::>() - .register_type::() - .register_type::() - .register_type::() .init_resource::() .init_resource::() .add_systems(Startup, setup_global_wireframe_material) @@ -384,13 +381,16 @@ impl ViewNode for Wireframe3dNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("wireframe_3d_pass"), + label: Some("wireframe_3d"), color_attachments: &[Some(target.get_color_attachment())], depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), timestamp_writes: None, occlusion_query_set: None, }); + let pass_span = diagnostics.pass_span(&mut render_pass, "wireframe_3d"); if let Some(viewport) = camera.viewport.as_ref() { render_pass.set_camera_viewport(viewport); @@ -401,6 +401,8 @@ impl ViewNode for Wireframe3dNode { return Err(NodeRunError::DrawError(err)); } + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index 51d653af5c7f8..e100662389448 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -95,10 +95,10 @@ where }; // Otherwise, send it to the window entity (unless this is a window entity). - if window.is_none() { - if let NormalizedRenderTarget::Window(window_ref) = pointer.pointer_location.target { - return Some(window_ref.entity()); - } + if window.is_none() + && let NormalizedRenderTarget::Window(window_ref) = pointer.pointer_location.target + { + return Some(window_ref.entity()); } None diff --git a/crates/bevy_picking/src/hover.rs b/crates/bevy_picking/src/hover.rs index f6755683940be..2621f105c2c40 100644 --- a/crates/bevy_picking/src/hover.rs +++ b/crates/bevy_picking/src/hover.rs @@ -243,10 +243,10 @@ pub fn update_interactions( }; for entity in previously_hovered_entities.keys() { - if !new_interaction_state.contains_key(entity) { - if let Ok(mut interaction) = interact.get_mut(*entity) { - interaction.set_if_neq(PickingInteraction::None); - } + if !new_interaction_state.contains_key(entity) + && let Ok(mut interaction) = interact.get_mut(*entity) + { + interaction.set_if_neq(PickingInteraction::None); } } } diff --git a/crates/bevy_picking/src/input.rs b/crates/bevy_picking/src/input.rs index 3050522ab5f4d..5108a849a1b79 100644 --- a/crates/bevy_picking/src/input.rs +++ b/crates/bevy_picking/src/input.rs @@ -95,7 +95,6 @@ pub struct PointerInputPlugin; impl Plugin for PointerInputPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .register_type::() .add_systems(Startup, spawn_mouse_pointer) .add_systems( First, diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index 05171914f9c6e..7a0e1dfb2b82d 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -409,16 +409,7 @@ impl Plugin for PickingPlugin { PickingSystems::Last, ) .chain(), - ) - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::(); + ); } } diff --git a/crates/bevy_picking/src/mesh_picking/mod.rs b/crates/bevy_picking/src/mesh_picking/mod.rs index 8e6a16690cfdb..41b981385b7fc 100644 --- a/crates/bevy_picking/src/mesh_picking/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/mod.rs @@ -25,7 +25,7 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_reflect::prelude::*; use bevy_render::{prelude::*, view::RenderLayers}; -use ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastVisibility, SimplifiedMesh}; +use ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastVisibility}; /// An optional component that marks cameras that should be used in the [`MeshPickingPlugin`]. /// @@ -69,8 +69,6 @@ pub struct MeshPickingPlugin; impl Plugin for MeshPickingPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .register_type::() - .register_type::() .add_systems(PreUpdate, update_hits.in_set(PickingSystems::Backend)); } } diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs index e42dc160e26fc..dfc6a22c11353 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs @@ -229,14 +229,14 @@ impl<'w, 's> MeshRayCast<'w, 's> { RayCastVisibility::Visible => inherited_visibility.get(), RayCastVisibility::VisibleInView => view_visibility.get(), }; - if should_ray_cast { - if let Some(distance) = ray_aabb_intersection_3d( + if should_ray_cast + && let Some(distance) = ray_aabb_intersection_3d( ray, &Aabb3d::new(aabb.center, aabb.half_extents), &transform.to_matrix(), - ) { - aabb_hits_tx.send((FloatOrd(distance), entity)).ok(); - } + ) + { + aabb_hits_tx.send((FloatOrd(distance), entity)).ok(); } }, ); diff --git a/crates/bevy_platform/Cargo.toml b/crates/bevy_platform/Cargo.toml index 614ff15fce0fb..5f95af866794d 100644 --- a/crates/bevy_platform/Cargo.toml +++ b/crates/bevy_platform/Cargo.toml @@ -32,6 +32,10 @@ std = [ "spin/std", "foldhash/std", "serde?/std", + "wasm-bindgen-futures?/std", + "futures-channel/std", + "wasm-bindgen?/std", + "js-sys?/std", ] ## Allows access to the `alloc` crate. @@ -43,7 +47,14 @@ critical-section = ["dep:critical-section", "portable-atomic/critical-section"] ## Enables use of browser APIs. ## Note this is currently only applicable on `wasm32` architectures. -web = ["dep:web-time", "dep:getrandom"] +web = [ + "std", + "dep:web-time", + "dep:getrandom", + "dep:wasm-bindgen-futures", + "dep:wasm-bindgen", + "dep:js-sys", +] [dependencies] critical-section = { version = "1.2.0", default-features = false, optional = true } @@ -65,9 +76,13 @@ rayon = { version = "1", default-features = false, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] web-time = { version = "1.1", default-features = false, optional = true } -getrandom = { version = "0.2.0", default-features = false, optional = true, features = [ - "js", +getrandom = { version = "0.3.0", default-features = false, optional = true, features = [ + "wasm_js", ] } +wasm-bindgen-futures = { version = "0.4", default-features = false, optional = true } +futures-channel = { version = "0.3", default-features = false } +js-sys = { version = "0.3", default-features = false, optional = true } +wasm-bindgen = { version = "0.2", default-features = false, optional = true } [target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies] portable-atomic = { version = "1", default-features = false, features = [ diff --git a/crates/bevy_platform/src/lib.rs b/crates/bevy_platform/src/lib.rs index 0dac76b011de3..ae02d2b877136 100644 --- a/crates/bevy_platform/src/lib.rs +++ b/crates/bevy_platform/src/lib.rs @@ -51,3 +51,14 @@ pub mod prelude { // * println // * thread_local } + +/// Re-exports of crates that are useful across Bevy. +/// Not intended for external crates to use. +#[doc(hidden)] +pub mod exports { + crate::cfg::web! { + pub use js_sys; + pub use wasm_bindgen; + pub use wasm_bindgen_futures; + } +} diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 8e2d4d0f38c4b..d40fbbf962481 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["bevy"] rust-version = "1.85.0" [features] -default = ["std", "smallvec", "debug"] +default = ["std", "smallvec", "debug", "auto_register_inventory"] # Features @@ -68,6 +68,22 @@ std = [ ## on all platforms, including `no_std`. critical-section = ["bevy_platform/critical-section"] +# Enables automatic reflect registration. Does nothing by itself, +# must select `auto_register_inventory` or `auto_register_static` to make it work. +auto_register = [] +## Enables automatic reflect registration using inventory. Not supported on all platforms. +auto_register_inventory = [ + "auto_register", + "bevy_reflect_derive/auto_register_inventory", + "dep:inventory", +] +## Enable automatic reflect registration without inventory. This feature has precedence over `auto_register_inventory`. +## See `load_type_registrations` for more info. +auto_register_static = [ + "auto_register", + "bevy_reflect_derive/auto_register_static", +] + ## Enables use of browser APIs. ## Note this is currently only applicable on `wasm32` architectures. web = ["bevy_platform/web", "uuid?/js"] @@ -97,7 +113,7 @@ derive_more = { version = "2", default-features = false, features = ["from"] } serde = { version = "1", default-features = false, features = ["alloc"] } assert_type_match = "0.1.1" smallvec = { version = "1", default-features = false, optional = true } -glam = { version = "0.29.3", default-features = false, features = [ +glam = { version = "0.30.1", default-features = false, features = [ "serde", ], optional = true } petgraph = { version = "0.8", features = ["serde-1"], optional = true } @@ -109,10 +125,13 @@ uuid = { version = "1.13.1", default-features = false, optional = true, features "serde", ] } variadics_please = "1.1" -wgpu-types = { version = "25", features = [ +wgpu-types = { version = "26", features = [ "serde", ], optional = true, default-features = false } +# deps for automatic type registration +inventory = { version = "0.3", optional = true } + [dev-dependencies] ron = "0.10" rmp-serde = "1.1" diff --git a/crates/bevy_reflect/derive/Cargo.toml b/crates/bevy_reflect/derive/Cargo.toml index 19875633edbad..7b56184881a4f 100644 --- a/crates/bevy_reflect/derive/Cargo.toml +++ b/crates/bevy_reflect/derive/Cargo.toml @@ -17,6 +17,13 @@ default = [] documentation = [] # Enables macro logic related to function reflection functions = [] +# Enables automatic reflect registration. Does nothing by itself, +# must select `auto_register_inventory` or `auto_register_static` to make it work. +auto_register = [] +# Enables automatic reflection using inventory. Not supported on all platforms. +auto_register_inventory = ["auto_register"] +# Enables automatic reflection on platforms not supported by inventory. See `load_type_registrations` for more info. +auto_register_static = ["auto_register", "dep:uuid"] [dependencies] bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" } @@ -24,6 +31,9 @@ indexmap = "2.0" proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["full", "extra-traits"] } +uuid = { version = "1.13.1", default-features = false, features = [ + "v4", +], optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] # TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption. diff --git a/crates/bevy_reflect/derive/src/container_attributes.rs b/crates/bevy_reflect/derive/src/container_attributes.rs index 8b2ae0351f53a..d6ff66218f121 100644 --- a/crates/bevy_reflect/derive/src/container_attributes.rs +++ b/crates/bevy_reflect/derive/src/container_attributes.rs @@ -25,6 +25,7 @@ mod kw { syn::custom_keyword!(Hash); syn::custom_keyword!(Clone); syn::custom_keyword!(no_field_bounds); + syn::custom_keyword!(no_auto_register); syn::custom_keyword!(opaque); } @@ -184,6 +185,7 @@ pub(crate) struct ContainerAttributes { type_path_attrs: TypePathAttrs, custom_where: Option, no_field_bounds: bool, + no_auto_register: bool, custom_attributes: CustomAttributes, is_opaque: bool, idents: Vec, @@ -240,6 +242,8 @@ impl ContainerAttributes { self.parse_no_field_bounds(input) } else if lookahead.peek(kw::Clone) { self.parse_clone(input) + } else if lookahead.peek(kw::no_auto_register) { + self.parse_no_auto_register(input) } else if lookahead.peek(kw::Debug) { self.parse_debug(input) } else if lookahead.peek(kw::Hash) { @@ -378,6 +382,16 @@ impl ContainerAttributes { Ok(()) } + /// Parse `no_auto_register` attribute. + /// + /// Examples: + /// - `#[reflect(no_auto_register)]` + fn parse_no_auto_register(&mut self, input: ParseStream) -> syn::Result<()> { + input.parse::()?; + self.no_auto_register = true; + Ok(()) + } + /// Parse `where` attribute. /// /// Examples: @@ -583,6 +597,12 @@ impl ContainerAttributes { self.no_field_bounds } + /// Returns true if the `no_auto_register` attribute was found on this type. + #[cfg(feature = "auto_register")] + pub fn no_auto_register(&self) -> bool { + self.no_auto_register + } + /// Returns true if the `opaque` attribute was found on this type. pub fn is_opaque(&self) -> bool { self.is_opaque diff --git a/crates/bevy_reflect/derive/src/derive_data.rs b/crates/bevy_reflect/derive/src/derive_data.rs index 9e3e169bc21f6..ce985d39bd2f3 100644 --- a/crates/bevy_reflect/derive/src/derive_data.rs +++ b/crates/bevy_reflect/derive/src/derive_data.rs @@ -332,7 +332,7 @@ impl<'a> ReflectDerive<'a> { } /// Get the remote type path, if any. - pub fn remote_ty(&self) -> Option { + pub fn remote_ty(&self) -> Option> { match self { Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => { data.meta.remote_ty() @@ -343,7 +343,7 @@ impl<'a> ReflectDerive<'a> { } /// Get the [`ReflectMeta`] for this derived type. - pub fn meta(&self) -> &ReflectMeta { + pub fn meta(&self) -> &ReflectMeta<'_> { match self { Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => data.meta(), Self::Enum(data) => data.meta(), @@ -351,7 +351,7 @@ impl<'a> ReflectDerive<'a> { } } - pub fn where_clause_options(&self) -> WhereClauseOptions { + pub fn where_clause_options(&self) -> WhereClauseOptions<'_, '_> { match self { Self::Struct(data) | Self::TupleStruct(data) | Self::UnitStruct(data) => { data.where_clause_options() @@ -462,7 +462,7 @@ impl<'a> ReflectMeta<'a> { } /// Get the remote type path, if any. - pub fn remote_ty(&self) -> Option { + pub fn remote_ty(&self) -> Option> { self.remote_ty } @@ -631,7 +631,7 @@ impl<'a> ReflectStruct<'a> { &self.fields } - pub fn where_clause_options(&self) -> WhereClauseOptions { + pub fn where_clause_options(&self) -> WhereClauseOptions<'_, '_> { WhereClauseOptions::new_with_types(self.meta(), self.active_types()) } @@ -851,7 +851,7 @@ impl<'a> ReflectEnum<'a> { self.variants.iter().flat_map(EnumVariant::active_fields) } - pub fn where_clause_options(&self) -> WhereClauseOptions { + pub fn where_clause_options(&self) -> WhereClauseOptions<'_, '_> { WhereClauseOptions::new_with_types(self.meta(), self.active_types()) } diff --git a/crates/bevy_reflect/derive/src/enum_utility.rs b/crates/bevy_reflect/derive/src/enum_utility.rs index c717b7723eb3d..2b32ce1e4d0f3 100644 --- a/crates/bevy_reflect/derive/src/enum_utility.rs +++ b/crates/bevy_reflect/derive/src/enum_utility.rs @@ -37,7 +37,7 @@ pub(crate) struct VariantField<'a, 'b> { /// Trait used to control how enum variants are built. pub(crate) trait VariantBuilder: Sized { /// Returns the enum data. - fn reflect_enum(&self) -> &ReflectEnum; + fn reflect_enum(&self) -> &ReflectEnum<'_>; /// Returns a token stream that accesses a field of a variant as an `Option`. /// @@ -212,7 +212,7 @@ impl<'a> FromReflectVariantBuilder<'a> { } impl<'a> VariantBuilder for FromReflectVariantBuilder<'a> { - fn reflect_enum(&self) -> &ReflectEnum { + fn reflect_enum(&self) -> &ReflectEnum<'_> { self.reflect_enum } @@ -244,7 +244,7 @@ impl<'a> TryApplyVariantBuilder<'a> { } impl<'a> VariantBuilder for TryApplyVariantBuilder<'a> { - fn reflect_enum(&self) -> &ReflectEnum { + fn reflect_enum(&self) -> &ReflectEnum<'_> { self.reflect_enum } @@ -300,7 +300,7 @@ impl<'a> ReflectCloneVariantBuilder<'a> { } impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> { - fn reflect_enum(&self) -> &ReflectEnum { + fn reflect_enum(&self) -> &ReflectEnum<'_> { self.reflect_enum } diff --git a/crates/bevy_reflect/derive/src/impls/common.rs b/crates/bevy_reflect/derive/src/impls/common.rs index 87836e383dc6d..c6507c6917491 100644 --- a/crates/bevy_reflect/derive/src/impls/common.rs +++ b/crates/bevy_reflect/derive/src/impls/common.rs @@ -154,3 +154,88 @@ pub fn common_partial_reflect_methods( #debug_fn } } + +#[cfg(feature = "auto_register")] +pub fn reflect_auto_registration(meta: &ReflectMeta) -> Option { + if meta.attrs().no_auto_register() { + return None; + } + + let bevy_reflect_path = meta.bevy_reflect_path(); + let type_path = meta.type_path(); + + if type_path.impl_is_generic() { + return None; + }; + + #[cfg(feature = "auto_register_static")] + { + use std::{ + env, fs, + io::Write, + path::PathBuf, + sync::{LazyLock, Mutex}, + }; + + // Skip unless env var is set, otherwise this might slow down rust-analyzer + if env::var("BEVY_REFLECT_AUTO_REGISTER_STATIC").is_err() { + return None; + } + + // Names of registrations functions will be stored in this file. + // To allow writing to this file from multiple threads during compilation it is protected by mutex. + // This static is valid for the duration of compilation of one crate and we have one file per crate, + // so it is enough to protect compilation threads from overwriting each other. + // This file is reset on every crate recompilation. + // + // It might make sense to replace the mutex with File::lock when file_lock feature becomes stable. + static REGISTRATION_FNS_EXPORT: LazyLock> = LazyLock::new(|| { + let path = PathBuf::from("target").join("bevy_reflect_type_registrations"); + fs::DirBuilder::new() + .recursive(true) + .create(&path) + .unwrap_or_else(|_| panic!("Failed to create {path:?}")); + let file_path = path.join(env::var("CARGO_CRATE_NAME").unwrap()); + let file = fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&file_path) + .unwrap_or_else(|_| panic!("Failed to create {file_path:?}")); + Mutex::new(file) + }); + + let export_name = format!("_bevy_reflect_register_{}", uuid::Uuid::new_v4().as_u128()); + + { + let mut file = REGISTRATION_FNS_EXPORT.lock().unwrap(); + writeln!(file, "{export_name}") + .unwrap_or_else(|_| panic!("Failed to write registration function {export_name}")); + // We must sync_data to ensure all content is written before releasing the lock. + file.sync_data().unwrap(); + }; + + Some(quote! { + /// # Safety + /// This function must only be used by the `load_type_registrations` macro. + #[unsafe(export_name=#export_name)] + pub unsafe extern "Rust" fn bevy_register_type(registry: &mut #bevy_reflect_path::TypeRegistry) { + <#type_path as #bevy_reflect_path::__macro_exports::RegisterForReflection>::__register(registry); + } + }) + } + + #[cfg(all( + feature = "auto_register_inventory", + not(feature = "auto_register_static") + ))] + { + Some(quote! { + #bevy_reflect_path::__macro_exports::auto_register::inventory::submit!{ + #bevy_reflect_path::__macro_exports::auto_register::AutomaticReflectRegistrations( + <#type_path as #bevy_reflect_path::__macro_exports::auto_register::RegisterForReflection>::__register + ) + } + }) + } +} diff --git a/crates/bevy_reflect/derive/src/impls/enums.rs b/crates/bevy_reflect/derive/src/impls/enums.rs index f2272c7c816e7..68f98e2617d26 100644 --- a/crates/bevy_reflect/derive/src/impls/enums.rs +++ b/crates/bevy_reflect/derive/src/impls/enums.rs @@ -77,6 +77,11 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream let (impl_generics, ty_generics, where_clause) = reflect_enum.meta().type_path().generics().split_for_impl(); + #[cfg(not(feature = "auto_register"))] + let auto_register = None::; + #[cfg(feature = "auto_register")] + let auto_register = crate::impls::reflect_auto_registration(reflect_enum.meta()); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); quote! { @@ -90,6 +95,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream #function_impls + #auto_register + impl #impl_generics #bevy_reflect_path::Enum for #enum_path #ty_generics #where_reflect_clause { fn field(&self, #ref_name: &str) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> { match #match_this { diff --git a/crates/bevy_reflect/derive/src/impls/mod.rs b/crates/bevy_reflect/derive/src/impls/mod.rs index 6477c4041e3a3..48c8c84621d46 100644 --- a/crates/bevy_reflect/derive/src/impls/mod.rs +++ b/crates/bevy_reflect/derive/src/impls/mod.rs @@ -9,6 +9,8 @@ mod tuple_structs; mod typed; pub(crate) use assertions::impl_assertions; +#[cfg(feature = "auto_register")] +pub(crate) use common::reflect_auto_registration; pub(crate) use common::{common_partial_reflect_methods, impl_full_reflect}; pub(crate) use enums::impl_enum; #[cfg(feature = "functions")] diff --git a/crates/bevy_reflect/derive/src/impls/opaque.rs b/crates/bevy_reflect/derive/src/impls/opaque.rs index a39b0b4849e6b..f3f80b632b672 100644 --- a/crates/bevy_reflect/derive/src/impls/opaque.rs +++ b/crates/bevy_reflect/derive/src/impls/opaque.rs @@ -55,6 +55,11 @@ pub(crate) fn impl_opaque(meta: &ReflectMeta) -> proc_macro2::TokenStream { #[cfg(feature = "functions")] let function_impls = crate::impls::impl_function_traits(&where_clause_options); + #[cfg(not(feature = "auto_register"))] + let auto_register = None::; + #[cfg(feature = "auto_register")] + let auto_register = crate::impls::reflect_auto_registration(meta); + let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); let get_type_registration_impl = meta.get_type_registration(&where_clause_options); @@ -70,6 +75,8 @@ pub(crate) fn impl_opaque(meta: &ReflectMeta) -> proc_macro2::TokenStream { #function_impls + #auto_register + impl #impl_generics #bevy_reflect_path::PartialReflect for #type_path #ty_generics #where_reflect_clause { #[inline] fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> { diff --git a/crates/bevy_reflect/derive/src/impls/structs.rs b/crates/bevy_reflect/derive/src/impls/structs.rs index b78ce40a08091..730a9092a38a2 100644 --- a/crates/bevy_reflect/derive/src/impls/structs.rs +++ b/crates/bevy_reflect/derive/src/impls/structs.rs @@ -58,6 +58,11 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS .generics() .split_for_impl(); + #[cfg(not(feature = "auto_register"))] + let auto_register = None::; + #[cfg(feature = "auto_register")] + let auto_register = crate::impls::reflect_auto_registration(reflect_struct.meta()); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); quote! { @@ -71,6 +76,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS #function_impls + #auto_register + impl #impl_generics #bevy_reflect_path::Struct for #struct_path #ty_generics #where_reflect_clause { fn field(&self, name: &str) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> { match name { diff --git a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs index 01b6a46b7bae7..e9fb9bd009508 100644 --- a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs @@ -46,6 +46,11 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: .generics() .split_for_impl(); + #[cfg(not(feature = "auto_register"))] + let auto_register = None::; + #[cfg(feature = "auto_register")] + let auto_register = crate::impls::reflect_auto_registration(reflect_struct.meta()); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); quote! { @@ -59,6 +64,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: #function_impls + #auto_register + impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_path #ty_generics #where_reflect_clause { fn field(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> { match index { diff --git a/crates/bevy_reflect/derive/src/lib.rs b/crates/bevy_reflect/derive/src/lib.rs index 7ee7ad83e7605..c979c4db1f75d 100644 --- a/crates/bevy_reflect/derive/src/lib.rs +++ b/crates/bevy_reflect/derive/src/lib.rs @@ -40,6 +40,8 @@ mod trait_reflection; mod type_path; mod where_clause_options; +use std::{fs, io::Read, path::PathBuf}; + use crate::derive_data::{ReflectDerive, ReflectMeta, ReflectStruct}; use container_attributes::ContainerAttributes; use derive_data::{ReflectImplSource, ReflectProvenance, ReflectTraitToImpl, ReflectTypePath}; @@ -320,6 +322,12 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre /// #[reflect(@Required, @EditorTooltip::new("An ID is required!"))] /// struct Id(u8); /// ``` +/// ## `#[reflect(no_auto_register)]` +/// +/// This attribute will opt-out of the automatic reflect type registration. +/// +/// All non-generic types annotated with `#[derive(Reflect)]` are usually automatically registered on app startup. +/// If this behavior is not desired, this attribute may be used to disable it for the annotated type. /// /// # Field Attributes /// @@ -843,3 +851,53 @@ pub fn impl_type_path(input: TokenStream) -> TokenStream { }; }) } + +/// Collects and loads type registrations when using `auto_register_static` feature. +/// +/// Correctly using this macro requires following: +/// 1. This macro must be called **last** during compilation. This can be achieved by putting your main function +/// in a separate crate or restructuring your project to be separated into `bin` and `lib`, and putting this macro in `bin`. +/// Any automatic type registrations using `#[derive(Reflect)]` within the same crate as this macro are not guaranteed to run. +/// 2. Your project must be compiled with `auto_register_static` feature **and** `BEVY_REFLECT_AUTO_REGISTER_STATIC=1` env variable. +/// Enabling the feature generates registration functions while setting the variable enables export and +/// caching of registration function names. +/// 3. Must be called before creating `App` or using `TypeRegistry::register_derived_types`. +/// +/// If you're experiencing linking issues try running `cargo clean` before rebuilding. +#[proc_macro] +pub fn load_type_registrations(_input: TokenStream) -> TokenStream { + if !cfg!(feature = "auto_register_static") { + return TokenStream::new(); + } + + let Ok(dir) = fs::read_dir(PathBuf::from("target").join("bevy_reflect_type_registrations")) + else { + return TokenStream::new(); + }; + let mut str_buf = String::new(); + let mut registration_fns = Vec::new(); + for file_path in dir { + let mut file = fs::OpenOptions::new() + .read(true) + .open(file_path.unwrap().path()) + .unwrap(); + file.read_to_string(&mut str_buf).unwrap(); + registration_fns.extend(str_buf.lines().filter(|s| !s.is_empty()).map(|s| { + s.parse::() + .expect("Unexpected function name") + })); + str_buf.clear(); + } + let bevy_reflect_path = meta::get_bevy_reflect_path(); + TokenStream::from(quote! { + { + fn _register_types(){ + unsafe extern "Rust" { + #( safe fn #registration_fns(registry_ptr: &mut #bevy_reflect_path::TypeRegistry); )* + }; + #( #bevy_reflect_path::__macro_exports::auto_register::push_registration_fn(#registration_fns); )* + } + _register_types(); + } + }) +} diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index df9580b8201e9..55412a1e79fba 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -63,7 +63,7 @@ pub trait Array: PartialReflect { } /// Returns an iterator over the array. - fn iter(&self) -> ArrayIter; + fn iter(&self) -> ArrayIter<'_>; /// Drain the elements of this array to get a vector of owned values. fn drain(self: Box) -> Vec>; @@ -242,12 +242,12 @@ impl PartialReflect for DynamicArray { } #[inline] - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Array(self) } #[inline] - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Array(self) } @@ -294,7 +294,7 @@ impl Array for DynamicArray { } #[inline] - fn iter(&self) -> ArrayIter { + fn iter(&self) -> ArrayIter<'_> { ArrayIter::new(self) } @@ -351,7 +351,7 @@ pub struct ArrayIter<'a> { impl ArrayIter<'_> { /// Creates a new [`ArrayIter`]. #[inline] - pub const fn new(array: &dyn Array) -> ArrayIter { + pub const fn new(array: &dyn Array) -> ArrayIter<'_> { ArrayIter { array, index: 0 } } } diff --git a/crates/bevy_reflect/src/enums/dynamic_enum.rs b/crates/bevy_reflect/src/enums/dynamic_enum.rs index 2835306b22505..8f218a5f04bbc 100644 --- a/crates/bevy_reflect/src/enums/dynamic_enum.rs +++ b/crates/bevy_reflect/src/enums/dynamic_enum.rs @@ -216,10 +216,10 @@ impl Enum for DynamicEnum { } fn field_at(&self, index: usize) -> Option<&dyn PartialReflect> { - if let DynamicVariant::Tuple(data) = &self.variant { - data.field(index) - } else { - None + match &self.variant { + DynamicVariant::Tuple(data) => data.field(index), + DynamicVariant::Struct(data) => data.field_at(index), + DynamicVariant::Unit => None, } } @@ -232,10 +232,10 @@ impl Enum for DynamicEnum { } fn field_at_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> { - if let DynamicVariant::Tuple(data) = &mut self.variant { - data.field_mut(index) - } else { - None + match &mut self.variant { + DynamicVariant::Tuple(data) => data.field_mut(index), + DynamicVariant::Struct(data) => data.field_at_mut(index), + DynamicVariant::Unit => None, } } @@ -255,7 +255,7 @@ impl Enum for DynamicEnum { } } - fn iter_fields(&self) -> VariantFieldIter { + fn iter_fields(&self) -> VariantFieldIter<'_> { VariantFieldIter::new(self) } @@ -372,12 +372,12 @@ impl PartialReflect for DynamicEnum { } #[inline] - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Enum(self) } #[inline] - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Enum(self) } diff --git a/crates/bevy_reflect/src/enums/enum_trait.rs b/crates/bevy_reflect/src/enums/enum_trait.rs index 32e4b9612402f..a24fe65f6d97f 100644 --- a/crates/bevy_reflect/src/enums/enum_trait.rs +++ b/crates/bevy_reflect/src/enums/enum_trait.rs @@ -115,7 +115,7 @@ pub trait Enum: PartialReflect { /// For non-[`VariantType::Struct`] variants, this should return `None`. fn name_at(&self, index: usize) -> Option<&str>; /// Returns an iterator over the values of the current variant's fields. - fn iter_fields(&self) -> VariantFieldIter; + fn iter_fields(&self) -> VariantFieldIter<'_>; /// Returns the number of fields in the current variant. fn field_len(&self) -> usize; /// The name of the current variant. diff --git a/crates/bevy_reflect/src/func/args/from_arg.rs b/crates/bevy_reflect/src/func/args/from_arg.rs index c49d065169902..a79fe511cbf45 100644 --- a/crates/bevy_reflect/src/func/args/from_arg.rs +++ b/crates/bevy_reflect/src/func/args/from_arg.rs @@ -29,13 +29,13 @@ pub trait FromArg { /// Creates an item from an argument. /// /// The argument must be of the expected type and ownership. - fn from_arg(arg: Arg) -> Result, ArgError>; + fn from_arg(arg: Arg<'_>) -> Result, ArgError>; } // Blanket impl. impl FromArg for &'static T { type This<'a> = &'a T; - fn from_arg(arg: Arg) -> Result, ArgError> { + fn from_arg(arg: Arg<'_>) -> Result, ArgError> { arg.take_ref() } } @@ -43,7 +43,7 @@ impl FromArg for &'static T { // Blanket impl. impl FromArg for &'static mut T { type This<'a> = &'a mut T; - fn from_arg(arg: Arg) -> Result, ArgError> { + fn from_arg(arg: Arg<'_>) -> Result, ArgError> { arg.take_mut() } } @@ -77,7 +77,7 @@ macro_rules! impl_from_arg { )? { type This<'from_arg> = $ty; - fn from_arg(arg: $crate::func::args::Arg) -> + fn from_arg(arg: $crate::func::args::Arg<'_>) -> Result, $crate::func::args::ArgError> { arg.take_owned() diff --git a/crates/bevy_reflect/src/func/dynamic_function.rs b/crates/bevy_reflect/src/func/dynamic_function.rs index ab1d70e4ed47a..d9f881af33626 100644 --- a/crates/bevy_reflect/src/func/dynamic_function.rs +++ b/crates/bevy_reflect/src/func/dynamic_function.rs @@ -409,11 +409,11 @@ impl PartialReflect for DynamicFunction<'static> { ReflectKind::Function } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Function(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Function(self) } diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 2f5f82fbf5499..f3675ca185f7a 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -177,7 +177,7 @@ impl FunctionInfo { /// let pretty = info.pretty_printer(); /// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32"); /// ``` - pub fn pretty_printer(&self) -> PrettyPrintFunctionInfo { + pub fn pretty_printer(&self) -> PrettyPrintFunctionInfo<'_> { PrettyPrintFunctionInfo::new(self) } diff --git a/crates/bevy_reflect/src/impls/alloc/borrow.rs b/crates/bevy_reflect/src/impls/alloc/borrow.rs index 9021343c67b75..8326ff9728922 100644 --- a/crates/bevy_reflect/src/impls/alloc/borrow.rs +++ b/crates/bevy_reflect/src/impls/alloc/borrow.rs @@ -55,11 +55,11 @@ impl PartialReflect for Cow<'static, str> { ReflectKind::Opaque } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Opaque(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Opaque(self) } @@ -180,7 +180,7 @@ impl List self.as_ref().len() } - fn iter(&self) -> ListIter { + fn iter(&self) -> ListIter<'_> { ListIter::new(self) } @@ -228,11 +228,11 @@ impl Parti ReflectKind::List } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::List(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::List(self) } diff --git a/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs b/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs index d5559a2985abd..ffa6ee34225a3 100644 --- a/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs +++ b/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs @@ -128,11 +128,11 @@ where ReflectKind::Map } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Map(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Map(self) } diff --git a/crates/bevy_reflect/src/impls/core/panic.rs b/crates/bevy_reflect/src/impls/core/panic.rs index 3d1cebe53e536..8bc583c64ba78 100644 --- a/crates/bevy_reflect/src/impls/core/panic.rs +++ b/crates/bevy_reflect/src/impls/core/panic.rs @@ -57,11 +57,11 @@ impl PartialReflect for &'static Location<'static> { ReflectKind::Opaque } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Opaque(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Opaque(self) } diff --git a/crates/bevy_reflect/src/impls/core/primitives.rs b/crates/bevy_reflect/src/impls/core/primitives.rs index 75825598233bc..8d53193ac6d03 100644 --- a/crates/bevy_reflect/src/impls/core/primitives.rs +++ b/crates/bevy_reflect/src/impls/core/primitives.rs @@ -191,11 +191,11 @@ impl PartialReflect for &'static str { Some(self) } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Opaque(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Opaque(self) } @@ -310,7 +310,7 @@ impl A } #[inline] - fn iter(&self) -> ArrayIter { + fn iter(&self) -> ArrayIter<'_> { ArrayIter::new(self) } @@ -360,12 +360,12 @@ impl P } #[inline] - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Array(self) } #[inline] - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Array(self) } diff --git a/crates/bevy_reflect/src/impls/core/sync.rs b/crates/bevy_reflect/src/impls/core/sync.rs index 06b930e8a9b92..5cd46e7dc431d 100644 --- a/crates/bevy_reflect/src/impls/core/sync.rs +++ b/crates/bevy_reflect/src/impls/core/sync.rs @@ -102,11 +102,11 @@ macro_rules! impl_reflect_for_atomic { ReflectKind::Opaque } #[inline] - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Opaque(self) } #[inline] - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Opaque(self) } #[inline] diff --git a/crates/bevy_reflect/src/impls/macros/list.rs b/crates/bevy_reflect/src/impls/macros/list.rs index 81a27047cb687..16fd6661348e6 100644 --- a/crates/bevy_reflect/src/impls/macros/list.rs +++ b/crates/bevy_reflect/src/impls/macros/list.rs @@ -48,7 +48,7 @@ macro_rules! impl_reflect_for_veclike { } #[inline] - fn iter(&self) -> $crate::list::ListIter { + fn iter(&self) -> $crate::list::ListIter<'_> { $crate::list::ListIter::new(self) } @@ -98,11 +98,11 @@ macro_rules! impl_reflect_for_veclike { $crate::kind::ReflectKind::List } - fn reflect_ref(&self) -> $crate::kind::ReflectRef { + fn reflect_ref(&self) -> $crate::kind::ReflectRef<'_> { $crate::kind::ReflectRef::List(self) } - fn reflect_mut(&mut self) -> $crate::kind::ReflectMut { + fn reflect_mut(&mut self) -> $crate::kind::ReflectMut<'_> { $crate::kind::ReflectMut::List(self) } diff --git a/crates/bevy_reflect/src/impls/macros/map.rs b/crates/bevy_reflect/src/impls/macros/map.rs index e87bb314b5a18..5ac779f3a747d 100644 --- a/crates/bevy_reflect/src/impls/macros/map.rs +++ b/crates/bevy_reflect/src/impls/macros/map.rs @@ -131,11 +131,11 @@ macro_rules! impl_reflect_for_hashmap { $crate::kind::ReflectKind::Map } - fn reflect_ref(&self) -> $crate::kind::ReflectRef { + fn reflect_ref(&self) -> $crate::kind::ReflectRef<'_> { $crate::kind::ReflectRef::Map(self) } - fn reflect_mut(&mut self) -> $crate::kind::ReflectMut { + fn reflect_mut(&mut self) -> $crate::kind::ReflectMut<'_> { $crate::kind::ReflectMut::Map(self) } diff --git a/crates/bevy_reflect/src/impls/macros/set.rs b/crates/bevy_reflect/src/impls/macros/set.rs index 844b904cdec4c..69506a88e4549 100644 --- a/crates/bevy_reflect/src/impls/macros/set.rs +++ b/crates/bevy_reflect/src/impls/macros/set.rs @@ -114,11 +114,11 @@ macro_rules! impl_reflect_for_hashset { $crate::kind::ReflectKind::Set } - fn reflect_ref(&self) -> $crate::kind::ReflectRef { + fn reflect_ref(&self) -> $crate::kind::ReflectRef<'_> { $crate::kind::ReflectRef::Set(self) } - fn reflect_mut(&mut self) -> $crate::kind::ReflectMut { + fn reflect_mut(&mut self) -> $crate::kind::ReflectMut<'_> { $crate::kind::ReflectMut::Set(self) } diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index 86b7284381c39..21e5240683178 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -67,7 +67,7 @@ where >::len(self) } - fn iter(&self) -> ListIter { + fn iter(&self) -> ListIter<'_> { ListIter::new(self) } @@ -123,11 +123,11 @@ where ReflectKind::List } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::List(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::List(self) } diff --git a/crates/bevy_reflect/src/impls/std/path.rs b/crates/bevy_reflect/src/impls/std/path.rs index a669068ae3aa4..df955d896855c 100644 --- a/crates/bevy_reflect/src/impls/std/path.rs +++ b/crates/bevy_reflect/src/impls/std/path.rs @@ -63,11 +63,11 @@ impl PartialReflect for &'static Path { ReflectKind::Opaque } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Opaque(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Opaque(self) } @@ -194,11 +194,11 @@ impl PartialReflect for Cow<'static, Path> { ReflectKind::Opaque } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Opaque(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Opaque(self) } diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 99a0a1f7b5606..4e2592daaeb69 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -463,13 +463,6 @@ //! typically require manual monomorphization (i.e. manually specifying the types the generic method can //! take). //! -//! ## Manual Registration -//! -//! Since Rust doesn't provide built-in support for running initialization code before `main`, -//! there is no way for `bevy_reflect` to automatically register types into the [type registry]. -//! This means types must manually be registered, including their desired monomorphized -//! representations if generic. -//! //! # Features //! //! ## `bevy` @@ -519,6 +512,24 @@ //! which enables capturing the type stack when serializing or deserializing a type //! and displaying it in error messages. //! +//! ## `auto_register_inventory`/`auto_register_static` +//! +//! | Default | Dependencies | +//! | :-----: | :-------------------------------: | +//! | ✅ | [`bevy_reflect_derive/auto_register_inventory`] | +//! | ❌ | [`bevy_reflect_derive/auto_register_static`] | +//! +//! These features enable automatic registration of types that derive [`Reflect`]. +//! +//! - `auto_register_inventory` uses `inventory` to collect types on supported platforms (Linux, macOS, iOS, FreeBSD, Android, Windows, WebAssembly). +//! - `auto_register_static` uses platform-independent way to collect types, but requires additional setup and might +//! slow down compilation, so it should only be used on platforms not supported by `inventory`. +//! See documentation for [`load_type_registrations`] macro for more info +//! +//! When this feature is enabled `bevy_reflect` will automatically collects all types that derive [`Reflect`] on app startup, +//! and [`TypeRegistry::register_derived_types`] can be used to register these types at any point in the program. +//! However, this does not apply to types with generics: their desired monomorphized representations must be registered manually. +//! //! [Reflection]: https://en.wikipedia.org/wiki/Reflective_programming //! [Bevy]: https://bevy.org/ //! [limitations]: #limitations @@ -723,6 +734,91 @@ pub mod __macro_exports { impl RegisterForReflection for DynamicArray {} impl RegisterForReflection for DynamicTuple {} + + /// Automatic reflect registration implementation + #[cfg(feature = "auto_register")] + pub mod auto_register { + pub use super::*; + + /// inventory impl + #[cfg(all( + not(feature = "auto_register_static"), + feature = "auto_register_inventory" + ))] + mod __automatic_type_registration_impl { + use super::*; + + pub use inventory; + + /// Stores type registration functions + pub struct AutomaticReflectRegistrations(pub fn(&mut TypeRegistry)); + + /// Registers all collected types. + pub fn register_types(registry: &mut TypeRegistry) { + #[cfg(target_family = "wasm")] + wasm_support::init(); + for registration_fn in inventory::iter:: { + registration_fn.0(registry); + } + } + + inventory::collect!(AutomaticReflectRegistrations); + + #[cfg(target_family = "wasm")] + mod wasm_support { + use bevy_platform::sync::atomic::{AtomicBool, Ordering}; + + static INIT_DONE: AtomicBool = AtomicBool::new(false); + + #[expect(unsafe_code, reason = "This function is generated by linker.")] + unsafe extern "C" { + fn __wasm_call_ctors(); + } + + /// This function must be called before using [`inventory::iter`] on [`AutomaticReflectRegistrations`] to run constructors on all platforms. + pub fn init() { + if INIT_DONE.swap(true, Ordering::Relaxed) { + return; + }; + // SAFETY: + // This will call constructors on wasm platforms at most once (as long as `init` is the only function that calls `__wasm_call_ctors`). + // + // For more information see: https://docs.rs/inventory/latest/inventory/#webassembly-and-constructors + #[expect( + unsafe_code, + reason = "This function must be called to use inventory on wasm." + )] + unsafe { + __wasm_call_ctors(); + } + } + } + } + + /// static impl + #[cfg(feature = "auto_register_static")] + mod __automatic_type_registration_impl { + use super::*; + use alloc::vec::Vec; + use bevy_platform::sync::Mutex; + + static REGISTRATION_FNS: Mutex> = Mutex::new(Vec::new()); + + /// Adds adds a new registration function for [`TypeRegistry`] + pub fn push_registration_fn(registration_fn: fn(&mut TypeRegistry)) { + REGISTRATION_FNS.lock().unwrap().push(registration_fn); + } + + /// Registers all collected types. + pub fn register_types(registry: &mut TypeRegistry) { + for func in REGISTRATION_FNS.lock().unwrap().iter() { + (func)(registry); + } + } + } + + pub use __automatic_type_registration_impl::*; + } } #[cfg(test)] @@ -2615,11 +2711,9 @@ bevy_reflect::tests::Test { #[reflect(where T: Default)] struct Foo(String, #[reflect(ignore)] PhantomData); - #[expect(dead_code, reason = "Bar is never constructed")] #[derive(Default, TypePath)] struct Bar; - #[expect(dead_code, reason = "Baz is never constructed")] #[derive(TypePath)] struct Baz; @@ -2633,7 +2727,6 @@ bevy_reflect::tests::Test { #[reflect(where)] struct Foo(String, #[reflect(ignore)] PhantomData); - #[expect(dead_code, reason = "Bar is never constructed")] #[derive(TypePath)] struct Bar; @@ -2668,7 +2761,6 @@ bevy_reflect::tests::Test { #[reflect(where T::Assoc: core::fmt::Display)] struct Foo(T::Assoc); - #[expect(dead_code, reason = "Bar is never constructed")] #[derive(TypePath)] struct Bar; @@ -2676,7 +2768,6 @@ bevy_reflect::tests::Test { type Assoc = usize; } - #[expect(dead_code, reason = "Baz is never constructed")] #[derive(TypePath)] struct Baz; @@ -3369,6 +3460,76 @@ bevy_reflect::tests::Test { ); } + #[cfg(feature = "auto_register")] + mod auto_register_reflect { + use super::*; + + #[test] + fn should_ignore_auto_reflect_registration() { + #[derive(Reflect)] + #[reflect(no_auto_register)] + struct NoAutomaticStruct { + a: usize, + } + + let mut registry = TypeRegistry::default(); + registry.register_derived_types(); + + assert!(!registry.contains(TypeId::of::())); + } + + #[test] + fn should_auto_register_reflect_for_all_supported_types() { + // Struct + #[derive(Reflect)] + struct StructReflect { + a: usize, + } + + // ZST struct + #[derive(Reflect)] + struct ZSTStructReflect; + + // Tuple struct + #[derive(Reflect)] + struct TupleStructReflect(pub u32); + + // Enum + #[derive(Reflect)] + enum EnumReflect { + A, + B, + } + + // ZST enum + #[derive(Reflect)] + enum ZSTEnumReflect {} + + // Opaque struct + #[derive(Reflect, Clone)] + #[reflect(opaque)] + struct OpaqueStructReflect { + _a: usize, + } + + // ZST opaque struct + #[derive(Reflect, Clone)] + #[reflect(opaque)] + struct ZSTOpaqueStructReflect; + + let mut registry = TypeRegistry::default(); + registry.register_derived_types(); + + assert!(registry.contains(TypeId::of::())); + assert!(registry.contains(TypeId::of::())); + assert!(registry.contains(TypeId::of::())); + assert!(registry.contains(TypeId::of::())); + assert!(registry.contains(TypeId::of::())); + assert!(registry.contains(TypeId::of::())); + assert!(registry.contains(TypeId::of::())); + } + } + #[cfg(feature = "glam")] mod glam { use super::*; diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 4ecdb632755d8..783eca12976d2 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -95,7 +95,7 @@ pub trait List: PartialReflect { } /// Returns an iterator over the list. - fn iter(&self) -> ListIter; + fn iter(&self) -> ListIter<'_>; /// Drain the elements of this list to get a vector of owned values. /// @@ -238,7 +238,7 @@ impl List for DynamicList { self.values.len() } - fn iter(&self) -> ListIter { + fn iter(&self) -> ListIter<'_> { ListIter::new(self) } @@ -294,12 +294,12 @@ impl PartialReflect for DynamicList { } #[inline] - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::List(self) } #[inline] - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::List(self) } @@ -382,7 +382,7 @@ pub struct ListIter<'a> { impl ListIter<'_> { /// Creates a new [`ListIter`]. #[inline] - pub const fn new(list: &dyn List) -> ListIter { + pub const fn new(list: &dyn List) -> ListIter<'_> { ListIter { list, index: 0 } } } diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index e7178692023e2..c2264ecf2d3d0 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -375,11 +375,11 @@ impl PartialReflect for DynamicMap { ReflectKind::Map } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Map(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Map(self) } diff --git a/crates/bevy_reflect/src/path/error.rs b/crates/bevy_reflect/src/path/error.rs index 00188a4cc37b0..3287333b89f31 100644 --- a/crates/bevy_reflect/src/path/error.rs +++ b/crates/bevy_reflect/src/path/error.rs @@ -64,7 +64,7 @@ impl<'a> AccessError<'a> { } /// The returns the [`Access`] that this [`AccessError`] occurred in. - pub const fn access(&self) -> &Access { + pub const fn access(&self) -> &Access<'_> { &self.access } diff --git a/crates/bevy_reflect/src/path/mod.rs b/crates/bevy_reflect/src/path/mod.rs index f0434686eef4f..fad18746c27b5 100644 --- a/crates/bevy_reflect/src/path/mod.rs +++ b/crates/bevy_reflect/src/path/mod.rs @@ -414,7 +414,7 @@ impl ParsedPath { /// /// assert_eq!(parsed_path.element::(&foo).unwrap(), &123); /// ``` - pub fn parse(string: &str) -> PathResult { + pub fn parse(string: &str) -> PathResult<'_, Self> { let mut parts = Vec::new(); for (access, offset) in PathParser::new(string) { parts.push(OffsetAccess { diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index ffe9be54fef1f..1c86d8d4d2841 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -226,12 +226,12 @@ where /// Returns an immutable enumeration of "kinds" of type. /// /// See [`ReflectRef`]. - fn reflect_ref(&self) -> ReflectRef; + fn reflect_ref(&self) -> ReflectRef<'_>; /// Returns a mutable enumeration of "kinds" of type. /// /// See [`ReflectMut`]. - fn reflect_mut(&mut self) -> ReflectMut; + fn reflect_mut(&mut self) -> ReflectMut<'_>; /// Returns an owned enumeration of "kinds" of type. /// diff --git a/crates/bevy_reflect/src/set.rs b/crates/bevy_reflect/src/set.rs index e464ee4aab699..a49ee03cd86bb 100644 --- a/crates/bevy_reflect/src/set.rs +++ b/crates/bevy_reflect/src/set.rs @@ -301,11 +301,11 @@ impl PartialReflect for DynamicSet { ReflectKind::Set } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Set(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Set(self) } diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index e419947b3ab5a..46c7a546fab42 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -69,7 +69,7 @@ pub trait Struct: PartialReflect { fn field_len(&self) -> usize; /// Returns an iterator over the values of the reflectable fields for this struct. - fn iter_fields(&self) -> FieldIter; + fn iter_fields(&self) -> FieldIter<'_>; /// Creates a new [`DynamicStruct`] from this struct. fn to_dynamic_struct(&self) -> DynamicStruct { @@ -371,7 +371,7 @@ impl Struct for DynamicStruct { } #[inline] - fn iter_fields(&self) -> FieldIter { + fn iter_fields(&self) -> FieldIter<'_> { FieldIter { struct_val: self, index: 0, @@ -429,12 +429,12 @@ impl PartialReflect for DynamicStruct { } #[inline] - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Struct(self) } #[inline] - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Struct(self) } diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 97da69b5e2c73..fce437292bea9 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -50,7 +50,7 @@ pub trait Tuple: PartialReflect { fn field_len(&self) -> usize; /// Returns an iterator over the values of the tuple's fields. - fn iter_fields(&self) -> TupleFieldIter; + fn iter_fields(&self) -> TupleFieldIter<'_>; /// Drain the fields of this tuple to get a vector of owned values. fn drain(self: Box) -> Vec>; @@ -264,7 +264,7 @@ impl Tuple for DynamicTuple { } #[inline] - fn iter_fields(&self) -> TupleFieldIter { + fn iter_fields(&self) -> TupleFieldIter<'_> { TupleFieldIter { tuple: self, index: 0, @@ -318,12 +318,12 @@ impl PartialReflect for DynamicTuple { } #[inline] - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Tuple(self) } #[inline] - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::Tuple(self) } @@ -493,7 +493,7 @@ macro_rules! impl_reflect_tuple { } #[inline] - fn iter_fields(&self) -> TupleFieldIter { + fn iter_fields(&self) -> TupleFieldIter<'_> { TupleFieldIter { tuple: self, index: 0, @@ -542,11 +542,11 @@ macro_rules! impl_reflect_tuple { ReflectKind::Tuple } - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef <'_> { ReflectRef::Tuple(self) } - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut <'_> { ReflectMut::Tuple(self) } diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index cceab9904e91a..f2eb4ae3adef2 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -53,7 +53,7 @@ pub trait TupleStruct: PartialReflect { fn field_len(&self) -> usize; /// Returns an iterator over the values of the tuple struct's fields. - fn iter_fields(&self) -> TupleStructFieldIter; + fn iter_fields(&self) -> TupleStructFieldIter<'_>; /// Creates a new [`DynamicTupleStruct`] from this tuple struct. fn to_dynamic_tuple_struct(&self) -> DynamicTupleStruct { @@ -278,7 +278,7 @@ impl TupleStruct for DynamicTupleStruct { } #[inline] - fn iter_fields(&self) -> TupleStructFieldIter { + fn iter_fields(&self) -> TupleStructFieldIter<'_> { TupleStructFieldIter { tuple_struct: self, index: 0, @@ -337,12 +337,12 @@ impl PartialReflect for DynamicTupleStruct { } #[inline] - fn reflect_ref(&self) -> ReflectRef { + fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::TupleStruct(self) } #[inline] - fn reflect_mut(&mut self) -> ReflectMut { + fn reflect_mut(&mut self) -> ReflectMut<'_> { ReflectMut::TupleStruct(self) } diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index 122ace029364d..0a2db8d57a9ed 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -69,8 +69,8 @@ use thiserror::Error; /// # fn try_as_reflect(&self) -> Option<&dyn Reflect> { todo!() } /// # fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { todo!() } /// # fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { todo!() } -/// # fn reflect_ref(&self) -> ReflectRef { todo!() } -/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() } +/// # fn reflect_ref(&self) -> ReflectRef<'_> { todo!() } +/// # fn reflect_mut(&mut self) -> ReflectMut<'_> { todo!() } /// # fn reflect_owned(self: Box) -> ReflectOwned { todo!() } /// # } /// # impl Reflect for MyStruct { diff --git a/crates/bevy_reflect/src/type_info_stack.rs b/crates/bevy_reflect/src/type_info_stack.rs index cdc19244de295..940bf4b6eafe7 100644 --- a/crates/bevy_reflect/src/type_info_stack.rs +++ b/crates/bevy_reflect/src/type_info_stack.rs @@ -30,7 +30,7 @@ impl TypeInfoStack { } /// Get an iterator over the stack in the order they were pushed. - pub fn iter(&self) -> Iter<&'static TypeInfo> { + pub fn iter(&self) -> Iter<'_, &'static TypeInfo> { self.stack.iter() } } diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index a20074b8274dd..d534cbff889b5 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -120,6 +120,44 @@ impl TypeRegistry { registry } + /// Register all non-generic types annotated with `#[derive(Reflect)]`. + /// + /// Calling this method is equivalent to calling [`register`](Self::register) on all types without generic parameters + /// that derived [`Reflect`] trait. + /// + /// This method is supported on Linux, macOS, Windows, iOS, Android, and Web via the `inventory` crate. + /// It does nothing on platforms not supported by either of those crates. + /// + /// # Example + /// + /// ``` + /// # use std::any::TypeId; + /// # use bevy_reflect::{Reflect, TypeRegistry, std_traits::ReflectDefault}; + /// #[derive(Reflect, Default)] + /// #[reflect(Default)] + /// struct Foo { + /// name: Option, + /// value: i32 + /// } + /// + /// let mut type_registry = TypeRegistry::empty(); + /// type_registry.register_derived_types(); + /// + /// // The main type + /// assert!(type_registry.contains(TypeId::of::())); + /// + /// // Its type dependencies + /// assert!(type_registry.contains(TypeId::of::>())); + /// assert!(type_registry.contains(TypeId::of::())); + /// + /// // Its type data + /// assert!(type_registry.get_type_data::(TypeId::of::()).is_some()); + /// ``` + #[cfg(feature = "auto_register")] + pub fn register_derived_types(&mut self) { + crate::__macro_exports::auto_register::register_types(self); + } + /// Attempts to register the type `T` if it has not yet been registered already. /// /// This will also recursively register any type dependencies as specified by [`GetTypeRegistration::register_type_dependencies`]. diff --git a/crates/bevy_reflect/src/utility.rs b/crates/bevy_reflect/src/utility.rs index db8416bd6cbe1..cb0bf0f097121 100644 --- a/crates/bevy_reflect/src/utility.rs +++ b/crates/bevy_reflect/src/utility.rs @@ -86,8 +86,8 @@ mod sealed { /// # fn try_as_reflect(&self) -> Option<&dyn Reflect> { todo!() } /// # fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { todo!() } /// # fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { todo!() } -/// # fn reflect_ref(&self) -> ReflectRef { todo!() } -/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() } +/// # fn reflect_ref(&self) -> ReflectRef<'_> { todo!() } +/// # fn reflect_mut(&mut self) -> ReflectMut<'_> { todo!() } /// # fn reflect_owned(self: Box) -> ReflectOwned { todo!() } /// # } /// # impl Reflect for Foo { @@ -173,8 +173,8 @@ impl Default for NonGenericTypeCell { /// # fn try_as_reflect(&self) -> Option<&dyn Reflect> { todo!() } /// # fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { todo!() } /// # fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { todo!() } -/// # fn reflect_ref(&self) -> ReflectRef { todo!() } -/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() } +/// # fn reflect_ref(&self) -> ReflectRef<'_> { todo!() } +/// # fn reflect_mut(&mut self) -> ReflectMut<'_> { todo!() } /// # fn reflect_owned(self: Box) -> ReflectOwned { todo!() } /// # } /// # impl Reflect for Foo { diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index a959b69d7be36..42ae39887497b 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -590,11 +590,11 @@ pub fn process_remote_get_components_watching_request( continue; }; - if let Some(ticks) = entity_ref.get_change_ticks_by_id(component_id) { - if ticks.is_changed(world.last_change_tick(), world.read_change_tick()) { - changed.push(component_path); - continue; - } + if let Some(ticks) = entity_ref.get_change_ticks_by_id(component_id) + && ticks.is_changed(world.last_change_tick(), world.read_change_tick()) + { + changed.push(component_path); + continue; }; let Some(events) = world.removed_components().get(component_id) else { @@ -914,10 +914,10 @@ fn serialize_components( }; if let Some(reflect_component) = type_registration.data::() { // If a component_id is provided, check if the entity has it - if let Some(component_id) = component_id_opt { - if !entity_ref.contains_id(component_id) { - continue; - } + if let Some(component_id) = component_id_opt + && !entity_ref.contains_id(component_id) + { + continue; } if let Some(reflected) = reflect_component.reflect(entity_ref) { let reflect_serializer = diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 6c19e48296284..81667276ed303 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -86,12 +86,11 @@ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-fea image = { version = "0.25.2", default-features = false } # misc -codespan-reporting = "0.12.0" # `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 = "25", default-features = false, features = [ +wgpu = { version = "26", default-features = false, features = [ "wgsl", "dx12", "metal", @@ -100,13 +99,13 @@ wgpu = { version = "25", default-features = false, features = [ "naga-ir", "fragile-send-sync-non-atomic-wasm", ] } -naga = { version = "25", features = ["wgsl-in"] } +naga = { version = "26", 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"] } futures-lite = "2.0.1" -encase = { version = "0.10", features = ["glam"] } +encase = { version = "0.11", features = ["glam"] } # For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans. profiling = { version = "1", features = [ "profile-with-tracing", @@ -144,9 +143,6 @@ wasm-bindgen = "0.2" bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [ "web", ] } -bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false, features = [ - "web", -] } bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ "web", ] } diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index b426088e22794..0fc325136c166 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -154,17 +154,18 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { // Read struct-level attributes, second pass. for attr in &ast.attrs { - if let Some(attr_ident) = attr.path().get_ident() { - if attr_ident == UNIFORM_ATTRIBUTE_NAME || attr_ident == DATA_ATTRIBUTE_NAME { - let UniformBindingAttr { - binding_type, - binding_index, - converted_shader_type, - binding_array: binding_array_binding, - } = get_uniform_binding_attr(attr)?; - match binding_type { - UniformBindingAttrType::Uniform => { - binding_impls.push(quote! {{ + if let Some(attr_ident) = attr.path().get_ident() + && (attr_ident == UNIFORM_ATTRIBUTE_NAME || attr_ident == DATA_ATTRIBUTE_NAME) + { + let UniformBindingAttr { + binding_type, + binding_index, + converted_shader_type, + binding_array: binding_array_binding, + } = get_uniform_binding_attr(attr)?; + match binding_type { + UniformBindingAttrType::Uniform => { + binding_impls.push(quote! {{ use #render_path::render_resource::AsBindGroupShaderType; let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); let converted: #converted_shader_type = self.as_bind_group_shader_type(&images); @@ -181,26 +182,26 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ) }}); - match (&binding_array_binding, &attr_bindless_count) { - (&None, &Some(_)) => { - return Err(Error::new_spanned( - attr, - "Must specify `binding_array(...)` with `#[uniform]` if the \ + match (&binding_array_binding, &attr_bindless_count) { + (&None, &Some(_)) => { + return Err(Error::new_spanned( + attr, + "Must specify `binding_array(...)` with `#[uniform]` if the \ object is bindless", - )); - } - (&Some(_), &None) => { - return Err(Error::new_spanned( - attr, - "`binding_array(...)` with `#[uniform]` requires the object to \ + )); + } + (&Some(_), &None) => { + return Err(Error::new_spanned( + attr, + "`binding_array(...)` with `#[uniform]` requires the object to \ be bindless", - )); - } - _ => {} + )); } + _ => {} + } - let binding_array_binding = binding_array_binding.unwrap_or(0); - bindless_binding_layouts.push(quote! { + let binding_array_binding = binding_array_binding.unwrap_or(0); + bindless_binding_layouts.push(quote! { #bind_group_layout_entries.push( #render_path::render_resource::BindGroupLayoutEntry { binding: #binding_array_binding, @@ -215,16 +216,16 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ); }); - add_bindless_resource_type( - &render_path, - &mut bindless_resource_types, - binding_index, - quote! { #render_path::render_resource::BindlessResourceType::Buffer }, - ); - } + add_bindless_resource_type( + &render_path, + &mut bindless_resource_types, + binding_index, + quote! { #render_path::render_resource::BindlessResourceType::Buffer }, + ); + } - UniformBindingAttrType::Data => { - binding_impls.push(quote! {{ + UniformBindingAttrType::Data => { + binding_impls.push(quote! {{ use #render_path::render_resource::AsBindGroupShaderType; use #render_path::render_resource::encase::{ShaderType, internal::WriteInto}; let mut buffer: Vec = Vec::new(); @@ -248,8 +249,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ) }}); - let binding_array_binding = binding_array_binding.unwrap_or(0); - bindless_binding_layouts.push(quote! { + let binding_array_binding = binding_array_binding.unwrap_or(0); + bindless_binding_layouts.push(quote! { #bind_group_layout_entries.push( #render_path::render_resource::BindGroupLayoutEntry { binding: #binding_array_binding, @@ -264,18 +265,18 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ); }); - add_bindless_resource_type( - &render_path, - &mut bindless_resource_types, - binding_index, - quote! { #render_path::render_resource::BindlessResourceType::DataBuffer }, - ); - } + add_bindless_resource_type( + &render_path, + &mut bindless_resource_types, + binding_index, + quote! { #render_path::render_resource::BindlessResourceType::DataBuffer }, + ); } + } - // Push the non-bindless binding layout. + // Push the non-bindless binding layout. - non_bindless_binding_layouts.push(quote!{ + non_bindless_binding_layouts.push(quote!{ #bind_group_layout_entries.push( #render_path::render_resource::BindGroupLayoutEntry { binding: #binding_index, @@ -290,33 +291,32 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ); }); - bindless_buffer_descriptors.push(quote! { - #render_path::render_resource::BindlessBufferDescriptor { - // Note that, because this is bindless, *binding - // index* here refers to the index in the - // bindless index table (`bindless_index`), and - // the actual binding number is the *binding - // array binding*. - binding_number: #render_path::render_resource::BindingNumber( - #binding_array_binding - ), - bindless_index: - #render_path::render_resource::BindlessIndex(#binding_index), - size: Some( - < - #converted_shader_type as - #render_path::render_resource::ShaderType - >::min_size().get() as usize - ), - } - }); - - let required_len = binding_index as usize + 1; - if required_len > binding_states.len() { - binding_states.resize(required_len, BindingState::Free); + bindless_buffer_descriptors.push(quote! { + #render_path::render_resource::BindlessBufferDescriptor { + // Note that, because this is bindless, *binding + // index* here refers to the index in the + // bindless index table (`bindless_index`), and + // the actual binding number is the *binding + // array binding*. + binding_number: #render_path::render_resource::BindingNumber( + #binding_array_binding + ), + bindless_index: + #render_path::render_resource::BindlessIndex(#binding_index), + size: Some( + < + #converted_shader_type as + #render_path::render_resource::ShaderType + >::min_size().get() as usize + ), } - binding_states[binding_index as usize] = BindingState::OccupiedConvertedUniform; + }); + + let required_len = binding_index as usize + 1; + if required_len > binding_states.len() { + binding_states.resize(required_len, BindingState::Free); } + binding_states[binding_index as usize] = BindingState::OccupiedConvertedUniform; } } @@ -1390,13 +1390,13 @@ fn get_visibility_flag_value(meta_list: &MetaList) -> Result), @@ -87,7 +85,6 @@ struct FieldInfo { ty: Type, member: Member, key: Key, - use_base_descriptor: bool, } impl FieldInfo { @@ -117,15 +114,6 @@ impl FieldInfo { parse_quote!(#ty: #specialize_path::Specializer<#target_path>) } } - - fn get_base_descriptor_predicate( - &self, - specialize_path: &Path, - target_path: &Path, - ) -> WherePredicate { - let ty = &self.ty; - parse_quote!(#ty: #specialize_path::GetBaseDescriptor<#target_path>) - } } fn get_field_info( @@ -151,12 +139,8 @@ fn get_field_info( let mut use_key_field = true; let mut key = Key::Index(key_index); - let mut use_base_descriptor = false; for attr in &field.attrs { match &attr.meta { - Meta::Path(path) if path.is_ident(&BASE_DESCRIPTOR_ATTR_IDENT) => { - use_base_descriptor = true; - } Meta::List(MetaList { path, tokens, .. }) if path.is_ident(&KEY_ATTR_IDENT) => { let owned_tokens = tokens.clone().into(); let Ok(parsed_key) = syn::parse::(owned_tokens) else { @@ -190,7 +174,6 @@ fn get_field_info( ty: field_ty, member: field_member, key, - use_base_descriptor, }); } @@ -206,10 +189,10 @@ fn get_specialize_targets( derive_name: &str, ) -> syn::Result { let specialize_attr = ast.attrs.iter().find_map(|attr| { - if attr.path().is_ident(SPECIALIZE_ATTR_IDENT) { - if let Meta::List(meta_list) = &attr.meta { - return Some(meta_list); - } + if attr.path().is_ident(SPECIALIZE_ATTR_IDENT) + && let Meta::List(meta_list) = &attr.meta + { + return Some(meta_list); } None }); @@ -261,41 +244,18 @@ pub fn impl_specializer(input: TokenStream) -> TokenStream { }) .collect(); - let base_descriptor_fields = field_info - .iter() - .filter(|field| field.use_base_descriptor) - .collect::>(); - - if base_descriptor_fields.len() > 1 { - return syn::Error::new( - Span::call_site(), - "Too many #[base_descriptor] attributes found. It must be present on exactly one field", - ) - .into_compile_error() - .into(); - } - - let base_descriptor_field = base_descriptor_fields.first().copied(); - match targets { - SpecializeImplTargets::All => { - let specialize_impl = impl_specialize_all( - &specialize_path, - &ecs_path, - &ast, - &field_info, - &key_patterns, - &key_tuple_idents, - ); - let get_base_descriptor_impl = base_descriptor_field - .map(|field_info| impl_get_base_descriptor_all(&specialize_path, &ast, field_info)) - .unwrap_or_default(); - [specialize_impl, get_base_descriptor_impl] - .into_iter() - .collect() - } - SpecializeImplTargets::Specific(targets) => { - let specialize_impls = targets.iter().map(|target| { + SpecializeImplTargets::All => impl_specialize_all( + &specialize_path, + &ecs_path, + &ast, + &field_info, + &key_patterns, + &key_tuple_idents, + ), + SpecializeImplTargets::Specific(targets) => targets + .iter() + .map(|target| { impl_specialize_specific( &specialize_path, &ecs_path, @@ -305,14 +265,8 @@ pub fn impl_specializer(input: TokenStream) -> TokenStream { &key_patterns, &key_tuple_idents, ) - }); - let get_base_descriptor_impls = targets.iter().filter_map(|target| { - base_descriptor_field.map(|field_info| { - impl_get_base_descriptor_specific(&specialize_path, &ast, field_info, target) - }) - }); - specialize_impls.chain(get_base_descriptor_impls).collect() - } + }) + .collect(), } } @@ -406,56 +360,6 @@ fn impl_specialize_specific( }) } -fn impl_get_base_descriptor_specific( - specialize_path: &Path, - ast: &DeriveInput, - base_descriptor_field_info: &FieldInfo, - target_path: &Path, -) -> TokenStream { - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - let field_ty = &base_descriptor_field_info.ty; - let field_member = &base_descriptor_field_info.member; - TokenStream::from(quote!( - impl #impl_generics #specialize_path::GetBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { - fn get_base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { - <#field_ty as #specialize_path::GetBaseDescriptor<#target_path>>::get_base_descriptor(&self.#field_member) - } - } - )) -} - -fn impl_get_base_descriptor_all( - specialize_path: &Path, - ast: &DeriveInput, - base_descriptor_field_info: &FieldInfo, -) -> TokenStream { - let target_path = Path::from(format_ident!("T")); - let struct_name = &ast.ident; - let mut generics = ast.generics.clone(); - generics.params.insert( - 0, - parse_quote!(#target_path: #specialize_path::Specializable), - ); - - let where_clause = generics.make_where_clause(); - where_clause.predicates.push( - base_descriptor_field_info.get_base_descriptor_predicate(specialize_path, &target_path), - ); - - let (_, type_generics, _) = ast.generics.split_for_impl(); - let (impl_generics, _, where_clause) = &generics.split_for_impl(); - let field_ty = &base_descriptor_field_info.ty; - let field_member = &base_descriptor_field_info.member; - TokenStream::from(quote! { - impl #impl_generics #specialize_path::GetBaseDescriptor<#target_path> for #struct_name #type_generics #where_clause { - fn get_base_descriptor(&self) -> <#target_path as #specialize_path::Specializable>::Descriptor { - <#field_ty as #specialize_path::GetBaseDescriptor<#target_path>>::get_base_descriptor(&self.#field_member) - } - } - }) -} - pub fn impl_specializer_key(input: TokenStream) -> TokenStream { let bevy_render_path: Path = crate::bevy_render_path(); let specialize_path = { diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index 2fb0172b219da..35671a84e0ef9 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -1122,10 +1122,10 @@ impl FromWorld for GpuPreprocessingSupport { // `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 | - DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW - ); + let downlevel_support = adapter + .get_downlevel_capabilities() + .flags + .contains(DownlevelFlags::COMPUTE_SHADERS); let max_supported_mode = if device.limits().max_compute_workgroup_size_x == 0 || is_non_supported_android_device(adapter) @@ -1185,7 +1185,7 @@ where /// 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 { + pub fn instance_data_binding(&self) -> Option> { self.data_buffer .buffer() .map(|buffer| buffer.as_entire_binding()) diff --git a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs index 8bbbff8dd9e46..a6127255911bd 100644 --- a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs @@ -42,7 +42,7 @@ where /// /// 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 { + pub fn instance_data_binding(&self) -> Option> { self.binding() } } diff --git a/crates/bevy_render/src/bindless.wgsl b/crates/bevy_render/src/bindless.wgsl index 717e9c1047c60..6c8eff1a29aed 100644 --- a/crates/bevy_render/src/bindless.wgsl +++ b/crates/bevy_render/src/bindless.wgsl @@ -16,22 +16,22 @@ // Binding 0 is the bindless index table. // Filtering samplers. -@group(3) @binding(1) var bindless_samplers_filtering: binding_array; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var bindless_samplers_filtering: binding_array; // Non-filtering samplers (nearest neighbor). -@group(3) @binding(2) var bindless_samplers_non_filtering: binding_array; +@group(#{MATERIAL_BIND_GROUP}) @binding(2) var bindless_samplers_non_filtering: binding_array; // Comparison samplers (typically for shadow mapping). -@group(3) @binding(3) var bindless_samplers_comparison: binding_array; +@group(#{MATERIAL_BIND_GROUP}) @binding(3) var bindless_samplers_comparison: binding_array; // 1D textures. -@group(3) @binding(4) var bindless_textures_1d: binding_array>; +@group(#{MATERIAL_BIND_GROUP}) @binding(4) var bindless_textures_1d: binding_array>; // 2D textures. -@group(3) @binding(5) var bindless_textures_2d: binding_array>; +@group(#{MATERIAL_BIND_GROUP}) @binding(5) var bindless_textures_2d: binding_array>; // 2D array textures. -@group(3) @binding(6) var bindless_textures_2d_array: binding_array>; +@group(#{MATERIAL_BIND_GROUP}) @binding(6) var bindless_textures_2d_array: binding_array>; // 3D textures. -@group(3) @binding(7) var bindless_textures_3d: binding_array>; +@group(#{MATERIAL_BIND_GROUP}) @binding(7) var bindless_textures_3d: binding_array>; // Cubemap textures. -@group(3) @binding(8) var bindless_textures_cube: binding_array>; +@group(#{MATERIAL_BIND_GROUP}) @binding(8) var bindless_textures_cube: binding_array>; // Cubemap array textures. -@group(3) @binding(9) var bindless_textures_cube_array: binding_array>; +@group(#{MATERIAL_BIND_GROUP}) @binding(9) var bindless_textures_cube_array: binding_array>; #endif // BINDLESS diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 03564a80fe24c..c53ffd82f5da2 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -54,10 +54,7 @@ pub struct CameraPlugin; impl Plugin for CameraPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .register_type::() - .register_required_components::() + app.register_required_components::() .register_required_components::() .register_required_components::() .register_required_components::() @@ -319,64 +316,57 @@ pub fn camera_system( .as_ref() .map(|viewport| viewport.physical_size); - if let Some(normalized_target) = &camera.target.normalize(primary_window) { - if normalized_target.is_changed(&changed_window_ids, &changed_image_handles) + if let Some(normalized_target) = &camera.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 + || 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(new_scale_factor), Some(old_scale_factor)) = ( + new_computed_target_info + .as_ref() + .map(|info| info.scale_factor), + camera + .computed + .target_info + .as_ref() + .map(|info| info.scale_factor), + ) { - 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()) - { - if let (Some(new_scale_factor), Some(old_scale_factor)) = ( - new_computed_target_info - .as_ref() - .map(|info| info.scale_factor), - camera - .computed - .target_info - .as_ref() - .map(|info| info.scale_factor), - ) { - let resize_factor = new_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); - } - } + let resize_factor = new_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 { - let target_info = &new_computed_target_info; - if let Some(target) = target_info { - viewport.clamp_to_size(target.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 { + let target_info = &new_computed_target_info; + if let Some(target) = target_info { + viewport.clamp_to_size(target.physical_size); } - camera.computed.target_info = new_computed_target_info; - if let Some(size) = camera.logical_viewport_size() { - if 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(), - } - } + } + camera.computed.target_info = 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(), } } } @@ -619,10 +609,10 @@ pub fn sort_cameras( 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 { - if previous_order_target == new_order_target { - ambiguities.insert(new_order_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 diff --git a/crates/bevy_render/src/diagnostic/tracy_gpu.rs b/crates/bevy_render/src/diagnostic/tracy_gpu.rs index 7a66db4ea6da4..c429c0ee245af 100644 --- a/crates/bevy_render/src/diagnostic/tracy_gpu.rs +++ b/crates/bevy_render/src/diagnostic/tracy_gpu.rs @@ -56,7 +56,7 @@ fn initial_timestamp(device: &RenderDevice, queue: &RenderQueue) -> i64 { // 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, QUERY_SIZE as _); + 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, |_| ()); diff --git a/crates/bevy_render/src/erased_render_asset.rs b/crates/bevy_render/src/erased_render_asset.rs index ac2423990b8c7..ecdaeb91599cc 100644 --- a/crates/bevy_render/src/erased_render_asset.rs +++ b/crates/bevy_render/src/erased_render_asset.rs @@ -3,7 +3,7 @@ use crate::{ RenderSystems, Res, }; use bevy_app::{App, Plugin, SubApp}; -pub use bevy_asset::RenderAssetUsages; +use bevy_asset::RenderAssetUsages; use bevy_asset::{Asset, AssetEvent, AssetId, Assets, UntypedAssetId}; use bevy_ecs::{ prelude::{Commands, EventReader, IntoScheduleConfigs, ResMut, Resource}, diff --git a/crates/bevy_render/src/extract_instances.rs b/crates/bevy_render/src/extract_instances.rs index cf0d0c0cce4ba..d85f8fa646b34 100644 --- a/crates/bevy_render/src/extract_instances.rs +++ b/crates/bevy_render/src/extract_instances.rs @@ -128,10 +128,10 @@ fn extract_visible( { extracted_instances.clear(); for (entity, view_visibility, other) in &query { - if view_visibility.get() { - if let Some(extract_instance) = EI::extract(other) { - extracted_instances.insert(entity.into(), extract_instance); - } + if view_visibility.get() + && let Some(extract_instance) = EI::extract(other) + { + extracted_instances.insert(entity.into(), extract_instance); } } } diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index e97c758260939..e7701843fb2df 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -1,6 +1,6 @@ use crate::MainWorld; use bevy_ecs::{ - component::{ComponentId, Tick}, + component::Tick, prelude::*, query::FilteredAccessSet, system::{ @@ -83,7 +83,7 @@ where fn init_access( state: &Self::State, system_meta: &mut SystemMeta, - component_access_set: &mut FilteredAccessSet, + component_access_set: &mut FilteredAccessSet, world: &mut World, ) { Res::::init_access( diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index 04e4109f098c9..b8d6810a8bb43 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -16,8 +16,6 @@ pub struct GlobalsPlugin; impl Plugin for GlobalsPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "globals.wgsl"); - app.register_type::(); - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 994bc35bb3e1e..bd7b350d93da4 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -78,52 +78,50 @@ pub mod prelude { Projection, }; } -use batching::gpu_preprocessing::BatchingPlugin; #[doc(hidden)] pub mod _macro { pub use bevy_asset; } -use bevy_ecs::schedule::ScheduleBuildSettings; -use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats}; -use bevy_utils::prelude::default; pub use extract_param::Extract; -use bevy_window::{PrimaryWindow, RawHandleWrapperHolder}; -use experimental::occlusion_culling::OcclusionCullingPlugin; -use globals::GlobalsPlugin; -use render_asset::{ - extract_render_asset_bytes_per_frame, reset_render_asset_bytes_per_frame, - RenderAssetBytesPerFrame, RenderAssetBytesPerFrameLimiter, -}; -use renderer::{RenderAdapter, RenderDevice, RenderQueue}; -use settings::RenderResources; -use sync_world::{ - despawn_temporary_render_entities, entity_sync_system, MainEntity, RenderEntity, - SyncToRenderWorld, SyncWorldPlugin, TemporaryRenderEntity, -}; - -use crate::gpu_readback::GpuReadbackPlugin; use crate::{ camera::CameraPlugin, + gpu_readback::GpuReadbackPlugin, mesh::{MeshPlugin, MorphPlugin, RenderMesh}, render_asset::prepare_assets, - render_resource::{PipelineCache, Shader, ShaderLoader}, + render_resource::{init_empty_bind_group_layout, PipelineCache, Shader, ShaderLoader}, renderer::{render_system, RenderInstance}, settings::RenderCreation, storage::StoragePlugin, view::{ViewPlugin, WindowRenderPlugin}, }; use alloc::sync::Arc; +use batching::gpu_preprocessing::BatchingPlugin; use bevy_app::{App, AppLabel, Plugin, SubApp}; use bevy_asset::{AssetApp, AssetServer}; -use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; +use bevy_ecs::{ + prelude::*, + schedule::{ScheduleBuildSettings, ScheduleLabel}, +}; +use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats}; +use bevy_utils::prelude::default; +use bevy_window::{PrimaryWindow, RawHandleWrapperHolder}; use bitflags::bitflags; use core::ops::{Deref, DerefMut}; +use experimental::occlusion_culling::OcclusionCullingPlugin; +use globals::GlobalsPlugin; +use render_asset::{ + extract_render_asset_bytes_per_frame, reset_render_asset_bytes_per_frame, + RenderAssetBytesPerFrame, RenderAssetBytesPerFrameLimiter, +}; +use renderer::{RenderAdapter, RenderDevice, RenderQueue}; +use settings::RenderResources; use std::sync::Mutex; +use sync_world::{despawn_temporary_render_entities, entity_sync_system, SyncWorldPlugin}; use tracing::debug; -use wgpu_wrapper::WgpuWrapper; +pub use wgpu_wrapper::WgpuWrapper; /// Inline shader as an `embedded_asset` and load it permanently. /// @@ -360,6 +358,7 @@ impl Plugin for RenderPlugin { let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends, flags: settings.instance_flags, + memory_budget_thresholds: settings.instance_memory_budget_thresholds, backend_options: wgpu::BackendOptions { gl: wgpu::GlBackendOptions { gles_minor_version: settings.gles3_minor_version, @@ -466,15 +465,9 @@ impl Plugin for RenderPlugin { Render, reset_render_asset_bytes_per_frame.in_set(RenderSystems::Cleanup), ); - } - app.register_type::() - // These types cannot be registered in bevy_color, as it does not depend on the rest of Bevy - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::(); + render_app.add_systems(RenderStartup, init_empty_bind_group_layout); + } } fn ready(&self, app: &App) -> bool { @@ -649,13 +642,13 @@ pub fn get_mali_driver_version(adapter: &RenderAdapter) -> Option { return None; } let driver_info = adapter.get_info().driver_info; - if let Some(start_pos) = driver_info.find("v1.r") { - if let Some(end_pos) = driver_info[start_pos..].find('p') { - let start_idx = start_pos + 4; // Skip "v1.r" - let end_idx = start_pos + end_pos; + if let Some(start_pos) = driver_info.find("v1.r") + && let Some(end_pos) = driver_info[start_pos..].find('p') + { + let start_idx = start_pos + 4; // Skip "v1.r" + let end_idx = start_pos + end_pos; - return driver_info[start_idx..end_idx].parse::().ok(); - } + return driver_info[start_idx..end_idx].parse::().ok(); } None diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index bbdb543116b6e..3746acbbc4f3d 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -384,7 +384,7 @@ impl MeshAllocator { /// the mesh with the given ID. /// /// If the mesh wasn't allocated, returns None. - pub fn mesh_vertex_slice(&self, mesh_id: &AssetId) -> Option { + pub fn mesh_vertex_slice(&self, mesh_id: &AssetId) -> Option> { self.mesh_slice_in_slab(mesh_id, *self.mesh_id_to_vertex_slab.get(mesh_id)?) } @@ -392,7 +392,7 @@ impl MeshAllocator { /// the mesh with the given ID. /// /// If the mesh has no index data or wasn't allocated, returns None. - pub fn mesh_index_slice(&self, mesh_id: &AssetId) -> Option { + pub fn mesh_index_slice(&self, mesh_id: &AssetId) -> Option> { self.mesh_slice_in_slab(mesh_id, *self.mesh_id_to_index_slab.get(mesh_id)?) } @@ -415,7 +415,7 @@ impl MeshAllocator { &self, mesh_id: &AssetId, slab_id: SlabId, - ) -> Option { + ) -> Option> { match self.slabs.get(&slab_id)? { Slab::General(general_slab) => { let slab_allocation = general_slab.resident_allocations.get(mesh_id)?; diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index f7aa593a3a8f2..2f998fbe2fd91 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -21,36 +21,6 @@ use bevy_ecs::{ pub use bevy_mesh::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh2d, Mesh3d, MeshTag}; use wgpu::IndexFormat; -/// Registers all [`MeshBuilder`] types. -pub struct MeshBuildersPlugin; - -impl Plugin for MeshBuildersPlugin { - fn build(&self, app: &mut App) { - // 2D Mesh builders - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - // 3D Mesh builders - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::(); - } -} - /// Adds the [`Mesh`] as an asset and makes sure that they are extracted and prepared for the GPU. pub struct MeshPlugin; @@ -59,10 +29,6 @@ impl Plugin for MeshPlugin { app.init_asset::() .init_asset::() .register_asset_reflect::() - .register_type::() - .register_type::() - .register_type::>() - .add_plugins(MeshBuildersPlugin) // 'Mesh' must be prepared after 'Image' as meshes rely on the morph target image being ready .add_plugins(RenderAssetPlugin::::default()) .add_plugins(MeshAllocatorPlugin) @@ -86,9 +52,7 @@ impl Plugin for MeshPlugin { pub struct MorphPlugin; impl Plugin for MorphPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .add_systems(PostUpdate, inherit_weights); + app.add_systems(PostUpdate, inherit_weights); } } diff --git a/crates/bevy_render/src/render_graph/camera_driver_node.rs b/crates/bevy_render/src/render_graph/camera_driver_node.rs index d18c7d11333eb..714ceec18adf5 100644 --- a/crates/bevy_render/src/render_graph/camera_driver_node.rs +++ b/crates/bevy_render/src/render_graph/camera_driver_node.rs @@ -77,6 +77,7 @@ impl Node for CameraDriverNode { 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()), diff --git a/crates/bevy_render/src/render_graph/graph.rs b/crates/bevy_render/src/render_graph/graph.rs index d1a7020bcfcab..a7c4851d869b5 100644 --- a/crates/bevy_render/src/render_graph/graph.rs +++ b/crates/bevy_render/src/render_graph/graph.rs @@ -478,14 +478,13 @@ impl RenderGraph { } else { false } - }) { - if should_exist == EdgeExistence::DoesNotExist { - return Err(RenderGraphError::NodeInputSlotAlreadyOccupied { - node: input_node, - input_slot: input_index, - occupied_by_node: *current_output_node, - }); - } + }) && 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 { @@ -507,14 +506,12 @@ impl RenderGraph { 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 { - if output_node_state.edges.output_edges().contains(edge) { - if let Ok(input_node_state) = input_node_state { - if input_node_state.edges.input_edges().contains(edge) { - return true; - } - } - } + 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 diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index c58318f654578..253d06f48521e 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -67,12 +67,12 @@ use bevy_ecs::{ 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; -pub use bevy_render_macros::ShaderLabel; - define_label!( #[diagnostic::on_unimplemented( note = "consider annotating `{Self}` with `#[derive(ShaderLabel)]`" @@ -612,13 +612,13 @@ where // 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 { - if 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, - }); - } + 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. @@ -658,9 +658,12 @@ where 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); + .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) => { @@ -901,11 +904,10 @@ where ) -> bool { if let indexmap::map::Entry::Occupied(entry) = self.cached_entity_bin_keys.entry(visible_entity) + && entry.get().change_tick == current_change_tick { - if entry.get().change_tick == current_change_tick { - self.valid_cached_entity_bin_keys.insert(entry.index()); - return true; - } + self.valid_cached_entity_bin_keys.insert(entry.index()); + return true; } false diff --git a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs index fecc90feac1a4..ad36839398715 100644 --- a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs @@ -109,7 +109,7 @@ impl BatchedUniformBuffer { } #[inline] - pub fn binding(&self) -> Option { + 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() diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 1772c0082e8a4..3dfd8881ce484 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -133,12 +133,12 @@ impl Deref for BindGroup { /// In WGSL shaders, the binding would look like this: /// /// ```wgsl -/// @group(3) @binding(0) var color: vec4; -/// @group(3) @binding(1) var color_texture: texture_2d; -/// @group(3) @binding(2) var color_sampler: sampler; -/// @group(3) @binding(3) var storage_buffer: array; -/// @group(3) @binding(4) var raw_buffer: array; -/// @group(3) @binding(5) var storage_texture: texture_storage_2d; +/// @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. @@ -261,7 +261,7 @@ impl Deref for BindGroup { /// roughness: f32, /// }; /// -/// @group(3) @binding(0) var material: CoolMaterial; +/// @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: @@ -312,7 +312,7 @@ impl Deref for BindGroup { /// declaration: /// /// ```wgsl -/// @group(3) @binding(10) var material_array: binding_array; +/// @group(#{MATERIAL_BIND_GROUP}) @binding(10) var material_array: binding_array; /// ``` /// /// On the other hand, if you write this declaration: @@ -325,7 +325,7 @@ impl Deref for BindGroup { /// Then Bevy produces a binding that matches this WGSL declaration instead: /// /// ```wgsl -/// @group(3) @binding(10) var material_array: array; +/// @group(#{MATERIAL_BIND_GROUP}) @binding(10) var material_array: array; /// ``` /// /// * Just as with the structure-level `uniform` attribute, Bevy converts the @@ -338,7 +338,7 @@ impl Deref for BindGroup { /// this in WGSL in non-bindless mode: /// /// ```wgsl -/// @group(3) @binding(0) var material: StandardMaterial; +/// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var material: StandardMaterial; /// ``` /// /// * For efficiency reasons, `data` is generally preferred over `uniform` @@ -659,7 +659,7 @@ impl OwnedBindingResource { /// [`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 { + pub fn get_binding(&self) -> BindingResource<'_> { match self { OwnedBindingResource::Buffer(buffer) => buffer.as_entire_binding(), OwnedBindingResource::TextureView(_, view) => BindingResource::TextureView(view), diff --git a/crates/bevy_render/src/render_resource/bind_group_layout.rs b/crates/bevy_render/src/render_resource/bind_group_layout.rs index 86f09cde0c11f..1ad9f70713b3f 100644 --- a/crates/bevy_render/src/render_resource/bind_group_layout.rs +++ b/crates/bevy_render/src/render_resource/bind_group_layout.rs @@ -1,5 +1,6 @@ -use crate::define_atomic_id; -use crate::WgpuWrapper; +use crate::{define_atomic_id, renderer::RenderDevice, WgpuWrapper}; +use bevy_ecs::system::Res; +use bevy_platform::sync::OnceLock; use core::ops::Deref; define_atomic_id!(BindGroupLayoutId); @@ -62,3 +63,19 @@ impl Deref for BindGroupLayout { &self.value } } + +static EMPTY_BIND_GROUP_LAYOUT: OnceLock = OnceLock::new(); + +pub(crate) fn init_empty_bind_group_layout(render_device: Res) { + let layout = render_device.create_bind_group_layout(Some("empty_bind_group_layout"), &[]); + EMPTY_BIND_GROUP_LAYOUT + .set(layout) + .expect("init_empty_bind_group_layout was called more than once"); +} + +pub fn empty_bind_group_layout() -> BindGroupLayout { + EMPTY_BIND_GROUP_LAYOUT + .get() + .expect("init_empty_bind_group_layout was not called") + .clone() +} diff --git a/crates/bevy_render/src/render_resource/buffer.rs b/crates/bevy_render/src/render_resource/buffer.rs index 811930da83667..22b8a933e0118 100644 --- a/crates/bevy_render/src/render_resource/buffer.rs +++ b/crates/bevy_render/src/render_resource/buffer.rs @@ -16,7 +16,7 @@ impl Buffer { self.id } - pub fn slice(&self, bounds: impl RangeBounds) -> BufferSlice { + pub fn slice(&self, bounds: impl RangeBounds) -> BufferSlice<'_> { // need to compute and store this manually because wgpu doesn't export offset and size on wgpu::BufferSlice let offset = match bounds.start_bound() { Bound::Included(&bound) => bound, diff --git a/crates/bevy_render/src/render_resource/buffer_vec.rs b/crates/bevy_render/src/render_resource/buffer_vec.rs index 1fdb26655dd45..e51f78cbc13e4 100644 --- a/crates/bevy_render/src/render_resource/buffer_vec.rs +++ b/crates/bevy_render/src/render_resource/buffer_vec.rs @@ -68,7 +68,7 @@ impl RawBufferVec { /// Returns the binding for the buffer if the data has been uploaded. #[inline] - pub fn binding(&self) -> Option { + pub fn binding(&self) -> Option> { Some(BindingResource::Buffer( self.buffer()?.as_entire_buffer_binding(), )) @@ -310,7 +310,7 @@ where /// Returns the binding for the buffer if the data has been uploaded. #[inline] - pub fn binding(&self) -> Option { + pub fn binding(&self) -> Option> { Some(BindingResource::Buffer( self.buffer()?.as_entire_buffer_binding(), )) @@ -498,7 +498,7 @@ where /// Returns the binding for the buffer if the data has been uploaded. #[inline] - pub fn binding(&self) -> Option { + pub fn binding(&self) -> Option> { Some(BindingResource::Buffer( self.buffer()?.as_entire_buffer_binding(), )) diff --git a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs index 0c5bf36bf6e35..2c81f56019d94 100644 --- a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs +++ b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs @@ -89,7 +89,7 @@ impl GpuArrayBuffer { } } - pub fn binding(&self) -> Option { + pub fn binding(&self) -> Option> { match self { GpuArrayBuffer::Uniform(buffer) => buffer.binding(), GpuArrayBuffer::Storage(buffer) => buffer.binding(), diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 30d1821a9abec..d87e6df3149ec 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -61,7 +61,7 @@ pub use wgpu::{ TexelCopyBufferInfo, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, TextureUsages, TextureView as WgpuTextureView, - TextureViewDescriptor, TextureViewDimension, Tlas, TlasInstance, TlasPackage, VertexAttribute, + TextureViewDescriptor, TextureViewDimension, Tlas, TlasInstance, VertexAttribute, VertexBufferLayout as RawVertexBufferLayout, VertexFormat, VertexState as RawVertexState, VertexStepMode, COPY_BUFFER_ALIGNMENT, }; diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 36075def15697..30d2a12f3b4c4 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -1,4 +1,4 @@ -use super::ShaderDefVal; +use super::{empty_bind_group_layout, ShaderDefVal}; use crate::mesh::VertexBufferLayout; use crate::WgpuWrapper; use crate::{ @@ -7,7 +7,9 @@ use crate::{ }; use alloc::borrow::Cow; use bevy_asset::Handle; +use core::iter; use core::ops::Deref; +use thiserror::Error; use wgpu::{ ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, PushConstantRange, }; @@ -112,6 +114,20 @@ pub struct RenderPipelineDescriptor { 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: BindGroupLayout) { + filling_set_at(&mut self.layout, index, empty_bind_group_layout(), layout); + } +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct VertexState { /// The compiled shader module for this stage. @@ -148,6 +164,12 @@ pub struct FragmentState { 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)); + } +} + impl Default for FragmentState { fn default() -> Self { Self { @@ -176,6 +198,14 @@ pub struct ComputePipelineDescriptor { 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; +} + impl Default for ComputePipelineDescriptor { fn default() -> Self { Self { diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 3b69e3331d232..9277eb08a59c3 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -5,7 +5,7 @@ use crate::{ Extract, }; use alloc::{borrow::Cow, sync::Arc}; -use bevy_asset::{AssetEvent, AssetId, Assets}; +use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_ecs::{ event::EventReader, resource::Resource, @@ -696,7 +696,12 @@ impl PipelineCache { PipelineCacheError::ProcessShaderError(err) => { let error_detail = err.emit_to_string(&self.shader_cache.lock().unwrap().composer); - error!("failed to process shader:\n{}", error_detail); + 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) => { @@ -746,6 +751,48 @@ impl PipelineCache { } } +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"), diff --git a/crates/bevy_render/src/render_resource/specializer.rs b/crates/bevy_render/src/render_resource/specializer.rs index d7a2f3aca1a03..7f2d12dac5bcb 100644 --- a/crates/bevy_render/src/render_resource/specializer.rs +++ b/crates/bevy_render/src/render_resource/specializer.rs @@ -2,11 +2,7 @@ use super::{ CachedComputePipelineId, CachedRenderPipelineId, ComputePipeline, ComputePipelineDescriptor, PipelineCache, RenderPipeline, RenderPipelineDescriptor, }; -use bevy_ecs::{ - error::BevyError, - resource::Resource, - world::{FromWorld, World}, -}; +use bevy_ecs::error::BevyError; use bevy_platform::{ collections::{ hash_map::{Entry, VacantEntry}, @@ -205,7 +201,7 @@ pub trait Specializer: Send + Sync + 'static { /// 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 [`SpecializedCache`] to +/// 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. /// @@ -260,113 +256,21 @@ macro_rules! impl_specialization_key_tuple { // TODO: How to we fake_variadics this? all_tuples!(impl_specialization_key_tuple, 0, 12, T); -/// Defines a specializer that can also provide a "base descriptor". -/// -/// In order to be composable, [`Specializer`] implementers don't create full -/// descriptors, only transform them. However, [`SpecializedCache`]s need a -/// "base descriptor" at creation time in order to have something for the -/// [`Specializer`] to work off of. This trait allows [`SpecializedCache`] -/// to impl [`FromWorld`] for [`Specializer`]s that also satisfy [`FromWorld`] -/// and [`GetBaseDescriptor`]. -/// -/// This trait can be also derived with `#[derive(Specializer)]`, by marking -/// a field with `#[base_descriptor]` to use its [`GetBaseDescriptor`] implementation. -/// -/// Example: -/// ```rust -/// # use bevy_ecs::error::BevyError; -/// # use bevy_render::render_resource::Specializer; -/// # use bevy_render::render_resource::GetBaseDescriptor; -/// # use bevy_render::render_resource::SpecializerKey; -/// # use bevy_render::render_resource::RenderPipeline; -/// # use bevy_render::render_resource::RenderPipelineDescriptor; -/// struct A; -/// struct B; -/// -/// impl Specializer for A { -/// # type Key = (); -/// # -/// # fn specialize( -/// # &self, -/// # key: (), -/// # _descriptor: &mut RenderPipelineDescriptor -/// # ) -> Result<(), BevyError> { -/// # Ok(key) -/// # } -/// // ... -/// } -/// -/// impl Specializer for B { -/// # type Key = (); -/// # -/// # fn specialize( -/// # &self, -/// # key: (), -/// # _descriptor: &mut RenderPipelineDescriptor -/// # ) -> Result<(), BevyError> { -/// # Ok(key) -/// # } -/// // ... -/// } -/// -/// impl GetBaseDescriptor for B { -/// fn get_base_descriptor(&self) -> RenderPipelineDescriptor { -/// # todo!() -/// // ... -/// } -/// } -/// -/// -/// #[derive(Specializer)] -/// #[specialize(RenderPipeline)] -/// struct C { -/// a: A, -/// #[base_descriptor] -/// b: B, -/// } -/// -/// /* -/// The generated implementation: -/// impl GetBaseDescriptor for C { -/// fn get_base_descriptor(&self) -> RenderPipelineDescriptor { -/// self.b.base_descriptor() -/// } -/// } -/// */ -/// ``` -pub trait GetBaseDescriptor: Specializer { - fn get_base_descriptor(&self) -> T::Descriptor; -} - -pub type SpecializerFn = - fn(>::Key, &mut ::Descriptor) -> Result<(), BevyError>; - -/// A cache for specializable resources. For a given key type the resulting -/// resource will only be created if it is missing, retrieving it from the -/// cache otherwise. -#[derive(Resource)] -pub struct SpecializedCache> { +/// 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, - user_specializer: Option>, base_descriptor: T::Descriptor, primary_cache: HashMap, secondary_cache: HashMap, T::CachedId>, } -impl> SpecializedCache { - /// Creates a new [`SpecializedCache`] from a [`Specializer`], - /// an optional "user specializer", and a base descriptor. The - /// user specializer is applied after the [`Specializer`], with - /// the same key. +impl> Variants { + /// Creates a new [`Variants`] from a [`Specializer`] and a base descriptor. #[inline] - pub fn new( - specializer: S, - user_specializer: Option>, - base_descriptor: T::Descriptor, - ) -> Self { + pub fn new(specializer: S, base_descriptor: T::Descriptor) -> Self { Self { specializer, - user_specializer, base_descriptor, primary_cache: Default::default(), secondary_cache: Default::default(), @@ -385,7 +289,6 @@ impl> SpecializedCache { Entry::Occupied(entry) => Ok(entry.get().clone()), Entry::Vacant(entry) => Self::specialize_slow( &self.specializer, - self.user_specializer, self.base_descriptor.clone(), pipeline_cache, key, @@ -398,7 +301,6 @@ impl> SpecializedCache { #[cold] fn specialize_slow( specializer: &S, - user_specializer: Option>, base_descriptor: T::Descriptor, pipeline_cache: &PipelineCache, key: S::Key, @@ -408,10 +310,6 @@ impl> SpecializedCache { let mut descriptor = base_descriptor.clone(); let canonical_key = specializer.specialize(key.clone(), &mut descriptor)?; - if let Some(user_specializer) = user_specializer { - (user_specializer)(key, &mut descriptor)?; - } - // if the whole key is canonical, the secondary cache isn't needed. if ::IS_CANONICAL { return Ok(primary_entry @@ -447,19 +345,3 @@ impl> SpecializedCache { Ok(id) } } - -/// [`SpecializedCache`] implements [`FromWorld`] for [`Specializer`]s -/// that also satisfy [`FromWorld`] and [`GetBaseDescriptor`]. This will -/// create a [`SpecializedCache`] with no user specializer, and the base -/// descriptor take from the specializer's [`GetBaseDescriptor`] implementation. -impl FromWorld for SpecializedCache -where - T: Specializable, - S: FromWorld + Specializer + GetBaseDescriptor, -{ - fn from_world(world: &mut World) -> Self { - let specializer = S::from_world(world); - let base_descriptor = specializer.get_base_descriptor(); - Self::new(specializer, None, base_descriptor) - } -} diff --git a/crates/bevy_render/src/render_resource/storage_buffer.rs b/crates/bevy_render/src/render_resource/storage_buffer.rs index b407e22d8f9e6..dd76f46a9c1c4 100644 --- a/crates/bevy_render/src/render_resource/storage_buffer.rs +++ b/crates/bevy_render/src/render_resource/storage_buffer.rs @@ -76,7 +76,7 @@ impl StorageBuffer { } #[inline] - pub fn binding(&self) -> Option { + pub fn binding(&self) -> Option> { Some(BindingResource::Buffer(BufferBinding { buffer: self.buffer()?, offset: 0, @@ -209,7 +209,7 @@ impl DynamicStorageBuffer { } #[inline] - pub fn binding(&self) -> Option { + pub fn binding(&self) -> Option> { Some(BindingResource::Buffer(BufferBinding { buffer: self.buffer()?, offset: 0, diff --git a/crates/bevy_render/src/render_resource/uniform_buffer.rs b/crates/bevy_render/src/render_resource/uniform_buffer.rs index b7d22972df469..03cfb8c644991 100644 --- a/crates/bevy_render/src/render_resource/uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/uniform_buffer.rs @@ -78,7 +78,7 @@ impl UniformBuffer { } #[inline] - pub fn binding(&self) -> Option { + pub fn binding(&self) -> Option> { Some(BindingResource::Buffer( self.buffer()?.as_entire_buffer_binding(), )) @@ -213,7 +213,7 @@ impl DynamicUniformBuffer { } #[inline] - pub fn binding(&self) -> Option { + pub fn binding(&self) -> Option> { Some(BindingResource::Buffer(BufferBinding { buffer: self.buffer()?, offset: 0, diff --git a/crates/bevy_render/src/renderer/graph_runner.rs b/crates/bevy_render/src/renderer/graph_runner.rs index 39f05ca6a85c6..ff2e421982dc7 100644 --- a/crates/bevy_render/src/renderer/graph_runner.rs +++ b/crates/bevy_render/src/renderer/graph_runner.rs @@ -68,8 +68,6 @@ impl RenderGraphRunner { render_device: RenderDevice, mut diagnostics_recorder: Option, queue: &wgpu::Queue, - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - adapter: &wgpu::Adapter, world: &World, finalizer: impl FnOnce(&mut wgpu::CommandEncoder), ) -> Result, RenderGraphRunnerError> { @@ -77,12 +75,7 @@ impl RenderGraphRunner { recorder.begin_frame(); } - let mut render_context = RenderContext::new( - render_device, - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - adapter.get_info(), - diagnostics_recorder, - ); + let mut render_context = RenderContext::new(render_device, diagnostics_recorder); Self::run_graph(graph, None, &mut render_context, world, &[], None)?; finalizer(render_context.command_encoder()); diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 5bcc62188d883..e06053cfd3cc0 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -37,16 +37,12 @@ pub fn render_system(world: &mut World, state: &mut SystemState(); let render_device = world.resource::(); let render_queue = world.resource::(); - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - let render_adapter = world.resource::(); let res = RenderGraphRunner::run( graph, render_device.clone(), // TODO: is this clone really necessary? diagnostics_recorder, &render_queue.0, - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - &render_adapter.0, world, |encoder| { crate::view::screenshot::submit_screenshot_commands(world, encoder); @@ -159,10 +155,10 @@ fn find_adapter_by_name( { tracing::trace!("Checking adapter: {:?}", adapter.get_info()); let info = adapter.get_info(); - if let Some(surface) = compatible_surface { - if !adapter.is_surface_supported(surface) { - continue; - } + if let Some(surface) = compatible_surface + && !adapter.is_surface_supported(surface) + { + continue; } if info.name.eq_ignore_ascii_case(adapter_name) { @@ -343,6 +339,15 @@ pub async fn initialize_renderer( 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), @@ -355,6 +360,7 @@ pub async fn initialize_renderer( max_subgroup_size: limits .max_subgroup_size .min(constrained_limits.max_subgroup_size), + max_acceleration_structures_per_shader_stage: 0, }; } @@ -387,8 +393,6 @@ pub struct RenderContext<'w> { render_device: RenderDevice, command_encoder: Option, command_buffer_queue: Vec>, - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - force_serial: bool, diagnostics_recorder: Option>, } @@ -396,30 +400,12 @@ impl<'w> RenderContext<'w> { /// Creates a new [`RenderContext`] from a [`RenderDevice`]. pub fn new( render_device: RenderDevice, - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - adapter_info: AdapterInfo, diagnostics_recorder: Option, ) -> Self { - // HACK: Parallel command encoding is currently bugged on AMD + Windows/Linux + Vulkan - #[cfg(any(target_os = "windows", target_os = "linux"))] - let force_serial = - adapter_info.driver.contains("AMD") && adapter_info.backend == wgpu::Backend::Vulkan; - #[cfg(not(any( - target_os = "windows", - target_os = "linux", - all(target_arch = "wasm32", target_feature = "atomics") - )))] - let force_serial = { - drop(adapter_info); - false - }; - Self { render_device, command_encoder: None, command_buffer_queue: Vec::new(), - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - force_serial, diagnostics_recorder: diagnostics_recorder.map(Arc::new), } } @@ -522,14 +508,9 @@ impl<'w> RenderContext<'w> { } QueuedCommandBuffer::Task(command_buffer_generation_task) => { let render_device = self.render_device.clone(); - if self.force_serial { - command_buffers - .push((i, command_buffer_generation_task(render_device))); - } else { - task_pool.spawn(async move { - (i, command_buffer_generation_task(render_device)) - }); - } + task_pool.spawn(async move { + (i, command_buffer_generation_task(render_device)) + }); } } } diff --git a/crates/bevy_render/src/renderer/render_device.rs b/crates/bevy_render/src/renderer/render_device.rs index 6785ad9c997c7..c965a9fb14424 100644 --- a/crates/bevy_render/src/renderer/render_device.rs +++ b/crates/bevy_render/src/renderer/render_device.rs @@ -139,7 +139,7 @@ impl RenderDevice { pub fn create_render_bundle_encoder( &self, desc: &wgpu::RenderBundleEncoderDescriptor, - ) -> wgpu::RenderBundleEncoder { + ) -> wgpu::RenderBundleEncoder<'_> { self.device.create_render_bundle_encoder(desc) } diff --git a/crates/bevy_render/src/settings.rs b/crates/bevy_render/src/settings.rs index d4456953af6ce..411a21ceeb0bc 100644 --- a/crates/bevy_render/src/settings.rs +++ b/crates/bevy_render/src/settings.rs @@ -3,11 +3,11 @@ use crate::renderer::{ }; use alloc::borrow::Cow; -use wgpu::DxcShaderModel; 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)] @@ -53,6 +53,8 @@ pub struct WgpuSettings { 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. @@ -107,15 +109,10 @@ impl Default for WgpuSettings { Dx12Compiler::StaticDxc } else { let dxc = "dxcompiler.dll"; - let dxil = "dxil.dll"; - if cfg!(target_os = "windows") - && std::fs::metadata(dxc).is_ok() - && std::fs::metadata(dxil).is_ok() - { + if cfg!(target_os = "windows") && std::fs::metadata(dxc).is_ok() { Dx12Compiler::DynamicDxc { dxc_path: String::from(dxc), - dxil_path: String::from(dxil), max_shader_model: DxcShaderModel::V6_7, } } else { @@ -140,6 +137,7 @@ impl Default for WgpuSettings { gles3_minor_version, instance_flags, memory_hints: MemoryHints::default(), + instance_memory_budget_thresholds: MemoryBudgetThresholds::default(), force_fallback_adapter: false, adapter_name: None, } diff --git a/crates/bevy_render/src/storage.rs b/crates/bevy_render/src/storage.rs index 6084271fee27c..a07e7d845414c 100644 --- a/crates/bevy_render/src/storage.rs +++ b/crates/bevy_render/src/storage.rs @@ -18,7 +18,6 @@ pub struct StoragePlugin; impl Plugin for StoragePlugin { fn build(&self, app: &mut App) { app.add_plugins(RenderAssetPlugin::::default()) - .register_type::() .init_asset::() .register_asset_reflect::(); } diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index b216b5fd32e3f..77643ba809052 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -336,10 +336,7 @@ mod render_entities_world_query_impls { unsafe { <&RenderEntity as WorldQuery>::set_table(fetch, &component_id, table) } } - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { + fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { <&RenderEntity as WorldQuery>::update_component_access(&component_id, access); } @@ -445,10 +442,7 @@ mod render_entities_world_query_impls { unsafe { <&MainEntity as WorldQuery>::set_table(fetch, &component_id, table) } } - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { + fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { <&MainEntity as WorldQuery>::update_component_access(&component_id, access); } diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 78cc78a9a132f..f9e7532b7a1ac 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -82,7 +82,6 @@ impl Plugin for ImagePlugin { ExtractResourcePlugin::::default(), )) .init_resource::() - .register_type::() .init_asset::() .register_asset_reflect::(); diff --git a/crates/bevy_render/src/texture/texture_attachment.rs b/crates/bevy_render/src/texture/texture_attachment.rs index ac3854227ff31..cf0e057db0f21 100644 --- a/crates/bevy_render/src/texture/texture_attachment.rs +++ b/crates/bevy_render/src/texture/texture_attachment.rs @@ -34,12 +34,13 @@ impl ColorAttachment { /// `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 { + 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) { @@ -58,11 +59,12 @@ impl ColorAttachment { /// 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 { + 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) { @@ -99,7 +101,7 @@ impl DepthAttachment { /// 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 { + pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment<'_> { let first_call = self .is_first_call .fetch_and(store != StoreOp::Store, Ordering::SeqCst); @@ -141,11 +143,12 @@ impl OutputColorAttachment { /// 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 { + 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) { diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index cdda04cf2de10..2ffe9a24cbe3d 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -3,6 +3,7 @@ pub mod window; use bevy_camera::{ primitives::Frustum, CameraMainTextureUsages, ClearColor, ClearColorConfig, Exposure, + MainPassResolutionOverride, }; use bevy_diagnostic::FrameCount; pub use visibility::*; @@ -99,9 +100,7 @@ impl Plugin for ViewPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "view.wgsl"); - app.register_type::() - .register_type::() - .register_type::() + app // NOTE: windows.is_changed() handles cases where a window was resized .add_plugins(( ExtractComponentPlugin::::default(), @@ -568,6 +567,7 @@ pub struct ViewUniform { 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.` @@ -725,7 +725,7 @@ 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 { + pub fn get_color_attachment(&self) -> RenderPassColorAttachment<'_> { if self.main_texture.load(Ordering::SeqCst) == 0 { self.main_textures.a.get_attachment() } else { @@ -734,7 +734,7 @@ impl ViewTarget { } /// Retrieve this target's "unsampled" main texture's color attachment. - pub fn get_unsampled_color_attachment(&self) -> RenderPassColorAttachment { + pub fn get_unsampled_color_attachment(&self) -> RenderPassColorAttachment<'_> { if self.main_texture.load(Ordering::SeqCst) == 0 { self.main_textures.a.get_unsampled_attachment() } else { @@ -826,7 +826,7 @@ impl ViewTarget { pub fn out_texture_color_attachment( &self, clear_color: Option, - ) -> RenderPassColorAttachment { + ) -> RenderPassColorAttachment<'_> { self.out_texture.get_attachment(clear_color) } @@ -843,7 +843,7 @@ impl ViewTarget { /// [`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 { + 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 { @@ -880,7 +880,7 @@ impl ViewDepthTexture { } } - pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment { + pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment<'_> { self.attachment.get_attachment(store) } @@ -901,6 +901,7 @@ pub fn prepare_view_uniforms( Option<&Frustum>, Option<&TemporalJitter>, Option<&MipBias>, + Option<&MainPassResolutionOverride>, )>, frame_count: Res, ) { @@ -913,13 +914,28 @@ pub fn prepare_view_uniforms( else { return; }; - for (entity, extracted_camera, extracted_view, frustum, temporal_jitter, mip_bias) in &views { + 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, viewport.zw()); + temporal_jitter.jitter_projection(&mut clip_from_view, main_pass_viewport.zw()); } let view_from_clip = clip_from_view.inverse(); @@ -953,6 +969,7 @@ pub fn prepare_view_uniforms( .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, diff --git a/crates/bevy_render/src/view/view.wgsl b/crates/bevy_render/src/view/view.wgsl index 375b9349f5036..0d0efb75b556e 100644 --- a/crates/bevy_render/src/view/view.wgsl +++ b/crates/bevy_render/src/view/view.wgsl @@ -48,7 +48,7 @@ struct View { // `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, @@ -56,6 +56,7 @@ struct View { 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.` @@ -78,7 +79,7 @@ struct View { /// 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: +/// 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 @@ -209,7 +210,7 @@ fn perspective_camera_near(clip_from_view: mat4x4) -> f32 { return clip_from_view[3][2]; } -/// Convert ndc depth to linear view z. +/// 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 @@ -222,7 +223,7 @@ fn depth_ndc_to_view_z(ndc_depth: f32, clip_from_view: mat4x4, view_from_cl #endif } -/// Convert linear view z to ndc depth. +/// 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 diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 986f6927e5d12..789e284c66d77 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -401,9 +401,7 @@ impl Plugin for ScreenshotPlugin { .after(event_update_system) .before(ApplyDeferred), ) - .add_systems(Update, trigger_screenshots) - .register_type::() - .register_type::(); + .add_systems(Update, trigger_screenshots); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -595,6 +593,7 @@ fn render_screenshot( 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, diff --git a/crates/bevy_scene/Cargo.toml b/crates/bevy_scene/Cargo.toml index 48d718b410981..ef26a21b1d034 100644 --- a/crates/bevy_scene/Cargo.toml +++ b/crates/bevy_scene/Cargo.toml @@ -15,7 +15,6 @@ serialize = [ "uuid/serde", "bevy_ecs/serialize", "bevy_platform/serialize", - "bevy_render?/serialize", ] [dependencies] @@ -27,7 +26,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" } -bevy_render = { path = "../bevy_render", version = "0.17.0-dev", optional = true } +bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" } bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ "std", ] } diff --git a/crates/bevy_scene/src/components.rs b/crates/bevy_scene/src/components.rs index 0c2d98962ce4b..e1e6c93430737 100644 --- a/crates/bevy_scene/src/components.rs +++ b/crates/bevy_scene/src/components.rs @@ -5,8 +5,7 @@ use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_transform::components::Transform; use derive_more::derive::From; -#[cfg(feature = "bevy_render")] -use bevy_render::view::visibility::Visibility; +use bevy_camera::visibility::Visibility; use crate::{DynamicScene, Scene}; @@ -15,7 +14,7 @@ use crate::{DynamicScene, Scene}; #[derive(Component, Clone, Debug, Deref, DerefMut, Reflect, PartialEq, Eq, From)] #[reflect(Component, Default, Debug, PartialEq, Clone)] #[require(Transform)] -#[cfg_attr(feature = "bevy_render", require(Visibility))] +#[require(Visibility)] pub struct SceneRoot(pub Handle); impl Default for SceneRoot { @@ -29,7 +28,7 @@ impl Default for SceneRoot { #[derive(Component, Clone, Debug, Deref, DerefMut, Reflect, PartialEq, Eq, From)] #[reflect(Component, Default, Debug, PartialEq, Clone)] #[require(Transform)] -#[cfg_attr(feature = "bevy_render", require(Visibility))] +#[require(Visibility)] pub struct DynamicSceneRoot(pub Handle); impl Default for DynamicSceneRoot { diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index fc3b223ce2b60..e6740090c65a4 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -122,7 +122,10 @@ impl DynamicScene { #[expect(unsafe_code, reason = "this is faster")] let component_info = unsafe { world.components().get_info_unchecked(component_id) }; - if *component_info.clone_behavior() == ComponentCloneBehavior::Ignore { + if matches!( + *component_info.clone_behavior(), + ComponentCloneBehavior::Ignore + ) { continue; } } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index b088fb9871055..4c464fc473f91 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -62,8 +62,6 @@ impl Plugin for ScenePlugin { .init_asset::() .init_asset_loader::() .init_resource::() - .register_type::() - .register_type::() .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); // Register component hooks for DynamicSceneRoot diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 2293beef1e9a2..255f688673c86 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -126,7 +126,10 @@ impl Scene { .get_info(component_id) .expect("component_ids in archetypes should have ComponentInfo"); - if *component_info.clone_behavior() == ComponentCloneBehavior::Ignore { + if matches!( + *component_info.clone_behavior(), + ComponentCloneBehavior::Ignore + ) { continue; } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 5243df357a401..386e81080a106 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -552,10 +552,10 @@ pub fn scene_spawner_system(world: &mut World) { .scene_asset_event_reader .read(scene_asset_events) { - if let AssetEvent::Modified { id } = event { - if scene_spawner.spawned_scenes.contains_key(id) { - updated_spawned_scenes.push(*id); - } + if let AssetEvent::Modified { id } = event + && scene_spawner.spawned_scenes.contains_key(id) + { + updated_spawned_scenes.push(*id); } } let mut updated_spawned_dynamic_scenes = Vec::new(); @@ -563,10 +563,10 @@ pub fn scene_spawner_system(world: &mut World) { .dynamic_scene_asset_event_reader .read(dynamic_scene_asset_events) { - if let AssetEvent::Modified { id } = event { - if scene_spawner.spawned_dynamic_scenes.contains_key(id) { - updated_spawned_dynamic_scenes.push(*id); - } + if let AssetEvent::Modified { id } = event + && scene_spawner.spawned_dynamic_scenes.contains_key(id) + { + updated_spawned_dynamic_scenes.push(*id); } } @@ -661,8 +661,7 @@ mod tests { app.add_plugins(ScheduleRunnerPlugin::default()) .add_plugins(AssetPlugin::default()) - .add_plugins(ScenePlugin) - .register_type::(); + .add_plugins(ScenePlugin); app.update(); let mut scene_world = World::new(); diff --git a/crates/bevy_shader/Cargo.toml b/crates/bevy_shader/Cargo.toml index d89bc0ec77734..f5df411739f35 100644 --- a/crates/bevy_shader/Cargo.toml +++ b/crates/bevy_shader/Cargo.toml @@ -16,8 +16,8 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev" } # other -wgpu-types = { version = "25", default-features = false } -naga = { version = "25", features = ["wgsl-in"] } +wgpu-types = { version = "26", default-features = false } +naga = { version = "26", features = ["wgsl-in"] } serde = { version = "1", features = ["derive"] } thiserror = { version = "2", default-features = false } wesl = { version = "0.1.2", optional = true } @@ -26,12 +26,12 @@ tracing = { version = "0.1", default-features = false, features = ["std"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Omit the `glsl` feature in non-WebAssembly by default. -naga_oil = { version = "0.18", default-features = false, features = [ +naga_oil = { version = "0.19", default-features = false, features = [ "test_shader", ] } [target.'cfg(target_arch = "wasm32")'.dependencies] -naga_oil = { version = "0.18" } +naga_oil = { version = "0.19" } [features] shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out", "naga_oil/glsl"] diff --git a/crates/bevy_shader/src/shader_cache.rs b/crates/bevy_shader/src/shader_cache.rs index 89926c856ce0b..77a004ecebb8e 100644 --- a/crates/bevy_shader/src/shader_cache.rs +++ b/crates/bevy_shader/src/shader_cache.rs @@ -357,11 +357,11 @@ impl ShaderCache { } #[cfg(feature = "shader_format_wesl")] - if let Source::Wesl(_) = shader.source { - if let ShaderImport::AssetPath(path) = shader.import_path() { - self.asset_paths - .insert(wesl::syntax::ModulePath::from_path(path), id); - } + if let Source::Wesl(_) = shader.source + && let ShaderImport::AssetPath(path) = shader.import_path() + { + self.asset_paths + .insert(wesl::syntax::ModulePath::from_path(path), id); } self.shaders.insert(id, shader); pipelines_to_queue @@ -401,7 +401,7 @@ impl<'a> wesl::Resolver for ShaderResolver<'a> { fn resolve_source( &self, module_path: &wesl::syntax::ModulePath, - ) -> Result, wesl::ResolveError> { + ) -> Result, wesl::ResolveError> { let asset_id = self.asset_paths.get(module_path).ok_or_else(|| { wesl::ResolveError::ModuleNotFound(module_path.clone(), "Invalid asset id".to_string()) })?; @@ -506,6 +506,10 @@ fn get_capabilities(features: Features, downlevel: DownlevelFlags) -> Capabiliti Capabilities::DUAL_SOURCE_BLENDING, features.contains(Features::DUAL_SOURCE_BLENDING), ); + capabilities.set( + Capabilities::CLIP_DISTANCE, + features.contains(Features::CLIP_DISTANCES), + ); capabilities.set( Capabilities::CUBE_ARRAY_TEXTURES, downlevel.contains(DownlevelFlags::CUBE_ARRAY_TEXTURES), diff --git a/crates/bevy_solari/src/pathtracer/extract.rs b/crates/bevy_solari/src/pathtracer/extract.rs index 38f27968a609d..c90302dd11ec1 100644 --- a/crates/bevy_solari/src/pathtracer/extract.rs +++ b/crates/bevy_solari/src/pathtracer/extract.rs @@ -22,8 +22,10 @@ pub fn extract_pathtracer( let mut entity_commands = commands .get_entity(entity) .expect("Camera entity wasn't synced."); - if pathtracer.is_some() && camera.is_active { - let mut pathtracer = pathtracer.unwrap().clone(); + if let Some(pathtracer) = pathtracer + && camera.is_active + { + let mut pathtracer = pathtracer.clone(); pathtracer.reset |= global_transform.is_changed(); entity_commands.insert(pathtracer); } else { diff --git a/crates/bevy_solari/src/pathtracer/mod.rs b/crates/bevy_solari/src/pathtracer/mod.rs index 72affe4b048a8..3f20119761290 100644 --- a/crates/bevy_solari/src/pathtracer/mod.rs +++ b/crates/bevy_solari/src/pathtracer/mod.rs @@ -28,8 +28,6 @@ pub struct PathtracingPlugin; impl Plugin for PathtracingPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "pathtracer.wgsl"); - - app.register_type::(); } fn finish(&self, app: &mut App) { diff --git a/crates/bevy_solari/src/pathtracer/pathtracer.wgsl b/crates/bevy_solari/src/pathtracer/pathtracer.wgsl index 45c5e519594f0..da216da959201 100644 --- a/crates/bevy_solari/src/pathtracer/pathtracer.wgsl +++ b/crates/bevy_solari/src/pathtracer/pathtracer.wgsl @@ -1,9 +1,11 @@ #import bevy_core_pipeline::tonemapping::tonemapping_luminance as luminance +#import bevy_pbr::pbr_functions::calculate_tbn_mikktspace #import bevy_pbr::utils::{rand_f, rand_vec2f, sample_cosine_hemisphere} #import bevy_render::maths::PI #import bevy_render::view::View -#import bevy_solari::sampling::sample_random_light -#import bevy_solari::scene_bindings::{trace_ray, resolve_ray_hit_full, RAY_T_MIN, RAY_T_MAX} +#import bevy_solari::brdf::evaluate_brdf +#import bevy_solari::sampling::{sample_random_light, random_light_pdf, sample_ggx_vndf, ggx_vndf_pdf, balance_heuristic, power_heuristic} +#import bevy_solari::scene_bindings::{trace_ray, resolve_ray_hit_full, ResolvedRayHitFull, RAY_T_MIN, RAY_T_MAX} @group(1) @binding(0) var accumulation_texture: texture_storage_2d; @group(1) @binding(1) var view_output: texture_storage_2d; @@ -35,32 +37,45 @@ fn pathtrace(@builtin(global_invocation_id) global_id: vec3) { // Path trace var radiance = vec3(0.0); var throughput = vec3(1.0); + var p_bounce = 0.0; + var bounce_was_perfect_reflection = true; + var previous_normal = vec3(0.0); loop { let ray_hit = trace_ray(ray_origin, ray_direction, ray_t_min, RAY_T_MAX, RAY_FLAG_NONE); if ray_hit.kind != RAY_QUERY_INTERSECTION_NONE { let ray_hit = resolve_ray_hit_full(ray_hit); + let wo = -ray_direction; - // Evaluate material BRDF - let diffuse_brdf = ray_hit.material.base_color / PI; + var mis_weight = 1.0; + if !bounce_was_perfect_reflection { + let p_light = random_light_pdf(ray_hit); + mis_weight = power_heuristic(p_bounce, p_light); + } + radiance += mis_weight * throughput * ray_hit.material.emissive; - // Use emissive only on the first ray (coming from the camera) - if ray_t_min == 0.0 { radiance = ray_hit.material.emissive; } - - // Sample direct lighting - let direct_lighting = sample_random_light(ray_hit.world_position, ray_hit.world_normal, &rng); - radiance += throughput * diffuse_brdf * direct_lighting.radiance * direct_lighting.inverse_pdf; + // Sample direct lighting, but only if the surface is not mirror-like + let is_perfectly_specular = ray_hit.material.roughness < 0.0001 && ray_hit.material.metallic > 0.9999; + if !is_perfectly_specular { + let direct_lighting = sample_random_light(ray_hit.world_position, ray_hit.world_normal, &rng); + let pdf_of_bounce = brdf_pdf(wo, direct_lighting.wi, ray_hit); + mis_weight = power_heuristic(1.0 / direct_lighting.inverse_pdf, pdf_of_bounce); + let direct_lighting_brdf = evaluate_brdf(ray_hit.world_normal, wo, direct_lighting.wi, ray_hit.material); + radiance += mis_weight * throughput * direct_lighting.radiance * direct_lighting.inverse_pdf * direct_lighting_brdf; + } // Sample new ray direction from the material BRDF for next bounce - ray_direction = sample_cosine_hemisphere(ray_hit.world_normal, &rng); - - // Update other variables for next bounce + let next_bounce = importance_sample_next_bounce(wo, ray_hit, &rng); + ray_direction = next_bounce.wi; ray_origin = ray_hit.world_position; ray_t_min = RAY_T_MIN; + p_bounce = next_bounce.pdf; + bounce_was_perfect_reflection = next_bounce.perfectly_specular_bounce; + previous_normal = ray_hit.world_normal; // Update throughput for next bounce - let cos_theta = dot(-ray_direction, ray_hit.world_normal); - let cosine_hemisphere_pdf = cos_theta / PI; // Weight for the next bounce because we importance sampled the diffuse BRDF for the next ray direction - throughput *= (diffuse_brdf * cos_theta) / cosine_hemisphere_pdf; + let brdf = evaluate_brdf(ray_hit.world_normal, wo, next_bounce.wi, ray_hit.material); + let cos_theta = dot(next_bounce.wi, ray_hit.world_normal); + throughput *= (brdf * cos_theta) / next_bounce.pdf; // Russian roulette for early termination let p = luminance(throughput); @@ -77,3 +92,60 @@ fn pathtrace(@builtin(global_invocation_id) global_id: vec3) { textureStore(accumulation_texture, global_id.xy, vec4(new_color, old_color.a + 1.0)); textureStore(view_output, global_id.xy, vec4(new_color, 1.0)); } + +struct NextBounce { + wi: vec3, + pdf: f32, + perfectly_specular_bounce: bool, +} + +fn importance_sample_next_bounce(wo: vec3, ray_hit: ResolvedRayHitFull, rng: ptr) -> NextBounce { + let is_perfectly_specular = ray_hit.material.roughness < 0.0001 && ray_hit.material.metallic > 0.9999; + if is_perfectly_specular { + return NextBounce(reflect(-wo, ray_hit.world_normal), 1.0, true); + } + let diffuse_weight = mix(mix(0.4f, 0.9f, ray_hit.material.perceptual_roughness), 0.f, ray_hit.material.metallic); + let specular_weight = 1.0 - diffuse_weight; + + let TBN = calculate_tbn_mikktspace(ray_hit.world_normal, ray_hit.world_tangent); + let T = TBN[0]; + let B = TBN[1]; + let N = TBN[2]; + + let wo_tangent = vec3(dot(wo, T), dot(wo, B), dot(wo, N)); + + var wi: vec3; + var wi_tangent: vec3; + let diffuse_selected = rand_f(rng) < diffuse_weight; + if diffuse_selected { + wi = sample_cosine_hemisphere(ray_hit.world_normal, rng); + wi_tangent = vec3(dot(wi, T), dot(wi, B), dot(wi, N)); + } else { + wi_tangent = sample_ggx_vndf(wo_tangent, ray_hit.material.roughness, rng); + wi = wi_tangent.x * T + wi_tangent.y * B + wi_tangent.z * N; + } + + let diffuse_pdf = dot(wi, ray_hit.world_normal) / PI; + let specular_pdf = ggx_vndf_pdf(wo_tangent, wi_tangent, ray_hit.material.roughness); + let pdf = (diffuse_weight * diffuse_pdf) + (specular_weight * specular_pdf); + + return NextBounce(wi, pdf, false); +} + +fn brdf_pdf(wo: vec3, wi: vec3, ray_hit: ResolvedRayHitFull) -> f32 { + let diffuse_weight = mix(mix(0.4f, 0.9f, ray_hit.material.roughness), 0.f, ray_hit.material.metallic); + let specular_weight = 1.0 - diffuse_weight; + + let TBN = calculate_tbn_mikktspace(ray_hit.world_normal, ray_hit.world_tangent); + let T = TBN[0]; + let B = TBN[1]; + let N = TBN[2]; + + let wo_tangent = vec3(dot(wo, T), dot(wo, B), dot(wo, N)); + let wi_tangent = vec3(dot(wi, T), dot(wi, B), dot(wi, N)); + + let diffuse_pdf = wi_tangent.z / PI; + let specular_pdf = ggx_vndf_pdf(wo_tangent, wi_tangent, ray_hit.material.roughness); + let pdf = (diffuse_weight * diffuse_pdf) + (specular_weight * specular_pdf); + return pdf; +} diff --git a/crates/bevy_solari/src/realtime/extract.rs b/crates/bevy_solari/src/realtime/extract.rs index 8e80f023275ee..28b7d36e6fa1b 100644 --- a/crates/bevy_solari/src/realtime/extract.rs +++ b/crates/bevy_solari/src/realtime/extract.rs @@ -6,16 +6,15 @@ use bevy_render::{camera::Camera, sync_world::RenderEntity, MainWorld}; pub fn extract_solari_lighting(mut main_world: ResMut, mut commands: Commands) { let mut cameras_3d = main_world.query::<(RenderEntity, &Camera, Option<&mut SolariLighting>)>(); - for (entity, camera, mut solari_lighting) in cameras_3d.iter_mut(&mut main_world) { + for (entity, camera, solari_lighting) in cameras_3d.iter_mut(&mut main_world) { let mut entity_commands = commands .get_entity(entity) .expect("Camera entity wasn't synced."); - if solari_lighting.is_some() && camera.is_active { - entity_commands.insert(( - solari_lighting.as_deref().unwrap().clone(), - SkipDeferredLighting, - )); - solari_lighting.as_mut().unwrap().reset = false; + if let Some(mut solari_lighting) = solari_lighting + && camera.is_active + { + entity_commands.insert((solari_lighting.clone(), SkipDeferredLighting)); + solari_lighting.reset = false; } else { entity_commands.remove::<( SolariLighting, diff --git a/crates/bevy_solari/src/realtime/mod.rs b/crates/bevy_solari/src/realtime/mod.rs index 472bce67d8ead..d8993840d1e4c 100644 --- a/crates/bevy_solari/src/realtime/mod.rs +++ b/crates/bevy_solari/src/realtime/mod.rs @@ -35,8 +35,7 @@ impl Plugin for SolariLightingPlugin { embedded_asset!(app, "restir_di.wgsl"); embedded_asset!(app, "restir_gi.wgsl"); - app.register_type::() - .insert_resource(DefaultOpaqueRendererMethod::deferred()); + app.insert_resource(DefaultOpaqueRendererMethod::deferred()); } fn finish(&self, app: &mut App) { diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index b873d7bee567f..552058f09651a 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -14,7 +14,6 @@ use bevy_ecs::{ }; use bevy_image::ToExtents; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ @@ -50,7 +49,6 @@ impl ViewNode for SolariLightingNode { type ViewQuery = ( &'static SolariLighting, &'static SolariLightingResources, - &'static ExtractedCamera, &'static ViewTarget, &'static ViewPrepassTextures, &'static ViewUniformOffset, @@ -64,7 +62,6 @@ impl ViewNode for SolariLightingNode { ( solari_lighting, solari_lighting_resources, - camera, view_target, view_prepass_textures, view_uniform_offset, @@ -84,7 +81,6 @@ impl ViewNode for SolariLightingNode { Some(gi_initial_and_temporal_pipeline), Some(gi_spatial_and_shade_pipeline), Some(scene_bindings), - Some(viewport), Some(gbuffer), Some(depth_buffer), Some(motion_vectors), @@ -97,7 +93,6 @@ impl ViewNode for SolariLightingNode { pipeline_cache.get_compute_pipeline(self.gi_initial_and_temporal_pipeline), pipeline_cache.get_compute_pipeline(self.gi_spatial_and_shade_pipeline), &scene_bindings.bind_group, - camera.physical_viewport_size, view_prepass_textures.deferred_view(), view_prepass_textures.depth_view(), view_prepass_textures.motion_vectors_view(), @@ -153,6 +148,9 @@ impl ViewNode for SolariLightingNode { }); let pass_span = diagnostics.pass_span(&mut pass, "solari_lighting"); + let dx = solari_lighting_resources.view_size.x.div_ceil(8); + let dy = solari_lighting_resources.view_size.y.div_ceil(8); + pass.set_bind_group(0, scene_bindings, &[]); pass.set_bind_group( 1, @@ -171,16 +169,16 @@ impl ViewNode for SolariLightingNode { pass.dispatch_workgroups(LIGHT_TILE_BLOCKS as u32, 1, 1); pass.set_pipeline(di_initial_and_temporal_pipeline); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + pass.dispatch_workgroups(dx, dy, 1); pass.set_pipeline(di_spatial_and_shade_pipeline); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + pass.dispatch_workgroups(dx, dy, 1); pass.set_pipeline(gi_initial_and_temporal_pipeline); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + pass.dispatch_workgroups(dx, dy, 1); pass.set_pipeline(gi_spatial_and_shade_pipeline); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + pass.dispatch_workgroups(dx, dy, 1); pass_span.end(&mut pass); drop(pass); @@ -195,7 +193,7 @@ impl ViewNode for SolariLightingNode { .texture .as_image_copy(), solari_lighting_resources.previous_gbuffer.0.as_image_copy(), - viewport.to_extents(), + solari_lighting_resources.view_size.to_extents(), ); command_encoder.copy_texture_to_texture( view_prepass_textures @@ -206,7 +204,7 @@ impl ViewNode for SolariLightingNode { .texture .as_image_copy(), solari_lighting_resources.previous_depth.0.as_image_copy(), - viewport.to_extents(), + solari_lighting_resources.view_size.to_extents(), ); Ok(()) diff --git a/crates/bevy_solari/src/realtime/prepare.rs b/crates/bevy_solari/src/realtime/prepare.rs index ea5ce3cf8fbf4..3bb6553214e8b 100644 --- a/crates/bevy_solari/src/realtime/prepare.rs +++ b/crates/bevy_solari/src/realtime/prepare.rs @@ -9,7 +9,7 @@ use bevy_ecs::{ use bevy_image::ToExtents; use bevy_math::UVec2; use bevy_render::{ - camera::ExtractedCamera, + camera::{ExtractedCamera, MainPassResolutionOverride}, render_resource::{ Buffer, BufferDescriptor, BufferUsages, Texture, TextureDescriptor, TextureDimension, TextureUsages, TextureView, TextureViewDescriptor, @@ -48,16 +48,24 @@ pub struct SolariLightingResources { pub fn prepare_solari_lighting_resources( query: Query< - (Entity, &ExtractedCamera, Option<&SolariLightingResources>), + ( + Entity, + &ExtractedCamera, + Option<&SolariLightingResources>, + Option<&MainPassResolutionOverride>, + ), With, >, render_device: Res, mut commands: Commands, ) { - for (entity, camera, solari_lighting_resources) in &query { - let Some(view_size) = camera.physical_viewport_size else { + for (entity, camera, solari_lighting_resources, resolution_override) in &query { + let Some(mut view_size) = camera.physical_viewport_size else { continue; }; + if let Some(MainPassResolutionOverride(resolution_override)) = resolution_override { + view_size = *resolution_override; + } if solari_lighting_resources.map(|r| r.view_size) == Some(view_size) { continue; diff --git a/crates/bevy_solari/src/realtime/restir_di.wgsl b/crates/bevy_solari/src/realtime/restir_di.wgsl index e3077e525e55c..e6527cc514d05 100644 --- a/crates/bevy_solari/src/realtime/restir_di.wgsl +++ b/crates/bevy_solari/src/realtime/restir_di.wgsl @@ -35,9 +35,9 @@ const NULL_RESERVOIR_SAMPLE = 0xFFFFFFFFu; @compute @workgroup_size(8, 8, 1) fn initial_and_temporal(@builtin(workgroup_id) workgroup_id: vec3, @builtin(global_invocation_id) global_id: vec3) { - if any(global_id.xy >= vec2u(view.viewport.zw)) { return; } + if any(global_id.xy >= vec2u(view.main_pass_viewport.zw)) { return; } - let pixel_index = global_id.x + global_id.y * u32(view.viewport.z); + let pixel_index = global_id.x + global_id.y * u32(view.main_pass_viewport.z); var rng = pixel_index + constants.frame_index; let depth = textureLoad(depth_buffer, global_id.xy, 0); @@ -60,9 +60,9 @@ fn initial_and_temporal(@builtin(workgroup_id) workgroup_id: vec3, @builtin @compute @workgroup_size(8, 8, 1) fn spatial_and_shade(@builtin(global_invocation_id) global_id: vec3) { - if any(global_id.xy >= vec2u(view.viewport.zw)) { return; } + if any(global_id.xy >= vec2u(view.main_pass_viewport.zw)) { return; } - let pixel_index = global_id.x + global_id.y * u32(view.viewport.z); + let pixel_index = global_id.x + global_id.y * u32(view.main_pass_viewport.z); var rng = pixel_index + constants.frame_index; let depth = textureLoad(depth_buffer, global_id.xy, 0); @@ -137,12 +137,12 @@ fn generate_initial_reservoir(world_position: vec3, world_normal: vec3 fn load_temporal_reservoir(pixel_id: vec2, depth: f32, world_position: vec3, world_normal: vec3) -> Reservoir { let motion_vector = textureLoad(motion_vectors, pixel_id, 0).xy; - let temporal_pixel_id_float = round(vec2(pixel_id) - (motion_vector * view.viewport.zw)); + let temporal_pixel_id_float = round(vec2(pixel_id) - (motion_vector * view.main_pass_viewport.zw)); let temporal_pixel_id = vec2(temporal_pixel_id_float); // Check if the current pixel was off screen during the previous frame (current pixel is newly visible), // or if all temporal history should assumed to be invalid - if any(temporal_pixel_id_float < vec2(0.0)) || any(temporal_pixel_id_float >= view.viewport.zw) || bool(constants.reset) { + if any(temporal_pixel_id_float < vec2(0.0)) || any(temporal_pixel_id_float >= view.main_pass_viewport.zw) || bool(constants.reset) { return empty_reservoir(); } @@ -155,7 +155,7 @@ fn load_temporal_reservoir(pixel_id: vec2, depth: f32, world_position: vec3 return empty_reservoir(); } - let temporal_pixel_index = temporal_pixel_id.x + temporal_pixel_id.y * u32(view.viewport.z); + let temporal_pixel_index = temporal_pixel_id.x + temporal_pixel_id.y * u32(view.main_pass_viewport.z); var temporal_reservoir = di_reservoirs_a[temporal_pixel_index]; // Check if the light selected in the previous frame no longer exists in the current frame (e.g. entity despawned) @@ -183,7 +183,7 @@ fn load_spatial_reservoir(pixel_id: vec2, depth: f32, world_position: vec3< return empty_reservoir(); } - let spatial_pixel_index = spatial_pixel_id.x + spatial_pixel_id.y * u32(view.viewport.z); + let spatial_pixel_index = spatial_pixel_id.x + spatial_pixel_id.y * u32(view.main_pass_viewport.z); var spatial_reservoir = di_reservoirs_b[spatial_pixel_index]; if reservoir_valid(spatial_reservoir) { @@ -196,19 +196,19 @@ fn load_spatial_reservoir(pixel_id: vec2, depth: f32, world_position: vec3< fn get_neighbor_pixel_id(center_pixel_id: vec2, rng: ptr) -> vec2 { var spatial_id = vec2(center_pixel_id) + sample_disk(SPATIAL_REUSE_RADIUS_PIXELS, rng); - spatial_id = clamp(spatial_id, vec2(0.0), view.viewport.zw - 1.0); + spatial_id = clamp(spatial_id, vec2(0.0), view.main_pass_viewport.zw - 1.0); return vec2(spatial_id); } fn reconstruct_world_position(pixel_id: vec2, depth: f32) -> vec3 { - let uv = (vec2(pixel_id) + 0.5) / view.viewport.zw; + let uv = (vec2(pixel_id) + 0.5) / view.main_pass_viewport.zw; let xy_ndc = (uv - vec2(0.5)) * vec2(2.0, -2.0); let world_pos = view.world_from_clip * vec4(xy_ndc, depth, 1.0); return world_pos.xyz / world_pos.w; } fn reconstruct_previous_world_position(pixel_id: vec2, depth: f32) -> vec3 { - let uv = (vec2(pixel_id) + 0.5) / view.viewport.zw; + let uv = (vec2(pixel_id) + 0.5) / view.main_pass_viewport.zw; let xy_ndc = (uv - vec2(0.5)) * vec2(2.0, -2.0); let world_pos = previous_view.world_from_clip * vec4(xy_ndc, depth, 1.0); return world_pos.xyz / world_pos.w; diff --git a/crates/bevy_solari/src/realtime/restir_gi.wgsl b/crates/bevy_solari/src/realtime/restir_gi.wgsl index 3a74411d12a96..44545aa330671 100644 --- a/crates/bevy_solari/src/realtime/restir_gi.wgsl +++ b/crates/bevy_solari/src/realtime/restir_gi.wgsl @@ -28,9 +28,9 @@ const CONFIDENCE_WEIGHT_CAP = 30.0; @compute @workgroup_size(8, 8, 1) fn initial_and_temporal(@builtin(global_invocation_id) global_id: vec3) { - if any(global_id.xy >= vec2u(view.viewport.zw)) { return; } + if any(global_id.xy >= vec2u(view.main_pass_viewport.zw)) { return; } - let pixel_index = global_id.x + global_id.y * u32(view.viewport.z); + let pixel_index = global_id.x + global_id.y * u32(view.main_pass_viewport.z); var rng = pixel_index + constants.frame_index; let depth = textureLoad(depth_buffer, global_id.xy, 0); @@ -51,9 +51,9 @@ fn initial_and_temporal(@builtin(global_invocation_id) global_id: vec3) { @compute @workgroup_size(8, 8, 1) fn spatial_and_shade(@builtin(global_invocation_id) global_id: vec3) { - if any(global_id.xy >= vec2u(view.viewport.zw)) { return; } + if any(global_id.xy >= vec2u(view.main_pass_viewport.zw)) { return; } - let pixel_index = global_id.x + global_id.y * u32(view.viewport.z); + let pixel_index = global_id.x + global_id.y * u32(view.main_pass_viewport.z); var rng = pixel_index + constants.frame_index; let depth = textureLoad(depth_buffer, global_id.xy, 0); @@ -112,12 +112,12 @@ fn generate_initial_reservoir(world_position: vec3, world_normal: vec3 fn load_temporal_reservoir(pixel_id: vec2, depth: f32, world_position: vec3, world_normal: vec3) -> Reservoir { let motion_vector = textureLoad(motion_vectors, pixel_id, 0).xy; - let temporal_pixel_id_float = round(vec2(pixel_id) - (motion_vector * view.viewport.zw)); + let temporal_pixel_id_float = round(vec2(pixel_id) - (motion_vector * view.main_pass_viewport.zw)); let temporal_pixel_id = vec2(temporal_pixel_id_float); // Check if the current pixel was off screen during the previous frame (current pixel is newly visible), // or if all temporal history should assumed to be invalid - if any(temporal_pixel_id_float < vec2(0.0)) || any(temporal_pixel_id_float >= view.viewport.zw) || bool(constants.reset) { + if any(temporal_pixel_id_float < vec2(0.0)) || any(temporal_pixel_id_float >= view.main_pass_viewport.zw) || bool(constants.reset) { return empty_reservoir(); } @@ -130,7 +130,7 @@ fn load_temporal_reservoir(pixel_id: vec2, depth: f32, world_position: vec3 return empty_reservoir(); } - let temporal_pixel_index = temporal_pixel_id.x + temporal_pixel_id.y * u32(view.viewport.z); + let temporal_pixel_index = temporal_pixel_id.x + temporal_pixel_id.y * u32(view.main_pass_viewport.z); var temporal_reservoir = gi_reservoirs_a[temporal_pixel_index]; temporal_reservoir.confidence_weight = min(temporal_reservoir.confidence_weight, CONFIDENCE_WEIGHT_CAP); @@ -158,7 +158,7 @@ fn load_spatial_reservoir(pixel_id: vec2, depth: f32, world_position: vec3< return SpatialInfo(empty_reservoir(), spatial_world_position, spatial_world_normal, spatial_diffuse_brdf); } - let spatial_pixel_index = spatial_pixel_id.x + spatial_pixel_id.y * u32(view.viewport.z); + let spatial_pixel_index = spatial_pixel_id.x + spatial_pixel_id.y * u32(view.main_pass_viewport.z); let spatial_reservoir = gi_reservoirs_b[spatial_pixel_index]; return SpatialInfo(spatial_reservoir, spatial_world_position, spatial_world_normal, spatial_diffuse_brdf); @@ -166,7 +166,7 @@ fn load_spatial_reservoir(pixel_id: vec2, depth: f32, world_position: vec3< fn get_neighbor_pixel_id(center_pixel_id: vec2, rng: ptr) -> vec2 { var spatial_id = vec2(center_pixel_id) + sample_disk(SPATIAL_REUSE_RADIUS_PIXELS, rng); - spatial_id = clamp(spatial_id, vec2(0.0), view.viewport.zw - 1.0); + spatial_id = clamp(spatial_id, vec2(0.0), view.main_pass_viewport.zw - 1.0); return vec2(spatial_id); } @@ -195,14 +195,14 @@ fn isnan(x: f32) -> bool { } fn reconstruct_world_position(pixel_id: vec2, depth: f32) -> vec3 { - let uv = (vec2(pixel_id) + 0.5) / view.viewport.zw; + let uv = (vec2(pixel_id) + 0.5) / view.main_pass_viewport.zw; let xy_ndc = (uv - vec2(0.5)) * vec2(2.0, -2.0); let world_pos = view.world_from_clip * vec4(xy_ndc, depth, 1.0); return world_pos.xyz / world_pos.w; } fn reconstruct_previous_world_position(pixel_id: vec2, depth: f32) -> vec3 { - let uv = (vec2(pixel_id) + 0.5) / view.viewport.zw; + let uv = (vec2(pixel_id) + 0.5) / view.main_pass_viewport.zw; let xy_ndc = (uv - vec2(0.5)) * vec2(2.0, -2.0); let world_pos = previous_view.world_from_clip * vec4(xy_ndc, depth, 1.0); return world_pos.xyz / world_pos.w; diff --git a/crates/bevy_solari/src/scene/binder.rs b/crates/bevy_solari/src/scene/binder.rs index 0685c1dcf1acf..124d5d05dc2fc 100644 --- a/crates/bevy_solari/src/scene/binder.rs +++ b/crates/bevy_solari/src/scene/binder.rs @@ -71,14 +71,14 @@ pub fn prepare_raytracing_scene_bindings( let mut textures = CachedBindingArray::new(); let mut samplers = Vec::new(); let mut materials = StorageBufferList::::default(); - let mut tlas = TlasPackage::new(render_device.wgpu_device().create_tlas( - &CreateTlasDescriptor { + let mut tlas = render_device + .wgpu_device() + .create_tlas(&CreateTlasDescriptor { label: Some("tlas"), flags: AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: AccelerationStructureUpdateMode::Build, max_instances: instances_query.iter().len() as u32, - }, - )); + }); let mut transforms = StorageBufferList::::default(); let mut geometry_ids = StorageBufferList::::default(); let mut material_ids = StorageBufferList::::default(); @@ -115,13 +115,23 @@ pub fn prepare_raytracing_scene_bindings( let Some(emissive_texture_id) = process_texture(&material.emissive_texture) else { continue; }; + let Some(metallic_roughness_texture_id) = + process_texture(&material.metallic_roughness_texture) + else { + continue; + }; materials.get_mut().push(GpuMaterial { - base_color: material.base_color.to_linear(), - emissive: material.emissive, - base_color_texture_id, normal_map_texture_id, + base_color_texture_id, emissive_texture_id, + metallic_roughness_texture_id, + + base_color: LinearRgba::from(material.base_color).to_vec3(), + perceptual_roughness: material.perceptual_roughness, + emissive: material.emissive.to_vec3(), + metallic: material.metallic, + reflectance: LinearRgba::from(material.specular_tint).to_vec3() * material.reflectance, _padding: Default::default(), }); @@ -180,11 +190,12 @@ pub fn prepare_raytracing_scene_bindings( vertex_buffer_offset: vertex_slice.range.start, index_buffer_id, index_buffer_offset: index_slice.range.start, + triangle_count: (index_slice.range.len() / 3) as u32, }); material_ids.get_mut().push(material_id); - if material.emissive != LinearRgba::BLACK { + if material.emissive != Vec3::ZERO { light_sources .get_mut() .push(GpuLightSource::new_emissive_mesh_light( @@ -342,16 +353,22 @@ struct GpuInstanceGeometryIds { vertex_buffer_offset: u32, index_buffer_id: u32, index_buffer_offset: u32, + triangle_count: u32, } #[derive(ShaderType)] struct GpuMaterial { - base_color: LinearRgba, - emissive: LinearRgba, - base_color_texture_id: u32, normal_map_texture_id: u32, + base_color_texture_id: u32, emissive_texture_id: u32, - _padding: u32, + metallic_roughness_texture_id: u32, + + base_color: Vec3, + perceptual_roughness: f32, + emissive: Vec3, + metallic: f32, + reflectance: Vec3, + _padding: f32, } #[derive(ShaderType)] diff --git a/crates/bevy_solari/src/scene/brdf.wgsl b/crates/bevy_solari/src/scene/brdf.wgsl new file mode 100644 index 0000000000000..bc42203481928 --- /dev/null +++ b/crates/bevy_solari/src/scene/brdf.wgsl @@ -0,0 +1,56 @@ +#define_import_path bevy_solari::brdf + +#import bevy_pbr::lighting::{F_AB, D_GGX, V_SmithGGXCorrelated, fresnel, specular_multiscatter} +#import bevy_pbr::pbr_functions::{calculate_diffuse_color, calculate_F0} +#import bevy_render::maths::PI +#import bevy_solari::scene_bindings::ResolvedMaterial + +fn evaluate_brdf( + world_normal: vec3, + wo: vec3, + wi: vec3, + material: ResolvedMaterial, +) -> vec3 { + let diffuse_brdf = diffuse_brdf(material.base_color, material.metallic); + let specular_brdf = specular_brdf( + world_normal, + wo, + wi, + material.base_color, + material.metallic, + material.reflectance, + material.perceptual_roughness, + material.roughness, + ); + return diffuse_brdf + specular_brdf; +} + +fn diffuse_brdf(base_color: vec3, metallic: f32) -> vec3 { + let diffuse_color = calculate_diffuse_color(base_color, metallic, 0.0, 0.0); + return diffuse_color / PI; +} + +fn specular_brdf( + N: vec3, + V: vec3, + L: vec3, + base_color: vec3, + metallic: f32, + reflectance: vec3, + perceptual_roughness: f32, + roughness: f32, +) -> vec3 { + let H = normalize(L + V); + let NdotL = saturate(dot(N, L)); + let NdotH = saturate(dot(N, H)); + let LdotH = saturate(dot(L, H)); + let NdotV = max(dot(N, V), 0.0001); + + let F0 = calculate_F0(base_color, metallic, reflectance); + let F_ab = F_AB(perceptual_roughness, NdotV); + + let D = D_GGX(roughness, NdotH); + let Vs = V_SmithGGXCorrelated(roughness, NdotV, NdotL); + let F = fresnel(F0, LdotH); + return specular_multiscatter(D, Vs, F, F0, F_ab, 1.0); +} diff --git a/crates/bevy_solari/src/scene/mod.rs b/crates/bevy_solari/src/scene/mod.rs index f1af566c1c8ab..16352879a8d3a 100644 --- a/crates/bevy_solari/src/scene/mod.rs +++ b/crates/bevy_solari/src/scene/mod.rs @@ -31,10 +31,9 @@ pub struct RaytracingScenePlugin; impl Plugin for RaytracingScenePlugin { fn build(&self, app: &mut App) { + load_shader_library!(app, "brdf.wgsl"); load_shader_library!(app, "raytracing_scene_bindings.wgsl"); load_shader_library!(app, "sampling.wgsl"); - - app.register_type::(); } fn finish(&self, app: &mut App) { diff --git a/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl b/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl index eeed96ad8e818..7359ad9063e2d 100644 --- a/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl +++ b/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl @@ -1,10 +1,14 @@ #define_import_path bevy_solari::scene_bindings +#import bevy_pbr::lighting::perceptualRoughnessToRoughness +#import bevy_pbr::pbr_functions::calculate_tbn_mikktspace + struct InstanceGeometryIds { vertex_buffer_id: u32, vertex_buffer_offset: u32, index_buffer_id: u32, index_buffer_offset: u32, + triangle_count: u32, } struct VertexBuffer { vertices: array } @@ -34,12 +38,17 @@ fn unpack_vertex(packed: PackedVertex) -> Vertex { } struct Material { - base_color: vec4, - emissive: vec4, - base_color_texture_id: u32, normal_map_texture_id: u32, + base_color_texture_id: u32, emissive_texture_id: u32, - _padding: u32, + metallic_roughness_texture_id: u32, + + base_color: vec3, + perceptual_roughness: f32, + emissive: vec3, + metallic: f32, + reflectance: vec3, + _padding: f32, } const TEXTURE_MAP_NONE = 0xFFFFFFFFu; @@ -94,14 +103,20 @@ fn sample_texture(id: u32, uv: vec2) -> vec3 { struct ResolvedMaterial { base_color: vec3, emissive: vec3, + reflectance: vec3, + perceptual_roughness: f32, + roughness: f32, + metallic: f32, } struct ResolvedRayHitFull { world_position: vec3, world_normal: vec3, geometric_world_normal: vec3, + world_tangent: vec4, uv: vec2, triangle_area: f32, + triangle_count: u32, material: ResolvedMaterial, } @@ -118,6 +133,17 @@ fn resolve_material(material: Material, uv: vec2) -> ResolvedMaterial { m.emissive *= sample_texture(material.emissive_texture_id, uv); } + m.reflectance = material.reflectance; + + m.perceptual_roughness = material.perceptual_roughness; + m.metallic = material.metallic; + if material.metallic_roughness_texture_id != TEXTURE_MAP_NONE { + let metallic_roughness = sample_texture(material.metallic_roughness_texture_id, uv); + m.perceptual_roughness *= metallic_roughness.g; + m.metallic *= metallic_roughness.b; + } + m.roughness = clamp(m.perceptual_roughness * m.perceptual_roughness, 0.001, 1.0); + return m; } @@ -144,15 +170,20 @@ fn resolve_triangle_data_full(instance_id: u32, triangle_id: u32, barycentrics: let uv = mat3x2(vertices[0].uv, vertices[1].uv, vertices[2].uv) * barycentrics; + let local_tangent = mat3x3(vertices[0].tangent.xyz, vertices[1].tangent.xyz, vertices[2].tangent.xyz) * barycentrics; + let world_tangent = vec4( + normalize(mat3x3(transform[0].xyz, transform[1].xyz, transform[2].xyz) * local_tangent), + vertices[0].tangent.w, + ); + let local_normal = mat3x3(vertices[0].normal, vertices[1].normal, vertices[2].normal) * barycentrics; // TODO: Use barycentric lerp, ray_hit.object_to_world, cross product geo normal var world_normal = normalize(mat3x3(transform[0].xyz, transform[1].xyz, transform[2].xyz) * local_normal); let geometric_world_normal = world_normal; if material.normal_map_texture_id != TEXTURE_MAP_NONE { - let local_tangent = mat3x3(vertices[0].tangent.xyz, vertices[1].tangent.xyz, vertices[2].tangent.xyz) * barycentrics; - let world_tangent = normalize(mat3x3(transform[0].xyz, transform[1].xyz, transform[2].xyz) * local_tangent); - let N = world_normal; - let T = world_tangent; - let B = vertices[0].tangent.w * cross(N, T); + let TBN = calculate_tbn_mikktspace(world_normal, world_tangent); + let T = TBN[0]; + let B = TBN[1]; + let N = TBN[2]; let Nt = sample_texture(material.normal_map_texture_id, uv); world_normal = normalize(Nt.x * T + Nt.y * B + Nt.z * N); } @@ -163,5 +194,5 @@ fn resolve_triangle_data_full(instance_id: u32, triangle_id: u32, barycentrics: let resolved_material = resolve_material(material, uv); - return ResolvedRayHitFull(world_position, world_normal, geometric_world_normal, uv, triangle_area, resolved_material); + return ResolvedRayHitFull(world_position, world_normal, geometric_world_normal, world_tangent, uv, triangle_area, instance_geometry_ids.triangle_count, resolved_material); } diff --git a/crates/bevy_solari/src/scene/sampling.wgsl b/crates/bevy_solari/src/scene/sampling.wgsl index 298cf8ad679a0..8385f9b3999e6 100644 --- a/crates/bevy_solari/src/scene/sampling.wgsl +++ b/crates/bevy_solari/src/scene/sampling.wgsl @@ -1,8 +1,57 @@ #define_import_path bevy_solari::sampling +#import bevy_pbr::lighting::D_GGX #import bevy_pbr::utils::{rand_f, rand_vec2f, rand_u, rand_range_u} -#import bevy_render::maths::PI_2 -#import bevy_solari::scene_bindings::{trace_ray, RAY_T_MIN, RAY_T_MAX, light_sources, directional_lights, LightSource, LIGHT_SOURCE_KIND_DIRECTIONAL, resolve_triangle_data_full} +#import bevy_render::maths::{PI_2, orthonormalize} +#import bevy_solari::scene_bindings::{trace_ray, RAY_T_MIN, RAY_T_MAX, light_sources, directional_lights, LightSource, LIGHT_SOURCE_KIND_DIRECTIONAL, resolve_triangle_data_full, ResolvedRayHitFull} + +fn power_heuristic(f: f32, g: f32) -> f32 { + return f * f / (f * f + g * g); +} + +fn balance_heuristic(f: f32, g: f32) -> f32 { + return f / (f + g); +} + +// https://gpuopen.com/download/Bounded_VNDF_Sampling_for_Smith-GGX_Reflections.pdf (Listing 1) +fn sample_ggx_vndf(wi_tangent: vec3, roughness: f32, rng: ptr) -> vec3 { + let i = wi_tangent; + let rand = rand_vec2f(rng); + let i_std = normalize(vec3(i.xy * roughness, i.z)); + let phi = PI_2 * rand.x; + let a = roughness; + let s = 1.0 + length(vec2(i.xy)); + let a2 = a * a; + let s2 = s * s; + let k = (1.0 - a2) * s2 / (s2 + a2 * i.z * i.z); + let b = select(i_std.z, k * i_std.z, i.z > 0.0); + let z = fma(1.0 - rand.y, 1.0 + b, -b); + let sin_theta = sqrt(saturate(1.0 - z * z)); + let o_std = vec3(sin_theta * cos(phi), sin_theta * sin(phi), z); + let m_std = i_std + o_std; + let m = normalize(vec3(m_std.xy * roughness, m_std.z)); + return 2.0 * dot(i, m) * m - i; +} + +// https://gpuopen.com/download/Bounded_VNDF_Sampling_for_Smith-GGX_Reflections.pdf (Listing 2) +fn ggx_vndf_pdf(wi_tangent: vec3, wo_tangent: vec3, roughness: f32) -> f32 { + let i = wi_tangent; + let o = wo_tangent; + let m = normalize(i + o); + let ndf = D_GGX(roughness, saturate(m.z)); + let ai = roughness * i.xy; + let len2 = dot(ai, ai); + let t = sqrt(len2 + i.z * i.z); + if i.z >= 0.0 { + let a = roughness; + let s = 1.0 + length(i.xy); + let a2 = a * a; + let s2 = s * s; + let k = (1.0 - a2) * s2 / (s2 + a2 * i.z * i.z); + return ndf / (2.0 * (k * i.z + t)); + } + return ndf * (t - i.z) / (2.0 * len2); +} struct LightSample { light_id: u32, @@ -39,6 +88,12 @@ fn sample_random_light(ray_origin: vec3, origin_world_normal: vec3, rn return light_contribution; } +fn random_light_pdf(hit: ResolvedRayHitFull) -> f32 { + let light_count = arrayLength(&light_sources); + let p_light = 1.0 / f32(light_count); + return p_light / (hit.triangle_area * f32(hit.triangle_count)); +} + fn generate_random_light_sample(rng: ptr) -> GenerateRandomLightSampleResult { let light_count = arrayLength(&light_sources); let light_id = rand_range_u(light_count, rng); diff --git a/crates/bevy_solari/src/scene/types.rs b/crates/bevy_solari/src/scene/types.rs index 7b4c164df82d4..967cbf8a5b7aa 100644 --- a/crates/bevy_solari/src/scene/types.rs +++ b/crates/bevy_solari/src/scene/types.rs @@ -15,7 +15,13 @@ use derive_more::derive::From; /// and use [`bevy_mesh::Indices::U32`]. /// /// The material used for this entity must be [`MeshMaterial3d`]. -#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] +#[derive(Component, Clone, Debug, Deref, DerefMut, Reflect, PartialEq, Eq, From)] #[reflect(Component, Default, Clone, PartialEq)] #[require(MeshMaterial3d, Transform, SyncToRenderWorld)] pub struct RaytracingMesh3d(pub Handle); + +impl Default for RaytracingMesh3d { + fn default() -> Self { + Self(Handle::default()) + } +} diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 91982938949ea..2a2a7353d28df 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -84,28 +84,23 @@ impl Plugin for SpritePlugin { app.add_plugins(TextureAtlasPlugin); } - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .add_plugins(( - Mesh2dRenderPlugin, - ColorMaterialPlugin, - TilemapChunkPlugin, - TilemapChunkMaterialPlugin, - )) - .add_systems( - PostUpdate, + app.add_plugins(( + Mesh2dRenderPlugin, + ColorMaterialPlugin, + TilemapChunkPlugin, + TilemapChunkMaterialPlugin, + )) + .add_systems( + PostUpdate, + ( + calculate_bounds_2d.in_set(VisibilitySystems::CalculateBounds), ( - calculate_bounds_2d.in_set(VisibilitySystems::CalculateBounds), - ( - compute_slices_on_asset_event.before(AssetEventSystems), - compute_slices_on_sprite_change, - ) - .in_set(SpriteSystems::ComputeSlices), - ), - ); + compute_slices_on_asset_event.before(AssetEventSystems), + compute_slices_on_sprite_change, + ) + .in_set(SpriteSystems::ComputeSlices), + ), + ); #[cfg(feature = "bevy_sprite_picking_backend")] app.add_plugins(SpritePickingPlugin); @@ -165,10 +160,10 @@ pub fn calculate_bounds_2d( >, ) { for (entity, mesh_handle) in &meshes_without_aabb { - if let Some(mesh) = meshes.get(&mesh_handle.0) { - if let Some(aabb) = mesh.compute_aabb() { - commands.entity(entity).try_insert(aabb); - } + if let Some(mesh) = meshes.get(&mesh_handle.0) + && let Some(aabb) = mesh.compute_aabb() + { + commands.entity(entity).try_insert(aabb); } } for (entity, sprite, anchor) in &sprites_to_recalculate_aabb { diff --git a/crates/bevy_sprite/src/mesh2d/color_material.wgsl b/crates/bevy_sprite/src/mesh2d/color_material.wgsl index a2dbe4e055937..bb3972426bbe5 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.wgsl +++ b/crates/bevy_sprite/src/mesh2d/color_material.wgsl @@ -21,9 +21,9 @@ const COLOR_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 0u; // (0u32 const COLOR_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 1073741824u; // (1u32 << 30) const COLOR_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 2147483648u; // (2u32 << 30) -@group(2) @binding(0) var material: ColorMaterial; -@group(2) @binding(1) var texture: texture_2d; -@group(2) @binding(2) var texture_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var material: ColorMaterial; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var texture: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(2) var texture_sampler: sampler; @fragment fn fragment( diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 8d575d8be4edb..84d8c80827934 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -24,7 +24,7 @@ use bevy_platform::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::camera::extract_cameras; use bevy_render::render_phase::{DrawFunctionId, InputUniformIndex}; -use bevy_render::render_resource::CachedRenderPipelineId; +use bevy_render::render_resource::{CachedRenderPipelineId, ShaderDefVal}; use bevy_render::view::RenderVisibleEntities; use bevy_render::RenderStartup; use bevy_render::{ @@ -52,6 +52,8 @@ use core::{hash::Hash, marker::PhantomData}; use derive_more::derive::From; use tracing::error; +pub const MATERIAL_2D_BIND_GROUP_INDEX: usize = 2; + /// Materials are used alongside [`Material2dPlugin`], [`Mesh2d`], and [`MeshMaterial2d`] /// to spawn entities that are rendered with a specific [`Material2d`] type. They serve as an easy to use high level /// way to render [`Mesh2d`] entities with custom shader logic. @@ -445,6 +447,16 @@ where layout: &MeshVertexBufferLayoutRef, ) -> Result { let mut descriptor = self.mesh2d_pipeline.specialize(key.mesh_key, layout)?; + descriptor.vertex.shader_defs.push(ShaderDefVal::UInt( + "MATERIAL_BIND_GROUP".into(), + MATERIAL_2D_BIND_GROUP_INDEX as u32, + )); + if let Some(ref mut fragment) = descriptor.fragment { + fragment.shader_defs.push(ShaderDefVal::UInt( + "MATERIAL_BIND_GROUP".into(), + MATERIAL_2D_BIND_GROUP_INDEX as u32, + )); + } if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -492,7 +504,7 @@ pub(super) type DrawMaterial2d = ( SetItemPipeline, SetMesh2dViewBindGroup<0>, SetMesh2dBindGroup<1>, - SetMaterial2dBindGroup, + SetMaterial2dBindGroup, DrawMesh2d, ); diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 17d693b990d44..0f44a59546af9 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -27,6 +27,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingMode, camera::ExtractedCamera, + diagnostic::RecordDiagnostics, extract_resource::ExtractResource, mesh::{ allocator::{MeshAllocator, SlabId}, @@ -86,9 +87,6 @@ impl Plugin for Wireframe2dPlugin { )) .init_asset::() .init_resource::>() - .register_type::() - .register_type::() - .register_type::() .init_resource::() .init_resource::() .add_systems(Startup, setup_global_wireframe_material) @@ -384,13 +382,16 @@ impl ViewNode for Wireframe2dNode { return Ok(()); }; + let diagnostics = render_context.diagnostic_recorder(); + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("wireframe_2d_pass"), + label: Some("wireframe_2d"), color_attachments: &[Some(target.get_color_attachment())], depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), timestamp_writes: None, occlusion_query_set: None, }); + let pass_span = diagnostics.pass_span(&mut render_pass, "wireframe_2d"); if let Some(viewport) = camera.viewport.as_ref() { render_pass.set_camera_viewport(viewport); @@ -401,6 +402,8 @@ impl ViewNode for Wireframe2dNode { return Err(NodeRunError::DrawError(err)); } + pass_span.end(&mut render_pass); + Ok(()) } } diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index cda9955b95282..a3a7c3a3db24f 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -76,9 +76,6 @@ pub struct SpritePickingPlugin; impl Plugin for SpritePickingPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .register_type::() - .register_type::() - .register_type::() .add_systems(PreUpdate, sprite_picking.in_set(PickingSystems::Backend)); } } @@ -146,11 +143,11 @@ fn sprite_picking( }; let viewport_pos = location.position; - if let Some(viewport) = camera.logical_viewport_rect() { - if !viewport.contains(viewport_pos) { - // The pointer is outside the viewport, skip it - continue; - } + if let Some(viewport) = camera.logical_viewport_rect() + && !viewport.contains(viewport_pos) + { + // The pointer is outside the viewport, skip it + continue; } let Ok(cursor_ray_world) = camera.viewport_to_world(cam_transform, viewport_pos) else { diff --git a/crates/bevy_sprite/src/tilemap_chunk/mod.rs b/crates/bevy_sprite/src/tilemap_chunk/mod.rs index df340090699a6..9b9758a5da873 100644 --- a/crates/bevy_sprite/src/tilemap_chunk/mod.rs +++ b/crates/bevy_sprite/src/tilemap_chunk/mod.rs @@ -31,10 +31,7 @@ pub struct TilemapChunkPlugin; impl Plugin for TilemapChunkPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .register_type::() - .init_resource::() + app.init_resource::() .add_systems(Update, update_tilemap_chunk_indices); } } diff --git a/crates/bevy_sprite/src/tilemap_chunk/tilemap_chunk_material.wgsl b/crates/bevy_sprite/src/tilemap_chunk/tilemap_chunk_material.wgsl index 0b2f38488cbc3..a1442aa006f9a 100644 --- a/crates/bevy_sprite/src/tilemap_chunk/tilemap_chunk_material.wgsl +++ b/crates/bevy_sprite/src/tilemap_chunk/tilemap_chunk_material.wgsl @@ -4,9 +4,9 @@ mesh2d_vertex_output::VertexOutput, } -@group(2) @binding(0) var tileset: texture_2d_array; -@group(2) @binding(1) var tileset_sampler: sampler; -@group(2) @binding(2) var tile_data: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var tileset: texture_2d_array; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var tileset_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(2) var tile_data: texture_2d; struct TileData { tileset_index: u32, diff --git a/crates/bevy_state/src/commands.rs b/crates/bevy_state/src/commands.rs index 46170117265d1..2484a2d8a2b95 100644 --- a/crates/bevy_state/src/commands.rs +++ b/crates/bevy_state/src/commands.rs @@ -19,10 +19,10 @@ impl CommandsStatesExt for Commands<'_, '_> { fn set_state(&mut self, state: S) { self.queue(move |w: &mut World| { let mut next = w.resource_mut::>(); - if let NextState::Pending(prev) = &*next { - if *prev != state { - debug!("overwriting next state {prev:?} with {state:?}"); - } + if let NextState::Pending(prev) = &*next + && *prev != state + { + debug!("overwriting next state {prev:?} with {state:?}"); } next.set(state); }); diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index e28d7fc88d319..fd16e8b3e7de1 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -9,42 +9,27 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -default = ["std", "async_executor"] +default = ["async_executor", "futures-lite"] -# Functionality - -## Enables multi-threading support. -## Without this feature, all tasks will be run on a single thread. +# Enables multi-threading support. +# Without this feature, all tasks will be run on a single thread. multi_threaded = [ - "std", + "bevy_platform/std", "dep:async-channel", "dep:concurrent-queue", "async_executor", ] -## Uses `async-executor` as a task execution backend. -## This backend is incompatible with `no_std` targets. -async_executor = ["std", "dep:async-executor"] - -# Platform Compatibility +# Uses `async-executor` as a task execution backend. +# This backend is incompatible with `no_std` targets. +async_executor = ["bevy_platform/std", "dep:async-executor"] -## Allows access to the `std` crate. Enabling this feature will prevent compilation -## on `no_std` targets, but provides access to certain additional features on -## supported platforms. -std = ["futures-lite/std", "async-task/std", "bevy_platform/std"] +# Provide an implementation of `block_on` from `futures-lite`. +futures-lite = ["bevy_platform/std", "futures-lite/std"] -## `critical-section` provides the building blocks for synchronization primitives -## on all platforms, including `no_std`. -critical-section = ["bevy_platform/critical-section"] - -## Enables use of browser APIs. -## Note this is currently only applicable on `wasm32` architectures. -web = [ - "bevy_platform/web", - "dep:wasm-bindgen-futures", - "dep:pin-project", - "dep:futures-channel", -] +# Use async-io's implementation of block_on instead of futures-lite's implementation. +# This is preferred if your application uses async-io. +async-io = ["bevy_platform/std", "dep:async-io"] [dependencies] bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ @@ -59,7 +44,6 @@ derive_more = { version = "2", default-features = false, features = [ "deref", "deref_mut", ] } -cfg-if = "1.0.0" async-executor = { version = "1.11", optional = true } async-channel = { version = "2.3.0", optional = true } async-io = { version = "2.0.0", optional = true } @@ -70,9 +54,8 @@ crossbeam-queue = { version = "0.3", default-features = false, features = [ ] } [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen-futures = { version = "0.4", optional = true } -pin-project = { version = "1", optional = true } -futures-channel = { version = "0.3", optional = true } +pin-project = { version = "1" } +futures-channel = { version = "0.3", default-features = false } [target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies] async-task = { version = "4.4.0", default-features = false, features = [ @@ -85,6 +68,11 @@ atomic-waker = { version = "1", default-features = false, features = [ "portable-atomic", ] } +[dev-dependencies] +futures-lite = { version = "2.0.1", default-features = false, features = [ + "std", +] } + [lints] workspace = true diff --git a/crates/bevy_tasks/src/executor.rs b/crates/bevy_tasks/src/executor.rs index 01bbe4a669258..fcce0e2985536 100644 --- a/crates/bevy_tasks/src/executor.rs +++ b/crates/bevy_tasks/src/executor.rs @@ -14,8 +14,8 @@ use core::{ }; use derive_more::{Deref, DerefMut}; -cfg_if::cfg_if! { - if #[cfg(feature = "async_executor")] { +crate::cfg::async_executor! { + if { type ExecutorInner<'a> = async_executor::Executor<'a>; type LocalExecutorInner<'a> = async_executor::LocalExecutor<'a>; } else { @@ -24,8 +24,9 @@ cfg_if::cfg_if! { } } -#[cfg(all(feature = "multi_threaded", not(target_arch = "wasm32")))] -pub use async_task::FallibleTask; +crate::cfg::multi_threaded! { + pub use async_task::FallibleTask; +} /// Wrapper around a multi-threading-aware async executor. /// Spawning will generally require tasks to be `Send` and `Sync` to allow multiple diff --git a/crates/bevy_tasks/src/futures.rs b/crates/bevy_tasks/src/futures.rs index a28138e0ecaa2..7bc6c59c00f5c 100644 --- a/crates/bevy_tasks/src/futures.rs +++ b/crates/bevy_tasks/src/futures.rs @@ -49,7 +49,7 @@ fn noop_raw_waker() -> RawWaker { RawWaker::new(core::ptr::null(), &NOOP_WAKER_VTABLE) } -fn noop_waker() -> Waker { +pub(crate) fn noop_waker() -> Waker { // SAFETY: the `RawWakerVTable` is just a big noop and doesn't violate any of the rules in `RawWakerVTable`s documentation // (which talks about retaining and releasing any "resources", of which there are none in this case) unsafe { Waker::from_raw(noop_raw_waker()) } diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index 66899ef36f095..1aa4598243a0c 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -6,28 +6,59 @@ )] #![no_std] -#[cfg(feature = "std")] -extern crate std; +/// Configuration information for this crate. +pub mod cfg { + pub(crate) use bevy_platform::cfg::*; -extern crate alloc; + pub use bevy_platform::cfg::{alloc, std, web}; + + define_alias! { + #[cfg(feature = "async_executor")] => { + /// Indicates `async_executor` is used as the future execution backend. + async_executor + } + + #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] => { + /// Indicates multithreading support. + multi_threaded + } + + #[cfg(target_arch = "wasm32")] => { + /// Indicates the current target requires additional `Send` bounds. + conditional_send + } + + #[cfg(feature = "async-io")] => { + /// Indicates `async-io` will be used for the implementation of `block_on`. + async_io + } -mod conditional_send { - cfg_if::cfg_if! { - if #[cfg(target_arch = "wasm32")] { - /// Use [`ConditionalSend`] to mark an optional Send trait bound. Useful as on certain platforms (eg. Wasm), - /// futures aren't Send. - pub trait ConditionalSend {} - impl ConditionalSend for T {} - } else { - /// Use [`ConditionalSend`] to mark an optional Send trait bound. Useful as on certain platforms (eg. Wasm), - /// futures aren't Send. - pub trait ConditionalSend: Send {} - impl ConditionalSend for T {} + #[cfg(feature = "futures-lite")] => { + /// Indicates `futures-lite` will be used for the implementation of `block_on`. + futures_lite } } } -pub use conditional_send::*; +cfg::std! { + extern crate std; +} + +extern crate alloc; + +cfg::conditional_send! { + if { + /// Use [`ConditionalSend`] to mark an optional Send trait bound. Useful as on certain platforms (eg. Wasm), + /// futures aren't Send. + pub trait ConditionalSend {} + impl ConditionalSend for T {} + } else { + /// Use [`ConditionalSend`] to mark an optional Send trait bound. Useful as on certain platforms (eg. Wasm), + /// futures aren't Send. + pub trait ConditionalSend: Send {} + impl ConditionalSend for T {} + } +} /// Use [`ConditionalSendFuture`] for a future with an optional Send trait bound, as on certain platforms (eg. Wasm), /// futures aren't Send. @@ -42,27 +73,38 @@ pub type BoxedFuture<'a, T> = core::pin::Pin { pub use async_io::block_on; - } else { + } + cfg::futures_lite => { pub use futures_lite::future::block_on; } + _ => { + /// Blocks on the supplied `future`. + /// This implementation will busy-wait until it is completed. + /// Consider enabling the `async-io` or `futures-lite` features. + pub fn block_on(future: impl Future) -> T { + use core::task::{Poll, Context}; + + // Pin the future on the stack. + let mut future = core::pin::pin!(future); + + // We don't care about the waker as we're just going to poll as fast as possible. + let waker = futures::noop_waker(); + let cx = &mut Context::from_waker(&waker); + + // Keep polling until the future is ready. + loop { + match future.as_mut().poll(cx) { + Poll::Ready(output) => return output, + Poll::Pending => core::hint::spin_loop(), + } + } + } + } } mod iter; @@ -96,38 +158,28 @@ pub use futures_lite; pub mod prelude { #[doc(hidden)] pub use crate::{ + block_on, iter::ParallelIterator, slice::{ParallelSlice, ParallelSliceMut}, usages::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool}, }; - - #[cfg(feature = "std")] - #[doc(hidden)] - pub use crate::block_on; } -cfg_if::cfg_if! { - if #[cfg(feature = "std")] { - use core::num::NonZero; - - /// Gets the logical CPU core count available to the current process. - /// - /// This is identical to [`std::thread::available_parallelism`], except - /// it will return a default value of 1 if it internally errors out. - /// - /// This will always return at least 1. - pub fn available_parallelism() -> usize { +/// Gets the logical CPU core count available to the current process. +/// +/// This is identical to `std::thread::available_parallelism`, except +/// it will return a default value of 1 if it internally errors out. +/// +/// This will always return at least 1. +pub fn available_parallelism() -> usize { + cfg::switch! {{ + cfg::std => { std::thread::available_parallelism() - .map(NonZero::::get) + .map(core::num::NonZero::::get) .unwrap_or(1) } - } else { - /// Gets the logical CPU core count available to the current process. - /// - /// This will always return at least 1. - pub fn available_parallelism() -> usize { - // Without access to std, assume a single thread is available + _ => { 1 } - } + }} } diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index 0f9488bcd0f33..d7f74c2d66c86 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -4,32 +4,26 @@ use core::{cell::RefCell, future::Future, marker::PhantomData, mem}; use crate::Task; -#[cfg(feature = "std")] -use std::thread_local; +crate::cfg::std! { + if { + use std::thread_local; + use crate::executor::LocalExecutor; -#[cfg(not(feature = "std"))] -use bevy_platform::sync::{Mutex, PoisonError}; + thread_local! { + static LOCAL_EXECUTOR: LocalExecutor<'static> = const { LocalExecutor::new() }; + } -#[cfg(feature = "std")] -use crate::executor::LocalExecutor; + type ScopeResult = alloc::rc::Rc>>; + } else { + use bevy_platform::sync::{Mutex, PoisonError}; + use crate::executor::Executor as LocalExecutor; -#[cfg(not(feature = "std"))] -use crate::executor::Executor as LocalExecutor; + static LOCAL_EXECUTOR: LocalExecutor<'static> = const { LocalExecutor::new() }; -#[cfg(feature = "std")] -thread_local! { - static LOCAL_EXECUTOR: LocalExecutor<'static> = const { LocalExecutor::new() }; + type ScopeResult = Arc>>; + } } -#[cfg(not(feature = "std"))] -static LOCAL_EXECUTOR: LocalExecutor<'static> = const { LocalExecutor::new() }; - -#[cfg(feature = "std")] -type ScopeResult = alloc::rc::Rc>>; - -#[cfg(not(feature = "std"))] -type ScopeResult = Arc>>; - /// Used to create a [`TaskPool`]. #[derive(Debug, Default, Clone)] pub struct TaskPoolBuilder {} @@ -173,16 +167,15 @@ impl TaskPool { let results = scope.results.borrow(); results .iter() - .map(|result| { - #[cfg(feature = "std")] - return result.borrow_mut().take().unwrap(); - - #[cfg(not(feature = "std"))] - { + .map(|result| crate::cfg::switch! {{ + crate::cfg::std => { + result.borrow_mut().take().unwrap() + } + _ => { let mut lock = result.lock().unwrap_or_else(PoisonError::into_inner); lock.take().unwrap() } - }) + }}) .collect() } @@ -199,10 +192,11 @@ impl TaskPool { where T: 'static + MaybeSend + MaybeSync, { - cfg_if::cfg_if! { - if #[cfg(all(target_arch = "wasm32", feature = "web"))] { + crate::cfg::switch! {{ + crate::cfg::web => { Task::wrap_future(future) - } else if #[cfg(feature = "std")] { + } + crate::cfg::std => { LOCAL_EXECUTOR.with(|executor| { let task = executor.spawn(future); // Loop until all tasks are done @@ -210,16 +204,15 @@ impl TaskPool { Task::new(task) }) - } else { - { - let task = LOCAL_EXECUTOR.spawn(future); - // Loop until all tasks are done - while LOCAL_EXECUTOR.try_tick() {} + } + _ => { + let task = LOCAL_EXECUTOR.spawn(future); + // Loop until all tasks are done + while LOCAL_EXECUTOR.try_tick() {} - Task::new(task) - } + Task::new(task) } - } + }} } /// Spawns a static future on the JS event loop. This is exactly the same as [`TaskPool::spawn`]. @@ -248,11 +241,14 @@ impl TaskPool { where F: FnOnce(&LocalExecutor) -> R, { - #[cfg(feature = "std")] - return LOCAL_EXECUTOR.with(f); - - #[cfg(not(feature = "std"))] - return f(&LOCAL_EXECUTOR); + crate::cfg::switch! {{ + crate::cfg::std => { + LOCAL_EXECUTOR.with(f) + } + _ => { + f(&LOCAL_EXECUTOR) + } + }} } } @@ -304,35 +300,31 @@ impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> { let f = async move { let temp_result = f.await; - #[cfg(feature = "std")] - result.borrow_mut().replace(temp_result); - - #[cfg(not(feature = "std"))] - { - let mut lock = result.lock().unwrap_or_else(PoisonError::into_inner); - *lock = Some(temp_result); + crate::cfg::std! { + if { + result.borrow_mut().replace(temp_result); + } else { + let mut lock = result.lock().unwrap_or_else(PoisonError::into_inner); + *lock = Some(temp_result); + } } }; self.executor.spawn(f).detach(); } } -#[cfg(feature = "std")] -mod send_sync_bounds { - pub trait MaybeSend {} - impl MaybeSend for T {} - - pub trait MaybeSync {} - impl MaybeSync for T {} -} - -#[cfg(not(feature = "std"))] -mod send_sync_bounds { - pub trait MaybeSend: Send {} - impl MaybeSend for T {} - - pub trait MaybeSync: Sync {} - impl MaybeSync for T {} +crate::cfg::std! { + if { + pub trait MaybeSend {} + impl MaybeSend for T {} + + pub trait MaybeSync {} + impl MaybeSync for T {} + } else { + pub trait MaybeSend: Send {} + impl MaybeSend for T {} + + pub trait MaybeSync: Sync {} + impl MaybeSync for T {} + } } - -use send_sync_bounds::{MaybeSend, MaybeSync}; diff --git a/crates/bevy_tasks/src/usages.rs b/crates/bevy_tasks/src/usages.rs index 8b08d5941c6bd..40cabd76ac5e6 100644 --- a/crates/bevy_tasks/src/usages.rs +++ b/crates/bevy_tasks/src/usages.rs @@ -75,32 +75,35 @@ taskpool! { (IO_TASK_POOL, IoTaskPool) } -/// A function used by `bevy_app` to tick the global tasks pools on the main thread. -/// This will run a maximum of 100 local tasks per executor per call to this function. -/// -/// # Warning -/// -/// This function *must* be called on the main thread, or the task pools will not be updated appropriately. -#[cfg(not(all(target_arch = "wasm32", feature = "web")))] -pub fn tick_global_task_pools_on_main_thread() { - COMPUTE_TASK_POOL - .get() - .unwrap() - .with_local_executor(|compute_local_executor| { - ASYNC_COMPUTE_TASK_POOL +crate::cfg::web! { + if {} else { + /// A function used by `bevy_app` to tick the global tasks pools on the main thread. + /// This will run a maximum of 100 local tasks per executor per call to this function. + /// + /// # Warning + /// + /// This function *must* be called on the main thread, or the task pools will not be updated appropriately. + pub fn tick_global_task_pools_on_main_thread() { + COMPUTE_TASK_POOL .get() .unwrap() - .with_local_executor(|async_local_executor| { - IO_TASK_POOL + .with_local_executor(|compute_local_executor| { + ASYNC_COMPUTE_TASK_POOL .get() .unwrap() - .with_local_executor(|io_local_executor| { - for _ in 0..100 { - compute_local_executor.try_tick(); - async_local_executor.try_tick(); - io_local_executor.try_tick(); - } + .with_local_executor(|async_local_executor| { + IO_TASK_POOL + .get() + .unwrap() + .with_local_executor(|io_local_executor| { + for _ in 0..100 { + compute_local_executor.try_tick(); + async_local_executor.try_tick(); + io_local_executor.try_tick(); + } + }); }); }); - }); + } + } } diff --git a/crates/bevy_tasks/src/wasm_task.rs b/crates/bevy_tasks/src/wasm_task.rs index 0cc569c47913d..91eac7304ddec 100644 --- a/crates/bevy_tasks/src/wasm_task.rs +++ b/crates/bevy_tasks/src/wasm_task.rs @@ -1,13 +1,14 @@ use alloc::boxed::Box; use core::{ any::Any, - future::{Future, IntoFuture}, + future::Future, panic::{AssertUnwindSafe, UnwindSafe}, pin::Pin, task::{Context, Poll}, }; use futures_channel::oneshot; +use bevy_platform::exports::wasm_bindgen_futures; /// Wraps an asynchronous task, a spawned future. /// @@ -24,7 +25,7 @@ impl Task { let value = CatchUnwind(AssertUnwindSafe(future)).await; let _ = sender.send(value); }); - Self(receiver.into_future()) + Self(receiver) } /// When building for Wasm, this method has no effect. @@ -60,12 +61,14 @@ impl Future for Task { // NOTE: Propagating the panic here sorta has parity with the async_executor behavior. // For those tasks, polling them after a panic returns a `None` which gets `unwrap`ed, so // using `resume_unwind` here is essentially keeping the same behavior while adding more information. - #[cfg(feature = "std")] - Poll::Ready(Ok(Err(panic))) => std::panic::resume_unwind(panic), - #[cfg(not(feature = "std"))] - Poll::Ready(Ok(Err(_panic))) => { - unreachable!("catching a panic is only possible with std") - } + Poll::Ready(Ok(Err(_panic))) => crate::cfg::switch! {{ + crate::cfg::std => { + std::panic::resume_unwind(_panic) + } + _ => { + unreachable!("catching a panic is only possible with std") + } + }}, Poll::Ready(Err(_)) => panic!("Polled a task after it was cancelled"), Poll::Pending => Poll::Pending, } @@ -82,11 +85,14 @@ impl Future for CatchUnwind { fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let f = AssertUnwindSafe(|| self.project().0.poll(cx)); - #[cfg(feature = "std")] - let result = std::panic::catch_unwind(f)?; - - #[cfg(not(feature = "std"))] - let result = f(); + let result = crate::cfg::switch! {{ + crate::cfg::std => { + std::panic::catch_unwind(f)? + } + _ => { + f() + } + }}; result.map(Ok) } diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index 58bcfb1c5b7b2..b99c329ba1348 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -37,7 +37,6 @@ cosmic-text = { version = "0.14", features = ["shape-run-cache"] } thiserror = { version = "2", default-features = false } serde = { version = "1", features = ["derive"] } smallvec = { version = "1", default-features = false } -unicode-bidi = "0.3.13" sys-locale = "0.3.0" tracing = { version = "0.1", default-features = false, features = ["std"] } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index b36f5fa2bb0d7..46af2b04242c5 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -67,8 +67,6 @@ pub mod prelude { } use bevy_app::{prelude::*, AnimationSystems}; -#[cfg(feature = "default_font")] -use bevy_asset::{load_internal_binary_asset, Handle}; use bevy_asset::{AssetApp, AssetEventSystems}; use bevy_ecs::prelude::*; use bevy_render::{ @@ -98,16 +96,6 @@ pub type Update2dText = Text2dUpdateSystems; impl Plugin for TextPlugin { fn build(&self, app: &mut App) { app.init_asset::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() .init_asset_loader::() .init_resource::() .init_resource::() @@ -141,11 +129,11 @@ impl Plugin for TextPlugin { } #[cfg(feature = "default_font")] - load_internal_binary_asset!( - app, - Handle::default(), - "FiraMono-subset.ttf", - |bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() } - ); + { + use bevy_asset::{AssetId, Assets}; + let mut assets = app.world_mut().resource_mut::>(); + let asset = Font::try_from_bytes(DEFAULT_FONT_DATA.to_vec()).unwrap(); + assets.insert(AssetId::default(), asset); + }; } } diff --git a/crates/bevy_text/src/text_access.rs b/crates/bevy_text/src/text_access.rs index 7aafa28ef63e2..3c1e83c959e3a 100644 --- a/crates/bevy_text/src/text_access.rs +++ b/crates/bevy_text/src/text_access.rs @@ -74,7 +74,7 @@ pub struct TextReader<'w, 's, R: TextRoot> { impl<'w, 's, R: TextRoot> TextReader<'w, 's, R> { /// Returns an iterator over text spans in a text block, starting with the root entity. - pub fn iter(&mut self, root_entity: Entity) -> TextSpanIter { + pub fn iter(&mut self, root_entity: Entity) -> TextSpanIter<'_, R> { let stack = self.scratch.take(); TextSpanIter { @@ -254,7 +254,13 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> { &mut self, root_entity: Entity, index: usize, - ) -> Option<(Entity, usize, Mut, Mut, Mut)> { + ) -> Option<( + Entity, + usize, + Mut<'_, String>, + Mut<'_, TextFont>, + Mut<'_, TextColor>, + )> { // Root if index == 0 { let (text, font, color) = self.roots.get_mut(root_entity).ok()?; @@ -321,17 +327,17 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> { } /// Gets the text value of a text span within a text block at a specific index in the flattened span list. - pub fn get_text(&mut self, root_entity: Entity, index: usize) -> Option> { + pub fn get_text(&mut self, root_entity: Entity, index: usize) -> Option> { self.get(root_entity, index).map(|(_, _, text, ..)| text) } /// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list. - pub fn get_font(&mut self, root_entity: Entity, index: usize) -> Option> { + pub fn get_font(&mut self, root_entity: Entity, index: usize) -> Option> { self.get(root_entity, index).map(|(_, _, _, font, _)| font) } /// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list. - pub fn get_color(&mut self, root_entity: Entity, index: usize) -> Option> { + pub fn get_color(&mut self, root_entity: Entity, index: usize) -> Option> { self.get(root_entity, index) .map(|(_, _, _, _, color)| color) } @@ -339,21 +345,21 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> { /// Gets the text value of a text span within a text block at a specific index in the flattened span list. /// /// Panics if there is no span at the requested index. - pub fn text(&mut self, root_entity: Entity, index: usize) -> Mut { + pub fn text(&mut self, root_entity: Entity, index: usize) -> Mut<'_, String> { self.get_text(root_entity, index).unwrap() } /// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list. /// /// Panics if there is no span at the requested index. - pub fn font(&mut self, root_entity: Entity, index: usize) -> Mut { + pub fn font(&mut self, root_entity: Entity, index: usize) -> Mut<'_, TextFont> { self.get_font(root_entity, index).unwrap() } /// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list. /// /// Panics if there is no span at the requested index. - pub fn color(&mut self, root_entity: Entity, index: usize) -> Mut { + pub fn color(&mut self, root_entity: Entity, index: usize) -> Mut<'_, TextColor> { self.get_color(root_entity, index).unwrap() } diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index 8173b7c91b8e2..792f9f03b284a 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -76,8 +76,7 @@ impl Plugin for TimePlugin { app.register_type::