diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88e124154..e9209e13a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -629,7 +629,7 @@ jobs: run: taplo format --check --diff - name: Check for typos - uses: crate-ci/typos@v1.26.8 + uses: crate-ci/typos@v1.27.3 check-cts-runner: # runtime is normally 2 minutes diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3000e5667..388d64d34 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -41,7 +41,7 @@ jobs: if: ${{ failure() }} - name: Deploy the docs - uses: JamesIves/github-pages-deploy-action@v4.6.8 + uses: JamesIves/github-pages-deploy-action@v4.6.9 if: github.ref == 'refs/heads/trunk' with: token: ${{ secrets.WEB_DEPLOY }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6361ce68f..e99227d5a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -41,7 +41,7 @@ jobs: run: cargo xtask run-wasm --no-serve - name: Deploy WebGPU examples - uses: JamesIves/github-pages-deploy-action@v4.6.8 + uses: JamesIves/github-pages-deploy-action@v4.6.9 if: github.ref == 'refs/heads/trunk' with: token: ${{ secrets.WEB_DEPLOY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index ae028d770..db1b82fee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,12 +40,69 @@ Bottom level categories: ## Unreleased +## Major changes + +### The `diagnostic(…);` directive is now supported in WGSL + +Naga now parses `diagnostic(…);` directives according to the WGSL spec. This allows users to control certain lints, similar to Rust's `allow`, `warn`, and `deny` attributes. For example, in standard WGSL (but, notably, not Naga yet—see ) this snippet would emit a uniformity error: + +```wgsl +@group(0) @binding(0) var s : sampler; +@group(0) @binding(2) var tex : texture_2d; +@group(1) @binding(0) var ro_buffer : array; + +@fragment +fn main(@builtin(position) p : vec4f) -> @location(0) vec4f { + if ro_buffer[0] == 0 { + // Emits a derivative uniformity error during validation. + return textureSample(tex, s, vec2(0.,0.)); + } + + return vec4f(0.); +} +``` + +…but we can now silence it with the `off` severity level, like so: + +```wgsl +// Disable the diagnosic with this… +diagnostic(off, derivative_uniformity); + +@group(0) @binding(0) var s : sampler; +@group(0) @binding(2) var tex : texture_2d; +@group(1) @binding(0) var ro_buffer : array; + +@fragment +fn main(@builtin(position) p : vec4f) -> @location(0) vec4f { + if ro_buffer[0] == 0 { + // Look ma, no error! + return textureSample(tex, s, vec2(0.,0.)); + } + + return vec4f(0.); +} +``` + +There are some limitations to keep in mind with this new functionality: + +- We do not yet support `diagnostic(…)` rules in attribute position (i.e., `@diagnostic(…) fn my_func { … }`). This is being tracked in . We expect that rules in `fn` attribute position will be relaxed shortly (see ), but the prioritization for statement positions is unclear. If you are blocked by not being able to parse `diagnostic(…)` rules in statement positions, please let us know in that issue, so we can determine how to prioritize it! +- Standard WGSL specifies `error`, `warning`, `info`, and `off` severity levels. These are all technically usable now! A caveat, though: warning- and info-level are only emitted to `stderr` via the `log` façade, rather than being reported through a `Result::Err` in Naga or the `CompilationInfo` interface in `wgpu{,-core}`. This will require breaking changes in Naga to fix, and is being tracked by . +- Not all lints can be controlled with `diagnostic(…)` rules. In fact, only the `derivative_uniformity` triggering rule exists in the WGSL standard. That said, Naga contributors are excited to see how this level of control unlocks a new ecosystem of configurable diagnostics. +- Finally, `diagnostic(…)` rules are not yet emitted in WGSL output. This means that `wgsl-in` → `wgsl-out` is currently a lossy process. We felt that it was important to unblock users who needed `diagnostic(…)` rules (i.e., ) before we took significant effort to fix this (tracked in ). + +By @ErichDonGubler in [#6456](https://github.com/gfx-rs/wgpu/pull/6456), [#6148](https://github.com/gfx-rs/wgpu/pull/6148). + ### New Features #### Naga -- Parse `diagnostic(…)` directives, but don't implement any triggering rules yet. By @ErichDonGubler in [#6456](https://github.com/gfx-rs/wgpu/pull/6456). - Fix an issue where `naga` CLI would incorrectly skip the first positional argument when `--stdin-file-path` was specified. By @ErichDonGubler in [#6480](https://github.com/gfx-rs/wgpu/pull/6480). +- Fix textureNumLevels in the GLSL backend. By @magcius in [#6483](https://github.com/gfx-rs/wgpu/pull/6483). +- Implement `quantizeToF16()` for WGSL frontend, and WGSL, SPIR-V, HLSL, MSL, and GLSL backends. By @jamienicol in [#6519](https://github.com/gfx-rs/wgpu/pull/6519). + +#### General + +- Return submission index in `map_async` and `on_submitted_work_done` to track down completion of async callbacks. By @eliemichel in [#6360](https://github.com/gfx-rs/wgpu/pull/6360). #### Vulkan @@ -57,10 +114,35 @@ Bottom level categories: ### Changes +#### Naga + +- Show types of LHS and RHS in binary operation type mismatch errors. By @ErichDonGubler in [#6450](https://github.com/gfx-rs/wgpu/pull/6450). + +#### General + +- Make `Surface::as_hal` take an immutable reference to the surface. By @jerzywilczek in [#9999](https://github.com/gfx-rs/wgpu/pull/9999) + #### HAL - Change the `DropCallback` API to use `FnOnce` instead of `FnMut`. By @jerzywilczek in [#6482](https://github.com/gfx-rs/wgpu/pull/6482) +### Bug Fixes + +#### General + +- Handle query set creation failure as an internal error that loses the `Device`, rather than panicking. By @ErichDonGubler in [#6505](https://github.com/gfx-rs/wgpu/pull/6505). + +#### Naga + +- Fix crash when a texture argument is missing. By @aedm in [#6486](https://github.com/gfx-rs/wgpu/pull/6486) +- Emit an error in constant evaluation, rather than crash, in certain cases where `vecN` constructors have less than N arguments. By @ErichDonGubler in [#6508](https://github.com/gfx-rs/wgpu/pull/6508). + +#### General + +- Ensure that `Features::TIMESTAMP_QUERY` is set when using timestamp writes in render and compute passes. By @ErichDonGubler in [#6497](https://github.com/gfx-rs/wgpu/pull/6497). +- Check for device mismatches when beginning render and compute passes. By @ErichDonGubler in [#6497](https://github.com/gfx-rs/wgpu/pull/6497). +- Lower `QUERY_SET_MAX_QUERIES` (and enforced limits) from 8192 to 4096 to match WebGPU spec. By @ErichDonGubler in [#6525](https://github.com/gfx-rs/wgpu/pull/6525). + ## 23.0.0 (2024-10-25) ### Themes of this release @@ -75,7 +157,7 @@ This may not sound exciting, but let us convince you otherwise! All major web br WGPU also benefits from standard, portable behavior in the same way as web browsers. Because of this behavior, it's generally fairly easy to port over usage of WebGPU in JavaScript to WGPU. It is also what lets WGPU go full circle: WGPU can be an implementation of WebGPU on native targets, but _also_ it can use _other implementations of WebGPU_ as a backend in JavaScript when compiled to WASM. Therefore, the same dynamic applies: if WGPU's own behavior were significantly different, then WGPU and end users would be _sad, sad humans_ as soon as they discover places where their nice apps are breaking, right? -The answer is: yes, we _do_ have sad, sad humans that really want their WGPU code to work _everywhere_. As Firefox and others use WGPU to implement WebGPU, the above example of Firefox diverging from standard is, unfortunately, today's reality. It _mostly_ behaves the same as a standards-compliant WebGPU, but it still doesn't in many important ways. Of particular note is Naga, its implementation of the WebGPU Shader Language. Shaders are pretty much a black-and-white point of failure in GPU programming; if they don't compile, then you can't use the rest of the API! And yet, it's extremely easy to run into this: +The answer is: yes, we _do_ have sad, sad humans that really want their WGPU code to work _everywhere_. As Firefox and others use WGPU to implement WebGPU, the above example of Firefox diverging from standard is, unfortunately, today's reality. It _mostly_ behaves the same as a standards-compliant WebGPU, but it still doesn't in many important ways. Of particular note is Naga, its implementation of the WebGPU Shader Language. Shaders are pretty much a black-and-white point of failure in GPU programming; if they don't compile, then you can't use the rest of the API! And yet, it's extremely easy to run into a case like that from : ```wgsl fn gimme_a_float() -> f32 { @@ -184,6 +266,10 @@ By @MarijnS95 in [#6006](https://github.com/gfx-rs/wgpu/pull/6006). ### New Features +#### Wgpu + +- Added initial acceleration structure and ray query support into wgpu. By @expenses @daniel-keitel @Vecvec @JMS55 @atlv24 in [#6291](https://github.com/gfx-rs/wgpu/pull/6291) + #### Naga - Support constant evaluation for `firstLeadingBit` and `firstTrailingBit` numeric built-ins in WGSL. Front-ends that translate to these built-ins also benefit from constant evaluation. By @ErichDonGubler in [#5101](https://github.com/gfx-rs/wgpu/pull/5101). diff --git a/Cargo.lock b/Cargo.lock index 92035455d..8ff6df69e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,9 +126,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" @@ -160,9 +160,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" +checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" [[package]] name = "arbitrary" @@ -434,9 +434,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.31" +version = "1.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" dependencies = [ "jobserver", "libc", @@ -1195,9 +1195,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210" dependencies = [ "fastrand", "futures-core", @@ -1305,6 +1305,9 @@ name = "glam" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" +dependencies = [ + "bytemuck", +] [[package]] name = "glow" @@ -1700,7 +1703,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1834,9 +1837,9 @@ dependencies = [ [[package]] name = "minicov" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def6d99771d7c499c26ad4d40eb6645eafd3a1553b35fc26ea5a489a45e82d9a" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" dependencies = [ "cc", "walkdir", @@ -2689,18 +2692,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", @@ -2927,9 +2930,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -2947,18 +2950,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -3746,6 +3749,7 @@ dependencies = [ "ctor", "env_logger", "futures-lite", + "glam", "image", "itertools", "js-sys", @@ -3814,7 +3818,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0a5481abf..5e1142575 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ path = "./naga" version = "23.0.0" [workspace.dependencies] -anyhow = "1.0.91" +anyhow = "1.0.92" argh = "0.1.5" arrayvec = "0.7" bincode = "1" @@ -125,7 +125,7 @@ smallvec = "1" static_assertions = "1.1.0" strum = { version = "0.25.0", features = ["derive"] } tracy-client = "0.17" -thiserror = "1.0.65" +thiserror = "1.0.69" wgpu = { version = "23.0.0", path = "./wgpu", default-features = false } wgpu-core = { version = "23.0.0", path = "./wgpu-core" } wgpu-macros = { version = "23.0.0", path = "./wgpu-macros" } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 1f4d4951f..02fb524a1 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -34,7 +34,7 @@ cfg-if.workspace = true encase = { workspace = true, features = ["glam"] } flume.workspace = true getrandom.workspace = true -glam.workspace = true +glam = { workspace = true, features = ["bytemuck"] } ktx2.workspace = true log.workspace = true nanorand.workspace = true diff --git a/examples/README.md b/examples/README.md index 0ce432cf2..c67941e06 100644 --- a/examples/README.md +++ b/examples/README.md @@ -25,6 +25,9 @@ The rest of the examples are for demonstrating specific features that you can co - `skybox` - Shows off too many concepts to list here. The name comes from game development where a "skybox" acts as a background for rendering, usually to add a sky texture for immersion, although they can also be used for backdrops to give the idea of a world beyond the game scene. This example does so much more than this, though, as it uses a car model loaded from a file and uses the user's mouse to rotate the car model in 3d. `skybox` also makes use of depth textures and similar app patterns to `uniform_values`. - `shadow` - Likely by far the most complex example (certainly the largest in lines of code) of the official WGPU examples. `shadow` demonstrates basic scene rendering with the main attraction being lighting and shadows (as the name implies). It is recommended that any user looking into lighting be very familiar with the basic concepts of not only rendering with WGPU but also the primary mathematical ideas of computer graphics. - `render_to_texture` - Renders to an image texture offscreen, demonstrating both off-screen rendering as well as how to add a sort of resolution-agnostic screenshot feature to an engine. This example either outputs an image file of your naming (pass command line arguments after specifying a `--` like `cargo run --bin wgpu-examples -- render_to_texture "test.png"`) or adds an `img` element containing the image to the page in WASM. +- `ray_cube_fragment` - Demonstrates using ray queries with a fragment shader. +- `ray_scene` - Demonstrates using ray queries and model loading +- `ray_shadows` - Demonstrates a simple use of ray queries - high quality shadows - uses a light set with push constants to raytrace through an untransformed scene and detect whether there is something obstructing the light. #### Compute @@ -37,48 +40,51 @@ The rest of the examples are for demonstrating specific features that you can co #### Combined - `boids` - Demonstrates how to combine compute and render workflows by performing a [boid](https://en.wikipedia.org/wiki/Boids) simulation and rendering the boids to the screen as little triangles. +- `ray_cube_compute` - Demonstrates using ray queries with a compute shader. +- `ray_traced_triangle` - A simpler example demonstrating using ray queries with a compute shader ## Feature matrix -| Feature | boids | bunnymark | conservative_raster | cube | hello_synchronization | hello_workgroups | mipmap | msaa_line | render_to_texture | repeated_compute | shadow | skybox | stencil_triangles | storage_texture | texture_arrays | uniform_values | water | -| ---------------------------- | ------ | --------- | ------------------- | ------ | --------------------- | ---------------- | ------ | --------- | ----------------- | ---------------- | ------ | ------ | ----------------- | --------------- | -------------- | -------------- | ------ | -| vertex attributes | :star: | | | :star: | | | | :star: | | | :star: | :star: | | | :star: | | :star: | -| instancing | :star: | | | | | | | | | | | | | | | | | -| lines and points | | | :star: | | | | | :star: | | | | | | | | | | -| dynamic buffer offsets | | :star: | | | | | | | | | :star: | | | | | | | -| implicit layout | | | | | | | :star: | | | | | | | | | | | -| sampled color textures | :star: | :star: | :star: | :star: | | | :star: | | | | | :star: | | | :star: | | :star: | -| storage textures | :star: | | | | | | | | | | | | | :star: | | | | -| comparison samplers | | | | | | | | | | | :star: | | | | | | | -| subresource views | | | | | | | :star: | | | | :star: | | | | | | | -| cubemaps | | | | | | | | | | | | :star: | | | | | | -| multisampling | | | | | | | | :star: | | | | | | | | | | -| off-screen rendering | | | :star: | | | | | | :star: | | :star: | | | | | | :star: | -| stencil testing | | | | | | | | | | | | | :star: | | | | | -| depth testing | | | | | | | | | | | :star: | :star: | | | | | :star: | -| depth biasing | | | | | | | | | | | :star: | | | | | | | -| read-only depth | | | | | | | | | | | | | | | | | :star: | -| blending | | :star: | | :star: | | | | | | | | | | | | | :star: | -| render bundles | | | | | | | | :star: | | | | | | | | | :star: | -| uniform buffers | | | | | | | | | | | | | | | | :star: | | -| compute passes | :star: | | | | :star: | :star: | | | | :star: | | | | :star: | | | | -| buffer mapping | | | | | :star: | :star: | | | | :star: | | | | :star: | | | | -| error scopes | | | | :star: | | | | | | | | | | | | | | -| compute workgroups | | | | | :star: | :star: | | | | | | | | | | | | -| compute synchronization | | | | | :star: | | | | | | | | | | | | | -| _optional extensions_ | | | | | | | | | | | | | | | :star: | | | -| - SPIR-V shaders | | | | | | | | | | | | | | | | | | -| - binding array | | | | | | | | | | | | | | | :star: | | | -| - push constants | | | | | | | | | | | | | | | | | | -| - depth clamping | | | | | | | | | | | :star: | | | | | | | -| - compressed textures | | | | | | | | | | | | :star: | | | | | | -| - polygon mode | | | | :star: | | | | | | | | | | | | | | -| - queries | | | | | | | :star: | | | | | | | | | | | -| - conservative rasterization | | | :star: | | | | | | | | | | | | | | | -| _integrations_ | | | | | | | | | | | | | | | | | | -| - staging belt | | | | | | | | | | | | :star: | | | | | | -| - typed arena | | | | | | | | | | | | | | | | | | -| - obj loading | | | | | | | | | | | | :star: | | | | | | +| Feature | boids | bunnymark | conservative_raster | cube | hello_synchronization | hello_workgroups | mipmap | msaa_line | render_to_texture | repeated_compute | shadow | skybox | stencil_triangles | storage_texture | texture_arrays | uniform_values | water | ray_cube_compute | ray_cube_fragment | ray_scene | ray_shadows | ray_traced_triangle | +|------------------------------| ------ | --------- | ------------------- | ------ | --------------------- | ---------------- | ------ | --------- | ----------------- | ---------------- | ------ | ------ | ----------------- | --------------- | -------------- | -------------- | ------ |------------------|-------------------|-----------|-------------|---------------------| +| vertex attributes | :star: | | | :star: | | | | :star: | | | :star: | :star: | | | :star: | | :star: | | | | | | +| instancing | :star: | | | | | | | | | | | | | | | | | | | | | | +| lines and points | | | :star: | | | | | :star: | | | | | | | | | | | | | | | +| dynamic buffer offsets | | :star: | | | | | | | | | :star: | | | | | | | | | | | | +| implicit layout | | | | | | | :star: | | | | | | | | | | | | | | | | +| sampled color textures | :star: | :star: | :star: | :star: | | | :star: | | | | | :star: | | | :star: | | :star: | | | | | | +| storage textures | :star: | | | | | | | | | | | | | :star: | | | | :star: | | | | :star: | +| comparison samplers | | | | | | | | | | | :star: | | | | | | | | | | | | +| subresource views | | | | | | | :star: | | | | :star: | | | | | | | | | | | | +| cubemaps | | | | | | | | | | | | :star: | | | | | | | | | | | +| multisampling | | | | | | | | :star: | | | | | | | | | | | | | | | +| off-screen rendering | | | :star: | | | | | | :star: | | :star: | | | | | | :star: | | | | | | +| stencil testing | | | | | | | | | | | | | :star: | | | | | | | | | | +| depth testing | | | | | | | | | | | :star: | :star: | | | | | :star: | | | | | | +| depth biasing | | | | | | | | | | | :star: | | | | | | | | | | | | +| read-only depth | | | | | | | | | | | | | | | | | :star: | | | | | | +| blending | | :star: | | :star: | | | | | | | | | | | | | :star: | | | | | | +| render bundles | | | | | | | | :star: | | | | | | | | | :star: | | | | | | +| uniform buffers | | | | | | | | | | | | | | | | :star: | | | | | | | +| compute passes | :star: | | | | :star: | :star: | | | | :star: | | | | :star: | | | | | | | | | +| buffer mapping | | | | | :star: | :star: | | | | :star: | | | | :star: | | | | | | | | | +| error scopes | | | | :star: | | | | | | | | | | | | | | | | | | | +| compute workgroups | | | | | :star: | :star: | | | | | | | | | | | | | | | | | +| compute synchronization | | | | | :star: | | | | | | | | | | | | | | | | | | +| _optional extensions_ | | | | | | | | | | | | | | | :star: | | | | | | | | +| - SPIR-V shaders | | | | | | | | | | | | | | | | | | | | | | | +| - binding array | | | | | | | | | | | | | | | :star: | | | | | | | | +| - push constants | | | | | | | | | | | | | | | | | | | | | :star: | | +| - depth clamping | | | | | | | | | | | :star: | | | | | | | | | | | | +| - compressed textures | | | | | | | | | | | | :star: | | | | | | | | | | | +| - polygon mode | | | | :star: | | | | | | | | | | | | | | | | | | | +| - queries | | | | | | | :star: | | | | | | | | | | | | | | | | +| - conservative rasterization | | | :star: | | | | | | | | | | | | | | | | | | | | +| - ray queries | | | | | | | | | | | | | | | | | | :star: | :star: | :star: | :star: | :star: | +| _integrations_ | | | | | | | | | | | | | | | | | | | | | | | +| - staging belt | | | | | | | | | | | | :star: | | | | | | | | | | | +| - typed arena | | | | | | | | | | | | | | | | | | | | | | | +| - obj loading | | | | | | | | | | | | :star: | | | | | | | | :star: | | | ## Running on the Web diff --git a/examples/src/lib.rs b/examples/src/lib.rs index d212fd404..358993e1a 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -15,6 +15,11 @@ pub mod hello_windows; pub mod hello_workgroups; pub mod mipmap; pub mod msaa_line; +pub mod ray_cube_compute; +pub mod ray_cube_fragment; +pub mod ray_scene; +pub mod ray_shadows; +pub mod ray_traced_triangle; pub mod render_to_texture; pub mod repeated_compute; pub mod shadow; diff --git a/examples/src/main.rs b/examples/src/main.rs index 5d29d484b..77e658a88 100644 --- a/examples/src/main.rs +++ b/examples/src/main.rs @@ -146,6 +146,36 @@ const EXAMPLES: &[ExampleDesc] = &[ webgl: false, // No RODS webgpu: true, }, + ExampleDesc { + name: "ray_cube_compute", + function: wgpu_examples::ray_cube_compute::main, + webgl: false, // No Ray-tracing extensions + webgpu: false, // No Ray-tracing extensions (yet) + }, + ExampleDesc { + name: "ray_cube_fragment", + function: wgpu_examples::ray_cube_fragment::main, + webgl: false, // No Ray-tracing extensions + webgpu: false, // No Ray-tracing extensions (yet) + }, + ExampleDesc { + name: "ray_scene", + function: wgpu_examples::ray_scene::main, + webgl: false, // No Ray-tracing extensions + webgpu: false, // No Ray-tracing extensions (yet) + }, + ExampleDesc { + name: "ray_shadows", + function: wgpu_examples::ray_shadows::main, + webgl: false, // No Ray-tracing extensions + webgpu: false, // No Ray-tracing extensions (yet) + }, + ExampleDesc { + name: "ray_traced_triangle", + function: wgpu_examples::ray_traced_triangle::main, + webgl: false, + webgpu: false, + }, ]; fn get_example_name() -> Option { diff --git a/examples/src/ray_cube_compute/README.md b/examples/src/ray_cube_compute/README.md new file mode 100644 index 000000000..25b4fc942 --- /dev/null +++ b/examples/src/ray_cube_compute/README.md @@ -0,0 +1,14 @@ +# ray-cube + +This example renders a ray traced cube with hardware acceleration. +A separate compute shader is used to perform the ray queries. + +## To Run + +``` +cargo run --bin wgpu-examples ray_cube_compute +``` + +## Screenshots + +![Cube example](screenshot.png) diff --git a/examples/src/ray_cube_compute/blit.wgsl b/examples/src/ray_cube_compute/blit.wgsl new file mode 100644 index 000000000..69adbb3cc --- /dev/null +++ b/examples/src/ray_cube_compute/blit.wgsl @@ -0,0 +1,52 @@ +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) tex_coords: vec2, +}; + +// meant to be called with 3 vertex indices: 0, 1, 2 +// draws one large triangle over the clip space like this: +// (the asterisks represent the clip space bounds) +//-1,1 1,1 +// --------------------------------- +// | * . +// | * . +// | * . +// | * . +// | * . +// | * . +// |*************** +// | . 1,-1 +// | . +// | . +// | . +// | . +// |. +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { + var result: VertexOutput; + let x = i32(vertex_index) / 2; + let y = i32(vertex_index) & 1; + let tc = vec2( + f32(x) * 2.0, + f32(y) * 2.0 + ); + result.position = vec4( + tc.x * 2.0 - 1.0, + 1.0 - tc.y * 2.0, + 0.0, 1.0 + ); + result.tex_coords = tc; + return result; +} + +@group(0) +@binding(0) +var r_color: texture_2d; +@group(0) +@binding(1) +var r_sampler: sampler; + +@fragment +fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { + return textureSample(r_color, r_sampler, vertex.tex_coords); +} diff --git a/examples/src/ray_cube_compute/mod.rs b/examples/src/ray_cube_compute/mod.rs new file mode 100644 index 000000000..3f8f31ee0 --- /dev/null +++ b/examples/src/ray_cube_compute/mod.rs @@ -0,0 +1,500 @@ +use std::{borrow::Cow, future::Future, iter, mem, pin::Pin, task, time::Instant}; + +use bytemuck::{Pod, Zeroable}; +use glam::{Affine3A, Mat4, Quat, Vec3}; +use wgpu::util::DeviceExt; + +use wgpu::StoreOp; + +// from cube +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Vertex { + _pos: [f32; 4], + _tex_coord: [f32; 2], +} + +fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex { + Vertex { + _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0], + _tex_coord: [tc[0] as f32, tc[1] as f32], + } +} + +fn create_vertices() -> (Vec, Vec) { + let vertex_data = [ + // top (0, 0, 1) + vertex([-1, -1, 1], [0, 0]), + vertex([1, -1, 1], [1, 0]), + vertex([1, 1, 1], [1, 1]), + vertex([-1, 1, 1], [0, 1]), + // bottom (0, 0, -1) + vertex([-1, 1, -1], [1, 0]), + vertex([1, 1, -1], [0, 0]), + vertex([1, -1, -1], [0, 1]), + vertex([-1, -1, -1], [1, 1]), + // right (1, 0, 0) + vertex([1, -1, -1], [0, 0]), + vertex([1, 1, -1], [1, 0]), + vertex([1, 1, 1], [1, 1]), + vertex([1, -1, 1], [0, 1]), + // left (-1, 0, 0) + vertex([-1, -1, 1], [1, 0]), + vertex([-1, 1, 1], [0, 0]), + vertex([-1, 1, -1], [0, 1]), + vertex([-1, -1, -1], [1, 1]), + // front (0, 1, 0) + vertex([1, 1, -1], [1, 0]), + vertex([-1, 1, -1], [0, 0]), + vertex([-1, 1, 1], [0, 1]), + vertex([1, 1, 1], [1, 1]), + // back (0, -1, 0) + vertex([1, -1, 1], [0, 0]), + vertex([-1, -1, 1], [1, 0]), + vertex([-1, -1, -1], [1, 1]), + vertex([1, -1, -1], [0, 1]), + ]; + + let index_data: &[u16] = &[ + 0, 1, 2, 2, 3, 0, // top + 4, 5, 6, 6, 7, 4, // bottom + 8, 9, 10, 10, 11, 8, // right + 12, 13, 14, 14, 15, 12, // left + 16, 17, 18, 18, 19, 16, // front + 20, 21, 22, 22, 23, 20, // back + ]; + + (vertex_data.to_vec(), index_data.to_vec()) +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Uniforms { + view_inverse: Mat4, + proj_inverse: Mat4, +} + +#[inline] +fn affine_to_rows(mat: &Affine3A) -> [f32; 12] { + let row_0 = mat.matrix3.row(0); + let row_1 = mat.matrix3.row(1); + let row_2 = mat.matrix3.row(2); + let translation = mat.translation; + [ + row_0.x, + row_0.y, + row_0.z, + translation.x, + row_1.x, + row_1.y, + row_1.z, + translation.y, + row_2.x, + row_2.y, + row_2.z, + translation.z, + ] +} + +/// A wrapper for `pop_error_scope` futures that panics if an error occurs. +/// +/// Given a future `inner` of an `Option` for some error type `E`, +/// wait for the future to be ready, and panic if its value is `Some`. +/// +/// This can be done simpler with `FutureExt`, but we don't want to add +/// a dependency just for this small case. +struct ErrorFuture { + inner: F, +} +impl>> Future for ErrorFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<()> { + let inner = unsafe { self.map_unchecked_mut(|me| &mut me.inner) }; + inner.poll(cx).map(|error| { + if let Some(e) = error { + panic!("Rendering {}", e); + } + }) + } +} + +#[allow(dead_code)] +struct Example { + rt_target: wgpu::Texture, + rt_view: wgpu::TextureView, + sampler: wgpu::Sampler, + uniform_buf: wgpu::Buffer, + vertex_buf: wgpu::Buffer, + index_buf: wgpu::Buffer, + tlas_package: wgpu::TlasPackage, + compute_pipeline: wgpu::ComputePipeline, + compute_bind_group: wgpu::BindGroup, + blit_pipeline: wgpu::RenderPipeline, + blit_bind_group: wgpu::BindGroup, + start_inst: Instant, +} + +impl crate::framework::Example for Example { + fn required_features() -> wgpu::Features { + wgpu::Features::TEXTURE_BINDING_ARRAY + | wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY + | wgpu::Features::VERTEX_WRITABLE_STORAGE + | wgpu::Features::EXPERIMENTAL_RAY_QUERY + | wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + } + + fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { + wgpu::DownlevelCapabilities::default() + } + fn required_limits() -> wgpu::Limits { + wgpu::Limits::default() + } + + fn init( + config: &wgpu::SurfaceConfiguration, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> Self { + let side_count = 8; + + let rt_target = device.create_texture(&wgpu::TextureDescriptor { + label: Some("rt_target"), + size: wgpu::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING, + view_formats: &[wgpu::TextureFormat::Rgba8Unorm], + }); + + let rt_view = rt_target.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Rgba8Unorm), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("rt_sampler"), + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let uniforms = { + let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y); + let proj = Mat4::perspective_rh( + 59.0_f32.to_radians(), + config.width as f32 / config.height as f32, + 0.001, + 1000.0, + ); + + Uniforms { + view_inverse: view.inverse(), + proj_inverse: proj.inverse(), + } + }; + + let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Uniform Buffer"), + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM, + }); + + let (vertex_data, index_data) = create_vertices(); + + let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertex_data), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&index_data), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let blas_geo_size_desc = wgpu::BlasTriangleGeometrySizeDescriptor { + vertex_format: wgpu::VertexFormat::Float32x3, + vertex_count: vertex_data.len() as u32, + index_format: Some(wgpu::IndexFormat::Uint16), + index_count: Some(index_data.len() as u32), + flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, + }; + + let blas = device.create_blas( + &wgpu::CreateBlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + }, + wgpu::BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_geo_size_desc.clone()], + }, + ); + + let tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + max_instances: side_count * side_count, + }); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("rt_computer"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let blit_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("blit"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("blit.wgsl"))), + }); + + let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("rt"), + layout: None, + module: &shader, + entry_point: Some("main"), + compilation_options: Default::default(), + cache: None, + }); + + let compute_bind_group_layout = compute_pipeline.get_bind_group_layout(0); + + let compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &compute_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&rt_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: uniform_buf.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::AccelerationStructure(&tlas), + }, + ], + }); + + let blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("blit"), + layout: None, + vertex: wgpu::VertexState { + module: &blit_shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &blit_shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(config.format.into())], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let blit_bind_group_layout = blit_pipeline.get_bind_group_layout(0); + + let blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &blit_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&rt_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + let mut tlas_package = wgpu::TlasPackage::new(tlas); + + let dist = 3.0; + + for x in 0..side_count { + for y in 0..side_count { + tlas_package[(x + y * side_count) as usize] = Some(wgpu::TlasInstance::new( + &blas, + affine_to_rows(&Affine3A::from_rotation_translation( + Quat::from_rotation_y(45.9_f32.to_radians()), + Vec3 { + x: x as f32 * dist, + y: y as f32 * dist, + z: -30.0, + }, + )), + 0, + 0xff, + )); + } + } + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures( + iter::once(&wgpu::BlasBuildEntry { + blas: &blas, + geometry: wgpu::BlasGeometries::TriangleGeometries(vec![ + wgpu::BlasTriangleGeometry { + size: &blas_geo_size_desc, + vertex_buffer: &vertex_buf, + first_vertex: 0, + vertex_stride: mem::size_of::() as u64, + index_buffer: Some(&index_buf), + index_buffer_offset: Some(0), + transform_buffer: None, + transform_buffer_offset: None, + }, + ]), + }), + iter::once(&tlas_package), + ); + + queue.submit(Some(encoder.finish())); + + let start_inst = Instant::now(); + + Example { + rt_target, + rt_view, + sampler, + uniform_buf, + vertex_buf, + index_buf, + tlas_package, + compute_pipeline, + compute_bind_group, + blit_pipeline, + blit_bind_group, + start_inst, + } + } + + fn update(&mut self, _event: winit::event::WindowEvent) { + //empty + } + + fn resize( + &mut self, + _config: &wgpu::SurfaceConfiguration, + _device: &wgpu::Device, + _queue: &wgpu::Queue, + ) { + } + + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + device.push_error_scope(wgpu::ErrorFilter::Validation); + + let anim_time = self.start_inst.elapsed().as_secs_f64() as f32; + + self.tlas_package[0].as_mut().unwrap().transform = + affine_to_rows(&Affine3A::from_rotation_translation( + Quat::from_euler( + glam::EulerRot::XYZ, + anim_time * 0.342, + anim_time * 0.254, + anim_time * 0.832, + ), + Vec3 { + x: 0.0, + y: 0.0, + z: -6.0, + }, + )); + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures(iter::empty(), iter::once(&self.tlas_package)); + + { + let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + cpass.set_pipeline(&self.compute_pipeline); + cpass.set_bind_group(0, Some(&self.compute_bind_group), &[]); + cpass.dispatch_workgroups(self.rt_target.width() / 8, self.rt_target.height() / 8, 1); + } + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&self.blit_pipeline); + rpass.set_bind_group(0, Some(&self.blit_bind_group), &[]); + rpass.draw(0..3, 0..1); + } + + queue.submit(Some(encoder.finish())); + } +} + +pub fn main() { + crate::framework::run::("ray-cube"); +} + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "ray_cube_compute", + image_path: "/examples/src/ray_cube_compute/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters { + required_features: ::required_features(), + required_limits: ::required_limits(), + force_fxc: false, + skips: vec![], + failures: Vec::new(), + required_downlevel_caps: + ::required_downlevel_capabilities(), + }, + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/src/ray_cube_compute/screenshot.png b/examples/src/ray_cube_compute/screenshot.png new file mode 100644 index 000000000..6b737f1e2 Binary files /dev/null and b/examples/src/ray_cube_compute/screenshot.png differ diff --git a/examples/src/ray_cube_compute/shader.wgsl b/examples/src/ray_cube_compute/shader.wgsl new file mode 100644 index 000000000..43604a997 --- /dev/null +++ b/examples/src/ray_cube_compute/shader.wgsl @@ -0,0 +1,82 @@ +/* +The contents of the RayQuery struct are roughly as follows +let RAY_FLAG_NONE = 0x00u; +let RAY_FLAG_OPAQUE = 0x01u; +let RAY_FLAG_NO_OPAQUE = 0x02u; +let RAY_FLAG_TERMINATE_ON_FIRST_HIT = 0x04u; +let RAY_FLAG_SKIP_CLOSEST_HIT_SHADER = 0x08u; +let RAY_FLAG_CULL_BACK_FACING = 0x10u; +let RAY_FLAG_CULL_FRONT_FACING = 0x20u; +let RAY_FLAG_CULL_OPAQUE = 0x40u; +let RAY_FLAG_CULL_NO_OPAQUE = 0x80u; +let RAY_FLAG_SKIP_TRIANGLES = 0x100u; +let RAY_FLAG_SKIP_AABBS = 0x200u; + +let RAY_QUERY_INTERSECTION_NONE = 0u; +let RAY_QUERY_INTERSECTION_TRIANGLE = 1u; +let RAY_QUERY_INTERSECTION_GENERATED = 2u; +let RAY_QUERY_INTERSECTION_AABB = 4u; + +struct RayDesc { + flags: u32, + cull_mask: u32, + t_min: f32, + t_max: f32, + origin: vec3, + dir: vec3, +} + +struct RayIntersection { + kind: u32, + t: f32, + instance_custom_index: u32, + instance_id: u32, + sbt_record_offset: u32, + geometry_index: u32, + primitive_index: u32, + barycentrics: vec2, + front_face: bool, + object_to_world: mat4x3, + world_to_object: mat4x3, +} +*/ + +struct Uniforms { + view_inv: mat4x4, + proj_inv: mat4x4, +}; + +@group(0) @binding(0) +var output: texture_storage_2d; + +@group(0) @binding(1) +var uniforms: Uniforms; + +@group(0) @binding(2) +var acc_struct: acceleration_structure; + +@compute @workgroup_size(8, 8) +fn main(@builtin(global_invocation_id) global_id: vec3) { + let target_size = textureDimensions(output); + var color = vec4(vec2(global_id.xy) / vec2(target_size), 0.0, 1.0); + + + let pixel_center = vec2(global_id.xy) + vec2(0.5); + let in_uv = pixel_center/vec2(target_size.xy); + let d = in_uv * 2.0 - 1.0; + + let origin = (uniforms.view_inv * vec4(0.0,0.0,0.0,1.0)).xyz; + let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); + let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; + + var rq: ray_query; + rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); + rayQueryProceed(&rq); + + let intersection = rayQueryGetCommittedIntersection(&rq); + if (intersection.kind != RAY_QUERY_INTERSECTION_NONE) { + color = vec4(intersection.barycentrics, 1.0 - intersection.barycentrics.x - intersection.barycentrics.y, 1.0); + } + + textureStore(output, global_id.xy, color); +} diff --git a/examples/src/ray_cube_fragment/README.md b/examples/src/ray_cube_fragment/README.md new file mode 100644 index 000000000..9298e39d6 --- /dev/null +++ b/examples/src/ray_cube_fragment/README.md @@ -0,0 +1,13 @@ +# ray-cube + +This example renders a ray traced cube with hardware acceleration. + +## To Run + +``` +cargo run --bin wgpu-examples ray_cube_fragment +``` + +## Screenshots + +![Cube example](screenshot.png) diff --git a/examples/src/ray_cube_fragment/mod.rs b/examples/src/ray_cube_fragment/mod.rs new file mode 100644 index 000000000..b9dfba9a1 --- /dev/null +++ b/examples/src/ray_cube_fragment/mod.rs @@ -0,0 +1,391 @@ +use bytemuck::{Pod, Zeroable}; +use glam::{Mat4, Quat, Vec3}; +use std::ops::IndexMut; +use std::{borrow::Cow, future::Future, iter, mem, pin::Pin, task, time::Instant}; +use wgpu::util::DeviceExt; + +// from cube +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Vertex { + _pos: [f32; 4], + _tex_coord: [f32; 2], +} + +fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex { + Vertex { + _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0], + _tex_coord: [tc[0] as f32, tc[1] as f32], + } +} + +fn create_vertices() -> (Vec, Vec) { + let vertex_data = [ + // top (0, 0, 1) + vertex([-1, -1, 1], [0, 0]), + vertex([1, -1, 1], [1, 0]), + vertex([1, 1, 1], [1, 1]), + vertex([-1, 1, 1], [0, 1]), + // bottom (0, 0, -1) + vertex([-1, 1, -1], [1, 0]), + vertex([1, 1, -1], [0, 0]), + vertex([1, -1, -1], [0, 1]), + vertex([-1, -1, -1], [1, 1]), + // right (1, 0, 0) + vertex([1, -1, -1], [0, 0]), + vertex([1, 1, -1], [1, 0]), + vertex([1, 1, 1], [1, 1]), + vertex([1, -1, 1], [0, 1]), + // left (-1, 0, 0) + vertex([-1, -1, 1], [1, 0]), + vertex([-1, 1, 1], [0, 0]), + vertex([-1, 1, -1], [0, 1]), + vertex([-1, -1, -1], [1, 1]), + // front (0, 1, 0) + vertex([1, 1, -1], [1, 0]), + vertex([-1, 1, -1], [0, 0]), + vertex([-1, 1, 1], [0, 1]), + vertex([1, 1, 1], [1, 1]), + // back (0, -1, 0) + vertex([1, -1, 1], [0, 0]), + vertex([-1, -1, 1], [1, 0]), + vertex([-1, -1, -1], [1, 1]), + vertex([1, -1, -1], [0, 1]), + ]; + + let index_data: &[u16] = &[ + 0, 1, 2, 2, 3, 0, // top + 4, 5, 6, 6, 7, 4, // bottom + 8, 9, 10, 10, 11, 8, // right + 12, 13, 14, 14, 15, 12, // left + 16, 17, 18, 18, 19, 16, // front + 20, 21, 22, 22, 23, 20, // back + ]; + + (vertex_data.to_vec(), index_data.to_vec()) +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Uniforms { + view_inverse: Mat4, + proj_inverse: Mat4, +} + +/// A wrapper for `pop_error_scope` futures that panics if an error occurs. +/// +/// Given a future `inner` of an `Option` for some error type `E`, +/// wait for the future to be ready, and panic if its value is `Some`. +/// +/// This can be done simpler with `FutureExt`, but we don't want to add +/// a dependency just for this small case. +struct ErrorFuture { + inner: F, +} +impl>> Future for ErrorFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<()> { + let inner = unsafe { self.map_unchecked_mut(|me| &mut me.inner) }; + inner.poll(cx).map(|error| { + if let Some(e) = error { + panic!("Rendering {}", e); + } + }) + } +} + +struct Example { + uniforms: Uniforms, + uniform_buf: wgpu::Buffer, + blas: wgpu::Blas, + tlas_package: wgpu::TlasPackage, + pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, + start_inst: Instant, +} + +impl crate::framework::Example for Example { + fn required_features() -> wgpu::Features { + wgpu::Features::EXPERIMENTAL_RAY_QUERY + | wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + } + + fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { + wgpu::DownlevelCapabilities::default() + } + fn required_limits() -> wgpu::Limits { + wgpu::Limits::default() + } + + fn init( + config: &wgpu::SurfaceConfiguration, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> Self { + let side_count = 8; + + let uniforms = { + let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y); + let proj = Mat4::perspective_rh( + 59.0_f32.to_radians(), + config.width as f32 / config.height as f32, + 0.001, + 1000.0, + ); + + Uniforms { + view_inverse: view.inverse(), + proj_inverse: proj.inverse(), + } + }; + + let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Uniform Buffer"), + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let (vertex_data, index_data) = create_vertices(); + + let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertex_data), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&index_data), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let blas_geo_size_desc = wgpu::BlasTriangleGeometrySizeDescriptor { + vertex_format: wgpu::VertexFormat::Float32x3, + vertex_count: vertex_data.len() as u32, + index_format: Some(wgpu::IndexFormat::Uint16), + index_count: Some(index_data.len() as u32), + flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, + }; + + let blas = device.create_blas( + &wgpu::CreateBlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + }, + wgpu::BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_geo_size_desc.clone()], + }, + ); + + let tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + max_instances: side_count * side_count, + }); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(config.format.into())], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let bind_group_layout = pipeline.get_bind_group_layout(0); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buf.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::AccelerationStructure(&tlas), + }, + ], + }); + + let tlas_package = wgpu::TlasPackage::new(tlas); + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures( + iter::once(&wgpu::BlasBuildEntry { + blas: &blas, + geometry: wgpu::BlasGeometries::TriangleGeometries(vec![ + wgpu::BlasTriangleGeometry { + size: &blas_geo_size_desc, + vertex_buffer: &vertex_buf, + first_vertex: 0, + vertex_stride: mem::size_of::() as u64, + index_buffer: Some(&index_buf), + index_buffer_offset: Some(0), + transform_buffer: None, + transform_buffer_offset: None, + }, + ]), + }), + // iter::empty(), + iter::once(&tlas_package), + ); + + queue.submit(Some(encoder.finish())); + + let start_inst = Instant::now(); + + Example { + uniforms, + uniform_buf, + blas, + tlas_package, + pipeline, + bind_group, + start_inst, + } + } + + fn update(&mut self, _event: winit::event::WindowEvent) {} + + fn resize( + &mut self, + config: &wgpu::SurfaceConfiguration, + _device: &wgpu::Device, + queue: &wgpu::Queue, + ) { + let proj = Mat4::perspective_rh( + 59.0_f32.to_radians(), + config.width as f32 / config.height as f32, + 0.001, + 1000.0, + ); + + self.uniforms.proj_inverse = proj.inverse(); + + queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(&[self.uniforms])); + } + + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + device.push_error_scope(wgpu::ErrorFilter::Validation); + + // scene update + { + let dist = 12.0; + + let side_count = 8; + + let anim_time = self.start_inst.elapsed().as_secs_f64() as f32; + + for x in 0..side_count { + for y in 0..side_count { + let instance = self.tlas_package.index_mut((x + y * side_count) as usize); + + let x = x as f32 / (side_count - 1) as f32; + let y = y as f32 / (side_count - 1) as f32; + let x = x * 2.0 - 1.0; + let y = y * 2.0 - 1.0; + + let transform = Mat4::from_rotation_translation( + Quat::from_euler( + glam::EulerRot::XYZ, + anim_time * 0.5 * 0.342, + anim_time * 0.5 * 0.254, + anim_time * 0.5 * 0.832, + ), + Vec3 { + x: x * dist, + y: y * dist, + z: -24.0, + }, + ); + let transform = transform.transpose().to_cols_array()[..12] + .try_into() + .unwrap(); + + *instance = Some(wgpu::TlasInstance::new(&self.blas, transform, 0, 0xff)); + } + } + } + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures(iter::empty(), iter::once(&self.tlas_package)); + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, Some(&self.bind_group), &[]); + rpass.draw(0..3, 0..1); + } + + queue.submit(Some(encoder.finish())); + } +} + +pub fn main() { + crate::framework::run::("ray-cube"); +} + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "ray_cube_fragment", + image_path: "/examples/src/ray_cube_fragment/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters { + required_features: ::required_features(), + required_limits: ::required_limits(), + force_fxc: false, + skips: vec![], + failures: Vec::new(), + required_downlevel_caps: + ::required_downlevel_capabilities(), + }, + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/src/ray_cube_fragment/screenshot.png b/examples/src/ray_cube_fragment/screenshot.png new file mode 100644 index 000000000..5e9578675 Binary files /dev/null and b/examples/src/ray_cube_fragment/screenshot.png differ diff --git a/examples/src/ray_cube_fragment/shader.wgsl b/examples/src/ray_cube_fragment/shader.wgsl new file mode 100644 index 000000000..d98faeb01 --- /dev/null +++ b/examples/src/ray_cube_fragment/shader.wgsl @@ -0,0 +1,74 @@ +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) tex_coords: vec2, +}; + +// meant to be called with 3 vertex indices: 0, 1, 2 +// draws one large triangle over the clip space like this: +// (the asterisks represent the clip space bounds) +//-1,1 1,1 +// --------------------------------- +// | * . +// | * . +// | * . +// | * . +// | * . +// | * . +// |*************** +// | . 1,-1 +// | . +// | . +// | . +// | . +// |. +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { + var result: VertexOutput; + let x = i32(vertex_index) / 2; + let y = i32(vertex_index) & 1; + let tc = vec2( + f32(x) * 2.0, + f32(y) * 2.0 + ); + result.position = vec4( + tc.x * 2.0 - 1.0, + 1.0 - tc.y * 2.0, + 0.0, 1.0 + ); + result.tex_coords = tc; + return result; +} + +struct Uniforms { + view_inv: mat4x4, + proj_inv: mat4x4, +}; + +@group(0) @binding(0) +var uniforms: Uniforms; + +@group(0) @binding(1) +var acc_struct: acceleration_structure; + +@fragment +fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { + + var color = vec4(vertex.tex_coords, 0.0, 1.0); + + let d = vertex.tex_coords * 2.0 - 1.0; + + let origin = (uniforms.view_inv * vec4(0.0,0.0,0.0,1.0)).xyz; + let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); + let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; + + var rq: ray_query; + rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); + rayQueryProceed(&rq); + + let intersection = rayQueryGetCommittedIntersection(&rq); + if (intersection.kind != RAY_QUERY_INTERSECTION_NONE) { + color = vec4(intersection.barycentrics, 1.0 - intersection.barycentrics.x - intersection.barycentrics.y, 1.0); + } + + return color; +} diff --git a/examples/src/ray_scene/cube.mtl b/examples/src/ray_scene/cube.mtl new file mode 100644 index 000000000..59994638a --- /dev/null +++ b/examples/src/ray_scene/cube.mtl @@ -0,0 +1,20 @@ +# Blender MTL File: 'None' +# Material Count: 2 + +newmtl Material +Ns 250.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.000000 0.009087 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 + +newmtl None +Ns 500 +Ka 0.8 0.8 0.8 +Kd 0.8 0.8 0.8 +Ks 0.8 0.8 0.8 +d 1 +illum 2 diff --git a/examples/src/ray_scene/cube.obj b/examples/src/ray_scene/cube.obj new file mode 100644 index 000000000..855a249d9 --- /dev/null +++ b/examples/src/ray_scene/cube.obj @@ -0,0 +1,2587 @@ +# Blender v3.3.1 OBJ File: '' +# www.blender.org +mtllib cube.mtl +o Cube +v 1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 1.000000 +vt 0.875000 0.500000 +vt 0.625000 0.750000 +vt 0.625000 0.500000 +vt 0.375000 1.000000 +vt 0.375000 0.750000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.375000 0.000000 +vt 0.375000 0.500000 +vt 0.125000 0.750000 +vt 0.125000 0.500000 +vt 0.625000 0.250000 +vt 0.875000 0.750000 +vt 0.625000 1.000000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +usemtl Material +s off +f 5/1/1 3/2/1 1/3/1 +f 3/2/2 8/4/2 4/5/2 +f 7/6/3 6/7/3 8/8/3 +f 2/9/4 8/10/4 6/11/4 +f 1/3/5 4/5/5 2/9/5 +f 5/12/6 2/9/6 6/7/6 +f 5/1/1 7/13/1 3/2/1 +f 3/2/2 7/14/2 8/4/2 +f 7/6/3 5/12/3 6/7/3 +f 2/9/4 4/5/4 8/10/4 +f 1/3/5 3/2/5 4/5/5 +f 5/12/6 1/3/6 2/9/6 +o Suzanne +v 0.437500 2.679682 0.765625 +v -0.437500 2.679682 0.765625 +v 0.500000 2.609370 0.687500 +v -0.500000 2.609370 0.687500 +v 0.546875 2.570307 0.578125 +v -0.546875 2.570307 0.578125 +v 0.351562 2.492182 0.617188 +v -0.351562 2.492182 0.617188 +v 0.351562 2.546870 0.718750 +v -0.351562 2.546870 0.718750 +v 0.351562 2.648432 0.781250 +v -0.351562 2.648432 0.781250 +v 0.273438 2.679682 0.796875 +v -0.273438 2.679682 0.796875 +v 0.203125 2.609370 0.742188 +v -0.203125 2.609370 0.742188 +v 0.156250 2.570307 0.648438 +v -0.156250 2.570307 0.648438 +v 0.078125 2.757807 0.656250 +v -0.078125 2.757807 0.656250 +v 0.140625 2.757807 0.742188 +v -0.140625 2.757807 0.742188 +v 0.242188 2.757807 0.796875 +v -0.242188 2.757807 0.796875 +v 0.273438 2.843745 0.796875 +v -0.273438 2.843745 0.796875 +v 0.203125 2.906245 0.742188 +v -0.203125 2.906245 0.742188 +v 0.156250 2.953120 0.648438 +v -0.156250 2.953120 0.648438 +v 0.351562 3.031245 0.617188 +v -0.351562 3.031245 0.617188 +v 0.351562 2.968745 0.718750 +v -0.351562 2.968745 0.718750 +v 0.351562 2.874995 0.781250 +v -0.351562 2.874995 0.781250 +v 0.437500 2.843745 0.765625 +v -0.437500 2.843745 0.765625 +v 0.500000 2.906245 0.687500 +v -0.500000 2.906245 0.687500 +v 0.546875 2.953120 0.578125 +v -0.546875 2.953120 0.578125 +v 0.625000 2.757807 0.562500 +v -0.625000 2.757807 0.562500 +v 0.562500 2.757807 0.671875 +v -0.562500 2.757807 0.671875 +v 0.468750 2.757807 0.757812 +v -0.468750 2.757807 0.757812 +v 0.476562 2.757807 0.773438 +v -0.476562 2.757807 0.773438 +v 0.445312 2.851557 0.781250 +v -0.445312 2.851557 0.781250 +v 0.351562 2.890620 0.804688 +v -0.351562 2.890620 0.804688 +v 0.265625 2.851557 0.820312 +v -0.265625 2.851557 0.820312 +v 0.226562 2.757807 0.820312 +v -0.226562 2.757807 0.820312 +v 0.265625 2.671870 0.820312 +v -0.265625 2.671870 0.820312 +v 0.351562 2.757807 0.828125 +v -0.351562 2.757807 0.828125 +v 0.351562 2.632807 0.804688 +v -0.351562 2.632807 0.804688 +v 0.445312 2.671870 0.781250 +v -0.445312 2.671870 0.781250 +v 0.000000 2.945307 0.742188 +v 0.000000 2.867182 0.820312 +v 0.000000 1.835932 0.734375 +v 0.000000 2.195307 0.781250 +v 0.000000 2.328120 0.796875 +v 0.000000 1.742182 0.718750 +v 0.000000 2.921870 0.601562 +v 0.000000 3.085932 0.570312 +v 0.000000 3.414057 -0.546875 +v 0.000000 3.078120 -0.851562 +v 0.000000 2.585932 -0.828125 +v 0.000000 2.132807 -0.351562 +v 0.203125 2.328120 0.562500 +v -0.203125 2.328120 0.562500 +v 0.312500 2.078120 0.570312 +v -0.312500 2.078120 0.570312 +v 0.351562 1.820307 0.570312 +v -0.351562 1.820307 0.570312 +v 0.367188 1.624995 0.531250 +v -0.367188 1.624995 0.531250 +v 0.328125 1.570307 0.523438 +v -0.328125 1.570307 0.523438 +v 0.179688 1.546870 0.554688 +v -0.179688 1.546870 0.554688 +v 0.000000 1.531245 0.578125 +v 0.437500 2.374995 0.531250 +v -0.437500 2.374995 0.531250 +v 0.632812 2.476557 0.539062 +v -0.632812 2.476557 0.539062 +v 0.828125 2.664057 0.445312 +v -0.828125 2.664057 0.445312 +v 0.859375 2.945307 0.593750 +v -0.859375 2.945307 0.593750 +v 0.710938 2.999995 0.625000 +v -0.710938 2.999995 0.625000 +v 0.492188 3.117182 0.687500 +v -0.492188 3.117182 0.687500 +v 0.320312 3.273432 0.734375 +v -0.320312 3.273432 0.734375 +v 0.156250 3.234370 0.757812 +v -0.156250 3.234370 0.757812 +v 0.062500 3.007807 0.750000 +v -0.062500 3.007807 0.750000 +v 0.164062 2.929682 0.773438 +v -0.164062 2.929682 0.773438 +v 0.125000 2.820307 0.765625 +v -0.125000 2.820307 0.765625 +v 0.203125 2.609370 0.742188 +v -0.203125 2.609370 0.742188 +v 0.375000 2.531245 0.703125 +v -0.375000 2.531245 0.703125 +v 0.492188 2.578120 0.671875 +v -0.492188 2.578120 0.671875 +v 0.625000 2.703120 0.648438 +v -0.625000 2.703120 0.648438 +v 0.640625 2.812495 0.648438 +v -0.640625 2.812495 0.648438 +v 0.601562 2.890620 0.664062 +v -0.601562 2.890620 0.664062 +v 0.429688 2.953120 0.718750 +v -0.429688 2.953120 0.718750 +v 0.250000 2.984370 0.757812 +v -0.250000 2.984370 0.757812 +v 0.000000 1.749995 0.734375 +v 0.109375 1.796870 0.734375 +v -0.109375 1.796870 0.734375 +v 0.117188 1.679682 0.710938 +v -0.117188 1.679682 0.710938 +v 0.062500 1.632807 0.695312 +v -0.062500 1.632807 0.695312 +v 0.000000 1.624995 0.687500 +v 0.000000 2.320307 0.750000 +v 0.000000 2.374995 0.742188 +v 0.101562 2.367182 0.742188 +v -0.101562 2.367182 0.742188 +v 0.125000 2.289057 0.750000 +v -0.125000 2.289057 0.750000 +v 0.085938 2.226557 0.742188 +v -0.085938 2.226557 0.742188 +v 0.398438 2.468745 0.671875 +v -0.398438 2.468745 0.671875 +v 0.617188 2.570307 0.625000 +v -0.617188 2.570307 0.625000 +v 0.726562 2.718745 0.601562 +v -0.726562 2.718745 0.601562 +v 0.742188 2.890620 0.656250 +v -0.742188 2.890620 0.656250 +v 0.687500 2.929682 0.726562 +v -0.687500 2.929682 0.726562 +v 0.437500 3.062495 0.796875 +v -0.437500 3.062495 0.796875 +v 0.312500 3.156245 0.835938 +v -0.312500 3.156245 0.835938 +v 0.203125 3.132807 0.851562 +v -0.203125 3.132807 0.851562 +v 0.101562 2.945307 0.843750 +v -0.101562 2.945307 0.843750 +v 0.125000 2.414057 0.812500 +v -0.125000 2.414057 0.812500 +v 0.210938 2.070307 0.710938 +v -0.210938 2.070307 0.710938 +v 0.250000 1.812495 0.687500 +v -0.250000 1.812495 0.687500 +v 0.265625 1.695307 0.664062 +v -0.265625 1.695307 0.664062 +v 0.234375 1.601557 0.632812 +v -0.234375 1.601557 0.632812 +v 0.164062 1.585932 0.632812 +v -0.164062 1.585932 0.632812 +v 0.000000 1.570307 0.640625 +v 0.000000 2.562495 0.726562 +v 0.000000 2.726557 0.765625 +v 0.328125 2.992182 0.742188 +v -0.328125 2.992182 0.742188 +v 0.164062 2.656245 0.750000 +v -0.164062 2.656245 0.750000 +v 0.132812 2.726557 0.757812 +v -0.132812 2.726557 0.757812 +v 0.117188 1.828120 0.734375 +v -0.117188 1.828120 0.734375 +v 0.078125 2.070307 0.750000 +v -0.078125 2.070307 0.750000 +v 0.000000 2.070307 0.750000 +v 0.000000 2.187495 0.742188 +v 0.093750 2.242182 0.781250 +v -0.093750 2.242182 0.781250 +v 0.132812 2.289057 0.796875 +v -0.132812 2.289057 0.796875 +v 0.109375 2.382807 0.781250 +v -0.109375 2.382807 0.781250 +v 0.039062 2.390620 0.781250 +v -0.039062 2.390620 0.781250 +v 0.000000 2.312495 0.828125 +v 0.046875 2.367182 0.812500 +v -0.046875 2.367182 0.812500 +v 0.093750 2.359370 0.812500 +v -0.093750 2.359370 0.812500 +v 0.109375 2.289057 0.828125 +v -0.109375 2.289057 0.828125 +v 0.078125 2.265620 0.804688 +v -0.078125 2.265620 0.804688 +v 0.000000 2.226557 0.804688 +v 0.257812 2.203120 0.554688 +v -0.257812 2.203120 0.554688 +v 0.164062 2.273432 0.710938 +v -0.164062 2.273432 0.710938 +v 0.179688 2.203120 0.710938 +v -0.179688 2.203120 0.710938 +v 0.234375 2.265620 0.554688 +v -0.234375 2.265620 0.554688 +v 0.000000 1.640620 0.687500 +v 0.046875 1.648432 0.687500 +v -0.046875 1.648432 0.687500 +v 0.093750 1.695307 0.710938 +v -0.093750 1.695307 0.710938 +v 0.093750 1.773432 0.726562 +v -0.093750 1.773432 0.726562 +v 0.000000 1.734370 0.656250 +v 0.093750 1.765620 0.664062 +v -0.093750 1.765620 0.664062 +v 0.093750 1.703120 0.640625 +v -0.093750 1.703120 0.640625 +v 0.046875 1.664057 0.632812 +v -0.046875 1.664057 0.632812 +v 0.000000 1.656245 0.632812 +v 0.171875 2.734370 0.781250 +v -0.171875 2.734370 0.781250 +v 0.187500 2.671870 0.773438 +v -0.187500 2.671870 0.773438 +v 0.335938 2.945307 0.757812 +v -0.335938 2.945307 0.757812 +v 0.273438 2.937495 0.773438 +v -0.273438 2.937495 0.773438 +v 0.421875 2.914057 0.773438 +v -0.421875 2.914057 0.773438 +v 0.562500 2.867182 0.695312 +v -0.562500 2.867182 0.695312 +v 0.585938 2.804682 0.687500 +v -0.585938 2.804682 0.687500 +v 0.578125 2.710932 0.679688 +v -0.578125 2.710932 0.679688 +v 0.476562 2.617182 0.718750 +v -0.476562 2.617182 0.718750 +v 0.375000 2.578120 0.742188 +v -0.375000 2.578120 0.742188 +v 0.226562 2.624995 0.781250 +v -0.226562 2.624995 0.781250 +v 0.179688 2.812495 0.781250 +v -0.179688 2.812495 0.781250 +v 0.210938 2.890620 0.781250 +v -0.210938 2.890620 0.781250 +v 0.234375 2.874995 0.757812 +v -0.234375 2.874995 0.757812 +v 0.195312 2.812495 0.757812 +v -0.195312 2.812495 0.757812 +v 0.242188 2.640620 0.757812 +v -0.242188 2.640620 0.757812 +v 0.375000 2.601557 0.726562 +v -0.375000 2.601557 0.726562 +v 0.460938 2.632807 0.703125 +v -0.460938 2.632807 0.703125 +v 0.546875 2.726557 0.671875 +v -0.546875 2.726557 0.671875 +v 0.554688 2.796870 0.671875 +v -0.554688 2.796870 0.671875 +v 0.531250 2.851557 0.679688 +v -0.531250 2.851557 0.679688 +v 0.414062 2.906245 0.750000 +v -0.414062 2.906245 0.750000 +v 0.281250 2.914057 0.765625 +v -0.281250 2.914057 0.765625 +v 0.335938 2.921870 0.750000 +v -0.335938 2.921870 0.750000 +v 0.203125 2.687495 0.750000 +v -0.203125 2.687495 0.750000 +v 0.195312 2.742182 0.750000 +v -0.195312 2.742182 0.750000 +v 0.109375 2.976557 0.609375 +v -0.109375 2.976557 0.609375 +v 0.195312 3.179682 0.617188 +v -0.195312 3.179682 0.617188 +v 0.335938 3.203120 0.593750 +v -0.335938 3.203120 0.593750 +v 0.484375 3.070307 0.554688 +v -0.484375 3.070307 0.554688 +v 0.679688 2.968745 0.492188 +v -0.679688 2.968745 0.492188 +v 0.796875 2.921870 0.460938 +v -0.796875 2.921870 0.460938 +v 0.773438 2.679682 0.375000 +v -0.773438 2.679682 0.375000 +v 0.601562 2.515620 0.414062 +v -0.601562 2.515620 0.414062 +v 0.437500 2.421870 0.468750 +v -0.437500 2.421870 0.468750 +v 0.000000 3.414057 0.289062 +v 0.000000 3.499995 -0.078125 +v 0.000000 2.320307 -0.671875 +v 0.000000 2.054682 0.187500 +v 0.000000 1.539057 0.460938 +v 0.000000 1.710932 0.343750 +v 0.000000 1.945307 0.320312 +v 0.000000 2.031245 0.281250 +v 0.851562 2.749995 0.054688 +v -0.851562 2.749995 0.054688 +v 0.859375 2.835932 -0.046875 +v -0.859375 2.835932 -0.046875 +v 0.773438 2.781245 -0.437500 +v -0.773438 2.781245 -0.437500 +v 0.460938 2.953120 -0.703125 +v -0.460938 2.953120 -0.703125 +v 0.734375 2.468745 0.070312 +v -0.734375 2.468745 0.070312 +v 0.593750 2.390620 -0.164062 +v -0.593750 2.390620 -0.164062 +v 0.640625 2.507807 -0.429688 +v -0.640625 2.507807 -0.429688 +v 0.335938 2.570307 -0.664062 +v -0.335938 2.570307 -0.664062 +v 0.234375 2.164057 0.406250 +v -0.234375 2.164057 0.406250 +v 0.179688 2.101557 0.257812 +v -0.179688 2.101557 0.257812 +v 0.289062 1.804682 0.382812 +v -0.289062 1.804682 0.382812 +v 0.250000 2.015620 0.390625 +v -0.250000 2.015620 0.390625 +v 0.328125 1.601557 0.398438 +v -0.328125 1.601557 0.398438 +v 0.140625 1.757807 0.367188 +v -0.140625 1.757807 0.367188 +v 0.125000 1.976557 0.359375 +v -0.125000 1.976557 0.359375 +v 0.164062 1.570307 0.437500 +v -0.164062 1.570307 0.437500 +v 0.218750 2.234370 0.429688 +v -0.218750 2.234370 0.429688 +v 0.210938 2.289057 0.468750 +v -0.210938 2.289057 0.468750 +v 0.203125 2.343745 0.500000 +v -0.203125 2.343745 0.500000 +v 0.210938 2.124995 0.164062 +v -0.210938 2.124995 0.164062 +v 0.296875 2.203120 -0.265625 +v -0.296875 2.203120 -0.265625 +v 0.343750 2.367182 -0.539062 +v -0.343750 2.367182 -0.539062 +v 0.453125 3.382807 -0.382812 +v -0.453125 3.382807 -0.382812 +v 0.453125 3.445307 -0.070312 +v -0.453125 3.445307 -0.070312 +v 0.453125 3.367182 0.234375 +v -0.453125 3.367182 0.234375 +v 0.460938 3.039057 0.429688 +v -0.460938 3.039057 0.429688 +v 0.726562 2.921870 0.335938 +v -0.726562 2.921870 0.335938 +v 0.632812 2.968745 0.281250 +v -0.632812 2.968745 0.281250 +v 0.640625 3.218745 0.054688 +v -0.640625 3.218745 0.054688 +v 0.796875 3.078120 0.125000 +v -0.796875 3.078120 0.125000 +v 0.796875 3.132807 -0.117188 +v -0.796875 3.132807 -0.117188 +v 0.640625 3.265620 -0.195312 +v -0.640625 3.265620 -0.195312 +v 0.640625 3.195307 -0.445312 +v -0.640625 3.195307 -0.445312 +v 0.796875 3.054682 -0.359375 +v -0.796875 3.054682 -0.359375 +v 0.617188 2.843745 -0.585938 +v -0.617188 2.843745 -0.585938 +v 0.484375 2.539057 -0.546875 +v -0.484375 2.539057 -0.546875 +v 0.820312 2.843745 -0.203125 +v -0.820312 2.843745 -0.203125 +v 0.406250 2.343745 0.148438 +v -0.406250 2.343745 0.148438 +v 0.429688 2.320307 -0.210938 +v -0.429688 2.320307 -0.210938 +v 0.890625 2.921870 -0.234375 +v -0.890625 2.921870 -0.234375 +v 0.773438 2.374995 -0.125000 +v -0.773438 2.374995 -0.125000 +v 1.039062 2.414057 -0.328125 +v -1.039062 2.414057 -0.328125 +v 1.281250 2.570307 -0.429688 +v -1.281250 2.570307 -0.429688 +v 1.351562 2.835932 -0.421875 +v -1.351562 2.835932 -0.421875 +v 1.234375 3.023432 -0.421875 +v -1.234375 3.023432 -0.421875 +v 1.023438 2.992182 -0.312500 +v -1.023438 2.992182 -0.312500 +v 1.015625 2.929682 -0.289062 +v -1.015625 2.929682 -0.289062 +v 1.187500 2.953120 -0.390625 +v -1.187500 2.953120 -0.390625 +v 1.265625 2.804682 -0.406250 +v -1.265625 2.804682 -0.406250 +v 1.210938 2.593745 -0.406250 +v -1.210938 2.593745 -0.406250 +v 1.031250 2.476557 -0.304688 +v -1.031250 2.476557 -0.304688 +v 0.828125 2.445307 -0.132812 +v -0.828125 2.445307 -0.132812 +v 0.921875 2.874995 -0.218750 +v -0.921875 2.874995 -0.218750 +v 0.945312 2.820307 -0.289062 +v -0.945312 2.820307 -0.289062 +v 0.882812 2.492182 -0.210938 +v -0.882812 2.492182 -0.210938 +v 1.039062 2.515620 -0.367188 +v -1.039062 2.515620 -0.367188 +v 1.187500 2.609370 -0.445312 +v -1.187500 2.609370 -0.445312 +v 1.234375 2.765620 -0.445312 +v -1.234375 2.765620 -0.445312 +v 1.171875 2.874995 -0.437500 +v -1.171875 2.874995 -0.437500 +v 1.023438 2.859370 -0.359375 +v -1.023438 2.859370 -0.359375 +v 0.843750 2.804682 -0.210938 +v -0.843750 2.804682 -0.210938 +v 0.835938 2.687495 -0.273438 +v -0.835938 2.687495 -0.273438 +v 0.757812 2.609370 -0.273438 +v -0.757812 2.609370 -0.273438 +v 0.820312 2.601557 -0.273438 +v -0.820312 2.601557 -0.273438 +v 0.843750 2.531245 -0.273438 +v -0.843750 2.531245 -0.273438 +v 0.812500 2.499995 -0.273438 +v -0.812500 2.499995 -0.273438 +v 0.726562 2.515620 -0.070312 +v -0.726562 2.515620 -0.070312 +v 0.718750 2.492182 -0.171875 +v -0.718750 2.492182 -0.171875 +v 0.718750 2.554682 -0.187500 +v -0.718750 2.554682 -0.187500 +v 0.796875 2.718745 -0.210938 +v -0.796875 2.718745 -0.210938 +v 0.890625 2.757807 -0.265625 +v -0.890625 2.757807 -0.265625 +v 0.890625 2.749995 -0.320312 +v -0.890625 2.749995 -0.320312 +v 0.812500 2.499995 -0.320312 +v -0.812500 2.499995 -0.320312 +v 0.851562 2.531245 -0.320312 +v -0.851562 2.531245 -0.320312 +v 0.828125 2.593745 -0.320312 +v -0.828125 2.593745 -0.320312 +v 0.765625 2.609370 -0.320312 +v -0.765625 2.609370 -0.320312 +v 0.843750 2.687495 -0.320312 +v -0.843750 2.687495 -0.320312 +v 1.039062 2.843745 -0.414062 +v -1.039062 2.843745 -0.414062 +v 1.187500 2.859370 -0.484375 +v -1.187500 2.859370 -0.484375 +v 1.257812 2.757807 -0.492188 +v -1.257812 2.757807 -0.492188 +v 1.210938 2.601557 -0.484375 +v -1.210938 2.601557 -0.484375 +v 1.046875 2.515620 -0.421875 +v -1.046875 2.515620 -0.421875 +v 0.882812 2.499995 -0.265625 +v -0.882812 2.499995 -0.265625 +v 0.953125 2.804682 -0.343750 +v -0.953125 2.804682 -0.343750 +v 0.890625 2.624995 -0.328125 +v -0.890625 2.624995 -0.328125 +v 0.937500 2.578120 -0.335938 +v -0.937500 2.578120 -0.335938 +v 1.000000 2.640620 -0.367188 +v -1.000000 2.640620 -0.367188 +v 0.960938 2.687495 -0.351562 +v -0.960938 2.687495 -0.351562 +v 1.015625 2.749995 -0.375000 +v -1.015625 2.749995 -0.375000 +v 1.054688 2.703120 -0.382812 +v -1.054688 2.703120 -0.382812 +v 1.109375 2.726557 -0.390625 +v -1.109375 2.726557 -0.390625 +v 1.085938 2.789057 -0.390625 +v -1.085938 2.789057 -0.390625 +v 1.023438 2.953120 -0.484375 +v -1.023438 2.953120 -0.484375 +v 1.250000 2.984370 -0.546875 +v -1.250000 2.984370 -0.546875 +v 1.367188 2.812495 -0.500000 +v -1.367188 2.812495 -0.500000 +v 1.312500 2.570307 -0.531250 +v -1.312500 2.570307 -0.531250 +v 1.039062 2.429682 -0.492188 +v -1.039062 2.429682 -0.492188 +v 0.789062 2.390620 -0.328125 +v -0.789062 2.390620 -0.328125 +v 0.859375 2.898432 -0.382812 +v -0.859375 2.898432 -0.382812 +vt 0.890955 0.590063 +vt 0.860081 0.560115 +vt 0.904571 0.559404 +vt 0.856226 0.850547 +vt 0.888398 0.821999 +vt 0.900640 0.853232 +vt 0.853018 0.521562 +vt 0.920166 0.524546 +vt 0.847458 0.888748 +vt 0.914672 0.888748 +vt 0.798481 0.569535 +vt 0.795104 0.838402 +vt 0.870622 0.589649 +vt 0.828900 0.590771 +vt 0.826436 0.818537 +vt 0.868067 0.821510 +vt 0.854402 0.604754 +vt 0.828171 0.633354 +vt 0.827598 0.775964 +vt 0.852534 0.805700 +vt 0.791018 0.645443 +vt 0.791018 0.762238 +vt 0.855181 0.668527 +vt 0.856142 0.742025 +vt 0.844839 0.707525 +vt 0.854107 0.625459 +vt 0.853157 0.785002 +vt 0.867508 0.642291 +vt 0.900375 0.666964 +vt 0.901223 0.745592 +vt 0.867293 0.768782 +vt 0.842358 0.702491 +vt 0.921180 0.713713 +vt 0.931889 0.636832 +vt 0.918898 0.699697 +vt 0.931368 0.777093 +vt 0.968213 0.770220 +vt 0.905882 0.627902 +vt 0.890474 0.641909 +vt 0.904990 0.784860 +vt 0.906232 0.605742 +vt 0.904357 0.807013 +vt 0.931250 0.820926 +vt 0.933717 0.593037 +vt 0.968392 0.645333 +vt 0.965038 0.841671 +vt 0.968392 0.573812 +vt 0.889591 0.593275 +vt 0.887178 0.818729 +vt 0.900583 0.804677 +vt 0.902359 0.607909 +vt 0.898822 0.786233 +vt 0.899781 0.626257 +vt 0.890219 0.770183 +vt 0.887351 0.775442 +vt 0.887842 0.636527 +vt 0.870376 0.775972 +vt 0.859881 0.623942 +vt 0.870908 0.635245 +vt 0.858859 0.786774 +vt 0.859664 0.608186 +vt 0.857942 0.802505 +vt 0.871664 0.593961 +vt 0.869299 0.817249 +vt 0.879400 0.616512 +vt 0.878029 0.795063 +vt 0.536419 0.062072 +vt 0.518916 0.050294 +vt 0.540260 0.053805 +vt 0.501452 0.062043 +vt 0.518925 0.059681 +vt 0.542788 0.064089 +vt 0.551930 0.058338 +vt 0.495083 0.064047 +vt 0.497626 0.053770 +vt 0.555073 0.061900 +vt 0.482805 0.061829 +vt 0.485955 0.058273 +vt 0.563812 0.076586 +vt 0.546290 0.072669 +vt 0.491565 0.072625 +vt 0.474014 0.076511 +vt 0.583135 0.108495 +vt 0.548333 0.084893 +vt 0.489507 0.084858 +vt 0.454527 0.108481 +vt 0.605512 0.165134 +vt 0.621513 0.227818 +vt 0.553118 0.209599 +vt 0.416514 0.229490 +vt 0.432024 0.165644 +vt 0.485339 0.210053 +vt 0.676379 0.233241 +vt 0.647395 0.200502 +vt 0.360308 0.235899 +vt 0.372747 0.256357 +vt 0.683908 0.279995 +vt 0.664761 0.253225 +vt 0.353696 0.284606 +vt 0.707254 0.310054 +vt 0.715342 0.265392 +vt 0.330721 0.316853 +vt 0.351187 0.317440 +vt 0.697446 0.332673 +vt 0.687515 0.311539 +vt 0.341964 0.339667 +vt 0.362723 0.329722 +vt 0.662817 0.372521 +vt 0.676824 0.323937 +vt 0.379297 0.378686 +vt 0.402772 0.362131 +vt 0.618316 0.375151 +vt 0.639050 0.357330 +vt 0.424583 0.379267 +vt 0.604826 0.397804 +vt 0.626842 0.395792 +vt 0.439252 0.401540 +vt 0.442396 0.381222 +vt 0.553095 0.390512 +vt 0.600808 0.377857 +vt 0.490934 0.391862 +vt 0.482938 0.358497 +vt 0.521923 0.386009 +vt 0.559674 0.357011 +vt 0.521086 0.343868 +vt 0.599845 0.344815 +vt 0.577279 0.340156 +vt 0.441977 0.347815 +vt 0.615546 0.342005 +vt 0.634472 0.332311 +vt 0.425972 0.345582 +vt 0.662406 0.312804 +vt 0.406362 0.336480 +vt 0.668440 0.297958 +vt 0.377061 0.317685 +vt 0.664101 0.277872 +vt 0.370304 0.302644 +vt 0.639236 0.253047 +vt 0.374100 0.281778 +vt 0.613992 0.242662 +vt 0.398938 0.255633 +vt 0.572941 0.258564 +vt 0.424464 0.244473 +vt 0.519760 0.248864 +vt 0.466409 0.259709 +vt 0.558527 0.316594 +vt 0.482619 0.317843 +vt 0.520277 0.294764 +vt 0.556923 0.291214 +vt 0.483433 0.292249 +vt 0.563905 0.272007 +vt 0.475886 0.273078 +vt 0.525483 0.068967 +vt 0.512375 0.068956 +vt 0.531231 0.073829 +vt 0.506626 0.073811 +vt 0.531019 0.087431 +vt 0.555621 0.121749 +vt 0.532669 0.090920 +vt 0.505177 0.090908 +vt 0.482177 0.121781 +vt 0.506827 0.087416 +vt 0.518981 0.151749 +vt 0.532042 0.127713 +vt 0.538112 0.158382 +vt 0.505828 0.127728 +vt 0.518941 0.128358 +vt 0.518925 0.093952 +vt 0.518927 0.085180 +vt 0.548362 0.173560 +vt 0.535214 0.166808 +vt 0.502799 0.166857 +vt 0.489683 0.173693 +vt 0.499851 0.158434 +vt 0.544281 0.193366 +vt 0.537959 0.175966 +vt 0.500100 0.176033 +vt 0.493996 0.193428 +vt 0.528757 0.191785 +vt 0.519841 0.200843 +vt 0.509219 0.191626 +vt 0.500890 0.187571 +vt 0.519132 0.185382 +vt 0.517577 0.190607 +vt 0.518998 0.159028 +vt 0.519016 0.165599 +vt 0.506910 0.171667 +vt 0.528222 0.186316 +vt 0.509787 0.186260 +vt 0.533528 0.184215 +vt 0.537248 0.187577 +vt 0.504547 0.184206 +vt 0.504604 0.176791 +vt 0.531131 0.171631 +vt 0.533449 0.176739 +vt 0.519099 0.179457 +vt 0.561572 0.167779 +vt 0.476363 0.167996 +vt 0.478371 0.149447 +vt 0.559475 0.149319 +vt 0.596138 0.133426 +vt 0.441395 0.133592 +vt 0.601169 0.147885 +vt 0.436337 0.148194 +vt 0.528933 0.084957 +vt 0.508915 0.084945 +vt 0.518925 0.083865 +vt 0.529036 0.075429 +vt 0.508820 0.075415 +vt 0.523751 0.070508 +vt 0.514106 0.070501 +vt 0.518928 0.067899 +vt 0.518929 0.069468 +vt 0.518928 0.074259 +vt 0.516297 0.074966 +vt 0.524236 0.076691 +vt 0.521560 0.074970 +vt 0.513619 0.076684 +vt 0.524601 0.079886 +vt 0.513252 0.079879 +vt 0.518926 0.079331 +vt 0.571787 0.277295 +vt 0.568351 0.292904 +vt 0.468070 0.278617 +vt 0.471978 0.294282 +vt 0.573085 0.311386 +vt 0.467790 0.313081 +vt 0.584855 0.327708 +vt 0.456477 0.329961 +vt 0.458737 0.268049 +vt 0.611720 0.255725 +vt 0.580734 0.266620 +vt 0.427062 0.257728 +vt 0.632494 0.262853 +vt 0.406068 0.265508 +vt 0.653658 0.279971 +vt 0.384904 0.283634 +vt 0.656064 0.297636 +vt 0.383015 0.301864 +vt 0.386858 0.314615 +vt 0.652752 0.310186 +vt 0.411556 0.327673 +vt 0.614408 0.331972 +vt 0.629040 0.323864 +vt 0.426727 0.335361 +vt 0.601033 0.333624 +vt 0.440344 0.336537 +vt 0.601799 0.328453 +vt 0.439372 0.331331 +vt 0.450408 0.323919 +vt 0.613335 0.327083 +vt 0.427623 0.330358 +vt 0.626851 0.320513 +vt 0.413648 0.324175 +vt 0.646248 0.306421 +vt 0.393381 0.310510 +vt 0.649541 0.296225 +vt 0.389662 0.300183 +vt 0.647785 0.283486 +vt 0.391040 0.287071 +vt 0.629829 0.267263 +vt 0.408893 0.269959 +vt 0.612641 0.261560 +vt 0.426254 0.263693 +vt 0.585166 0.270991 +vt 0.454369 0.272583 +vt 0.578124 0.281900 +vt 0.461798 0.283441 +vt 0.579548 0.309340 +vt 0.590644 0.321516 +vt 0.461204 0.311233 +vt 0.577524 0.293776 +vt 0.462754 0.295432 +vt 0.553209 0.433063 +vt 0.523031 0.433628 +vt 0.492809 0.434538 +vt 0.609819 0.431516 +vt 0.435860 0.435740 +vt 0.416915 0.400552 +vt 0.396518 0.425416 +vt 0.648174 0.419316 +vt 0.350292 0.396229 +vt 0.692106 0.388274 +vt 0.312756 0.350588 +vt 0.735879 0.312112 +vt 0.726332 0.341754 +vt 0.301067 0.320593 +vt 0.320452 0.270303 +vt 0.304876 0.261087 +vt 0.698172 0.216906 +vt 0.729900 0.256393 +vt 0.337414 0.219179 +vt 0.663103 0.190671 +vt 0.373474 0.191872 +vt 0.649444 0.022378 +vt 0.621440 0.048089 +vt 0.626908 0.015608 +vt 0.388827 0.021586 +vt 0.416419 0.047631 +vt 0.376796 0.075296 +vt 0.577206 0.032801 +vt 0.567460 0.000144 +vt 0.411318 0.015131 +vt 0.460782 0.032656 +vt 0.547413 0.041724 +vt 0.518922 0.024886 +vt 0.470636 0.000144 +vt 0.490511 0.041669 +vt 0.558059 0.053871 +vt 0.479842 0.053785 +vt 0.576951 0.057998 +vt 0.460920 0.057845 +vt 0.611687 0.078268 +vt 0.425932 0.077985 +vt 0.660451 0.076084 +vt 0.626663 0.111357 +vt 0.410618 0.111244 +vt 0.629482 0.130456 +vt 0.407648 0.130594 +vt 0.413741 0.147158 +vt 0.619303 0.159841 +vt 0.418035 0.160361 +vt 0.389677 0.201890 +vt 0.886245 0.121777 +vt 0.891780 0.036916 +vt 0.945900 0.079569 +vt 0.141314 0.112482 +vt 0.142277 0.021467 +vt 0.183115 0.092127 +vt 0.849114 0.099732 +vt 0.805584 0.010786 +vt 0.232648 0.003484 +vt 0.246353 0.076510 +vt 0.687018 0.077204 +vt 0.672384 0.022201 +vt 0.349875 0.075955 +vt 0.365979 0.020991 +vt 0.760215 0.193244 +vt 0.789046 0.233323 +vt 0.271553 0.193871 +vt 0.241255 0.236977 +vt 0.909112 0.183261 +vt 0.994525 0.167705 +vt 0.107928 0.179083 +vt 0.078961 0.060719 +vt 0.862868 0.338556 +vt 0.962901 0.344752 +vt 0.911671 0.402429 +vt 0.160557 0.356821 +vt 0.043968 0.367038 +vt 0.123776 0.315519 +vt 0.915360 0.259804 +vt 0.999856 0.254640 +vt 0.098965 0.266968 +vt 0.000144 0.259113 +vt 0.011829 0.155367 +vt 0.749542 0.334683 +vt 0.766337 0.300809 +vt 0.789162 0.313727 +vt 0.267408 0.310142 +vt 0.288183 0.346496 +vt 0.242992 0.325552 +vt 0.815314 0.276388 +vt 0.846174 0.293397 +vt 0.213065 0.285164 +vt 0.178537 0.304983 +vt 0.845007 0.256352 +vt 0.873517 0.265922 +vt 0.179662 0.263312 +vt 0.147089 0.274284 +vt 0.859075 0.228168 +vt 0.886999 0.233769 +vt 0.162803 0.231720 +vt 0.131514 0.237587 +vt 0.875030 0.184705 +vt 0.842355 0.195160 +vt 0.145224 0.182749 +vt 0.894128 0.301884 +vt 0.794286 0.364062 +vt 0.770185 0.379538 +vt 0.239776 0.382592 +vt 0.845499 0.449967 +vt 0.106400 0.432652 +vt 0.815858 0.445381 +vt 0.755700 0.418603 +vt 0.287033 0.442912 +vt 0.219260 0.477186 +vt 0.268122 0.398737 +vt 0.185281 0.484099 +vt 0.819845 0.468071 +vt 0.215894 0.503605 +vt 0.809631 0.233887 +vt 0.219168 0.237388 +vt 0.829287 0.219562 +vt 0.199067 0.222464 +vt 0.788458 0.080826 +vt 0.715482 0.139727 +vt 0.319538 0.139409 +vt 0.246666 0.114850 +vt 0.785486 0.152330 +vt 0.245969 0.151002 +vt 0.623495 0.146796 +vt 0.837382 0.156361 +vt 0.196622 0.155241 +vt 0.171653 0.132294 +vt 0.786480 0.117591 +vt 0.858171 0.137775 +vt 0.432388 0.894943 +vt 0.491058 0.881714 +vt 0.506166 0.904851 +vt 0.321637 0.893225 +vt 0.263032 0.878321 +vt 0.315867 0.868209 +vt 0.572792 0.860484 +vt 0.604825 0.879946 +vt 0.181486 0.854693 +vt 0.247207 0.901159 +vt 0.148729 0.873349 +vt 0.619962 0.791615 +vt 0.136063 0.784093 +vt 0.169745 0.787474 +vt 0.586396 0.793977 +vt 0.563786 0.739211 +vt 0.194086 0.733241 +vt 0.208656 0.740879 +vt 0.549027 0.746412 +vt 0.508270 0.697693 +vt 0.250811 0.693249 +vt 0.258399 0.707497 +vt 0.438641 0.680683 +vt 0.434803 0.658882 +vt 0.320962 0.677959 +vt 0.325318 0.656224 +vt 0.500314 0.711729 +vt 0.452955 0.700023 +vt 0.306136 0.696976 +vt 0.505666 0.730944 +vt 0.252524 0.726592 +vt 0.568148 0.787367 +vt 0.188269 0.781375 +vt 0.214575 0.750414 +vt 0.555495 0.826352 +vt 0.199850 0.820889 +vt 0.501231 0.844356 +vt 0.253846 0.840502 +vt 0.457832 0.840040 +vt 0.297562 0.837358 +vt 0.783193 0.187449 +vt 0.246955 0.187075 +vt 0.233625 0.175620 +vt 0.394766 0.686125 +vt 0.391039 0.611891 +vt 0.364838 0.684445 +vt 0.391747 0.862097 +vt 0.438797 0.870229 +vt 0.363377 0.861308 +vt 0.435018 0.718280 +vt 0.323658 0.715731 +vt 0.384658 0.710299 +vt 0.433669 0.729661 +vt 0.374400 0.708969 +vt 0.410995 0.747662 +vt 0.427812 0.742828 +vt 0.324726 0.727177 +vt 0.347028 0.745816 +vt 0.330270 0.740536 +vt 0.384657 0.795423 +vt 0.418086 0.784946 +vt 0.372270 0.794472 +vt 0.431333 0.817535 +vt 0.401605 0.841460 +vt 0.324790 0.815460 +vt 0.338952 0.783073 +vt 0.354026 0.840297 +vt 0.825107 0.209762 +vt 0.199767 0.214827 +vt 0.816266 0.203086 +vt 0.209828 0.206161 +vt 0.226485 0.183086 +vt 0.796021 0.176969 +vt 0.802192 0.184609 +vt 0.448505 0.804621 +vt 0.473386 0.824700 +vt 0.307886 0.802031 +vt 0.282357 0.821525 +vt 0.321237 0.777208 +vt 0.423718 0.754191 +vt 0.435868 0.779569 +vt 0.334089 0.752045 +vt 0.319919 0.747250 +vt 0.437950 0.749777 +vt 0.312907 0.729222 +vt 0.440995 0.724383 +vt 0.445392 0.731997 +vt 0.317510 0.721697 +vt 0.455277 0.713731 +vt 0.303460 0.710657 +vt 0.512485 0.828811 +vt 0.242975 0.824574 +vt 0.550942 0.811814 +vt 0.204839 0.806417 +vt 0.552139 0.787682 +vt 0.204331 0.782156 +vt 0.539407 0.764539 +vt 0.542850 0.755753 +vt 0.217774 0.759319 +vt 0.508439 0.743135 +vt 0.249419 0.738732 +vt 0.454776 0.761665 +vt 0.302729 0.758742 +vt 0.286960 0.745020 +vt 0.470841 0.748408 +vt 0.475403 0.783904 +vt 0.281439 0.780511 +vt 0.268291 0.766661 +vt 0.503673 0.787562 +vt 0.494476 0.802470 +vt 0.252972 0.783410 +vt 0.261790 0.798626 +vt 0.516802 0.807339 +vt 0.239243 0.802891 +vt 0.237920 0.787045 +vt 0.518562 0.791602 +vt 0.484068 0.628776 +vt 0.543385 0.683538 +vt 0.276936 0.625067 +vt 0.216123 0.678120 +vt 0.581052 0.726933 +vt 0.177176 0.720426 +vt 0.616701 0.759965 +vt 0.140379 0.752377 +vt 0.660647 0.741167 +vt 0.707492 0.759884 +vt 0.097038 0.732052 +vt 0.677256 0.670436 +vt 0.745511 0.652100 +vt 0.049526 0.748824 +vt 0.083564 0.662038 +vt 0.671403 0.592656 +vt 0.740843 0.572428 +vt 0.019409 0.639749 +vt 0.092820 0.589862 +vt 0.834705 0.206959 +vt 0.051216 0.522659 +vt 0.033664 0.564403 +vt 0.620420 0.565675 +vt 0.498072 0.552315 +vt 0.145041 0.562595 +vt 0.264218 0.550140 +vt 0.369913 0.610196 +vt 0.464579 0.342230 +vt 0.176788 0.196179 +vt 0.770572 0.444261 +vt 0.271364 0.473316 +vt 0.488870 0.770464 +vt 0.834578 0.206879 +vn 0.9693 -0.0118 0.2456 +vn 0.6076 -0.5104 0.6085 +vn 0.8001 -0.0028 0.5999 +vn -0.6076 -0.5104 0.6085 +vn -0.9693 -0.0118 0.2456 +vn -0.8001 -0.0028 0.5999 +vn 0.6802 -0.5463 0.4888 +vn 0.8682 -0.0048 0.4961 +vn -0.6802 -0.5463 0.4888 +vn -0.8682 -0.0048 0.4961 +vn 0.1193 -0.8712 0.4763 +vn -0.1193 -0.8712 0.4763 +vn 0.7290 -0.6566 0.1934 +vn 0.0995 -0.7515 0.6522 +vn -0.0995 -0.7515 0.6522 +vn -0.7290 -0.6566 0.1934 +vn 0.0314 -0.9670 0.2529 +vn -0.4563 -0.5362 0.7101 +vn 0.4563 -0.5362 0.7101 +vn -0.0314 -0.9670 0.2529 +vn -0.5539 -0.6332 0.5406 +vn 0.5539 -0.6332 0.5406 +vn -0.6899 -0.0041 0.7239 +vn 0.6899 -0.0041 0.7239 +vn 0.8097 -0.0070 0.5868 +vn -0.6506 -0.6883 0.3210 +vn 0.6506 -0.6883 0.3210 +vn -0.9521 -0.0102 0.3057 +vn -0.4560 0.5222 0.7207 +vn 0.4560 0.5222 0.7207 +vn 0.9521 -0.0102 0.3057 +vn -0.8097 -0.0070 0.5868 +vn 0.5306 0.6258 0.5717 +vn 0.1031 0.7402 0.6644 +vn -0.5306 0.6258 0.5717 +vn -0.1031 0.7402 0.6644 +vn -0.1257 0.8416 0.5253 +vn 0.0258 0.9726 0.2312 +vn -0.6644 0.6821 0.3056 +vn -0.0258 0.9726 0.2312 +vn 0.7364 0.6521 0.1803 +vn -0.7364 0.6521 0.1803 +vn -0.6102 0.4956 0.6181 +vn 0.6102 0.4956 0.6181 +vn 0.1257 0.8416 0.5253 +vn -0.6682 0.5371 0.5148 +vn 0.6682 0.5371 0.5148 +vn 0.9645 -0.0127 0.2639 +vn -0.9645 -0.0127 0.2639 +vn -0.7216 0.6556 0.2224 +vn 0.7216 0.6556 0.2224 +vn -0.0432 0.9389 0.3415 +vn 0.0432 0.9389 0.3415 +vn 0.6644 0.6821 0.3056 +vn 0.6237 0.6285 0.4647 +vn -0.6237 0.6285 0.4647 +vn 0.9270 -0.0130 0.3749 +vn -0.6159 -0.6366 0.4641 +vn -0.9270 -0.0130 0.3749 +vn 0.6159 -0.6366 0.4641 +vn 0.0426 -0.9404 0.3375 +vn -0.0426 -0.9404 0.3375 +vn 0.7152 -0.6625 0.2227 +vn -0.7152 -0.6625 0.2227 +vn 0.1836 -0.0053 0.9830 +vn -0.1836 -0.0053 0.9830 +vn 0.1554 -0.7590 0.6323 +vn 0.0000 -0.9677 0.2523 +vn 0.1596 -0.9753 0.1529 +vn -0.1554 -0.7590 0.6323 +vn 0.0000 -0.7753 0.6316 +vn 0.3502 -0.6392 0.6847 +vn 0.5267 -0.8347 0.1611 +vn -0.3502 -0.6392 0.6847 +vn -0.1596 -0.9753 0.1529 +vn 0.9457 -0.2579 0.1977 +vn -0.9457 -0.2579 0.1977 +vn -0.5267 -0.8347 0.1611 +vn 0.9728 0.1003 0.2087 +vn 0.5557 -0.2264 0.8000 +vn -0.5557 -0.2264 0.8000 +vn -0.9728 0.1003 0.2087 +vn 0.9557 0.2492 0.1565 +vn 0.5652 -0.0297 0.8244 +vn -0.5652 -0.0297 0.8244 +vn -0.9557 0.2492 0.1565 +vn 0.8916 -0.3307 0.3095 +vn 0.3842 -0.5671 0.7286 +vn 0.0402 -0.2722 0.9614 +vn -0.3842 -0.5671 0.7286 +vn -0.8916 -0.3307 0.3095 +vn -0.0402 -0.2722 0.9614 +vn 0.5875 -0.7849 0.1970 +vn 0.3489 -0.9371 -0.0082 +vn -0.5875 -0.7849 0.1970 +vn -0.4991 -0.3761 0.7807 +vn 0.5666 -0.3188 0.7598 +vn 0.4991 -0.3761 0.7807 +vn -0.5666 -0.3188 0.7598 +vn 0.8451 0.4434 0.2985 +vn 0.9070 -0.4009 -0.1290 +vn -0.8451 0.4434 0.2985 +vn -0.4607 -0.1448 0.8757 +vn 0.5171 0.8291 0.2125 +vn 0.4607 -0.1448 0.8757 +vn -0.5171 0.8291 0.2125 +vn -0.4801 -0.1833 0.8578 +vn 0.5976 0.7847 0.1646 +vn 0.4801 -0.1833 0.8578 +vn -0.5976 0.7847 0.1646 +vn -0.3085 0.0039 0.9512 +vn 0.2666 0.2166 0.9392 +vn 0.3085 0.0039 0.9512 +vn -0.2666 0.2166 0.9392 +vn -0.6051 0.7680 0.2098 +vn 0.2313 0.9570 0.1751 +vn 0.6051 0.7680 0.2098 +vn 0.1574 0.1660 0.9735 +vn -0.8242 0.5468 0.1473 +vn -0.1574 0.1660 0.9735 +vn 0.8242 0.5468 0.1473 +vn 0.0611 -0.0253 0.9978 +vn 0.0000 0.9636 0.2673 +vn -0.0611 -0.0253 0.9978 +vn 0.0000 -0.0827 0.9966 +vn 0.2582 -0.1265 0.9578 +vn 0.3679 -0.2836 0.8856 +vn -0.2582 -0.1265 0.9578 +vn 0.1490 -0.1542 0.9767 +vn 0.2190 0.0372 0.9750 +vn -0.1490 -0.1542 0.9767 +vn 0.2254 -0.3608 0.9050 +vn -0.2190 0.0372 0.9750 +vn 0.3588 -0.1192 0.9258 +vn -0.2254 -0.3608 0.9050 +vn 0.4602 -0.1651 0.8723 +vn -0.3588 -0.1192 0.9258 +vn 0.4279 -0.3895 0.8156 +vn -0.4602 -0.1651 0.8723 +vn 0.3322 -0.3667 0.8690 +vn -0.4279 -0.3895 0.8156 +vn -0.1522 -0.2549 0.9549 +vn -0.3322 -0.3667 0.8690 +vn -0.0000 0.0643 0.9979 +vn 0.1522 -0.2549 0.9549 +vn 0.0316 -0.1782 0.9835 +vn -0.0316 -0.1782 0.9835 +vn -0.0000 -0.2220 0.9750 +vn -0.2006 -0.1350 0.9703 +vn 0.2006 -0.1350 0.9703 +vn -0.2393 -0.3012 0.9230 +vn 0.2393 -0.3012 0.9230 +vn -0.0589 -0.3784 0.9238 +vn 0.0589 -0.3784 0.9238 +vn 0.1307 -0.3187 0.9388 +vn -0.1307 -0.3187 0.9388 +vn 0.1460 -0.1202 0.9820 +vn 0.5937 0.1082 0.7974 +vn 0.1815 -0.0452 0.9823 +vn -0.1815 -0.0452 0.9823 +vn -0.5937 0.1082 0.7974 +vn -0.1460 -0.1202 0.9820 +vn 0.0000 -0.4760 0.8795 +vn 0.1341 0.0063 0.9909 +vn 0.5003 -0.4293 0.7520 +vn -0.1341 0.0063 0.9909 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -0.0341 0.9994 +vn -0.0000 -0.5870 0.8096 +vn 0.9304 -0.1242 0.3448 +vn 0.5836 -0.6929 0.4235 +vn -0.5836 -0.6929 0.4235 +vn -0.9304 -0.1242 0.3448 +vn -0.5003 -0.4293 0.7520 +vn 0.4931 -0.3412 0.8002 +vn 0.9306 -0.2353 0.2804 +vn -0.9306 -0.2353 0.2804 +vn -0.4931 -0.3412 0.8002 +vn -0.2405 0.9491 0.2036 +vn 0.0000 0.5166 0.8562 +vn 0.2405 0.9491 0.2036 +vn -0.6286 0.7688 0.1177 +vn 0.0000 0.8287 0.5597 +vn 0.0000 0.9515 0.3076 +vn 0.0000 -0.8654 0.5011 +vn 0.0000 -0.4815 0.8764 +vn -0.1833 -0.5864 0.7890 +vn -0.1858 0.5956 0.7815 +vn 0.1858 0.5956 0.7815 +vn 0.3611 0.4713 0.8047 +vn 0.6286 0.7688 0.1177 +vn -0.3611 0.4713 0.8047 +vn -0.4488 -0.3147 0.8364 +vn 0.1833 -0.5864 0.7890 +vn 0.4488 -0.3147 0.8364 +vn 0.0000 0.1578 0.9875 +vn 0.7752 0.0387 0.6306 +vn -0.7752 0.0387 0.6306 +vn -0.6507 0.1488 0.7447 +vn 0.6507 0.1488 0.7447 +vn 0.9278 0.3530 0.1209 +vn -0.9278 0.3530 0.1209 +vn 0.9306 0.3435 0.1263 +vn -0.9306 0.3435 0.1263 +vn -0.1369 -0.5273 0.8386 +vn 0.1369 -0.5273 0.8386 +vn 0.0000 -0.9619 0.2732 +vn -0.6351 0.0428 0.7712 +vn 0.6351 0.0428 0.7712 +vn -0.4141 0.5798 0.7016 +vn 0.4141 0.5798 0.7016 +vn 0.0000 -0.3465 0.9380 +vn 0.0000 0.5588 0.8293 +vn 0.0000 0.5334 0.8459 +vn 0.2959 0.4750 0.8288 +vn -0.6738 0.1155 0.7299 +vn -0.2959 0.4750 0.8288 +vn 0.6738 0.1155 0.7299 +vn -0.5177 -0.7041 0.4860 +vn 0.5177 -0.7041 0.4860 +vn 0.0000 -0.6989 0.7152 +vn -0.0101 -0.0700 0.9975 +vn 0.1581 -0.0843 0.9838 +vn 0.0101 -0.0700 0.9975 +vn -0.1581 -0.0843 0.9838 +vn 0.2934 -0.0602 0.9541 +vn -0.2934 -0.0602 0.9541 +vn 0.1588 -0.1065 0.9816 +vn -0.1588 -0.1065 0.9816 +vn 0.0317 -0.2198 0.9750 +vn 0.1845 -0.1863 0.9650 +vn -0.0317 -0.2198 0.9750 +vn -0.1845 -0.1863 0.9650 +vn 0.2990 -0.0356 0.9536 +vn -0.2990 -0.0356 0.9536 +vn 0.2943 -0.1021 0.9502 +vn -0.2943 -0.1020 0.9502 +vn 0.1776 -0.0608 0.9822 +vn -0.1776 -0.0608 0.9822 +vn -0.2944 0.0046 0.9557 +vn 0.2944 0.0046 0.9557 +vn -0.0887 -0.1272 0.9879 +vn 0.2036 0.1032 0.9736 +vn 0.0887 -0.1272 0.9879 +vn -0.2036 0.1032 0.9736 +vn 0.1435 0.0966 0.9849 +vn -0.1435 0.0966 0.9849 +vn 0.2886 -0.2786 0.9160 +vn -0.2886 -0.2786 0.9160 +vn -0.4508 -0.4658 0.7614 +vn 0.1133 -0.3142 0.9426 +vn -0.1133 -0.3142 0.9426 +vn -0.2741 -0.8556 0.4391 +vn 0.2741 -0.8556 0.4391 +vn -0.1423 -0.5826 0.8002 +vn 0.1423 -0.5826 0.8002 +vn -0.4229 -0.1078 0.8997 +vn 0.4229 -0.1078 0.8997 +vn -0.1921 0.1914 0.9625 +vn 0.1921 0.1914 0.9625 +vn -0.1653 0.6098 0.7751 +vn 0.1653 0.6098 0.7751 +vn 0.1431 0.5587 0.8169 +vn -0.1431 0.5587 0.8169 +vn 0.4323 0.5833 0.6877 +vn -0.4323 0.5833 0.6877 +vn 0.6881 0.2985 0.6614 +vn -0.6881 0.2985 0.6614 +vn 0.7894 -0.2032 0.5793 +vn 0.4508 -0.4658 0.7614 +vn -0.7894 -0.2032 0.5793 +vn 0.8016 0.0110 0.5977 +vn -0.8016 0.0110 0.5977 +vn -0.4603 0.8619 0.2127 +vn 0.0000 0.8592 0.5116 +vn 0.4603 0.8619 0.2127 +vn -0.4792 0.5120 -0.7129 +vn 0.4792 0.5120 -0.7129 +vn -0.2313 0.9570 0.1751 +vn -0.1217 0.6503 -0.7499 +vn 0.1217 0.6503 -0.7499 +vn -0.2275 0.8745 -0.4283 +vn 0.2275 0.8745 -0.4283 +vn -0.3456 0.9125 -0.2192 +vn 0.6957 0.5814 -0.4218 +vn 0.3456 0.9125 -0.2192 +vn -0.6957 0.5814 -0.4218 +vn -0.9070 -0.4009 -0.1290 +vn -0.9302 -0.3062 -0.2024 +vn 0.5444 -0.8372 -0.0533 +vn 0.9302 -0.3062 -0.2024 +vn -0.5444 -0.8372 -0.0533 +vn 0.4720 -0.8637 -0.1768 +vn -0.4720 -0.8637 -0.1768 +vn 0.0000 -0.7711 -0.6367 +vn 0.2771 -0.3147 -0.9078 +vn -0.0000 -0.2133 -0.9770 +vn -0.2771 -0.3147 -0.9078 +vn -0.6894 -0.6687 -0.2786 +vn 0.1514 -0.1510 -0.9769 +vn 0.0000 -0.2974 -0.9548 +vn -0.1514 -0.1510 -0.9769 +vn 0.0675 -0.7832 -0.6181 +vn 0.0000 -0.8818 -0.4716 +vn -0.0675 -0.7832 -0.6181 +vn 0.5551 -0.4762 -0.6820 +vn -0.5551 -0.4762 -0.6820 +vn 0.6204 0.0835 -0.7798 +vn -0.6204 0.0835 -0.7798 +vn 0.7799 -0.0105 -0.6259 +vn -0.7799 -0.0105 -0.6259 +vn 0.6894 -0.6687 -0.2786 +vn 0.8957 0.2578 -0.3624 +vn -0.8957 0.2578 -0.3624 +vn 0.9787 -0.1959 0.0615 +vn -0.9787 -0.1959 0.0615 +vn -0.8872 -0.1577 0.4336 +vn 0.7857 -0.5715 0.2368 +vn -0.7857 -0.5715 0.2368 +vn -0.3489 -0.9371 -0.0082 +vn 0.4455 -0.3584 -0.8204 +vn 0.0000 -0.6913 -0.7226 +vn -0.0000 -0.3049 -0.9524 +vn -0.4455 -0.3584 -0.8204 +vn -0.5223 -0.6536 -0.5477 +vn 0.5223 -0.6536 -0.5477 +vn 0.0000 -0.9417 -0.3365 +vn -0.5071 -0.8376 -0.2033 +vn 0.5727 -0.8197 0.0120 +vn 0.0000 -0.9831 -0.1833 +vn -0.5727 -0.8197 0.0120 +vn 0.7211 -0.6898 0.0651 +vn 0.9850 -0.1605 0.0631 +vn -0.7211 -0.6898 0.0651 +vn -0.9850 -0.1605 0.0631 +vn 0.4730 0.1763 -0.8632 +vn 0.0000 0.3650 -0.9310 +vn -0.4730 0.1763 -0.8632 +vn 0.4442 0.7244 0.5271 +vn 0.0000 0.9997 0.0226 +vn 0.0000 0.8306 0.5568 +vn -0.4442 0.7244 0.5271 +vn -0.4135 0.9096 0.0395 +vn 0.3913 0.8153 -0.4268 +vn 0.0000 0.8343 -0.5514 +vn -0.3913 0.8153 -0.4268 +vn 0.7717 0.6311 0.0785 +vn 0.4444 0.7886 0.4250 +vn -0.7717 0.6311 0.0785 +vn -0.4444 0.7886 0.4250 +vn 0.7418 0.5164 0.4279 +vn 0.6682 0.6719 0.3195 +vn -0.7418 0.5164 0.4279 +vn -0.6682 0.6719 0.3195 +vn 0.8486 0.5288 -0.0140 +vn 0.6784 0.7314 -0.0695 +vn -0.8486 0.5288 -0.0140 +vn -0.6784 0.7314 -0.0695 +vn 0.8722 0.3146 -0.3747 +vn 0.6075 0.5696 -0.5536 +vn -0.8722 0.3146 -0.3747 +vn -0.6075 0.5696 -0.5536 +vn 0.6197 -0.0605 -0.7825 +vn 0.6708 -0.0453 -0.7403 +vn -0.6197 -0.0605 -0.7825 +vn 0.4135 0.9096 0.0395 +vn 0.3406 0.8832 0.3223 +vn -0.3406 0.8832 0.3223 +vn 0.0000 0.5293 0.8485 +vn 0.9983 -0.0283 -0.0502 +vn -0.9983 -0.0283 -0.0502 +vn 0.8403 0.4934 0.2246 +vn -0.8403 0.4934 0.2246 +vn 0.5071 -0.8376 -0.2033 +vn 0.5790 -0.8027 0.1427 +vn -0.5790 -0.8027 0.1427 +vn -0.5633 -0.8173 -0.1213 +vn 0.3123 -0.9500 0.0012 +vn -0.3123 -0.9500 0.0012 +vn 0.8872 -0.1577 0.4336 +vn 0.3255 -0.6029 -0.7284 +vn -0.3255 -0.6029 -0.7284 +vn -0.5292 -0.5051 -0.6817 +vn 0.5633 -0.8173 -0.1213 +vn 0.5292 -0.5051 -0.6817 +vn -0.2793 0.7683 0.5759 +vn 0.5512 -0.0788 0.8307 +vn 0.0188 0.8723 0.4887 +vn 0.2793 0.7683 0.5759 +vn -0.5512 -0.0788 0.8307 +vn -0.4493 -0.0383 0.8926 +vn 0.3215 -0.0923 0.9424 +vn 0.3836 0.8630 0.3288 +vn -0.3215 -0.0923 0.9424 +vn -0.0188 0.8723 0.4887 +vn -0.3836 0.8630 0.3288 +vn 0.7788 0.1678 0.6044 +vn -0.7788 0.1678 0.6044 +vn 0.1545 -0.1239 0.9802 +vn -0.1545 -0.1239 0.9802 +vn 0.6526 -0.4768 0.5888 +vn -0.6526 -0.4768 0.5888 +vn 0.0411 0.3108 0.9496 +vn -0.0411 0.3108 0.9496 +vn 0.5029 -0.7810 0.3703 +vn -0.5029 -0.7810 0.3703 +vn -0.5384 0.2953 0.7893 +vn 0.3300 0.3157 0.8896 +vn 0.0295 -0.6350 0.7719 +vn -0.3300 0.3157 0.8896 +vn -0.0295 -0.6350 0.7719 +vn 0.5384 0.2953 0.7893 +vn 0.1629 0.8581 0.4870 +vn -0.1629 0.8581 0.4870 +vn -0.1868 0.9538 0.2351 +vn 0.1868 0.9538 0.2351 +vn -0.9848 -0.0996 0.1426 +vn 0.9848 -0.0996 0.1426 +vn 0.7622 0.6471 -0.0193 +vn -0.1496 -0.7455 0.6495 +vn 0.1496 -0.7455 0.6495 +vn 0.5605 -0.6609 0.4991 +vn -0.5605 -0.6609 0.4991 +vn 0.6842 -0.5558 0.4722 +vn -0.6842 -0.5558 0.4722 +vn 0.8572 -0.4931 -0.1483 +vn -0.8572 -0.4931 -0.1483 +vn -0.7312 0.1144 0.6725 +vn 0.7312 0.1144 0.6725 +vn 0.4493 -0.0383 0.8926 +vn 0.5998 0.5131 0.6139 +vn -0.5998 0.5131 0.6139 +vn 0.9610 -0.1188 0.2499 +vn 0.8420 -0.1763 0.5098 +vn -0.9610 -0.1188 0.2499 +vn 0.8515 0.0414 0.5228 +vn 0.4814 0.6344 0.6048 +vn -0.8420 -0.1763 0.5098 +vn -0.8515 0.0414 0.5228 +vn -0.4814 0.6344 0.6048 +vn 0.8303 -0.4790 0.2850 +vn 0.6864 -0.6234 0.3746 +vn -0.8303 -0.4790 0.2850 +vn 0.7261 -0.4989 0.4732 +vn 0.7949 -0.2332 0.5601 +vn -0.7261 -0.4989 0.4732 +vn -0.6864 -0.6234 0.3746 +vn -0.7949 -0.2332 0.5601 +vn 0.6593 -0.4685 0.5881 +vn 0.6482 -0.4206 0.6347 +vn -0.6593 -0.4685 0.5881 +vn -0.6482 -0.4206 0.6347 +vn -0.5725 -0.4189 0.7048 +vn 0.7584 0.2665 0.5948 +vn 0.5725 -0.4189 0.7048 +vn -0.7584 0.2665 0.5948 +vn -0.4492 0.3799 0.8086 +vn 0.4492 0.3799 0.8086 +vn -0.2929 0.3709 0.8813 +vn 0.6450 0.3102 0.6984 +vn 0.2929 0.3709 0.8813 +vn -0.6450 0.3102 0.6984 +vn -0.0331 0.9449 0.3256 +vn 0.0331 0.9449 0.3256 +vn 0.4618 -0.3291 0.8237 +vn -0.4618 -0.3291 0.8237 +vn -0.2624 -0.5331 0.8043 +vn 0.2624 -0.5331 0.8043 +vn -0.7529 -0.0338 0.6573 +vn 0.7529 -0.0338 0.6573 +vn -0.5831 0.4999 0.6403 +vn -0.7622 0.6471 -0.0193 +vn 0.5831 0.4999 0.6403 +vn 0.0650 0.7039 0.7074 +vn -0.0650 0.7039 0.7074 +vn 0.1951 0.0390 0.9800 +vn -0.1951 0.0390 0.9800 +vn -0.4085 0.1273 0.9039 +vn 0.4085 0.1273 0.9039 +vn 0.3347 -0.0046 0.9423 +vn -0.3347 -0.0046 0.9423 +vn -0.4448 -0.0937 0.8907 +vn 0.3144 -0.1038 0.9436 +vn 0.3343 0.1068 0.9364 +vn -0.3144 -0.1038 0.9436 +vn -0.3343 0.1068 0.9364 +vn 0.2897 0.3158 0.9035 +vn -0.2897 0.3158 0.9035 +vn -0.3831 -0.0685 0.9211 +vn 0.3831 -0.0685 0.9211 +vn -0.0989 -0.8408 -0.5322 +vn -0.0253 -0.6796 -0.7331 +vn 0.0989 -0.8408 -0.5322 +vn 0.0253 -0.6796 -0.7331 +vn 0.6366 -0.5043 -0.5834 +vn -0.6366 -0.5043 -0.5834 +vn 0.9253 0.0918 -0.3680 +vn -0.9253 0.0918 -0.3680 +vn 0.2870 0.5978 -0.7485 +vn -0.2870 0.5978 -0.7485 +vn -0.4142 0.5509 -0.7245 +vn 0.4142 0.5509 -0.7245 +vn -0.6501 0.5847 -0.4854 +vn 0.6501 0.5847 -0.4854 +vn -0.6708 -0.0453 -0.7403 +vn -0.3679 -0.2836 0.8856 +vn 0.4448 -0.0937 0.8907 +usemtl None +s 1 +f 55/15/7 11/16/8 53/17/9 +f 12/18/10 56/19/11 54/20/12 +f 53/17/9 13/21/13 51/22/14 +f 14/23/15 54/20/12 52/24/16 +f 11/16/8 15/25/17 13/21/13 +f 16/26/18 12/18/10 14/23/15 +f 9/27/19 17/28/20 11/16/8 +f 18/29/21 10/30/22 12/18/10 +f 19/31/23 23/32/24 17/28/20 +f 24/33/25 20/34/26 18/29/21 +f 17/28/20 25/35/27 15/25/17 +f 26/36/28 18/29/21 16/26/18 +f 29/37/29 25/35/27 23/32/24 +f 30/38/30 26/36/28 28/39/31 +f 21/40/32 29/37/29 23/32/24 +f 30/38/30 22/41/33 24/33/25 +f 31/42/34 35/43/35 29/37/29 +f 36/44/36 32/45/37 30/38/30 +f 35/43/35 27/46/38 29/37/29 +f 36/44/36 28/39/31 38/47/39 +f 41/48/40 37/49/41 35/43/35 +f 42/50/42 38/47/39 40/51/43 +f 43/52/44 35/43/35 33/53/45 +f 44/54/46 36/44/36 42/50/42 +f 45/55/47 41/48/40 43/52/44 +f 46/56/48 42/50/42 48/57/49 +f 47/58/50 39/59/51 41/48/40 +f 48/57/49 40/51/43 50/60/52 +f 53/17/9 49/61/53 47/58/50 +f 54/20/12 50/60/52 52/24/16 +f 55/15/7 47/58/50 45/55/47 +f 56/19/11 48/57/49 54/20/12 +f 45/55/47 57/62/54 55/15/7 +f 46/56/48 58/63/55 60/64/56 +f 43/52/44 59/65/57 45/55/47 +f 44/54/46 60/64/56 62/66/58 +f 33/53/45 61/67/59 43/52/44 +f 34/68/60 62/66/58 64/69/61 +f 31/42/34 63/70/62 33/53/45 +f 32/45/37 64/69/61 66/71/63 +f 31/42/34 67/72/64 65/73/65 +f 68/74/66 32/45/37 66/71/63 +f 21/40/32 71/75/67 67/72/64 +f 72/76/68 22/41/33 68/74/66 +f 19/31/23 73/77/69 71/75/67 +f 74/78/70 20/34/26 72/76/68 +f 9/27/19 57/62/54 73/77/69 +f 58/63/55 10/30/22 74/78/70 +f 69/79/71 73/77/69 57/62/54 +f 58/63/55 74/78/70 70/80/72 +f 71/75/67 73/77/69 69/79/71 +f 70/80/72 74/78/70 72/76/68 +f 69/79/71 67/72/64 71/75/67 +f 72/76/68 68/74/66 70/80/72 +f 69/79/71 65/73/65 67/72/64 +f 68/74/66 66/71/63 70/80/72 +f 69/79/71 63/70/62 65/73/65 +f 66/71/63 64/69/61 70/80/72 +f 69/79/71 61/67/59 63/70/62 +f 64/69/61 62/66/58 70/80/72 +f 69/79/71 59/65/57 61/67/59 +f 62/66/58 60/64/56 70/80/72 +f 69/79/71 57/62/54 59/65/57 +f 60/64/56 58/63/55 70/80/72 +f 182/81/73 99/82/74 97/83/75 +f 183/84/76 99/82/74 184/85/77 +f 180/86/78 97/83/75 95/87/79 +f 181/88/80 98/89/81 183/84/76 +f 93/90/82 180/86/78 95/87/79 +f 181/88/80 94/91/83 96/92/84 +f 91/93/85 178/94/86 93/90/82 +f 179/95/87 92/96/88 94/91/83 +f 89/97/89 176/98/90 91/93/85 +f 177/99/91 90/100/92 92/96/88 +f 87/101/93 154/102/94 172/103/95 +f 155/104/96 88/105/97 173/106/98 +f 102/107/99 154/102/94 100/108/100 +f 103/109/101 155/104/96 157/110/102 +f 102/107/99 158/111/103 156/112/104 +f 159/113/105 103/109/101 157/110/102 +f 106/114/106 158/111/103 104/115/107 +f 107/116/108 159/113/105 161/117/109 +f 108/118/110 160/119/111 106/114/106 +f 109/120/112 161/117/109 163/121/113 +f 110/122/114 162/123/115 108/118/110 +f 111/124/116 163/121/113 165/125/117 +f 110/122/114 166/126/118 164/127/119 +f 167/128/120 111/124/116 165/125/117 +f 114/129/121 166/126/118 112/130/122 +f 115/131/123 167/128/120 169/132/124 +f 116/133/125 168/134/126 114/129/121 +f 117/135/127 169/132/124 171/136/128 +f 75/137/129 170/138/130 116/133/125 +f 75/137/129 171/136/128 76/139/131 +f 136/140/132 170/138/130 118/141/133 +f 137/142/134 171/136/128 169/132/124 +f 136/140/132 166/126/118 168/134/126 +f 167/128/120 137/142/134 169/132/124 +f 164/127/119 187/143/135 134/144/136 +f 165/125/117 188/145/137 167/128/120 +f 162/123/115 134/144/136 132/146/138 +f 163/121/113 135/147/139 165/125/117 +f 160/119/111 132/146/138 130/148/140 +f 161/117/109 133/149/141 163/121/113 +f 158/111/103 130/148/140 128/150/142 +f 159/113/105 131/151/143 161/117/109 +f 156/112/104 128/150/142 126/152/144 +f 157/110/102 129/153/145 159/113/105 +f 154/102/94 126/152/144 124/154/146 +f 155/104/96 127/155/147 157/110/102 +f 172/103/95 124/154/146 122/156/148 +f 173/106/98 125/157/149 155/104/96 +f 122/156/148 185/158/150 172/103/95 +f 185/158/150 123/159/151 173/106/98 +f 170/138/130 120/160/152 118/141/133 +f 171/136/128 121/161/153 76/139/131 +f 120/160/152 186/162/154 191/163/155 +f 186/162/154 121/161/153 192/164/156 +f 189/165/157 186/162/154 185/158/150 +f 190/166/158 186/162/154 192/164/156 +f 143/167/159 184/85/77 182/81/73 +f 184/85/77 144/168/160 183/84/76 +f 141/169/161 182/81/73 180/86/78 +f 183/84/76 142/170/162 181/88/80 +f 141/169/161 178/94/86 139/171/163 +f 142/170/162 179/95/87 181/88/80 +f 174/172/164 193/173/165 176/98/90 +f 194/174/166 175/175/167 177/99/91 +f 139/171/163 176/98/90 193/173/165 +f 177/99/91 140/176/168 194/174/166 +f 198/177/169 195/178/170 152/179/171 +f 198/177/169 196/180/172 197/181/173 +f 195/178/170 77/182/174 193/173/165 +f 196/180/172 77/182/174 197/181/173 +f 139/171/163 77/182/174 138/183/175 +f 140/176/168 77/182/174 194/174/166 +f 150/184/176 199/185/177 152/179/171 +f 200/186/178 151/187/179 153/188/180 +f 148/189/181 201/190/182 150/184/176 +f 202/191/183 149/192/184 151/187/179 +f 205/193/185 148/189/181 147/194/186 +f 206/195/187 149/192/184 204/196/188 +f 79/197/189 147/194/186 146/198/190 +f 79/197/189 147/194/186 206/195/187 +f 152/179/171 78/199/191 198/177/169 +f 153/188/180 78/199/191 200/186/178 +f 199/185/177 216/200/192 78/199/191 +f 200/186/178 216/200/192 215/201/193 +f 79/197/189 208/202/194 205/193/185 +f 209/203/195 79/197/189 206/195/187 +f 205/193/185 210/204/196 203/205/197 +f 211/206/198 206/195/187 204/196/188 +f 210/204/196 201/190/182 203/205/197 +f 211/206/198 202/191/183 213/207/199 +f 201/190/182 214/208/200 199/185/177 +f 215/201/193 202/191/183 200/186/178 +f 212/209/201 208/202/194 207/210/202 +f 213/207/199 209/203/195 211/206/198 +f 207/210/202 214/208/200 212/209/201 +f 215/201/193 207/210/202 213/207/199 +f 147/194/186 172/103/95 185/158/150 +f 173/106/98 147/194/186 185/158/150 +f 148/189/181 219/211/203 172/103/95 +f 220/212/204 149/192/184 173/106/98 +f 152/179/171 219/211/203 150/184/176 +f 153/188/180 220/212/204 222/213/205 +f 195/178/170 221/214/206 152/179/171 +f 196/180/172 222/213/205 175/175/167 +f 217/215/207 174/172/164 89/97/89 +f 218/216/208 175/175/167 222/213/205 +f 223/217/209 221/214/206 217/215/207 +f 224/218/210 222/213/205 220/212/204 +f 87/101/93 219/211/203 223/217/209 +f 220/212/204 88/105/97 224/218/210 +f 138/183/175 230/219/211 139/171/163 +f 138/183/175 231/220/212 80/221/213 +f 141/169/161 230/219/211 228/222/214 +f 231/220/212 142/170/162 229/223/215 +f 143/167/159 228/222/214 226/224/216 +f 229/223/215 144/168/160 227/225/217 +f 145/226/218 226/224/216 225/227/219 +f 227/225/217 145/226/218 225/227/219 +f 226/224/216 239/228/220 225/227/219 +f 227/225/217 239/228/220 238/229/221 +f 226/224/216 235/230/222 237/231/223 +f 236/232/224 227/225/217 238/229/221 +f 228/222/214 233/233/225 235/230/222 +f 234/234/226 229/223/215 236/232/224 +f 80/221/213 233/233/225 230/219/211 +f 80/221/213 234/234/226 232/235/227 +f 232/235/227 237/231/223 233/233/225 +f 238/229/221 232/235/227 234/234/226 +f 233/233/225 237/231/223 235/230/222 +f 236/232/224 238/229/221 234/234/226 +f 191/163/155 242/236/228 240/237/229 +f 243/238/230 192/164/156 241/239/231 +f 120/160/152 240/237/229 262/240/232 +f 241/239/231 121/161/153 263/241/233 +f 120/160/152 264/242/234 118/141/133 +f 121/161/153 265/243/235 263/241/233 +f 122/156/148 242/236/228 189/165/157 +f 123/159/151 243/238/230 261/244/236 +f 122/156/148 258/245/237 260/246/238 +f 259/247/239 123/159/151 261/244/236 +f 124/154/146 256/248/240 258/245/237 +f 257/249/241 125/157/149 259/247/239 +f 126/152/144 254/250/242 256/248/240 +f 255/251/243 127/155/147 257/249/241 +f 128/150/142 252/252/244 254/250/242 +f 253/253/245 129/153/145 255/251/243 +f 132/146/138 252/252/244 130/148/140 +f 133/149/141 253/253/245 251/254/246 +f 134/144/136 250/255/247 132/146/138 +f 135/147/139 251/254/246 249/256/248 +f 134/144/136 244/257/249 248/258/250 +f 245/259/251 135/147/139 249/256/248 +f 187/143/135 246/260/252 244/257/249 +f 247/261/253 188/145/137 245/259/251 +f 136/140/132 264/242/234 246/260/252 +f 265/243/235 137/142/134 247/261/253 +f 264/242/234 284/262/254 246/260/252 +f 265/243/235 285/263/255 267/264/256 +f 244/257/249 284/262/254 286/265/257 +f 285/263/255 245/259/251 287/266/258 +f 244/257/249 282/267/259 248/258/250 +f 245/259/251 283/268/260 287/266/258 +f 248/258/250 280/269/261 250/255/247 +f 249/256/248 281/270/262 283/268/260 +f 252/252/244 280/269/261 278/271/263 +f 281/270/262 253/253/245 279/272/264 +f 252/252/244 276/273/265 254/250/242 +f 253/253/245 277/274/266 279/272/264 +f 256/248/240 276/273/265 274/275/267 +f 277/274/266 257/249/241 275/276/268 +f 256/248/240 272/277/269 258/245/237 +f 257/249/241 273/278/270 275/276/268 +f 258/245/237 270/279/271 260/246/238 +f 259/247/239 271/280/272 273/278/270 +f 242/236/228 270/279/271 288/281/273 +f 271/280/272 243/238/230 289/282/274 +f 264/242/234 268/283/275 266/284/276 +f 269/285/277 265/243/235 267/264/256 +f 262/240/232 290/286/278 268/283/275 +f 291/287/279 263/241/233 269/285/277 +f 240/237/229 288/281/273 290/286/278 +f 289/282/274 241/239/231 291/287/279 +f 75/137/129 292/288/280 81/289/281 +f 293/290/282 75/137/129 81/289/281 +f 116/133/125 294/291/283 292/288/280 +f 295/292/284 117/135/127 293/290/282 +f 112/130/122 294/291/283 114/129/121 +f 113/293/285 295/292/284 297/294/286 +f 110/122/114 296/295/287 112/130/122 +f 111/124/116 297/294/286 299/296/288 +f 108/118/110 298/297/289 110/122/114 +f 109/120/112 299/296/288 301/298/290 +f 108/118/110 302/299/291 300/300/292 +f 303/301/293 109/120/112 301/298/290 +f 104/115/107 302/299/291 106/114/106 +f 105/302/294 303/301/293 305/303/295 +f 104/115/107 306/304/296 304/305/297 +f 307/306/298 105/302/294 305/303/295 +f 102/107/99 308/307/299 306/304/296 +f 309/308/300 103/109/101 307/306/298 +f 317/309/301 346/310/302 316/311/303 +f 317/312/301 347/313/304 337/314/305 +f 316/311/303 344/315/306 315/316/307 +f 316/317/303 345/318/308 347/313/304 +f 315/316/307 348/319/309 314/320/310 +f 315/321/307 349/322/311 345/318/308 +f 97/83/75 314/320/310 348/319/309 +f 314/320/310 98/89/81 349/322/311 +f 95/87/79 348/319/309 342/323/312 +f 349/322/311 96/92/84 343/324/313 +f 93/90/82 342/323/312 338/325/314 +f 343/324/313 94/91/83 339/326/315 +f 91/93/85 338/325/314 340/327/316 +f 339/326/315 92/96/88 341/328/317 +f 338/325/314 346/310/302 340/327/316 +f 347/313/304 339/326/315 341/328/317 +f 342/323/312 344/315/306 338/325/314 +f 343/324/313 345/318/308 349/322/311 +f 340/327/316 336/329/318 334/330/319 +f 341/328/317 337/314/305 347/313/304 +f 89/97/89 340/327/316 334/330/319 +f 341/328/317 90/100/92 335/331/320 +f 350/332/321 223/217/209 217/215/207 +f 351/333/322 224/218/210 353/334/323 +f 334/330/319 217/215/207 89/97/89 +f 335/331/320 218/216/208 351/333/322 +f 223/217/209 354/335/324 87/101/93 +f 224/218/210 355/336/325 353/334/323 +f 354/335/324 100/108/100 87/101/93 +f 355/336/325 101/337/326 309/308/300 +f 332/338/327 312/339/328 85/340/329 +f 333/341/330 312/342/328 361/343/331 +f 360/344/332 86/345/333 312/339/328 +f 361/343/331 86/346/333 359/347/334 +f 86/345/333 356/348/335 313/349/336 +f 357/350/337 86/346/333 313/351/336 +f 313/349/336 336/329/318 317/309/301 +f 337/314/305 313/351/336 317/312/301 +f 336/329/318 350/332/321 334/330/319 +f 337/314/305 351/333/322 357/350/337 +f 304/305/297 326/352/338 318/353/339 +f 327/354/340 305/303/295 319/355/341 +f 324/356/342 85/340/329 84/357/343 +f 325/358/344 85/359/329 333/341/330 +f 366/360/345 311/361/346 310/362/347 +f 367/363/348 311/364/346 365/365/349 +f 311/361/346 362/366/350 83/367/351 +f 363/368/352 311/364/346 83/369/351 +f 83/367/351 324/356/342 84/357/343 +f 325/358/344 83/369/351 84/370/343 +f 300/371/292 370/372/353 372/373/354 +f 371/374/355 301/375/290 373/376/356 +f 372/373/354 376/377/357 374/378/358 +f 377/379/359 373/376/356 375/380/360 +f 374/378/358 378/381/361 380/382/362 +f 379/383/363 375/380/360 381/384/364 +f 380/382/362 384/385/365 382/386/366 +f 385/387/367 381/384/364 383/388/368 +f 386/389/369 384/385/365 322/390/370 +f 387/391/371 385/387/367 383/388/368 +f 324/356/342 382/386/366 386/389/369 +f 383/388/368 325/358/344 387/391/371 +f 362/366/350 380/382/362 382/386/366 +f 381/384/364 363/368/352 383/388/368 +f 364/392/372 374/378/358 380/382/362 +f 375/380/360 365/365/349 381/384/364 +f 366/360/345 372/373/354 374/378/358 +f 373/376/356 367/363/348 375/380/360 +f 300/371/292 368/393/373 298/394/289 +f 301/375/290 369/395/374 373/376/356 +f 368/393/373 310/362/347 82/396/375 +f 369/395/374 310/397/347 367/363/348 +f 292/398/280 296/399/287 298/394/289 +f 297/400/286 293/401/282 299/402/288 +f 292/398/280 368/393/373 82/396/375 +f 369/395/374 293/401/282 82/403/375 +f 81/404/281 292/398/280 82/396/375 +f 82/403/375 293/401/282 81/405/281 +f 304/305/297 370/372/353 302/299/291 +f 305/303/295 371/374/355 319/355/341 +f 318/353/339 376/377/357 370/372/353 +f 377/379/359 319/355/341 371/374/355 +f 320/406/376 378/381/361 376/377/357 +f 379/383/363 321/407/377 377/379/359 +f 384/385/365 390/408/378 322/390/370 +f 385/387/367 391/409/379 379/383/363 +f 358/410/380 392/411/381 356/348/335 +f 359/347/334 393/412/382 395/413/383 +f 392/411/381 328/414/384 326/352/338 +f 393/412/382 329/415/385 395/413/383 +f 306/304/296 392/411/381 326/352/338 +f 393/412/382 307/306/298 327/354/340 +f 308/307/299 350/332/321 392/411/381 +f 351/333/322 309/308/300 393/412/382 +f 350/332/321 356/348/335 392/411/381 +f 393/412/382 357/350/337 351/333/322 +f 308/307/299 354/335/324 352/416/386 +f 353/334/323 355/336/325 309/308/300 +f 330/417/387 386/389/369 322/390/370 +f 331/418/388 387/391/371 389/419/389 +f 386/389/369 332/338/327 324/356/342 +f 387/391/371 333/341/330 389/419/389 +f 394/420/390 330/417/387 328/414/384 +f 395/413/383 331/418/388 389/419/389 +f 360/344/332 394/420/390 358/410/380 +f 361/343/331 395/413/383 389/419/389 +f 332/338/327 388/421/391 360/344/332 +f 361/343/331 389/419/389 333/341/330 +f 396/422/392 410/423/393 408/424/394 +f 397/425/395 411/426/396 423/427/397 +f 408/424/394 412/428/398 406/429/399 +f 413/430/400 409/431/401 407/432/402 +f 412/428/398 404/433/403 406/429/399 +f 413/430/400 405/434/404 415/435/405 +f 414/436/406 402/437/407 404/433/403 +f 415/435/405 403/438/408 417/439/409 +f 416/440/410 400/441/411 402/437/407 +f 417/439/409 401/442/412 419/443/413 +f 400/441/411 420/444/414 398/445/415 +f 421/446/416 401/442/412 399/447/417 +f 418/448/418 426/449/419 420/444/414 +f 427/450/420 419/443/413 421/446/416 +f 416/440/410 428/451/421 418/448/418 +f 429/452/422 417/439/409 419/443/413 +f 432/453/423 416/440/410 414/436/406 +f 433/454/424 417/439/409 431/455/425 +f 434/456/426 414/436/406 412/428/398 +f 435/457/427 415/435/405 433/454/424 +f 436/458/428 412/428/398 410/423/393 +f 437/459/429 413/430/400 435/457/427 +f 410/423/393 424/460/430 436/458/428 +f 425/461/431 411/426/396 437/459/429 +f 328/414/384 450/462/432 326/352/338 +f 329/415/385 451/463/433 453/464/434 +f 398/445/415 452/465/435 328/466/384 +f 399/447/417 453/467/434 421/446/416 +f 318/353/339 450/462/432 320/406/376 +f 451/463/433 319/355/341 321/407/377 +f 390/468/378 422/469/436 396/422/392 +f 423/427/397 391/470/379 397/425/395 +f 420/444/414 448/471/437 452/465/435 +f 449/472/438 421/446/416 453/467/434 +f 454/473/439 448/471/437 446/474/440 +f 455/475/441 449/472/438 453/467/434 +f 442/476/442 446/474/440 444/477/443 +f 447/478/444 443/479/445 445/480/446 +f 456/481/447 442/476/442 440/482/448 +f 457/483/449 443/479/445 455/475/441 +f 456/481/447 458/484/450 438/485/451 +f 457/483/449 459/486/452 441/487/453 +f 438/485/451 424/460/430 422/469/436 +f 439/488/454 425/461/431 459/486/452 +f 320/406/376 438/489/451 390/408/378 +f 439/490/454 321/407/377 391/409/379 +f 450/462/432 456/491/447 320/406/376 +f 451/463/433 457/492/449 455/493/441 +f 450/462/432 452/494/435 454/495/439 +f 455/493/441 453/464/434 451/463/433 +f 424/460/430 460/496/455 484/497/456 +f 461/498/457 425/461/431 485/499/458 +f 440/482/448 460/496/455 458/484/450 +f 441/487/453 461/498/457 471/500/459 +f 440/482/448 468/501/460 470/502/461 +f 469/503/462 441/487/453 471/500/459 +f 444/477/443 468/501/460 442/476/442 +f 445/480/446 469/503/462 467/504/463 +f 446/474/440 466/505/464 444/477/443 +f 447/478/444 467/504/463 465/506/465 +f 446/474/440 462/507/466 464/508/467 +f 463/509/468 447/478/444 465/506/465 +f 448/471/437 482/510/469 462/507/466 +f 483/511/470 449/472/438 463/509/468 +f 436/458/428 484/497/456 472/512/471 +f 485/499/458 437/459/429 473/513/472 +f 434/456/426 472/512/471 474/514/473 +f 473/513/472 435/457/427 475/515/474 +f 432/453/423 474/514/473 476/516/475 +f 475/515/474 433/454/424 477/517/476 +f 432/453/423 478/518/477 430/519/478 +f 433/454/424 479/520/479 477/517/476 +f 430/519/478 480/521/480 428/451/421 +f 431/455/425 481/522/481 479/520/479 +f 428/451/421 482/510/469 426/449/419 +f 429/452/422 483/511/470 481/522/481 +f 464/508/467 486/523/482 466/505/464 +f 465/506/465 487/524/483 489/525/484 +f 488/526/485 492/527/486 486/523/482 +f 489/525/484 493/528/487 491/529/488 +f 492/527/486 496/530/489 494/531/490 +f 497/532/491 493/528/487 495/533/492 +f 496/530/489 500/534/493 494/531/490 +f 497/532/491 501/535/494 499/536/495 +f 472/512/471 494/531/490 500/534/493 +f 495/533/492 473/513/472 501/535/494 +f 492/527/486 484/497/456 460/496/455 +f 493/528/487 485/499/458 495/533/492 +f 470/502/461 492/527/486 460/496/455 +f 471/500/459 493/528/487 487/524/483 +f 466/505/464 470/502/461 468/501/460 +f 471/500/459 467/504/463 469/503/462 +f 482/510/469 464/508/467 462/507/466 +f 483/511/470 465/506/465 489/525/484 +f 480/521/480 488/526/485 482/510/469 +f 489/525/484 481/522/481 483/511/470 +f 496/530/489 480/521/480 478/518/477 +f 497/532/491 481/522/481 491/529/488 +f 498/537/496 478/518/477 476/516/475 +f 499/536/495 479/520/479 497/532/491 +f 474/514/473 498/537/496 476/516/475 +f 499/536/495 475/515/474 477/517/476 +f 472/512/471 500/534/493 474/514/473 +f 475/515/474 501/535/494 473/513/472 +f 400/441/411 512/538/497 510/539/498 +f 513/540/499 401/442/412 511/541/500 +f 402/437/407 510/539/498 508/542/501 +f 511/541/500 403/438/408 509/543/502 +f 402/437/407 506/544/503 404/433/403 +f 403/438/408 507/545/504 509/543/502 +f 404/433/403 504/546/505 406/547/399 +f 405/434/404 505/548/506 507/545/504 +f 406/547/399 502/549/507 408/550/394 +f 407/551/402 503/552/508 505/548/506 +f 408/550/394 514/553/509 396/554/392 +f 409/555/401 515/556/510 503/552/508 +f 510/539/498 514/553/509 502/549/507 +f 511/541/500 515/556/510 513/540/499 +f 502/549/507 508/542/501 510/539/498 +f 509/543/502 503/552/508 511/541/500 +f 504/546/505 506/544/503 508/542/501 +f 509/543/502 507/545/504 505/548/506 +f 390/408/378 514/557/509 322/390/370 +f 391/558/379 515/556/510 397/559/395 +f 322/560/370 512/538/497 330/561/387 +f 513/540/499 323/562/511 331/563/388 +f 328/466/384 512/538/497 398/445/415 +f 513/540/499 329/564/385 399/447/417 +f 55/15/7 9/27/19 11/16/8 +f 12/18/10 10/30/22 56/19/11 +f 53/17/9 11/16/8 13/21/13 +f 14/23/15 12/18/10 54/20/12 +f 11/16/8 17/28/20 15/25/17 +f 16/26/18 18/29/21 12/18/10 +f 9/27/19 19/31/23 17/28/20 +f 18/29/21 20/34/26 10/30/22 +f 19/31/23 21/40/32 23/32/24 +f 24/33/25 22/41/33 20/34/26 +f 17/28/20 23/32/24 25/35/27 +f 26/36/28 24/33/25 18/29/21 +f 29/37/29 27/46/38 25/35/27 +f 30/38/30 24/33/25 26/36/28 +f 21/40/32 31/42/34 29/37/29 +f 30/38/30 32/45/37 22/41/33 +f 31/42/34 33/53/45 35/43/35 +f 36/44/36 34/68/60 32/45/37 +f 35/43/35 37/49/41 27/46/38 +f 36/44/36 30/38/30 28/39/31 +f 41/48/40 39/59/51 37/49/41 +f 42/50/42 36/44/36 38/47/39 +f 43/52/44 41/48/40 35/43/35 +f 44/54/46 34/68/60 36/44/36 +f 45/55/47 47/58/50 41/48/40 +f 46/56/48 44/54/46 42/50/42 +f 47/58/50 49/61/53 39/59/51 +f 48/57/49 42/50/42 40/51/43 +f 53/17/9 51/22/14 49/61/53 +f 54/20/12 48/57/49 50/60/52 +f 55/15/7 53/17/9 47/58/50 +f 56/19/11 46/56/48 48/57/49 +f 45/55/47 59/65/57 57/62/54 +f 46/56/48 56/19/11 58/63/55 +f 43/52/44 61/67/59 59/65/57 +f 44/54/46 46/56/48 60/64/56 +f 33/53/45 63/70/62 61/67/59 +f 34/68/60 44/54/46 62/66/58 +f 31/42/34 65/73/65 63/70/62 +f 32/45/37 34/68/60 64/69/61 +f 31/42/34 21/40/32 67/72/64 +f 68/74/66 22/41/33 32/45/37 +f 21/40/32 19/31/23 71/75/67 +f 72/76/68 20/34/26 22/41/33 +f 19/31/23 9/27/19 73/77/69 +f 74/78/70 10/30/22 20/34/26 +f 9/27/19 55/15/7 57/62/54 +f 58/63/55 56/19/11 10/30/22 +f 182/81/73 184/85/77 99/82/74 +f 183/84/76 98/89/81 99/82/74 +f 180/86/78 182/81/73 97/83/75 +f 181/88/80 96/92/84 98/89/81 +f 93/90/82 178/94/86 180/86/78 +f 181/88/80 179/95/87 94/91/83 +f 91/93/85 176/98/90 178/94/86 +f 179/95/87 177/99/91 92/96/88 +f 89/97/89 174/172/164 176/98/90 +f 177/99/91 175/175/167 90/100/92 +f 87/101/93 100/108/100 154/102/94 +f 155/104/96 101/337/326 88/105/97 +f 102/107/99 156/112/104 154/102/94 +f 103/109/101 101/337/326 155/104/96 +f 102/107/99 104/115/107 158/111/103 +f 159/113/105 105/302/294 103/109/101 +f 106/114/106 160/119/111 158/111/103 +f 107/116/108 105/302/294 159/113/105 +f 108/118/110 162/123/115 160/119/111 +f 109/120/112 107/116/108 161/117/109 +f 110/122/114 164/127/119 162/123/115 +f 111/124/116 109/120/112 163/121/113 +f 110/122/114 112/130/122 166/126/118 +f 167/128/120 113/293/285 111/124/116 +f 114/129/121 168/134/126 166/126/118 +f 115/131/123 113/293/285 167/128/120 +f 116/133/125 170/138/130 168/134/126 +f 117/135/127 115/131/123 169/132/124 +f 75/137/129 76/139/131 170/138/130 +f 75/137/129 117/135/127 171/136/128 +f 136/140/132 168/134/126 170/138/130 +f 137/142/134 119/565/512 171/136/128 +f 136/140/132 187/143/135 166/126/118 +f 167/128/120 188/145/137 137/142/134 +f 164/127/119 166/126/118 187/143/135 +f 165/125/117 135/147/139 188/145/137 +f 162/123/115 164/127/119 134/144/136 +f 163/121/113 133/149/141 135/147/139 +f 160/119/111 162/123/115 132/146/138 +f 161/117/109 131/151/143 133/149/141 +f 158/111/103 160/119/111 130/148/140 +f 159/113/105 129/153/145 131/151/143 +f 156/112/104 158/111/103 128/150/142 +f 157/110/102 127/155/147 129/153/145 +f 154/102/94 156/112/104 126/152/144 +f 155/104/96 125/157/149 127/155/147 +f 172/103/95 154/102/94 124/154/146 +f 173/106/98 123/159/151 125/157/149 +f 122/156/148 189/165/157 185/158/150 +f 185/158/150 190/166/158 123/159/151 +f 170/138/130 76/139/131 120/160/152 +f 171/136/128 119/565/512 121/161/153 +f 120/160/152 76/139/131 186/162/154 +f 186/162/154 76/139/131 121/161/153 +f 189/165/157 191/163/155 186/162/154 +f 190/166/158 185/158/150 186/162/154 +f 143/167/159 145/226/218 184/85/77 +f 184/85/77 145/226/218 144/168/160 +f 141/169/161 143/167/159 182/81/73 +f 183/84/76 144/168/160 142/170/162 +f 141/169/161 180/86/78 178/94/86 +f 142/170/162 140/176/168 179/95/87 +f 174/172/164 195/178/170 193/173/165 +f 194/174/166 196/180/172 175/175/167 +f 139/171/163 178/94/86 176/98/90 +f 177/99/91 179/95/87 140/176/168 +f 198/177/169 197/181/173 195/178/170 +f 198/177/169 153/188/180 196/180/172 +f 195/178/170 197/181/173 77/182/174 +f 196/180/172 194/174/166 77/182/174 +f 139/171/163 193/173/165 77/182/174 +f 140/176/168 138/183/175 77/182/174 +f 150/184/176 201/190/182 199/185/177 +f 200/186/178 202/191/183 151/187/179 +f 148/189/181 203/205/197 201/190/182 +f 202/191/183 204/196/188 149/192/184 +f 205/193/185 203/205/197 148/189/181 +f 206/195/187 147/194/186 149/192/184 +f 79/197/189 205/193/185 147/194/186 +f 79/197/189 146/198/190 147/194/186 +f 152/179/171 199/185/177 78/199/191 +f 153/188/180 198/177/169 78/199/191 +f 199/185/177 214/208/200 216/200/192 +f 200/186/178 78/199/191 216/200/192 +f 79/197/189 207/210/202 208/202/194 +f 209/203/195 207/210/202 79/197/189 +f 205/193/185 208/202/194 210/204/196 +f 211/206/198 209/203/195 206/195/187 +f 210/204/196 212/209/201 201/190/182 +f 211/206/198 204/196/188 202/191/183 +f 201/190/182 212/209/201 214/208/200 +f 215/201/193 213/207/199 202/191/183 +f 212/209/201 210/204/196 208/202/194 +f 213/207/199 207/210/202 209/203/195 +f 207/210/202 216/200/192 214/208/200 +f 215/201/193 216/200/192 207/210/202 +f 147/194/186 148/189/181 172/103/95 +f 173/106/98 149/192/184 147/194/186 +f 148/189/181 150/184/176 219/211/203 +f 220/212/204 151/187/179 149/192/184 +f 152/179/171 221/214/206 219/211/203 +f 153/188/180 151/187/179 220/212/204 +f 195/178/170 174/172/164 221/214/206 +f 196/180/172 153/188/180 222/213/205 +f 217/215/207 221/214/206 174/172/164 +f 218/216/208 90/100/92 175/175/167 +f 223/217/209 219/211/203 221/214/206 +f 224/218/210 218/216/208 222/213/205 +f 87/101/93 172/103/95 219/211/203 +f 220/212/204 173/106/98 88/105/97 +f 138/183/175 80/221/213 230/219/211 +f 138/183/175 140/176/168 231/220/212 +f 141/169/161 139/171/163 230/219/211 +f 231/220/212 140/176/168 142/170/162 +f 143/167/159 141/169/161 228/222/214 +f 229/223/215 142/170/162 144/168/160 +f 145/226/218 143/167/159 226/224/216 +f 227/225/217 144/168/160 145/226/218 +f 226/224/216 237/231/223 239/228/220 +f 227/225/217 225/227/219 239/228/220 +f 226/224/216 228/222/214 235/230/222 +f 236/232/224 229/223/215 227/225/217 +f 228/222/214 230/219/211 233/233/225 +f 234/234/226 231/220/212 229/223/215 +f 80/221/213 232/235/227 233/233/225 +f 80/221/213 231/220/212 234/234/226 +f 232/235/227 239/228/220 237/231/223 +f 238/229/221 239/228/220 232/235/227 +f 191/163/155 189/165/157 242/236/228 +f 243/238/230 190/166/158 192/164/156 +f 120/160/152 191/163/155 240/237/229 +f 241/239/231 192/164/156 121/161/153 +f 120/160/152 262/240/232 264/242/234 +f 121/161/153 119/565/512 265/243/235 +f 122/156/148 260/246/238 242/236/228 +f 123/159/151 190/166/158 243/238/230 +f 122/156/148 124/154/146 258/245/237 +f 259/247/239 125/157/149 123/159/151 +f 124/154/146 126/152/144 256/248/240 +f 257/249/241 127/155/147 125/157/149 +f 126/152/144 128/150/142 254/250/242 +f 255/251/243 129/153/145 127/155/147 +f 128/150/142 130/148/140 252/252/244 +f 253/253/245 131/151/143 129/153/145 +f 132/146/138 250/255/247 252/252/244 +f 133/149/141 131/151/143 253/253/245 +f 134/144/136 248/258/250 250/255/247 +f 135/147/139 133/149/141 251/254/246 +f 134/144/136 187/143/135 244/257/249 +f 245/259/251 188/145/137 135/147/139 +f 187/143/135 136/140/132 246/260/252 +f 247/261/253 137/142/134 188/145/137 +f 136/140/132 118/141/133 264/242/234 +f 265/243/235 119/565/512 137/142/134 +f 264/242/234 266/284/276 284/262/254 +f 265/243/235 247/261/253 285/263/255 +f 244/257/249 246/260/252 284/262/254 +f 285/263/255 247/261/253 245/259/251 +f 244/257/249 286/265/257 282/267/259 +f 245/259/251 249/256/248 283/268/260 +f 248/258/250 282/267/259 280/269/261 +f 249/256/248 251/254/246 281/270/262 +f 252/252/244 250/255/247 280/269/261 +f 281/270/262 251/254/246 253/253/245 +f 252/252/244 278/271/263 276/273/265 +f 253/253/245 255/251/243 277/274/266 +f 256/248/240 254/250/242 276/273/265 +f 277/274/266 255/251/243 257/249/241 +f 256/248/240 274/275/267 272/277/269 +f 257/249/241 259/247/239 273/278/270 +f 258/245/237 272/277/269 270/279/271 +f 259/247/239 261/244/236 271/280/272 +f 242/236/228 260/246/238 270/279/271 +f 271/280/272 261/244/236 243/238/230 +f 264/242/234 262/240/232 268/283/275 +f 269/285/277 263/241/233 265/243/235 +f 262/240/232 240/237/229 290/286/278 +f 291/287/279 241/239/231 263/241/233 +f 240/237/229 242/236/228 288/281/273 +f 289/282/274 243/238/230 241/239/231 +f 75/137/129 116/133/125 292/288/280 +f 293/290/282 117/135/127 75/137/129 +f 116/133/125 114/129/121 294/291/283 +f 295/292/284 115/131/123 117/135/127 +f 112/130/122 296/295/287 294/291/283 +f 113/293/285 115/131/123 295/292/284 +f 110/122/114 298/297/289 296/295/287 +f 111/124/116 113/293/285 297/294/286 +f 108/118/110 300/300/292 298/297/289 +f 109/120/112 111/124/116 299/296/288 +f 108/118/110 106/114/106 302/299/291 +f 303/301/293 107/116/108 109/120/112 +f 104/115/107 304/305/297 302/299/291 +f 105/302/294 107/116/108 303/301/293 +f 104/115/107 102/107/99 306/304/296 +f 307/306/298 103/109/101 105/302/294 +f 102/107/99 100/108/100 308/307/299 +f 309/308/300 101/337/326 103/109/101 +f 317/309/301 336/329/318 346/310/302 +f 317/312/301 316/317/303 347/313/304 +f 316/311/303 346/310/302 344/315/306 +f 316/317/303 315/321/307 345/318/308 +f 315/316/307 344/315/306 348/319/309 +f 315/321/307 314/320/310 349/322/311 +f 97/83/75 99/82/74 314/320/310 +f 314/320/310 99/82/74 98/89/81 +f 95/87/79 97/83/75 348/319/309 +f 349/322/311 98/89/81 96/92/84 +f 93/90/82 95/87/79 342/323/312 +f 343/324/313 96/92/84 94/91/83 +f 91/93/85 93/90/82 338/325/314 +f 339/326/315 94/91/83 92/96/88 +f 338/325/314 344/315/306 346/310/302 +f 347/313/304 345/318/308 339/326/315 +f 342/323/312 348/319/309 344/315/306 +f 343/324/313 339/326/315 345/318/308 +f 340/327/316 346/310/302 336/329/318 +f 341/328/317 335/331/320 337/314/305 +f 89/97/89 91/93/85 340/327/316 +f 341/328/317 92/96/88 90/100/92 +f 350/332/321 352/416/386 223/217/209 +f 351/333/322 218/216/208 224/218/210 +f 334/330/319 350/332/321 217/215/207 +f 335/331/320 90/100/92 218/216/208 +f 223/217/209 352/416/386 354/335/324 +f 224/218/210 88/105/97 355/336/325 +f 354/335/324 308/307/299 100/108/100 +f 355/336/325 88/105/97 101/337/326 +f 332/338/327 360/344/332 312/339/328 +f 333/341/330 85/359/329 312/342/328 +f 360/344/332 358/410/380 86/345/333 +f 361/343/331 312/342/328 86/346/333 +f 86/345/333 358/410/380 356/348/335 +f 357/350/337 359/347/334 86/346/333 +f 313/349/336 356/348/335 336/329/318 +f 337/314/305 357/350/337 313/351/336 +f 336/329/318 356/348/335 350/332/321 +f 337/314/305 335/331/320 351/333/322 +f 304/305/297 306/304/296 326/352/338 +f 327/354/340 307/306/298 305/303/295 +f 324/356/342 332/338/327 85/340/329 +f 325/358/344 84/370/343 85/359/329 +f 366/360/345 364/392/372 311/361/346 +f 367/363/348 310/397/347 311/364/346 +f 311/361/346 364/392/372 362/366/350 +f 363/368/352 365/365/349 311/364/346 +f 83/367/351 362/366/350 324/356/342 +f 325/358/344 363/368/352 83/369/351 +f 300/371/292 302/299/291 370/372/353 +f 371/374/355 303/301/293 301/375/290 +f 372/373/354 370/372/353 376/377/357 +f 377/379/359 371/374/355 373/376/356 +f 374/378/358 376/377/357 378/381/361 +f 379/383/363 377/379/359 375/380/360 +f 380/382/362 378/381/361 384/385/365 +f 385/387/367 379/383/363 381/384/364 +f 386/389/369 382/386/366 384/385/365 +f 387/391/371 323/566/511 385/387/367 +f 324/356/342 362/366/350 382/386/366 +f 383/388/368 363/368/352 325/358/344 +f 362/366/350 364/392/372 380/382/362 +f 381/384/364 365/365/349 363/368/352 +f 364/392/372 366/360/345 374/378/358 +f 375/380/360 367/363/348 365/365/349 +f 366/360/345 368/393/373 372/373/354 +f 373/376/356 369/395/374 367/363/348 +f 300/371/292 372/373/354 368/393/373 +f 301/375/290 299/402/288 369/395/374 +f 368/393/373 366/360/345 310/362/347 +f 369/395/374 82/403/375 310/397/347 +f 292/398/280 294/567/283 296/399/287 +f 297/400/286 295/568/284 293/401/282 +f 292/398/280 298/394/289 368/393/373 +f 369/395/374 299/402/288 293/401/282 +f 304/305/297 318/353/339 370/372/353 +f 305/303/295 303/301/293 371/374/355 +f 318/353/339 320/406/376 376/377/357 +f 377/379/359 321/407/377 319/355/341 +f 320/406/376 390/408/378 378/381/361 +f 379/383/363 391/409/379 321/407/377 +f 384/385/365 378/381/361 390/408/378 +f 385/387/367 323/566/511 391/409/379 +f 358/410/380 394/420/390 392/411/381 +f 359/347/334 357/350/337 393/412/382 +f 392/411/381 394/420/390 328/414/384 +f 393/412/382 327/354/340 329/415/385 +f 306/304/296 308/307/299 392/411/381 +f 393/412/382 309/308/300 307/306/298 +f 308/307/299 352/416/386 350/332/321 +f 351/333/322 353/334/323 309/308/300 +f 330/417/387 388/421/391 386/389/369 +f 331/418/388 323/566/511 387/391/371 +f 386/389/369 388/421/391 332/338/327 +f 387/391/371 325/358/344 333/341/330 +f 394/420/390 388/421/391 330/417/387 +f 395/413/383 329/415/385 331/418/388 +f 360/344/332 388/421/391 394/420/390 +f 361/343/331 359/347/334 395/413/383 +f 396/422/392 422/469/436 410/423/393 +f 397/425/395 409/431/401 411/426/396 +f 408/424/394 410/423/393 412/428/398 +f 413/430/400 411/426/396 409/431/401 +f 412/428/398 414/436/406 404/433/403 +f 413/430/400 407/432/402 405/434/404 +f 414/436/406 416/440/410 402/437/407 +f 415/435/405 405/434/404 403/438/408 +f 416/440/410 418/448/418 400/441/411 +f 417/439/409 403/438/408 401/442/412 +f 400/441/411 418/448/418 420/444/414 +f 421/446/416 419/443/413 401/442/412 +f 418/448/418 428/451/421 426/449/419 +f 427/450/420 429/452/422 419/443/413 +f 416/440/410 430/519/478 428/451/421 +f 429/452/422 431/455/425 417/439/409 +f 432/453/423 430/519/478 416/440/410 +f 433/454/424 415/435/405 417/439/409 +f 434/456/426 432/453/423 414/436/406 +f 435/457/427 413/430/400 415/435/405 +f 436/458/428 434/456/426 412/428/398 +f 437/459/429 411/426/396 413/430/400 +f 410/423/393 422/469/436 424/460/430 +f 425/461/431 423/427/397 411/426/396 +f 328/414/384 452/494/435 450/462/432 +f 329/415/385 327/354/340 451/463/433 +f 398/445/415 420/444/414 452/465/435 +f 399/447/417 329/564/385 453/467/434 +f 318/353/339 326/352/338 450/462/432 +f 451/463/433 327/354/340 319/355/341 +f 390/468/378 438/485/451 422/469/436 +f 423/427/397 439/488/454 391/470/379 +f 420/444/414 426/449/419 448/471/437 +f 449/472/438 427/450/420 421/446/416 +f 454/473/439 452/465/435 448/471/437 +f 455/475/441 447/478/444 449/472/438 +f 442/476/442 454/473/439 446/474/440 +f 447/478/444 455/475/441 443/479/445 +f 456/481/447 454/473/439 442/476/442 +f 457/483/449 441/487/453 443/479/445 +f 456/481/447 440/482/448 458/484/450 +f 457/483/449 439/488/454 459/486/452 +f 438/485/451 458/484/450 424/460/430 +f 439/488/454 423/427/397 425/461/431 +f 320/406/376 456/491/447 438/489/451 +f 439/490/454 457/492/449 321/407/377 +f 450/462/432 454/495/439 456/491/447 +f 451/463/433 321/407/377 457/492/449 +f 424/460/430 458/484/450 460/496/455 +f 461/498/457 459/486/452 425/461/431 +f 440/482/448 470/502/461 460/496/455 +f 441/487/453 459/486/452 461/498/457 +f 440/482/448 442/476/442 468/501/460 +f 469/503/462 443/479/445 441/487/453 +f 444/477/443 466/505/464 468/501/460 +f 445/480/446 443/479/445 469/503/462 +f 446/474/440 464/508/467 466/505/464 +f 447/478/444 445/480/446 467/504/463 +f 446/474/440 448/471/437 462/507/466 +f 463/509/468 449/472/438 447/478/444 +f 448/471/437 426/449/419 482/510/469 +f 483/511/470 427/450/420 449/472/438 +f 436/458/428 424/460/430 484/497/456 +f 485/499/458 425/461/431 437/459/429 +f 434/456/426 436/458/428 472/512/471 +f 473/513/472 437/459/429 435/457/427 +f 432/453/423 434/456/426 474/514/473 +f 475/515/474 435/457/427 433/454/424 +f 432/453/423 476/516/475 478/518/477 +f 433/454/424 431/455/425 479/520/479 +f 430/519/478 478/518/477 480/521/480 +f 431/455/425 429/452/422 481/522/481 +f 428/451/421 480/521/480 482/510/469 +f 429/452/422 427/450/420 483/511/470 +f 464/508/467 488/526/485 486/523/482 +f 465/506/465 467/504/463 487/524/483 +f 488/526/485 490/569/513 492/527/486 +f 489/525/484 487/524/483 493/528/487 +f 492/527/486 490/569/513 496/530/489 +f 497/532/491 491/529/488 493/528/487 +f 496/530/489 498/537/496 500/534/493 +f 497/532/491 495/533/492 501/535/494 +f 472/512/471 484/497/456 494/531/490 +f 495/533/492 485/499/458 473/513/472 +f 492/527/486 494/531/490 484/497/456 +f 493/528/487 461/498/457 485/499/458 +f 470/502/461 486/523/482 492/527/486 +f 471/500/459 461/498/457 493/528/487 +f 466/505/464 486/523/482 470/502/461 +f 471/500/459 487/524/483 467/504/463 +f 482/510/469 488/526/485 464/508/467 +f 483/511/470 463/509/468 465/506/465 +f 480/521/480 490/569/513 488/526/485 +f 489/525/484 491/529/488 481/522/481 +f 496/530/489 490/569/513 480/521/480 +f 497/532/491 479/520/479 481/522/481 +f 498/537/496 496/530/489 478/518/477 +f 499/536/495 477/517/476 479/520/479 +f 474/514/473 500/534/493 498/537/496 +f 499/536/495 501/535/494 475/515/474 +f 400/441/411 398/445/415 512/538/497 +f 513/540/499 399/447/417 401/442/412 +f 402/437/407 400/441/411 510/539/498 +f 511/541/500 401/442/412 403/438/408 +f 402/437/407 508/542/501 506/544/503 +f 403/438/408 405/434/404 507/545/504 +f 404/433/403 506/544/503 504/546/505 +f 405/434/404 407/551/402 505/548/506 +f 406/547/399 504/546/505 502/549/507 +f 407/551/402 409/555/401 503/552/508 +f 408/550/394 502/549/507 514/553/509 +f 409/555/401 397/559/395 515/556/510 +f 510/539/498 512/538/497 514/553/509 +f 511/541/500 503/552/508 515/556/510 +f 502/549/507 504/546/505 508/542/501 +f 509/543/502 505/548/506 503/552/508 +f 390/408/378 396/570/392 514/557/509 +f 391/558/379 323/562/511 515/556/510 +f 322/560/370 514/553/509 512/538/497 +f 513/540/499 515/556/510 323/562/511 +f 328/466/384 330/561/387 512/538/497 +f 513/540/499 331/563/388 329/564/385 diff --git a/examples/src/ray_scene/mod.rs b/examples/src/ray_scene/mod.rs new file mode 100644 index 000000000..886d0c018 --- /dev/null +++ b/examples/src/ray_scene/mod.rs @@ -0,0 +1,569 @@ +use bytemuck::{Pod, Zeroable}; +use glam::{Mat4, Quat, Vec3}; +use std::f32::consts::PI; +use std::ops::IndexMut; +use std::{borrow::Cow, future::Future, iter, mem, ops::Range, pin::Pin, task, time::Instant}; +use wgpu::util::DeviceExt; + +// from cube +#[repr(C)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Default)] +struct Vertex { + pos: [f32; 3], + _p0: [u32; 1], + normal: [f32; 3], + _p1: [u32; 1], + uv: [f32; 2], + _p2: [u32; 2], +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Uniforms { + view_inverse: Mat4, + proj_inverse: Mat4, +} + +/// A wrapper for `pop_error_scope` futures that panics if an error occurs. +/// +/// Given a future `inner` of an `Option` for some error type `E`, +/// wait for the future to be ready, and panic if its value is `Some`. +/// +/// This can be done simpler with `FutureExt`, but we don't want to add +/// a dependency just for this small case. +struct ErrorFuture { + inner: F, +} +impl>> Future for ErrorFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<()> { + let inner = unsafe { self.map_unchecked_mut(|me| &mut me.inner) }; + inner.poll(cx).map(|error| { + if let Some(e) = error { + panic!("Rendering {}", e); + } + }) + } +} + +#[derive(Debug, Clone, Default)] +struct RawSceneComponents { + vertices: Vec, + indices: Vec, + geometries: Vec<(Range, Material)>, // index range, material + instances: Vec<(Range, Range)>, // vertex range, geometry range +} + +struct SceneComponents { + vertices: wgpu::Buffer, + indices: wgpu::Buffer, + geometries: wgpu::Buffer, + instances: wgpu::Buffer, + bottom_level_acceleration_structures: Vec, +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct InstanceEntry { + first_vertex: u32, + first_geometry: u32, + last_geometry: u32, + _pad: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable, Default)] +struct GeometryEntry { + first_index: u32, + _p0: [u32; 3], + material: Material, +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable, Default, Debug)] +struct Material { + roughness_exponent: f32, + metalness: f32, + specularity: f32, + _p0: [u32; 1], + albedo: [f32; 3], + _p1: [u32; 1], +} + +fn load_model(scene: &mut RawSceneComponents, path: &str) { + let path = env!("CARGO_MANIFEST_DIR").to_string() + "/src" + path; + println!("{}", path); + let mut object = obj::Obj::load(path).unwrap(); + object.load_mtls().unwrap(); + + let data = object.data; + + let start_vertex_index = scene.vertices.len(); + let start_geometry_index = scene.geometries.len(); + + let mut mapping = std::collections::HashMap::<(usize, usize, usize), usize>::new(); + + let mut next_index = 0; + + for object in data.objects { + for group in object.groups { + let start_index_index = scene.indices.len(); + for poly in group.polys { + for end_index in 2..poly.0.len() { + for &index in &[0, end_index - 1, end_index] { + let obj::IndexTuple(position_id, texture_id, normal_id) = poly.0[index]; + let texture_id = texture_id.expect("uvs required"); + let normal_id = normal_id.expect("normals required"); + + let index = *mapping + .entry((position_id, texture_id, normal_id)) + .or_insert(next_index); + if index == next_index { + next_index += 1; + + scene.vertices.push(Vertex { + pos: data.position[position_id], + uv: data.texture[texture_id], + normal: data.normal[normal_id], + ..Default::default() + }) + } + + scene.indices.push(index as u32); + } + } + } + + let mut material: Material = Default::default(); + + if let Some(obj::ObjMaterial::Mtl(mat)) = group.material { + if let Some(kd) = mat.kd { + material.albedo = kd; + } + if let Some(ns) = mat.ns { + material.roughness_exponent = ns; + } + if let Some(ka) = mat.ka { + material.metalness = ka[0]; + } + if let Some(ks) = mat.ks { + material.specularity = ks[0]; + } + } + + scene + .geometries + .push((start_index_index..scene.indices.len(), material)); + } + } + scene.instances.push(( + start_vertex_index..scene.vertices.len(), + start_geometry_index..scene.geometries.len(), + )); + + // dbg!(scene.vertices.len()); + // dbg!(scene.indices.len()); + // dbg!(&scene.geometries); + // dbg!(&scene.instances); +} + +fn upload_scene_components( + device: &wgpu::Device, + queue: &wgpu::Queue, + scene: &RawSceneComponents, +) -> SceneComponents { + let geometry_buffer_content = scene + .geometries + .iter() + .map(|(index_range, material)| GeometryEntry { + first_index: index_range.start as u32, + material: *material, + ..Default::default() + }) + .collect::>(); + + let instance_buffer_content = scene + .instances + .iter() + .map(|geometry| InstanceEntry { + first_vertex: geometry.0.start as u32, + first_geometry: geometry.1.start as u32, + last_geometry: geometry.1.end as u32, + _pad: 1, + }) + .collect::>(); + + let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertices"), + contents: bytemuck::cast_slice(&scene.vertices), + usage: wgpu::BufferUsages::VERTEX + | wgpu::BufferUsages::STORAGE + | wgpu::BufferUsages::BLAS_INPUT, + }); + let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Indices"), + contents: bytemuck::cast_slice(&scene.indices), + usage: wgpu::BufferUsages::INDEX + | wgpu::BufferUsages::STORAGE + | wgpu::BufferUsages::BLAS_INPUT, + }); + let geometries = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Geometries"), + contents: bytemuck::cast_slice(&geometry_buffer_content), + usage: wgpu::BufferUsages::STORAGE, + }); + let instances = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Instances"), + contents: bytemuck::cast_slice(&instance_buffer_content), + usage: wgpu::BufferUsages::STORAGE, + }); + + let (size_descriptors, bottom_level_acceleration_structures): (Vec<_>, Vec<_>) = scene + .instances + .iter() + .map(|(vertex_range, geometry_range)| { + let size_desc: Vec = (*geometry_range) + .clone() + .map(|i| wgpu::BlasTriangleGeometrySizeDescriptor { + vertex_format: wgpu::VertexFormat::Float32x3, + vertex_count: vertex_range.end as u32 - vertex_range.start as u32, + index_format: Some(wgpu::IndexFormat::Uint32), + index_count: Some( + scene.geometries[i].0.end as u32 - scene.geometries[i].0.start as u32, + ), + flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, + }) + .collect(); + + let blas = device.create_blas( + &wgpu::CreateBlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + }, + wgpu::BlasGeometrySizeDescriptors::Triangles { + descriptors: size_desc.clone(), + }, + ); + (size_desc, blas) + }) + .unzip(); + + let build_entries: Vec<_> = scene + .instances + .iter() + .zip(size_descriptors.iter()) + .zip(bottom_level_acceleration_structures.iter()) + .map(|(((vertex_range, geometry_range), size_desc), blas)| { + let triangle_geometries: Vec<_> = size_desc + .iter() + .zip(geometry_range.clone()) + .map(|(size, i)| wgpu::BlasTriangleGeometry { + size, + vertex_buffer: &vertices, + first_vertex: vertex_range.start as u32, + vertex_stride: mem::size_of::() as u64, + index_buffer: Some(&indices), + index_buffer_offset: Some(scene.geometries[i].0.start as u64 * 4), + transform_buffer: None, + transform_buffer_offset: None, + }) + .collect(); + + wgpu::BlasBuildEntry { + blas, + geometry: wgpu::BlasGeometries::TriangleGeometries(triangle_geometries), + } + }) + .collect(); + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures(build_entries.iter(), iter::empty()); + + queue.submit(Some(encoder.finish())); + + SceneComponents { + vertices, + indices, + geometries, + instances, + bottom_level_acceleration_structures, + } +} + +fn load_scene(device: &wgpu::Device, queue: &wgpu::Queue) -> SceneComponents { + let mut scene = RawSceneComponents::default(); + + load_model(&mut scene, "/skybox/models/teslacyberv3.0.obj"); + load_model(&mut scene, "/ray_scene/cube.obj"); + + upload_scene_components(device, queue, &scene) +} + +struct Example { + uniforms: Uniforms, + uniform_buf: wgpu::Buffer, + tlas_package: wgpu::TlasPackage, + pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, + start_inst: Instant, + scene_components: SceneComponents, +} + +impl crate::framework::Example for Example { + fn required_features() -> wgpu::Features { + wgpu::Features::EXPERIMENTAL_RAY_QUERY + | wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + } + + fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { + wgpu::DownlevelCapabilities::default() + } + fn required_limits() -> wgpu::Limits { + wgpu::Limits::default() + } + + fn init( + config: &wgpu::SurfaceConfiguration, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> Self { + let side_count = 8; + + let scene_components = load_scene(device, queue); + + let uniforms = { + let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y); + let proj = Mat4::perspective_rh( + 59.0_f32.to_radians(), + config.width as f32 / config.height as f32, + 0.001, + 1000.0, + ); + + Uniforms { + view_inverse: view.inverse(), + proj_inverse: proj.inverse(), + } + }; + + let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Uniform Buffer"), + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + max_instances: side_count * side_count, + }); + + let tlas_package = wgpu::TlasPackage::new(tlas); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(config.format.into())], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let bind_group_layout = pipeline.get_bind_group_layout(0); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buf.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 5, + resource: tlas_package.as_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: scene_components.vertices.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: scene_components.indices.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: scene_components.geometries.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 4, + resource: scene_components.instances.as_entire_binding(), + }, + ], + }); + + let start_inst = Instant::now(); + + Example { + uniforms, + uniform_buf, + tlas_package, + pipeline, + bind_group, + start_inst, + scene_components, + } + } + + fn update(&mut self, _event: winit::event::WindowEvent) {} + + fn resize( + &mut self, + config: &wgpu::SurfaceConfiguration, + _device: &wgpu::Device, + queue: &wgpu::Queue, + ) { + let proj = Mat4::perspective_rh( + 59.0_f32.to_radians(), + config.width as f32 / config.height as f32, + 0.001, + 1000.0, + ); + + self.uniforms.proj_inverse = proj.inverse(); + + queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(&[self.uniforms])); + } + + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + device.push_error_scope(wgpu::ErrorFilter::Validation); + + // scene update + { + let dist = 3.5; + + let side_count = 2; + + let anim_time = self.start_inst.elapsed().as_secs_f64() as f32; + + for x in 0..side_count { + for y in 0..side_count { + let instance = self.tlas_package.index_mut(x + y * side_count); + + let blas_index = (x + y) + % self + .scene_components + .bottom_level_acceleration_structures + .len(); + + let x = x as f32 / (side_count - 1) as f32; + let y = y as f32 / (side_count - 1) as f32; + let x = x * 2.0 - 1.0; + let y = y * 2.0 - 1.0; + + let transform = Mat4::from_rotation_translation( + Quat::from_euler( + glam::EulerRot::XYZ, + anim_time * 0.5 * 0.342, + anim_time * 0.5 * 0.254, + anim_time * 0.5 * 0.832 + PI, + ), + Vec3 { + x: x * dist, + y: y * dist, + z: -14.0, + }, + ); + let transform = transform.transpose().to_cols_array()[..12] + .try_into() + .unwrap(); + *instance = Some(wgpu::TlasInstance::new( + &self.scene_components.bottom_level_acceleration_structures[blas_index], + transform, + blas_index as u32, + 0xff, + )); + } + } + } + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures(iter::empty(), iter::once(&self.tlas_package)); + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, Some(&self.bind_group), &[]); + rpass.draw(0..3, 0..1); + } + + queue.submit(Some(encoder.finish())); + } +} + +pub fn main() { + crate::framework::run::("ray_scene"); +} + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "ray_scene", + image_path: "/examples/src/ray_scene/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters { + required_features: ::required_features(), + required_limits: ::required_limits(), + force_fxc: false, + skips: vec![], + failures: Vec::new(), + required_downlevel_caps: + ::required_downlevel_capabilities(), + }, + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/src/ray_scene/screenshot.png b/examples/src/ray_scene/screenshot.png new file mode 100644 index 000000000..d238d489d Binary files /dev/null and b/examples/src/ray_scene/screenshot.png differ diff --git a/examples/src/ray_scene/shader.wgsl b/examples/src/ray_scene/shader.wgsl new file mode 100644 index 000000000..f6bd2398c --- /dev/null +++ b/examples/src/ray_scene/shader.wgsl @@ -0,0 +1,164 @@ +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) tex_coords: vec2, +}; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { + var result: VertexOutput; + let x = i32(vertex_index) / 2; + let y = i32(vertex_index) & 1; + let tc = vec2( + f32(x) * 2.0, + f32(y) * 2.0 + ); + result.position = vec4( + tc.x * 2.0 - 1.0, + 1.0 - tc.y * 2.0, + 0.0, 1.0 + ); + result.tex_coords = tc; + return result; +} + +/* +The contents of the RayQuery struct are roughly as follows +let RAY_FLAG_NONE = 0x00u; +let RAY_FLAG_OPAQUE = 0x01u; +let RAY_FLAG_NO_OPAQUE = 0x02u; +let RAY_FLAG_TERMINATE_ON_FIRST_HIT = 0x04u; +let RAY_FLAG_SKIP_CLOSEST_HIT_SHADER = 0x08u; +let RAY_FLAG_CULL_BACK_FACING = 0x10u; +let RAY_FLAG_CULL_FRONT_FACING = 0x20u; +let RAY_FLAG_CULL_OPAQUE = 0x40u; +let RAY_FLAG_CULL_NO_OPAQUE = 0x80u; +let RAY_FLAG_SKIP_TRIANGLES = 0x100u; +let RAY_FLAG_SKIP_AABBS = 0x200u; + +let RAY_QUERY_INTERSECTION_NONE = 0u; +let RAY_QUERY_INTERSECTION_TRIANGLE = 1u; +let RAY_QUERY_INTERSECTION_GENERATED = 2u; +let RAY_QUERY_INTERSECTION_AABB = 4u; + +struct RayDesc { + flags: u32, + cull_mask: u32, + t_min: f32, + t_max: f32, + origin: vec3, + dir: vec3, +} + +struct RayIntersection { + kind: u32, + t: f32, + instance_custom_index: u32, + instance_id: u32, + sbt_record_offset: u32, + geometry_index: u32, + primitive_index: u32, + barycentrics: vec2, + front_face: bool, + object_to_world: mat4x3, + world_to_object: mat4x3, +} +*/ + +struct Uniforms { + view_inv: mat4x4, + proj_inv: mat4x4, +}; + +struct Vertex { + pos: vec3, + normal: vec3, + uv: vec2, +}; + + +struct Instance { + first_vertex: u32, + first_geometry: u32, + last_geometry: u32, + _pad: u32 +}; + +struct Material{ + roughness_exponent: f32, + metalness: f32, + specularity: f32, + albedo: vec3 +} + +struct Geometry { + first_index: u32, + material: Material, +}; + + +@group(0) @binding(0) +var uniforms: Uniforms; + +@group(0) @binding(1) +var vertices: array; + +@group(0) @binding(2) +var indices: array; + +@group(0) @binding(3) +var geometries: array; + +@group(0) @binding(4) +var instances: array; + +@group(0) @binding(5) +var acc_struct: acceleration_structure; + +@fragment +fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { + + var color = vec4(vertex.tex_coords, 0.0, 1.0); + + let d = vertex.tex_coords * 2.0 - 1.0; + + let origin = (uniforms.view_inv * vec4(0.0,0.0,0.0,1.0)).xyz; + let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); + let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; + + var rq: ray_query; + rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); + rayQueryProceed(&rq); + + let intersection = rayQueryGetCommittedIntersection(&rq); + if (intersection.kind != RAY_QUERY_INTERSECTION_NONE) { + let instance = instances[intersection.instance_custom_index]; + let geometry = geometries[intersection.geometry_index + instance.first_geometry]; + + let index_offset = geometry.first_index; + let vertex_offset = instance.first_vertex; + + let first_index_index = intersection.primitive_index * 3u + index_offset; + + let v_0 = vertices[vertex_offset+indices[first_index_index+0u]]; + let v_1 = vertices[vertex_offset+indices[first_index_index+1u]]; + let v_2 = vertices[vertex_offset+indices[first_index_index+2u]]; + + let bary = vec3(1.0 - intersection.barycentrics.x - intersection.barycentrics.y, intersection.barycentrics); + + let pos = v_0.pos * bary.x + v_1.pos * bary.y + v_2.pos * bary.z; + let normal_raw = v_0.normal * bary.x + v_1.normal * bary.y + v_2.normal * bary.z; + let uv = v_0.uv * bary.x + v_1.uv * bary.y + v_2.uv * bary.z; + + let normal = normalize(normal_raw); + + let material = geometry.material; + + color = vec4(material.albedo, 1.0); + + if(intersection.instance_custom_index == 1u){ + color = vec4(normal, 1.0); + } + } + + return color; +} diff --git a/examples/src/ray_shadows/README.md b/examples/src/ray_shadows/README.md new file mode 100644 index 000000000..2b751e8e8 --- /dev/null +++ b/examples/src/ray_shadows/README.md @@ -0,0 +1,13 @@ +# ray-shadows + +This example renders a ray traced shadow with hardware acceleration. + +## To Run + +``` +cargo run --bin wgpu-examples ray_shadows +``` + +## Screenshots + +![Shadow example](screenshot.png) diff --git a/examples/src/ray_shadows/mod.rs b/examples/src/ray_shadows/mod.rs new file mode 100644 index 000000000..adf25cd45 --- /dev/null +++ b/examples/src/ray_shadows/mod.rs @@ -0,0 +1,385 @@ +use std::{borrow::Cow, future::Future, iter, mem, pin::Pin, task, time::Instant}; + +use bytemuck::{Pod, Zeroable}; +use glam::{Mat4, Vec3}; +use wgpu::util::DeviceExt; +use wgpu::{vertex_attr_array, IndexFormat, VertexBufferLayout}; + +// from cube +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Vertex { + _pos: [f32; 3], + _normal: [f32; 3], +} + +fn vertex(pos: [f32; 3], normal: [f32; 3]) -> Vertex { + Vertex { + _pos: pos, + _normal: normal, + } +} + +fn create_vertices() -> (Vec, Vec) { + let vertex_data = [ + // base + vertex([-1.0, 0.0, -1.0], [0.0, 1.0, 0.0]), + vertex([-1.0, 0.0, 1.0], [0.0, 1.0, 0.0]), + vertex([1.0, 0.0, -1.0], [0.0, 1.0, 0.0]), + vertex([1.0, 0.0, 1.0], [0.0, 1.0, 0.0]), + //shadow caster + vertex([-(1.0 / 3.0), 0.0, 1.0], [0.0, 0.0, 1.0]), + vertex([-(1.0 / 3.0), 2.0 / 3.0, 1.0], [0.0, 0.0, 1.0]), + vertex([1.0 / 3.0, 0.0, 1.0], [0.0, 0.0, 1.0]), + vertex([1.0 / 3.0, 2.0 / 3.0, 1.0], [0.0, 0.0, 1.0]), + ]; + + let index_data: &[u16] = &[ + 0, 1, 2, 2, 3, 1, //base + 4, 5, 6, 6, 7, 5, + ]; + + (vertex_data.to_vec(), index_data.to_vec()) +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Uniforms { + view_inverse: Mat4, + proj_inverse: Mat4, + vertex: Mat4, +} + +/// A wrapper for `pop_error_scope` futures that panics if an error occurs. +/// +/// Given a future `inner` of an `Option` for some error type `E`, +/// wait for the future to be ready, and panic if its value is `Some`. +/// +/// This can be done simpler with `FutureExt`, but we don't want to add +/// a dependency just for this small case. +struct ErrorFuture { + inner: F, +} +impl>> Future for ErrorFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<()> { + let inner = unsafe { self.map_unchecked_mut(|me| &mut me.inner) }; + inner.poll(cx).map(|error| { + if let Some(e) = error { + panic!("Rendering {}", e); + } + }) + } +} + +struct Example { + uniforms: Uniforms, + uniform_buf: wgpu::Buffer, + vertex_buf: wgpu::Buffer, + index_buf: wgpu::Buffer, + pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, + start_inst: Instant, +} + +const CAM_LOOK_AT: Vec3 = Vec3::new(0.0, 1.0, -1.5); + +fn create_matrix(config: &wgpu::SurfaceConfiguration) -> Uniforms { + let view = Mat4::look_at_rh(CAM_LOOK_AT, Vec3::ZERO, Vec3::Y); + let proj = Mat4::perspective_rh( + 59.0_f32.to_radians(), + config.width as f32 / config.height as f32, + 0.1, + 1000.0, + ); + + Uniforms { + view_inverse: view.inverse(), + proj_inverse: proj.inverse(), + vertex: (proj * view), + } +} + +impl crate::framework::Example for Example { + fn required_features() -> wgpu::Features { + wgpu::Features::EXPERIMENTAL_RAY_QUERY + | wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + | wgpu::Features::PUSH_CONSTANTS + } + + fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { + wgpu::DownlevelCapabilities::default() + } + fn required_limits() -> wgpu::Limits { + wgpu::Limits { + max_push_constant_size: 12, + ..wgpu::Limits::default() + } + } + + fn init( + config: &wgpu::SurfaceConfiguration, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> Self { + let uniforms = create_matrix(config); + + let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Uniform Buffer"), + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let (vertex_data, index_data) = create_vertices(); + + let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertex_data), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&index_data), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let blas_geo_size_desc = wgpu::BlasTriangleGeometrySizeDescriptor { + vertex_format: wgpu::VertexFormat::Float32x3, + vertex_count: vertex_data.len() as u32, + index_format: Some(wgpu::IndexFormat::Uint16), + index_count: Some(index_data.len() as u32), + flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, + }; + + let blas = device.create_blas( + &wgpu::CreateBlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + }, + wgpu::BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_geo_size_desc.clone()], + }, + ); + + let tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + max_instances: 1, + }); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::AccelerationStructure, + count: None, + }, + ], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[wgpu::PushConstantRange { + stages: wgpu::ShaderStages::FRAGMENT, + range: 0..12, + }], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + buffers: &[VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: Default::default(), + attributes: &vertex_attr_array![0 => Float32x3, 1 => Float32x3], + }], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(config.format.into())], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let mut tlas_package = wgpu::TlasPackage::new(tlas); + + tlas_package[0] = Some(wgpu::TlasInstance::new( + &blas, + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + 0, + 0xFF, + )); + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures( + iter::once(&wgpu::BlasBuildEntry { + blas: &blas, + geometry: wgpu::BlasGeometries::TriangleGeometries(vec![ + wgpu::BlasTriangleGeometry { + size: &blas_geo_size_desc, + vertex_buffer: &vertex_buf, + first_vertex: 0, + vertex_stride: mem::size_of::() as u64, + index_buffer: Some(&index_buf), + index_buffer_offset: Some(0), + transform_buffer: None, + transform_buffer_offset: None, + }, + ]), + }), + iter::once(&tlas_package), + ); + + queue.submit(Some(encoder.finish())); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buf.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: tlas_package.as_binding(), + }, + ], + }); + + let start_inst = Instant::now(); + + Example { + uniforms, + uniform_buf, + vertex_buf, + index_buf, + pipeline, + bind_group, + start_inst, + } + } + + fn update(&mut self, _event: winit::event::WindowEvent) {} + + fn resize( + &mut self, + config: &wgpu::SurfaceConfiguration, + _device: &wgpu::Device, + queue: &wgpu::Queue, + ) { + self.uniforms = create_matrix(config); + + queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(&[self.uniforms])); + queue.submit(None); + } + + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + //device.push_error_scope(wgpu::ErrorFilter::Validation); + const LIGHT_DISTANCE: f32 = 5.0; + const TIME_SCALE: f32 = -0.2; + const INITIAL_TIME: f32 = 1.0; + let cos = (self.start_inst.elapsed().as_secs_f32() * TIME_SCALE + INITIAL_TIME).cos() + * LIGHT_DISTANCE; + let sin = (self.start_inst.elapsed().as_secs_f32() * TIME_SCALE + INITIAL_TIME).sin() + * LIGHT_DISTANCE; + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.1, + b: 0.1, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, Some(&self.bind_group), &[]); + rpass.set_push_constants(wgpu::ShaderStages::FRAGMENT, 0, &0.0_f32.to_ne_bytes()); + rpass.set_push_constants(wgpu::ShaderStages::FRAGMENT, 4, &cos.to_ne_bytes()); + rpass.set_push_constants(wgpu::ShaderStages::FRAGMENT, 8, &sin.to_ne_bytes()); + rpass.set_vertex_buffer(0, self.vertex_buf.slice(..)); + rpass.set_index_buffer(self.index_buf.slice(..), IndexFormat::Uint16); + rpass.draw_indexed(0..12, 0, 0..1); + } + queue.submit(Some(encoder.finish())); + device.poll(wgpu::Maintain::Wait); + } +} + +pub fn main() { + crate::framework::run::("ray-shadows"); +} + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "ray_cube_shadows", + image_path: "/examples/src/ray_shadows/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters { + required_features: ::required_features(), + required_limits: ::required_limits(), + skips: vec![], + failures: Vec::new(), + required_downlevel_caps: + ::required_downlevel_capabilities(), + force_fxc: false, + }, + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/src/ray_shadows/screenshot.png b/examples/src/ray_shadows/screenshot.png new file mode 100644 index 000000000..53e99aa7f Binary files /dev/null and b/examples/src/ray_shadows/screenshot.png differ diff --git a/examples/src/ray_shadows/shader.wgsl b/examples/src/ray_shadows/shader.wgsl new file mode 100644 index 000000000..fb36f1045 --- /dev/null +++ b/examples/src/ray_shadows/shader.wgsl @@ -0,0 +1,70 @@ +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) tex_coords: vec2, + @location(1) normal: vec3, + @location(2) world_position: vec3, +}; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32, @location(0) position: vec3, @location(1) normal: vec3,) -> VertexOutput { + var result: VertexOutput; + let x = i32(vertex_index) / 2; + let y = i32(vertex_index) & 1; + let tc = vec2( + f32(x) * 2.0, + f32(y) * 2.0 + ); + result.tex_coords = tc; + result.position = uniforms.vertex * vec4(position, 1.0); + result.normal = normal; + result.world_position = position; + return result; +} + +struct Uniforms { + view_inv: mat4x4, + proj_inv: mat4x4, + vertex: mat4x4, +}; + +@group(0) @binding(0) +var uniforms: Uniforms; + +@group(0) @binding(1) +var acc_struct: acceleration_structure; + +var light: vec3; + +const SURFACE_BRIGHTNESS = 0.5; + +@fragment +fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { + let camera = (uniforms.view_inv * vec4(0.0,0.0,0.0,1.0)).xyz; + var color = vec4(vertex.tex_coords, 0.0, 1.0); + + let d = vertex.tex_coords * 2.0 - 1.0; + + let origin = vertex.world_position; + let direction = normalize(light - vertex.world_position); + + var normal: vec3; + let dir_cam = normalize(camera - vertex.world_position); + if (dot(dir_cam, vertex.normal) < 0.0) { + normal = -vertex.normal; + } else { + normal = vertex.normal; + } + + var rq: ray_query; + rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.0001, 200.0, origin, direction)); + rayQueryProceed(&rq); + + let intersection = rayQueryGetCommittedIntersection(&rq); + if (intersection.kind != RAY_QUERY_INTERSECTION_NONE) { + color = vec4(vec3(0.1) * SURFACE_BRIGHTNESS, 1.0); + } else { + color = vec4(vec3(max(dot(direction, normal), 0.1)) * SURFACE_BRIGHTNESS, 1.0); + } + + return color; +} diff --git a/examples/src/ray_traced_triangle/README.md b/examples/src/ray_traced_triangle/README.md new file mode 100644 index 000000000..1f9f3435c --- /dev/null +++ b/examples/src/ray_traced_triangle/README.md @@ -0,0 +1,14 @@ +# ray-traced-triangle + +This example renders three triangles with hardware acceleration. +This is the same scene set-up as hal ray-traced triangle + +## To Run + +``` +cargo run --bin wgpu-examples ray_traced_triangle +``` + +## Screenshots + +![Triangle example](screenshot.png) diff --git a/examples/src/ray_traced_triangle/blit.wgsl b/examples/src/ray_traced_triangle/blit.wgsl new file mode 100644 index 000000000..a79e0657d --- /dev/null +++ b/examples/src/ray_traced_triangle/blit.wgsl @@ -0,0 +1,54 @@ +// same as ray_cube_compute/blit.wgsl + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) tex_coords: vec2, +}; + +// meant to be called with 3 vertex indices: 0, 1, 2 +// draws one large triangle over the clip space like this: +// (the asterisks represent the clip space bounds) +//-1,1 1,1 +// --------------------------------- +// | * . +// | * . +// | * . +// | * . +// | * . +// | * . +// |*************** +// | . 1,-1 +// | . +// | . +// | . +// | . +// |. +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { + var result: VertexOutput; + let x = i32(vertex_index) / 2; + let y = i32(vertex_index) & 1; + let tc = vec2( + f32(x) * 2.0, + f32(y) * 2.0 + ); + result.position = vec4( + tc.x * 2.0 - 1.0, + 1.0 - tc.y * 2.0, + 0.0, 1.0 + ); + result.tex_coords = tc; + return result; +} + +@group(0) +@binding(0) +var r_color: texture_2d; +@group(0) +@binding(1) +var r_sampler: sampler; + +@fragment +fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { + return textureSample(r_color, r_sampler, vertex.tex_coords); +} diff --git a/examples/src/ray_traced_triangle/mod.rs b/examples/src/ray_traced_triangle/mod.rs new file mode 100644 index 000000000..d508e6113 --- /dev/null +++ b/examples/src/ray_traced_triangle/mod.rs @@ -0,0 +1,443 @@ +use glam::{Mat4, Vec3}; +use std::mem; +use std::time::Instant; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; +use wgpu::{include_wgsl, BufferUsages, IndexFormat, SamplerDescriptor}; +use wgpu::{ + AccelerationStructureFlags, AccelerationStructureUpdateMode, BlasBuildEntry, BlasGeometries, + BlasGeometrySizeDescriptors, BlasTriangleGeometry, BlasTriangleGeometrySizeDescriptor, + CreateBlasDescriptor, CreateTlasDescriptor, TlasInstance, TlasPackage, +}; + +struct Example { + tlas_package: TlasPackage, + compute_pipeline: wgpu::ComputePipeline, + blit_pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, + blit_bind_group: wgpu::BindGroup, + storage_texture: wgpu::Texture, + start: Instant, +} + +#[repr(C)] +#[derive(bytemuck::Pod, bytemuck::Zeroable, Clone, Copy, Debug)] +struct Uniforms { + view_inverse: Mat4, + proj_inverse: Mat4, +} + +impl crate::framework::Example for Example { + fn required_features() -> wgpu::Features { + wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + | wgpu::Features::EXPERIMENTAL_RAY_QUERY + } + + fn required_limits() -> wgpu::Limits { + wgpu::Limits::default() + } + + fn init( + config: &wgpu::SurfaceConfiguration, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> Self { + let shader = device.create_shader_module(include_wgsl!("shader.wgsl")); + + let blit_shader = device.create_shader_module(include_wgsl!("blit.wgsl")); + + let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bgl for shader.wgsl"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::WriteOnly, + format: wgpu::TextureFormat::Rgba8Unorm, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::AccelerationStructure, + count: None, + }, + ], + }); + + let blit_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bgl for blit.wgsl"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, + ], + }); + + let vertices: [f32; 9] = [1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0, -1.0, 0.0]; + + let indices: [u32; 3] = [0, 1, 2]; + + let vertex_buffer = device.create_buffer_init(&BufferInitDescriptor { + label: Some("vertex buffer"), + contents: bytemuck::cast_slice(&vertices), + usage: BufferUsages::BLAS_INPUT, + }); + + let index_buffer = device.create_buffer_init(&BufferInitDescriptor { + label: Some("vertex buffer"), + contents: bytemuck::cast_slice(&indices), + usage: BufferUsages::BLAS_INPUT, + }); + + let blas_size_desc = BlasTriangleGeometrySizeDescriptor { + vertex_format: wgpu::VertexFormat::Float32x3, + // 3 coordinates per vertex + vertex_count: (vertices.len() / 3) as u32, + index_format: Some(IndexFormat::Uint32), + index_count: Some(indices.len() as u32), + flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, + }; + + let blas = device.create_blas( + &CreateBlasDescriptor { + label: None, + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }, + BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_size_desc.clone()], + }, + ); + + let tlas = device.create_tlas(&CreateTlasDescriptor { + label: None, + max_instances: 3, + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }); + + let mut tlas_package = TlasPackage::new(tlas); + + tlas_package[0] = Some(TlasInstance::new( + &blas, + Mat4::from_translation(Vec3 { + x: 0.0, + y: 0.0, + z: 0.0, + }) + .transpose() + .to_cols_array()[..12] + .try_into() + .unwrap(), + 0, + 0xff, + )); + + tlas_package[1] = Some(TlasInstance::new( + &blas, + Mat4::from_translation(Vec3 { + x: -1.0, + y: -1.0, + z: -2.0, + }) + .transpose() + .to_cols_array()[..12] + .try_into() + .unwrap(), + 0, + 0xff, + )); + + tlas_package[2] = Some(TlasInstance::new( + &blas, + Mat4::from_translation(Vec3 { + x: 1.0, + y: -1.0, + z: -2.0, + }) + .transpose() + .to_cols_array()[..12] + .try_into() + .unwrap(), + 0, + 0xff, + )); + + let uniforms = { + let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y); + let proj = Mat4::perspective_rh(59.0_f32.to_radians(), 1.0, 0.001, 1000.0); + + Uniforms { + view_inverse: view.inverse(), + proj_inverse: proj.inverse(), + } + }; + + let uniform_buffer = device.create_buffer_init(&BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&[uniforms]), + usage: BufferUsages::UNIFORM, + }); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + encoder.build_acceleration_structures( + Some(&BlasBuildEntry { + blas: &blas, + geometry: BlasGeometries::TriangleGeometries(vec![BlasTriangleGeometry { + size: &blas_size_desc, + vertex_buffer: &vertex_buffer, + first_vertex: 0, + vertex_stride: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + // in this case since one triangle gets no compression from an index buffer `index_buffer` and `index_buffer_offset` could be `None`. + index_buffer: Some(&index_buffer), + index_buffer_offset: Some(0), + transform_buffer: None, + transform_buffer_offset: None, + }]), + }), + Some(&tlas_package), + ); + + queue.submit(Some(encoder.finish())); + + let storage_tex = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + + let sampler = device.create_sampler(&SamplerDescriptor { + label: None, + address_mode_u: Default::default(), + address_mode_v: Default::default(), + address_mode_w: Default::default(), + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + lod_min_clamp: 1.0, + lod_max_clamp: 1.0, + compare: None, + anisotropy_clamp: 1, + border_color: None, + }); + + let compute_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("pipeline layout for shader.wgsl"), + bind_group_layouts: &[&bgl], + push_constant_ranges: &[], + }); + + let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("pipeline for shader.wgsl"), + layout: Some(&compute_pipeline_layout), + module: &shader, + entry_point: None, + compilation_options: Default::default(), + cache: None, + }); + + let blit_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("pipeline layout for blit.wgsl"), + bind_group_layouts: &[&blit_bgl], + push_constant_ranges: &[], + }); + + let blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("pipeline for blit.wgsl"), + layout: Some(&blit_pipeline_layout), + vertex: wgpu::VertexState { + module: &blit_shader, + entry_point: None, + compilation_options: Default::default(), + buffers: &[], + }, + primitive: Default::default(), + depth_stencil: None, + multisample: Default::default(), + fragment: Some(wgpu::FragmentState { + module: &blit_shader, + entry_point: None, + compilation_options: Default::default(), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: None, + write_mask: Default::default(), + })], + }), + multiview: None, + cache: None, + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bind group for shader.wgsl"), + layout: &bgl, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView( + &storage_tex.create_view(&wgpu::TextureViewDescriptor::default()), + ), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::AccelerationStructure(tlas_package.tlas()), + }, + ], + }); + + let blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bind group for blit.wgsl"), + layout: &blit_bgl, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &storage_tex.create_view(&wgpu::TextureViewDescriptor::default()), + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + Self { + tlas_package, + compute_pipeline, + blit_pipeline, + bind_group, + blit_bind_group, + storage_texture: storage_tex, + start: Instant::now(), + } + } + + fn resize( + &mut self, + _config: &wgpu::SurfaceConfiguration, + _device: &wgpu::Device, + _queue: &wgpu::Queue, + ) { + } + + fn update(&mut self, _event: winit::event::WindowEvent) {} + + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + self.tlas_package[0].as_mut().unwrap().transform = + Mat4::from_rotation_y(self.start.elapsed().as_secs_f32()) + .transpose() + .to_cols_array()[..12] + .try_into() + .unwrap(); + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures(None, Some(&self.tlas_package)); + + { + let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + cpass.set_pipeline(&self.compute_pipeline); + cpass.set_bind_group(0, Some(&self.bind_group), &[]); + cpass.dispatch_workgroups( + self.storage_texture.width() / 8, + self.storage_texture.height() / 8, + 1, + ); + } + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&self.blit_pipeline); + rpass.set_bind_group(0, Some(&self.blit_bind_group), &[]); + rpass.draw(0..3, 0..1); + } + + queue.submit(Some(encoder.finish())); + } +} + +pub fn main() { + crate::framework::run::("ray-traced-triangle"); +} + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "ray_traced_triangle", + image_path: "/examples/src/ray_traced_triangle/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters { + required_features: ::required_features(), + required_limits: ::required_limits(), + force_fxc: false, + skips: vec![], + failures: Vec::new(), + required_downlevel_caps: + ::required_downlevel_capabilities(), + }, + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/src/ray_traced_triangle/screenshot.png b/examples/src/ray_traced_triangle/screenshot.png new file mode 100644 index 000000000..888f674b8 Binary files /dev/null and b/examples/src/ray_traced_triangle/screenshot.png differ diff --git a/examples/src/ray_traced_triangle/shader.wgsl b/examples/src/ray_traced_triangle/shader.wgsl new file mode 100644 index 000000000..54b1eb542 --- /dev/null +++ b/examples/src/ray_traced_triangle/shader.wgsl @@ -0,0 +1,39 @@ +// duplicate of hal's ray-traced triangle shader + +struct Uniforms { + view_inv: mat4x4, + proj_inv: mat4x4, +}; +@group(0) @binding(0) +var uniforms: Uniforms; + +@group(0) @binding(1) +var output: texture_storage_2d; + +@group(0) @binding(2) +var acc_struct: acceleration_structure; + +@compute @workgroup_size(8, 8) +fn main(@builtin(global_invocation_id) global_id: vec3) { + let target_size = textureDimensions(output); + + let pixel_center = vec2(global_id.xy) + vec2(0.5); + let in_uv = pixel_center / vec2(target_size.xy); + let d = in_uv * 2.0 - 1.0; + + let origin = (uniforms.view_inv * vec4(0.0, 0.0, 0.0, 1.0)).xyz; + let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); + let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; + + var rq: ray_query; + rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); + rayQueryProceed(&rq); + + var color = vec4(0.0, 0.0, 0.0, 1.0); + let intersection = rayQueryGetCommittedIntersection(&rq); + if intersection.kind != RAY_QUERY_INTERSECTION_NONE { + color = vec4(intersection.barycentrics, 1.0 - intersection.barycentrics.x - intersection.barycentrics.y, 1.0); + } + + textureStore(output, global_id.xy, color); +} \ No newline at end of file diff --git a/naga/Cargo.toml b/naga/Cargo.toml index 16682e526..0d54c964c 100644 --- a/naga/Cargo.toml +++ b/naga/Cargo.toml @@ -77,7 +77,7 @@ indexmap.workspace = true log = "0.4" spirv = { version = "0.3", optional = true } thiserror.workspace = true -serde = { version = "1.0.213", features = ["derive"], optional = true } +serde = { version = "1.0.214", features = ["derive"], optional = true } petgraph = { version = "0.6", optional = true } pp-rs = { version = "0.2.1", optional = true } hexf-parse = { version = "0.2.1", optional = true } diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index de4a31b74..d13abb199 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -1332,7 +1332,8 @@ impl<'a, W: Write> Writer<'a, W> { crate::MathFunction::Pack4xI8 | crate::MathFunction::Pack4xU8 | crate::MathFunction::Unpack4xI8 - | crate::MathFunction::Unpack4xU8 => { + | crate::MathFunction::Unpack4xU8 + | crate::MathFunction::QuantizeToF16 => { self.need_bake_expressions.insert(arg); } crate::MathFunction::ExtractBits => { @@ -3095,7 +3096,7 @@ impl<'a, W: Write> Writer<'a, W> { self.write_expr(image, ctx)?; // All textureSize calls requires an lod argument // except for multisampled samplers - if class.is_multisampled() { + if !class.is_multisampled() { write!(self.out, ", 0")?; } write!(self.out, ")")?; @@ -3495,6 +3496,48 @@ impl<'a, W: Write> Writer<'a, W> { Mf::Inverse => "inverse", Mf::Transpose => "transpose", Mf::Determinant => "determinant", + Mf::QuantizeToF16 => match *ctx.resolve_type(arg, &self.module.types) { + crate::TypeInner::Scalar { .. } => { + write!(self.out, "unpackHalf2x16(packHalf2x16(vec2(")?; + self.write_expr(arg, ctx)?; + write!(self.out, "))).x")?; + return Ok(()); + } + crate::TypeInner::Vector { + size: crate::VectorSize::Bi, + .. + } => { + write!(self.out, "unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, "))")?; + return Ok(()); + } + crate::TypeInner::Vector { + size: crate::VectorSize::Tri, + .. + } => { + write!(self.out, "vec3(unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ".zz)).x)")?; + return Ok(()); + } + crate::TypeInner::Vector { + size: crate::VectorSize::Quad, + .. + } => { + write!(self.out, "vec4(unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ".zw)))")?; + return Ok(()); + } + _ => unreachable!( + "Correct TypeInner for QuantizeToF16 should be already validated" + ), + }, // bits Mf::CountTrailingZeros => { match *ctx.resolve_type(arg, &self.module.types) { diff --git a/naga/src/back/hlsl/writer.rs b/naga/src/back/hlsl/writer.rs index 3f2755878..236a7bc79 100644 --- a/naga/src/back/hlsl/writer.rs +++ b/naga/src/back/hlsl/writer.rs @@ -3036,6 +3036,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { Unpack4x8unorm, Unpack4xI8, Unpack4xU8, + QuantizeToF16, Regular(&'static str), MissingIntOverload(&'static str), MissingIntReturnType(&'static str), @@ -3102,6 +3103,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { //Mf::Inverse =>, Mf::Transpose => Function::Regular("transpose"), Mf::Determinant => Function::Regular("determinant"), + Mf::QuantizeToF16 => Function::QuantizeToF16, // bits Mf::CountTrailingZeros => Function::CountTrailingZeros, Mf::CountLeadingZeros => Function::CountLeadingZeros, @@ -3303,6 +3305,11 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 24) << 24 >> 24")?; } + Function::QuantizeToF16 => { + write!(self.out, "f16tof32(f32tof16(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "))")?; + } Function::Regular(fun_name) => { write!(self.out, "{fun_name}(")?; self.write_expr(module, arg, func_ctx)?; diff --git a/naga/src/back/msl/writer.rs b/naga/src/back/msl/writer.rs index 83d937d48..282e28fe1 100644 --- a/naga/src/back/msl/writer.rs +++ b/naga/src/back/msl/writer.rs @@ -1936,6 +1936,7 @@ impl Writer { Mf::Inverse => return Err(Error::UnsupportedCall(format!("{fun:?}"))), Mf::Transpose => "transpose", Mf::Determinant => "determinant", + Mf::QuantizeToF16 => "", // bits Mf::CountTrailingZeros => "ctz", Mf::CountLeadingZeros => "clz", @@ -2144,6 +2145,22 @@ impl Writer { self.put_expression(arg, context, true)?; write!(self.out, " >> 24) << 24 >> 24")?; } + Mf::QuantizeToF16 => { + match *context.resolve_type(arg) { + crate::TypeInner::Scalar { .. } => write!(self.out, "float(half(")?, + crate::TypeInner::Vector { size, .. } => write!( + self.out, + "{NAMESPACE}::float{size}({NAMESPACE}::half{size}(", + size = back::vector_size_str(size), + )?, + _ => unreachable!( + "Correct TypeInner for QuantizeToF16 should be already validated" + ), + }; + + self.put_expression(arg, context, true)?; + write!(self.out, "))")?; + } _ => { write!(self.out, "{NAMESPACE}::{fun_name}")?; self.put_call_parameters( diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 7543ea762..f2d7bc2d0 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -1032,6 +1032,12 @@ impl<'w> BlockContext<'w> { arg0_id, )), Mf::Determinant => MathOp::Ext(spirv::GLOp::Determinant), + Mf::QuantizeToF16 => MathOp::Custom(Instruction::unary( + spirv::Op::QuantizeToF16, + result_type_id, + id, + arg0_id, + )), Mf::ReverseBits => MathOp::Custom(Instruction::unary( spirv::Op::BitReverse, result_type_id, diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index 171102f24..6e7ac0bf5 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -1723,6 +1723,7 @@ impl Writer { Mf::InverseSqrt => Function::Regular("inverseSqrt"), Mf::Transpose => Function::Regular("transpose"), Mf::Determinant => Function::Regular("determinant"), + Mf::QuantizeToF16 => Function::Regular("quantizeToF16"), // bits Mf::CountTrailingZeros => Function::Regular("countTrailingZeros"), Mf::CountLeadingZeros => Function::Regular("countLeadingZeros"), diff --git a/naga/src/diagnostic_filter.rs b/naga/src/diagnostic_filter.rs index e2683b8fa..602953ca4 100644 --- a/naga/src/diagnostic_filter.rs +++ b/naga/src/diagnostic_filter.rs @@ -1,9 +1,24 @@ //! [`DiagnosticFilter`]s and supporting functionality. +#[cfg(feature = "wgsl-in")] +use crate::Span; +use crate::{Arena, Handle}; +#[cfg(feature = "arbitrary")] +use arbitrary::Arbitrary; +#[cfg(feature = "wgsl-in")] +use indexmap::IndexMap; +#[cfg(feature = "deserialize")] +use serde::Deserialize; +#[cfg(feature = "serialize")] +use serde::Serialize; + /// A severity set on a [`DiagnosticFilter`]. /// /// #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum Severity { Off, Info, @@ -33,7 +48,6 @@ impl Severity { /// Naga does not yet support diagnostic items at lesser severities than /// [`Severity::Error`]. When this is implemented, this method should be deleted, and the /// severity should be used directly for reporting diagnostics. - #[cfg(feature = "wgsl-in")] pub(crate) fn report_diag( self, err: E, @@ -57,6 +71,9 @@ impl Severity { /// /// #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum FilterableTriggeringRule { DerivativeUniformity, } @@ -79,10 +96,13 @@ impl FilterableTriggeringRule { } } - #[cfg(feature = "wgsl-in")] - pub(crate) const fn tracking_issue_num(self) -> u16 { + /// The default severity associated with this triggering rule. + /// + /// See for a table of default + /// severities. + pub(crate) const fn default_severity(self) -> Severity { match self { - FilterableTriggeringRule::DerivativeUniformity => 5320, + FilterableTriggeringRule::DerivativeUniformity => Severity::Error, } } } @@ -91,7 +111,140 @@ impl FilterableTriggeringRule { /// /// #[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct DiagnosticFilter { pub new_severity: Severity, pub triggering_rule: FilterableTriggeringRule, } + +/// A map of diagnostic filters to their severity and first occurrence's span. +/// +/// Intended for front ends' first step into storing parsed [`DiagnosticFilter`]s. +#[derive(Clone, Debug, Default)] +#[cfg(feature = "wgsl-in")] +pub(crate) struct DiagnosticFilterMap(IndexMap); + +#[cfg(feature = "wgsl-in")] +impl DiagnosticFilterMap { + pub(crate) fn new() -> Self { + Self::default() + } + + /// Add the given `diagnostic_filter` parsed at the given `span` to this map. + pub(crate) fn add( + &mut self, + diagnostic_filter: DiagnosticFilter, + span: Span, + ) -> Result<(), ConflictingDiagnosticRuleError> { + use indexmap::map::Entry; + + let &mut Self(ref mut diagnostic_filters) = self; + let DiagnosticFilter { + new_severity, + triggering_rule, + } = diagnostic_filter; + + match diagnostic_filters.entry(triggering_rule) { + Entry::Vacant(entry) => { + entry.insert((new_severity, span)); + } + Entry::Occupied(entry) => { + let &(first_severity, first_span) = entry.get(); + if first_severity != new_severity { + return Err(ConflictingDiagnosticRuleError { + triggering_rule, + triggering_rule_spans: [first_span, span], + }); + } + } + } + Ok(()) + } +} + +#[cfg(feature = "wgsl-in")] +impl IntoIterator for DiagnosticFilterMap { + type Item = (FilterableTriggeringRule, (Severity, Span)); + + type IntoIter = indexmap::map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let Self(this) = self; + this.into_iter() + } +} + +/// An error returned by [`DiagnosticFilterMap::add`] when it encounters conflicting rules. +#[cfg(feature = "wgsl-in")] +#[derive(Clone, Debug)] +pub(crate) struct ConflictingDiagnosticRuleError { + pub triggering_rule: FilterableTriggeringRule, + pub triggering_rule_spans: [Span; 2], +} + +/// Represents a single parent-linking node in a tree of [`DiagnosticFilter`]s backed by a +/// [`crate::Arena`]. +/// +/// A single element of a _tree_ of diagnostic filter rules stored in +/// [`crate::Module::diagnostic_filters`]. When nodes are built by a front-end, module-applicable +/// filter rules are chained together in runs based on parse site. For instance, given the +/// following: +/// +/// - Module-applicable rules `a` and `b`. +/// - Rules `c` and `d`, applicable to an entry point called `c_and_d_func`. +/// - Rule `e`, applicable to an entry point called `e_func`. +/// +/// The tree would be represented as follows: +/// +/// ```text +/// a <- b +/// ^ +/// |- c <- d +/// | +/// \- e +/// ``` +/// +/// ...where: +/// +/// - `d` is the first leaf consulted by validation in `c_and_d_func`. +/// - `e` is the first leaf consulted by validation in `e_func`. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct DiagnosticFilterNode { + pub inner: DiagnosticFilter, + pub parent: Option>, +} + +impl DiagnosticFilterNode { + /// Finds the most specific filter rule applicable to `triggering_rule` from the chain of + /// diagnostic filter rules in `arena`, starting with `node`, and returns its severity. If none + /// is found, return the value of [`FilterableTriggeringRule::default_severity`]. + /// + /// When `triggering_rule` is not applicable to this node, its parent is consulted recursively. + pub(crate) fn search( + node: Option>, + arena: &Arena, + triggering_rule: FilterableTriggeringRule, + ) -> Severity { + let mut next = node; + while let Some(handle) = next { + let node = &arena[handle]; + let &Self { ref inner, parent } = node; + let &DiagnosticFilter { + triggering_rule: rule, + new_severity, + } = inner; + + if rule == triggering_rule { + return new_severity; + } + + next = parent; + } + triggering_rule.default_severity() + } +} diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index f5e633208..c3f104b2b 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -1,4 +1,4 @@ -use crate::diagnostic_filter::FilterableTriggeringRule; +use crate::diagnostic_filter::ConflictingDiagnosticRuleError; use crate::front::wgsl::parse::directive::enable_extension::{ EnableExtension, UnimplementedEnableExtension, }; @@ -295,10 +295,13 @@ pub(crate) enum Error<'a> { DiagnosticInvalidSeverity { severity_control_name_span: Span, }, - DiagnosticNotYetImplemented { - triggering_rule: FilterableTriggeringRule, - span: Span, - }, + DiagnosticDuplicateTriggeringRule(ConflictingDiagnosticRuleError), +} + +impl<'a> From for Error<'a> { + fn from(value: ConflictingDiagnosticRuleError) -> Self { + Self::DiagnosticDuplicateTriggeringRule(value) + } } #[derive(Clone, Debug)] @@ -1017,24 +1020,29 @@ impl<'a> Error<'a> { ) .into()], }, - Error::DiagnosticNotYetImplemented { + Error::DiagnosticDuplicateTriggeringRule(ConflictingDiagnosticRuleError { triggering_rule, - span, - } => ParseError { - message: format!( - "the `{}` diagnostic filter is not yet supported", - triggering_rule.to_ident() - ), - labels: vec![(span, "".into())], - notes: vec![format!( - concat!( - "Let Naga maintainers know that you ran into this at ", - ", ", - "so they can prioritize it!" + triggering_rule_spans, + }) => { + let [first_span, second_span] = triggering_rule_spans; + ParseError { + message: format!( + "found conflicting `diagnostic(…)` rule(s) for `{}`", + triggering_rule.to_ident() ), - triggering_rule.tracking_issue_num() - )], - }, + labels: vec![ + (first_span, "first rule".into()), + (second_span, "second rule".into()), + ], + notes: vec![concat!( + "multiple `diagnostic(…)` rules with the same rule name ", + "conflict unless the severity is the same; ", + "delete the rule you don't want, or ", + "ensure that all severities with the same rule name match" + ) + .into()], + } + } } } } diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index 78e81350b..d413e883e 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -1013,7 +1013,11 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { &mut self, tu: &'temp ast::TranslationUnit<'source>, ) -> Result> { - let mut module = crate::Module::default(); + let mut module = crate::Module { + diagnostic_filters: tu.diagnostic_filters.clone(), + diagnostic_filter_leaf: tu.diagnostic_filter_leaf, + ..Default::default() + }; let mut ctx = GlobalContext { ast_expressions: &tu.expressions, @@ -1244,7 +1248,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { .arguments .iter() .enumerate() - .map(|(i, arg)| { + .map(|(i, arg)| -> Result<_, Error<'_>> { let ty = self.resolve_ast_type(arg.ty, ctx)?; let expr = expressions .append(crate::Expression::FunctionArgument(i as u32), arg.name.span); @@ -1263,7 +1267,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { let result = f .result .as_ref() - .map(|res| { + .map(|res| -> Result<_, Error<'_>> { let ty = self.resolve_ast_type(res.ty, ctx)?; Ok(crate::FunctionResult { ty, diff --git a/naga/src/front/wgsl/parse/ast.rs b/naga/src/front/wgsl/parse/ast.rs index 81d8d90bb..938510893 100644 --- a/naga/src/front/wgsl/parse/ast.rs +++ b/naga/src/front/wgsl/parse/ast.rs @@ -1,3 +1,4 @@ +use crate::diagnostic_filter::DiagnosticFilterNode; use crate::front::wgsl::parse::directive::enable_extension::EnableExtensions; use crate::front::wgsl::parse::number::Number; use crate::front::wgsl::Scalar; @@ -26,6 +27,17 @@ pub struct TranslationUnit<'a> { /// These are referred to by `Handle>` values. /// User-defined types are referred to by name until lowering. pub types: Arena>, + + /// Arena for all diagnostic filter rules parsed in this module, including those in functions. + /// + /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in + /// validation. + pub diagnostic_filters: Arena, + /// The leaf of all `diagnostic(…)` directives in this module. + /// + /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in + /// validation. + pub diagnostic_filter_leaf: Option>, } #[derive(Debug, Clone, Copy)] diff --git a/naga/src/front/wgsl/parse/conv.rs b/naga/src/front/wgsl/parse/conv.rs index 3ba71b07c..0c9341eb6 100644 --- a/naga/src/front/wgsl/parse/conv.rs +++ b/naga/src/front/wgsl/parse/conv.rs @@ -230,6 +230,7 @@ pub fn map_standard_fun(word: &str) -> Option { "inverseSqrt" => Mf::InverseSqrt, "transpose" => Mf::Transpose, "determinant" => Mf::Determinant, + "quantizeToF16" => Mf::QuantizeToF16, // bits "countTrailingZeros" => Mf::CountTrailingZeros, "countLeadingZeros" => Mf::CountLeadingZeros, diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index 4e674062b..06c501495 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -1,4 +1,6 @@ -use crate::diagnostic_filter::{self, DiagnosticFilter, FilterableTriggeringRule}; +use crate::diagnostic_filter::{ + self, DiagnosticFilter, DiagnosticFilterMap, DiagnosticFilterNode, FilterableTriggeringRule, +}; use crate::front::wgsl::error::{Error, ExpectedToken}; use crate::front::wgsl::parse::directive::enable_extension::{ EnableExtension, EnableExtensions, UnimplementedEnableExtension, @@ -1907,10 +1909,8 @@ impl Parser { let _ = lexer.next(); let mut body = ast::Block::default(); - let (condition, span) = lexer.capture_span(|lexer| { - let condition = self.general_expression(lexer, ctx)?; - Ok(condition) - })?; + let (condition, span) = + lexer.capture_span(|lexer| self.general_expression(lexer, ctx))?; let mut reject = ast::Block::default(); reject.stmts.push(ast::Statement { kind: ast::StatementKind::Break, @@ -1966,11 +1966,12 @@ impl Parser { let mut body = ast::Block::default(); if !lexer.skip(Token::Separator(';')) { - let (condition, span) = lexer.capture_span(|lexer| { - let condition = self.general_expression(lexer, ctx)?; - lexer.expect(Token::Separator(';'))?; - Ok(condition) - })?; + let (condition, span) = + lexer.capture_span(|lexer| -> Result<_, Error<'_>> { + let condition = self.general_expression(lexer, ctx)?; + lexer.expect(Token::Separator(';'))?; + Ok(condition) + })?; let mut reject = ast::Block::default(); reject.stmts.push(ast::Statement { kind: ast::StatementKind::Break, @@ -2523,6 +2524,7 @@ impl Parser { let mut lexer = Lexer::new(source); let mut tu = ast::TranslationUnit::default(); let mut enable_extensions = EnableExtensions::empty(); + let mut diagnostic_filters = DiagnosticFilterMap::new(); // Parse directives. while let Ok((ident, _directive_ident_span)) = lexer.peek_ident_with_span() { @@ -2532,12 +2534,8 @@ impl Parser { match kind { DirectiveKind::Diagnostic => { if let Some(diagnostic_filter) = self.diagnostic_filter(&mut lexer)? { - let triggering_rule = diagnostic_filter.triggering_rule; let span = self.peek_rule_span(&lexer); - Err(Error::DiagnosticNotYetImplemented { - triggering_rule, - span, - })?; + diagnostic_filters.add(diagnostic_filter, span)?; } lexer.expect(Token::Separator(';'))?; } @@ -2583,6 +2581,8 @@ impl Parser { lexer.enable_extensions = enable_extensions.clone(); tu.enable_extensions = enable_extensions; + tu.diagnostic_filter_leaf = + Self::write_diagnostic_filters(&mut tu.diagnostic_filters, diagnostic_filters, None); loop { match self.global_decl(&mut lexer, &mut tu) { @@ -2674,4 +2674,25 @@ impl Parser { Ok(filter) } + + pub(crate) fn write_diagnostic_filters( + arena: &mut Arena, + filters: DiagnosticFilterMap, + parent: Option>, + ) -> Option> { + filters + .into_iter() + .fold(parent, |parent, (triggering_rule, (new_severity, span))| { + Some(arena.append( + DiagnosticFilterNode { + inner: DiagnosticFilter { + new_severity, + triggering_rule, + }, + parent, + }, + span, + )) + }) + } } diff --git a/naga/src/lib.rs b/naga/src/lib.rs index ba8678c7a..7a7267f41 100644 --- a/naga/src/lib.rs +++ b/naga/src/lib.rs @@ -269,6 +269,7 @@ pub use crate::arena::{Arena, Handle, Range, UniqueArena}; pub use crate::span::{SourceLocation, Span, SpanContext, WithSpan}; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; +use diagnostic_filter::DiagnosticFilterNode; #[cfg(feature = "deserialize")] use serde::Deserialize; #[cfg(feature = "serialize")] @@ -1199,6 +1200,7 @@ pub enum MathFunction { Inverse, Transpose, Determinant, + QuantizeToF16, // bits CountTrailingZeros, CountLeadingZeros, @@ -2286,4 +2288,17 @@ pub struct Module { pub functions: Arena, /// Entry points. pub entry_points: Vec, + /// Arena for all diagnostic filter rules parsed in this module, including those in functions + /// and statements. + /// + /// This arena contains elements of a _tree_ of diagnostic filter rules. When nodes are built + /// by a front-end, they refer to a parent scope + pub diagnostic_filters: Arena, + /// The leaf of all diagnostic filter rules tree parsed from directives in this module. + /// + /// In WGSL, this corresponds to `diagnostic(…);` directives. + /// + /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in + /// validation. + pub diagnostic_filter_leaf: Option>, } diff --git a/naga/src/proc/constant_evaluator.rs b/naga/src/proc/constant_evaluator.rs index 5fdf48151..c12983c0c 100644 --- a/naga/src/proc/constant_evaluator.rs +++ b/naga/src/proc/constant_evaluator.rs @@ -137,8 +137,8 @@ macro_rules! gen_component_wise_extractor { for idx in 0..(size as u8).into() { let group = component_groups .iter() - .map(|cs| cs[idx]) - .collect::>() + .map(|cs| cs.get(idx).cloned().ok_or(err.clone())) + .collect::, _>>()? .into_inner() .unwrap(); new_components.push($ident( diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index 5661e13d5..b6f7a55ef 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -478,6 +478,7 @@ impl super::MathFunction { Self::Inverse => 1, Self::Transpose => 1, Self::Determinant => 1, + Self::QuantizeToF16 => 1, // bits Self::CountTrailingZeros => 1, Self::CountLeadingZeros => 1, diff --git a/naga/src/proc/typifier.rs b/naga/src/proc/typifier.rs index a94546fbc..1e1a4c93a 100644 --- a/naga/src/proc/typifier.rs +++ b/naga/src/proc/typifier.rs @@ -665,7 +665,8 @@ impl<'a> ResolveContext<'a> { | Mf::Exp2 | Mf::Log | Mf::Log2 - | Mf::Pow => res_arg.clone(), + | Mf::Pow + | Mf::QuantizeToF16 => res_arg.clone(), Mf::Modf | Mf::Frexp => { let (size, width) = match res_arg.inner_with(types) { &Ti::Scalar(crate::Scalar { diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs index af95fd098..e169ed1d0 100644 --- a/naga/src/valid/analyzer.rs +++ b/naga/src/valid/analyzer.rs @@ -6,6 +6,7 @@ //! - expression reference counts use super::{ExpressionError, FunctionError, ModuleInfo, ShaderStages, ValidationFlags}; +use crate::diagnostic_filter::{DiagnosticFilterNode, FilterableTriggeringRule}; use crate::span::{AddSpan as _, WithSpan}; use crate::{ arena::{Arena, Handle}, @@ -15,10 +16,6 @@ use std::ops; pub type NonUniformResult = Option>; -// Remove this once we update our uniformity analysis and -// add support for the `derivative_uniformity` diagnostic -const DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE: bool = true; - bitflags::bitflags! { /// Kinds of expressions that require uniform control flow. #[cfg_attr(feature = "serialize", derive(serde::Serialize))] @@ -26,8 +23,8 @@ bitflags::bitflags! { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct UniformityRequirements: u8 { const WORK_GROUP_BARRIER = 0x1; - const DERIVATIVE = if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE { 0 } else { 0x2 }; - const IMPLICIT_LEVEL = if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE { 0 } else { 0x4 }; + const DERIVATIVE = 0x2; + const IMPLICIT_LEVEL = 0x4; } } @@ -289,6 +286,13 @@ pub struct FunctionInfo { /// Indicates that the function is using dual source blending. pub dual_source_blending: bool, + + /// The leaf of all module-wide diagnostic filter rules tree parsed from directives in this + /// module. + /// + /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in + /// validation. + diagnostic_filter_leaf: Option>, } impl FunctionInfo { @@ -421,7 +425,10 @@ impl FunctionInfo { let image_storage = match sampling.image { GlobalOrArgument::Global(var) => GlobalOrArgument::Global(var), GlobalOrArgument::Argument(i) => { - let handle = arguments[i as usize]; + let Some(handle) = arguments.get(i as usize).cloned() else { + // Argument count mismatch, will be reported later by validate_call + break; + }; GlobalOrArgument::from_expression(expression_arena, handle).map_err( |source| { FunctionError::Expression { handle, source } @@ -434,7 +441,10 @@ impl FunctionInfo { let sampler_storage = match sampling.sampler { GlobalOrArgument::Global(var) => GlobalOrArgument::Global(var), GlobalOrArgument::Argument(i) => { - let handle = arguments[i as usize]; + let Some(handle) = arguments.get(i as usize).cloned() else { + // Argument count mismatch, will be reported later by validate_call + break; + }; GlobalOrArgument::from_expression(expression_arena, handle).map_err( |source| { FunctionError::Expression { handle, source } @@ -820,6 +830,7 @@ impl FunctionInfo { other_functions: &[FunctionInfo], mut disruptor: Option, expression_arena: &Arena, + diagnostic_filter_arena: &Arena, ) -> Result> { use crate::Statement as S; @@ -836,8 +847,21 @@ impl FunctionInfo { && !req.is_empty() { if let Some(cause) = disruptor { - return Err(FunctionError::NonUniformControlFlow(req, expr, cause) - .with_span_handle(expr, expression_arena)); + let severity = DiagnosticFilterNode::search( + self.diagnostic_filter_leaf, + diagnostic_filter_arena, + FilterableTriggeringRule::DerivativeUniformity, + ); + severity.report_diag( + FunctionError::NonUniformControlFlow(req, expr, cause) + .with_span_handle(expr, expression_arena), + // TODO: Yes, this isn't contextualized with source, because + // the user is supposed to render what would normally be an + // error here. Once we actually support warning-level + // diagnostic items, then we won't need this non-compliant hack: + // + |e, level| log::log!(level, "{e}"), + )?; } } requirements |= req; @@ -895,9 +919,13 @@ impl FunctionInfo { exit: ExitFlags::empty(), } } - S::Block(ref b) => { - self.process_block(b, other_functions, disruptor, expression_arena)? - } + S::Block(ref b) => self.process_block( + b, + other_functions, + disruptor, + expression_arena, + diagnostic_filter_arena, + )?, S::If { condition, ref accept, @@ -911,12 +939,14 @@ impl FunctionInfo { other_functions, branch_disruptor, expression_arena, + diagnostic_filter_arena, )?; let reject_uniformity = self.process_block( reject, other_functions, branch_disruptor, expression_arena, + diagnostic_filter_arena, )?; accept_uniformity | reject_uniformity } @@ -935,6 +965,7 @@ impl FunctionInfo { other_functions, case_disruptor, expression_arena, + diagnostic_filter_arena, )?; case_disruptor = if case.fall_through { case_disruptor.or(case_uniformity.exit_disruptor()) @@ -950,14 +981,20 @@ impl FunctionInfo { ref continuing, break_if, } => { - let body_uniformity = - self.process_block(body, other_functions, disruptor, expression_arena)?; + let body_uniformity = self.process_block( + body, + other_functions, + disruptor, + expression_arena, + diagnostic_filter_arena, + )?; let continuing_disruptor = disruptor.or(body_uniformity.exit_disruptor()); let continuing_uniformity = self.process_block( continuing, other_functions, continuing_disruptor, expression_arena, + diagnostic_filter_arena, )?; if let Some(expr) = break_if { let _ = self.add_ref(expr); @@ -1111,6 +1148,7 @@ impl ModuleInfo { expressions: vec![ExpressionInfo::new(); fun.expressions.len()].into_boxed_slice(), sampling: crate::FastHashSet::default(), dual_source_blending: false, + diagnostic_filter_leaf: module.diagnostic_filter_leaf, }; let resolve_context = ResolveContext::with_locals(module, &fun.local_variables, &fun.arguments); @@ -1134,7 +1172,13 @@ impl ModuleInfo { } } - let uniformity = info.process_block(&fun.body, &self.functions, None, &fun.expressions)?; + let uniformity = info.process_block( + &fun.body, + &self.functions, + None, + &fun.expressions, + &module.diagnostic_filters, + )?; info.uniformity = uniformity.result; info.may_kill = uniformity.exit.contains(ExitFlags::MAY_KILL); @@ -1224,6 +1268,7 @@ fn uniform_control_flow() { expressions: vec![ExpressionInfo::new(); expressions.len()].into_boxed_slice(), sampling: crate::FastHashSet::default(), dual_source_blending: false, + diagnostic_filter_leaf: None, }; let resolve_context = ResolveContext { constants: &Arena::new(), @@ -1270,7 +1315,8 @@ fn uniform_control_flow() { &vec![stmt_emit1, stmt_if_uniform].into(), &[], None, - &expressions + &expressions, + &Arena::new(), ), Ok(FunctionUniformity { result: Uniformity { @@ -1297,26 +1343,59 @@ fn uniform_control_flow() { reject: crate::Block::new(), }; { + let block_info = info.process_block( + &vec![stmt_emit2.clone(), stmt_if_non_uniform.clone()].into(), + &[], + None, + &expressions, + &Arena::new(), + ); + assert_eq!( + block_info, + Err(FunctionError::NonUniformControlFlow( + UniformityRequirements::DERIVATIVE, + derivative_expr, + UniformityDisruptor::Expression(non_uniform_global_expr) + ) + .with_span()), + ); + assert_eq!(info[derivative_expr].ref_count, 1); + + // Test that the same thing passes when we disable the `derivative_uniformity` + let mut diagnostic_filters = Arena::new(); + let diagnostic_filter_leaf = diagnostic_filters.append( + DiagnosticFilterNode { + inner: crate::diagnostic_filter::DiagnosticFilter { + new_severity: crate::diagnostic_filter::Severity::Off, + triggering_rule: FilterableTriggeringRule::DerivativeUniformity, + }, + parent: None, + }, + crate::Span::default(), + ); + let mut info = FunctionInfo { + diagnostic_filter_leaf: Some(diagnostic_filter_leaf), + ..info.clone() + }; + let block_info = info.process_block( &vec![stmt_emit2, stmt_if_non_uniform].into(), &[], None, &expressions, + &diagnostic_filters, ); - if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE { - assert_eq!(info[derivative_expr].ref_count, 2); - } else { - assert_eq!( - block_info, - Err(FunctionError::NonUniformControlFlow( - UniformityRequirements::DERIVATIVE, - derivative_expr, - UniformityDisruptor::Expression(non_uniform_global_expr) - ) - .with_span()), - ); - assert_eq!(info[derivative_expr].ref_count, 1); - } + assert_eq!( + block_info, + Ok(FunctionUniformity { + result: Uniformity { + non_uniform_result: None, + requirements: UniformityRequirements::DERIVATIVE, + }, + exit: ExitFlags::empty() + }), + ); + assert_eq!(info[derivative_expr].ref_count, 2); } assert_eq!(info[non_uniform_global], GlobalUse::READ); @@ -1329,7 +1408,8 @@ fn uniform_control_flow() { &vec![stmt_emit3, stmt_return_non_uniform].into(), &[], Some(UniformityDisruptor::Return), - &expressions + &expressions, + &Arena::new(), ), Ok(FunctionUniformity { result: Uniformity { @@ -1356,7 +1436,8 @@ fn uniform_control_flow() { &vec![stmt_emit4, stmt_assign, stmt_kill, stmt_return_pointer].into(), &[], Some(UniformityDisruptor::Discard), - &expressions + &expressions, + &Arena::new(), ), Ok(FunctionUniformity { result: Uniformity { diff --git a/naga/src/valid/expression.rs b/naga/src/valid/expression.rs index ccdc501a5..9a1503401 100644 --- a/naga/src/valid/expression.rs +++ b/naga/src/valid/expression.rs @@ -39,12 +39,21 @@ pub enum ExpressionError { IndexableLength(#[from] IndexableLengthError), #[error("Operation {0:?} can't work with {1:?}")] InvalidUnaryOperandType(crate::UnaryOperator, Handle), - #[error("Operation {0:?} can't work with {1:?} and {2:?}")] - InvalidBinaryOperandTypes( - crate::BinaryOperator, - Handle, - Handle, - ), + #[error( + "Operation {:?} can't work with {:?} (of type {:?}) and {:?} (of type {:?})", + op, + lhs_expr, + lhs_type, + rhs_expr, + rhs_type + )] + InvalidBinaryOperandTypes { + op: crate::BinaryOperator, + lhs_expr: Handle, + lhs_type: crate::TypeInner, + rhs_expr: Handle, + rhs_type: crate::TypeInner, + }, #[error("Selecting is not possible")] InvalidSelectTypes, #[error("Relational argument {0:?} is not a boolean vector")] @@ -847,7 +856,13 @@ impl super::Validator { function.expressions[right], right_inner ); - return Err(ExpressionError::InvalidBinaryOperandTypes(op, left, right)); + return Err(ExpressionError::InvalidBinaryOperandTypes { + op, + lhs_expr: left, + lhs_type: left_inner.clone(), + rhs_expr: right, + rhs_type: right_inner.clone(), + }); } ShaderStages::all() } @@ -1363,6 +1378,26 @@ impl super::Validator { _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), } } + Mf::QuantizeToF16 => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + match *arg_ty { + Ti::Scalar(Sc { + kind: Sk::Float, + width: 4, + }) + | Ti::Vector { + scalar: + Sc { + kind: Sk::Float, + width: 4, + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } // Remove once fixed https://github.com/gfx-rs/wgpu/issues/5276 Mf::CountLeadingZeros | Mf::CountTrailingZeros diff --git a/naga/src/valid/handles.rs b/naga/src/valid/handles.rs index f8be76d02..069261eb4 100644 --- a/naga/src/valid/handles.rs +++ b/naga/src/valid/handles.rs @@ -2,6 +2,7 @@ use crate::{ arena::{BadHandle, BadRangeError}, + diagnostic_filter::DiagnosticFilterNode, Handle, }; @@ -39,6 +40,8 @@ impl super::Validator { ref types, ref special_types, ref global_expressions, + ref diagnostic_filters, + ref diagnostic_filter_leaf, } = module; // NOTE: Types being first is important. All other forms of validation depend on this. @@ -180,6 +183,14 @@ impl super::Validator { validate_type(ty)?; } + for (handle, _node) in diagnostic_filters.iter() { + let DiagnosticFilterNode { inner: _, parent } = diagnostic_filters[handle]; + handle.check_dep_opt(parent)?; + } + if let Some(handle) = *diagnostic_filter_leaf { + handle.check_valid_for(diagnostic_filters)?; + } + Ok(()) } diff --git a/naga/tests/in/diagnostic-filter.param.ron b/naga/tests/in/diagnostic-filter.param.ron new file mode 100644 index 000000000..72873dd66 --- /dev/null +++ b/naga/tests/in/diagnostic-filter.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/diagnostic-filter.wgsl b/naga/tests/in/diagnostic-filter.wgsl new file mode 100644 index 000000000..93a3942cf --- /dev/null +++ b/naga/tests/in/diagnostic-filter.wgsl @@ -0,0 +1 @@ +diagnostic(off, derivative_uniformity); diff --git a/naga/tests/in/image.param.ron b/naga/tests/in/image.param.ron index 5b6d71def..7f2d247d8 100644 --- a/naga/tests/in/image.param.ron +++ b/naga/tests/in/image.param.ron @@ -3,5 +3,11 @@ version: (1, 1), debug: true, ), - glsl_exclude_list: ["depth_load", "depth_no_comparison", "levels_queries"] + glsl: ( + version: Desktop(430), + writer_flags: (""), + binding_map: {}, + zero_initialize_workgroup_memory: true, + ), + glsl_exclude_list: ["depth_load", "depth_no_comparison"] ) diff --git a/naga/tests/in/image.wgsl b/naga/tests/in/image.wgsl index 2bae8f9d8..e78480118 100644 --- a/naga/tests/in/image.wgsl +++ b/naga/tests/in/image.wgsl @@ -92,8 +92,9 @@ fn queries() -> @builtin(position) vec4 { @vertex fn levels_queries() -> @builtin(position) vec4 { let num_levels_2d = textureNumLevels(image_2d); - let num_levels_2d_array = textureNumLevels(image_2d_array); let num_layers_2d = textureNumLayers(image_2d_array); + let num_levels_2d_array = textureNumLevels(image_2d_array); + let num_layers_2d_array = textureNumLayers(image_2d_array); let num_levels_cube = textureNumLevels(image_cube); let num_levels_cube_array = textureNumLevels(image_cube_array); let num_layers_cube = textureNumLayers(image_cube_array); diff --git a/naga/tests/in/math-functions.wgsl b/naga/tests/in/math-functions.wgsl index d08e76e4f..cc865202c 100644 --- a/naga/tests/in/math-functions.wgsl +++ b/naga/tests/in/math-functions.wgsl @@ -45,4 +45,8 @@ fn main() { let frexp_b = frexp(1.5).fract; let frexp_c: i32 = frexp(1.5).exp; let frexp_d: i32 = frexp(vec4(1.5, 1.5, 1.5, 1.5)).exp.x; + let quantizeToF16_a: f32 = quantizeToF16(1.0); + let quantizeToF16_b: vec2 = quantizeToF16(vec2(1.0, 1.0)); + let quantizeToF16_c: vec3 = quantizeToF16(vec3(1.0, 1.0, 1.0)); + let quantizeToF16_d: vec4 = quantizeToF16(vec4(1.0, 1.0, 1.0, 1.0)); } diff --git a/naga/tests/out/analysis/access.info.ron b/naga/tests/out/analysis/access.info.ron index 1a45af466..886f0bf9e 100644 --- a/naga/tests/out/analysis/access.info.ron +++ b/naga/tests/out/analysis/access.info.ron @@ -1191,6 +1191,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2516,6 +2517,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2555,6 +2557,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2603,6 +2606,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2645,6 +2649,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2738,6 +2743,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2789,6 +2795,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2843,6 +2850,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2894,6 +2902,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2948,6 +2957,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ], entry_points: [ @@ -3623,6 +3633,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -4074,6 +4085,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -4194,6 +4206,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -4257,6 +4270,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ], const_expression_types: [ diff --git a/naga/tests/out/analysis/collatz.info.ron b/naga/tests/out/analysis/collatz.info.ron index 94b879fcb..6e7dd37be 100644 --- a/naga/tests/out/analysis/collatz.info.ron +++ b/naga/tests/out/analysis/collatz.info.ron @@ -274,6 +274,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ], entry_points: [ @@ -428,6 +429,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ], const_expression_types: [], diff --git a/naga/tests/out/analysis/overrides.info.ron b/naga/tests/out/analysis/overrides.info.ron index 848d1780e..0bb10336c 100644 --- a/naga/tests/out/analysis/overrides.info.ron +++ b/naga/tests/out/analysis/overrides.info.ron @@ -163,6 +163,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ], const_expression_types: [ diff --git a/naga/tests/out/analysis/shadow.info.ron b/naga/tests/out/analysis/shadow.info.ron index fb14beb1c..e7a122dc7 100644 --- a/naga/tests/out/analysis/shadow.info.ron +++ b/naga/tests/out/analysis/shadow.info.ron @@ -412,6 +412,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -1571,6 +1572,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ], entry_points: [ @@ -1664,6 +1666,7 @@ ], sampling: [], dual_source_blending: false, + diagnostic_filter_leaf: None, ), ], const_expression_types: [ diff --git a/naga/tests/out/glsl/image.gather.Fragment.glsl b/naga/tests/out/glsl/image.gather.Fragment.glsl index c7c2fc534..43e6dcc85 100644 --- a/naga/tests/out/glsl/image.gather.Fragment.glsl +++ b/naga/tests/out/glsl/image.gather.Fragment.glsl @@ -1,16 +1,11 @@ -#version 310 es -#extension GL_EXT_texture_cube_map_array : require +#version 430 core +uniform sampler2D _group_0_binding_1_fs; -precision highp float; -precision highp int; +uniform usampler2D _group_0_binding_2_fs; -uniform highp sampler2D _group_0_binding_1_fs; +uniform isampler2D _group_0_binding_3_fs; -uniform highp usampler2D _group_0_binding_2_fs; - -uniform highp isampler2D _group_0_binding_3_fs; - -uniform highp sampler2DShadow _group_1_binding_2_fs; +uniform sampler2DShadow _group_1_binding_2_fs; layout(location = 0) out vec4 _fs2p_location0; diff --git a/naga/tests/out/glsl/image.levels_queries.Vertex.glsl b/naga/tests/out/glsl/image.levels_queries.Vertex.glsl new file mode 100644 index 000000000..36bbf47dd --- /dev/null +++ b/naga/tests/out/glsl/image.levels_queries.Vertex.glsl @@ -0,0 +1,30 @@ +#version 430 core +#extension GL_ARB_shader_texture_image_samples : require +uniform sampler2D _group_0_binding_1_vs; + +uniform sampler2DArray _group_0_binding_4_vs; + +uniform samplerCube _group_0_binding_5_vs; + +uniform samplerCubeArray _group_0_binding_6_vs; + +uniform sampler3D _group_0_binding_7_vs; + +uniform sampler2DMS _group_0_binding_8_vs; + + +void main() { + uint num_levels_2d = uint(textureQueryLevels(_group_0_binding_1_vs)); + uint num_layers_2d = uint(textureSize(_group_0_binding_4_vs, 0).z); + uint num_levels_2d_array = uint(textureQueryLevels(_group_0_binding_4_vs)); + uint num_layers_2d_array = uint(textureSize(_group_0_binding_4_vs, 0).z); + uint num_levels_cube = uint(textureQueryLevels(_group_0_binding_5_vs)); + uint num_levels_cube_array = uint(textureQueryLevels(_group_0_binding_6_vs)); + uint num_layers_cube = uint(textureSize(_group_0_binding_6_vs, 0).z); + uint num_levels_3d = uint(textureQueryLevels(_group_0_binding_7_vs)); + uint num_samples_aa = uint(textureSamples(_group_0_binding_8_vs)); + uint sum = (((((((num_layers_2d + num_layers_cube) + num_samples_aa) + num_levels_2d) + num_levels_2d_array) + num_levels_3d) + num_levels_cube) + num_levels_cube_array); + gl_Position = vec4(float(sum)); + return; +} + diff --git a/naga/tests/out/glsl/image.main.Compute.glsl b/naga/tests/out/glsl/image.main.Compute.glsl index 78324dd4c..13a4b24d6 100644 --- a/naga/tests/out/glsl/image.main.Compute.glsl +++ b/naga/tests/out/glsl/image.main.Compute.glsl @@ -1,22 +1,18 @@ -#version 310 es -#extension GL_EXT_texture_cube_map_array : require - -precision highp float; -precision highp int; - +#version 430 core +#extension GL_ARB_compute_shader : require layout(local_size_x = 16, local_size_y = 1, local_size_z = 1) in; -uniform highp usampler2D _group_0_binding_0_cs; +uniform usampler2D _group_0_binding_0_cs; -uniform highp usampler2DMS _group_0_binding_3_cs; +uniform usampler2DMS _group_0_binding_3_cs; -layout(rgba8ui) readonly uniform highp uimage2D _group_0_binding_1_cs; +layout(rgba8ui) readonly uniform uimage2D _group_0_binding_1_cs; -uniform highp usampler2DArray _group_0_binding_5_cs; +uniform usampler2DArray _group_0_binding_5_cs; -uniform highp usampler2D _group_0_binding_7_cs; +uniform usampler1D _group_0_binding_7_cs; -layout(r32ui) writeonly uniform highp uimage2D _group_0_binding_2_cs; +layout(r32ui) writeonly uniform uimage1D _group_0_binding_2_cs; void main() { @@ -28,15 +24,15 @@ void main() { uvec4 value4_ = imageLoad(_group_0_binding_1_cs, itc); uvec4 value5_ = texelFetch(_group_0_binding_5_cs, ivec3(itc, local_id.z), (int(local_id.z) + 1)); uvec4 value6_ = texelFetch(_group_0_binding_5_cs, ivec3(itc, int(local_id.z)), (int(local_id.z) + 1)); - uvec4 value7_ = texelFetch(_group_0_binding_7_cs, ivec2(int(local_id.x), 0), int(local_id.z)); + uvec4 value7_ = texelFetch(_group_0_binding_7_cs, int(local_id.x), int(local_id.z)); uvec4 value1u = texelFetch(_group_0_binding_0_cs, ivec2(uvec2(itc)), int(local_id.z)); uvec4 value2u = texelFetch(_group_0_binding_3_cs, ivec2(uvec2(itc)), int(local_id.z)); uvec4 value4u = imageLoad(_group_0_binding_1_cs, ivec2(uvec2(itc))); uvec4 value5u = texelFetch(_group_0_binding_5_cs, ivec3(uvec2(itc), local_id.z), (int(local_id.z) + 1)); uvec4 value6u = texelFetch(_group_0_binding_5_cs, ivec3(uvec2(itc), int(local_id.z)), (int(local_id.z) + 1)); - uvec4 value7u = texelFetch(_group_0_binding_7_cs, ivec2(uint(local_id.x), 0), int(local_id.z)); - imageStore(_group_0_binding_2_cs, ivec2(itc.x, 0), ((((value1_ + value2_) + value4_) + value5_) + value6_)); - imageStore(_group_0_binding_2_cs, ivec2(uint(itc.x), 0), ((((value1u + value2u) + value4u) + value5u) + value6u)); + uvec4 value7u = texelFetch(_group_0_binding_7_cs, int(uint(local_id.x)), int(local_id.z)); + imageStore(_group_0_binding_2_cs, itc.x, ((((value1_ + value2_) + value4_) + value5_) + value6_)); + imageStore(_group_0_binding_2_cs, int(uint(itc.x)), ((((value1u + value2u) + value4u) + value5u) + value6u)); return; } diff --git a/naga/tests/out/glsl/image.queries.Vertex.glsl b/naga/tests/out/glsl/image.queries.Vertex.glsl index 932a0a3bc..4fff6ac0d 100644 --- a/naga/tests/out/glsl/image.queries.Vertex.glsl +++ b/naga/tests/out/glsl/image.queries.Vertex.glsl @@ -1,27 +1,22 @@ -#version 310 es -#extension GL_EXT_texture_cube_map_array : require +#version 430 core +uniform sampler1D _group_0_binding_0_vs; -precision highp float; -precision highp int; +uniform sampler2D _group_0_binding_1_vs; -uniform highp sampler2D _group_0_binding_0_vs; +uniform sampler2DArray _group_0_binding_4_vs; -uniform highp sampler2D _group_0_binding_1_vs; +uniform samplerCube _group_0_binding_5_vs; -uniform highp sampler2DArray _group_0_binding_4_vs; +uniform samplerCubeArray _group_0_binding_6_vs; -uniform highp samplerCube _group_0_binding_5_vs; +uniform sampler3D _group_0_binding_7_vs; -uniform highp samplerCubeArray _group_0_binding_6_vs; - -uniform highp sampler3D _group_0_binding_7_vs; - -uniform highp sampler2DMS _group_0_binding_8_vs; +uniform sampler2DMS _group_0_binding_8_vs; void main() { - uint dim_1d = uint(textureSize(_group_0_binding_0_vs, 0).x); - uint dim_1d_lod = uint(textureSize(_group_0_binding_0_vs, int(dim_1d)).x); + uint dim_1d = uint(textureSize(_group_0_binding_0_vs, 0)); + uint dim_1d_lod = uint(textureSize(_group_0_binding_0_vs, int(dim_1d))); uvec2 dim_2d = uvec2(textureSize(_group_0_binding_1_vs, 0).xy); uvec2 dim_2d_lod = uvec2(textureSize(_group_0_binding_1_vs, 1).xy); uvec2 dim_2d_array = uvec2(textureSize(_group_0_binding_4_vs, 0).xy); @@ -35,7 +30,6 @@ void main() { uvec2 dim_2s_ms = uvec2(textureSize(_group_0_binding_8_vs).xy); uint sum = ((((((((((dim_1d + dim_2d.y) + dim_2d_lod.y) + dim_2d_array.y) + dim_2d_array_lod.y) + dim_cube.y) + dim_cube_lod.y) + dim_cube_array.y) + dim_cube_array_lod.y) + dim_3d.z) + dim_3d_lod.z); gl_Position = vec4(float(sum)); - gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w); return; } diff --git a/naga/tests/out/glsl/image.texture_sample.Fragment.glsl b/naga/tests/out/glsl/image.texture_sample.Fragment.glsl index 97be5a59d..3657b2ea1 100644 --- a/naga/tests/out/glsl/image.texture_sample.Fragment.glsl +++ b/naga/tests/out/glsl/image.texture_sample.Fragment.glsl @@ -1,16 +1,11 @@ -#version 310 es -#extension GL_EXT_texture_cube_map_array : require +#version 430 core +uniform sampler1D _group_0_binding_0_fs; -precision highp float; -precision highp int; +uniform sampler2D _group_0_binding_1_fs; -uniform highp sampler2D _group_0_binding_0_fs; +uniform sampler2DArray _group_0_binding_4_fs; -uniform highp sampler2D _group_0_binding_1_fs; - -uniform highp sampler2DArray _group_0_binding_4_fs; - -uniform highp samplerCubeArray _group_0_binding_6_fs; +uniform samplerCubeArray _group_0_binding_6_fs; layout(location = 0) out vec4 _fs2p_location0; @@ -18,7 +13,7 @@ void main() { vec4 a = vec4(0.0); vec2 tc = vec2(0.5); vec3 tc3_ = vec3(0.5); - vec4 _e9 = texture(_group_0_binding_0_fs, vec2(tc.x, 0.0)); + vec4 _e9 = texture(_group_0_binding_0_fs, tc.x); vec4 _e10 = a; a = (_e10 + _e9); vec4 _e14 = texture(_group_0_binding_1_fs, vec2(tc)); diff --git a/naga/tests/out/glsl/image.texture_sample_comparison.Fragment.glsl b/naga/tests/out/glsl/image.texture_sample_comparison.Fragment.glsl index 1dc303ed6..b1c198b0c 100644 --- a/naga/tests/out/glsl/image.texture_sample_comparison.Fragment.glsl +++ b/naga/tests/out/glsl/image.texture_sample_comparison.Fragment.glsl @@ -1,14 +1,9 @@ -#version 310 es -#extension GL_EXT_texture_cube_map_array : require +#version 430 core +uniform sampler2DShadow _group_1_binding_2_fs; -precision highp float; -precision highp int; +uniform sampler2DArrayShadow _group_1_binding_3_fs; -uniform highp sampler2DShadow _group_1_binding_2_fs; - -uniform highp sampler2DArrayShadow _group_1_binding_3_fs; - -uniform highp samplerCubeShadow _group_1_binding_4_fs; +uniform samplerCubeShadow _group_1_binding_4_fs; layout(location = 0) out float _fs2p_location0; diff --git a/naga/tests/out/glsl/math-functions.main.Fragment.glsl b/naga/tests/out/glsl/math-functions.main.Fragment.glsl index 4ab85269e..6ac3a4de0 100644 --- a/naga/tests/out/glsl/math-functions.main.Fragment.glsl +++ b/naga/tests/out/glsl/math-functions.main.Fragment.glsl @@ -87,5 +87,12 @@ void main() { float frexp_b = naga_frexp(1.5).fract_; int frexp_c = naga_frexp(1.5).exp_; int frexp_d = naga_frexp(vec4(1.5, 1.5, 1.5, 1.5)).exp_.x; + float quantizeToF16_a = unpackHalf2x16(packHalf2x16(vec2(1.0))).x; + vec2 _e120 = vec2(1.0, 1.0); + vec2 quantizeToF16_b = unpackHalf2x16(packHalf2x16(_e120)); + vec3 _e125 = vec3(1.0, 1.0, 1.0); + vec3 quantizeToF16_c = vec3(unpackHalf2x16(packHalf2x16(_e125.xy)), unpackHalf2x16(packHalf2x16(_e125.zz)).x); + vec4 _e131 = vec4(1.0, 1.0, 1.0, 1.0); + vec4 quantizeToF16_d = vec4(unpackHalf2x16(packHalf2x16(_e131.xy)), unpackHalf2x16(packHalf2x16(_e131.zw))); } diff --git a/naga/tests/out/hlsl/image.hlsl b/naga/tests/out/hlsl/image.hlsl index 5ad6d3d2c..a81625b05 100644 --- a/naga/tests/out/hlsl/image.hlsl +++ b/naga/tests/out/hlsl/image.hlsl @@ -177,14 +177,14 @@ uint NagaNumLevels2D(Texture2D tex) return ret.z; } -uint NagaNumLevels2DArray(Texture2DArray tex) +uint NagaNumLayers2DArray(Texture2DArray tex) { uint4 ret; tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); return ret.w; } -uint NagaNumLayers2DArray(Texture2DArray tex) +uint NagaNumLevels2DArray(Texture2DArray tex) { uint4 ret; tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); @@ -229,8 +229,9 @@ uint NagaMSNumSamples2D(Texture2DMS tex) float4 levels_queries() : SV_Position { uint num_levels_2d = NagaNumLevels2D(image_2d); - uint num_levels_2d_array = NagaNumLevels2DArray(image_2d_array); uint num_layers_2d = NagaNumLayers2DArray(image_2d_array); + uint num_levels_2d_array = NagaNumLevels2DArray(image_2d_array); + uint num_layers_2d_array = NagaNumLayers2DArray(image_2d_array); uint num_levels_cube = NagaNumLevelsCube(image_cube); uint num_levels_cube_array = NagaNumLevelsCubeArray(image_cube_array); uint num_layers_cube = NagaNumLayersCubeArray(image_cube_array); diff --git a/naga/tests/out/hlsl/math-functions.hlsl b/naga/tests/out/hlsl/math-functions.hlsl index a02b2b128..a94b5062a 100644 --- a/naga/tests/out/hlsl/math-functions.hlsl +++ b/naga/tests/out/hlsl/math-functions.hlsl @@ -101,4 +101,8 @@ void main() float frexp_b = naga_frexp(1.5).fract; int frexp_c = naga_frexp(1.5).exp_; int frexp_d = naga_frexp(float4(1.5, 1.5, 1.5, 1.5)).exp_.x; + float quantizeToF16_a = f16tof32(f32tof16(1.0)); + float2 quantizeToF16_b = f16tof32(f32tof16(float2(1.0, 1.0))); + float3 quantizeToF16_c = f16tof32(f32tof16(float3(1.0, 1.0, 1.0))); + float4 quantizeToF16_d = f16tof32(f32tof16(float4(1.0, 1.0, 1.0, 1.0))); } diff --git a/naga/tests/out/ir/access.compact.ron b/naga/tests/out/ir/access.compact.ron index 2e18f8b35..cae5d7d8e 100644 --- a/naga/tests/out/ir/access.compact.ron +++ b/naga/tests/out/ir/access.compact.ron @@ -2476,4 +2476,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/access.ron b/naga/tests/out/ir/access.ron index 2e18f8b35..cae5d7d8e 100644 --- a/naga/tests/out/ir/access.ron +++ b/naga/tests/out/ir/access.ron @@ -2476,4 +2476,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/atomic_i_increment.compact.ron b/naga/tests/out/ir/atomic_i_increment.compact.ron index 12a4692a3..c7c33fcaa 100644 --- a/naga/tests/out/ir/atomic_i_increment.compact.ron +++ b/naga/tests/out/ir/atomic_i_increment.compact.ron @@ -279,4 +279,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/atomic_i_increment.ron b/naga/tests/out/ir/atomic_i_increment.ron index 82fa97502..4fe2266a1 100644 --- a/naga/tests/out/ir/atomic_i_increment.ron +++ b/naga/tests/out/ir/atomic_i_increment.ron @@ -304,4 +304,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/collatz.compact.ron b/naga/tests/out/ir/collatz.compact.ron index 5999cf85a..9091d269c 100644 --- a/naga/tests/out/ir/collatz.compact.ron +++ b/naga/tests/out/ir/collatz.compact.ron @@ -330,4 +330,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/collatz.ron b/naga/tests/out/ir/collatz.ron index 5999cf85a..9091d269c 100644 --- a/naga/tests/out/ir/collatz.ron +++ b/naga/tests/out/ir/collatz.ron @@ -330,4 +330,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/const_assert.compact.ron b/naga/tests/out/ir/const_assert.compact.ron index 9dce67b5f..8d4a729f9 100644 --- a/naga/tests/out/ir/const_assert.compact.ron +++ b/naga/tests/out/ir/const_assert.compact.ron @@ -51,4 +51,6 @@ ), ], entry_points: [], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/const_assert.ron b/naga/tests/out/ir/const_assert.ron index 9dce67b5f..8d4a729f9 100644 --- a/naga/tests/out/ir/const_assert.ron +++ b/naga/tests/out/ir/const_assert.ron @@ -51,4 +51,6 @@ ), ], entry_points: [], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/diagnostic-filter.compact.ron b/naga/tests/out/ir/diagnostic-filter.compact.ron new file mode 100644 index 000000000..e96fc1f4d --- /dev/null +++ b/naga/tests/out/ir/diagnostic-filter.compact.ron @@ -0,0 +1,24 @@ +( + types: [], + special_types: ( + ray_desc: None, + ray_intersection: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [], + global_expressions: [], + functions: [], + entry_points: [], + diagnostic_filters: [ + ( + inner: ( + new_severity: Off, + triggering_rule: DerivativeUniformity, + ), + parent: None, + ), + ], + diagnostic_filter_leaf: Some(0), +) \ No newline at end of file diff --git a/naga/tests/out/ir/diagnostic-filter.ron b/naga/tests/out/ir/diagnostic-filter.ron new file mode 100644 index 000000000..e96fc1f4d --- /dev/null +++ b/naga/tests/out/ir/diagnostic-filter.ron @@ -0,0 +1,24 @@ +( + types: [], + special_types: ( + ray_desc: None, + ray_intersection: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [], + global_expressions: [], + functions: [], + entry_points: [], + diagnostic_filters: [ + ( + inner: ( + new_severity: Off, + triggering_rule: DerivativeUniformity, + ), + parent: None, + ), + ], + diagnostic_filter_leaf: Some(0), +) \ No newline at end of file diff --git a/naga/tests/out/ir/fetch_depth.compact.ron b/naga/tests/out/ir/fetch_depth.compact.ron index dc5ecf08a..08c9296bb 100644 --- a/naga/tests/out/ir/fetch_depth.compact.ron +++ b/naga/tests/out/ir/fetch_depth.compact.ron @@ -192,4 +192,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/fetch_depth.ron b/naga/tests/out/ir/fetch_depth.ron index 210a9ee26..d48c09bfe 100644 --- a/naga/tests/out/ir/fetch_depth.ron +++ b/naga/tests/out/ir/fetch_depth.ron @@ -262,4 +262,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/index-by-value.compact.ron b/naga/tests/out/ir/index-by-value.compact.ron index 9cc07b077..461d5f661 100644 --- a/naga/tests/out/ir/index-by-value.compact.ron +++ b/naga/tests/out/ir/index-by-value.compact.ron @@ -369,4 +369,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/index-by-value.ron b/naga/tests/out/ir/index-by-value.ron index 9cc07b077..461d5f661 100644 --- a/naga/tests/out/ir/index-by-value.ron +++ b/naga/tests/out/ir/index-by-value.ron @@ -369,4 +369,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/local-const.compact.ron b/naga/tests/out/ir/local-const.compact.ron index a9b9f32af..4a0041f50 100644 --- a/naga/tests/out/ir/local-const.compact.ron +++ b/naga/tests/out/ir/local-const.compact.ron @@ -136,4 +136,6 @@ ), ], entry_points: [], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/local-const.ron b/naga/tests/out/ir/local-const.ron index a9b9f32af..4a0041f50 100644 --- a/naga/tests/out/ir/local-const.ron +++ b/naga/tests/out/ir/local-const.ron @@ -136,4 +136,6 @@ ), ], entry_points: [], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/overrides-atomicCompareExchangeWeak.compact.ron b/naga/tests/out/ir/overrides-atomicCompareExchangeWeak.compact.ron index 33846ef30..55184adae 100644 --- a/naga/tests/out/ir/overrides-atomicCompareExchangeWeak.compact.ron +++ b/naga/tests/out/ir/overrides-atomicCompareExchangeWeak.compact.ron @@ -125,4 +125,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/overrides-atomicCompareExchangeWeak.ron b/naga/tests/out/ir/overrides-atomicCompareExchangeWeak.ron index 33846ef30..55184adae 100644 --- a/naga/tests/out/ir/overrides-atomicCompareExchangeWeak.ron +++ b/naga/tests/out/ir/overrides-atomicCompareExchangeWeak.ron @@ -125,4 +125,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/overrides-ray-query.compact.ron b/naga/tests/out/ir/overrides-ray-query.compact.ron index 544f63ede..a66c7ba73 100644 --- a/naga/tests/out/ir/overrides-ray-query.compact.ron +++ b/naga/tests/out/ir/overrides-ray-query.compact.ron @@ -256,4 +256,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/overrides-ray-query.ron b/naga/tests/out/ir/overrides-ray-query.ron index 544f63ede..a66c7ba73 100644 --- a/naga/tests/out/ir/overrides-ray-query.ron +++ b/naga/tests/out/ir/overrides-ray-query.ron @@ -256,4 +256,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/overrides.compact.ron b/naga/tests/out/ir/overrides.compact.ron index e7921e732..f1e3f411e 100644 --- a/naga/tests/out/ir/overrides.compact.ron +++ b/naga/tests/out/ir/overrides.compact.ron @@ -196,4 +196,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/overrides.ron b/naga/tests/out/ir/overrides.ron index e7921e732..f1e3f411e 100644 --- a/naga/tests/out/ir/overrides.ron +++ b/naga/tests/out/ir/overrides.ron @@ -196,4 +196,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/shadow.compact.ron b/naga/tests/out/ir/shadow.compact.ron index a33b7d996..65e78c142 100644 --- a/naga/tests/out/ir/shadow.compact.ron +++ b/naga/tests/out/ir/shadow.compact.ron @@ -1026,4 +1026,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/shadow.ron b/naga/tests/out/ir/shadow.ron index 7662f586f..88379a86c 100644 --- a/naga/tests/out/ir/shadow.ron +++ b/naga/tests/out/ir/shadow.ron @@ -1304,4 +1304,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/spec-constants.compact.ron b/naga/tests/out/ir/spec-constants.compact.ron index e33bec657..5e8a1c22c 100644 --- a/naga/tests/out/ir/spec-constants.compact.ron +++ b/naga/tests/out/ir/spec-constants.compact.ron @@ -609,4 +609,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/ir/spec-constants.ron b/naga/tests/out/ir/spec-constants.ron index 8319c7bd3..8a42262d2 100644 --- a/naga/tests/out/ir/spec-constants.ron +++ b/naga/tests/out/ir/spec-constants.ron @@ -715,4 +715,6 @@ ), ), ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, ) \ No newline at end of file diff --git a/naga/tests/out/msl/image.msl b/naga/tests/out/msl/image.msl index 40d6e809e..9f0a24300 100644 --- a/naga/tests/out/msl/image.msl +++ b/naga/tests/out/msl/image.msl @@ -94,8 +94,9 @@ vertex levels_queriesOutput levels_queries( , metal::texture2d_ms image_aa [[user(fake0)]] ) { uint num_levels_2d = image_2d.get_num_mip_levels(); - uint num_levels_2d_array = image_2d_array.get_num_mip_levels(); uint num_layers_2d = image_2d_array.get_array_size(); + uint num_levels_2d_array = image_2d_array.get_num_mip_levels(); + uint num_layers_2d_array = image_2d_array.get_array_size(); uint num_levels_cube = image_cube.get_num_mip_levels(); uint num_levels_cube_array = image_cube_array.get_num_mip_levels(); uint num_layers_cube = image_cube_array.get_array_size(); diff --git a/naga/tests/out/msl/math-functions.msl b/naga/tests/out/msl/math-functions.msl index 559002c39..f3dbe0a20 100644 --- a/naga/tests/out/msl/math-functions.msl +++ b/naga/tests/out/msl/math-functions.msl @@ -89,4 +89,8 @@ fragment void main_( float frexp_b = naga_frexp(1.5).fract; int frexp_c = naga_frexp(1.5).exp; int frexp_d = naga_frexp(metal::float4(1.5, 1.5, 1.5, 1.5)).exp.x; + float quantizeToF16_a = float(half(1.0)); + metal::float2 quantizeToF16_b = metal::float2(metal::half2(metal::float2(1.0, 1.0))); + metal::float3 quantizeToF16_c = metal::float3(metal::half3(metal::float3(1.0, 1.0, 1.0))); + metal::float4 quantizeToF16_d = metal::float4(metal::half4(metal::float4(1.0, 1.0, 1.0, 1.0))); } diff --git a/naga/tests/out/spv/image.spvasm b/naga/tests/out/spv/image.spvasm index 708cd65f2..974f29e16 100644 --- a/naga/tests/out/spv/image.spvasm +++ b/naga/tests/out/spv/image.spvasm @@ -1,7 +1,7 @@ ; SPIR-V ; Version: 1.1 ; Generator: rspirv -; Bound: 518 +; Bound: 520 OpCapability Shader OpCapability Image1D OpCapability Sampled1D @@ -13,16 +13,16 @@ OpEntryPoint GLCompute %78 "main" %75 OpEntryPoint GLCompute %169 "depth_load" %167 OpEntryPoint Vertex %189 "queries" %187 OpEntryPoint Vertex %241 "levels_queries" %240 -OpEntryPoint Fragment %270 "texture_sample" %269 -OpEntryPoint Fragment %417 "texture_sample_comparison" %415 -OpEntryPoint Fragment %473 "gather" %472 -OpEntryPoint Fragment %507 "depth_no_comparison" %506 +OpEntryPoint Fragment %272 "texture_sample" %271 +OpEntryPoint Fragment %419 "texture_sample_comparison" %417 +OpEntryPoint Fragment %475 "gather" %474 +OpEntryPoint Fragment %509 "depth_no_comparison" %508 OpExecutionMode %78 LocalSize 16 1 1 OpExecutionMode %169 LocalSize 16 1 1 -OpExecutionMode %270 OriginUpperLeft -OpExecutionMode %417 OriginUpperLeft -OpExecutionMode %473 OriginUpperLeft -OpExecutionMode %507 OriginUpperLeft +OpExecutionMode %272 OriginUpperLeft +OpExecutionMode %419 OriginUpperLeft +OpExecutionMode %475 OriginUpperLeft +OpExecutionMode %509 OriginUpperLeft OpName %31 "image_mipmapped_src" OpName %33 "image_multisampled_src" OpName %35 "image_depth_multisampled_src" @@ -51,12 +51,12 @@ OpName %167 "local_id" OpName %169 "depth_load" OpName %189 "queries" OpName %241 "levels_queries" -OpName %270 "texture_sample" -OpName %284 "a" -OpName %417 "texture_sample_comparison" -OpName %422 "a" -OpName %473 "gather" -OpName %507 "depth_no_comparison" +OpName %272 "texture_sample" +OpName %286 "a" +OpName %419 "texture_sample_comparison" +OpName %424 "a" +OpName %475 "gather" +OpName %509 "depth_no_comparison" OpDecorate %31 DescriptorSet 0 OpDecorate %31 Binding 0 OpDecorate %33 DescriptorSet 0 @@ -108,10 +108,10 @@ OpDecorate %75 BuiltIn LocalInvocationId OpDecorate %167 BuiltIn LocalInvocationId OpDecorate %187 BuiltIn Position OpDecorate %240 BuiltIn Position -OpDecorate %269 Location 0 -OpDecorate %415 Location 0 -OpDecorate %472 Location 0 -OpDecorate %506 Location 0 +OpDecorate %271 Location 0 +OpDecorate %417 Location 0 +OpDecorate %474 Location 0 +OpDecorate %508 Location 0 %2 = OpTypeVoid %4 = OpTypeInt 32 0 %3 = OpTypeImage %4 2D 0 0 0 1 Unknown @@ -198,36 +198,36 @@ OpDecorate %506 Location 0 %187 = OpVariable %188 Output %198 = OpConstant %4 0 %240 = OpVariable %188 Output -%269 = OpVariable %188 Output -%276 = OpConstant %7 0.5 -%277 = OpTypeVector %7 2 -%278 = OpConstantComposite %277 %276 %276 -%279 = OpTypeVector %7 3 -%280 = OpConstantComposite %279 %276 %276 %276 -%281 = OpConstant %7 2.3 -%282 = OpConstant %7 2.0 -%283 = OpConstant %14 0 -%285 = OpTypePointer Function %23 -%286 = OpConstantNull %23 -%289 = OpTypeSampledImage %15 -%294 = OpTypeSampledImage %16 -%315 = OpTypeSampledImage %18 -%376 = OpTypeSampledImage %20 -%416 = OpTypePointer Output %7 -%415 = OpVariable %416 Output -%423 = OpTypePointer Function %7 -%424 = OpConstantNull %7 -%426 = OpTypeSampledImage %25 -%431 = OpTypeSampledImage %26 -%444 = OpTypeSampledImage %27 -%451 = OpConstant %7 0.0 -%472 = OpVariable %188 Output -%483 = OpConstant %4 1 -%486 = OpConstant %4 3 -%491 = OpTypeSampledImage %3 -%494 = OpTypeVector %14 4 -%495 = OpTypeSampledImage %17 -%506 = OpVariable %188 Output +%271 = OpVariable %188 Output +%278 = OpConstant %7 0.5 +%279 = OpTypeVector %7 2 +%280 = OpConstantComposite %279 %278 %278 +%281 = OpTypeVector %7 3 +%282 = OpConstantComposite %281 %278 %278 %278 +%283 = OpConstant %7 2.3 +%284 = OpConstant %7 2.0 +%285 = OpConstant %14 0 +%287 = OpTypePointer Function %23 +%288 = OpConstantNull %23 +%291 = OpTypeSampledImage %15 +%296 = OpTypeSampledImage %16 +%317 = OpTypeSampledImage %18 +%378 = OpTypeSampledImage %20 +%418 = OpTypePointer Output %7 +%417 = OpVariable %418 Output +%425 = OpTypePointer Function %7 +%426 = OpConstantNull %7 +%428 = OpTypeSampledImage %25 +%433 = OpTypeSampledImage %26 +%446 = OpTypeSampledImage %27 +%453 = OpConstant %7 0.0 +%474 = OpVariable %188 Output +%485 = OpConstant %4 1 +%488 = OpConstant %4 3 +%493 = OpTypeSampledImage %3 +%496 = OpTypeVector %14 4 +%497 = OpTypeSampledImage %17 +%508 = OpVariable %188 Output %78 = OpFunction %2 None %79 %74 = OpLabel %77 = OpLoad %12 %75 @@ -403,290 +403,292 @@ OpFunctionEnd OpBranch %248 %248 = OpLabel %249 = OpImageQueryLevels %4 %242 -%250 = OpImageQueryLevels %4 %243 -%251 = OpImageQuerySizeLod %12 %243 %198 -%252 = OpCompositeExtract %4 %251 2 -%253 = OpImageQueryLevels %4 %244 -%254 = OpImageQueryLevels %4 %245 -%255 = OpImageQuerySizeLod %12 %245 %198 -%256 = OpCompositeExtract %4 %255 2 -%257 = OpImageQueryLevels %4 %246 -%258 = OpImageQuerySamples %4 %247 -%259 = OpIAdd %4 %252 %256 -%260 = OpIAdd %4 %259 %258 -%261 = OpIAdd %4 %260 %249 -%262 = OpIAdd %4 %261 %250 -%263 = OpIAdd %4 %262 %257 -%264 = OpIAdd %4 %263 %253 -%265 = OpIAdd %4 %264 %254 -%266 = OpConvertUToF %7 %265 -%267 = OpCompositeConstruct %23 %266 %266 %266 %266 -OpStore %240 %267 +%250 = OpImageQuerySizeLod %12 %243 %198 +%251 = OpCompositeExtract %4 %250 2 +%252 = OpImageQueryLevels %4 %243 +%253 = OpImageQuerySizeLod %12 %243 %198 +%254 = OpCompositeExtract %4 %253 2 +%255 = OpImageQueryLevels %4 %244 +%256 = OpImageQueryLevels %4 %245 +%257 = OpImageQuerySizeLod %12 %245 %198 +%258 = OpCompositeExtract %4 %257 2 +%259 = OpImageQueryLevels %4 %246 +%260 = OpImageQuerySamples %4 %247 +%261 = OpIAdd %4 %251 %258 +%262 = OpIAdd %4 %261 %260 +%263 = OpIAdd %4 %262 %249 +%264 = OpIAdd %4 %263 %252 +%265 = OpIAdd %4 %264 %259 +%266 = OpIAdd %4 %265 %255 +%267 = OpIAdd %4 %266 %256 +%268 = OpConvertUToF %7 %267 +%269 = OpCompositeConstruct %23 %268 %268 %268 %268 +OpStore %240 %269 OpReturn OpFunctionEnd -%270 = OpFunction %2 None %79 -%268 = OpLabel -%284 = OpVariable %285 Function %286 -%271 = OpLoad %15 %47 -%272 = OpLoad %16 %49 -%273 = OpLoad %18 %54 -%274 = OpLoad %20 %58 -%275 = OpLoad %24 %64 -OpBranch %287 -%287 = OpLabel -%288 = OpCompositeExtract %7 %278 0 -%290 = OpSampledImage %289 %271 %275 -%291 = OpImageSampleImplicitLod %23 %290 %288 -%292 = OpLoad %23 %284 -%293 = OpFAdd %23 %292 %291 -OpStore %284 %293 -%295 = OpSampledImage %294 %272 %275 -%296 = OpImageSampleImplicitLod %23 %295 %278 -%297 = OpLoad %23 %284 -%298 = OpFAdd %23 %297 %296 -OpStore %284 %298 -%299 = OpSampledImage %294 %272 %275 -%300 = OpImageSampleImplicitLod %23 %299 %278 ConstOffset %30 -%301 = OpLoad %23 %284 -%302 = OpFAdd %23 %301 %300 -OpStore %284 %302 -%303 = OpSampledImage %294 %272 %275 -%304 = OpImageSampleExplicitLod %23 %303 %278 Lod %281 -%305 = OpLoad %23 %284 -%306 = OpFAdd %23 %305 %304 -OpStore %284 %306 -%307 = OpSampledImage %294 %272 %275 -%308 = OpImageSampleExplicitLod %23 %307 %278 Lod|ConstOffset %281 %30 -%309 = OpLoad %23 %284 -%310 = OpFAdd %23 %309 %308 -OpStore %284 %310 -%311 = OpSampledImage %294 %272 %275 -%312 = OpImageSampleImplicitLod %23 %311 %278 Bias|ConstOffset %282 %30 -%313 = OpLoad %23 %284 -%314 = OpFAdd %23 %313 %312 -OpStore %284 %314 -%316 = OpConvertUToF %7 %198 -%317 = OpCompositeConstruct %279 %278 %316 -%318 = OpSampledImage %315 %273 %275 -%319 = OpImageSampleImplicitLod %23 %318 %317 -%320 = OpLoad %23 %284 -%321 = OpFAdd %23 %320 %319 -OpStore %284 %321 -%322 = OpConvertUToF %7 %198 -%323 = OpCompositeConstruct %279 %278 %322 -%324 = OpSampledImage %315 %273 %275 -%325 = OpImageSampleImplicitLod %23 %324 %323 ConstOffset %30 -%326 = OpLoad %23 %284 -%327 = OpFAdd %23 %326 %325 -OpStore %284 %327 -%328 = OpConvertUToF %7 %198 -%329 = OpCompositeConstruct %279 %278 %328 -%330 = OpSampledImage %315 %273 %275 -%331 = OpImageSampleExplicitLod %23 %330 %329 Lod %281 -%332 = OpLoad %23 %284 -%333 = OpFAdd %23 %332 %331 -OpStore %284 %333 -%334 = OpConvertUToF %7 %198 -%335 = OpCompositeConstruct %279 %278 %334 -%336 = OpSampledImage %315 %273 %275 -%337 = OpImageSampleExplicitLod %23 %336 %335 Lod|ConstOffset %281 %30 -%338 = OpLoad %23 %284 -%339 = OpFAdd %23 %338 %337 -OpStore %284 %339 -%340 = OpConvertUToF %7 %198 -%341 = OpCompositeConstruct %279 %278 %340 -%342 = OpSampledImage %315 %273 %275 -%343 = OpImageSampleImplicitLod %23 %342 %341 Bias|ConstOffset %282 %30 -%344 = OpLoad %23 %284 -%345 = OpFAdd %23 %344 %343 -OpStore %284 %345 -%346 = OpConvertSToF %7 %283 -%347 = OpCompositeConstruct %279 %278 %346 -%348 = OpSampledImage %315 %273 %275 -%349 = OpImageSampleImplicitLod %23 %348 %347 -%350 = OpLoad %23 %284 -%351 = OpFAdd %23 %350 %349 -OpStore %284 %351 -%352 = OpConvertSToF %7 %283 -%353 = OpCompositeConstruct %279 %278 %352 -%354 = OpSampledImage %315 %273 %275 -%355 = OpImageSampleImplicitLod %23 %354 %353 ConstOffset %30 -%356 = OpLoad %23 %284 -%357 = OpFAdd %23 %356 %355 -OpStore %284 %357 -%358 = OpConvertSToF %7 %283 -%359 = OpCompositeConstruct %279 %278 %358 -%360 = OpSampledImage %315 %273 %275 -%361 = OpImageSampleExplicitLod %23 %360 %359 Lod %281 -%362 = OpLoad %23 %284 -%363 = OpFAdd %23 %362 %361 -OpStore %284 %363 -%364 = OpConvertSToF %7 %283 -%365 = OpCompositeConstruct %279 %278 %364 -%366 = OpSampledImage %315 %273 %275 -%367 = OpImageSampleExplicitLod %23 %366 %365 Lod|ConstOffset %281 %30 -%368 = OpLoad %23 %284 -%369 = OpFAdd %23 %368 %367 -OpStore %284 %369 -%370 = OpConvertSToF %7 %283 -%371 = OpCompositeConstruct %279 %278 %370 -%372 = OpSampledImage %315 %273 %275 -%373 = OpImageSampleImplicitLod %23 %372 %371 Bias|ConstOffset %282 %30 -%374 = OpLoad %23 %284 -%375 = OpFAdd %23 %374 %373 -OpStore %284 %375 -%377 = OpConvertUToF %7 %198 -%378 = OpCompositeConstruct %23 %280 %377 -%379 = OpSampledImage %376 %274 %275 -%380 = OpImageSampleImplicitLod %23 %379 %378 -%381 = OpLoad %23 %284 -%382 = OpFAdd %23 %381 %380 -OpStore %284 %382 -%383 = OpConvertUToF %7 %198 -%384 = OpCompositeConstruct %23 %280 %383 -%385 = OpSampledImage %376 %274 %275 -%386 = OpImageSampleExplicitLod %23 %385 %384 Lod %281 -%387 = OpLoad %23 %284 -%388 = OpFAdd %23 %387 %386 -OpStore %284 %388 -%389 = OpConvertUToF %7 %198 -%390 = OpCompositeConstruct %23 %280 %389 -%391 = OpSampledImage %376 %274 %275 -%392 = OpImageSampleImplicitLod %23 %391 %390 Bias %282 -%393 = OpLoad %23 %284 -%394 = OpFAdd %23 %393 %392 -OpStore %284 %394 -%395 = OpConvertSToF %7 %283 -%396 = OpCompositeConstruct %23 %280 %395 -%397 = OpSampledImage %376 %274 %275 -%398 = OpImageSampleImplicitLod %23 %397 %396 -%399 = OpLoad %23 %284 -%400 = OpFAdd %23 %399 %398 -OpStore %284 %400 -%401 = OpConvertSToF %7 %283 -%402 = OpCompositeConstruct %23 %280 %401 -%403 = OpSampledImage %376 %274 %275 -%404 = OpImageSampleExplicitLod %23 %403 %402 Lod %281 -%405 = OpLoad %23 %284 -%406 = OpFAdd %23 %405 %404 -OpStore %284 %406 -%407 = OpConvertSToF %7 %283 -%408 = OpCompositeConstruct %23 %280 %407 -%409 = OpSampledImage %376 %274 %275 -%410 = OpImageSampleImplicitLod %23 %409 %408 Bias %282 -%411 = OpLoad %23 %284 -%412 = OpFAdd %23 %411 %410 -OpStore %284 %412 -%413 = OpLoad %23 %284 -OpStore %269 %413 +%272 = OpFunction %2 None %79 +%270 = OpLabel +%286 = OpVariable %287 Function %288 +%273 = OpLoad %15 %47 +%274 = OpLoad %16 %49 +%275 = OpLoad %18 %54 +%276 = OpLoad %20 %58 +%277 = OpLoad %24 %64 +OpBranch %289 +%289 = OpLabel +%290 = OpCompositeExtract %7 %280 0 +%292 = OpSampledImage %291 %273 %277 +%293 = OpImageSampleImplicitLod %23 %292 %290 +%294 = OpLoad %23 %286 +%295 = OpFAdd %23 %294 %293 +OpStore %286 %295 +%297 = OpSampledImage %296 %274 %277 +%298 = OpImageSampleImplicitLod %23 %297 %280 +%299 = OpLoad %23 %286 +%300 = OpFAdd %23 %299 %298 +OpStore %286 %300 +%301 = OpSampledImage %296 %274 %277 +%302 = OpImageSampleImplicitLod %23 %301 %280 ConstOffset %30 +%303 = OpLoad %23 %286 +%304 = OpFAdd %23 %303 %302 +OpStore %286 %304 +%305 = OpSampledImage %296 %274 %277 +%306 = OpImageSampleExplicitLod %23 %305 %280 Lod %283 +%307 = OpLoad %23 %286 +%308 = OpFAdd %23 %307 %306 +OpStore %286 %308 +%309 = OpSampledImage %296 %274 %277 +%310 = OpImageSampleExplicitLod %23 %309 %280 Lod|ConstOffset %283 %30 +%311 = OpLoad %23 %286 +%312 = OpFAdd %23 %311 %310 +OpStore %286 %312 +%313 = OpSampledImage %296 %274 %277 +%314 = OpImageSampleImplicitLod %23 %313 %280 Bias|ConstOffset %284 %30 +%315 = OpLoad %23 %286 +%316 = OpFAdd %23 %315 %314 +OpStore %286 %316 +%318 = OpConvertUToF %7 %198 +%319 = OpCompositeConstruct %281 %280 %318 +%320 = OpSampledImage %317 %275 %277 +%321 = OpImageSampleImplicitLod %23 %320 %319 +%322 = OpLoad %23 %286 +%323 = OpFAdd %23 %322 %321 +OpStore %286 %323 +%324 = OpConvertUToF %7 %198 +%325 = OpCompositeConstruct %281 %280 %324 +%326 = OpSampledImage %317 %275 %277 +%327 = OpImageSampleImplicitLod %23 %326 %325 ConstOffset %30 +%328 = OpLoad %23 %286 +%329 = OpFAdd %23 %328 %327 +OpStore %286 %329 +%330 = OpConvertUToF %7 %198 +%331 = OpCompositeConstruct %281 %280 %330 +%332 = OpSampledImage %317 %275 %277 +%333 = OpImageSampleExplicitLod %23 %332 %331 Lod %283 +%334 = OpLoad %23 %286 +%335 = OpFAdd %23 %334 %333 +OpStore %286 %335 +%336 = OpConvertUToF %7 %198 +%337 = OpCompositeConstruct %281 %280 %336 +%338 = OpSampledImage %317 %275 %277 +%339 = OpImageSampleExplicitLod %23 %338 %337 Lod|ConstOffset %283 %30 +%340 = OpLoad %23 %286 +%341 = OpFAdd %23 %340 %339 +OpStore %286 %341 +%342 = OpConvertUToF %7 %198 +%343 = OpCompositeConstruct %281 %280 %342 +%344 = OpSampledImage %317 %275 %277 +%345 = OpImageSampleImplicitLod %23 %344 %343 Bias|ConstOffset %284 %30 +%346 = OpLoad %23 %286 +%347 = OpFAdd %23 %346 %345 +OpStore %286 %347 +%348 = OpConvertSToF %7 %285 +%349 = OpCompositeConstruct %281 %280 %348 +%350 = OpSampledImage %317 %275 %277 +%351 = OpImageSampleImplicitLod %23 %350 %349 +%352 = OpLoad %23 %286 +%353 = OpFAdd %23 %352 %351 +OpStore %286 %353 +%354 = OpConvertSToF %7 %285 +%355 = OpCompositeConstruct %281 %280 %354 +%356 = OpSampledImage %317 %275 %277 +%357 = OpImageSampleImplicitLod %23 %356 %355 ConstOffset %30 +%358 = OpLoad %23 %286 +%359 = OpFAdd %23 %358 %357 +OpStore %286 %359 +%360 = OpConvertSToF %7 %285 +%361 = OpCompositeConstruct %281 %280 %360 +%362 = OpSampledImage %317 %275 %277 +%363 = OpImageSampleExplicitLod %23 %362 %361 Lod %283 +%364 = OpLoad %23 %286 +%365 = OpFAdd %23 %364 %363 +OpStore %286 %365 +%366 = OpConvertSToF %7 %285 +%367 = OpCompositeConstruct %281 %280 %366 +%368 = OpSampledImage %317 %275 %277 +%369 = OpImageSampleExplicitLod %23 %368 %367 Lod|ConstOffset %283 %30 +%370 = OpLoad %23 %286 +%371 = OpFAdd %23 %370 %369 +OpStore %286 %371 +%372 = OpConvertSToF %7 %285 +%373 = OpCompositeConstruct %281 %280 %372 +%374 = OpSampledImage %317 %275 %277 +%375 = OpImageSampleImplicitLod %23 %374 %373 Bias|ConstOffset %284 %30 +%376 = OpLoad %23 %286 +%377 = OpFAdd %23 %376 %375 +OpStore %286 %377 +%379 = OpConvertUToF %7 %198 +%380 = OpCompositeConstruct %23 %282 %379 +%381 = OpSampledImage %378 %276 %277 +%382 = OpImageSampleImplicitLod %23 %381 %380 +%383 = OpLoad %23 %286 +%384 = OpFAdd %23 %383 %382 +OpStore %286 %384 +%385 = OpConvertUToF %7 %198 +%386 = OpCompositeConstruct %23 %282 %385 +%387 = OpSampledImage %378 %276 %277 +%388 = OpImageSampleExplicitLod %23 %387 %386 Lod %283 +%389 = OpLoad %23 %286 +%390 = OpFAdd %23 %389 %388 +OpStore %286 %390 +%391 = OpConvertUToF %7 %198 +%392 = OpCompositeConstruct %23 %282 %391 +%393 = OpSampledImage %378 %276 %277 +%394 = OpImageSampleImplicitLod %23 %393 %392 Bias %284 +%395 = OpLoad %23 %286 +%396 = OpFAdd %23 %395 %394 +OpStore %286 %396 +%397 = OpConvertSToF %7 %285 +%398 = OpCompositeConstruct %23 %282 %397 +%399 = OpSampledImage %378 %276 %277 +%400 = OpImageSampleImplicitLod %23 %399 %398 +%401 = OpLoad %23 %286 +%402 = OpFAdd %23 %401 %400 +OpStore %286 %402 +%403 = OpConvertSToF %7 %285 +%404 = OpCompositeConstruct %23 %282 %403 +%405 = OpSampledImage %378 %276 %277 +%406 = OpImageSampleExplicitLod %23 %405 %404 Lod %283 +%407 = OpLoad %23 %286 +%408 = OpFAdd %23 %407 %406 +OpStore %286 %408 +%409 = OpConvertSToF %7 %285 +%410 = OpCompositeConstruct %23 %282 %409 +%411 = OpSampledImage %378 %276 %277 +%412 = OpImageSampleImplicitLod %23 %411 %410 Bias %284 +%413 = OpLoad %23 %286 +%414 = OpFAdd %23 %413 %412 +OpStore %286 %414 +%415 = OpLoad %23 %286 +OpStore %271 %415 OpReturn OpFunctionEnd -%417 = OpFunction %2 None %79 -%414 = OpLabel -%422 = OpVariable %423 Function %424 -%418 = OpLoad %24 %66 -%419 = OpLoad %25 %68 -%420 = OpLoad %26 %70 -%421 = OpLoad %27 %72 -OpBranch %425 -%425 = OpLabel -%427 = OpSampledImage %426 %419 %418 -%428 = OpImageSampleDrefImplicitLod %7 %427 %278 %276 -%429 = OpLoad %7 %422 -%430 = OpFAdd %7 %429 %428 -OpStore %422 %430 -%432 = OpConvertUToF %7 %198 -%433 = OpCompositeConstruct %279 %278 %432 -%434 = OpSampledImage %431 %420 %418 -%435 = OpImageSampleDrefImplicitLod %7 %434 %433 %276 -%436 = OpLoad %7 %422 -%437 = OpFAdd %7 %436 %435 -OpStore %422 %437 -%438 = OpConvertSToF %7 %283 -%439 = OpCompositeConstruct %279 %278 %438 -%440 = OpSampledImage %431 %420 %418 -%441 = OpImageSampleDrefImplicitLod %7 %440 %439 %276 -%442 = OpLoad %7 %422 -%443 = OpFAdd %7 %442 %441 -OpStore %422 %443 -%445 = OpSampledImage %444 %421 %418 -%446 = OpImageSampleDrefImplicitLod %7 %445 %280 %276 -%447 = OpLoad %7 %422 -%448 = OpFAdd %7 %447 %446 -OpStore %422 %448 -%449 = OpSampledImage %426 %419 %418 -%450 = OpImageSampleDrefExplicitLod %7 %449 %278 %276 Lod %451 -%452 = OpLoad %7 %422 -%453 = OpFAdd %7 %452 %450 -OpStore %422 %453 -%454 = OpConvertUToF %7 %198 -%455 = OpCompositeConstruct %279 %278 %454 -%456 = OpSampledImage %431 %420 %418 -%457 = OpImageSampleDrefExplicitLod %7 %456 %455 %276 Lod %451 -%458 = OpLoad %7 %422 -%459 = OpFAdd %7 %458 %457 -OpStore %422 %459 -%460 = OpConvertSToF %7 %283 -%461 = OpCompositeConstruct %279 %278 %460 -%462 = OpSampledImage %431 %420 %418 -%463 = OpImageSampleDrefExplicitLod %7 %462 %461 %276 Lod %451 -%464 = OpLoad %7 %422 -%465 = OpFAdd %7 %464 %463 -OpStore %422 %465 -%466 = OpSampledImage %444 %421 %418 -%467 = OpImageSampleDrefExplicitLod %7 %466 %280 %276 Lod %451 -%468 = OpLoad %7 %422 -%469 = OpFAdd %7 %468 %467 -OpStore %422 %469 -%470 = OpLoad %7 %422 -OpStore %415 %470 +%419 = OpFunction %2 None %79 +%416 = OpLabel +%424 = OpVariable %425 Function %426 +%420 = OpLoad %24 %66 +%421 = OpLoad %25 %68 +%422 = OpLoad %26 %70 +%423 = OpLoad %27 %72 +OpBranch %427 +%427 = OpLabel +%429 = OpSampledImage %428 %421 %420 +%430 = OpImageSampleDrefImplicitLod %7 %429 %280 %278 +%431 = OpLoad %7 %424 +%432 = OpFAdd %7 %431 %430 +OpStore %424 %432 +%434 = OpConvertUToF %7 %198 +%435 = OpCompositeConstruct %281 %280 %434 +%436 = OpSampledImage %433 %422 %420 +%437 = OpImageSampleDrefImplicitLod %7 %436 %435 %278 +%438 = OpLoad %7 %424 +%439 = OpFAdd %7 %438 %437 +OpStore %424 %439 +%440 = OpConvertSToF %7 %285 +%441 = OpCompositeConstruct %281 %280 %440 +%442 = OpSampledImage %433 %422 %420 +%443 = OpImageSampleDrefImplicitLod %7 %442 %441 %278 +%444 = OpLoad %7 %424 +%445 = OpFAdd %7 %444 %443 +OpStore %424 %445 +%447 = OpSampledImage %446 %423 %420 +%448 = OpImageSampleDrefImplicitLod %7 %447 %282 %278 +%449 = OpLoad %7 %424 +%450 = OpFAdd %7 %449 %448 +OpStore %424 %450 +%451 = OpSampledImage %428 %421 %420 +%452 = OpImageSampleDrefExplicitLod %7 %451 %280 %278 Lod %453 +%454 = OpLoad %7 %424 +%455 = OpFAdd %7 %454 %452 +OpStore %424 %455 +%456 = OpConvertUToF %7 %198 +%457 = OpCompositeConstruct %281 %280 %456 +%458 = OpSampledImage %433 %422 %420 +%459 = OpImageSampleDrefExplicitLod %7 %458 %457 %278 Lod %453 +%460 = OpLoad %7 %424 +%461 = OpFAdd %7 %460 %459 +OpStore %424 %461 +%462 = OpConvertSToF %7 %285 +%463 = OpCompositeConstruct %281 %280 %462 +%464 = OpSampledImage %433 %422 %420 +%465 = OpImageSampleDrefExplicitLod %7 %464 %463 %278 Lod %453 +%466 = OpLoad %7 %424 +%467 = OpFAdd %7 %466 %465 +OpStore %424 %467 +%468 = OpSampledImage %446 %423 %420 +%469 = OpImageSampleDrefExplicitLod %7 %468 %282 %278 Lod %453 +%470 = OpLoad %7 %424 +%471 = OpFAdd %7 %470 %469 +OpStore %424 %471 +%472 = OpLoad %7 %424 +OpStore %417 %472 OpReturn OpFunctionEnd -%473 = OpFunction %2 None %79 -%471 = OpLabel -%474 = OpLoad %16 %49 -%475 = OpLoad %3 %51 -%476 = OpLoad %17 %52 -%477 = OpLoad %24 %64 -%478 = OpLoad %24 %66 -%479 = OpLoad %25 %68 -OpBranch %480 -%480 = OpLabel -%481 = OpSampledImage %294 %474 %477 -%482 = OpImageGather %23 %481 %278 %483 -%484 = OpSampledImage %294 %474 %477 -%485 = OpImageGather %23 %484 %278 %486 ConstOffset %30 -%487 = OpSampledImage %426 %479 %478 -%488 = OpImageDrefGather %23 %487 %278 %276 -%489 = OpSampledImage %426 %479 %478 -%490 = OpImageDrefGather %23 %489 %278 %276 ConstOffset %30 -%492 = OpSampledImage %491 %475 %477 -%493 = OpImageGather %98 %492 %278 %198 -%496 = OpSampledImage %495 %476 %477 -%497 = OpImageGather %494 %496 %278 %198 -%498 = OpConvertUToF %23 %493 -%499 = OpConvertSToF %23 %497 -%500 = OpFAdd %23 %498 %499 -%501 = OpFAdd %23 %482 %485 -%502 = OpFAdd %23 %501 %488 -%503 = OpFAdd %23 %502 %490 -%504 = OpFAdd %23 %503 %500 -OpStore %472 %504 +%475 = OpFunction %2 None %79 +%473 = OpLabel +%476 = OpLoad %16 %49 +%477 = OpLoad %3 %51 +%478 = OpLoad %17 %52 +%479 = OpLoad %24 %64 +%480 = OpLoad %24 %66 +%481 = OpLoad %25 %68 +OpBranch %482 +%482 = OpLabel +%483 = OpSampledImage %296 %476 %479 +%484 = OpImageGather %23 %483 %280 %485 +%486 = OpSampledImage %296 %476 %479 +%487 = OpImageGather %23 %486 %280 %488 ConstOffset %30 +%489 = OpSampledImage %428 %481 %480 +%490 = OpImageDrefGather %23 %489 %280 %278 +%491 = OpSampledImage %428 %481 %480 +%492 = OpImageDrefGather %23 %491 %280 %278 ConstOffset %30 +%494 = OpSampledImage %493 %477 %479 +%495 = OpImageGather %98 %494 %280 %198 +%498 = OpSampledImage %497 %478 %479 +%499 = OpImageGather %496 %498 %280 %198 +%500 = OpConvertUToF %23 %495 +%501 = OpConvertSToF %23 %499 +%502 = OpFAdd %23 %500 %501 +%503 = OpFAdd %23 %484 %487 +%504 = OpFAdd %23 %503 %490 +%505 = OpFAdd %23 %504 %492 +%506 = OpFAdd %23 %505 %502 +OpStore %474 %506 OpReturn OpFunctionEnd -%507 = OpFunction %2 None %79 -%505 = OpLabel -%508 = OpLoad %24 %64 -%509 = OpLoad %25 %68 -OpBranch %510 -%510 = OpLabel -%511 = OpSampledImage %426 %509 %508 -%512 = OpImageSampleImplicitLod %23 %511 %278 -%513 = OpCompositeExtract %7 %512 0 -%514 = OpSampledImage %426 %509 %508 -%515 = OpImageGather %23 %514 %278 %198 -%516 = OpCompositeConstruct %23 %513 %513 %513 %513 -%517 = OpFAdd %23 %516 %515 -OpStore %506 %517 +%509 = OpFunction %2 None %79 +%507 = OpLabel +%510 = OpLoad %24 %64 +%511 = OpLoad %25 %68 +OpBranch %512 +%512 = OpLabel +%513 = OpSampledImage %428 %511 %510 +%514 = OpImageSampleImplicitLod %23 %513 %280 +%515 = OpCompositeExtract %7 %514 0 +%516 = OpSampledImage %428 %511 %510 +%517 = OpImageGather %23 %516 %280 %198 +%518 = OpCompositeConstruct %23 %515 %515 %515 %515 +%519 = OpFAdd %23 %518 %517 +OpStore %508 %519 OpReturn OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/math-functions.spvasm b/naga/tests/out/spv/math-functions.spvasm index 47efe2a6f..cfdab1f73 100644 --- a/naga/tests/out/spv/math-functions.spvasm +++ b/naga/tests/out/spv/math-functions.spvasm @@ -1,12 +1,12 @@ ; SPIR-V ; Version: 1.1 ; Generator: rspirv -; Bound: 87 +; Bound: 95 OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %17 "main" -OpExecutionMode %17 OriginUpperLeft +OpEntryPoint Fragment %18 "main" +OpExecutionMode %18 OriginUpperLeft OpMemberDecorate %11 0 Offset 0 OpMemberDecorate %11 1 Offset 4 OpMemberDecorate %12 0 Offset 0 @@ -31,77 +31,85 @@ OpMemberDecorate %15 1 Offset 16 %13 = OpTypeStruct %4 %4 %14 = OpTypeStruct %3 %6 %15 = OpTypeStruct %4 %5 -%18 = OpTypeFunction %2 -%19 = OpConstant %3 1.0 -%20 = OpConstant %3 0.0 -%21 = OpConstantComposite %4 %20 %20 %20 %20 -%22 = OpConstant %6 -1 -%23 = OpConstantComposite %5 %22 %22 %22 %22 -%24 = OpConstant %3 -1.0 -%25 = OpConstantComposite %4 %24 %24 %24 %24 -%26 = OpConstantNull %7 -%27 = OpConstant %9 4294967295 -%28 = OpConstantComposite %7 %22 %22 -%29 = OpConstant %9 0 -%30 = OpConstantComposite %8 %29 %29 -%31 = OpConstant %6 0 -%32 = OpConstantComposite %7 %31 %31 -%33 = OpConstant %9 32 -%34 = OpConstant %6 32 -%35 = OpConstantComposite %8 %33 %33 -%36 = OpConstantComposite %7 %34 %34 -%37 = OpConstant %9 31 -%38 = OpConstantComposite %8 %37 %37 -%39 = OpConstant %6 2 -%40 = OpConstant %3 2.0 -%41 = OpConstantComposite %10 %19 %40 -%42 = OpConstant %6 3 -%43 = OpConstant %6 4 -%44 = OpConstantComposite %7 %42 %43 -%45 = OpConstant %3 1.5 -%46 = OpConstantComposite %10 %45 %45 -%47 = OpConstantComposite %4 %45 %45 %45 %45 -%54 = OpConstantComposite %4 %19 %19 %19 %19 -%57 = OpConstantNull %6 -%17 = OpFunction %2 None %18 -%16 = OpLabel -OpBranch %48 -%48 = OpLabel -%49 = OpExtInst %3 %1 Degrees %19 -%50 = OpExtInst %3 %1 Radians %19 -%51 = OpExtInst %4 %1 Degrees %21 -%52 = OpExtInst %4 %1 Radians %21 -%53 = OpExtInst %4 %1 FClamp %21 %21 %54 -%55 = OpExtInst %4 %1 Refract %21 %21 %19 -%58 = OpCompositeExtract %6 %26 0 -%59 = OpCompositeExtract %6 %26 0 -%60 = OpIMul %6 %58 %59 -%61 = OpIAdd %6 %57 %60 -%62 = OpCompositeExtract %6 %26 1 -%63 = OpCompositeExtract %6 %26 1 +%16 = OpTypeVector %3 3 +%19 = OpTypeFunction %2 +%20 = OpConstant %3 1.0 +%21 = OpConstant %3 0.0 +%22 = OpConstantComposite %4 %21 %21 %21 %21 +%23 = OpConstant %6 -1 +%24 = OpConstantComposite %5 %23 %23 %23 %23 +%25 = OpConstant %3 -1.0 +%26 = OpConstantComposite %4 %25 %25 %25 %25 +%27 = OpConstantNull %7 +%28 = OpConstant %9 4294967295 +%29 = OpConstantComposite %7 %23 %23 +%30 = OpConstant %9 0 +%31 = OpConstantComposite %8 %30 %30 +%32 = OpConstant %6 0 +%33 = OpConstantComposite %7 %32 %32 +%34 = OpConstant %9 32 +%35 = OpConstant %6 32 +%36 = OpConstantComposite %8 %34 %34 +%37 = OpConstantComposite %7 %35 %35 +%38 = OpConstant %9 31 +%39 = OpConstantComposite %8 %38 %38 +%40 = OpConstant %6 2 +%41 = OpConstant %3 2.0 +%42 = OpConstantComposite %10 %20 %41 +%43 = OpConstant %6 3 +%44 = OpConstant %6 4 +%45 = OpConstantComposite %7 %43 %44 +%46 = OpConstant %3 1.5 +%47 = OpConstantComposite %10 %46 %46 +%48 = OpConstantComposite %4 %46 %46 %46 %46 +%49 = OpConstantComposite %10 %20 %20 +%50 = OpConstantComposite %16 %20 %20 %20 +%51 = OpConstantComposite %4 %20 %20 %20 %20 +%58 = OpConstantComposite %4 %20 %20 %20 %20 +%61 = OpConstantNull %6 +%18 = OpFunction %2 None %19 +%17 = OpLabel +OpBranch %52 +%52 = OpLabel +%53 = OpExtInst %3 %1 Degrees %20 +%54 = OpExtInst %3 %1 Radians %20 +%55 = OpExtInst %4 %1 Degrees %22 +%56 = OpExtInst %4 %1 Radians %22 +%57 = OpExtInst %4 %1 FClamp %22 %22 %58 +%59 = OpExtInst %4 %1 Refract %22 %22 %20 +%62 = OpCompositeExtract %6 %27 0 +%63 = OpCompositeExtract %6 %27 0 %64 = OpIMul %6 %62 %63 -%56 = OpIAdd %6 %61 %64 -%65 = OpExtInst %3 %1 Ldexp %19 %39 -%66 = OpExtInst %10 %1 Ldexp %41 %44 -%67 = OpExtInst %11 %1 ModfStruct %45 -%68 = OpExtInst %11 %1 ModfStruct %45 -%69 = OpCompositeExtract %3 %68 0 -%70 = OpExtInst %11 %1 ModfStruct %45 -%71 = OpCompositeExtract %3 %70 1 -%72 = OpExtInst %12 %1 ModfStruct %46 -%73 = OpExtInst %13 %1 ModfStruct %47 -%74 = OpCompositeExtract %4 %73 1 -%75 = OpCompositeExtract %3 %74 0 -%76 = OpExtInst %12 %1 ModfStruct %46 -%77 = OpCompositeExtract %10 %76 0 -%78 = OpCompositeExtract %3 %77 1 -%79 = OpExtInst %14 %1 FrexpStruct %45 -%80 = OpExtInst %14 %1 FrexpStruct %45 -%81 = OpCompositeExtract %3 %80 0 -%82 = OpExtInst %14 %1 FrexpStruct %45 -%83 = OpCompositeExtract %6 %82 1 -%84 = OpExtInst %15 %1 FrexpStruct %47 -%85 = OpCompositeExtract %5 %84 1 -%86 = OpCompositeExtract %6 %85 0 +%65 = OpIAdd %6 %61 %64 +%66 = OpCompositeExtract %6 %27 1 +%67 = OpCompositeExtract %6 %27 1 +%68 = OpIMul %6 %66 %67 +%60 = OpIAdd %6 %65 %68 +%69 = OpExtInst %3 %1 Ldexp %20 %40 +%70 = OpExtInst %10 %1 Ldexp %42 %45 +%71 = OpExtInst %11 %1 ModfStruct %46 +%72 = OpExtInst %11 %1 ModfStruct %46 +%73 = OpCompositeExtract %3 %72 0 +%74 = OpExtInst %11 %1 ModfStruct %46 +%75 = OpCompositeExtract %3 %74 1 +%76 = OpExtInst %12 %1 ModfStruct %47 +%77 = OpExtInst %13 %1 ModfStruct %48 +%78 = OpCompositeExtract %4 %77 1 +%79 = OpCompositeExtract %3 %78 0 +%80 = OpExtInst %12 %1 ModfStruct %47 +%81 = OpCompositeExtract %10 %80 0 +%82 = OpCompositeExtract %3 %81 1 +%83 = OpExtInst %14 %1 FrexpStruct %46 +%84 = OpExtInst %14 %1 FrexpStruct %46 +%85 = OpCompositeExtract %3 %84 0 +%86 = OpExtInst %14 %1 FrexpStruct %46 +%87 = OpCompositeExtract %6 %86 1 +%88 = OpExtInst %15 %1 FrexpStruct %48 +%89 = OpCompositeExtract %5 %88 1 +%90 = OpCompositeExtract %6 %89 0 +%91 = OpQuantizeToF16 %3 %20 +%92 = OpQuantizeToF16 %10 %49 +%93 = OpQuantizeToF16 %16 %50 +%94 = OpQuantizeToF16 %4 %51 OpReturn OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/wgsl/image.wgsl b/naga/tests/out/wgsl/image.wgsl index a680e70ab..dddc79404 100644 --- a/naga/tests/out/wgsl/image.wgsl +++ b/naga/tests/out/wgsl/image.wgsl @@ -95,8 +95,9 @@ fn queries() -> @builtin(position) vec4 { @vertex fn levels_queries() -> @builtin(position) vec4 { let num_levels_2d = textureNumLevels(image_2d); - let num_levels_2d_array = textureNumLevels(image_2d_array); let num_layers_2d = textureNumLayers(image_2d_array); + let num_levels_2d_array = textureNumLevels(image_2d_array); + let num_layers_2d_array = textureNumLayers(image_2d_array); let num_levels_cube = textureNumLevels(image_cube); let num_levels_cube_array = textureNumLevels(image_cube_array); let num_layers_cube = textureNumLayers(image_cube_array); diff --git a/naga/tests/out/wgsl/math-functions.wgsl b/naga/tests/out/wgsl/math-functions.wgsl index 732f7acdc..f48a5dd02 100644 --- a/naga/tests/out/wgsl/math-functions.wgsl +++ b/naga/tests/out/wgsl/math-functions.wgsl @@ -32,4 +32,8 @@ fn main() { let frexp_b = frexp(1.5f).fract; let frexp_c = frexp(1.5f).exp; let frexp_d = frexp(vec4(1.5f, 1.5f, 1.5f, 1.5f)).exp.x; + let quantizeToF16_a = quantizeToF16(1f); + let quantizeToF16_b = quantizeToF16(vec2(1f, 1f)); + let quantizeToF16_c = quantizeToF16(vec3(1f, 1f, 1f)); + let quantizeToF16_d = quantizeToF16(vec4(1f, 1f, 1f, 1f)); } diff --git a/naga/tests/snapshots.rs b/naga/tests/snapshots.rs index b69646f59..3c0d64e16 100644 --- a/naga/tests/snapshots.rs +++ b/naga/tests/snapshots.rs @@ -945,6 +945,7 @@ fn convert_wgsl() { "6438-conflicting-idents", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), + ("diagnostic-filter", Targets::IR), ]; for &(name, targets) in inputs.iter() { diff --git a/naga/tests/validation.rs b/naga/tests/validation.rs index b8a30c49b..df8fad2ac 100644 --- a/naga/tests/validation.rs +++ b/naga/tests/validation.rs @@ -606,3 +606,40 @@ fn binding_arrays_cannot_hold_scalars() { assert!(t.validator.validate(&t.module).is_err()); } + +#[cfg(feature = "wgsl-in")] +#[test] +fn validation_error_messages() { + let cases = [( + r#"@group(0) @binding(0) var my_sampler: sampler; + + fn foo(tex: texture_2d) -> vec4 { + return textureSampleLevel(tex, my_sampler, vec2f(0, 0), 0.0); + } + + fn main() { + foo(); + } + "#, + "\ +error: Function [1] 'main' is invalid + ┌─ wgsl:7:17 + │ \n7 │ ╭ fn main() { +8 │ │ foo(); + │ │ ^^^^ invalid function call + │ ╰──────────────────────────^ naga::Function [1] + │ \n = Call to [0] is invalid + = Requires 1 arguments, but 0 are provided + +", + )]; + + for (source, expected_err) in cases { + let module = naga::front::wgsl::parse_str(source).unwrap(); + let err = valid::Validator::new(Default::default(), valid::Capabilities::all()) + .validate_no_overrides(&module) + .expect_err("module should be invalid"); + println!("{}", err.emit_to_string(source)); + assert_eq!(err.emit_to_string(source), expected_err); + } +} diff --git a/player/src/lib.rs b/player/src/lib.rs index 3b0b4149a..af82168ae 100644 --- a/player/src/lib.rs +++ b/player/src/lib.rs @@ -119,6 +119,94 @@ impl GlobalPlay for wgc::global::Global { ) .unwrap(); } + trace::Command::BuildAccelerationStructuresUnsafeTlas { blas, tlas } => { + let blas_iter = blas.iter().map(|x| { + let geometries = match &x.geometries { + wgc::ray_tracing::TraceBlasGeometries::TriangleGeometries( + triangle_geometries, + ) => { + let iter = triangle_geometries.iter().map(|tg| { + wgc::ray_tracing::BlasTriangleGeometry { + size: &tg.size, + vertex_buffer: tg.vertex_buffer, + index_buffer: tg.index_buffer, + transform_buffer: tg.transform_buffer, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + } + }); + wgc::ray_tracing::BlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + wgc::ray_tracing::BlasBuildEntry { + blas_id: x.blas_id, + geometries, + } + }); + + if !tlas.is_empty() { + log::error!("a trace of command_encoder_build_acceleration_structures_unsafe_tlas containing a tlas build is not replayable! skipping tlas build"); + } + + self.command_encoder_build_acceleration_structures_unsafe_tlas( + encoder, + blas_iter, + std::iter::empty(), + ) + .unwrap(); + } + trace::Command::BuildAccelerationStructures { blas, tlas } => { + let blas_iter = blas.iter().map(|x| { + let geometries = match &x.geometries { + wgc::ray_tracing::TraceBlasGeometries::TriangleGeometries( + triangle_geometries, + ) => { + let iter = triangle_geometries.iter().map(|tg| { + wgc::ray_tracing::BlasTriangleGeometry { + size: &tg.size, + vertex_buffer: tg.vertex_buffer, + index_buffer: tg.index_buffer, + transform_buffer: tg.transform_buffer, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + } + }); + wgc::ray_tracing::BlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + wgc::ray_tracing::BlasBuildEntry { + blas_id: x.blas_id, + geometries, + } + }); + + let tlas_iter = tlas.iter().map(|x| { + let instances = x.instances.iter().map(|instance| { + instance + .as_ref() + .map(|instance| wgc::ray_tracing::TlasInstance { + blas_id: instance.blas_id, + transform: &instance.transform, + custom_index: instance.custom_index, + mask: instance.mask, + }) + }); + wgc::ray_tracing::TlasPackage { + tlas_id: x.tlas_id, + instances: Box::new(instances), + lowest_unmodified: x.lowest_unmodified, + } + }); + + self.command_encoder_build_acceleration_structures( + encoder, blas_iter, tlas_iter, + ) + .unwrap(); + } } } let (cmd_buf, error) = @@ -360,6 +448,24 @@ impl GlobalPlay for wgc::global::Global { let cmdbuf = self.encode_commands(encoder, commands); self.queue_submit(queue, &[cmdbuf]).unwrap(); } + Action::CreateBlas { id, desc, sizes } => { + self.device_create_blas(device, &desc, sizes, Some(id)); + } + Action::FreeBlas(id) => { + self.blas_destroy(id).unwrap(); + } + Action::DestroyBlas(id) => { + self.blas_drop(id); + } + Action::CreateTlas { id, desc } => { + self.device_create_tlas(device, &desc, Some(id)); + } + Action::FreeTlas(id) => { + self.tlas_destroy(id).unwrap(); + } + Action::DestroyTlas(id) => { + self.tlas_drop(id); + } } } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 63776a174..a9663a3f7 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -40,6 +40,7 @@ strum = { workspace = true, features = ["derive"] } wgpu-macros.workspace = true wgpu = { workspace = true, features = ["wgsl"] } wgt = { workspace = true, features = ["serde"] } +glam.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] env_logger.workspace = true diff --git a/tests/tests/buffer_usages.rs b/tests/tests/buffer_usages.rs index e0cadeed8..00d63adae 100644 --- a/tests/tests/buffer_usages.rs +++ b/tests/tests/buffer_usages.rs @@ -21,7 +21,8 @@ const NEEDS_MAPPABLE_PRIMARY_BUFFERS: &[Bu; 7] = &[ Bu::MAP_WRITE.union(Bu::MAP_READ), Bu::MAP_READ.union(Bu::COPY_DST.union(Bu::STORAGE)), Bu::MAP_WRITE.union(Bu::COPY_SRC.union(Bu::STORAGE)), - Bu::all(), + // these two require acceleration_structures feature + Bu::all().intersection(Bu::BLAS_INPUT.union(Bu::TLAS_INPUT).complement()), ]; const INVALID_BITS: Bu = Bu::from_bits_retain(0b1111111111111); const ALWAYS_FAIL: &[Bu; 2] = &[Bu::empty(), INVALID_BITS]; diff --git a/tests/tests/ray_tracing/as_build.rs b/tests/tests/ray_tracing/as_build.rs new file mode 100644 index 000000000..5a030a5d5 --- /dev/null +++ b/tests/tests/ray_tracing/as_build.rs @@ -0,0 +1,285 @@ +use std::mem; + +use wgpu::{ + util::{BufferInitDescriptor, DeviceExt}, + *, +}; +use wgpu_test::{fail, gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +struct AsBuildContext { + vertices: Buffer, + blas_size: BlasTriangleGeometrySizeDescriptor, + blas: Blas, + // Putting this last, forces the BLAS to die before the TLAS. + tlas_package: TlasPackage, +} + +impl AsBuildContext { + fn new(ctx: &TestingContext) -> Self { + let vertices = ctx.device.create_buffer_init(&BufferInitDescriptor { + label: None, + contents: &[0; mem::size_of::<[[f32; 3]; 3]>()], + usage: BufferUsages::BLAS_INPUT, + }); + + let blas_size = BlasTriangleGeometrySizeDescriptor { + vertex_format: VertexFormat::Float32x3, + vertex_count: 3, + index_format: None, + index_count: None, + flags: AccelerationStructureGeometryFlags::empty(), + }; + + let blas = ctx.device.create_blas( + &CreateBlasDescriptor { + label: Some("BLAS"), + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }, + BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_size.clone()], + }, + ); + + let tlas = ctx.device.create_tlas(&CreateTlasDescriptor { + label: Some("TLAS"), + max_instances: 1, + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }); + + let mut tlas_package = TlasPackage::new(tlas); + tlas_package[0] = Some(TlasInstance::new( + &blas, + [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + 0, + 0xFF, + )); + + Self { + vertices, + blas_size, + blas, + tlas_package, + } + } + + fn blas_build_entry(&self) -> BlasBuildEntry { + BlasBuildEntry { + blas: &self.blas, + geometry: BlasGeometries::TriangleGeometries(vec![BlasTriangleGeometry { + size: &self.blas_size, + vertex_buffer: &self.vertices, + first_vertex: 0, + vertex_stride: mem::size_of::<[f32; 3]>() as BufferAddress, + index_buffer: None, + index_buffer_offset: None, + transform_buffer: None, + transform_buffer_offset: None, + }]), + } + } +} + +#[gpu_test] +static UNBUILT_BLAS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + ) + .run_sync(unbuilt_blas); + +fn unbuilt_blas(ctx: TestingContext) { + let as_ctx = AsBuildContext::new(&ctx); + + // Build the TLAS package with an unbuilt BLAS. + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()); + + encoder.build_acceleration_structures([], [&as_ctx.tlas_package]); + + fail( + &ctx.device, + || { + ctx.queue.submit([encoder.finish()]); + }, + None, + ); +} + +#[gpu_test] +static OUT_OF_ORDER_AS_BUILD: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + ) + .run_sync(out_of_order_as_build); + +fn out_of_order_as_build(ctx: TestingContext) { + let as_ctx = AsBuildContext::new(&ctx); + + // + // Encode the TLAS build before the BLAS build, but submit them in the right order. + // + + let mut encoder_tlas = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("TLAS 1"), + }); + + encoder_tlas.build_acceleration_structures([], [&as_ctx.tlas_package]); + + let mut encoder_blas = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("BLAS 1"), + }); + + encoder_blas.build_acceleration_structures([&as_ctx.blas_build_entry()], []); + + ctx.queue + .submit([encoder_blas.finish(), encoder_tlas.finish()]); + + drop(as_ctx); + + // + // Create a clean `AsBuildContext` + // + + let as_ctx = AsBuildContext::new(&ctx); + + // + // Encode the BLAS build before the TLAS build, but submit them in the wrong order. + // + + let mut encoder_blas = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("BLAS 2"), + }); + + encoder_blas.build_acceleration_structures([&as_ctx.blas_build_entry()], []); + + let mut encoder_tlas = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("TLAS 2"), + }); + + encoder_tlas.build_acceleration_structures([], [&as_ctx.tlas_package]); + + fail( + &ctx.device, + || { + ctx.queue + .submit([encoder_tlas.finish(), encoder_blas.finish()]); + }, + None, + ); +} + +#[gpu_test] +static OUT_OF_ORDER_AS_BUILD_USE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().test_features_limits().features( + wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + | wgpu::Features::EXPERIMENTAL_RAY_QUERY, + )) + .run_sync(out_of_order_as_build_use); + +fn out_of_order_as_build_use(ctx: TestingContext) { + // + // Create a clean `AsBuildContext` + // + + let as_ctx = AsBuildContext::new(&ctx); + + // + // Build in the right order, then rebuild the BLAS so the TLAS is invalid, then use the TLAS. + // + + let mut encoder_blas = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("BLAS 1"), + }); + + encoder_blas.build_acceleration_structures([&as_ctx.blas_build_entry()], []); + + let mut encoder_tlas = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("TLAS 1"), + }); + + encoder_tlas.build_acceleration_structures([], [&as_ctx.tlas_package]); + + let mut encoder_blas2 = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("BLAS 2"), + }); + + encoder_blas2.build_acceleration_structures([&as_ctx.blas_build_entry()], []); + + ctx.queue.submit([ + encoder_blas.finish(), + encoder_tlas.finish(), + encoder_blas2.finish(), + ]); + + // + // Create shader to use tlas with + // + + let shader = ctx + .device + .create_shader_module(include_wgsl!("shader.wgsl")); + let compute_pipeline = ctx + .device + .create_compute_pipeline(&ComputePipelineDescriptor { + label: None, + layout: None, + module: &shader, + entry_point: Some("comp_main"), + compilation_options: Default::default(), + cache: None, + }); + + let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &compute_pipeline.get_bind_group_layout(0), + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::AccelerationStructure(as_ctx.tlas_package.tlas()), + }], + }); + + // + // Use TLAS + // + + let mut encoder_compute = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()); + { + let mut pass = encoder_compute.begin_compute_pass(&ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + pass.set_pipeline(&compute_pipeline); + pass.set_bind_group(0, Some(&bind_group), &[]); + pass.dispatch_workgroups(1, 1, 1) + } + + fail( + &ctx.device, + || { + ctx.queue.submit(Some(encoder_compute.finish())); + }, + None, + ); +} diff --git a/tests/tests/ray_tracing/as_create.rs b/tests/tests/ray_tracing/as_create.rs new file mode 100644 index 000000000..617852333 --- /dev/null +++ b/tests/tests/ray_tracing/as_create.rs @@ -0,0 +1,117 @@ +use wgpu::{ + AccelerationStructureFlags, AccelerationStructureGeometryFlags, + AccelerationStructureUpdateMode, BlasGeometrySizeDescriptors, + BlasTriangleGeometrySizeDescriptor, CreateBlasDescriptor, +}; +use wgpu_macros::gpu_test; +use wgpu_test::{fail, GpuTestConfiguration, TestParameters, TestingContext}; +use wgt::{IndexFormat, VertexFormat}; + +#[gpu_test] +static BLAS_INVALID_VERTEX_FORMAT: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + ) + .run_sync(invalid_vertex_format_blas_create); + +fn invalid_vertex_format_blas_create(ctx: TestingContext) { + // + // Create a BLAS with a format that is not allowed + // + + let blas_size = BlasTriangleGeometrySizeDescriptor { + vertex_format: VertexFormat::Float32x4, + vertex_count: 3, + index_format: None, + index_count: None, + flags: AccelerationStructureGeometryFlags::empty(), + }; + + fail( + &ctx.device, + || { + let _ = ctx.device.create_blas( + &CreateBlasDescriptor { + label: Some("BLAS"), + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }, + BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_size.clone()], + }, + ); + }, + None, + ); +} + +#[gpu_test] +static BLAS_MISMATCHED_INDEX: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + ) + .run_sync(mismatched_index_blas_create); + +fn mismatched_index_blas_create(ctx: TestingContext) { + // + // Create a BLAS with just an index format + // + + let blas_size = BlasTriangleGeometrySizeDescriptor { + vertex_format: VertexFormat::Float32x3, + vertex_count: 3, + index_format: Some(IndexFormat::Uint32), + index_count: None, + flags: AccelerationStructureGeometryFlags::empty(), + }; + + fail( + &ctx.device, + || { + let _ = ctx.device.create_blas( + &CreateBlasDescriptor { + label: Some("BLAS1"), + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }, + BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_size.clone()], + }, + ); + }, + None, + ); + + // + // Create a BLAS with just an index count + // + + let blas_size = BlasTriangleGeometrySizeDescriptor { + vertex_format: VertexFormat::Float32x3, + vertex_count: 3, + index_format: None, + index_count: Some(3), + flags: AccelerationStructureGeometryFlags::empty(), + }; + + fail( + &ctx.device, + || { + let _ = ctx.device.create_blas( + &CreateBlasDescriptor { + label: Some("BLAS2"), + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }, + BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_size.clone()], + }, + ); + }, + None, + ); +} diff --git a/tests/tests/ray_tracing/as_use_after_free.rs b/tests/tests/ray_tracing/as_use_after_free.rs new file mode 100644 index 000000000..c0df9d385 --- /dev/null +++ b/tests/tests/ray_tracing/as_use_after_free.rs @@ -0,0 +1,152 @@ +use std::{iter, mem}; +use wgpu::{ + include_wgsl, + util::{BufferInitDescriptor, DeviceExt}, + AccelerationStructureFlags, AccelerationStructureGeometryFlags, + AccelerationStructureUpdateMode, BindGroupDescriptor, BindGroupEntry, BindingResource, + BlasBuildEntry, BlasGeometries, BlasGeometrySizeDescriptors, BlasTriangleGeometry, + BlasTriangleGeometrySizeDescriptor, BufferAddress, BufferUsages, CommandEncoderDescriptor, + ComputePassDescriptor, ComputePipelineDescriptor, CreateBlasDescriptor, CreateTlasDescriptor, + Maintain, TlasInstance, TlasPackage, VertexFormat, +}; +use wgpu_macros::gpu_test; +use wgpu_test::{GpuTestConfiguration, TestParameters, TestingContext}; + +fn required_features() -> wgpu::Features { + wgpu::Features::EXPERIMENTAL_RAY_QUERY + | wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE +} + +/// This test creates a blas, puts a reference to it in a tlas instance inside a tlas package, +/// drops the blas, and ensures it gets kept alive by the tlas instance. Then it uses the built +/// package in a bindgroup, drops it, and checks that it is kept alive by the bindgroup by +/// executing a shader using that bindgroup. +fn acceleration_structure_use_after_free(ctx: TestingContext) { + // Dummy vertex buffer. + let vertices = ctx.device.create_buffer_init(&BufferInitDescriptor { + label: None, + contents: &[0; mem::size_of::<[[f32; 3]; 3]>()], + usage: BufferUsages::BLAS_INPUT, + }); + + // Create a BLAS with a single triangle. + let blas_size = BlasTriangleGeometrySizeDescriptor { + vertex_format: VertexFormat::Float32x3, + vertex_count: 3, + index_format: None, + index_count: None, + flags: AccelerationStructureGeometryFlags::empty(), + }; + + let blas = ctx.device.create_blas( + &CreateBlasDescriptor { + label: Some("blas use after free"), + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }, + BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_size.clone()], + }, + ); + // Create the TLAS + let tlas = ctx.device.create_tlas(&CreateTlasDescriptor { + label: Some("tlas use after free"), + max_instances: 1, + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }); + + // Put an unbuilt BLAS in the tlas package. + let mut tlas_package = TlasPackage::new(tlas); + tlas_package[0] = Some(TlasInstance::new( + &blas, + [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + 0, + 0xFF, + )); + + // Actually build the BLAS. + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()); + encoder.build_acceleration_structures( + iter::once(&BlasBuildEntry { + blas: &blas, + geometry: BlasGeometries::TriangleGeometries(vec![BlasTriangleGeometry { + size: &blas_size, + vertex_buffer: &vertices, + first_vertex: 0, + vertex_stride: mem::size_of::<[f32; 3]>() as BufferAddress, + index_buffer: None, + index_buffer_offset: None, + transform_buffer: None, + transform_buffer_offset: None, + }]), + }), + iter::empty(), + ); + ctx.queue.submit(Some(encoder.finish())); + + // Drop the blas and ensure that if it was going to die, it is dead. + drop(blas); + ctx.device.poll(Maintain::Wait); + + // build the tlas package to ensure the blas is dropped + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()); + encoder.build_acceleration_structures(iter::empty(), iter::once(&tlas_package)); + ctx.queue.submit(Some(encoder.finish())); + + // Create a compute shader that uses an AS. + let shader = ctx + .device + .create_shader_module(include_wgsl!("shader.wgsl")); + let compute_pipeline = ctx + .device + .create_compute_pipeline(&ComputePipelineDescriptor { + label: None, + layout: None, + module: &shader, + entry_point: Some("comp_main"), + compilation_options: Default::default(), + cache: None, + }); + + let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &compute_pipeline.get_bind_group_layout(0), + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::AccelerationStructure(tlas_package.tlas()), + }], + }); + + // Drop the TLAS package and ensure that if it was going to die, it is dead. + drop(tlas_package); + ctx.device.poll(Maintain::Wait); + + // Run the pass with the bind group that references the TLAS package. + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()); + { + let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + pass.set_pipeline(&compute_pipeline); + pass.set_bind_group(0, Some(&bind_group), &[]); + pass.dispatch_workgroups(1, 1, 1) + } + ctx.queue.submit(Some(encoder.finish())); +} + +#[gpu_test] +static ACCELERATION_STRUCTURE_USE_AFTER_FREE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(required_features()), + ) + .run_sync(acceleration_structure_use_after_free); diff --git a/tests/tests/ray_tracing/mod.rs b/tests/tests/ray_tracing/mod.rs new file mode 100644 index 000000000..e204392d2 --- /dev/null +++ b/tests/tests/ray_tracing/mod.rs @@ -0,0 +1,4 @@ +mod as_build; +mod as_create; +mod as_use_after_free; +mod scene; diff --git a/tests/tests/ray_tracing/scene/mesh_gen.rs b/tests/tests/ray_tracing/scene/mesh_gen.rs new file mode 100644 index 000000000..52012bb92 --- /dev/null +++ b/tests/tests/ray_tracing/scene/mesh_gen.rs @@ -0,0 +1,83 @@ +use bytemuck::{Pod, Zeroable}; +use glam::Affine3A; + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +pub struct Vertex { + _pos: [f32; 4], + _tex_coord: [f32; 2], +} + +fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex { + Vertex { + _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0], + _tex_coord: [tc[0] as f32, tc[1] as f32], + } +} + +pub fn create_vertices() -> (Vec, Vec) { + let vertex_data = [ + // top (0, 0, 1) + vertex([-1, -1, 1], [0, 0]), + vertex([1, -1, 1], [1, 0]), + vertex([1, 1, 1], [1, 1]), + vertex([-1, 1, 1], [0, 1]), + // bottom (0, 0, -1) + vertex([-1, 1, -1], [1, 0]), + vertex([1, 1, -1], [0, 0]), + vertex([1, -1, -1], [0, 1]), + vertex([-1, -1, -1], [1, 1]), + // right (1, 0, 0) + vertex([1, -1, -1], [0, 0]), + vertex([1, 1, -1], [1, 0]), + vertex([1, 1, 1], [1, 1]), + vertex([1, -1, 1], [0, 1]), + // left (-1, 0, 0) + vertex([-1, -1, 1], [1, 0]), + vertex([-1, 1, 1], [0, 0]), + vertex([-1, 1, -1], [0, 1]), + vertex([-1, -1, -1], [1, 1]), + // front (0, 1, 0) + vertex([1, 1, -1], [1, 0]), + vertex([-1, 1, -1], [0, 0]), + vertex([-1, 1, 1], [0, 1]), + vertex([1, 1, 1], [1, 1]), + // back (0, -1, 0) + vertex([1, -1, 1], [0, 0]), + vertex([-1, -1, 1], [1, 0]), + vertex([-1, -1, -1], [1, 1]), + vertex([1, -1, -1], [0, 1]), + ]; + + let index_data: &[u16] = &[ + 0, 1, 2, 2, 3, 0, // top + 4, 5, 6, 6, 7, 4, // bottom + 8, 9, 10, 10, 11, 8, // right + 12, 13, 14, 14, 15, 12, // left + 16, 17, 18, 18, 19, 16, // front + 20, 21, 22, 22, 23, 20, // back + ]; + + (vertex_data.to_vec(), index_data.to_vec()) +} + +pub fn affine_to_rows(mat: &Affine3A) -> [f32; 12] { + let row_0 = mat.matrix3.row(0); + let row_1 = mat.matrix3.row(1); + let row_2 = mat.matrix3.row(2); + let translation = mat.translation; + [ + row_0.x, + row_0.y, + row_0.z, + translation.x, + row_1.x, + row_1.y, + row_1.z, + translation.y, + row_2.x, + row_2.y, + row_2.z, + translation.z, + ] +} diff --git a/tests/tests/ray_tracing/scene/mod.rs b/tests/tests/ray_tracing/scene/mod.rs new file mode 100644 index 000000000..20cc321d4 --- /dev/null +++ b/tests/tests/ray_tracing/scene/mod.rs @@ -0,0 +1,121 @@ +use std::{iter, mem}; + +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +use wgpu::util::DeviceExt; + +use glam::{Affine3A, Quat, Vec3}; + +mod mesh_gen; + +fn acceleration_structure_build(ctx: &TestingContext, use_index_buffer: bool) { + let max_instances = 1000; + let device = &ctx.device; + + let (vertex_data, index_data) = mesh_gen::create_vertices(); + + let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertex_data), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&index_data), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let index_format = wgpu::IndexFormat::Uint16; + let index_count = index_data.len() as u32; + + let blas_geo_size_desc = wgpu::BlasTriangleGeometrySizeDescriptor { + vertex_format: wgpu::VertexFormat::Float32x3, + vertex_count: vertex_data.len() as u32, + index_format: use_index_buffer.then_some(index_format), + index_count: use_index_buffer.then_some(index_count), + flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, + }; + + let blas = device.create_blas( + &wgpu::CreateBlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + }, + wgpu::BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_geo_size_desc.clone()], + }, + ); + + let tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { + label: None, + flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: wgpu::AccelerationStructureUpdateMode::Build, + max_instances, + }); + + let mut tlas_package = wgpu::TlasPackage::new(tlas); + + for j in 0..max_instances { + tlas_package[j as usize] = Some(wgpu::TlasInstance::new( + &blas, + mesh_gen::affine_to_rows(&Affine3A::from_rotation_translation( + Quat::from_rotation_y(45.9_f32.to_radians()), + Vec3 { + x: j as f32, + y: 0.0, + z: 0.0, + }, + )), + 0, + 0xff, + )); + } + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures( + iter::once(&wgpu::BlasBuildEntry { + blas: &blas, + geometry: wgpu::BlasGeometries::TriangleGeometries(vec![wgpu::BlasTriangleGeometry { + size: &blas_geo_size_desc, + vertex_buffer: &vertex_buf, + first_vertex: 0, + vertex_stride: mem::size_of::() as u64, + index_buffer: use_index_buffer.then_some(&index_buffer), + index_buffer_offset: use_index_buffer.then_some(0), + transform_buffer: None, + transform_buffer_offset: None, + }]), + }), + iter::once(&tlas_package), + ); + + ctx.queue.submit(Some(encoder.finish())); + + ctx.device.poll(wgpu::Maintain::Wait); +} + +#[gpu_test] +static ACCELERATION_STRUCTURE_BUILD_NO_INDEX: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + ) + .run_sync(|ctx| { + acceleration_structure_build(&ctx, false); + }); + +#[gpu_test] +static ACCELERATION_STRUCTURE_BUILD_WITH_INDEX: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + ) + .run_sync(|ctx| { + acceleration_structure_build(&ctx, true); + }); diff --git a/tests/tests/ray_tracing/shader.wgsl b/tests/tests/ray_tracing/shader.wgsl new file mode 100644 index 000000000..370d69e1c --- /dev/null +++ b/tests/tests/ray_tracing/shader.wgsl @@ -0,0 +1,11 @@ +@group(0) @binding(0) +var acc_struct: acceleration_structure; + +@workgroup_size(1) +@compute +fn comp_main() { + var rq: ray_query; + rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.001, 100000.0, vec3f(0.0, 0.0, 0.0), vec3f(0.0, 0.0, 1.0))); + rayQueryProceed(&rq); + let intersection = rayQueryGetCommittedIntersection(&rq); +} \ No newline at end of file diff --git a/tests/tests/regression/issue_6467.rs b/tests/tests/regression/issue_6467.rs new file mode 100644 index 000000000..da40a791d --- /dev/null +++ b/tests/tests/regression/issue_6467.rs @@ -0,0 +1,75 @@ +use wgpu::util::DeviceExt; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; + +/// Running a compute shader with one or more of the workgroup sizes set to 0 implies that no work +/// should be done, and is a user error. Vulkan and DX12 accept this invalid input with grace, but +/// Metal does not guard against this and eventually the machine will crash. Since this is a public +/// API that may be given untrusted values in a browser, this must be protected again. +/// +/// The following test should successfully do nothing on all platforms. +#[gpu_test] +static ZERO_WORKGROUP_SIZE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().limits(wgpu::Limits::default())) + .run_async(|ctx| async move { + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + " + @group(0) + @binding(0) + var vals: array; + + @compute + @workgroup_size(1) + fn main(@builtin(global_invocation_id) id: vec3u) { + vals[id.x] = vals[id.x] * i32(id.x); + } + ", + )), + }); + let compute_pipeline = + ctx.device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: None, + module: &module, + entry_point: Some("main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + cache: None, + }); + let buffer = DeviceExt::create_buffer_init( + &ctx.device, + &wgpu::util::BufferInitDescriptor { + label: None, + contents: &[1, 1, 1, 1, 1, 1, 1, 1], + usage: wgpu::BufferUsages::STORAGE + | wgpu::BufferUsages::COPY_DST + | wgpu::BufferUsages::COPY_SRC, + }, + ); + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + cpass.set_pipeline(&compute_pipeline); + let bind_group_layout = compute_pipeline.get_bind_group_layout(0); + let bind_group_entries = [wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }]; + let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &bind_group_entries, + }); + cpass.set_bind_group(0, &bind_group, &[]); + cpass.dispatch_workgroups(1, 0, 1); + } + ctx.queue.submit(Some(encoder.finish())); + }); diff --git a/tests/tests/root.rs b/tests/tests/root.rs index 7312b3c2e..dac56a9db 100644 --- a/tests/tests/root.rs +++ b/tests/tests/root.rs @@ -7,6 +7,7 @@ mod regression { mod issue_4514; mod issue_5553; mod issue_6317; + mod issue_6467; } mod bgra8unorm_storage; @@ -36,6 +37,7 @@ mod poll; mod push_constants; mod query_set; mod queue_transfer; +mod ray_tracing; mod render_pass_ownership; mod resource_descriptor_accessor; mod resource_error; diff --git a/wgpu-core/src/binding_model.rs b/wgpu-core/src/binding_model.rs index 55a3e145f..8906b5875 100644 --- a/wgpu-core/src/binding_model.rs +++ b/wgpu-core/src/binding_model.rs @@ -2,7 +2,7 @@ use crate::{ device::{ bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT, }, - id::{BindGroupLayoutId, BufferId, SamplerId, TextureViewId}, + id::{BindGroupLayoutId, BufferId, SamplerId, TextureViewId, TlasId}, init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction}, pipeline::{ComputePipeline, RenderPipeline}, resource::{ @@ -29,6 +29,7 @@ use std::{ sync::{Arc, OnceLock, Weak}, }; +use crate::resource::Tlas; use thiserror::Error; #[derive(Clone, Debug, Error)] @@ -302,6 +303,7 @@ pub(crate) struct BindingTypeMaxCountValidator { storage_buffers: PerStageBindingTypeCounter, storage_textures: PerStageBindingTypeCounter, uniform_buffers: PerStageBindingTypeCounter, + acceleration_structures: PerStageBindingTypeCounter, } impl BindingTypeMaxCountValidator { @@ -337,7 +339,9 @@ impl BindingTypeMaxCountValidator { wgt::BindingType::StorageTexture { .. } => { self.storage_textures.add(binding.visibility, count); } - wgt::BindingType::AccelerationStructure => todo!(), + wgt::BindingType::AccelerationStructure => { + self.acceleration_structures.add(binding.visibility, count); + } } } @@ -781,6 +785,7 @@ pub enum BindingResource<'a> { SamplerArray(Cow<'a, [SamplerId]>), TextureView(TextureViewId), TextureViewArray(Cow<'a, [TextureViewId]>), + AccelerationStructure(TlasId), } // Note: Duplicated in `wgpu-rs` as `BindingResource` @@ -793,6 +798,7 @@ pub enum ResolvedBindingResource<'a> { SamplerArray(Cow<'a, [Arc]>), TextureView(Arc), TextureViewArray(Cow<'a, [Arc]>), + AccelerationStructure(Arc), } #[derive(Clone, Debug, Error)] diff --git a/wgpu-core/src/command/compute.rs b/wgpu-core/src/command/compute.rs index 07dcc2475..28e76c111 100644 --- a/wgpu-core/src/command/compute.rs +++ b/wgpu-core/src/command/compute.rs @@ -28,11 +28,11 @@ use crate::{ use thiserror::Error; use wgt::{BufferAddress, DynamicOffset}; +use super::{bind::BinderError, memory_init::CommandBufferTextureMemoryActions}; +use crate::ray_tracing::TlasAction; use std::sync::Arc; use std::{fmt, mem::size_of, str}; -use super::{bind::BinderError, memory_init::CommandBufferTextureMemoryActions}; - pub struct ComputePass { /// All pass data & records is stored here. /// @@ -210,6 +210,7 @@ struct State<'scope, 'snatch_guard, 'cmd_buf, 'raw_encoder> { tracker: &'cmd_buf mut Tracker, buffer_memory_init_actions: &'cmd_buf mut Vec, texture_memory_actions: &'cmd_buf mut CommandBufferTextureMemoryActions, + tlas_actions: &'cmd_buf mut Vec, temp_offsets: Vec, dynamic_offset_count: usize, @@ -312,6 +313,17 @@ impl Global { Ok(query_set) => query_set, Err(e) => return make_err(e.into(), arc_desc), }; + match query_set.same_device(&cmd_buf.device) { + Ok(()) => (), + Err(e) => return make_err(e.into(), arc_desc), + } + match cmd_buf + .device + .require_features(wgt::Features::TIMESTAMP_QUERY) + { + Ok(()) => (), + Err(e) => return make_err(e.into(), arc_desc), + } Some(ArcPassTimestampWrites { query_set, @@ -439,6 +451,7 @@ impl Global { tracker: &mut cmd_buf_data.trackers, buffer_memory_init_actions: &mut cmd_buf_data.buffer_memory_init_actions, texture_memory_actions: &mut cmd_buf_data.texture_memory_actions, + tlas_actions: &mut cmd_buf_data.tlas_actions, temp_offsets: Vec::new(), dynamic_offset_count: 0, @@ -684,6 +697,17 @@ fn set_bind_group( .extend(state.texture_memory_actions.register_init_action(action)); } + let used_resource = bind_group + .used + .acceleration_structures + .into_iter() + .map(|tlas| TlasAction { + tlas: tlas.clone(), + kind: crate::ray_tracing::TlasActionKind::Use, + }); + + state.tlas_actions.extend(used_resource); + let pipeline_layout = state.binder.pipeline_layout.clone(); let entries = state .binder diff --git a/wgpu-core/src/command/mod.rs b/wgpu-core/src/command/mod.rs index 42c80d3ca..33025579a 100644 --- a/wgpu-core/src/command/mod.rs +++ b/wgpu-core/src/command/mod.rs @@ -7,6 +7,7 @@ mod compute_command; mod draw; mod memory_init; mod query; +mod ray_tracing; mod render; mod render_command; mod timestamp_writes; @@ -26,11 +27,12 @@ pub use timestamp_writes::PassTimestampWrites; use self::memory_init::CommandBufferTextureMemoryActions; -use crate::device::{Device, DeviceError}; +use crate::device::{Device, DeviceError, MissingFeatures}; use crate::lock::{rank, Mutex}; use crate::snatch::SnatchGuard; use crate::init_tracker::BufferInitTrackerAction; +use crate::ray_tracing::{BlasAction, TlasAction}; use crate::resource::{InvalidResourceError, Labeled}; use crate::track::{DeviceTracker, Tracker, UsageScope}; use crate::LabelHelpers; @@ -242,6 +244,8 @@ impl CommandEncoder { } } +/// Look at the documentation for [`CommandBufferMutable`] for an explanation of +/// the fields in this struct. This is the "built" counterpart to that type. pub(crate) struct BakedCommands { pub(crate) encoder: Box, pub(crate) list: Vec>, @@ -274,6 +278,10 @@ pub struct CommandBufferMutable { texture_memory_actions: CommandBufferTextureMemoryActions, pub(crate) pending_query_resets: QueryResetMap, + + blas_actions: Vec, + tlas_actions: Vec, + #[cfg(feature = "trace")] pub(crate) commands: Option>, } @@ -456,6 +464,8 @@ impl CommandBuffer { buffer_memory_init_actions: Default::default(), texture_memory_actions: Default::default(), pending_query_resets: QueryResetMap::new(), + blas_actions: Default::default(), + tlas_actions: Default::default(), #[cfg(feature = "trace")] commands: if device.trace.lock().is_some() { Some(Vec::new()) @@ -642,6 +652,8 @@ pub enum CommandEncoderError { InvalidColorAttachment(#[from] ColorAttachmentError), #[error(transparent)] InvalidResource(#[from] InvalidResourceError), + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), } impl Global { diff --git a/wgpu-core/src/command/ray_tracing.rs b/wgpu-core/src/command/ray_tracing.rs new file mode 100644 index 000000000..ac3bbb67b --- /dev/null +++ b/wgpu-core/src/command/ray_tracing.rs @@ -0,0 +1,1285 @@ +use crate::{ + device::queue::TempResource, + global::Global, + hub::Hub, + id::CommandEncoderId, + init_tracker::MemoryInitKind, + ray_tracing::{ + tlas_instance_into_bytes, BlasAction, BlasBuildEntry, BlasGeometries, BlasTriangleGeometry, + BuildAccelerationStructureError, TlasAction, TlasBuildEntry, TlasInstance, TlasPackage, + TraceBlasBuildEntry, TraceBlasGeometries, TraceBlasTriangleGeometry, TraceTlasInstance, + TraceTlasPackage, ValidateBlasActionsError, ValidateTlasActionsError, + }, + resource::{AccelerationStructure, Blas, Buffer, Labeled, StagingBuffer, Tlas, Trackable}, + scratch::ScratchBuffer, + snatch::SnatchGuard, + track::PendingTransition, + FastHashSet, +}; + +use wgt::{math::align_to, BufferUsages, Features}; + +use super::CommandBufferMutable; +use crate::device::queue::PendingWrites; +use hal::BufferUses; +use std::mem::ManuallyDrop; +use std::ops::DerefMut; +use std::{ + cmp::max, + num::NonZeroU64, + ops::{Deref, Range}, + sync::{atomic::Ordering, Arc}, +}; + +struct TriangleBufferStore<'a> { + vertex_buffer: Arc, + vertex_transition: Option>, + index_buffer_transition: Option<(Arc, Option>)>, + transform_buffer_transition: Option<(Arc, Option>)>, + geometry: BlasTriangleGeometry<'a>, + ending_blas: Option>, +} + +struct BlasStore<'a> { + blas: Arc, + entries: hal::AccelerationStructureEntries<'a, dyn hal::DynBuffer>, + scratch_buffer_offset: u64, +} + +struct UnsafeTlasStore<'a> { + tlas: Arc, + entries: hal::AccelerationStructureEntries<'a, dyn hal::DynBuffer>, + scratch_buffer_offset: u64, +} + +struct TlasStore<'a> { + internal: UnsafeTlasStore<'a>, + range: Range, +} + +struct TlasBufferStore { + buffer: Arc, + transition: Option>, + entry: TlasBuildEntry, +} + +// TODO: Get this from the device (e.g. VkPhysicalDeviceAccelerationStructurePropertiesKHR.minAccelerationStructureScratchOffsetAlignment) this is currently the largest possible some devices have 0, 64, 128 (lower limits) so this could create excess allocation (Note: dx12 has 256). +const SCRATCH_BUFFER_ALIGNMENT: u32 = 256; + +impl Global { + // Currently this function is very similar to its safe counterpart, however certain parts of it are very different, + // making for the two to be implemented differently, the main difference is this function has separate buffers for each + // of the TLAS instances while the other has one large buffer + // TODO: reconsider this function's usefulness once blas and tlas `as_hal` are added and some time has passed. + pub fn command_encoder_build_acceleration_structures_unsafe_tlas<'a>( + &self, + command_encoder_id: CommandEncoderId, + blas_iter: impl Iterator>, + tlas_iter: impl Iterator, + ) -> Result<(), BuildAccelerationStructureError> { + profiling::scope!("CommandEncoder::build_acceleration_structures_unsafe_tlas"); + + let hub = &self.hub; + + let cmd_buf = hub + .command_buffers + .get(command_encoder_id.into_command_buffer_id()); + + let device = &cmd_buf.device; + + if !device + .features + .contains(Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + { + return Err(BuildAccelerationStructureError::MissingFeature); + } + + let build_command_index = NonZeroU64::new( + device + .last_acceleration_structure_build_command_index + .fetch_add(1, Ordering::Relaxed), + ) + .unwrap(); + + #[cfg(feature = "trace")] + let trace_blas: Vec = blas_iter + .map(|blas_entry| { + let geometries = match blas_entry.geometries { + BlasGeometries::TriangleGeometries(triangle_geometries) => { + TraceBlasGeometries::TriangleGeometries( + triangle_geometries + .map(|tg| TraceBlasTriangleGeometry { + size: tg.size.clone(), + vertex_buffer: tg.vertex_buffer, + index_buffer: tg.index_buffer, + transform_buffer: tg.transform_buffer, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + }) + .collect(), + ) + } + }; + TraceBlasBuildEntry { + blas_id: blas_entry.blas_id, + geometries, + } + }) + .collect(); + + #[cfg(feature = "trace")] + let trace_tlas: Vec = tlas_iter.collect(); + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.data.lock().as_mut().unwrap().commands { + list.push( + crate::device::trace::Command::BuildAccelerationStructuresUnsafeTlas { + blas: trace_blas.clone(), + tlas: trace_tlas.clone(), + }, + ); + if !trace_tlas.is_empty() { + log::warn!("a trace of command_encoder_build_acceleration_structures_unsafe_tlas containing a tlas build is not replayable!"); + } + } + + #[cfg(feature = "trace")] + let blas_iter = trace_blas.iter().map(|blas_entry| { + let geometries = match &blas_entry.geometries { + TraceBlasGeometries::TriangleGeometries(triangle_geometries) => { + let iter = triangle_geometries.iter().map(|tg| BlasTriangleGeometry { + size: &tg.size, + vertex_buffer: tg.vertex_buffer, + index_buffer: tg.index_buffer, + transform_buffer: tg.transform_buffer, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + }); + BlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + BlasBuildEntry { + blas_id: blas_entry.blas_id, + geometries, + } + }); + + #[cfg(feature = "trace")] + let tlas_iter = trace_tlas.iter(); + + let mut input_barriers = Vec::>::new(); + let mut buf_storage = Vec::new(); + + let mut scratch_buffer_blas_size = 0; + let mut blas_storage = Vec::new(); + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); + + iter_blas( + blas_iter, + cmd_buf_data, + build_command_index, + &mut buf_storage, + hub, + device.pending_writes.lock().deref_mut(), + )?; + + let snatch_guard = device.snatchable_lock.read(); + iter_buffers( + &mut buf_storage, + &snatch_guard, + &mut input_barriers, + cmd_buf_data, + &mut scratch_buffer_blas_size, + &mut blas_storage, + hub, + )?; + + let mut scratch_buffer_tlas_size = 0; + let mut tlas_storage = Vec::::new(); + let mut tlas_buf_storage = Vec::new(); + + for entry in tlas_iter { + let instance_buffer = match hub.buffers.get(entry.instance_buffer_id).get() { + Ok(buffer) => buffer, + Err(_) => { + return Err(BuildAccelerationStructureError::InvalidBufferId); + } + }; + let data = cmd_buf_data.trackers.buffers.set_single( + &instance_buffer, + BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + ); + tlas_buf_storage.push(TlasBufferStore { + buffer: instance_buffer.clone(), + transition: data, + entry: entry.clone(), + }); + } + + for tlas_buf in &mut tlas_buf_storage { + let entry = &tlas_buf.entry; + let instance_buffer = { + let (instance_buffer, instance_pending) = + (&mut tlas_buf.buffer, &mut tlas_buf.transition); + let instance_raw = instance_buffer.raw.get(&snatch_guard).ok_or( + BuildAccelerationStructureError::InvalidBuffer(instance_buffer.error_ident()), + )?; + if !instance_buffer.usage.contains(BufferUsages::TLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingTlasInputUsageFlag( + instance_buffer.error_ident(), + )); + } + if let Some(barrier) = instance_pending + .take() + .map(|pending| pending.into_hal(instance_buffer, &snatch_guard)) + { + input_barriers.push(barrier); + } + instance_raw + }; + + let tlas = hub + .tlas_s + .get(entry.tlas_id) + .get() + .map_err(|_| BuildAccelerationStructureError::InvalidTlasId)?; + cmd_buf_data.trackers.tlas_s.set_single(tlas.clone()); + device.pending_writes.lock().insert_tlas(&tlas); + + cmd_buf_data.tlas_actions.push(TlasAction { + tlas: tlas.clone(), + kind: crate::ray_tracing::TlasActionKind::Build { + build_index: build_command_index, + dependencies: Vec::new(), + }, + }); + + let scratch_buffer_offset = scratch_buffer_tlas_size; + scratch_buffer_tlas_size += align_to( + tlas.size_info.build_scratch_size as u32, + SCRATCH_BUFFER_ALIGNMENT, + ) as u64; + + tlas_storage.push(UnsafeTlasStore { + tlas, + entries: hal::AccelerationStructureEntries::Instances( + hal::AccelerationStructureInstances { + buffer: Some(instance_buffer.as_ref()), + offset: 0, + count: entry.instance_count, + }, + ), + scratch_buffer_offset, + }); + } + + let scratch_size = + match wgt::BufferSize::new(max(scratch_buffer_blas_size, scratch_buffer_tlas_size)) { + None => return Ok(()), + Some(size) => size, + }; + + let scratch_buffer = + ScratchBuffer::new(device, scratch_size).map_err(crate::device::DeviceError::from)?; + + let scratch_buffer_barrier = hal::BufferBarrier:: { + buffer: scratch_buffer.raw(), + usage: BufferUses::ACCELERATION_STRUCTURE_SCRATCH + ..BufferUses::ACCELERATION_STRUCTURE_SCRATCH, + }; + + let mut tlas_descriptors = Vec::new(); + + for UnsafeTlasStore { + tlas, + entries, + scratch_buffer_offset, + } in &tlas_storage + { + if tlas.update_mode == wgt::AccelerationStructureUpdateMode::PreferUpdate { + log::info!("only rebuild implemented") + } + tlas_descriptors.push(hal::BuildAccelerationStructureDescriptor { + entries, + mode: hal::AccelerationStructureBuildMode::Build, + flags: tlas.flags, + source_acceleration_structure: None, + destination_acceleration_structure: tlas.raw(&snatch_guard).ok_or( + BuildAccelerationStructureError::InvalidTlas(tlas.error_ident()), + )?, + scratch_buffer: scratch_buffer.raw(), + scratch_buffer_offset: *scratch_buffer_offset, + }) + } + + let blas_present = !blas_storage.is_empty(); + let tlas_present = !tlas_storage.is_empty(); + + let cmd_buf_raw = cmd_buf_data.encoder.open(device)?; + + let mut descriptors = Vec::new(); + + for storage in &blas_storage { + descriptors.push(map_blas(storage, scratch_buffer.raw(), &snatch_guard)?); + } + + build_blas( + cmd_buf_raw, + blas_present, + tlas_present, + input_barriers, + &descriptors, + scratch_buffer_barrier, + ); + + if tlas_present { + unsafe { + cmd_buf_raw.build_acceleration_structures(&tlas_descriptors); + + cmd_buf_raw.place_acceleration_structure_barrier( + hal::AccelerationStructureBarrier { + usage: hal::AccelerationStructureUses::BUILD_OUTPUT + ..hal::AccelerationStructureUses::SHADER_INPUT, + }, + ); + } + } + + device + .pending_writes + .lock() + .consume_temp(TempResource::ScratchBuffer(scratch_buffer)); + + Ok(()) + } + + pub fn command_encoder_build_acceleration_structures<'a>( + &self, + command_encoder_id: CommandEncoderId, + blas_iter: impl Iterator>, + tlas_iter: impl Iterator>, + ) -> Result<(), BuildAccelerationStructureError> { + profiling::scope!("CommandEncoder::build_acceleration_structures"); + + let hub = &self.hub; + + let cmd_buf = hub + .command_buffers + .get(command_encoder_id.into_command_buffer_id()); + + let device = &cmd_buf.device; + + if !device + .features + .contains(Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + { + return Err(BuildAccelerationStructureError::MissingFeature); + } + + let build_command_index = NonZeroU64::new( + device + .last_acceleration_structure_build_command_index + .fetch_add(1, Ordering::Relaxed), + ) + .unwrap(); + + let trace_blas: Vec = blas_iter + .map(|blas_entry| { + let geometries = match blas_entry.geometries { + BlasGeometries::TriangleGeometries(triangle_geometries) => { + TraceBlasGeometries::TriangleGeometries( + triangle_geometries + .map(|tg| TraceBlasTriangleGeometry { + size: tg.size.clone(), + vertex_buffer: tg.vertex_buffer, + index_buffer: tg.index_buffer, + transform_buffer: tg.transform_buffer, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + }) + .collect(), + ) + } + }; + TraceBlasBuildEntry { + blas_id: blas_entry.blas_id, + geometries, + } + }) + .collect(); + + let trace_tlas: Vec = tlas_iter + .map(|package: TlasPackage| { + let instances = package + .instances + .map(|instance| { + instance.map(|instance| TraceTlasInstance { + blas_id: instance.blas_id, + transform: *instance.transform, + custom_index: instance.custom_index, + mask: instance.mask, + }) + }) + .collect(); + TraceTlasPackage { + tlas_id: package.tlas_id, + instances, + lowest_unmodified: package.lowest_unmodified, + } + }) + .collect(); + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.data.lock().as_mut().unwrap().commands { + list.push(crate::device::trace::Command::BuildAccelerationStructures { + blas: trace_blas.clone(), + tlas: trace_tlas.clone(), + }); + } + + let blas_iter = trace_blas.iter().map(|blas_entry| { + let geometries = match &blas_entry.geometries { + TraceBlasGeometries::TriangleGeometries(triangle_geometries) => { + let iter = triangle_geometries.iter().map(|tg| BlasTriangleGeometry { + size: &tg.size, + vertex_buffer: tg.vertex_buffer, + index_buffer: tg.index_buffer, + transform_buffer: tg.transform_buffer, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + }); + BlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + BlasBuildEntry { + blas_id: blas_entry.blas_id, + geometries, + } + }); + + let tlas_iter = trace_tlas.iter().map(|tlas_package| { + let instances = tlas_package.instances.iter().map(|instance| { + instance.as_ref().map(|instance| TlasInstance { + blas_id: instance.blas_id, + transform: &instance.transform, + custom_index: instance.custom_index, + mask: instance.mask, + }) + }); + TlasPackage { + tlas_id: tlas_package.tlas_id, + instances: Box::new(instances), + lowest_unmodified: tlas_package.lowest_unmodified, + } + }); + + let mut input_barriers = Vec::>::new(); + let mut buf_storage = Vec::new(); + + let mut scratch_buffer_blas_size = 0; + let mut blas_storage = Vec::new(); + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); + + iter_blas( + blas_iter, + cmd_buf_data, + build_command_index, + &mut buf_storage, + hub, + device.pending_writes.lock().deref_mut(), + )?; + + let snatch_guard = device.snatchable_lock.read(); + iter_buffers( + &mut buf_storage, + &snatch_guard, + &mut input_barriers, + cmd_buf_data, + &mut scratch_buffer_blas_size, + &mut blas_storage, + hub, + )?; + let mut tlas_lock_store = Vec::<(Option, Arc)>::new(); + + for package in tlas_iter { + let tlas = hub + .tlas_s + .get(package.tlas_id) + .get() + .map_err(|_| BuildAccelerationStructureError::InvalidTlasId)?; + device.pending_writes.lock().insert_tlas(&tlas); + cmd_buf_data.trackers.tlas_s.set_single(tlas.clone()); + + tlas_lock_store.push((Some(package), tlas.clone())) + } + + let mut scratch_buffer_tlas_size = 0; + let mut tlas_storage = Vec::::new(); + let mut instance_buffer_staging_source = Vec::::new(); + + for (package, tlas) in &mut tlas_lock_store { + let package = package.take().unwrap(); + + let scratch_buffer_offset = scratch_buffer_tlas_size; + scratch_buffer_tlas_size += align_to( + tlas.size_info.build_scratch_size as u32, + SCRATCH_BUFFER_ALIGNMENT, + ) as u64; + + let first_byte_index = instance_buffer_staging_source.len(); + + let mut dependencies = Vec::new(); + + let mut instance_count = 0; + for instance in package.instances.flatten() { + if instance.custom_index >= (1u32 << 24u32) { + return Err(BuildAccelerationStructureError::TlasInvalidCustomIndex( + tlas.error_ident(), + )); + } + let blas = hub + .blas_s + .get(instance.blas_id) + .get() + .map_err(|_| BuildAccelerationStructureError::InvalidBlasIdForInstance)? + .clone(); + + cmd_buf_data.trackers.blas_s.set_single(blas.clone()); + + instance_buffer_staging_source.extend(tlas_instance_into_bytes( + &instance, + blas.handle, + device.backend(), + )); + + instance_count += 1; + + dependencies.push(blas.clone()); + + cmd_buf_data.blas_actions.push(BlasAction { + blas: blas.clone(), + kind: crate::ray_tracing::BlasActionKind::Use, + }); + } + + cmd_buf_data.tlas_actions.push(TlasAction { + tlas: tlas.clone(), + kind: crate::ray_tracing::TlasActionKind::Build { + build_index: build_command_index, + dependencies, + }, + }); + + if instance_count > tlas.max_instance_count { + return Err(BuildAccelerationStructureError::TlasInstanceCountExceeded( + tlas.error_ident(), + instance_count, + tlas.max_instance_count, + )); + } + + tlas_storage.push(TlasStore { + internal: UnsafeTlasStore { + tlas: tlas.clone(), + entries: hal::AccelerationStructureEntries::Instances( + hal::AccelerationStructureInstances { + buffer: Some(tlas.instance_buffer.as_ref()), + offset: 0, + count: instance_count, + }, + ), + scratch_buffer_offset, + }, + range: first_byte_index..instance_buffer_staging_source.len(), + }); + } + + let scratch_size = + match wgt::BufferSize::new(max(scratch_buffer_blas_size, scratch_buffer_tlas_size)) { + // if the size is zero there is nothing to build + None => return Ok(()), + Some(size) => size, + }; + + let scratch_buffer = + ScratchBuffer::new(device, scratch_size).map_err(crate::device::DeviceError::from)?; + + let scratch_buffer_barrier = hal::BufferBarrier:: { + buffer: scratch_buffer.raw(), + usage: BufferUses::ACCELERATION_STRUCTURE_SCRATCH + ..BufferUses::ACCELERATION_STRUCTURE_SCRATCH, + }; + + let mut tlas_descriptors = Vec::with_capacity(tlas_storage.len()); + + for &TlasStore { + internal: + UnsafeTlasStore { + ref tlas, + ref entries, + ref scratch_buffer_offset, + }, + .. + } in &tlas_storage + { + if tlas.update_mode == wgt::AccelerationStructureUpdateMode::PreferUpdate { + log::info!("only rebuild implemented") + } + tlas_descriptors.push(hal::BuildAccelerationStructureDescriptor { + entries, + mode: hal::AccelerationStructureBuildMode::Build, + flags: tlas.flags, + source_acceleration_structure: None, + destination_acceleration_structure: tlas + .raw + .get(&snatch_guard) + .ok_or(BuildAccelerationStructureError::InvalidTlas( + tlas.error_ident(), + ))? + .as_ref(), + scratch_buffer: scratch_buffer.raw(), + scratch_buffer_offset: *scratch_buffer_offset, + }) + } + + let blas_present = !blas_storage.is_empty(); + let tlas_present = !tlas_storage.is_empty(); + + let cmd_buf_raw = cmd_buf_data.encoder.open(device)?; + + let mut descriptors = Vec::new(); + + for storage in &blas_storage { + descriptors.push(map_blas(storage, scratch_buffer.raw(), &snatch_guard)?); + } + + build_blas( + cmd_buf_raw, + blas_present, + tlas_present, + input_barriers, + &descriptors, + scratch_buffer_barrier, + ); + + if tlas_present { + let staging_buffer = if !instance_buffer_staging_source.is_empty() { + let mut staging_buffer = StagingBuffer::new( + device, + wgt::BufferSize::new(instance_buffer_staging_source.len() as u64).unwrap(), + ) + .map_err(crate::device::DeviceError::from)?; + staging_buffer.write(&instance_buffer_staging_source); + let flushed = staging_buffer.flush(); + Some(flushed) + } else { + None + }; + + unsafe { + if let Some(ref staging_buffer) = staging_buffer { + cmd_buf_raw.transition_buffers(&[hal::BufferBarrier:: { + buffer: staging_buffer.raw(), + usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, + }]); + } + } + + let mut instance_buffer_barriers = Vec::new(); + for &TlasStore { + internal: UnsafeTlasStore { ref tlas, .. }, + ref range, + } in &tlas_storage + { + let size = match wgt::BufferSize::new((range.end - range.start) as u64) { + None => continue, + Some(size) => size, + }; + instance_buffer_barriers.push(hal::BufferBarrier:: { + buffer: tlas.instance_buffer.as_ref(), + usage: BufferUses::COPY_DST..BufferUses::TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT, + }); + unsafe { + cmd_buf_raw.transition_buffers(&[hal::BufferBarrier:: { + buffer: tlas.instance_buffer.as_ref(), + usage: hal::BufferUses::MAP_READ..hal::BufferUses::COPY_DST, + }]); + let temp = hal::BufferCopy { + src_offset: range.start as u64, + dst_offset: 0, + size, + }; + cmd_buf_raw.copy_buffer_to_buffer( + // the range whose size we just checked end is at (at that point in time) instance_buffer_staging_source.len() + // and since instance_buffer_staging_source doesn't shrink we can un wrap this without a panic + staging_buffer.as_ref().unwrap().raw(), + tlas.instance_buffer.as_ref(), + &[temp], + ); + } + } + + unsafe { + cmd_buf_raw.transition_buffers(&instance_buffer_barriers); + + cmd_buf_raw.build_acceleration_structures(&tlas_descriptors); + + cmd_buf_raw.place_acceleration_structure_barrier( + hal::AccelerationStructureBarrier { + usage: hal::AccelerationStructureUses::BUILD_OUTPUT + ..hal::AccelerationStructureUses::SHADER_INPUT, + }, + ); + } + + if let Some(staging_buffer) = staging_buffer { + device + .pending_writes + .lock() + .consume_temp(TempResource::StagingBuffer(staging_buffer)); + } + } + + device + .pending_writes + .lock() + .consume_temp(TempResource::ScratchBuffer(scratch_buffer)); + + Ok(()) + } +} + +impl CommandBufferMutable { + // makes sure a blas is build before it is used + pub(crate) fn validate_blas_actions(&self) -> Result<(), ValidateBlasActionsError> { + profiling::scope!("CommandEncoder::[submission]::validate_blas_actions"); + let mut built = FastHashSet::default(); + for action in &self.blas_actions { + match &action.kind { + crate::ray_tracing::BlasActionKind::Build(id) => { + built.insert(action.blas.tracker_index()); + *action.blas.built_index.write() = Some(*id); + } + crate::ray_tracing::BlasActionKind::Use => { + if !built.contains(&action.blas.tracker_index()) + && (*action.blas.built_index.read()).is_none() + { + return Err(ValidateBlasActionsError::UsedUnbuilt( + action.blas.error_ident(), + )); + } + } + } + } + Ok(()) + } + + // makes sure a tlas is built before it is used + pub(crate) fn validate_tlas_actions( + &self, + snatch_guard: &SnatchGuard, + ) -> Result<(), ValidateTlasActionsError> { + profiling::scope!("CommandEncoder::[submission]::validate_tlas_actions"); + for action in &self.tlas_actions { + match &action.kind { + crate::ray_tracing::TlasActionKind::Build { + build_index, + dependencies, + } => { + *action.tlas.built_index.write() = Some(*build_index); + *action.tlas.dependencies.write() = dependencies.clone(); + } + crate::ray_tracing::TlasActionKind::Use => { + let tlas_build_index = action.tlas.built_index.read(); + let dependencies = action.tlas.dependencies.read(); + + if (*tlas_build_index).is_none() { + return Err(ValidateTlasActionsError::UsedUnbuilt( + action.tlas.error_ident(), + )); + } + for blas in dependencies.deref() { + let blas_build_index = *blas.built_index.read(); + if blas_build_index.is_none() { + return Err(ValidateTlasActionsError::UsedUnbuiltBlas( + action.tlas.error_ident(), + blas.error_ident(), + )); + } + if blas_build_index.unwrap() > tlas_build_index.unwrap() { + return Err(ValidateTlasActionsError::BlasNewerThenTlas( + blas.error_ident(), + action.tlas.error_ident(), + )); + } + if blas.raw.get(snatch_guard).is_none() { + return Err(ValidateTlasActionsError::InvalidBlas(blas.error_ident())); + } + } + } + } + } + Ok(()) + } +} + +///iterates over the blas iterator, and it's geometry, pushing the buffers into a storage vector (and also some validation). +fn iter_blas<'a>( + blas_iter: impl Iterator>, + cmd_buf_data: &mut CommandBufferMutable, + build_command_index: NonZeroU64, + buf_storage: &mut Vec>, + hub: &Hub, + pending_writes: &mut ManuallyDrop, +) -> Result<(), BuildAccelerationStructureError> { + let mut temp_buffer = Vec::new(); + for entry in blas_iter { + let blas = hub + .blas_s + .get(entry.blas_id) + .get() + .map_err(|_| BuildAccelerationStructureError::InvalidBlasId)?; + cmd_buf_data.trackers.blas_s.set_single(blas.clone()); + pending_writes.insert_blas(&blas); + + cmd_buf_data.blas_actions.push(BlasAction { + blas: blas.clone(), + kind: crate::ray_tracing::BlasActionKind::Build(build_command_index), + }); + + match entry.geometries { + BlasGeometries::TriangleGeometries(triangle_geometries) => { + for (i, mesh) in triangle_geometries.enumerate() { + let size_desc = match &blas.sizes { + wgt::BlasGeometrySizeDescriptors::Triangles { descriptors } => descriptors, + }; + if i >= size_desc.len() { + return Err(BuildAccelerationStructureError::IncompatibleBlasBuildSizes( + blas.error_ident(), + )); + } + let size_desc = &size_desc[i]; + + if size_desc.flags != mesh.size.flags { + return Err(BuildAccelerationStructureError::IncompatibleBlasFlags( + blas.error_ident(), + size_desc.flags, + mesh.size.flags, + )); + } + + if size_desc.vertex_count < mesh.size.vertex_count { + return Err( + BuildAccelerationStructureError::IncompatibleBlasVertexCount( + blas.error_ident(), + size_desc.vertex_count, + mesh.size.vertex_count, + ), + ); + } + + if size_desc.vertex_format != mesh.size.vertex_format { + return Err(BuildAccelerationStructureError::DifferentBlasVertexFormats( + blas.error_ident(), + size_desc.vertex_format, + mesh.size.vertex_format, + )); + } + + match (size_desc.index_count, mesh.size.index_count) { + (Some(_), None) | (None, Some(_)) => { + return Err( + BuildAccelerationStructureError::BlasIndexCountProvidedMismatch( + blas.error_ident(), + ), + ) + } + (Some(create), Some(build)) if create < build => { + return Err( + BuildAccelerationStructureError::IncompatibleBlasIndexCount( + blas.error_ident(), + create, + build, + ), + ) + } + _ => {} + } + + if size_desc.index_format != mesh.size.index_format { + return Err(BuildAccelerationStructureError::DifferentBlasIndexFormats( + blas.error_ident(), + size_desc.index_format, + mesh.size.index_format, + )); + } + + if size_desc.index_count.is_some() && mesh.index_buffer.is_none() { + return Err(BuildAccelerationStructureError::MissingIndexBuffer( + blas.error_ident(), + )); + } + let vertex_buffer = match hub.buffers.get(mesh.vertex_buffer).get() { + Ok(buffer) => buffer, + Err(_) => return Err(BuildAccelerationStructureError::InvalidBufferId), + }; + let vertex_pending = cmd_buf_data.trackers.buffers.set_single( + &vertex_buffer, + BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + ); + let index_data = if let Some(index_id) = mesh.index_buffer { + let index_buffer = match hub.buffers.get(index_id).get() { + Ok(buffer) => buffer, + Err(_) => return Err(BuildAccelerationStructureError::InvalidBufferId), + }; + if mesh.index_buffer_offset.is_none() + || mesh.size.index_count.is_none() + || mesh.size.index_count.is_none() + { + return Err(BuildAccelerationStructureError::MissingAssociatedData( + index_buffer.error_ident(), + )); + } + let data = cmd_buf_data.trackers.buffers.set_single( + &index_buffer, + hal::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + ); + Some((index_buffer.clone(), data)) + } else { + None + }; + let transform_data = if let Some(transform_id) = mesh.transform_buffer { + let transform_buffer = match hub.buffers.get(transform_id).get() { + Ok(buffer) => buffer, + Err(_) => return Err(BuildAccelerationStructureError::InvalidBufferId), + }; + if mesh.transform_buffer_offset.is_none() { + return Err(BuildAccelerationStructureError::MissingAssociatedData( + transform_buffer.error_ident(), + )); + } + let data = cmd_buf_data.trackers.buffers.set_single( + &transform_buffer, + BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + ); + Some((transform_buffer, data)) + } else { + None + }; + temp_buffer.push(TriangleBufferStore { + vertex_buffer: vertex_buffer.clone(), + vertex_transition: vertex_pending, + index_buffer_transition: index_data, + transform_buffer_transition: transform_data, + geometry: mesh, + ending_blas: None, + }); + } + + if let Some(last) = temp_buffer.last_mut() { + last.ending_blas = Some(blas.clone()); + buf_storage.append(&mut temp_buffer); + } + } + } + } + Ok(()) +} + +/// Iterates over the buffers generated in [iter_blas], convert the barriers into hal barriers, and the triangles into [hal::AccelerationStructureEntries] (and also some validation). +fn iter_buffers<'a, 'b>( + buf_storage: &'a mut Vec>, + snatch_guard: &'a SnatchGuard, + input_barriers: &mut Vec>, + cmd_buf_data: &mut CommandBufferMutable, + scratch_buffer_blas_size: &mut u64, + blas_storage: &mut Vec>, + hub: &Hub, +) -> Result<(), BuildAccelerationStructureError> { + let mut triangle_entries = + Vec::>::new(); + for buf in buf_storage { + let mesh = &buf.geometry; + let vertex_buffer = { + let vertex_buffer = buf.vertex_buffer.as_ref(); + let vertex_raw = vertex_buffer.raw.get(snatch_guard).ok_or( + BuildAccelerationStructureError::InvalidBuffer(vertex_buffer.error_ident()), + )?; + if !vertex_buffer.usage.contains(BufferUsages::BLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingBlasInputUsageFlag( + vertex_buffer.error_ident(), + )); + } + if let Some(barrier) = buf + .vertex_transition + .take() + .map(|pending| pending.into_hal(vertex_buffer, snatch_guard)) + { + input_barriers.push(barrier); + } + if vertex_buffer.size + < (mesh.size.vertex_count + mesh.first_vertex) as u64 * mesh.vertex_stride + { + return Err(BuildAccelerationStructureError::InsufficientBufferSize( + vertex_buffer.error_ident(), + vertex_buffer.size, + (mesh.size.vertex_count + mesh.first_vertex) as u64 * mesh.vertex_stride, + )); + } + let vertex_buffer_offset = mesh.first_vertex as u64 * mesh.vertex_stride; + cmd_buf_data.buffer_memory_init_actions.extend( + vertex_buffer.initialization_status.read().create_action( + &hub.buffers + .get(mesh.vertex_buffer) + .get() + .map_err(|_| BuildAccelerationStructureError::InvalidBufferId)?, + vertex_buffer_offset + ..(vertex_buffer_offset + + mesh.size.vertex_count as u64 * mesh.vertex_stride), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + vertex_raw + }; + let index_buffer = if let Some((ref mut index_buffer, ref mut index_pending)) = + buf.index_buffer_transition + { + let index_raw = index_buffer.raw.get(snatch_guard).ok_or( + BuildAccelerationStructureError::InvalidBuffer(index_buffer.error_ident()), + )?; + if !index_buffer.usage.contains(BufferUsages::BLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingBlasInputUsageFlag( + index_buffer.error_ident(), + )); + } + if let Some(barrier) = index_pending + .take() + .map(|pending| pending.into_hal(index_buffer, snatch_guard)) + { + input_barriers.push(barrier); + } + let index_stride = match mesh.size.index_format.unwrap() { + wgt::IndexFormat::Uint16 => 2, + wgt::IndexFormat::Uint32 => 4, + }; + if mesh.index_buffer_offset.unwrap() % index_stride != 0 { + return Err(BuildAccelerationStructureError::UnalignedIndexBufferOffset( + index_buffer.error_ident(), + )); + } + let index_buffer_size = mesh.size.index_count.unwrap() as u64 * index_stride; + + if mesh.size.index_count.unwrap() % 3 != 0 { + return Err(BuildAccelerationStructureError::InvalidIndexCount( + index_buffer.error_ident(), + mesh.size.index_count.unwrap(), + )); + } + if index_buffer.size + < mesh.size.index_count.unwrap() as u64 * index_stride + + mesh.index_buffer_offset.unwrap() + { + return Err(BuildAccelerationStructureError::InsufficientBufferSize( + index_buffer.error_ident(), + index_buffer.size, + mesh.size.index_count.unwrap() as u64 * index_stride + + mesh.index_buffer_offset.unwrap(), + )); + } + + cmd_buf_data.buffer_memory_init_actions.extend( + index_buffer.initialization_status.read().create_action( + index_buffer, + mesh.index_buffer_offset.unwrap() + ..(mesh.index_buffer_offset.unwrap() + index_buffer_size), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + Some(index_raw) + } else { + None + }; + let transform_buffer = if let Some((ref mut transform_buffer, ref mut transform_pending)) = + buf.transform_buffer_transition + { + if mesh.transform_buffer_offset.is_none() { + return Err(BuildAccelerationStructureError::MissingAssociatedData( + transform_buffer.error_ident(), + )); + } + let transform_raw = transform_buffer.raw.get(snatch_guard).ok_or( + BuildAccelerationStructureError::InvalidBuffer(transform_buffer.error_ident()), + )?; + if !transform_buffer.usage.contains(BufferUsages::BLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingBlasInputUsageFlag( + transform_buffer.error_ident(), + )); + } + if let Some(barrier) = transform_pending + .take() + .map(|pending| pending.into_hal(transform_buffer, snatch_guard)) + { + input_barriers.push(barrier); + } + if mesh.transform_buffer_offset.unwrap() % wgt::TRANSFORM_BUFFER_ALIGNMENT != 0 { + return Err( + BuildAccelerationStructureError::UnalignedTransformBufferOffset( + transform_buffer.error_ident(), + ), + ); + } + if transform_buffer.size < 48 + mesh.transform_buffer_offset.unwrap() { + return Err(BuildAccelerationStructureError::InsufficientBufferSize( + transform_buffer.error_ident(), + transform_buffer.size, + 48 + mesh.transform_buffer_offset.unwrap(), + )); + } + cmd_buf_data.buffer_memory_init_actions.extend( + transform_buffer.initialization_status.read().create_action( + transform_buffer, + mesh.transform_buffer_offset.unwrap()..(mesh.index_buffer_offset.unwrap() + 48), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + Some(transform_raw) + } else { + None + }; + + let triangles = hal::AccelerationStructureTriangles { + vertex_buffer: Some(vertex_buffer.as_ref()), + vertex_format: mesh.size.vertex_format, + first_vertex: mesh.first_vertex, + vertex_count: mesh.size.vertex_count, + vertex_stride: mesh.vertex_stride, + indices: index_buffer.map(|index_buffer| hal::AccelerationStructureTriangleIndices::< + dyn hal::DynBuffer, + > { + format: mesh.size.index_format.unwrap(), + buffer: Some(index_buffer.as_ref()), + offset: mesh.index_buffer_offset.unwrap() as u32, + count: mesh.size.index_count.unwrap(), + }), + transform: transform_buffer.map(|transform_buffer| { + hal::AccelerationStructureTriangleTransform { + buffer: transform_buffer.as_ref(), + offset: mesh.transform_buffer_offset.unwrap() as u32, + } + }), + flags: mesh.size.flags, + }; + triangle_entries.push(triangles); + if let Some(blas) = buf.ending_blas.take() { + let scratch_buffer_offset = *scratch_buffer_blas_size; + *scratch_buffer_blas_size += align_to( + blas.size_info.build_scratch_size as u32, + SCRATCH_BUFFER_ALIGNMENT, + ) as u64; + + blas_storage.push(BlasStore { + blas, + entries: hal::AccelerationStructureEntries::Triangles(triangle_entries), + scratch_buffer_offset, + }); + triangle_entries = Vec::new(); + } + } + Ok(()) +} + +fn map_blas<'a>( + storage: &'a BlasStore<'_>, + scratch_buffer: &'a dyn hal::DynBuffer, + snatch_guard: &'a SnatchGuard, +) -> Result< + hal::BuildAccelerationStructureDescriptor< + 'a, + dyn hal::DynBuffer, + dyn hal::DynAccelerationStructure, + >, + BuildAccelerationStructureError, +> { + let BlasStore { + blas, + entries, + scratch_buffer_offset, + } = storage; + if blas.update_mode == wgt::AccelerationStructureUpdateMode::PreferUpdate { + log::info!("only rebuild implemented") + } + Ok(hal::BuildAccelerationStructureDescriptor { + entries, + mode: hal::AccelerationStructureBuildMode::Build, + flags: blas.flags, + source_acceleration_structure: None, + destination_acceleration_structure: blas + .raw + .get(snatch_guard) + .ok_or(BuildAccelerationStructureError::InvalidBlas( + blas.error_ident(), + ))? + .as_ref(), + scratch_buffer, + scratch_buffer_offset: *scratch_buffer_offset, + }) +} + +fn build_blas<'a>( + cmd_buf_raw: &mut dyn hal::DynCommandEncoder, + blas_present: bool, + tlas_present: bool, + input_barriers: Vec>, + blas_descriptors: &[hal::BuildAccelerationStructureDescriptor< + 'a, + dyn hal::DynBuffer, + dyn hal::DynAccelerationStructure, + >], + scratch_buffer_barrier: hal::BufferBarrier, +) { + unsafe { + cmd_buf_raw.transition_buffers(&input_barriers); + } + + if blas_present { + unsafe { + cmd_buf_raw.place_acceleration_structure_barrier(hal::AccelerationStructureBarrier { + usage: hal::AccelerationStructureUses::BUILD_INPUT + ..hal::AccelerationStructureUses::BUILD_OUTPUT, + }); + + cmd_buf_raw.build_acceleration_structures(blas_descriptors); + } + } + + if blas_present && tlas_present { + unsafe { + cmd_buf_raw.transition_buffers(&[scratch_buffer_barrier]); + } + } + + let mut source_usage = hal::AccelerationStructureUses::empty(); + let mut destination_usage = hal::AccelerationStructureUses::empty(); + if blas_present { + source_usage |= hal::AccelerationStructureUses::BUILD_OUTPUT; + destination_usage |= hal::AccelerationStructureUses::BUILD_INPUT + } + if tlas_present { + source_usage |= hal::AccelerationStructureUses::SHADER_INPUT; + destination_usage |= hal::AccelerationStructureUses::BUILD_OUTPUT; + } + unsafe { + cmd_buf_raw.place_acceleration_structure_barrier(hal::AccelerationStructureBarrier { + usage: source_usage..destination_usage, + }); + } +} diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index d22eb5f0d..6102d0d49 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -1358,9 +1358,11 @@ impl Global { }) = color_attachment { let view = texture_views.get(*view_id).get()?; + view.same_device(device)?; let resolve_target = if let Some(resolve_target_id) = resolve_target { let rt_arc = texture_views.get(*resolve_target_id).get()?; + rt_arc.same_device(device)?; Some(rt_arc) } else { @@ -1382,6 +1384,7 @@ impl Global { arc_desc.depth_stencil_attachment = if let Some(depth_stencil_attachment) = desc.depth_stencil_attachment { let view = texture_views.get(depth_stencil_attachment.view).get()?; + view.same_device(device)?; Some(ArcRenderPassDepthStencilAttachment { view, @@ -1394,6 +1397,9 @@ impl Global { arc_desc.timestamp_writes = if let Some(tw) = desc.timestamp_writes { let query_set = query_sets.get(tw.query_set).get()?; + query_set.same_device(device)?; + + device.require_features(wgt::Features::TIMESTAMP_QUERY)?; Some(ArcPassTimestampWrites { query_set, @@ -1407,6 +1413,7 @@ impl Global { arc_desc.occlusion_query_set = if let Some(occlusion_query_set) = desc.occlusion_query_set { let query_set = query_sets.get(occlusion_query_set).get()?; + query_set.same_device(device)?; Some(query_set) } else { diff --git a/wgpu-core/src/conv.rs b/wgpu-core/src/conv.rs index d27583b02..a9f48ff69 100644 --- a/wgpu-core/src/conv.rs +++ b/wgpu-core/src/conv.rs @@ -93,6 +93,14 @@ pub fn map_buffer_usage(usage: wgt::BufferUsages) -> hal::BufferUses { hal::BufferUses::QUERY_RESOLVE, usage.contains(wgt::BufferUsages::QUERY_RESOLVE), ); + u.set( + hal::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + usage.contains(wgt::BufferUsages::BLAS_INPUT), + ); + u.set( + hal::BufferUses::TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT, + usage.contains(wgt::BufferUsages::TLAS_INPUT), + ); u } diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs index 583d3e03d..b6ad2354c 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -732,6 +732,7 @@ impl Global { buffer_storage: &Storage>, sampler_storage: &Storage>, texture_view_storage: &Storage>, + tlas_storage: &Storage>, ) -> Result, binding_model::CreateBindGroupError> { let resolve_buffer = |bb: &BufferBinding| { @@ -757,6 +758,12 @@ impl Global { .get() .map_err(binding_model::CreateBindGroupError::from) }; + let resolve_tlas = |id: &id::TlasId| { + tlas_storage + .get(*id) + .get() + .map_err(binding_model::CreateBindGroupError::from) + }; let resource = match e.resource { BindingResource::Buffer(ref buffer) => { ResolvedBindingResource::Buffer(resolve_buffer(buffer)?) @@ -788,6 +795,9 @@ impl Global { .collect::, _>>()?; ResolvedBindingResource::TextureViewArray(Cow::Owned(views)) } + BindingResource::AccelerationStructure(ref tlas) => { + ResolvedBindingResource::AccelerationStructure(resolve_tlas(tlas)?) + } }; Ok(ResolvedBindGroupEntry { binding: e.binding, @@ -799,9 +809,18 @@ impl Global { let buffer_guard = hub.buffers.read(); let texture_view_guard = hub.texture_views.read(); let sampler_guard = hub.samplers.read(); + let tlas_guard = hub.tlas_s.read(); desc.entries .iter() - .map(|e| resolve_entry(e, &buffer_guard, &sampler_guard, &texture_view_guard)) + .map(|e| { + resolve_entry( + e, + &buffer_guard, + &sampler_guard, + &texture_view_guard, + &tlas_guard, + ) + }) .collect::, _>>() }; let entries = match entries { @@ -2147,33 +2166,27 @@ impl Global { offset: BufferAddress, size: Option, op: BufferMapOperation, - ) -> BufferAccessResult { + ) -> Result { profiling::scope!("Buffer::map_async"); api_log!("Buffer::map_async {buffer_id:?} offset {offset:?} size {size:?} op: {op:?}"); let hub = &self.hub; - let op_and_err = 'error: { - let buffer = match hub.buffers.get(buffer_id).get() { - Ok(buffer) => buffer, - Err(e) => break 'error Some((op, e.into())), - }; - - buffer.map_async(offset, size, op).err() + let map_result = match hub.buffers.get(buffer_id).get() { + Ok(buffer) => buffer.map_async(offset, size, op), + Err(e) => Err((op, e.into())), }; - // User callbacks must not be called while holding `buffer.map_async`'s locks, so we - // defer the error callback if it needs to be called immediately (typically when running - // into errors). - if let Some((mut operation, err)) = op_and_err { - if let Some(callback) = operation.callback.take() { - callback.call(Err(err.clone())); + match map_result { + Ok(submission_index) => Ok(submission_index), + Err((mut operation, err)) => { + if let Some(callback) = operation.callback.take() { + callback.call(Err(err.clone())); + } + log::error!("Buffer::map_async error: {err}"); + Err(err) } - log::error!("Buffer::map_async error: {err}"); - return Err(err); } - - Ok(()) } pub fn buffer_get_mapped_range( diff --git a/wgpu-core/src/device/life.rs b/wgpu-core/src/device/life.rs index e6aed78a0..70e5337a7 100644 --- a/wgpu-core/src/device/life.rs +++ b/wgpu-core/src/device/life.rs @@ -9,6 +9,7 @@ use crate::{ }; use smallvec::SmallVec; +use crate::resource::{Blas, Tlas}; use std::sync::Arc; use thiserror::Error; @@ -103,6 +104,44 @@ impl ActiveSubmission { false } + + pub fn contains_blas(&self, blas: &Blas) -> bool { + for encoder in &self.encoders { + // The ownership location of blas's depends on where the command encoder + // came from. If it is the staging command encoder on the queue, it is + // in the pending buffer list. If it came from a user command encoder, + // it is in the tracker. + + if encoder.trackers.blas_s.contains(blas) { + return true; + } + + if encoder.pending_blas_s.contains_key(&blas.tracker_index()) { + return true; + } + } + + false + } + + pub fn contains_tlas(&self, tlas: &Tlas) -> bool { + for encoder in &self.encoders { + // The ownership location of tlas's depends on where the command encoder + // came from. If it is the staging command encoder on the queue, it is + // in the pending buffer list. If it came from a user command encoder, + // it is in the tracker. + + if encoder.trackers.tlas_s.contains(tlas) { + return true; + } + + if encoder.pending_tlas_s.contains_key(&tlas.tracker_index()) { + return true; + } + } + + false + } } #[derive(Clone, Debug, Error)] @@ -126,35 +165,20 @@ pub enum WaitIdleError { /// - Each buffer's `ResourceInfo::submission_index` records the index of the /// most recent queue submission that uses that buffer. /// -/// - Calling `Global::buffer_map_async` adds the buffer to -/// `self.mapped`, and changes `Buffer::map_state` to prevent it -/// from being used in any new submissions. -/// /// - When the device is polled, the following `LifetimeTracker` methods decide /// what should happen next: /// -/// 1) `triage_mapped` drains `self.mapped`, checking the submission index -/// of each buffer against the queue submissions that have finished -/// execution. Buffers used by submissions still in flight go in -/// `self.active[index].mapped`, and the rest go into -/// `self.ready_to_map`. -/// -/// 2) `triage_submissions` moves entries in `self.active[i]` for completed +/// 1) `triage_submissions` moves entries in `self.active[i]` for completed /// submissions to `self.ready_to_map`. At this point, both /// `self.active` and `self.ready_to_map` are up to date with the given /// submission index. /// -/// 3) `handle_mapping` drains `self.ready_to_map` and actually maps the +/// 2) `handle_mapping` drains `self.ready_to_map` and actually maps the /// buffers, collecting a list of notification closures to call. /// /// Only calling `Global::buffer_map_async` clones a new `Arc` for the /// buffer. This new `Arc` is only dropped by `handle_mapping`. pub(crate) struct LifetimeTracker { - /// Buffers for which a call to [`Buffer::map_async`] has succeeded, but - /// which haven't been examined by `triage_mapped` yet to decide when they - /// can be mapped. - mapped: Vec>, - /// Resources used by queue submissions still in flight. One entry per /// submission, with older submissions appearing before younger. /// @@ -182,7 +206,6 @@ pub(crate) struct LifetimeTracker { impl LifetimeTracker { pub fn new() -> Self { Self { - mapped: Vec::new(), active: Vec::new(), ready_to_map: Vec::new(), work_done_closures: SmallVec::new(), @@ -211,8 +234,21 @@ impl LifetimeTracker { }); } - pub(crate) fn map(&mut self, value: &Arc) { - self.mapped.push(value.clone()); + pub(crate) fn map(&mut self, buffer: &Arc) -> Option { + // Determine which buffers are ready to map, and which must wait for the GPU. + let submission = self + .active + .iter_mut() + .rev() + .find(|a| a.contains_buffer(buffer)); + + let maybe_submission_index = submission.as_ref().map(|s| s.index); + + submission + .map_or(&mut self.ready_to_map, |a| &mut a.mapped) + .push(buffer.clone()); + + maybe_submission_index } /// Returns the submission index of the most recent submission that uses the @@ -229,6 +265,34 @@ impl LifetimeTracker { }) } + /// Returns the submission index of the most recent submission that uses the + /// given blas. + pub fn get_blas_latest_submission_index(&self, blas: &Blas) -> Option { + // We iterate in reverse order, so that we can bail out early as soon + // as we find a hit. + self.active.iter().rev().find_map(|submission| { + if submission.contains_blas(blas) { + Some(submission.index) + } else { + None + } + }) + } + + /// Returns the submission index of the most recent submission that uses the + /// given tlas. + pub fn get_tlas_latest_submission_index(&self, tlas: &Tlas) -> Option { + // We iterate in reverse order, so that we can bail out early as soon + // as we find a hit. + self.active.iter().rev().find_map(|submission| { + if submission.contains_tlas(tlas) { + Some(submission.index) + } else { + None + } + }) + } + /// Returns the submission index of the most recent submission that uses the /// given texture. pub fn get_texture_latest_submission_index( @@ -304,41 +368,24 @@ impl LifetimeTracker { } } - pub fn add_work_done_closure(&mut self, closure: SubmittedWorkDoneClosure) { + pub fn add_work_done_closure( + &mut self, + closure: SubmittedWorkDoneClosure, + ) -> Option { match self.active.last_mut() { Some(active) => { active.work_done_closures.push(closure); + Some(active.index) } // We must defer the closure until all previously occurring map_async closures // have fired. This is required by the spec. None => { self.work_done_closures.push(closure); + None } } } - /// Determine which buffers are ready to map, and which must wait for the - /// GPU. - /// - /// See the documentation for [`LifetimeTracker`] for details. - pub(crate) fn triage_mapped(&mut self) { - if self.mapped.is_empty() { - return; - } - - for buffer in self.mapped.drain(..) { - let submission = self - .active - .iter_mut() - .rev() - .find(|a| a.contains_buffer(&buffer)); - - submission - .map_or(&mut self.ready_to_map, |a| &mut a.mapped) - .push(buffer); - } - } - /// Map the buffers in `self.ready_to_map`. /// /// Return a list of mapping notifications to send. diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index e06195a50..2230ee041 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -22,6 +22,7 @@ pub(crate) mod bgl; pub mod global; mod life; pub mod queue; +pub mod ray_tracing; pub mod resource; #[cfg(any(feature = "trace", feature = "replay"))] pub mod trace; @@ -548,6 +549,10 @@ pub fn create_validator( Caps::SUBGROUP_BARRIER, features.intersects(wgt::Features::SUBGROUP_BARRIER), ); + caps.set( + Caps::RAY_QUERY, + features.intersects(wgt::Features::EXPERIMENTAL_RAY_QUERY), + ); caps.set( Caps::SUBGROUP_VERTEX_STAGE, features.contains(wgt::Features::SUBGROUP_VERTEX), diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index da4ee6395..ade609f20 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -27,6 +27,8 @@ use crate::{ use smallvec::SmallVec; +use crate::resource::{Blas, DestroyedAccelerationStructure, Tlas}; +use crate::scratch::ScratchBuffer; use std::{ iter, mem::{self, ManuallyDrop}, @@ -143,8 +145,10 @@ impl SubmittedWorkDoneClosure { #[derive(Debug)] pub enum TempResource { StagingBuffer(FlushedStagingBuffer), + ScratchBuffer(ScratchBuffer), DestroyedBuffer(DestroyedBuffer), DestroyedTexture(DestroyedTexture), + DestroyedAccelerationStructure(DestroyedAccelerationStructure), } /// A series of raw [`CommandBuffer`]s that have been submitted to a @@ -161,6 +165,10 @@ pub(crate) struct EncoderInFlight { pub(crate) pending_buffers: FastHashMap>, /// These are the textures that have been tracked by `PendingWrites`. pub(crate) pending_textures: FastHashMap>, + /// These are the BLASes that have been tracked by `PendingWrites`. + pub(crate) pending_blas_s: FastHashMap>, + /// These are the TLASes that have been tracked by `PendingWrites`. + pub(crate) pending_tlas_s: FastHashMap>, } impl EncoderInFlight { @@ -177,6 +185,8 @@ impl EncoderInFlight { drop(self.trackers); drop(self.pending_buffers); drop(self.pending_textures); + drop(self.pending_blas_s); + drop(self.pending_tlas_s); } self.raw } @@ -216,6 +226,8 @@ pub(crate) struct PendingWrites { temp_resources: Vec, dst_buffers: FastHashMap>, dst_textures: FastHashMap>, + dst_blas_s: FastHashMap>, + dst_tlas_s: FastHashMap>, } impl PendingWrites { @@ -226,6 +238,8 @@ impl PendingWrites { temp_resources: Vec::new(), dst_buffers: FastHashMap::default(), dst_textures: FastHashMap::default(), + dst_blas_s: FastHashMap::default(), + dst_tlas_s: FastHashMap::default(), } } @@ -258,6 +272,22 @@ impl PendingWrites { self.dst_textures.contains_key(&texture.tracker_index()) } + pub fn insert_blas(&mut self, blas: &Arc) { + self.dst_blas_s.insert(blas.tracker_index(), blas.clone()); + } + + pub fn insert_tlas(&mut self, tlas: &Arc) { + self.dst_tlas_s.insert(tlas.tracker_index(), tlas.clone()); + } + + pub fn contains_blas(&mut self, blas: &Arc) -> bool { + self.dst_blas_s.contains_key(&blas.tracker_index()) + } + + pub fn contains_tlas(&mut self, tlas: &Arc) -> bool { + self.dst_tlas_s.contains_key(&tlas.tracker_index()) + } + pub fn consume_temp(&mut self, resource: TempResource) { self.temp_resources.push(resource); } @@ -276,6 +306,8 @@ impl PendingWrites { if self.is_recording { let pending_buffers = mem::take(&mut self.dst_buffers); let pending_textures = mem::take(&mut self.dst_textures); + let pending_blas_s = mem::take(&mut self.dst_blas_s); + let pending_tlas_s = mem::take(&mut self.dst_tlas_s); let cmd_buf = unsafe { self.command_encoder.end_encoding() } .map_err(|e| device.handle_hal_error(e))?; @@ -291,6 +323,8 @@ impl PendingWrites { trackers: Tracker::new(), pending_buffers, pending_textures, + pending_blas_s, + pending_tlas_s, }; Ok(Some(encoder)) } else { @@ -358,6 +392,10 @@ pub enum QueueSubmitError { InvalidResource(#[from] InvalidResourceError), #[error(transparent)] CommandEncoder(#[from] CommandEncoderError), + #[error(transparent)] + ValidateBlasActionsError(#[from] crate::ray_tracing::ValidateBlasActionsError), + #[error(transparent)] + ValidateTlasActionsError(#[from] crate::ray_tracing::ValidateTlasActionsError), } //TODO: move out common parts of write_xxx. @@ -1117,6 +1155,8 @@ impl Queue { trackers: baked.trackers, pending_buffers: FastHashMap::default(), pending_textures: FastHashMap::default(), + pending_blas_s: FastHashMap::default(), + pending_tlas_s: FastHashMap::default(), }); } @@ -1256,10 +1296,13 @@ impl Queue { unsafe { self.raw().get_timestamp_period() } } - pub fn on_submitted_work_done(&self, closure: SubmittedWorkDoneClosure) { + pub fn on_submitted_work_done( + &self, + closure: SubmittedWorkDoneClosure, + ) -> Option { api_log!("Queue::on_submitted_work_done"); //TODO: flush pending writes - self.device.lock_life().add_work_done_closure(closure); + self.device.lock_life().add_work_done_closure(closure) } } @@ -1402,9 +1445,13 @@ impl Global { &self, queue_id: QueueId, closure: SubmittedWorkDoneClosure, - ) { + ) -> SubmissionIndex { + api_log!("Queue::on_submitted_work_done {queue_id:?}"); + + //TODO: flush pending writes let queue = self.hub.queues.get(queue_id); - queue.on_submitted_work_done(closure); + let result = queue.on_submitted_work_done(closure); + result.unwrap_or(0) // '0' means no wait is necessary } } @@ -1455,6 +1502,13 @@ fn validate_command_buffer( } } } + + if let Err(e) = cmd_buf_data.validate_blas_actions() { + return Err(e.into()); + } + if let Err(e) = cmd_buf_data.validate_tlas_actions(snatch_guard) { + return Err(e.into()); + } } Ok(()) } diff --git a/wgpu-core/src/device/ray_tracing.rs b/wgpu-core/src/device/ray_tracing.rs new file mode 100644 index 000000000..baab42091 --- /dev/null +++ b/wgpu-core/src/device/ray_tracing.rs @@ -0,0 +1,355 @@ +use std::mem::ManuallyDrop; +use std::sync::Arc; + +#[cfg(feature = "trace")] +use crate::device::trace; +use crate::lock::{rank, Mutex}; +use crate::resource::{Fallible, TrackingData}; +use crate::snatch::Snatchable; +use crate::weak_vec::WeakVec; +use crate::{ + device::{Device, DeviceError}, + global::Global, + id::{self, BlasId, TlasId}, + lock::RwLock, + ray_tracing::{get_raw_tlas_instance_size, CreateBlasError, CreateTlasError}, + resource, LabelHelpers, +}; +use hal::AccelerationStructureTriangleIndices; +use wgt::Features; + +impl Device { + fn create_blas( + self: &Arc, + blas_desc: &resource::BlasDescriptor, + sizes: wgt::BlasGeometrySizeDescriptors, + ) -> Result, CreateBlasError> { + let size_info = match &sizes { + wgt::BlasGeometrySizeDescriptors::Triangles { descriptors } => { + let mut entries = + Vec::>::with_capacity( + descriptors.len(), + ); + for desc in descriptors { + if desc.index_count.is_some() != desc.index_format.is_some() { + return Err(CreateBlasError::MissingIndexData); + } + let indices = + desc.index_count + .map(|count| AccelerationStructureTriangleIndices::< + dyn hal::DynBuffer, + > { + format: desc.index_format.unwrap(), + buffer: None, + offset: 0, + count, + }); + if !self + .features + .allowed_vertex_formats_for_blas() + .contains(&desc.vertex_format) + { + return Err(CreateBlasError::InvalidVertexFormat( + desc.vertex_format, + self.features.allowed_vertex_formats_for_blas(), + )); + } + entries.push(hal::AccelerationStructureTriangles:: { + vertex_buffer: None, + vertex_format: desc.vertex_format, + first_vertex: 0, + vertex_count: desc.vertex_count, + vertex_stride: 0, + indices, + transform: None, + flags: desc.flags, + }); + } + unsafe { + self.raw().get_acceleration_structure_build_sizes( + &hal::GetAccelerationStructureBuildSizesDescriptor { + entries: &hal::AccelerationStructureEntries::Triangles(entries), + flags: blas_desc.flags, + }, + ) + } + } + }; + + let raw = unsafe { + self.raw() + .create_acceleration_structure(&hal::AccelerationStructureDescriptor { + label: blas_desc.label.as_deref(), + size: size_info.acceleration_structure_size, + format: hal::AccelerationStructureFormat::BottomLevel, + }) + } + .map_err(DeviceError::from_hal)?; + + let handle = unsafe { + self.raw() + .get_acceleration_structure_device_address(raw.as_ref()) + }; + + Ok(Arc::new(resource::Blas { + raw: Snatchable::new(raw), + device: self.clone(), + size_info, + sizes, + flags: blas_desc.flags, + update_mode: blas_desc.update_mode, + handle, + label: blas_desc.label.to_string(), + built_index: RwLock::new(rank::BLAS_BUILT_INDEX, None), + tracking_data: TrackingData::new(self.tracker_indices.blas_s.clone()), + })) + } + + fn create_tlas( + self: &Arc, + desc: &resource::TlasDescriptor, + ) -> Result, CreateTlasError> { + let size_info = unsafe { + self.raw().get_acceleration_structure_build_sizes( + &hal::GetAccelerationStructureBuildSizesDescriptor { + entries: &hal::AccelerationStructureEntries::Instances( + hal::AccelerationStructureInstances { + buffer: None, + offset: 0, + count: desc.max_instances, + }, + ), + flags: desc.flags, + }, + ) + }; + + let raw = unsafe { + self.raw() + .create_acceleration_structure(&hal::AccelerationStructureDescriptor { + label: desc.label.as_deref(), + size: size_info.acceleration_structure_size, + format: hal::AccelerationStructureFormat::TopLevel, + }) + } + .map_err(DeviceError::from_hal)?; + + let instance_buffer_size = + get_raw_tlas_instance_size(self.backend()) * desc.max_instances.max(1) as usize; + let instance_buffer = unsafe { + self.raw().create_buffer(&hal::BufferDescriptor { + label: Some("(wgpu-core) instances_buffer"), + size: instance_buffer_size as u64, + usage: hal::BufferUses::COPY_DST + | hal::BufferUses::TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT, + memory_flags: hal::MemoryFlags::PREFER_COHERENT, + }) + } + .map_err(DeviceError::from_hal)?; + + Ok(Arc::new(resource::Tlas { + raw: Snatchable::new(raw), + device: self.clone(), + size_info, + flags: desc.flags, + update_mode: desc.update_mode, + built_index: RwLock::new(rank::TLAS_BUILT_INDEX, None), + dependencies: RwLock::new(rank::TLAS_DEPENDENCIES, Vec::new()), + instance_buffer: ManuallyDrop::new(instance_buffer), + label: desc.label.to_string(), + max_instance_count: desc.max_instances, + tracking_data: TrackingData::new(self.tracker_indices.tlas_s.clone()), + bind_groups: Mutex::new(rank::TLAS_BIND_GROUPS, WeakVec::new()), + })) + } +} + +impl Global { + pub fn device_create_blas( + &self, + device_id: id::DeviceId, + desc: &resource::BlasDescriptor, + sizes: wgt::BlasGeometrySizeDescriptors, + id_in: Option, + ) -> (BlasId, Option, Option) { + profiling::scope!("Device::create_blas"); + + let hub = &self.hub; + let fid = hub.blas_s.prepare(id_in); + + let device_guard = hub.devices.read(); + let error = 'error: { + let device = device_guard.get(device_id); + match device.check_is_valid() { + Ok(_) => {} + Err(err) => break 'error CreateBlasError::Device(err), + }; + + if !device + .features + .contains(Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + { + break 'error CreateBlasError::MissingFeature; + } + + #[cfg(feature = "trace")] + if let Some(trace) = device.trace.lock().as_mut() { + trace.add(trace::Action::CreateBlas { + id: fid.id(), + desc: desc.clone(), + sizes: sizes.clone(), + }); + } + + let blas = match device.create_blas(desc, sizes) { + Ok(blas) => blas, + Err(e) => break 'error e, + }; + let handle = blas.handle; + + let id = fid.assign(Fallible::Valid(blas.clone())); + log::info!("Created blas {:?} with {:?}", id, desc); + + return (id, Some(handle), None); + }; + + let id = fid.assign(Fallible::Invalid(Arc::new(error.to_string()))); + (id, None, Some(error)) + } + + pub fn device_create_tlas( + &self, + device_id: id::DeviceId, + desc: &resource::TlasDescriptor, + id_in: Option, + ) -> (TlasId, Option) { + profiling::scope!("Device::create_tlas"); + + let hub = &self.hub; + let fid = hub.tlas_s.prepare(id_in); + + let device_guard = hub.devices.read(); + let error = 'error: { + let device = device_guard.get(device_id); + match device.check_is_valid() { + Ok(_) => {} + Err(e) => break 'error CreateTlasError::Device(e), + } + + if !device + .features + .contains(Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + { + break 'error CreateTlasError::MissingFeature; + } + + #[cfg(feature = "trace")] + if let Some(trace) = device.trace.lock().as_mut() { + trace.add(trace::Action::CreateTlas { + id: fid.id(), + desc: desc.clone(), + }); + } + + let tlas = match device.create_tlas(desc) { + Ok(tlas) => tlas, + Err(e) => break 'error e, + }; + + let id = fid.assign(Fallible::Valid(tlas)); + log::info!("Created tlas {:?} with {:?}", id, desc); + + return (id, None); + }; + + let id = fid.assign(Fallible::Invalid(Arc::new(error.to_string()))); + (id, Some(error)) + } + + pub fn blas_destroy(&self, blas_id: BlasId) -> Result<(), resource::DestroyError> { + profiling::scope!("Blas::destroy"); + log::info!("Blas::destroy {blas_id:?}"); + + let hub = &self.hub; + + let blas = hub.blas_s.get(blas_id).get()?; + let _device = &blas.device; + + #[cfg(feature = "trace")] + if let Some(trace) = _device.trace.lock().as_mut() { + trace.add(trace::Action::FreeBlas(blas_id)); + } + + blas.destroy() + } + + pub fn blas_drop(&self, blas_id: BlasId) { + profiling::scope!("Blas::drop"); + log::debug!("blas {:?} is dropped", blas_id); + + let hub = &self.hub; + + let _blas = match hub.blas_s.remove(blas_id).get() { + Ok(blas) => blas, + Err(_) => { + return; + } + }; + + #[cfg(feature = "trace")] + { + let mut lock = _blas.device.trace.lock(); + + if let Some(t) = lock.as_mut() { + t.add(trace::Action::DestroyBlas(blas_id)); + } + } + } + + pub fn tlas_destroy(&self, tlas_id: TlasId) -> Result<(), resource::DestroyError> { + profiling::scope!("Tlas::destroy"); + + let hub = &self.hub; + + log::info!("Tlas {:?} is destroyed", tlas_id); + let tlas_guard = hub.tlas_s.write(); + let tlas = tlas_guard + .get(tlas_id) + .get() + .map_err(resource::DestroyError::InvalidResource)? + .clone(); + drop(tlas_guard); + + let _device = &mut tlas.device.clone(); + + #[cfg(feature = "trace")] + if let Some(trace) = _device.trace.lock().as_mut() { + trace.add(trace::Action::FreeTlas(tlas_id)); + } + + tlas.destroy() + } + + pub fn tlas_drop(&self, tlas_id: TlasId) { + profiling::scope!("Tlas::drop"); + log::debug!("tlas {:?} is dropped", tlas_id); + + let hub = &self.hub; + + let _tlas = match hub.tlas_s.remove(tlas_id).get() { + Ok(tlas) => tlas, + Err(_) => { + return; + } + }; + + #[cfg(feature = "trace")] + { + let mut lock = _tlas.device.trace.lock(); + + if let Some(t) = lock.as_mut() { + t.add(trace::Action::DestroyTlas(tlas_id)); + } + } + } +} diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 21ecf85d2..afbf73bc0 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -41,6 +41,7 @@ use wgt::{ math::align_to, DeviceLostReason, TextureFormat, TextureSampleType, TextureViewDimension, }; +use crate::resource::{AccelerationStructure, DestroyedResourceError, Tlas}; use std::{ borrow::Cow, mem::{self, ManuallyDrop}, @@ -145,6 +146,7 @@ pub struct Device { #[cfg(feature = "trace")] pub(crate) trace: Mutex>, pub(crate) usage_scopes: UsageScopePool, + pub(crate) last_acceleration_structure_build_command_index: AtomicU64, #[cfg(feature = "indirect-validation")] pub(crate) indirect_validation: Option, @@ -334,6 +336,8 @@ impl Device { ), deferred_destroy: Mutex::new(rank::DEVICE_DEFERRED_DESTROY, Vec::new()), usage_scopes: Mutex::new(rank::DEVICE_USAGE_SCOPES, Default::default()), + // By starting at one, we can put the result in a NonZeroU64. + last_acceleration_structure_build_command_index: AtomicU64::new(1), #[cfg(feature = "indirect-validation")] indirect_validation, }) @@ -493,8 +497,6 @@ impl Device { let submission_closures = life_tracker.triage_submissions(submission_index, &self.command_allocator); - life_tracker.triage_mapped(); - let mapping_closures = life_tracker.handle_mapping(self.raw(), &snatch_guard); let queue_empty = life_tracker.queue_empty(); @@ -1863,7 +1865,7 @@ impl Device { }, ) } - Bt::AccelerationStructure => todo!(), + Bt::AccelerationStructure => (None, WritableStorage::No), }; // Validate the count parameter @@ -2190,6 +2192,36 @@ impl Device { }) } + fn create_tlas_binding<'a>( + self: &Arc, + used: &mut BindGroupStates, + binding: u32, + decl: &wgt::BindGroupLayoutEntry, + tlas: &'a Arc, + snatch_guard: &'a SnatchGuard<'a>, + ) -> Result<&'a dyn hal::DynAccelerationStructure, binding_model::CreateBindGroupError> { + use crate::binding_model::CreateBindGroupError as Error; + + used.acceleration_structures.insert_single(tlas.clone()); + + tlas.same_device(self)?; + + match decl.ty { + wgt::BindingType::AccelerationStructure => (), + _ => { + return Err(Error::WrongBindingType { + binding, + actual: decl.ty, + expected: "Tlas", + }); + } + } + + Ok(tlas + .raw(snatch_guard) + .ok_or(DestroyedResourceError(tlas.error_ident()))?) + } + // This function expects the provided bind group layout to be resolved // (not passing a duplicate) beforehand. pub(crate) fn create_bind_group( @@ -2229,6 +2261,7 @@ impl Device { let mut hal_buffers = Vec::new(); let mut hal_samplers = Vec::new(); let mut hal_textures = Vec::new(); + let mut hal_tlas_s = Vec::new(); let snatch_guard = self.snatchable_lock.read(); for entry in desc.entries.iter() { let binding = entry.binding; @@ -2328,6 +2361,13 @@ impl Device { (res_index, num_bindings) } + Br::AccelerationStructure(ref tlas) => { + let tlas = + self.create_tlas_binding(&mut used, binding, decl, tlas, &snatch_guard)?; + let res_index = hal_tlas_s.len(); + hal_tlas_s.push(tlas); + (res_index, 1) + } }; hal_entries.push(hal::BindGroupEntry { @@ -2352,7 +2392,7 @@ impl Device { buffers: &hal_buffers, samplers: &hal_samplers, textures: &hal_textures, - acceleration_structures: &[], + acceleration_structures: &hal_tlas_s, }; let raw = unsafe { self.raw().create_bind_group(&hal_desc) } .map_err(|e| self.handle_hal_error(e))?; @@ -3607,7 +3647,8 @@ impl Device { let hal_desc = desc.map_label(|label| label.to_hal(self.instance_flags)); - let raw = unsafe { self.raw().create_query_set(&hal_desc).unwrap() }; + let raw = unsafe { self.raw().create_query_set(&hal_desc) } + .map_err(|e| self.handle_hal_error(e))?; let query_set = QuerySet { raw: ManuallyDrop::new(raw), diff --git a/wgpu-core/src/device/trace.rs b/wgpu-core/src/device/trace.rs index ff4eea47b..89b7b48f9 100644 --- a/wgpu-core/src/device/trace.rs +++ b/wgpu-core/src/device/trace.rs @@ -127,6 +127,19 @@ pub enum Action<'a> { size: wgt::Extent3d, }, Submit(crate::SubmissionIndex, Vec), + CreateBlas { + id: id::BlasId, + desc: crate::resource::BlasDescriptor<'a>, + sizes: wgt::BlasGeometrySizeDescriptors, + }, + FreeBlas(id::BlasId), + DestroyBlas(id::BlasId), + CreateTlas { + id: id::TlasId, + desc: crate::resource::TlasDescriptor<'a>, + }, + FreeTlas(id::TlasId), + DestroyTlas(id::TlasId), } #[derive(Debug)] @@ -188,6 +201,14 @@ pub enum Command { timestamp_writes: Option, occlusion_query_set_id: Option, }, + BuildAccelerationStructuresUnsafeTlas { + blas: Vec, + tlas: Vec, + }, + BuildAccelerationStructures { + blas: Vec, + tlas: Vec, + }, } #[cfg(feature = "trace")] diff --git a/wgpu-core/src/hub.rs b/wgpu-core/src/hub.rs index ae6cbbb87..15d3e3c06 100644 --- a/wgpu-core/src/hub.rs +++ b/wgpu-core/src/hub.rs @@ -107,7 +107,9 @@ use crate::{ instance::Adapter, pipeline::{ComputePipeline, PipelineCache, RenderPipeline, ShaderModule}, registry::{Registry, RegistryReport}, - resource::{Buffer, Fallible, QuerySet, Sampler, StagingBuffer, Texture, TextureView}, + resource::{ + Blas, Buffer, Fallible, QuerySet, Sampler, StagingBuffer, Texture, TextureView, Tlas, + }, }; use std::{fmt::Debug, sync::Arc}; @@ -178,6 +180,8 @@ pub struct Hub { pub(crate) textures: Registry>, pub(crate) texture_views: Registry>, pub(crate) samplers: Registry>, + pub(crate) blas_s: Registry>, + pub(crate) tlas_s: Registry>, } impl Hub { @@ -201,6 +205,8 @@ impl Hub { textures: Registry::new(), texture_views: Registry::new(), samplers: Registry::new(), + blas_s: Registry::new(), + tlas_s: Registry::new(), } } diff --git a/wgpu-core/src/id.rs b/wgpu-core/src/id.rs index 4e4897c83..fbf366982 100644 --- a/wgpu-core/src/id.rs +++ b/wgpu-core/src/id.rs @@ -261,6 +261,9 @@ ids! { pub type RenderBundleEncoderId RenderBundleEncoder; pub type RenderBundleId RenderBundle; pub type QuerySetId QuerySet; + pub type BlasId Blas; + pub type TlasId Tlas; + pub type TlasInstanceId TlasInstance; } // The CommandBuffer type serves both as encoder and diff --git a/wgpu-core/src/lib.rs b/wgpu-core/src/lib.rs index b87b2cd63..1edb27e7a 100644 --- a/wgpu-core/src/lib.rs +++ b/wgpu-core/src/lib.rs @@ -76,6 +76,7 @@ pub mod pipeline; mod pipeline_cache; mod pool; pub mod present; +pub mod ray_tracing; pub mod registry; pub mod resource; mod snatch; @@ -86,6 +87,7 @@ mod weak_vec; // preserve all run-time checks that `wgpu-core` does. // See , after which this can be // made private again. +mod scratch; pub mod validation; pub use hal::{api, MAX_BIND_GROUPS, MAX_COLOR_ATTACHMENTS, MAX_VERTEX_BUFFERS}; diff --git a/wgpu-core/src/lock/rank.rs b/wgpu-core/src/lock/rank.rs index abb5cb002..842dadf26 100644 --- a/wgpu-core/src/lock/rank.rs +++ b/wgpu-core/src/lock/rank.rs @@ -144,6 +144,10 @@ define_lock_ranks! { rank TEXTURE_BIND_GROUPS "Texture::bind_groups" followed by { } rank TEXTURE_INITIALIZATION_STATUS "Texture::initialization_status" followed by { } rank TEXTURE_VIEWS "Texture::views" followed by { } + rank BLAS_BUILT_INDEX "Blas::built_index" followed by { } + rank TLAS_BUILT_INDEX "Tlas::built_index" followed by { } + rank TLAS_DEPENDENCIES "Tlas::dependencies" followed by { } + rank TLAS_BIND_GROUPS "Tlas::bind_groups" followed by { } #[cfg(test)] rank PAWN "pawn" followed by { ROOK, BISHOP } diff --git a/wgpu-core/src/ray_tracing.rs b/wgpu-core/src/ray_tracing.rs new file mode 100644 index 000000000..11ccb714f --- /dev/null +++ b/wgpu-core/src/ray_tracing.rs @@ -0,0 +1,337 @@ +// Ray tracing +// Major missing optimizations (no api surface changes needed): +// - use custom tracker to track build state +// - no forced rebuilt (build mode deduction) +// - lazy instance buffer allocation +// - maybe share scratch and instance staging buffer allocation +// - partial instance buffer uploads (api surface already designed with this in mind) +// - ([non performance] extract function in build (rust function extraction with guards is a pain)) + +use crate::{ + command::CommandEncoderError, + device::DeviceError, + id::{BlasId, BufferId, TlasId}, + resource::CreateBufferError, +}; +use std::sync::Arc; +use std::{num::NonZeroU64, slice}; + +use crate::resource::{Blas, ResourceErrorIdent, Tlas}; +use thiserror::Error; +use wgt::{AccelerationStructureGeometryFlags, BufferAddress, IndexFormat, VertexFormat}; + +#[derive(Clone, Debug, Error)] +pub enum CreateBlasError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error(transparent)] + CreateBufferError(#[from] CreateBufferError), + #[error( + "Only one of 'index_count' and 'index_format' was provided (either provide both or none)" + )] + MissingIndexData, + #[error("Provided format was not within allowed formats. Provided format: {0:?}. Allowed formats: {1:?}")] + InvalidVertexFormat(VertexFormat, Vec), + #[error("Features::RAY_TRACING_ACCELERATION_STRUCTURE is not enabled")] + MissingFeature, +} + +#[derive(Clone, Debug, Error)] +pub enum CreateTlasError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error(transparent)] + CreateBufferError(#[from] CreateBufferError), + #[error("Features::RAY_TRACING_ACCELERATION_STRUCTURE is not enabled")] + MissingFeature, + #[error("Unimplemented Tlas error: this error is not yet implemented")] + Unimplemented, +} + +/// Error encountered while attempting to do a copy on a command encoder. +#[derive(Clone, Debug, Error)] +pub enum BuildAccelerationStructureError { + #[error(transparent)] + Encoder(#[from] CommandEncoderError), + + #[error(transparent)] + Device(#[from] DeviceError), + + #[error("BufferId is invalid or destroyed")] + InvalidBufferId, + + #[error("Buffer {0:?} is invalid or destroyed")] + InvalidBuffer(ResourceErrorIdent), + + #[error("Buffer {0:?} is missing `BLAS_INPUT` usage flag")] + MissingBlasInputUsageFlag(ResourceErrorIdent), + + #[error( + "Buffer {0:?} size is insufficient for provided size information (size: {1}, required: {2}" + )] + InsufficientBufferSize(ResourceErrorIdent, u64, u64), + + #[error("Buffer {0:?} associated offset doesn't align with the index type")] + UnalignedIndexBufferOffset(ResourceErrorIdent), + + #[error("Buffer {0:?} associated offset is unaligned")] + UnalignedTransformBufferOffset(ResourceErrorIdent), + + #[error("Buffer {0:?} associated index count not divisible by 3 (count: {1}")] + InvalidIndexCount(ResourceErrorIdent, u32), + + #[error("Buffer {0:?} associated data contains None")] + MissingAssociatedData(ResourceErrorIdent), + + #[error( + "Blas {0:?} build sizes to may be greater than the descriptor at build time specified" + )] + IncompatibleBlasBuildSizes(ResourceErrorIdent), + + #[error("Blas {0:?} flags are different, creation flags: {1:?}, provided: {2:?}")] + IncompatibleBlasFlags( + ResourceErrorIdent, + AccelerationStructureGeometryFlags, + AccelerationStructureGeometryFlags, + ), + + #[error("Blas {0:?} build vertex count is greater than creation count (needs to be less than or equal to), creation: {1:?}, build: {2:?}")] + IncompatibleBlasVertexCount(ResourceErrorIdent, u32, u32), + + #[error("Blas {0:?} vertex formats are different, creation format: {1:?}, provided: {2:?}")] + DifferentBlasVertexFormats(ResourceErrorIdent, VertexFormat, VertexFormat), + + #[error("Blas {0:?} index count was provided at creation or building, but not the other")] + BlasIndexCountProvidedMismatch(ResourceErrorIdent), + + #[error("Blas {0:?} build index count is greater than creation count (needs to be less than or equal to), creation: {1:?}, build: {2:?}")] + IncompatibleBlasIndexCount(ResourceErrorIdent, u32, u32), + + #[error("Blas {0:?} index formats are different, creation format: {1:?}, provided: {2:?}")] + DifferentBlasIndexFormats(ResourceErrorIdent, Option, Option), + + #[error("Blas {0:?} build sizes require index buffer but none was provided")] + MissingIndexBuffer(ResourceErrorIdent), + + #[error("BlasId is invalid")] + InvalidBlasId, + + #[error("Blas {0:?} is destroyed")] + InvalidBlas(ResourceErrorIdent), + + #[error( + "Tlas {0:?} an associated instances contains an invalid custom index (more than 24bits)" + )] + TlasInvalidCustomIndex(ResourceErrorIdent), + + #[error( + "Tlas {0:?} has {1} active instances but only {2} are allowed as specified by the descriptor at creation" + )] + TlasInstanceCountExceeded(ResourceErrorIdent, u32, u32), + + #[error("BlasId is invalid or destroyed (for instance)")] + InvalidBlasIdForInstance, + + #[error("Blas {0:?} is invalid or destroyed (for instance)")] + InvalidBlasForInstance(ResourceErrorIdent), + + #[error("TlasId is invalid or destroyed")] + InvalidTlasId, + + #[error("Tlas {0:?} is invalid or destroyed")] + InvalidTlas(ResourceErrorIdent), + + #[error("Features::RAY_TRACING_ACCELERATION_STRUCTURE is not enabled")] + MissingFeature, + + #[error("Buffer {0:?} is missing `TLAS_INPUT` usage flag")] + MissingTlasInputUsageFlag(ResourceErrorIdent), +} + +#[derive(Clone, Debug, Error)] +pub enum ValidateBlasActionsError { + #[error("BlasId is invalid or destroyed")] + InvalidBlas, + + #[error("Blas {0:?} is used before it is built")] + UsedUnbuilt(ResourceErrorIdent), +} + +#[derive(Clone, Debug, Error)] +pub enum ValidateTlasActionsError { + #[error("Tlas {0:?} is invalid or destroyed")] + InvalidTlas(ResourceErrorIdent), + + #[error("Tlas {0:?} is used before it is built")] + UsedUnbuilt(ResourceErrorIdent), + + #[error("Blas {0:?} is used before it is built (in Tlas {1:?})")] + UsedUnbuiltBlas(ResourceErrorIdent, ResourceErrorIdent), + + #[error("BlasId is destroyed (in Tlas {0:?})")] + InvalidBlas(ResourceErrorIdent), + + #[error("Blas {0:?} is newer than the containing Tlas {1:?}")] + BlasNewerThenTlas(ResourceErrorIdent, ResourceErrorIdent), +} + +#[derive(Debug)] +pub struct BlasTriangleGeometry<'a> { + pub size: &'a wgt::BlasTriangleGeometrySizeDescriptor, + pub vertex_buffer: BufferId, + pub index_buffer: Option, + pub transform_buffer: Option, + pub first_vertex: u32, + pub vertex_stride: BufferAddress, + pub index_buffer_offset: Option, + pub transform_buffer_offset: Option, +} + +pub enum BlasGeometries<'a> { + TriangleGeometries(Box> + 'a>), +} + +pub struct BlasBuildEntry<'a> { + pub blas_id: BlasId, + pub geometries: BlasGeometries<'a>, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TlasBuildEntry { + pub tlas_id: TlasId, + pub instance_buffer_id: BufferId, + pub instance_count: u32, +} + +#[derive(Debug)] +pub struct TlasInstance<'a> { + pub blas_id: BlasId, + pub transform: &'a [f32; 12], + pub custom_index: u32, + pub mask: u8, +} + +pub struct TlasPackage<'a> { + pub tlas_id: TlasId, + pub instances: Box>> + 'a>, + pub lowest_unmodified: u32, +} + +#[derive(Debug, Copy, Clone)] +pub(crate) enum BlasActionKind { + Build(NonZeroU64), + Use, +} + +#[derive(Debug, Clone)] +pub(crate) enum TlasActionKind { + Build { + build_index: NonZeroU64, + dependencies: Vec>, + }, + Use, +} + +#[derive(Debug, Clone)] +pub(crate) struct BlasAction { + pub blas: Arc, + pub kind: BlasActionKind, +} + +#[derive(Debug, Clone)] +pub(crate) struct TlasAction { + pub tlas: Arc, + pub kind: TlasActionKind, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TraceBlasTriangleGeometry { + pub size: wgt::BlasTriangleGeometrySizeDescriptor, + pub vertex_buffer: BufferId, + pub index_buffer: Option, + pub transform_buffer: Option, + pub first_vertex: u32, + pub vertex_stride: BufferAddress, + pub index_buffer_offset: Option, + pub transform_buffer_offset: Option, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TraceBlasGeometries { + TriangleGeometries(Vec), +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TraceBlasBuildEntry { + pub blas_id: BlasId, + pub geometries: TraceBlasGeometries, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TraceTlasInstance { + pub blas_id: BlasId, + pub transform: [f32; 12], + pub custom_index: u32, + pub mask: u8, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TraceTlasPackage { + pub tlas_id: TlasId, + pub instances: Vec>, + pub lowest_unmodified: u32, +} + +pub(crate) fn get_raw_tlas_instance_size(backend: wgt::Backend) -> usize { + // TODO: this should be provided by the backend + match backend { + wgt::Backend::Empty => 0, + wgt::Backend::Vulkan => 64, + _ => unimplemented!(), + } +} + +#[derive(Clone)] +#[repr(C)] +struct RawTlasInstance { + transform: [f32; 12], + custom_index_and_mask: u32, + shader_binding_table_record_offset_and_flags: u32, + acceleration_structure_reference: u64, +} + +pub(crate) fn tlas_instance_into_bytes( + instance: &TlasInstance, + blas_address: u64, + backend: wgt::Backend, +) -> Vec { + // TODO: get the device to do this + match backend { + wgt::Backend::Empty => vec![], + wgt::Backend::Vulkan => { + const MAX_U24: u32 = (1u32 << 24u32) - 1u32; + let temp = RawTlasInstance { + transform: *instance.transform, + custom_index_and_mask: (instance.custom_index & MAX_U24) + | (u32::from(instance.mask) << 24), + shader_binding_table_record_offset_and_flags: 0, + acceleration_structure_reference: blas_address, + }; + let temp: *const _ = &temp; + unsafe { + slice::from_raw_parts::( + temp.cast::(), + std::mem::size_of::(), + ) + .to_vec() + } + } + _ => unimplemented!(), + } +} diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index bddccd939..8ea9dd07e 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -15,12 +15,13 @@ use crate::{ snatch::{SnatchGuard, Snatchable}, track::{SharedTrackerIndexAllocator, TextureSelector, TrackerIndex}, weak_vec::WeakVec, - Label, LabelHelpers, + Label, LabelHelpers, SubmissionIndex, }; use smallvec::SmallVec; use thiserror::Error; +use std::num::NonZeroU64; use std::{ borrow::{Borrow, Cow}, fmt::Debug, @@ -304,7 +305,7 @@ impl BufferMapCallback { // SAFETY: the contract of the call to from_c says that this unsafe is sound. BufferMapCallbackInner::C { inner } => unsafe { let status = match result { - Ok(()) => BufferMapAsyncStatus::Success, + Ok(_) => BufferMapAsyncStatus::Success, Err(BufferAccessError::Device(_)) => BufferMapAsyncStatus::ContextLost, Err(BufferAccessError::InvalidResource(_)) | Err(BufferAccessError::DestroyedResource(_)) => BufferMapAsyncStatus::Invalid, @@ -546,7 +547,7 @@ impl Buffer { offset: wgt::BufferAddress, size: Option, op: BufferMapOperation, - ) -> Result<(), (BufferMapOperation, BufferAccessError)> { + ) -> Result { let range_size = if let Some(size) = size { size } else if offset > self.size { @@ -633,9 +634,9 @@ impl Buffer { .buffers .set_single(self, internal_use); - device.lock_life().map(self); + let submit_index = device.lock_life().map(self).unwrap_or(0); // '0' means no wait is necessary - Ok(()) + Ok(submit_index) } // Note: This must not be called while holding a lock. @@ -1894,3 +1895,201 @@ pub enum DestroyError { #[error(transparent)] InvalidResource(#[from] InvalidResourceError), } + +pub type BlasDescriptor<'a> = wgt::CreateBlasDescriptor>; +pub type TlasDescriptor<'a> = wgt::CreateTlasDescriptor>; + +pub(crate) trait AccelerationStructure: Trackable { + fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&'a dyn hal::DynAccelerationStructure>; +} + +#[derive(Debug)] +pub struct Blas { + pub(crate) raw: Snatchable>, + pub(crate) device: Arc, + pub(crate) size_info: hal::AccelerationStructureBuildSizes, + pub(crate) sizes: wgt::BlasGeometrySizeDescriptors, + pub(crate) flags: wgt::AccelerationStructureFlags, + pub(crate) update_mode: wgt::AccelerationStructureUpdateMode, + pub(crate) built_index: RwLock>, + pub(crate) handle: u64, + /// The `label` from the descriptor used to create the resource. + pub(crate) label: String, + pub(crate) tracking_data: TrackingData, +} + +impl Drop for Blas { + fn drop(&mut self) { + resource_log!("Destroy raw {}", self.error_ident()); + // SAFETY: We are in the Drop impl, and we don't use self.raw anymore after this point. + if let Some(raw) = self.raw.take() { + unsafe { + self.device.raw().destroy_acceleration_structure(raw); + } + } + } +} + +impl AccelerationStructure for Blas { + fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&'a dyn hal::DynAccelerationStructure> { + Some(self.raw.get(guard)?.as_ref()) + } +} + +impl Blas { + pub(crate) fn destroy(self: &Arc) -> Result<(), DestroyError> { + let device = &self.device; + + let temp = { + let mut snatch_guard = device.snatchable_lock.write(); + + let raw = match self.raw.snatch(&mut snatch_guard) { + Some(raw) => raw, + None => { + return Err(DestroyError::AlreadyDestroyed); + } + }; + + drop(snatch_guard); + + queue::TempResource::DestroyedAccelerationStructure(DestroyedAccelerationStructure { + raw: ManuallyDrop::new(raw), + device: Arc::clone(&self.device), + label: self.label().to_owned(), + bind_groups: WeakVec::new(), + }) + }; + + let mut pending_writes = device.pending_writes.lock(); + if pending_writes.contains_blas(self) { + pending_writes.consume_temp(temp); + } else { + let mut life_lock = device.lock_life(); + let last_submit_index = life_lock.get_blas_latest_submission_index(self); + if let Some(last_submit_index) = last_submit_index { + life_lock.schedule_resource_destruction(temp, last_submit_index); + } + } + + Ok(()) + } +} + +crate::impl_resource_type!(Blas); +crate::impl_labeled!(Blas); +crate::impl_parent_device!(Blas); +crate::impl_storage_item!(Blas); +crate::impl_trackable!(Blas); + +#[derive(Debug)] +pub struct Tlas { + pub(crate) raw: Snatchable>, + pub(crate) device: Arc, + pub(crate) size_info: hal::AccelerationStructureBuildSizes, + pub(crate) max_instance_count: u32, + pub(crate) flags: wgt::AccelerationStructureFlags, + pub(crate) update_mode: wgt::AccelerationStructureUpdateMode, + pub(crate) built_index: RwLock>, + pub(crate) dependencies: RwLock>>, + pub(crate) instance_buffer: ManuallyDrop>, + /// The `label` from the descriptor used to create the resource. + pub(crate) label: String, + pub(crate) tracking_data: TrackingData, + pub(crate) bind_groups: Mutex>, +} + +impl Drop for Tlas { + fn drop(&mut self) { + unsafe { + resource_log!("Destroy raw {}", self.error_ident()); + if let Some(structure) = self.raw.take() { + self.device.raw().destroy_acceleration_structure(structure); + } + let buffer = ManuallyDrop::take(&mut self.instance_buffer); + self.device.raw().destroy_buffer(buffer); + } + } +} + +impl AccelerationStructure for Tlas { + fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&dyn hal::DynAccelerationStructure> { + Some(self.raw.get(guard)?.as_ref()) + } +} + +crate::impl_resource_type!(Tlas); +crate::impl_labeled!(Tlas); +crate::impl_parent_device!(Tlas); +crate::impl_storage_item!(Tlas); +crate::impl_trackable!(Tlas); + +impl Tlas { + pub(crate) fn destroy(self: &Arc) -> Result<(), DestroyError> { + let device = &self.device; + + let temp = { + let mut snatch_guard = device.snatchable_lock.write(); + + let raw = match self.raw.snatch(&mut snatch_guard) { + Some(raw) => raw, + None => { + return Err(DestroyError::AlreadyDestroyed); + } + }; + + drop(snatch_guard); + + queue::TempResource::DestroyedAccelerationStructure(DestroyedAccelerationStructure { + raw: ManuallyDrop::new(raw), + device: Arc::clone(&self.device), + label: self.label().to_owned(), + bind_groups: mem::take(&mut self.bind_groups.lock()), + }) + }; + + let mut pending_writes = device.pending_writes.lock(); + if pending_writes.contains_tlas(self) { + pending_writes.consume_temp(temp); + } else { + let mut life_lock = device.lock_life(); + let last_submit_index = life_lock.get_tlas_latest_submission_index(self); + if let Some(last_submit_index) = last_submit_index { + life_lock.schedule_resource_destruction(temp, last_submit_index); + } + } + + Ok(()) + } +} + +#[derive(Debug)] +pub struct DestroyedAccelerationStructure { + raw: ManuallyDrop>, + device: Arc, + label: String, + // only filled if the acceleration structure is a TLAS + bind_groups: WeakVec, +} + +impl DestroyedAccelerationStructure { + pub fn label(&self) -> &dyn Debug { + &self.label + } +} + +impl Drop for DestroyedAccelerationStructure { + fn drop(&mut self) { + let mut deferred = self.device.deferred_destroy.lock(); + deferred.push(DeferredDestroy::BindGroups(mem::take( + &mut self.bind_groups, + ))); + drop(deferred); + + resource_log!("Destroy raw Buffer (destroyed) {:?}", self.label()); + // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. + let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; + unsafe { + hal::DynDevice::destroy_acceleration_structure(self.device.raw(), raw); + } + } +} diff --git a/wgpu-core/src/scratch.rs b/wgpu-core/src/scratch.rs new file mode 100644 index 000000000..f5930a5e0 --- /dev/null +++ b/wgpu-core/src/scratch.rs @@ -0,0 +1,43 @@ +use crate::device::{Device, DeviceError}; +use crate::resource_log; +use hal::BufferUses; +use std::mem::ManuallyDrop; +use std::sync::Arc; + +#[derive(Debug)] +pub struct ScratchBuffer { + raw: ManuallyDrop>, + device: Arc, +} + +impl ScratchBuffer { + pub(crate) fn new(device: &Arc, size: wgt::BufferSize) -> Result { + let raw = unsafe { + device + .raw() + .create_buffer(&hal::BufferDescriptor { + label: Some("(wgpu) scratch buffer"), + size: size.get(), + usage: BufferUses::ACCELERATION_STRUCTURE_SCRATCH, + memory_flags: hal::MemoryFlags::empty(), + }) + .map_err(crate::device::DeviceError::from_hal)? + }; + Ok(Self { + raw: ManuallyDrop::new(raw), + device: device.clone(), + }) + } + pub(crate) fn raw(&self) -> &dyn hal::DynBuffer { + self.raw.as_ref() + } +} + +impl Drop for ScratchBuffer { + fn drop(&mut self) { + resource_log!("Destroy raw ScratchBuffer"); + // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. + let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; + unsafe { self.device.raw().destroy_buffer(raw) }; + } +} diff --git a/wgpu-core/src/track/mod.rs b/wgpu-core/src/track/mod.rs index 1c2718981..9a66b5f90 100644 --- a/wgpu-core/src/track/mod.rs +++ b/wgpu-core/src/track/mod.rs @@ -98,6 +98,7 @@ Device <- CommandBuffer = insert(device.start, device.end, buffer.start, buffer. mod buffer; mod metadata; mod range; +mod ray_tracing; mod stateless; mod texture; @@ -112,6 +113,7 @@ use crate::{ use std::{fmt, ops, sync::Arc}; use thiserror::Error; +use crate::track::ray_tracing::AccelerationStructureTracker; pub(crate) use buffer::{ BufferBindGroupState, BufferTracker, BufferUsageScope, DeviceBufferTracker, }; @@ -224,6 +226,8 @@ pub(crate) struct TrackerIndexAllocators { pub render_pipelines: Arc, pub bundles: Arc, pub query_sets: Arc, + pub blas_s: Arc, + pub tlas_s: Arc, } impl TrackerIndexAllocators { @@ -238,6 +242,8 @@ impl TrackerIndexAllocators { render_pipelines: Arc::new(SharedTrackerIndexAllocator::new()), bundles: Arc::new(SharedTrackerIndexAllocator::new()), query_sets: Arc::new(SharedTrackerIndexAllocator::new()), + blas_s: Arc::new(SharedTrackerIndexAllocator::new()), + tlas_s: Arc::new(SharedTrackerIndexAllocator::new()), } } } @@ -420,6 +426,7 @@ pub(crate) struct BindGroupStates { pub buffers: BufferBindGroupState, pub views: TextureViewBindGroupState, pub samplers: StatelessTracker, + pub acceleration_structures: StatelessTracker, } impl BindGroupStates { @@ -428,6 +435,7 @@ impl BindGroupStates { buffers: BufferBindGroupState::new(), views: TextureViewBindGroupState::new(), samplers: StatelessTracker::new(), + acceleration_structures: StatelessTracker::new(), } } @@ -440,7 +448,7 @@ impl BindGroupStates { // Views are stateless, however, `TextureViewBindGroupState` // is special as it will be merged with other texture trackers. self.views.optimize(); - // Samplers are stateless and don't need to be optimized + // Samplers and Tlas's are stateless and don't need to be optimized // since the tracker is never merged with any other tracker. } } @@ -594,6 +602,8 @@ impl DeviceTracker { pub(crate) struct Tracker { pub buffers: BufferTracker, pub textures: TextureTracker, + pub blas_s: AccelerationStructureTracker, + pub tlas_s: AccelerationStructureTracker, pub views: StatelessTracker, pub bind_groups: StatelessTracker, pub compute_pipelines: StatelessTracker, @@ -607,6 +617,8 @@ impl Tracker { Self { buffers: BufferTracker::new(), textures: TextureTracker::new(), + blas_s: AccelerationStructureTracker::new(), + tlas_s: AccelerationStructureTracker::new(), views: StatelessTracker::new(), bind_groups: StatelessTracker::new(), compute_pipelines: StatelessTracker::new(), diff --git a/wgpu-core/src/track/ray_tracing.rs b/wgpu-core/src/track/ray_tracing.rs new file mode 100644 index 000000000..c344526df --- /dev/null +++ b/wgpu-core/src/track/ray_tracing.rs @@ -0,0 +1,81 @@ +use crate::resource::AccelerationStructure; +use crate::track::metadata::ResourceMetadata; +use crate::track::ResourceUses; +use hal::AccelerationStructureUses; +use std::sync::Arc; +use wgt::strict_assert; + +pub(crate) struct AccelerationStructureTracker { + start: Vec, + end: Vec, + + metadata: ResourceMetadata>, +} + +impl AccelerationStructureTracker { + pub fn new() -> Self { + Self { + start: Vec::new(), + end: Vec::new(), + + metadata: ResourceMetadata::new(), + } + } + + fn tracker_assert_in_bounds(&self, index: usize) { + strict_assert!(index < self.start.len()); + strict_assert!(index < self.end.len()); + self.metadata.tracker_assert_in_bounds(index); + } + + /// Sets the size of all the vectors inside the tracker. + /// + /// Must be called with the highest possible Buffer ID before + /// all unsafe functions are called. + pub fn set_size(&mut self, size: usize) { + self.start.resize(size, AccelerationStructureUses::empty()); + self.end.resize(size, AccelerationStructureUses::empty()); + + self.metadata.set_size(size); + } + + /// Extend the vectors to let the given index be valid. + fn allow_index(&mut self, index: usize) { + if index >= self.start.len() { + self.set_size(index + 1); + } + } + + /// Returns true if the given buffer is tracked. + pub fn contains(&self, acceleration_structure: &T) -> bool { + self.metadata + .contains(acceleration_structure.tracker_index().as_usize()) + } + + /// Inserts a single resource into the resource tracker. + pub fn set_single(&mut self, resource: Arc) { + let index: usize = resource.tracker_index().as_usize(); + + self.allow_index(index); + + self.tracker_assert_in_bounds(index); + } +} + +impl ResourceUses for AccelerationStructureUses { + const EXCLUSIVE: Self = Self::empty(); + + type Selector = (); + + fn bits(self) -> u16 { + Self::bits(&self) as u16 + } + + fn all_ordered(self) -> bool { + true + } + + fn any_exclusive(self) -> bool { + self.intersects(Self::EXCLUSIVE) + } +} diff --git a/wgpu-core/src/track/stateless.rs b/wgpu-core/src/track/stateless.rs index d1c2c87dd..975a850f3 100644 --- a/wgpu-core/src/track/stateless.rs +++ b/wgpu-core/src/track/stateless.rs @@ -1,3 +1,4 @@ +use std::slice::Iter; use std::sync::Arc; /// A tracker that holds strong references to resources. @@ -24,3 +25,12 @@ impl StatelessTracker { unsafe { self.resources.last().unwrap_unchecked() } } } + +impl<'a, T> IntoIterator for &'a StatelessTracker { + type Item = &'a Arc; + type IntoIter = Iter<'a, Arc>; + + fn into_iter(self) -> Self::IntoIter { + self.resources.as_slice().iter() + } +} diff --git a/wgpu-core/src/validation.rs b/wgpu-core/src/validation.rs index c1cbdaf18..13cf443d4 100644 --- a/wgpu-core/src/validation.rs +++ b/wgpu-core/src/validation.rs @@ -17,6 +17,7 @@ enum ResourceType { Sampler { comparison: bool, }, + AccelerationStructure, } #[derive(Debug)] @@ -490,6 +491,7 @@ impl Resource { }); } } + ResourceType::AccelerationStructure => (), }; Ok(()) @@ -567,6 +569,7 @@ impl Resource { }, } } + ResourceType::AccelerationStructure => BindingType::AccelerationStructure, }) } } @@ -861,6 +864,7 @@ impl Interface { class, }, naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison }, + naga::TypeInner::AccelerationStructure => ResourceType::AccelerationStructure, ref other => ResourceType::Buffer { size: wgt::BufferSize::new(other.size(module.to_ctx()) as u64).unwrap(), }, diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index 92c009b9c..bc9f0db15 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -777,6 +777,12 @@ pub struct Texture { allocation: Option, } +impl Texture { + pub unsafe fn raw_resource(&self) -> &Direct3D12::ID3D12Resource { + &self.resource + } +} + impl crate::DynTexture for Texture {} impl crate::DynSurfaceTexture for Texture {} diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index 9e38cf865..f113639a1 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -1240,13 +1240,15 @@ impl crate::CommandEncoder for super::CommandEncoder { } unsafe fn dispatch(&mut self, count: [u32; 3]) { - let encoder = self.state.compute.as_ref().unwrap(); - let raw_count = metal::MTLSize { - width: count[0] as u64, - height: count[1] as u64, - depth: count[2] as u64, - }; - encoder.dispatch_thread_groups(raw_count, self.state.raw_wg_size); + if count[0] > 0 && count[1] > 0 && count[2] > 0 { + let encoder = self.state.compute.as_ref().unwrap(); + let raw_count = metal::MTLSize { + width: count[0] as u64, + height: count[1] as u64, + depth: count[2] as u64, + }; + encoder.dispatch_thread_groups(raw_count, self.state.raw_wg_size); + } } unsafe fn dispatch_indirect(&mut self, buffer: &super::Buffer, offset: wgt::BufferAddress) { diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 776762b6b..03792a340 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -77,17 +77,17 @@ pub struct PhysicalDeviceFeatures { /// Features provided by `VK_KHR_buffer_device_address`, promoted to Vulkan 1.2. /// /// We only use this feature for - /// [`Features::RAY_TRACING_ACCELERATION_STRUCTURE`], which requires + /// [`Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE`], which requires /// `VK_KHR_acceleration_structure`, which depends on /// `VK_KHR_buffer_device_address`, so [`Instance::expose_adapter`] only /// bothers to check if `VK_KHR_acceleration_structure` is available, /// leaving this `None`. /// /// However, we do populate this when creating a device if - /// [`Features::RAY_TRACING_ACCELERATION_STRUCTURE`] is requested. + /// [`Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE`] is requested. /// /// [`Instance::expose_adapter`]: super::Instance::expose_adapter - /// [`Features::RAY_TRACING_ACCELERATION_STRUCTURE`]: wgt::Features::RAY_TRACING_ACCELERATION_STRUCTURE + /// [`Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE`]: wgt::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE buffer_device_address: Option>, /// Features provided by `VK_KHR_ray_query`, @@ -746,13 +746,16 @@ impl PhysicalDeviceFeatures { features.set(F::DEPTH32FLOAT_STENCIL8, texture_d32_s8); features.set( - F::RAY_TRACING_ACCELERATION_STRUCTURE, + F::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE, caps.supports_extension(khr::deferred_host_operations::NAME) && caps.supports_extension(khr::acceleration_structure::NAME) && caps.supports_extension(khr::buffer_device_address::NAME), ); - features.set(F::RAY_QUERY, caps.supports_extension(khr::ray_query::NAME)); + features.set( + F::EXPERIMENTAL_RAY_QUERY, + caps.supports_extension(khr::ray_query::NAME), + ); let rg11b10ufloat_renderable = supports_format( instance, @@ -1007,14 +1010,16 @@ impl PhysicalDeviceProperties { } // Require `VK_KHR_deferred_host_operations`, `VK_KHR_acceleration_structure` and `VK_KHR_buffer_device_address` if the feature `RAY_TRACING` was requested - if requested_features.contains(wgt::Features::RAY_TRACING_ACCELERATION_STRUCTURE) { + if requested_features + .contains(wgt::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + { extensions.push(khr::deferred_host_operations::NAME); extensions.push(khr::acceleration_structure::NAME); extensions.push(khr::buffer_device_address::NAME); } // Require `VK_KHR_ray_query` if the associated feature was requested - if requested_features.contains(wgt::Features::RAY_QUERY) { + if requested_features.contains(wgt::Features::EXPERIMENTAL_RAY_QUERY) { extensions.push(khr::ray_query::NAME); } @@ -1814,7 +1819,7 @@ impl super::Adapter { capabilities.push(spv::Capability::StorageImageWriteWithoutFormat); } - if features.contains(wgt::Features::RAY_QUERY) { + if features.contains(wgt::Features::EXPERIMENTAL_RAY_QUERY) { capabilities.push(spv::Capability::RayQueryKHR); } @@ -1849,6 +1854,9 @@ impl super::Adapter { // But this requires cloning the `spv::Options` struct, which has heap allocations. true, // could check `super::Workarounds::SEPARATE_ENTRY_POINTS` ); + if features.contains(wgt::Features::EXPERIMENTAL_RAY_QUERY) { + capabilities.push(spv::Capability::RayQueryKHR); + } spv::Options { lang_version: if features .intersects(wgt::Features::SUBGROUP | wgt::Features::SUBGROUP_VERTEX) diff --git a/wgpu-hal/src/vulkan/command.rs b/wgpu-hal/src/vulkan/command.rs index 6b02e35f4..eb8069733 100644 --- a/wgpu-hal/src/vulkan/command.rs +++ b/wgpu-hal/src/vulkan/command.rs @@ -647,10 +647,14 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, barrier: crate::AccelerationStructureBarrier, ) { - let (src_stage, src_access) = - conv::map_acceleration_structure_usage_to_barrier(barrier.usage.start); - let (dst_stage, dst_access) = - conv::map_acceleration_structure_usage_to_barrier(barrier.usage.end); + let (src_stage, src_access) = conv::map_acceleration_structure_usage_to_barrier( + barrier.usage.start, + self.device.features, + ); + let (dst_stage, dst_access) = conv::map_acceleration_structure_usage_to_barrier( + barrier.usage.end, + self.device.features, + ); unsafe { self.device.raw.cmd_pipeline_barrier( diff --git a/wgpu-hal/src/vulkan/conv.rs b/wgpu-hal/src/vulkan/conv.rs index b82930706..75b807a29 100644 --- a/wgpu-hal/src/vulkan/conv.rs +++ b/wgpu-hal/src/vulkan/conv.rs @@ -943,6 +943,7 @@ pub fn map_acceleration_structure_geometry_flags( pub fn map_acceleration_structure_usage_to_barrier( usage: crate::AccelerationStructureUses, + features: wgt::Features, ) -> (vk::PipelineStageFlags, vk::AccessFlags) { let mut stages = vk::PipelineStageFlags::empty(); let mut access = vk::AccessFlags::empty(); @@ -955,7 +956,9 @@ pub fn map_acceleration_structure_usage_to_barrier( stages |= vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR; access |= vk::AccessFlags::ACCELERATION_STRUCTURE_WRITE_KHR; } - if usage.contains(crate::AccelerationStructureUses::SHADER_INPUT) { + if usage.contains(crate::AccelerationStructureUses::SHADER_INPUT) + && features.contains(wgt::Features::EXPERIMENTAL_RAY_QUERY) + { stages |= vk::PipelineStageFlags::VERTEX_SHADER | vk::PipelineStageFlags::FRAGMENT_SHADER | vk::PipelineStageFlags::COMPUTE_SHADER; diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index a56b42ae8..39afe88ef 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -1075,14 +1075,7 @@ impl crate::Device for super::Device { desc.memory_flags.contains(crate::MemoryFlags::TRANSIENT), ); - let alignment_mask = if desc.usage.intersects( - crate::BufferUses::TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT - | crate::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, - ) { - 16 - } else { - req.alignment - } - 1; + let alignment_mask = req.alignment - 1; let block = unsafe { self.mem_allocator.lock().alloc( diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 9194ad41b..f58846695 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -91,7 +91,7 @@ pub const VERTEX_STRIDE_ALIGNMENT: BufferAddress = 4; /// Alignment all push constants need pub const PUSH_CONSTANT_ALIGNMENT: u32 = 4; /// Maximum queries in a query set -pub const QUERY_SET_MAX_QUERIES: u32 = 8192; +pub const QUERY_SET_MAX_QUERIES: u32 = 4096; /// Size of a single piece of query data. pub const QUERY_SIZE: u32 = 8; @@ -828,23 +828,32 @@ bitflags::bitflags! { /// /// This is a native only feature. const TEXTURE_FORMAT_NV12 = 1 << 47; - /// Allows for the creation of ray-tracing acceleration structures. + /// ***THIS IS EXPERIMENTAL:*** Features enabled by this may have + /// major bugs in them and are expected to be subject to breaking changes, suggestions + /// for the API exposed by this should be posted on [the ray-tracing issue](https://github.com/gfx-rs/wgpu/issues/1040) + /// + /// Allows for the creation of ray-tracing acceleration structures. Currently, + /// ray-tracing acceleration structures are only useful when used with [Features::EXPERIMENTAL_RAY_QUERY] /// /// Supported platforms: /// - Vulkan /// /// This is a native-only feature. - const RAY_TRACING_ACCELERATION_STRUCTURE = 1 << 48; + const EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE = 1 << 48; // Shader: + /// ***THIS IS EXPERIMENTAL:*** Features enabled by this may have + /// major bugs in it and are expected to be subject to breaking changes, suggestions + /// for the API exposed by this should be posted on [the ray-tracing issue](https://github.com/gfx-rs/wgpu/issues/1040) + /// /// Allows for the creation of ray-tracing queries within shaders. /// /// Supported platforms: /// - Vulkan /// /// This is a native-only feature. - const RAY_QUERY = 1 << 49; + const EXPERIMENTAL_RAY_QUERY = 1 << 49; /// Enables 64-bit floating point types in SPIR-V shaders. /// /// Note: even when supported by GPU hardware, 64-bit floating point operations are @@ -996,6 +1005,16 @@ impl Features { pub const fn all_native_mask() -> Self { Self::from_bits_truncate(!Self::all_webgpu_mask().bits()) } + + /// Vertex formats allowed for creating and building BLASes + #[must_use] + pub fn allowed_vertex_formats_for_blas(&self) -> Vec { + let mut formats = Vec::new(); + if self.contains(Self::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) { + formats.push(VertexFormat::Float32x3); + } + formats + } } bitflags::bitflags! { @@ -5306,6 +5325,10 @@ bitflags::bitflags! { const INDIRECT = 1 << 8; /// Allow a buffer to be the destination buffer for a [`CommandEncoder::resolve_query_set`] operation. const QUERY_RESOLVE = 1 << 9; + /// Allows a buffer to be used as input for a bottom level acceleration structure build + const BLAS_INPUT = 1 << 10; + /// Allows a buffer to be used as input for a top level acceleration structure build + const TLAS_INPUT = 1 << 11; } } @@ -7433,19 +7456,119 @@ impl Default for InstanceDescriptor { } } +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Descriptor for all size defining attributes of a single triangle geometry inside a bottom level acceleration structure. +pub struct BlasTriangleGeometrySizeDescriptor { + /// Format of a vertex position, must be [VertexFormat::Float32x3] + /// with just [Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE] + /// but later features may add more formats. + pub vertex_format: VertexFormat, + /// Number of vertices. + pub vertex_count: u32, + /// Format of an index. Only needed if an index buffer is used. + /// If `index_format` is provided `index_count` is required. + pub index_format: Option, + /// Number of indices. Only needed if an index buffer is used. + /// If `index_count` is provided `index_format` is required. + pub index_count: Option, + /// Flags for the geometry. + pub flags: AccelerationStructureGeometryFlags, +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Descriptor for all size defining attributes of all geometries inside a bottom level acceleration structure. +pub enum BlasGeometrySizeDescriptors { + /// Triangle geometry version. + Triangles { + /// Descriptor for each triangle geometry. + descriptors: Vec, + }, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Update mode for acceleration structure builds. +pub enum AccelerationStructureUpdateMode { + /// Always perform a full build. + Build, + /// If possible, perform an incremental update. + /// + /// Not advised for major topology changes. + /// (Useful for e.g. skinning) + PreferUpdate, +} + +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Descriptor for creating a bottom level acceleration structure. +pub struct CreateBlasDescriptor { + /// Label for the bottom level acceleration structure. + pub label: L, + /// Flags for the bottom level acceleration structure. + pub flags: AccelerationStructureFlags, + /// Update mode for the bottom level acceleration structure. + pub update_mode: AccelerationStructureUpdateMode, +} + +impl CreateBlasDescriptor { + /// Takes a closure and maps the label of the blas descriptor into another. + pub fn map_label(&self, fun: impl FnOnce(&L) -> K) -> CreateBlasDescriptor { + CreateBlasDescriptor { + label: fun(&self.label), + flags: self.flags, + update_mode: self.update_mode, + } + } +} + +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Descriptor for creating a top level acceleration structure. +pub struct CreateTlasDescriptor { + /// Label for the top level acceleration structure. + pub label: L, + /// Number of instances that can be stored in the acceleration structure. + pub max_instances: u32, + /// Flags for the bottom level acceleration structure. + pub flags: AccelerationStructureFlags, + /// Update mode for the bottom level acceleration structure. + pub update_mode: AccelerationStructureUpdateMode, +} + +impl CreateTlasDescriptor { + /// Takes a closure and maps the label of the blas descriptor into another. + pub fn map_label(&self, fun: impl FnOnce(&L) -> K) -> CreateTlasDescriptor { + CreateTlasDescriptor { + label: fun(&self.label), + flags: self.flags, + update_mode: self.update_mode, + max_instances: self.max_instances, + } + } +} + bitflags::bitflags!( /// Flags for acceleration structures #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct AccelerationStructureFlags: u8 { - /// Allow for incremental updates (no change in size) + /// Allow for incremental updates (no change in size), currently this is unimplemented + /// and will build as normal (this is fine, update vs build should be unnoticeable) const ALLOW_UPDATE = 1 << 0; - /// Allow the acceleration structure to be compacted in a copy operation + /// Allow the acceleration structure to be compacted in a copy operation, the function + /// to compact is not currently implemented. const ALLOW_COMPACTION = 1 << 1; - /// Optimize for fast ray tracing performance + /// Optimize for fast ray tracing performance, recommended if the geometry is unlikely + /// to change (e.g. in a game: non-interactive scene geometry) const PREFER_FAST_TRACE = 1 << 2; - /// Optimize for fast build time + /// Optimize for fast build time, recommended if geometry is likely to change frequently + /// (e.g. in a game: player model). const PREFER_FAST_BUILD = 1 << 3; - /// Optimize for low memory footprint (scratch and output) + /// Optimize for low memory footprint (both while building and in the output BLAS). const LOW_MEMORY = 1 << 4; } ); @@ -7455,14 +7578,27 @@ bitflags::bitflags!( /// Flags for acceleration structure geometries #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct AccelerationStructureGeometryFlags: u8 { - /// Is OPAQUE + /// Is OPAQUE (is there no alpha test) recommended as currently in naga there is no + /// candidate intersections yet so currently BLASes without this flag will not have hits. + /// Not enabling this makes the BLAS unable to be interacted with in WGSL. const OPAQUE = 1 << 0; - /// NO_DUPLICATE_ANY_HIT_INVOCATION + /// NO_DUPLICATE_ANY_HIT_INVOCATION, not useful unless using hal with wgpu, ray-tracing + /// pipelines are not supported in wgpu so any-hit shaders do not exist. For when any-hit + /// shaders are implemented (or experienced users who combine this with an underlying library: + /// for any primitive (triangle or AABB) multiple any-hit shaders sometimes may be invoked + /// (especially in AABBs like a sphere), if this flag in present only one hit on a primitive may + /// invoke an any-hit shader. const NO_DUPLICATE_ANY_HIT_INVOCATION = 1 << 1; } ); impl_bitflags!(AccelerationStructureGeometryFlags); +/// Alignment requirement for transform buffers used in acceleration structure builds +pub const TRANSFORM_BUFFER_ALIGNMENT: BufferAddress = 16; + +/// Alignment requirement for instance buffers used in acceleration structure builds (`build_acceleration_structures_unsafe_tlas`) +pub const INSTANCE_BUFFER_ALIGNMENT: BufferAddress = 16; + pub use send_sync::*; #[doc(hidden)] diff --git a/wgpu/src/api/bind_group.rs b/wgpu/src/api/bind_group.rs index 42a774b29..fac59338f 100644 --- a/wgpu/src/api/bind_group.rs +++ b/wgpu/src/api/bind_group.rs @@ -70,6 +70,18 @@ pub enum BindingResource<'a> { /// Corresponds to [`wgt::BindingType::Texture`] and [`wgt::BindingType::StorageTexture`] with /// [`BindGroupLayoutEntry::count`] set to Some. TextureViewArray(&'a [&'a TextureView]), + /// Binding is backed by a top level acceleration structure + /// + /// Corresponds to [`wgt::BindingType::AccelerationStructure`] with [`BindGroupLayoutEntry::count`] set to None. + /// + /// # Validation + /// When using (e.g. with `set_bind_group`) a bind group that has been created with one or more of this binding + /// resource certain checks take place. + /// - TLAS must have been built, if not a validation error is generated + /// - All BLASes that were built into the TLAS must be built before the TLAS, if this was not satisfied and TLAS was + /// built using `build_acceleration_structures` a validation error is generated otherwise this is a part of the + /// safety section of `build_acceleration_structures_unsafe_tlas` and so undefined behavior occurs. + AccelerationStructure(&'a Tlas), } #[cfg(send_sync)] static_assertions::assert_impl_all!(BindingResource<'_>: Send, Sync); diff --git a/wgpu/src/api/blas.rs b/wgpu/src/api/blas.rs new file mode 100644 index 000000000..329f11849 --- /dev/null +++ b/wgpu/src/api/blas.rs @@ -0,0 +1,232 @@ +use crate::context::{Context, DynContext}; +use crate::{Buffer, Data, Label, C}; +use std::sync::Arc; +use std::thread; +use wgt::WasmNotSendSync; + +/// Descriptor for the size defining attributes of a triangle geometry, for a bottom level acceleration structure. +pub type BlasTriangleGeometrySizeDescriptor = wgt::BlasTriangleGeometrySizeDescriptor; +static_assertions::assert_impl_all!(BlasTriangleGeometrySizeDescriptor: Send, Sync); + +/// Descriptor for the size defining attributes, for a bottom level acceleration structure. +pub type BlasGeometrySizeDescriptors = wgt::BlasGeometrySizeDescriptors; +static_assertions::assert_impl_all!(BlasGeometrySizeDescriptors: Send, Sync); + +/// Flags for an acceleration structure. +pub type AccelerationStructureFlags = wgt::AccelerationStructureFlags; +static_assertions::assert_impl_all!(AccelerationStructureFlags: Send, Sync); + +/// Flags for a geometry inside a bottom level acceleration structure. +pub type AccelerationStructureGeometryFlags = wgt::AccelerationStructureGeometryFlags; +static_assertions::assert_impl_all!(AccelerationStructureGeometryFlags: Send, Sync); + +/// Update mode for acceleration structure builds. +pub type AccelerationStructureUpdateMode = wgt::AccelerationStructureUpdateMode; +static_assertions::assert_impl_all!(AccelerationStructureUpdateMode: Send, Sync); + +/// Descriptor to create bottom level acceleration structures. +pub type CreateBlasDescriptor<'a> = wgt::CreateBlasDescriptor>; +static_assertions::assert_impl_all!(CreateBlasDescriptor<'_>: Send, Sync); + +/// Safe instance for a [Tlas]. +/// +/// A TlasInstance may be made invalid, if a TlasInstance is invalid, any attempt to build a [TlasPackage] containing an +/// invalid TlasInstance will generate a validation error +/// +/// Each one contains: +/// - A reference to a BLAS, this ***must*** be interacted with using [TlasInstance::new] or [TlasInstance::set_blas], a +/// TlasInstance that references a BLAS keeps that BLAS from being dropped, but if the BLAS is explicitly destroyed (e.g. +/// using [Blas::destroy]) the TlasInstance becomes invalid +/// - A user accessible transformation matrix +/// - A user accessible mask +/// - A user accessible custom index +/// +/// [Tlas]: crate::Tlas +/// [TlasPackage]: crate::TlasPackage +#[derive(Debug, Clone)] +pub struct TlasInstance { + pub(crate) blas: Arc, + /// Affine transform matrix 3x4 (rows x columns, row major order). + pub transform: [f32; 12], + /// Custom index for the instance used inside the shader. + /// + /// This must only use the lower 24 bits, if any bits are outside that range (byte 4 does not equal 0) the TlasInstance becomes + /// invalid and generates a validation error when built + pub custom_index: u32, + /// Mask for the instance used inside the shader to filter instances. + /// Reports hit only if `(shader_cull_mask & tlas_instance.mask) != 0u`. + pub mask: u8, +} + +impl TlasInstance { + /// Construct TlasInstance. + /// - blas: Reference to the bottom level acceleration structure + /// - transform: Transform buffer offset in bytes (optional, required if transform buffer is present) + /// - custom_index: Custom index for the instance used inside the shader (max 24 bits) + /// - mask: Mask for the instance used inside the shader to filter instances + /// + /// Note: while one of these contains a reference to a BLAS that BLAS will not be dropped, + /// but it can still be destroyed. Destroying a BLAS that is referenced by one or more + /// TlasInstance(s) will immediately make them invalid. If one or more of those invalid + /// TlasInstances is inside a TlasPackage that is attempted to be built, the build will + /// generate a validation error. + pub fn new(blas: &Blas, transform: [f32; 12], custom_index: u32, mask: u8) -> Self { + Self { + blas: blas.shared.clone(), + transform, + custom_index, + mask, + } + } + + /// Set the bottom level acceleration structure. + /// + /// See the note on [TlasInstance] about the + /// guarantees of keeping a BLAS alive. + pub fn set_blas(&mut self, blas: &Blas) { + self.blas = blas.shared.clone(); + } +} + +pub(crate) struct DynContextTlasInstance<'a> { + pub(crate) blas: &'a Data, + pub(crate) transform: &'a [f32; 12], + pub(crate) custom_index: u32, + pub(crate) mask: u8, +} + +/// Context version of [TlasInstance]. +#[allow(dead_code)] +pub struct ContextTlasInstance<'a, T: Context> { + pub(crate) blas_data: &'a T::BlasData, + pub(crate) transform: &'a [f32; 12], + pub(crate) custom_index: u32, + pub(crate) mask: u8, +} + +#[derive(Debug)] +/// Definition for a triangle geometry for a Bottom Level Acceleration Structure (BLAS). +/// +/// The size must match the rest of the structures fields, otherwise the build will fail. +/// (e.g. if index count is present in the size, the index buffer must be present as well.) +pub struct BlasTriangleGeometry<'a> { + /// Sub descriptor for the size defining attributes of a triangle geometry. + pub size: &'a BlasTriangleGeometrySizeDescriptor, + /// Vertex buffer. + pub vertex_buffer: &'a Buffer, + /// Offset into the vertex buffer as a factor of the vertex stride. + pub first_vertex: u32, + /// Vertex stride. + pub vertex_stride: wgt::BufferAddress, + /// Index buffer (optional). + pub index_buffer: Option<&'a Buffer>, + /// Index buffer offset in bytes (optional, required if index buffer is present). + pub index_buffer_offset: Option, + /// Transform buffer containing 3x4 (rows x columns, row major) affine transform matrices `[f32; 12]` (optional). + pub transform_buffer: Option<&'a Buffer>, + /// Transform buffer offset in bytes (optional, required if transform buffer is present). + pub transform_buffer_offset: Option, +} +static_assertions::assert_impl_all!(BlasTriangleGeometry<'_>: WasmNotSendSync); + +/// Contains the sets of geometry that go into a [Blas]. +pub enum BlasGeometries<'a> { + /// Triangle geometry variant. + TriangleGeometries(Vec>), +} +static_assertions::assert_impl_all!(BlasGeometries<'_>: WasmNotSendSync); + +/// Builds the given sets of geometry into the given [Blas]. +pub struct BlasBuildEntry<'a> { + /// Reference to the acceleration structure. + pub blas: &'a Blas, + /// Geometries. + pub geometry: BlasGeometries<'a>, +} +static_assertions::assert_impl_all!(BlasBuildEntry<'_>: WasmNotSendSync); + +#[derive(Debug)] +pub(crate) struct BlasShared { + pub(crate) context: Arc, + pub(crate) data: Box, +} +static_assertions::assert_impl_all!(BlasShared: WasmNotSendSync); + +#[derive(Debug)] +/// Bottom Level Acceleration Structure (BLAS). +/// +/// A BLAS is a device-specific raytracing acceleration structure that contains geometry data. +/// +/// These BLASes are combined with transform in a [TlasInstance] to create a [Tlas]. +/// +/// [Tlas]: crate::Tlas +pub struct Blas { + pub(crate) handle: Option, + pub(crate) shared: Arc, +} +static_assertions::assert_impl_all!(Blas: WasmNotSendSync); + +impl Blas { + /// Raw handle to the acceleration structure, used inside raw instance buffers. + pub fn handle(&self) -> Option { + self.handle + } + /// Destroy the associated native resources as soon as possible. + pub fn destroy(&self) { + DynContext::blas_destroy(&*self.shared.context, self.shared.data.as_ref()); + } +} + +impl Drop for BlasShared { + fn drop(&mut self) { + if !thread::panicking() { + self.context.blas_drop(self.data.as_ref()); + } + } +} + +pub(crate) struct DynContextBlasTriangleGeometry<'a> { + pub(crate) size: &'a BlasTriangleGeometrySizeDescriptor, + pub(crate) vertex_buffer: &'a Data, + pub(crate) index_buffer: Option<&'a Data>, + pub(crate) transform_buffer: Option<&'a Data>, + pub(crate) first_vertex: u32, + pub(crate) vertex_stride: wgt::BufferAddress, + pub(crate) index_buffer_offset: Option, + pub(crate) transform_buffer_offset: Option, +} + +pub(crate) enum DynContextBlasGeometries<'a> { + TriangleGeometries(Box> + 'a>), +} + +pub(crate) struct DynContextBlasBuildEntry<'a> { + pub(crate) blas_data: &'a Data, + pub(crate) geometries: DynContextBlasGeometries<'a>, +} + +/// Context version of [BlasTriangleGeometry]. +#[allow(dead_code)] +pub struct ContextBlasTriangleGeometry<'a, T: Context> { + pub(crate) size: &'a BlasTriangleGeometrySizeDescriptor, + pub(crate) vertex_buffer: &'a T::BufferData, + pub(crate) index_buffer: Option<&'a T::BufferData>, + pub(crate) transform_buffer: Option<&'a T::BufferData>, + pub(crate) first_vertex: u32, + pub(crate) vertex_stride: wgt::BufferAddress, + pub(crate) index_buffer_offset: Option, + pub(crate) transform_buffer_offset: Option, +} + +/// Context version of [BlasGeometries]. +pub enum ContextBlasGeometries<'a, T: Context> { + /// Triangle geometries. + TriangleGeometries(Box> + 'a>), +} + +/// Context version see [BlasBuildEntry]. +#[allow(dead_code)] +pub struct ContextBlasBuildEntry<'a, T: Context> { + pub(crate) blas_data: &'a T::BlasData, + pub(crate) geometries: ContextBlasGeometries<'a, T>, +} diff --git a/wgpu/src/api/command_encoder.rs b/wgpu/src/api/command_encoder.rs index a45564b45..f7f453aec 100644 --- a/wgpu/src/api/command_encoder.rs +++ b/wgpu/src/api/command_encoder.rs @@ -55,7 +55,15 @@ pub type ImageCopyTexture<'a> = ImageCopyTextureBase<&'a Texture>; #[cfg(send_sync)] static_assertions::assert_impl_all!(ImageCopyTexture<'_>: Send, Sync); +use crate::api::blas::{ + BlasBuildEntry, BlasGeometries, BlasTriangleGeometry, DynContextBlasBuildEntry, + DynContextBlasGeometries, DynContextBlasTriangleGeometry, DynContextTlasInstance, TlasInstance, +}; +use crate::api::tlas::{ + DynContextTlasBuildEntry, DynContextTlasPackage, TlasBuildEntry, TlasPackage, +}; pub use wgt::ImageCopyTextureTagged as ImageCopyTextureTaggedBase; + /// View of a texture which can be used to copy to a texture, including /// color space and alpha premultiplication information. /// @@ -340,3 +348,164 @@ impl CommandEncoder { ) } } + +/// [`Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE`] must be enabled on the device in order to call these functions. +impl CommandEncoder { + /// Build bottom and top level acceleration structures. + /// + /// Builds the BLASes then the TLASes, but does ***not*** build the BLASes into the TLASes, + /// that must be done by setting a TLAS instance in the TLAS package to one that contains the BLAS (and with an appropriate transform) + /// + /// # Validation + /// + /// - blas: Iterator of bottom level acceleration structure entries to build. + /// For each entry, the provided size descriptor must be strictly smaller or equal to the descriptor given at BLAS creation, this means: + /// - Less or equal number of geometries + /// - Same kind of geometry (with index buffer or without) (same vertex/index format) + /// - Same flags + /// - Less or equal number of vertices + /// - Less or equal number of indices (if applicable) + /// - tlas: iterator of top level acceleration structure packages to build + /// For each entry: + /// - Each BLAS in each TLAS instance must have been being built in the current call or in a previous call to `build_acceleration_structures` or `build_acceleration_structures_unsafe_tlas` + /// - The number of TLAS instances must be less than or equal to the max number of tlas instances when creating (if creating a package with `TlasPackage::new()` this is already satisfied) + /// + /// If the device the command encoder is created from does not have [Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE] enabled then a validation error is generated + /// + /// A bottom level acceleration structure may be build and used as a reference in a top level acceleration structure in the same invocation of this function. + /// + /// # Bind group usage + /// + /// When a top level acceleration structure is used in a bind group, some validation takes place: + /// - The top level acceleration structure is valid and has been built. + /// - All the bottom level acceleration structures referenced by the top level acceleration structure are valid and have been built prior, + /// or at same time as the containing top level acceleration structure. + /// + /// [Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE]: wgt::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + pub fn build_acceleration_structures<'a>( + &mut self, + blas: impl IntoIterator>, + tlas: impl IntoIterator, + ) { + let mut blas = blas.into_iter().map(|e: &BlasBuildEntry<'_>| { + let geometries = match &e.geometry { + BlasGeometries::TriangleGeometries(triangle_geometries) => { + let iter = triangle_geometries + .iter() + .map( + |tg: &BlasTriangleGeometry<'_>| DynContextBlasTriangleGeometry { + size: tg.size, + vertex_buffer: tg.vertex_buffer.data.as_ref(), + + index_buffer: tg + .index_buffer + .map(|index_buffer| index_buffer.data.as_ref()), + + transform_buffer: tg + .transform_buffer + .map(|transform_buffer| transform_buffer.data.as_ref()), + + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + }, + ); + DynContextBlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + DynContextBlasBuildEntry { + blas_data: e.blas.shared.data.as_ref(), + geometries, + } + }); + + let mut tlas = tlas.into_iter().map(|e: &TlasPackage| { + let instances = e.instances.iter().map(|instance: &Option| { + instance.as_ref().map(|instance| DynContextTlasInstance { + blas: instance.blas.data.as_ref(), + transform: &instance.transform, + custom_index: instance.custom_index, + mask: instance.mask, + }) + }); + DynContextTlasPackage { + tlas_data: e.tlas.data.as_ref(), + instances: Box::new(instances), + lowest_unmodified: e.lowest_unmodified, + } + }); + + DynContext::command_encoder_build_acceleration_structures( + &*self.context, + self.data.as_ref(), + &mut blas, + &mut tlas, + ); + } + + /// Build bottom and top level acceleration structures. + /// See [`CommandEncoder::build_acceleration_structures`] for the safe version and more details. All validation in [`CommandEncoder::build_acceleration_structures`] except that + /// listed under tlas applies here as well. + /// + /// # Safety + /// + /// - The contents of the raw instance buffer must be valid for the underling api. + /// - All bottom level acceleration structures, referenced in the raw instance buffer must be valid and built, + /// when the corresponding top level acceleration structure is built. (builds may happen in the same invocation of this function). + /// - At the time when the top level acceleration structure is used in a bind group, all associated bottom level acceleration structures must be valid, + /// and built (no later than the time when the top level acceleration structure was built). + pub unsafe fn build_acceleration_structures_unsafe_tlas<'a>( + &mut self, + blas: impl IntoIterator>, + tlas: impl IntoIterator>, + ) { + let mut blas = blas.into_iter().map(|e: &BlasBuildEntry<'_>| { + let geometries = match &e.geometry { + BlasGeometries::TriangleGeometries(triangle_geometries) => { + let iter = triangle_geometries + .iter() + .map( + |tg: &BlasTriangleGeometry<'_>| DynContextBlasTriangleGeometry { + size: tg.size, + vertex_buffer: tg.vertex_buffer.data.as_ref(), + + index_buffer: tg + .index_buffer + .map(|index_buffer| index_buffer.data.as_ref()), + + transform_buffer: tg + .transform_buffer + .map(|transform_buffer| transform_buffer.data.as_ref()), + + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + }, + ); + DynContextBlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + DynContextBlasBuildEntry { + blas_data: e.blas.shared.data.as_ref(), + geometries, + } + }); + + let mut tlas = tlas + .into_iter() + .map(|e: &TlasBuildEntry<'_>| DynContextTlasBuildEntry { + tlas_data: e.tlas.data.as_ref(), + instance_buffer_data: e.instance_buffer.data.as_ref(), + instance_count: e.instance_count, + }); + + DynContext::command_encoder_build_acceleration_structures_unsafe_tlas( + &*self.context, + self.data.as_ref(), + &mut blas, + &mut tlas, + ); + } +} diff --git a/wgpu/src/api/device.rs b/wgpu/src/api/device.rs index 5da9f6c80..2fa040ec1 100644 --- a/wgpu/src/api/device.rs +++ b/wgpu/src/api/device.rs @@ -2,6 +2,8 @@ use std::{error, fmt, future::Future, sync::Arc, thread}; use parking_lot::Mutex; +use crate::api::blas::{Blas, BlasGeometrySizeDescriptors, BlasShared, CreateBlasDescriptor}; +use crate::api::tlas::{CreateTlasDescriptor, Tlas}; use crate::context::DynContext; use crate::*; @@ -514,6 +516,64 @@ impl Device { } } +/// [`Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE`] must be enabled on the device in order to call these functions. +impl Device { + /// Create a bottom level acceleration structure, used inside a top level acceleration structure for ray tracing. + /// - `desc`: The descriptor of the acceleration structure. + /// - `sizes`: Size descriptor limiting what can be built into the acceleration structure. + /// + /// # Validation + /// If any of the following is not satisfied a validation error is generated + /// + /// The device ***must*** have [Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE] enabled. + /// if `sizes` is [BlasGeometrySizeDescriptors::Triangles] then the following must be satisfied + /// - For every geometry descriptor (for the purposes this is called `geo_desc`) of `sizes.descriptors` the following must be satisfied: + /// - `geo_desc.vertex_format` must be within allowed formats (allowed formats for a given feature set + /// may be queried with [Features::allowed_vertex_formats_for_blas]). + /// - Both or neither of `geo_desc.index_format` and `geo_desc.index_count` must be provided. + /// + /// [Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE]: wgt::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + /// [Features::allowed_vertex_formats_for_blas]: wgt::Features::allowed_vertex_formats_for_blas + #[must_use] + pub fn create_blas( + &self, + desc: &CreateBlasDescriptor<'_>, + sizes: BlasGeometrySizeDescriptors, + ) -> Blas { + let (handle, data) = + DynContext::device_create_blas(&*self.context, self.data.as_ref(), desc, sizes); + + Blas { + #[allow(clippy::arc_with_non_send_sync)] + shared: Arc::new(BlasShared { + context: Arc::clone(&self.context), + data, + }), + handle, + } + } + + /// Create a top level acceleration structure, used for ray tracing. + /// - `desc`: The descriptor of the acceleration structure. + /// + /// # Validation + /// If any of the following is not satisfied a validation error is generated + /// + /// The device ***must*** have [Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE] enabled. + /// + /// [Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE]: wgt::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + #[must_use] + pub fn create_tlas(&self, desc: &CreateTlasDescriptor<'_>) -> Tlas { + let data = DynContext::device_create_tlas(&*self.context, self.data.as_ref(), desc); + + Tlas { + context: Arc::clone(&self.context), + data, + max_instances: desc.max_instances, + } + } +} + impl Drop for Device { fn drop(&mut self) { if !thread::panicking() { diff --git a/wgpu/src/api/mod.rs b/wgpu/src/api/mod.rs index 52b9ec160..b94235393 100644 --- a/wgpu/src/api/mod.rs +++ b/wgpu/src/api/mod.rs @@ -28,6 +28,7 @@ mod buffer; mod command_buffer; mod command_encoder; // Not a root type, but common descriptor types for pipelines. +mod blas; mod common_pipeline; mod compute_pass; mod compute_pipeline; @@ -47,10 +48,12 @@ mod surface; mod surface_texture; mod texture; mod texture_view; +mod tlas; pub use adapter::*; pub use bind_group::*; pub use bind_group_layout::*; +pub use blas::*; pub use buffer::*; pub use command_buffer::*; pub use command_encoder::*; @@ -73,6 +76,7 @@ pub use surface::*; pub use surface_texture::*; pub use texture::*; pub use texture_view::*; +pub use tlas::*; /// Object debugging label. pub type Label<'a> = Option<&'a str>; diff --git a/wgpu/src/api/surface.rs b/wgpu/src/api/surface.rs index 6e433596d..14b482850 100644 --- a/wgpu/src/api/surface.rs +++ b/wgpu/src/api/surface.rs @@ -159,7 +159,7 @@ impl Surface<'_> { /// - The raw handle obtained from the hal Surface must not be manually destroyed #[cfg(wgpu_core)] pub unsafe fn as_hal) -> R, R>( - &mut self, + &self, hal_surface_callback: F, ) -> Option { self.context diff --git a/wgpu/src/api/tlas.rs b/wgpu/src/api/tlas.rs new file mode 100644 index 000000000..d387e508a --- /dev/null +++ b/wgpu/src/api/tlas.rs @@ -0,0 +1,198 @@ +use crate::api::blas::{ContextTlasInstance, DynContextTlasInstance, TlasInstance}; +use crate::context::{Context, DynContext}; +use crate::{BindingResource, Buffer, Data, Label, C}; +use std::ops::{Index, IndexMut, Range}; +use std::sync::Arc; +use std::thread; +use wgt::WasmNotSendSync; + +/// Descriptor to create top level acceleration structures. +pub type CreateTlasDescriptor<'a> = wgt::CreateTlasDescriptor>; +static_assertions::assert_impl_all!(CreateTlasDescriptor<'_>: Send, Sync); + +#[derive(Debug)] +/// Top Level Acceleration Structure (TLAS). +/// +/// A TLAS contains a series of [TLAS instances], which are a reference to +/// a BLAS and a transformation matrix placing the geometry in the world. +/// +/// A TLAS contains TLAS instances in a device readable form, you cant interact +/// directly with these, instead you have to build the TLAS with [TLAS instances]. +/// +/// [TLAS instances]: TlasInstance +pub struct Tlas { + pub(crate) context: Arc, + pub(crate) data: Box, + pub(crate) max_instances: u32, +} +static_assertions::assert_impl_all!(Tlas: WasmNotSendSync); + +impl Tlas { + /// Destroy the associated native resources as soon as possible. + pub fn destroy(&self) { + DynContext::tlas_destroy(&*self.context, self.data.as_ref()); + } +} + +impl Drop for Tlas { + fn drop(&mut self) { + if !thread::panicking() { + self.context.tlas_drop(self.data.as_ref()); + } + } +} + +/// Entry for a top level acceleration structure build. +/// Used with raw instance buffers for an unvalidated builds. +/// See [TlasPackage] for the safe version. +pub struct TlasBuildEntry<'a> { + /// Reference to the acceleration structure. + pub tlas: &'a Tlas, + /// Reference to the raw instance buffer, each instance is similar to [TlasInstance] but contains a handle to the BLAS. + pub instance_buffer: &'a Buffer, + /// Number of instances in the instance buffer. + pub instance_count: u32, +} +static_assertions::assert_impl_all!(TlasBuildEntry<'_>: WasmNotSendSync); + +/// The safe version of TlasEntry, containing TlasInstances instead of a raw buffer. +pub struct TlasPackage { + pub(crate) tlas: Tlas, + pub(crate) instances: Vec>, + pub(crate) lowest_unmodified: u32, +} +static_assertions::assert_impl_all!(TlasPackage: WasmNotSendSync); + +impl TlasPackage { + /// Construct [TlasPackage] consuming the [Tlas] (prevents modification of the [Tlas] without using this package). + pub fn new(tlas: Tlas) -> Self { + let max_instances = tlas.max_instances; + Self::new_with_instances(tlas, vec![None; max_instances as usize]) + } + + /// Construct [TlasPackage] consuming the [Tlas] (prevents modification of the Tlas without using this package). + /// This constructor moves the instances into the package (the number of instances needs to fit into tlas, + /// otherwise when building a validation error will be raised). + pub fn new_with_instances(tlas: Tlas, instances: Vec>) -> Self { + Self { + tlas, + lowest_unmodified: instances.len() as u32, + instances, + } + } + + /// Get a reference to all instances. + pub fn get(&self) -> &[Option] { + &self.instances + } + + /// Get a mutable slice to a range of instances. + /// Returns None if the range is out of bounds. + /// All elements from the lowest accessed index up are marked as modified. + // this recommendation is not useful yet, but is likely to be when ability to update arrives or possible optimisations for building get implemented. + /// For best performance it is recommended to prefer access to low elements and modify higher elements as little as possible. + /// This can be done by ordering instances from the most to the least used. It is recommended + /// to use [Self::index_mut] unless the option if out of bounds is required + pub fn get_mut_slice(&mut self, range: Range) -> Option<&mut [Option]> { + if range.end > self.instances.len() { + return None; + } + if range.end as u32 > self.lowest_unmodified { + self.lowest_unmodified = range.end as u32; + } + Some(&mut self.instances[range]) + } + + /// Get a single mutable reference to an instance. + /// Returns None if the range is out of bounds. + /// All elements from the lowest accessed index up are marked as modified. + // this recommendation is not useful yet, but is likely to be when ability to update arrives or possible optimisations for building get implemented. + /// For best performance it is recommended to prefer access to low elements and modify higher elements as little as possible. + /// This can be done by ordering instances from the most to the least used. It is recommended + /// to use [Self::index_mut] unless the option if out of bounds is required + pub fn get_mut_single(&mut self, index: usize) -> Option<&mut Option> { + if index >= self.instances.len() { + return None; + } + if index as u32 + 1 > self.lowest_unmodified { + self.lowest_unmodified = index as u32 + 1; + } + Some(&mut self.instances[index]) + } + + /// Get the binding resource for the underling acceleration structure, to be used when creating a [BindGroup] + /// + /// [BindGroup]: super::BindGroup + pub fn as_binding(&self) -> BindingResource<'_> { + BindingResource::AccelerationStructure(&self.tlas) + } + + /// Get a reference to the underling [Tlas]. + pub fn tlas(&self) -> &Tlas { + &self.tlas + } +} + +impl Index for TlasPackage { + type Output = Option; + + fn index(&self, index: usize) -> &Self::Output { + self.instances.index(index) + } +} + +impl Index> for TlasPackage { + type Output = [Option]; + + fn index(&self, index: Range) -> &Self::Output { + self.instances.index(index) + } +} + +impl IndexMut for TlasPackage { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + let idx = self.instances.index_mut(index); + if index as u32 + 1 > self.lowest_unmodified { + self.lowest_unmodified = index as u32 + 1; + } + idx + } +} + +impl IndexMut> for TlasPackage { + fn index_mut(&mut self, index: Range) -> &mut Self::Output { + let idx = self.instances.index_mut(index.clone()); + if index.end > self.lowest_unmodified as usize { + self.lowest_unmodified = index.end as u32; + } + idx + } +} + +pub(crate) struct DynContextTlasBuildEntry<'a> { + pub(crate) tlas_data: &'a Data, + pub(crate) instance_buffer_data: &'a Data, + pub(crate) instance_count: u32, +} + +pub(crate) struct DynContextTlasPackage<'a> { + pub(crate) tlas_data: &'a Data, + pub(crate) instances: Box>> + 'a>, + pub(crate) lowest_unmodified: u32, +} + +/// Context version see [TlasBuildEntry]. +#[allow(dead_code)] +pub struct ContextTlasBuildEntry<'a, T: Context> { + pub(crate) tlas_data: &'a T::TlasData, + pub(crate) instance_buffer_data: &'a T::BufferData, + pub(crate) instance_count: u32, +} + +/// Context version see [TlasPackage]. +#[allow(dead_code)] +pub struct ContextTlasPackage<'a, T: Context> { + pub(crate) tlas_data: &'a T::TlasData, + pub(crate) instances: Box>> + 'a>, + pub(crate) lowest_unmodified: u32, +} diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index e0cf006e6..963d7836f 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -1137,6 +1137,8 @@ impl crate::context::Context for ContextWebGpu { type RenderBundleEncoderData = Sendable; type RenderBundleData = Sendable; type SurfaceData = Sendable<(Canvas, webgpu_sys::GpuCanvasContext)>; + type BlasData = (); + type TlasData = (); type SurfaceOutputDetail = SurfaceOutputDetail; type SubmissionIndexData = (); @@ -1758,6 +1760,9 @@ impl crate::context::Context for ContextWebGpu { crate::BindingResource::TextureViewArray(..) => { panic!("Web backend does not support BINDING_INDEXING extension") } + crate::BindingResource::AccelerationStructure(_) => { + unimplemented!("Raytracing not implemented for web") + } }; webgpu_sys::GpuBindGroupEntry::new(binding.binding, &mapped_resource) @@ -3384,6 +3389,57 @@ impl crate::context::Context for ContextWebGpu { fn render_pass_end(&self, pass_data: &mut Self::RenderPassData) { pass_data.0.end(); } + + fn device_create_blas( + &self, + _device_data: &Self::DeviceData, + _desc: &crate::CreateBlasDescriptor<'_>, + _sizes: wgt::BlasGeometrySizeDescriptors, + ) -> (Option, Self::BlasData) { + unimplemented!("Raytracing not implemented for web"); + } + + fn device_create_tlas( + &self, + _device_data: &Self::DeviceData, + _desc: &crate::CreateTlasDescriptor<'_>, + ) -> Self::TlasData { + unimplemented!("Raytracing not implemented for web"); + } + + fn command_encoder_build_acceleration_structures_unsafe_tlas<'a>( + &'a self, + _encoder_data: &Self::CommandEncoderData, + _blas: impl Iterator>, + _tlas: impl Iterator>, + ) { + unimplemented!("Raytracing not implemented for web"); + } + + fn command_encoder_build_acceleration_structures<'a>( + &'a self, + _encoder_data: &Self::CommandEncoderData, + _blas: impl Iterator>, + _tlas: impl Iterator>, + ) { + unimplemented!("Raytracing not implemented for web"); + } + + fn blas_destroy(&self, _blas_data: &Self::BlasData) { + unimplemented!("Raytracing not implemented for web"); + } + + fn blas_drop(&self, _blas_data: &Self::BlasData) { + unimplemented!("Raytracing not implemented for web"); + } + + fn tlas_destroy(&self, _tlas_data: &Self::TlasData) { + unimplemented!("Raytracing not implemented for web"); + } + + fn tlas_drop(&self, _tlas_data: &Self::TlasData) { + unimplemented!("Raytracing not implemented for web"); + } } pub(crate) type SurfaceOutputDetail = (); diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index 652df388f..befec4bd7 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -504,6 +504,18 @@ pub struct CommandEncoder { open: bool, } +#[derive(Debug)] +pub struct Blas { + id: wgc::id::BlasId, + // error_sink: ErrorSink, +} + +#[derive(Debug)] +pub struct Tlas { + id: wgc::id::TlasId, + // error_sink: ErrorSink, +} + impl crate::Context for ContextWgpuCore { type AdapterData = wgc::id::AdapterId; type DeviceData = Device; @@ -532,6 +544,8 @@ impl crate::Context for ContextWgpuCore { type SubmissionIndexData = wgc::SubmissionIndex; type RequestAdapterFuture = Ready>; + type BlasData = Blas; + type TlasData = Tlas; #[allow(clippy::type_complexity)] type RequestDeviceFuture = @@ -983,6 +997,11 @@ impl crate::Context for ContextWgpuCore { &remaining_arrayed_texture_views[array.len()..]; bm::BindingResource::TextureViewArray(Borrowed(slice)) } + BindingResource::AccelerationStructure(acceleration_structure) => { + bm::BindingResource::AccelerationStructure( + downcast_tlas(acceleration_structure).id, + ) + } }, }) .collect::>(); @@ -1411,7 +1430,7 @@ impl crate::Context for ContextWgpuCore { Some(range.end - range.start), operation, ) { - Ok(()) => (), + Ok(_) => (), Err(cause) => { self.handle_error_nolabel(&buffer_data.error_sink, cause, "Buffer::map_async") } @@ -2984,6 +3003,194 @@ impl crate::Context for ContextWgpuCore { ); } } + + fn device_create_blas( + &self, + device_data: &Self::DeviceData, + desc: &crate::CreateBlasDescriptor<'_>, + sizes: wgt::BlasGeometrySizeDescriptors, + ) -> (Option, Self::BlasData) { + let global = &self.0; + let (id, handle, error) = global.device_create_blas( + device_data.id, + &desc.map_label(|l| l.map(Borrowed)), + sizes, + None, + ); + if let Some(cause) = error { + self.handle_error( + &device_data.error_sink, + cause, + desc.label, + "Device::create_blas", + ); + } + ( + handle, + Blas { + id, + // error_sink: Arc::clone(&device_data.error_sink), + }, + ) + } + + fn device_create_tlas( + &self, + device_data: &Self::DeviceData, + desc: &crate::CreateTlasDescriptor<'_>, + ) -> Self::TlasData { + let global = &self.0; + let (id, error) = + global.device_create_tlas(device_data.id, &desc.map_label(|l| l.map(Borrowed)), None); + if let Some(cause) = error { + self.handle_error( + &device_data.error_sink, + cause, + desc.label, + "Device::create_blas", + ); + } + Tlas { + id, + // error_sink: Arc::clone(&device_data.error_sink), + } + } + + fn command_encoder_build_acceleration_structures_unsafe_tlas<'a>( + &'a self, + encoder_data: &Self::CommandEncoderData, + blas: impl Iterator>, + tlas: impl Iterator>, + ) { + let global = &self.0; + + let blas = blas.map(|e: crate::ContextBlasBuildEntry<'_, Self>| { + let geometries = match e.geometries { + crate::ContextBlasGeometries::TriangleGeometries(triangle_geometries) => { + let iter = triangle_geometries.into_iter().map(|tg| { + wgc::ray_tracing::BlasTriangleGeometry { + vertex_buffer: tg.vertex_buffer.id, + index_buffer: tg.index_buffer.map(|buf| buf.id), + transform_buffer: tg.transform_buffer.map(|buf| buf.id), + size: tg.size, + transform_buffer_offset: tg.transform_buffer_offset, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + } + }); + wgc::ray_tracing::BlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + wgc::ray_tracing::BlasBuildEntry { + blas_id: e.blas_data.id, + geometries, + } + }); + + let tlas = tlas + .into_iter() + .map(|e: crate::ContextTlasBuildEntry<'a, ContextWgpuCore>| { + wgc::ray_tracing::TlasBuildEntry { + tlas_id: e.tlas_data.id, + instance_buffer_id: e.instance_buffer_data.id, + instance_count: e.instance_count, + } + }); + + if let Err(cause) = global.command_encoder_build_acceleration_structures_unsafe_tlas( + encoder_data.id, + blas, + tlas, + ) { + self.handle_error_nolabel( + &encoder_data.error_sink, + cause, + "CommandEncoder::build_acceleration_structures_unsafe_tlas", + ); + } + } + + fn command_encoder_build_acceleration_structures<'a>( + &'a self, + encoder_data: &Self::CommandEncoderData, + blas: impl Iterator>, + tlas: impl Iterator>, + ) { + let global = &self.0; + + let blas = blas.map(|e: crate::ContextBlasBuildEntry<'_, Self>| { + let geometries = match e.geometries { + crate::ContextBlasGeometries::TriangleGeometries(triangle_geometries) => { + let iter = triangle_geometries.into_iter().map(|tg| { + wgc::ray_tracing::BlasTriangleGeometry { + vertex_buffer: tg.vertex_buffer.id, + index_buffer: tg.index_buffer.map(|buf| buf.id), + transform_buffer: tg.transform_buffer.map(|buf| buf.id), + size: tg.size, + transform_buffer_offset: tg.transform_buffer_offset, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + } + }); + wgc::ray_tracing::BlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + wgc::ray_tracing::BlasBuildEntry { + blas_id: e.blas_data.id, + geometries, + } + }); + + let tlas = tlas.into_iter().map(|e| { + let instances = + e.instances + .map(|instance: Option>| { + instance.map(|instance| wgc::ray_tracing::TlasInstance { + blas_id: instance.blas_data.id, + transform: instance.transform, + custom_index: instance.custom_index, + mask: instance.mask, + }) + }); + wgc::ray_tracing::TlasPackage { + tlas_id: e.tlas_data.id, + instances: Box::new(instances), + lowest_unmodified: e.lowest_unmodified, + } + }); + + if let Err(cause) = + global.command_encoder_build_acceleration_structures(encoder_data.id, blas, tlas) + { + self.handle_error_nolabel( + &encoder_data.error_sink, + cause, + "CommandEncoder::build_acceleration_structures_unsafe_tlas", + ); + } + } + + fn blas_destroy(&self, blas_data: &Self::BlasData) { + let global = &self.0; + let _ = global.blas_destroy(blas_data.id); + } + + fn blas_drop(&self, blas_data: &Self::BlasData) { + let global = &self.0; + global.blas_drop(blas_data.id) + } + + fn tlas_destroy(&self, tlas_data: &Self::TlasData) { + let global = &self.0; + let _ = global.tlas_destroy(tlas_data.id); + } + + fn tlas_drop(&self, tlas_data: &Self::TlasData) { + let global = &self.0; + global.tlas_drop(tlas_data.id) + } } #[derive(Debug)] @@ -3145,6 +3352,9 @@ fn downcast_texture_view( ) -> &::TextureViewData { downcast_ref(texture_view.data.as_ref()) } +fn downcast_tlas(tlas: &crate::Tlas) -> &::TlasData { + downcast_ref(tlas.data.as_ref()) +} fn downcast_sampler(sampler: &crate::Sampler) -> &::SamplerData { downcast_ref(sampler.data.as_ref()) } diff --git a/wgpu/src/context.rs b/wgpu/src/context.rs index a27459ab4..e9e23a55f 100644 --- a/wgpu/src/context.rs +++ b/wgpu/src/context.rs @@ -49,6 +49,9 @@ pub trait Context: Debug + WasmNotSendSync + Sized { type RenderBundleData: ContextData; type SurfaceData: ContextData; + type BlasData: ContextData; + type TlasData: ContextData; + type SurfaceOutputDetail: WasmNotSendSync + 'static; type SubmissionIndexData: ContextData + Copy; @@ -695,6 +698,34 @@ pub trait Context: Debug + WasmNotSendSync + Sized { render_bundles: &mut dyn Iterator, ); fn render_pass_end(&self, pass_data: &mut Self::RenderPassData); + + fn device_create_blas( + &self, + device_data: &Self::DeviceData, + desc: &crate::CreateBlasDescriptor<'_>, + sizes: wgt::BlasGeometrySizeDescriptors, + ) -> (Option, Self::BlasData); + fn device_create_tlas( + &self, + device_data: &Self::DeviceData, + desc: &crate::CreateTlasDescriptor<'_>, + ) -> Self::TlasData; + fn command_encoder_build_acceleration_structures_unsafe_tlas<'a>( + &'a self, + encoder_data: &Self::CommandEncoderData, + blas: impl Iterator>, + tlas: impl Iterator>, + ); + fn command_encoder_build_acceleration_structures<'a>( + &'a self, + encoder_data: &Self::CommandEncoderData, + blas: impl Iterator>, + tlas: impl Iterator>, + ); + fn blas_destroy(&self, blas_data: &Self::BlasData); + fn blas_drop(&self, blas_data: &Self::BlasData); + fn tlas_destroy(&self, tlas_data: &Self::TlasData); + fn tlas_drop(&self, tlas_data: &Self::TlasData); } pub(crate) fn downcast_ref(data: &crate::Data) -> &T { @@ -1341,6 +1372,33 @@ pub(crate) trait DynContext: Debug + WasmNotSendSync { pass_data: &mut crate::Data, render_bundles: &mut dyn Iterator, ); + fn device_create_blas( + &self, + device_data: &crate::Data, + desc: &crate::CreateBlasDescriptor<'_>, + sizes: wgt::BlasGeometrySizeDescriptors, + ) -> (Option, Box); + fn device_create_tlas( + &self, + device_data: &crate::Data, + desc: &crate::CreateTlasDescriptor<'_>, + ) -> Box; + fn command_encoder_build_acceleration_structures_unsafe_tlas( + &self, + encoder_data: &crate::Data, + blas: &mut dyn Iterator>, + tlas: &mut dyn Iterator>, + ); + fn command_encoder_build_acceleration_structures( + &self, + encoder_data: &crate::Data, + blas: &mut dyn Iterator>, + tlas: &mut dyn Iterator>, + ); + fn blas_destroy(&self, blas_data: &crate::Data); + fn blas_drop(&self, blas_data: &crate::Data); + fn tlas_destroy(&self, tlas_data: &crate::Data); + fn tlas_drop(&self, tlas_data: &crate::Data); fn render_pass_end(&self, pass_data: &mut crate::Data); } @@ -2681,6 +2739,152 @@ where let pass_data = downcast_mut(pass_data); Context::render_pass_end(self, pass_data) } + + fn device_create_blas( + &self, + device_data: &crate::Data, + desc: &crate::CreateBlasDescriptor<'_>, + sizes: wgt::BlasGeometrySizeDescriptors, + ) -> (Option, Box) { + let device_data = downcast_ref(device_data); + let (handle, data) = Context::device_create_blas(self, device_data, desc, sizes); + (handle, Box::new(data) as _) + } + + fn device_create_tlas( + &self, + device_data: &crate::Data, + desc: &crate::CreateTlasDescriptor<'_>, + ) -> Box { + let device_data = downcast_ref(device_data); + let data = Context::device_create_tlas(self, device_data, desc); + Box::new(data) as _ + } + + fn command_encoder_build_acceleration_structures_unsafe_tlas( + &self, + encoder_data: &crate::Data, + blas: &mut dyn Iterator>, + tlas: &mut dyn Iterator>, + ) { + let encoder_data = downcast_ref(encoder_data); + + let blas = blas.into_iter().map(|e| { + let geometries = match e.geometries { + crate::DynContextBlasGeometries::TriangleGeometries(triangle_geometries) => { + let iter = triangle_geometries.into_iter().map(|tg| { + crate::ContextBlasTriangleGeometry { + vertex_buffer: downcast_ref(tg.vertex_buffer), + index_buffer: tg.index_buffer.map(downcast_ref), + transform_buffer: tg.transform_buffer.map(downcast_ref), + size: tg.size, + transform_buffer_offset: tg.transform_buffer_offset, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + } + }); + crate::ContextBlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + crate::ContextBlasBuildEntry { + blas_data: downcast_ref(e.blas_data), + // blas_data: downcast_ref(e.blas_data), + geometries, + } + }); + + let tlas = tlas + .into_iter() + .map( + |e: crate::DynContextTlasBuildEntry<'_>| crate::ContextTlasBuildEntry { + tlas_data: downcast_ref(e.tlas_data), + instance_buffer_data: downcast_ref(e.instance_buffer_data), + instance_count: e.instance_count, + }, + ); + + Context::command_encoder_build_acceleration_structures_unsafe_tlas( + self, + encoder_data, + blas, + tlas, + ) + } + + fn command_encoder_build_acceleration_structures( + &self, + encoder_data: &crate::Data, + blas: &mut dyn Iterator>, + tlas: &mut dyn Iterator>, + ) { + let encoder_data = downcast_ref(encoder_data); + + let blas = blas.into_iter().map(|e| { + let geometries = match e.geometries { + crate::DynContextBlasGeometries::TriangleGeometries(triangle_geometries) => { + let iter = triangle_geometries.into_iter().map(|tg| { + crate::ContextBlasTriangleGeometry { + vertex_buffer: downcast_ref(tg.vertex_buffer), + index_buffer: tg.index_buffer.map(downcast_ref), + transform_buffer: tg.transform_buffer.map(downcast_ref), + size: tg.size, + transform_buffer_offset: tg.transform_buffer_offset, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + } + }); + crate::ContextBlasGeometries::TriangleGeometries(Box::new(iter)) + } + }; + crate::ContextBlasBuildEntry { + blas_data: downcast_ref(e.blas_data), + // blas_data: downcast_ref(e.blas_data), + geometries, + } + }); + + let tlas = tlas.into_iter().map(|e: crate::DynContextTlasPackage<'_>| { + let instances = + e.instances + .map(|instance: Option>| { + instance.map(|instance| crate::ContextTlasInstance { + blas_data: downcast_ref(instance.blas), + transform: instance.transform, + custom_index: instance.custom_index, + mask: instance.mask, + }) + }); + crate::ContextTlasPackage { + tlas_data: downcast_ref(e.tlas_data), + instances: Box::new(instances), + lowest_unmodified: e.lowest_unmodified, + } + }); + + Context::command_encoder_build_acceleration_structures(self, encoder_data, blas, tlas) + } + + fn blas_destroy(&self, blas_data: &crate::Data) { + let blas_data = downcast_ref(blas_data); + Context::blas_destroy(self, blas_data) + } + + fn blas_drop(&self, blas_data: &crate::Data) { + let blas_data = downcast_ref(blas_data); + Context::blas_drop(self, blas_data) + } + + fn tlas_destroy(&self, tlas_data: &crate::Data) { + let tlas_data = downcast_ref(tlas_data); + Context::tlas_destroy(self, tlas_data) + } + + fn tlas_drop(&self, tlas_data: &crate::Data) { + let tlas_data = downcast_ref(tlas_data); + Context::tlas_drop(self, tlas_data) + } } pub trait QueueWriteBuffer: WasmNotSendSync + Debug {