diff --git a/CHANGELOG.md b/CHANGELOG.md index 2087875ee1..ecfc220a95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -196,6 +196,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 59c7e19875..c472cdb86b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" @@ -3746,6 +3749,7 @@ dependencies = [ "ctor", "env_logger", "futures-lite", + "glam", "image", "itertools", "js-sys", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 1f4d4951f5..02fb524a1c 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 0ce432cf23..c67941e067 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 d212fd404a..358993e1af 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 5d29d484b6..77e658a883 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 0000000000..25b4fc942d --- /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 0000000000..69adbb3ccd --- /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 0000000000..3f8f31ee0a --- /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 0000000000..6b737f1e2a 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 0000000000..43604a9970 --- /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 0000000000..9298e39d60 --- /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 0000000000..b9dfba9a16 --- /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 0000000000..5e95786759 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 0000000000..d98faeb011 --- /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 0000000000..59994638a4 --- /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 0000000000..855a249d94 --- /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 0000000000..886d0c0183 --- /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 0000000000..d238d489dc 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 0000000000..f6bd2398c8 --- /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 0000000000..2b751e8e82 --- /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 0000000000..adf25cd454 --- /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 0000000000..53e99aa7f7 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 0000000000..fb36f10459 --- /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 0000000000..1f9f3435cb --- /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 0000000000..a79e0657d6 --- /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 0000000000..d508e6113e --- /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 0000000000..888f674b83 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 0000000000..54b1eb5420 --- /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/player/src/lib.rs b/player/src/lib.rs index 3b0b4149ab..af82168ae4 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 63776a174f..a9663a3f73 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 e0cadeed8a..00d63adae8 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 0000000000..5a030a5d5a --- /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 0000000000..617852333b --- /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 0000000000..c0df9d385e --- /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 0000000000..e204392d2e --- /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 0000000000..52012bb928 --- /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 0000000000..20cc321d44 --- /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 0000000000..370d69e1c3 --- /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/root.rs b/tests/tests/root.rs index 5b70d2053f..dac56a9db0 100644 --- a/tests/tests/root.rs +++ b/tests/tests/root.rs @@ -37,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 55a3e145f1..8906b58755 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 93654c9235..28e76c1115 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, @@ -450,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, @@ -695,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 540ff9d2e5..33025579aa 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; @@ -31,6 +32,7 @@ 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()) diff --git a/wgpu-core/src/command/ray_tracing.rs b/wgpu-core/src/command/ray_tracing.rs new file mode 100644 index 0000000000..ac3bbb67b4 --- /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/conv.rs b/wgpu-core/src/conv.rs index d27583b02a..a9f48ff691 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 72ba39dc87..b6ad2354c3 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 { diff --git a/wgpu-core/src/device/life.rs b/wgpu-core/src/device/life.rs index 84ff18440a..70e5337a7f 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)] @@ -226,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( diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index a4c2d22277..81cb4654d2 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; @@ -544,6 +545,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 25e8806c9c..ade609f20c 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(), }); } @@ -1462,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 0000000000..baab420919 --- /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 46271f7ac9..f47bc29030 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, }) @@ -1861,7 +1865,7 @@ impl Device { }, ) } - Bt::AccelerationStructure => todo!(), + Bt::AccelerationStructure => (None, WritableStorage::No), }; // Validate the count parameter @@ -2188,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( @@ -2227,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; @@ -2326,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 { @@ -2350,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))?; diff --git a/wgpu-core/src/device/trace.rs b/wgpu-core/src/device/trace.rs index ff4eea47be..89b7b48f9d 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 ae6cbbb874..15d3e3c06b 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 4e4897c832..fbf366982d 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 b87b2cd638..1edb27e7ab 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 abb5cb002d..842dadf26d 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 0000000000..11ccb714f1 --- /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 157b83641a..8ea9dd07ed 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -21,6 +21,7 @@ use crate::{ use smallvec::SmallVec; use thiserror::Error; +use std::num::NonZeroU64; use std::{ borrow::{Borrow, Cow}, fmt::Debug, @@ -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 0000000000..f5930a5e01 --- /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 1c2718981b..9a66b5f903 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 0000000000..c344526dfb --- /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 d1c2c87dd5..975a850f36 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 c1cbdaf183..13cf443d48 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/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 6d15492f4c..30ae7f155d 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`, @@ -722,13 +722,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, @@ -983,14 +986,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); } @@ -1779,7 +1784,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); } @@ -1810,6 +1815,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 6b02e35f4e..eb80697336 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 b829307068..75b807a29c 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 a56b42ae8a..39afe88ef6 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 89abd63b51..92ff6da74e 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -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 @@ -986,6 +995,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! { @@ -5296,6 +5315,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; } } @@ -7423,19 +7446,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; } ); @@ -7445,14 +7568,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 42a774b295..fac59338ff 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 0000000000..329f11849a --- /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 a45564b45d..f7f453aec2 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 5da9f6c80a..2fa040ec10 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 52b9ec1602..b94235393d 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/tlas.rs b/wgpu/src/api/tlas.rs new file mode 100644 index 0000000000..d387e508a0 --- /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 e0cf006e6e..963d7836f6 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 712e01cc44..befec4bd78 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::>(); @@ -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 a27459ab45..e9e23a55f3 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 {