From af61d9c91703d3c031ca724814ded077177db69b Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Sun, 3 Jan 2021 02:35:40 -0500 Subject: [PATCH] [rs] Add pipeline statistics and timeline queries --- wgpu/Cargo.toml | 6 +- wgpu/examples/README.md | 59 +++---- wgpu/examples/boids/main.rs | 1 + wgpu/examples/cube/main.rs | 1 + wgpu/examples/framework.rs | 3 +- wgpu/examples/hello-compute/shader.comp | 2 +- wgpu/examples/hello-compute/shader.comp.spv | Bin 1576 -> 1616 bytes wgpu/examples/mipmap/main.rs | 158 ++++++++++++++++- wgpu/examples/msaa-line/main.rs | 1 + wgpu/examples/shadow/main.rs | 1 + wgpu/examples/skybox/main.rs | 1 + wgpu/examples/texture-arrays/main.rs | 1 + wgpu/examples/water/main.rs | 1 + wgpu/src/backend/direct.rs | 118 +++++++++++++ wgpu/src/lib.rs | 178 +++++++++++++++++++- 15 files changed, 481 insertions(+), 50 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 0c0a6fec2a..33bc9e54cd 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -26,20 +26,20 @@ webgl = ["wgc"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgc] package = "wgpu-core" git = "https://github.com/gfx-rs/wgpu" -rev = "c788b1cef630fa5b5f0a86132fe84551d4149fb6" +rev = "9dce0afa746d624533dd086985889a2439750628" features = ["raw-window-handle"] [target.'cfg(target_arch = "wasm32")'.dependencies.wgc] package = "wgpu-core" git = "https://github.com/gfx-rs/wgpu" -rev = "c788b1cef630fa5b5f0a86132fe84551d4149fb6" +rev = "9dce0afa746d624533dd086985889a2439750628" features = ["raw-window-handle"] optional = true [dependencies.wgt] package = "wgpu-types" git = "https://github.com/gfx-rs/wgpu" -rev = "c788b1cef630fa5b5f0a86132fe84551d4149fb6" +rev = "9dce0afa746d624533dd086985889a2439750628" [dependencies] arrayvec = "0.5" diff --git a/wgpu/examples/README.md b/wgpu/examples/README.md index 6046d2703b..71bf215d16 100644 --- a/wgpu/examples/README.md +++ b/wgpu/examples/README.md @@ -10,35 +10,36 @@ Notably, `capture` example shows rendering without a surface/window. It reads ba All framework-based examples render to the window. ## Feature matrix -| Feature | boids | cube | mipmap | msaa-line | shadow | skybox | texture-arrays | water | -| ------------------------- | ------ | ------ | ------ | --------- | ------ | ------ | -------------- | ------ | -| vertex attributes | :star: | :star: | :star: | :star: | :star: | | :star: | :star: | -| instancing | :star: | | | | | | | | -| lines and points | | | | :star: | | | | | -| dynamic buffer offsets | | | | | :star: | | | | -| implicit layout | | | :star: | | | | | | -| sampled color textures | :star: | :star: | :star: | | | :star: | :star: | :star: | -| storage textures | :star: | | | | | | | | -| binding array | | | | | | | :star: | | -| comparison samplers | | | | | :star: | | | | -| subresource views | | | :star: | | :star: | | | | -| cubemaps | | | | | | :star: | | | -| multisampling | | | | :star: | | | | | -| off-screen rendering | | | | | :star: | | | :star: | -| stencil testing | | | | | | | | | -| depth testing | | | | | :star: | | | :star: | -| depth biasing | | | | | :star: | | | | -| read-only depth | | | | | | | | :star: | -| blending | | :star: | | | | | | :star: | -| render bundles | | | | :star: | | | | :star: | -| compute passes | :star: | | | | | | | | -| optional extensions | | | | | | | :star: | | -| - binding indexing | | | | | | | :star: | | -| - push constants | | | | | | | :star: | | -| - depth clamping | | | | | :star: | | | | -| - BCn compressed textures | | | | | | :star: | | | -| - polygon mode | | :star: | | | | | | | -| WGSL shaders | | | | | | | | | +| Feature | boids | cube | mipmap | msaa-line | shadow | skybox | texture-arrays | water | +| ---------------------- | ------ | ------ | ------ | --------- | ------ | ------ | -------------- | ------ | +| vertex attributes | :star: | :star: | :star: | :star: | :star: | | :star: | :star: | +| instancing | :star: | | | | | | | | +| lines and points | | | | :star: | | | | | +| dynamic buffer offsets | | | | | :star: | | | | +| implicit layout | | | :star: | | | | | | +| sampled color textures | :star: | :star: | :star: | | | :star: | :star: | :star: | +| storage textures | :star: | | | | | | | | +| binding array | | | | | | | :star: | | +| comparison samplers | | | | | :star: | | | | +| subresource views | | | :star: | | :star: | | | | +| cubemaps | | | | | | :star: | | | +| multisampling | | | | :star: | | | | | +| off-screen rendering | | | | | :star: | | | :star: | +| stencil testing | | | | | | | | | +| depth testing | | | | | :star: | | | :star: | +| depth biasing | | | | | :star: | | | | +| read-only depth | | | | | | | | :star: | +| blending | | :star: | | | | | | :star: | +| render bundles | | | | :star: | | | | :star: | +| compute passes | :star: | | | | | | | | +| optional extensions | | | | | | | :star: | | +| - binding indexing | | | | | | | :star: | | +| - push constants | | | | | | | :star: | | +| - depth clamping | | | | | :star: | | | | +| - compressed textures | | | | | | :star: | | | +| - polygon mode | | :star: | | | | | | | +| - queries | | | :star: | | | | | | +| WGSL shaders | | | | | | | | | ## Hacking diff --git a/wgpu/examples/boids/main.rs b/wgpu/examples/boids/main.rs index b6917b86c9..36a32bf816 100644 --- a/wgpu/examples/boids/main.rs +++ b/wgpu/examples/boids/main.rs @@ -29,6 +29,7 @@ impl framework::Example for Example { /// constructs initial instance of Example struct fn init( sc_desc: &wgpu::SwapChainDescriptor, + _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { diff --git a/wgpu/examples/cube/main.rs b/wgpu/examples/cube/main.rs index 981256b4ee..50d84622b3 100644 --- a/wgpu/examples/cube/main.rs +++ b/wgpu/examples/cube/main.rs @@ -118,6 +118,7 @@ impl framework::Example for Example { fn init( sc_desc: &wgpu::SwapChainDescriptor, + _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { diff --git a/wgpu/examples/framework.rs b/wgpu/examples/framework.rs index d9023162c4..6dd9e92cbb 100644 --- a/wgpu/examples/framework.rs +++ b/wgpu/examples/framework.rs @@ -41,6 +41,7 @@ pub trait Example: 'static + Sized { } fn init( sc_desc: &wgpu::SwapChainDescriptor, + adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self; @@ -209,7 +210,7 @@ fn start( let mut swap_chain = device.create_swap_chain(&surface, &sc_desc); log::info!("Initializing the example..."); - let mut example = E::init(&sc_desc, &device, &queue); + let mut example = E::init(&sc_desc, &adapter, &device, &queue); #[cfg(not(target_arch = "wasm32"))] let mut last_update_inst = Instant::now(); diff --git a/wgpu/examples/hello-compute/shader.comp b/wgpu/examples/hello-compute/shader.comp index d1ab4cfd2e..b7b1edef88 100644 --- a/wgpu/examples/hello-compute/shader.comp +++ b/wgpu/examples/hello-compute/shader.comp @@ -13,7 +13,7 @@ layout(set = 0, binding = 0) buffer PrimeIndices { // This function returns how many times this recurrence needs to be applied to reach 1. uint collatz_iterations(uint n) { uint i = 0; - while(n != 1) { + while(n > 1) { if (mod(n, 2) == 0) { n = n / 2; } diff --git a/wgpu/examples/hello-compute/shader.comp.spv b/wgpu/examples/hello-compute/shader.comp.spv index 417dd3c5ed8594a4f69086fe183b2d86d4f4a937..7bf37d71de44ae838b62ac8144d5f85bcefd14ca 100644 GIT binary patch delta 75 zcmZ3%bAgAKnMs+Qfq{{M0|;F<^7b?Gx-+maI5RLXa09XA=9!GejG{UWYzziKE=X7e ShBtFEw=*)X**uYDIwJsi3JSUa delta 39 vcmcb>vx0}0nMs+Qfq{{Mg@J>?c_VK> Vec { .collect() } +struct QuerySets { + timestamp: wgpu::QuerySet, + timestamp_period: f32, + pipeline_statistics: wgpu::QuerySet, + data_buffer: wgpu::Buffer, +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct TimestampData { + start: u64, + end: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct QueryData { + timestamps: [TimestampData; MIP_PASS_COUNT as usize], + pipeline_queries: [u64; MIP_PASS_COUNT as usize], +} + struct Example { vertex_buf: wgpu::Buffer, bind_group: wgpu::BindGroup, @@ -77,6 +100,7 @@ impl Example { encoder: &mut wgpu::CommandEncoder, device: &wgpu::Device, texture: &wgpu::Texture, + query_sets: &Option, mip_count: u32, ) { let vs_module = device.create_shader_module(&wgpu::include_spirv!("blit.vert.spv")); @@ -154,6 +178,9 @@ impl Example { label: None, }); + let pipeline_query_index_base = target_mip as u32 - 1; + let timestamp_query_index_base = (target_mip as u32 - 1) * 2; + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { @@ -166,21 +193,51 @@ impl Example { }], depth_stencil_attachment: None, }); + if let Some(ref query_sets) = query_sets { + rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base); + rpass.begin_pipeline_statistics_query( + &query_sets.pipeline_statistics, + pipeline_query_index_base, + ); + } rpass.set_pipeline(&pipeline); rpass.set_bind_group(0, &bind_group, &[]); rpass.draw(0..4, 0..1); + if let Some(ref query_sets) = query_sets { + rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base + 1); + rpass.end_pipeline_statistics_query(); + } + } + + if let Some(ref query_sets) = query_sets { + let timestamp_query_count = MIP_PASS_COUNT * 2; + encoder.resolve_query_set( + &query_sets.timestamp, + 0..timestamp_query_count, + &query_sets.data_buffer, + 0, + ); + encoder.resolve_query_set( + &query_sets.pipeline_statistics, + 0..MIP_PASS_COUNT, + &query_sets.data_buffer, + (timestamp_query_count * mem::size_of::() as u32) as wgpu::BufferAddress, + ); } } } impl framework::Example for Example { + fn optional_features() -> wgpu::Features { + wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::PIPELINE_STATISTICS_QUERY + } + fn init( sc_desc: &wgpu::SwapChainDescriptor, + adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { - use std::mem; - let mut init_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); @@ -194,8 +251,7 @@ impl framework::Example for Example { }); // Create the texture - let mip_level_count = 9; - let size = 1 << mip_level_count; + let size = 1 << MIP_LEVEL_COUNT; let texels = create_texels(size as usize, -0.8, 0.156); let texture_extent = wgpu::Extent3d { width: size, @@ -204,7 +260,7 @@ impl framework::Example for Example { }; let texture = device.create_texture(&wgpu::TextureDescriptor { size: texture_extent, - mip_level_count, + mip_level_count: MIP_LEVEL_COUNT, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: TEXTURE_FORMAT, @@ -314,9 +370,95 @@ impl framework::Example for Example { label: None, }); - // Done - Self::generate_mipmaps(&mut init_encoder, &device, &texture, mip_level_count); + // If both kinds of query are supported, use queries + let query_sets = if device + .features() + .contains(wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::PIPELINE_STATISTICS_QUERY) + { + // For N total mips, it takes N - 1 passes to generate them, and we're measuring those. + let mip_passes = MIP_LEVEL_COUNT - 1; + + // Create the timestamp query set. We need twice as many queries as we have passes, + // as we need a query at the beginning and at the end of the operation. + let timestamp = device.create_query_set(&wgpu::QuerySetDescriptor { + count: mip_passes * 2, + ty: wgpu::QueryType::Timestamp, + }); + // Timestamp queries use an device-specific timestamp unit. We need to figure out how many + // nanoseconds go by for the timestamp to be incremented by one. The period is this value. + let timestamp_period = adapter.get_timestamp_period(); + + // We only need one pipeline statistics query per pass. + let pipeline_statistics = device.create_query_set(&wgpu::QuerySetDescriptor { + count: mip_passes, + ty: wgpu::QueryType::PipelineStatistics( + wgpu::PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS, + ), + }); + + // This databuffer has to store all of the query results, 2 * passes timestamp queries + // and 1 * passes statistics queries. Each query returns a u64 value. + let data_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("query buffer"), + size: mip_passes as wgpu::BufferAddress + * 3 + * mem::size_of::() as wgpu::BufferAddress, + usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::MAP_READ, + mapped_at_creation: false, + }); + + Some(QuerySets { + timestamp, + timestamp_period, + pipeline_statistics, + data_buffer, + }) + } else { + None + }; + + Self::generate_mipmaps( + &mut init_encoder, + &device, + &texture, + &query_sets, + MIP_LEVEL_COUNT, + ); + queue.submit(Some(init_encoder.finish())); + if let Some(ref query_sets) = query_sets { + // We can ignore the future as we're about to wait for the device. + let _ = query_sets + .data_buffer + .slice(..) + .map_async(wgpu::MapMode::Read); + // Wait for device to be done rendering mipmaps + device.poll(wgpu::Maintain::Wait); + // This is guaranteed to be ready. + let view = query_sets.data_buffer.slice(..).get_mapped_range(); + // Convert the raw data into a useful structure + let data: &QueryData = bytemuck::from_bytes(&*view); + // Iterate over the data + for (idx, (timestamp, pipeline)) in data + .timestamps + .iter() + .zip(data.pipeline_queries.iter()) + .enumerate() + { + // Figure out the timestamp differences and multiply by the period to get nanoseconds + let nanoseconds = + (timestamp.end - timestamp.start) as f32 * query_sets.timestamp_period; + // Nanoseconds is a bit small, so lets use microseconds. + let microseconds = nanoseconds / 1000.0; + // Print the data! + println!( + "Generating mip level {} took {:.3} μs and called the fragment shader {} times", + idx + 1, + microseconds, + pipeline + ); + } + } Example { vertex_buf, diff --git a/wgpu/examples/msaa-line/main.rs b/wgpu/examples/msaa-line/main.rs index d2c4ef43c2..277b969d1c 100644 --- a/wgpu/examples/msaa-line/main.rs +++ b/wgpu/examples/msaa-line/main.rs @@ -122,6 +122,7 @@ impl Example { impl framework::Example for Example { fn init( sc_desc: &wgpu::SwapChainDescriptor, + _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { diff --git a/wgpu/examples/shadow/main.rs b/wgpu/examples/shadow/main.rs index 262112c142..1a6b48201f 100644 --- a/wgpu/examples/shadow/main.rs +++ b/wgpu/examples/shadow/main.rs @@ -199,6 +199,7 @@ impl framework::Example for Example { fn init( sc_desc: &wgpu::SwapChainDescriptor, + _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { diff --git a/wgpu/examples/skybox/main.rs b/wgpu/examples/skybox/main.rs index 74a47a256c..1012ab9ec3 100644 --- a/wgpu/examples/skybox/main.rs +++ b/wgpu/examples/skybox/main.rs @@ -46,6 +46,7 @@ impl framework::Example for Skybox { fn init( sc_desc: &wgpu::SwapChainDescriptor, + _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { diff --git a/wgpu/examples/texture-arrays/main.rs b/wgpu/examples/texture-arrays/main.rs index 27e0cc2745..b891cd60b7 100644 --- a/wgpu/examples/texture-arrays/main.rs +++ b/wgpu/examples/texture-arrays/main.rs @@ -87,6 +87,7 @@ impl framework::Example for Example { } fn init( sc_desc: &wgpu::SwapChainDescriptor, + _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { diff --git a/wgpu/examples/water/main.rs b/wgpu/examples/water/main.rs index b72d4986ad..c81efe3954 100644 --- a/wgpu/examples/water/main.rs +++ b/wgpu/examples/water/main.rs @@ -263,6 +263,7 @@ impl Example { impl framework::Example for Example { fn init( sc_desc: &wgpu::SwapChainDescriptor, + _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index e8cad6b193..dd96680a6a 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -184,6 +184,25 @@ mod pass_impl { fn pop_debug_group(&mut self) { wgpu_compute_pass_pop_debug_group(self); } + + fn write_timestamp(&mut self, query_set: &wgc::id::QuerySetId, query_index: u32) { + unsafe { wgpu_compute_pass_write_timestamp(self, *query_set, query_index) } + } + + fn begin_pipeline_statistics_query( + &mut self, + query_set: &wgc::id::QuerySetId, + query_index: u32, + ) { + unsafe { + wgpu_compute_pass_begin_pipeline_statistics_query(self, *query_set, query_index) + } + } + + fn end_pipeline_statistics_query(&mut self) { + unsafe { wgpu_compute_pass_end_pipeline_statistics_query(self) } + } + fn dispatch(&mut self, x: u32, y: u32, z: u32) { wgpu_compute_pass_dispatch(self, x, y, z) } @@ -375,6 +394,24 @@ mod pass_impl { wgpu_render_pass_pop_debug_group(self); } + fn write_timestamp(&mut self, query_set: &wgc::id::QuerySetId, query_index: u32) { + unsafe { wgpu_render_pass_write_timestamp(self, *query_set, query_index) } + } + + fn begin_pipeline_statistics_query( + &mut self, + query_set: &wgc::id::QuerySetId, + query_index: u32, + ) { + unsafe { + wgpu_render_pass_begin_pipeline_statistics_query(self, *query_set, query_index) + } + } + + fn end_pipeline_statistics_query(&mut self) { + unsafe { wgpu_render_pass_end_pipeline_statistics_query(self) } + } + fn execute_bundles<'a, I: Iterator>( &mut self, render_bundles: I, @@ -600,6 +637,7 @@ impl crate::Context for Context { type BindGroupId = wgc::id::BindGroupId; type TextureViewId = wgc::id::TextureViewId; type SamplerId = wgc::id::SamplerId; + type QuerySetId = wgc::id::QuerySetId; type BufferId = Buffer; type TextureId = Texture; type PipelineLayoutId = wgc::id::PipelineLayoutId; @@ -702,6 +740,19 @@ impl crate::Context for Context { } } + fn adapter_get_timestamp_period(&self, adapter: &Self::AdapterId) -> f32 { + let global = &self.0; + let res = wgc::gfx_select!(adapter => global.adapter_get_timestamp_period( + *adapter + )); + match res { + Ok(v) => v, + Err(cause) => { + self.handle_error_fatal(cause, "Adapter::get_timestamp_period"); + } + } + } + fn adapter_get_info(&self, adapter: &wgc::id::AdapterId) -> AdapterInfo { let global = &self.0; match wgc::gfx_select!(*adapter => global.adapter_get_info(*adapter)) { @@ -1123,6 +1174,23 @@ impl crate::Context for Context { id } + fn device_create_query_set( + &self, + device: &Self::DeviceId, + desc: &wgt::QuerySetDescriptor, + ) -> Self::QuerySetId { + let global = &self.0; + let (id, error) = wgc::gfx_select!(device.id => global.device_create_query_set( + device.id, + &desc, + PhantomData + )); + if let Some(cause) = error { + self.handle_error_nolabel(&device.error_sink, cause, "Device::create_query_set"); + } + id + } + fn device_create_command_encoder( &self, device: &Self::DeviceId, @@ -1382,6 +1450,10 @@ impl crate::Context for Context { let global = &self.0; wgc::gfx_select!(*sampler => global.sampler_drop(*sampler)) } + fn query_set_drop(&self, query_set: &Self::QuerySetId) { + let global = &self.0; + wgc::gfx_select!(*query_set => global.query_set_drop(*query_set)) + } fn bind_group_drop(&self, bind_group: &Self::BindGroupId) { let global = &self.0; wgc::gfx_select!(*bind_group => global.bind_group_drop(*bind_group)) @@ -1532,6 +1604,52 @@ impl crate::Context for Context { } } + fn command_encoder_write_timestamp( + &self, + encoder: &Self::CommandEncoderId, + query_set: &Self::QuerySetId, + query_index: u32, + ) { + let global = &self.0; + if let Err(cause) = wgc::gfx_select!(encoder.id => global.command_encoder_write_timestamp( + encoder.id, + *query_set, + query_index + )) { + self.handle_error_nolabel( + &encoder.error_sink, + cause, + "CommandEncoder::write_timestamp", + ); + } + } + + fn command_encoder_resolve_query_set( + &self, + encoder: &Self::CommandEncoderId, + query_set: &Self::QuerySetId, + first_query: u32, + query_count: u32, + destination: &Self::BufferId, + destination_offset: wgt::BufferAddress, + ) { + let global = &self.0; + if let Err(cause) = wgc::gfx_select!(encoder.id => global.command_encoder_resolve_query_set( + encoder.id, + *query_set, + first_query, + query_count, + destination.id, + destination_offset + )) { + self.handle_error_nolabel( + &encoder.error_sink, + cause, + "CommandEncoder::resolve_query_set", + ); + } + } + fn command_encoder_begin_compute_pass( &self, encoder: &Self::CommandEncoderId, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 7691ff41f6..ec7444a717 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -29,14 +29,15 @@ pub use wgt::{ BlendDescriptor, BlendFactor, BlendOperation, BufferAddress, BufferBindingType, BufferSize, BufferUsage, Color, ColorStateDescriptor, ColorWrite, CommandBufferDescriptor, CompareFunction, CullMode, DepthStencilStateDescriptor, DeviceType, DynamicOffset, Extent3d, Features, - FilterMode, FrontFace, IndexFormat, InputStepMode, Limits, Origin3d, PolygonMode, - PowerPreference, PresentMode, PrimitiveTopology, PushConstantRange, - RasterizationStateDescriptor, SamplerBorderColor, ShaderFlags, ShaderLocation, ShaderStage, - StencilOperation, StencilStateDescriptor, StencilStateFaceDescriptor, StorageTextureAccess, - SwapChainDescriptor, SwapChainStatus, TextureAspect, TextureDataLayout, TextureDimension, - TextureFormat, TextureSampleType, TextureUsage, TextureViewDimension, - VertexAttributeDescriptor, VertexFormat, BIND_BUFFER_ALIGNMENT, COPY_BUFFER_ALIGNMENT, - COPY_BYTES_PER_ROW_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, + FilterMode, FrontFace, IndexFormat, InputStepMode, Limits, Origin3d, PipelineStatisticsTypes, + PolygonMode, PowerPreference, PresentMode, PrimitiveTopology, PushConstantRange, + QuerySetDescriptor, QueryType, RasterizationStateDescriptor, SamplerBorderColor, ShaderFlags, + ShaderLocation, ShaderStage, StencilOperation, StencilStateDescriptor, + StencilStateFaceDescriptor, StorageTextureAccess, SwapChainDescriptor, SwapChainStatus, + TextureAspect, TextureDataLayout, TextureDimension, TextureFormat, TextureSampleType, + TextureUsage, TextureViewDimension, VertexAttributeDescriptor, VertexFormat, + BIND_BUFFER_ALIGNMENT, COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, + PUSH_CONSTANT_ALIGNMENT, }; use backend::{BufferMappedRange, Context as C}; @@ -53,6 +54,9 @@ trait ComputePassInner { fn insert_debug_marker(&mut self, label: &str); fn push_debug_group(&mut self, group_label: &str); fn pop_debug_group(&mut self); + fn write_timestamp(&mut self, query_set: &Ctx::QuerySetId, query_index: u32); + fn begin_pipeline_statistics_query(&mut self, query_set: &Ctx::QuerySetId, query_index: u32); + fn end_pipeline_statistics_query(&mut self); fn dispatch(&mut self, x: u32, y: u32, z: u32); fn dispatch_indirect( &mut self, @@ -138,6 +142,9 @@ trait RenderPassInner: RenderInner { fn insert_debug_marker(&mut self, label: &str); fn push_debug_group(&mut self, group_label: &str); fn pop_debug_group(&mut self); + fn write_timestamp(&mut self, query_set: &Ctx::QuerySetId, query_index: u32); + fn begin_pipeline_statistics_query(&mut self, query_set: &Ctx::QuerySetId, query_index: u32); + fn end_pipeline_statistics_query(&mut self); fn execute_bundles<'a, I: Iterator>( &mut self, render_bundles: I, @@ -155,6 +162,7 @@ trait Context: Debug + Send + Sized + Sync { type SamplerId: Debug + Send + Sync + 'static; type BufferId: Debug + Send + Sync + 'static; type TextureId: Debug + Send + Sync + 'static; + type QuerySetId: Debug + Send + Sync + 'static; type PipelineLayoutId: Debug + Send + Sync + 'static; type RenderPipelineId: Debug + Send + Sync + 'static; type ComputePipelineId: Debug + Send + Sync + 'static; @@ -196,6 +204,7 @@ trait Context: Debug + Send + Sized + Sync { ) -> TextureFormat; fn adapter_features(&self, adapter: &Self::AdapterId) -> Features; fn adapter_limits(&self, adapter: &Self::AdapterId) -> Limits; + fn adapter_get_timestamp_period(&self, adapter: &Self::AdapterId) -> f32; fn adapter_get_info(&self, adapter: &Self::AdapterId) -> AdapterInfo; fn adapter_get_texture_format_features( &self, @@ -256,6 +265,11 @@ trait Context: Debug + Send + Sized + Sync { device: &Self::DeviceId, desc: &SamplerDescriptor, ) -> Self::SamplerId; + fn device_create_query_set( + &self, + device: &Self::DeviceId, + desc: &QuerySetDescriptor, + ) -> Self::QuerySetId; fn device_create_command_encoder( &self, device: &Self::DeviceId, @@ -309,6 +323,7 @@ trait Context: Debug + Send + Sized + Sync { fn texture_drop(&self, texture: &Self::TextureId); fn texture_view_drop(&self, texture_view: &Self::TextureViewId); fn sampler_drop(&self, sampler: &Self::SamplerId); + fn query_set_drop(&self, query_set: &Self::QuerySetId); fn bind_group_drop(&self, bind_group: &Self::BindGroupId); fn bind_group_layout_drop(&self, bind_group_layout: &Self::BindGroupLayoutId); fn pipeline_layout_drop(&self, pipeline_layout: &Self::PipelineLayoutId); @@ -386,6 +401,22 @@ trait Context: Debug + Send + Sized + Sync { fn command_encoder_push_debug_group(&self, encoder: &Self::CommandEncoderId, label: &str); fn command_encoder_pop_debug_group(&self, encoder: &Self::CommandEncoderId); + fn command_encoder_write_timestamp( + &self, + encoder: &Self::CommandEncoderId, + query_set: &Self::QuerySetId, + query_index: u32, + ); + fn command_encoder_resolve_query_set( + &self, + encoder: &Self::CommandEncoderId, + query_set: &Self::QuerySetId, + first_query: u32, + query_count: u32, + destination: &Self::BufferId, + destination_offset: BufferAddress, + ); + fn render_bundle_encoder_finish( &self, encoder: Self::RenderBundleEncoderId, @@ -868,6 +899,20 @@ impl Drop for RenderBundle { } } +/// Handle to a query set. +pub struct QuerySet { + context: Arc, + id: ::QuerySetId, +} + +impl Drop for QuerySet { + fn drop(&mut self) { + if !thread::panicking() { + self.context.query_set_drop(&self.id); + } + } +} + /// Handle to a command queue on a device. /// /// A `Queue` executes recorded [`CommandBuffer`] objects and provides convenience methods @@ -1414,6 +1459,13 @@ impl Adapter { Context::adapter_limits(&*self.context, &self.id) } + /// Gets the amount of nanoseconds each tick of a timestamp query represents. + /// + /// Returns zero if timestamp queries are unsupported. + pub fn get_timestamp_period(&self) -> f32 { + Context::adapter_get_timestamp_period(&*self.context, &self.id) + } + /// Get info about the adapter itself. pub fn get_info(&self) -> AdapterInfo { Context::adapter_get_info(&*self.context, &self.id) @@ -1558,6 +1610,14 @@ impl Device { } } + /// Creates a new [`QuerySet`]. + pub fn create_query_set(&self, desc: &QuerySetDescriptor) -> QuerySet { + QuerySet { + context: Arc::clone(&self.context), + id: Context::device_create_query_set(&*self.context, &self.id, desc), + } + } + /// Create a new [`SwapChain`] which targets `surface`. /// /// # Panics @@ -1991,6 +2051,49 @@ impl CommandEncoder { } } +/// [`Features::TIMESTAMP_QUERY`] must be enabled on the device in order to call these functions. +impl CommandEncoder { + /// Issue a timestamp command at this point in the queue. + /// The timestamp will be written to the specified query set, at the specified index. + /// + /// Must be multiplied by [`Device::get_timestamp_period`] to get + /// the value in nanoseconds. Absolute values have no meaning, + /// but timestamps can be subtracted to get the time it takes + /// for a string of operations to complete. + pub fn write_timestamp(&mut self, query_set: &QuerySet, query_index: u32) { + Context::command_encoder_write_timestamp( + &*self.context, + &self.id, + &query_set.id, + query_index, + ) + } +} + +/// [`Features::TIMESTAMP_QUERY`] or [`Features::PIPELINE_STATISTICS_QUERY`] must be enabled on the device in order to call these functions. +impl CommandEncoder { + /// Resolve a query set, writing the results into the supplied destination buffer. + /// + /// Queries may be between 8 and 40 bytes each. See [`PipelineStatisticsType`] for more information. + pub fn resolve_query_set( + &mut self, + query_set: &QuerySet, + query_range: Range, + destination: &Buffer, + destination_offset: BufferAddress, + ) { + Context::command_encoder_resolve_query_set( + &*self.context, + &self.id, + &query_set.id, + query_range.start, + query_range.end - query_range.start, + &destination.id, + destination_offset, + ) + } +} + impl<'a> RenderPass<'a> { /// Sets the active bind group for a given bind group index. The bind group layout /// in the active pipeline when any `draw()` function is called must match the layout of this bind group. @@ -2354,6 +2457,36 @@ impl<'a> RenderPass<'a> { } } +/// [`Features::TIMESTAMP_QUERY`] must be enabled on the device in order to call these functions. +impl<'a> RenderPass<'a> { + /// Issue a timestamp command at this point in the queue. The + /// timestamp will be written to the specified query set, at the specified index. + /// + /// Must be multiplied by [`Device::get_timestamp_period`] to get + /// the value in nanoseconds. Absolute values have no meaning, + /// but timestamps can be subtracted to get the time it takes + /// for a string of operations to complete. + pub fn write_timestamp(&mut self, query_set: &QuerySet, query_index: u32) { + self.id.write_timestamp(&query_set.id, query_index) + } +} + +/// [`Features::PIPEILNE_STATISTICS_QUERY`] must be enabled on the device in order to call these functions. +impl<'a> RenderPass<'a> { + /// Start a pipeline statistics query on this render pass. It can be ended with + /// `end_pipeline_statistics_query`. Pipeline statistics queries may not be nested. + pub fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, query_index: u32) { + self.id + .begin_pipeline_statistics_query(&query_set.id, query_index); + } + + /// End the pipeline statistics query on this render pass. It can be started with + /// `begin_pipeline_statistics_query`. Pipeline statistics queries may not be nested. + pub fn end_pipeline_statistics_query(&mut self) { + self.id.end_pipeline_statistics_query(); + } +} + impl<'a> Drop for RenderPass<'a> { fn drop(&mut self) { if !thread::panicking() { @@ -2430,6 +2563,35 @@ impl<'a> ComputePass<'a> { } } +/// [`Features::TIMESTAMP_QUERY`] must be enabled on the device in order to call these functions. +impl<'a> ComputePass<'a> { + /// Issue a timestamp command at this point in the queue. The timestamp will be written to the specified query set, at the specified index. + /// + /// Must be multiplied by [`Device::get_timestamp_period`] to get + /// the value in nanoseconds. Absolute values have no meaning, + /// but timestamps can be subtracted to get the time it takes + /// for a string of operations to complete. + pub fn write_timestamp(&mut self, query_set: &QuerySet, query_index: u32) { + self.id.write_timestamp(&query_set.id, query_index) + } +} + +/// [`Features::PIPEILNE_STATISTICS_QUERY`] must be enabled on the device in order to call these functions. +impl<'a> ComputePass<'a> { + /// Start a pipeline statistics query on this render pass. It can be ended with + /// `end_pipeline_statistics_query`. Pipeline statistics queries may not be nested. + pub fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, query_index: u32) { + self.id + .begin_pipeline_statistics_query(&query_set.id, query_index); + } + + /// End the pipeline statistics query on this render pass. It can be started with + /// `begin_pipeline_statistics_query`. Pipeline statistics queries may not be nested. + pub fn end_pipeline_statistics_query(&mut self) { + self.id.end_pipeline_statistics_query(); + } +} + impl<'a> Drop for ComputePass<'a> { fn drop(&mut self) { if !thread::panicking() {