use std::{borrow::Cow, future::Future, iter, mem, pin::Pin, task}; use bytemuck::{Pod, Zeroable}; use glam::{Affine3A, Mat4, Quat, Vec3}; use wgpu::util::DeviceExt; use crate::utils; 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}"); } }) } } struct Example { rt_target: wgpu::Texture, tlas: wgpu::Tlas, compute_pipeline: wgpu::ComputePipeline, compute_bind_group: wgpu::BindGroup, blit_pipeline: wgpu::RenderPipeline, blit_bind_group: wgpu::BindGroup, animation_timer: utils::AnimationTimer, } impl crate::framework::Example for Example { // Don't want srgb, so normals show up better. const SRGB: bool = false; fn required_features() -> wgpu::Features { wgpu::Features::EXPERIMENTAL_RAY_QUERY | wgpu::Features::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { wgpu::DownlevelCapabilities { flags: wgpu::DownlevelFlags::COMPUTE_SHADERS, ..Default::default() } } fn required_limits() -> wgpu::Limits { wgpu::Limits::default().using_minimum_supported_acceleration_structure_values() } 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), usage: None, 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::MipmapFilterMode::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 | wgpu::AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN, update_mode: wgpu::AccelerationStructureUpdateMode::Build, }, wgpu::BlasGeometrySizeDescriptors::Triangles { descriptors: vec![blas_geo_size_desc.clone()], }, ); let mut tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE | wgpu::AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN, 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: None, 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_mask: 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 dist = 3.0; for x in 0..side_count { for y in 0..side_count { tlas[(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), first_index: Some(0), transform_buffer: None, transform_buffer_offset: None, }, ]), }), iter::once(&tlas), ); queue.submit(Some(encoder.finish())); Example { rt_target, tlas, compute_pipeline, compute_bind_group, blit_pipeline, blit_bind_group, animation_timer: utils::AnimationTimer::default(), } } 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.animation_timer.time(); self.tlas[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)); { 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, depth_slice: None, 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, multiview_mask: 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] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "ray_cube_normals", image_path: "/examples/features/src/ray_cube_normals/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default().expect_fail( wgpu_test::FailureCase::backend_adapter(wgpu::Backends::VULKAN, "AMD") .panic("Image data mismatch"), ), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, };