From 92868c6dd716109620dd6a57525f45c68314c8d5 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Thu, 24 Sep 2026 11:17:21 -0700 Subject: [PATCH] Add bring-your-own-encoder methods everywhere --- fidget-wgpu/src/lib.rs | 17 +- fidget-wgpu/src/pixel/effects/mod.rs | 235 +++++---- fidget-wgpu/src/pixel/mod.rs | 27 +- fidget-wgpu/src/voxel/effects/mod.rs | 740 ++++++++++++++------------- fidget-wgpu/src/voxel/mod.rs | 5 +- 5 files changed, 554 insertions(+), 470 deletions(-) diff --git a/fidget-wgpu/src/lib.rs b/fidget-wgpu/src/lib.rs index 3ef205b5..cf6db2f3 100644 --- a/fidget-wgpu/src/lib.rs +++ b/fidget-wgpu/src/lib.rs @@ -26,11 +26,18 @@ //! Users are expected to create one (of each) context object per thread or //! worker, since GPU resources can't be shared. //! -//! Context objects have two flavors of functions. At the highest level, `run` -//! and `run_async` functions perform rendering and copy data back to the CPU -//! (e.g. [`voxel::Context::run`] and [`run_async`](voxel::Context::run_async)). -//! To simply submit work to the GPU, use a `submit` function (e.g. -//! [`voxel::Context::submit`]). +//! Context objects have three flavors of functions: +//! +//! - At the highest level, `run` and `run_async` functions perform rendering +//! and copy data back to the CPU (e.g. [`voxel::Context::run`] and +//! [`run_async`](voxel::Context::run_async)). +//! - To simply submit work to the GPU, use a `submit` function (e.g. +//! [`voxel::Context::submit`]). +//! - At the lowest level, to encode a rendering operation into a WebGPU +//! `CommandEncoder`, use an `encode` function (e.g. +//! [`voxel::Context::encode`]). Note that encode-flavored functions may +//! still use the GPU device and queue from the context, e.g. to allocate +//! buffers or copy configuration blobs. //! //! ### Workspace objects //! Workspace objects contain all of the buffers that are used when dispatching diff --git a/fidget-wgpu/src/pixel/effects/mod.rs b/fidget-wgpu/src/pixel/effects/mod.rs index d647dcd1..3a66bc65 100644 --- a/fidget-wgpu/src/pixel/effects/mod.rs +++ b/fidget-wgpu/src/pixel/effects/mod.rs @@ -230,6 +230,24 @@ impl Context { image: &FlexBuffer, remove_nans: bool, buf: &mut MergeWorkspace, + ) -> Result<(), MergeError> { + let mut encoder = self.gpu.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("merge compute encoder"), + }, + ); + self.encode_merge(image, remove_nans, buf, &mut encoder)?; + self.gpu.queue.submit(Some(encoder.finish())); + Ok(()) + } + + /// Low-level function to encode a merge operation into a command encoder + pub fn encode_merge( + &self, + image: &FlexBuffer, + remove_nans: bool, + buf: &mut MergeWorkspace, + encoder: &mut wgpu::CommandEncoder, ) -> Result<(), MergeError> { let size = image.size(); if buf.image_count > 0 { @@ -250,73 +268,64 @@ impl Context { .map_err(MergeError::OutputSize)?; } buf.has_color = false; - let mut encoder = self.gpu.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("merge compute encoder"), - }, - ); - // Scope to bound the lifetime of compute_pass + let mut compute_pass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("merge compute pass"), + timestamp_writes: None, // TODO add timestamps? + }); + compute_pass.set_pipeline(&self.merge_pipeline); + let cfg = MergeConfig { + image_size: [size.width(), size.height()], + remove_nans: remove_nans as u32, + index_base: buf.image_count as u32, + }; { - let mut compute_pass = - encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("merge compute pass"), - timestamp_writes: None, // TODO add timestamps? - }); - compute_pass.set_pipeline(&self.merge_pipeline); - let cfg = MergeConfig { - image_size: [size.width(), size.height()], - remove_nans: remove_nans as u32, - index_base: buf.image_count as u32, - }; - { - let mut writer = self - .gpu - .queue - .write_buffer_with( - &buf.config, - 0, - (std::mem::size_of::() as u64) - .try_into() - .unwrap(), - ) - .unwrap(); - writer.copy_from_slice(cfg.as_bytes()); - } - - let bg = - self.gpu - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("merge bind group"), - layout: &self.merge_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: buf.config.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: image.bind_active(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: buf.distance.bind_active(), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: buf.color.bind_active(), - }, - ], - }); - compute_pass.set_bind_group(0, Some(&bg), &[]); - compute_pass.dispatch_workgroups( - size.width().div_ceil(8), - size.height().div_ceil(8), - 1, - ); - buf.image_count += 1; + let mut writer = self + .gpu + .queue + .write_buffer_with( + &buf.config, + 0, + (std::mem::size_of::() as u64) + .try_into() + .unwrap(), + ) + .unwrap(); + writer.copy_from_slice(cfg.as_bytes()); } - self.gpu.queue.submit(Some(encoder.finish())); + + let bg = + self.gpu + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("merge bind group"), + layout: &self.merge_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buf.config.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: image.bind_active(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: buf.distance.bind_active(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: buf.color.bind_active(), + }, + ], + }); + compute_pass.set_bind_group(0, Some(&bg), &[]); + compute_pass.dispatch_workgroups( + size.width().div_ceil(8), + size.height().div_ceil(8), + 1, + ); + buf.image_count += 1; Ok(()) } @@ -379,9 +388,27 @@ impl Context { shape: &ShapeColorBuffers, bufs: &mut ColorWorkspace, vars: &ShapeVars, + ) -> Result<(), ColorError> { + let mut encoder = self.gpu.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { label: None }, + ); + self.encode_color(merge, settings, shape, bufs, vars, &mut encoder)?; + self.gpu.queue.submit(Some(encoder.finish())); + Ok(()) + } + + /// Low-level function to encode a color rendering pass + pub fn encode_color( + &self, + merge: &mut MergeWorkspace, + settings: ColorSettings, + shape: &ShapeColorBuffers, + bufs: &mut ColorWorkspace, + vars: &ShapeVars, + encoder: &mut wgpu::CommandEncoder, ) -> Result<(), ColorError> { self.color_ctx - .submit(merge, settings, shape, bufs, vars, &self.gpu) + .encode(merge, settings, shape, bufs, vars, &self.gpu, encoder) } /// Returns a new workspace for color evaluation @@ -500,7 +527,8 @@ impl ColorContext { } } - fn submit( + #[allow(clippy::too_many_arguments)] + fn encode( &self, image: &mut MergeWorkspace, settings: ColorSettings, @@ -508,6 +536,7 @@ impl ColorContext { bufs: &mut ColorWorkspace, vars: &ShapeVars, gpu: &Gpu, + encoder: &mut wgpu::CommandEncoder, ) -> Result<(), ColorError> { if image.image_count != shape.shape_count() { return Err(ColorError::BadShapeCount { @@ -544,47 +573,39 @@ impl ColorContext { let config_bg = bufs.config_bind_group(&gpu.device, &self.config_bind_group_layout); - // Create a command encoder and dispatch the compute work - let mut encoder = gpu.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { label: None }, + let mut compute_pass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, // TODO add timestamps? + }); + compute_pass.set_bind_group(0, config_bg, &[]); + + // TODO this creates a bind group for every evaluation, instead of + // caching it somewhere. However, *where* to cache it is not + // obvious, because it combines fields from two different buffer + // objects. + let image_bg = + gpu.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("color image bind group"), + layout: &self.image_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: image.distance.bind_active(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: image.color.bind_active(), + }, + ], + }); + compute_pass.set_bind_group(1, &image_bg, &[]); + compute_pass.set_pipeline(self.color_pipeline.get(shape.reg_count())); + compute_pass.dispatch_workgroups( + size.width().div_ceil(8), + size.height().div_ceil(8), + 1, ); - { - let mut compute_pass = - encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: None, - timestamp_writes: None, // TODO add timestamps? - }); - compute_pass.set_bind_group(0, config_bg, &[]); - - // TODO this creates a bind group for every evaluation, instead of - // caching it somewhere. However, *where* to cache it is not - // obvious, because it combines fields from two different buffer - // objects. - let image_bg = - gpu.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("color image bind group"), - layout: &self.image_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: image.distance.bind_active(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: image.color.bind_active(), - }, - ], - }); - compute_pass.set_bind_group(1, &image_bg, &[]); - compute_pass - .set_pipeline(self.color_pipeline.get(shape.reg_count())); - compute_pass.dispatch_workgroups( - size.width().div_ceil(8), - size.height().div_ceil(8), - 1, - ); - } - gpu.queue.submit(Some(encoder.finish())); image.has_color = true; Ok(()) } diff --git a/fidget-wgpu/src/pixel/mod.rs b/fidget-wgpu/src/pixel/mod.rs index 48a98650..f51f9cbc 100644 --- a/fidget-wgpu/src/pixel/mod.rs +++ b/fidget-wgpu/src/pixel/mod.rs @@ -1104,6 +1104,23 @@ impl Context { vars: &ShapeVars, workspace: &mut Workspace, settings: &RenderConfig, + ) -> Result<(), SubmitError> { + let mut encoder = self.gpu.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { label: None }, + ); + self.encode(shape, vars, workspace, settings, &mut encoder)?; + self.gpu.queue.submit(Some(encoder.finish())); + Ok(()) + } + + /// Low-level function to encode pixel rendering to a command encoder + pub fn encode( + &self, + shape: &RenderShape, + vars: &ShapeVars, + workspace: &mut Workspace, + settings: &RenderConfig, + encoder: &mut wgpu::CommandEncoder, ) -> Result<(), SubmitError> { workspace.set_image_size(&self.gpu.device, settings.image_size)?; let render_size = TileRenderSize::from(workspace.image_size); @@ -1163,13 +1180,8 @@ impl Context { workspace.bind_groups.common = Default::default(); } - // Create a command encoder and dispatch the compute work - let mut encoder = self.gpu.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { label: None }, - ); - // Initial buffer reset pass - self.reset_ctx.run(&mut encoder, workspace); + self.reset_ctx.run(encoder, workspace); let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { @@ -1210,9 +1222,6 @@ impl Context { &mut compute_pass, ); drop(compute_pass); - - // Submit the commands and wait for the GPU to complete - self.gpu.queue.submit(Some(encoder.finish())); Ok(()) } } diff --git a/fidget-wgpu/src/voxel/effects/mod.rs b/fidget-wgpu/src/voxel/effects/mod.rs index f61c3cb3..ea420ade 100644 --- a/fidget-wgpu/src/voxel/effects/mod.rs +++ b/fidget-wgpu/src/voxel/effects/mod.rs @@ -563,6 +563,24 @@ impl Context { image: &FlexBuffer, settings: MergeSettings, buf: &mut MergeWorkspace, + ) -> Result<(), MergeError> { + let mut encoder = self.gpu.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("merge compute encoder"), + }, + ); + self.encode_merge(image, settings, buf, &mut encoder)?; + self.gpu.queue.submit(Some(encoder.finish())); + Ok(()) + } + + /// Low-level function to encode a merge operation to a command encoder + pub fn encode_merge( + &self, + image: &FlexBuffer, + settings: MergeSettings, + buf: &mut MergeWorkspace, + encoder: &mut wgpu::CommandEncoder, ) -> Result<(), MergeError> { let size = image.size(); if buf.image_count == 0 { @@ -576,71 +594,63 @@ impl Context { } .into()); } - let mut encoder = self.gpu.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("merge compute encoder"), - }, - ); // Scope to bound the lifetime of compute_pass + let mut compute_pass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("merge compute pass"), + timestamp_writes: None, // TODO add timestamps? + }); + compute_pass.set_pipeline(&self.merge_pipeline); + let cfg = MergeConfig { + image_size: [size.width(), size.height()], + denoise: settings.denoise as u32, + index_base: buf.image_count as u32, + z_scale: settings.z_scale, + _pad: 0, + }; { - let mut compute_pass = - encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("merge compute pass"), - timestamp_writes: None, // TODO add timestamps? - }); - compute_pass.set_pipeline(&self.merge_pipeline); - let cfg = MergeConfig { - image_size: [size.width(), size.height()], - denoise: settings.denoise as u32, - index_base: buf.image_count as u32, - z_scale: settings.z_scale, - _pad: 0, - }; - { - let mut writer = self - .gpu - .queue - .write_buffer_with( - &buf.config, - 0, - (std::mem::size_of::() as u64) - .try_into() - .unwrap(), - ) - .unwrap(); - writer.copy_from_slice(cfg.as_bytes()); - } - - let bg = - self.gpu - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("merge bind group"), - layout: &self.merge_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: buf.config.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: image.bind_active(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: buf.out.bind_active(), - }, - ], - }); - compute_pass.set_bind_group(0, Some(&bg), &[]); - compute_pass.dispatch_workgroups( - size.width().div_ceil(8), - size.height().div_ceil(8), - 1, - ); - buf.image_count += 1; + let mut writer = self + .gpu + .queue + .write_buffer_with( + &buf.config, + 0, + (std::mem::size_of::() as u64) + .try_into() + .unwrap(), + ) + .unwrap(); + writer.copy_from_slice(cfg.as_bytes()); } - self.gpu.queue.submit(Some(encoder.finish())); + + let bg = + self.gpu + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("merge bind group"), + layout: &self.merge_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buf.config.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: image.bind_active(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: buf.out.bind_active(), + }, + ], + }); + compute_pass.set_bind_group(0, Some(&bg), &[]); + compute_pass.dispatch_workgroups( + size.width().div_ceil(8), + size.height().div_ceil(8), + 1, + ); + buf.image_count += 1; Ok(()) } @@ -652,6 +662,24 @@ impl Context { image: &MergeWorkspace, ssao: Option<&SsaoWorkspace>, buf: &mut ShadeWorkspace, + ) -> Result<(), ShadeError> { + let mut encoder = self.gpu.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("shade compute encoder"), + }, + ); + self.encode_shade(image, ssao, buf, &mut encoder)?; + self.gpu.queue.submit(Some(encoder.finish())); + Ok(()) + } + + /// Low-level function to encode a shading operation to a command encoder + pub fn encode_shade( + &self, + image: &MergeWorkspace, + ssao: Option<&SsaoWorkspace>, + buf: &mut ShadeWorkspace, + encoder: &mut wgpu::CommandEncoder, ) -> Result<(), ShadeError> { let size = image.out.size(); if buf.has_color { @@ -663,83 +691,73 @@ impl Context { .grow_to_fit(&self.gpu.device, size) .map_err(ShadeError::OutputSize)?; } - let mut encoder = self.gpu.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("shade compute encoder"), - }, - ); // Scope to bound the lifetime of compute_pass + let mut compute_pass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("shade compute pass"), + timestamp_writes: None, // TODO add timestamps? + }); + compute_pass.set_pipeline(&self.shade_pipeline); + let cfg = ShadeConfig { + image_size: [size.width(), size.height(), size.depth()], + flags: if ssao.is_some() { + SHADE_CONFIG_HAS_SSAO + } else { + 0 + } | if buf.has_color { + SHADE_CONFIG_HAS_COLOR + } else { + 0 + }, + }; { - let mut compute_pass = - encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("shade compute pass"), - timestamp_writes: None, // TODO add timestamps? - }); - compute_pass.set_pipeline(&self.shade_pipeline); - let cfg = ShadeConfig { - image_size: [size.width(), size.height(), size.depth()], - flags: if ssao.is_some() { - SHADE_CONFIG_HAS_SSAO - } else { - 0 - } | if buf.has_color { - SHADE_CONFIG_HAS_COLOR - } else { - 0 - }, - }; - { - let mut writer = self - .gpu - .queue - .write_buffer_with( - &buf.config, - 0, - buf.config.size().try_into().unwrap(), - ) - .unwrap(); - writer.copy_from_slice(cfg.as_bytes()); - } - // TODO This is created on every pass - let bg = - self.gpu - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("shade bind group"), - layout: &self.shade_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: buf.config.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: image.out.bind_active(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: ssao - .map(|s| { - s.blurred_occlusion().bind_active() - }) - .unwrap_or_else(|| image.out.bind_active()), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: buf.out.bind_active(), - }, - ], - }); - compute_pass.set_bind_group(0, Some(&bg), &[]); - compute_pass.dispatch_workgroups( - size.width().div_ceil(8), - size.height().div_ceil(8), - 1, - ); + let mut writer = self + .gpu + .queue + .write_buffer_with( + &buf.config, + 0, + buf.config.size().try_into().unwrap(), + ) + .unwrap(); + writer.copy_from_slice(cfg.as_bytes()); } + // TODO This is created on every pass + let bg = + self.gpu + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("shade bind group"), + layout: &self.shade_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buf.config.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: image.out.bind_active(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: ssao + .map(|s| s.blurred_occlusion().bind_active()) + .unwrap_or_else(|| image.out.bind_active()), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: buf.out.bind_active(), + }, + ], + }); + compute_pass.set_bind_group(0, Some(&bg), &[]); + compute_pass.dispatch_workgroups( + size.width().div_ceil(8), + size.height().div_ceil(8), + 1, + ); buf.has_color = false; - self.gpu.queue.submit(Some(encoder.finish())); Ok(()) } @@ -750,6 +768,23 @@ impl Context { &self, image: &MergeWorkspace, buf: &mut ShadeWorkspace, + ) -> Result<(), HeightmapError> { + let mut encoder = self.gpu.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("heightmap compute encoder"), + }, + ); + self.encode_heightmap(image, buf, &mut encoder)?; + self.gpu.queue.submit(Some(encoder.finish())); + Ok(()) + } + + /// Low-level function to encode heightmap rendering + pub fn encode_heightmap( + &self, + image: &MergeWorkspace, + buf: &mut ShadeWorkspace, + encoder: &mut wgpu::CommandEncoder, ) -> Result<(), HeightmapError> { let size = image.out.size(); if buf.has_color { @@ -761,67 +796,58 @@ impl Context { .grow_to_fit(&self.gpu.device, size) .map_err(HeightmapError::OutputSize)?; } - let mut encoder = self.gpu.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("heightmap compute encoder"), - }, - ); - // Scope to bound the lifetime of compute_pass + let mut compute_pass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("heightmap compute pass"), + timestamp_writes: None, // TODO add timestamps? + }); + compute_pass.set_pipeline(&self.heightmap_pipeline); + let cfg = HeightmapConfig { + image_size: [size.width(), size.height(), size.depth()], + has_color: buf.has_color.into(), + }; { - let mut compute_pass = - encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("heightmap compute pass"), - timestamp_writes: None, // TODO add timestamps? - }); - compute_pass.set_pipeline(&self.heightmap_pipeline); - let cfg = HeightmapConfig { - image_size: [size.width(), size.height(), size.depth()], - has_color: buf.has_color.into(), - }; - { - let mut writer = self - .gpu - .queue - .write_buffer_with( - &buf.config, - 0, - buf.config.size().try_into().unwrap(), - ) - .unwrap(); - writer.copy_from_slice(cfg.as_bytes()); - } - // TODO This is created on every pass - let bg = - self.gpu - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("heightmap bind group"), - layout: &self.heightmap_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: buf.config.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: image.out.bind_active(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: buf.out.bind_active(), - }, - ], - }); - compute_pass.set_bind_group(0, Some(&bg), &[]); - compute_pass.dispatch_workgroups( - size.width().div_ceil(8), - size.height().div_ceil(8), - 1, - ); + let mut writer = self + .gpu + .queue + .write_buffer_with( + &buf.config, + 0, + buf.config.size().try_into().unwrap(), + ) + .unwrap(); + writer.copy_from_slice(cfg.as_bytes()); } + // TODO This is created on every pass + let bg = + self.gpu + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("heightmap bind group"), + layout: &self.heightmap_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buf.config.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: image.out.bind_active(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: buf.out.bind_active(), + }, + ], + }); + compute_pass.set_bind_group(0, Some(&bg), &[]); + compute_pass.dispatch_workgroups( + size.width().div_ceil(8), + size.height().div_ceil(8), + 1, + ); buf.has_color = false; - self.gpu.queue.submit(Some(encoder.finish())); Ok(()) } @@ -831,7 +857,24 @@ impl Context { image: &MergeWorkspace, buf: &mut SsaoWorkspace, ) -> Result<(), SsaoError> { - self.ssao_ctx.submit(image, buf, &self.gpu) + let mut encoder = self.gpu.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("ssao command encoder"), + }, + ); + self.encode_ssao(image, buf, &mut encoder)?; + self.gpu.queue.submit(Some(encoder.finish())); + Ok(()) + } + + /// Low-level function to encode an SSAO pass + pub fn encode_ssao( + &self, + image: &MergeWorkspace, + buf: &mut SsaoWorkspace, + encoder: &mut wgpu::CommandEncoder, + ) -> Result<(), SsaoError> { + self.ssao_ctx.encode(image, buf, &self.gpu, encoder) } /// Submits a color evaluation pass @@ -871,7 +914,35 @@ impl Context { vars: &ShapeVars, out: &mut ShadeWorkspace, ) -> Result<(), ColorError> { - self.color_ctx.submit( + let mut encoder = self.gpu.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { label: None }, + ); + self.encode_color( + merge, + world_to_model, + shape, + bufs, + vars, + out, + &mut encoder, + )?; + self.gpu.queue.submit(Some(encoder.finish())); + Ok(()) + } + + /// Low-level function to encode a color rendering pass + #[allow(clippy::too_many_arguments)] + pub fn encode_color( + &self, + merge: &MergeWorkspace, + world_to_model: &nalgebra::Matrix4, + shape: &ShapeColorBuffers, + bufs: &mut ColorWorkspace, + vars: &ShapeVars, + out: &mut ShadeWorkspace, + encoder: &mut wgpu::CommandEncoder, + ) -> Result<(), ColorError> { + self.color_ctx.encode( merge, world_to_model, shape, @@ -879,6 +950,7 @@ impl Context { vars, out, &self.gpu, + encoder, ) } @@ -1083,11 +1155,12 @@ impl SsaoContext { } } - fn submit( + fn encode( &self, image: &MergeWorkspace, buf: &mut SsaoWorkspace, gpu: &Gpu, + encoder: &mut wgpu::CommandEncoder, ) -> Result<(), SsaoError> { let image_size = image.out.size(); buf.raw_occlusion @@ -1097,123 +1170,105 @@ impl SsaoContext { .grow_to_fit(&gpu.device, image_size.into()) .map_err(SsaoError::OutputSize)?; - // TODO make this passed in? - let mut encoder = gpu.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("ssao command encoder"), - }, - ); - - // Scope to bound the lifetime of compute_pass - { - let mut compute_pass = - encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("ssao compute pass"), - timestamp_writes: None, // TODO add timestamps? - }); - compute_pass.set_pipeline(&self.ssao_pipeline); - let cfg = SsaoConfig { - image_size: [ - image_size.width(), - image_size.height(), - image_size.depth(), - ], - radius: 0.1, - }; - { - let mut writer = gpu - .queue - .write_buffer_with( - &buf.ssao_config, - 0, - (std::mem::size_of::() as u64) - .try_into() - .unwrap(), - ) - .unwrap(); - writer.copy_from_slice(cfg.as_bytes()); - } - - let bg = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("ssao bind group"), - layout: &self.ssao_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: buf.ssao_config.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: image.out.bind_active(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: buf.raw_occlusion.bind_active(), - }, - ], + let mut compute_pass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("ssao compute pass"), + timestamp_writes: None, // TODO add timestamps? }); - compute_pass.set_bind_group(0, Some(&bg), &[]); - compute_pass.set_bind_group(1, Some(&self.ssao_bind_group), &[]); - compute_pass.dispatch_workgroups( - image_size.width().div_ceil(8), - image_size.height().div_ceil(8), - 1, - ); + compute_pass.set_pipeline(&self.ssao_pipeline); + let cfg = SsaoConfig { + image_size: [ + image_size.width(), + image_size.height(), + image_size.depth(), + ], + radius: 0.1, + }; + { + let mut writer = gpu + .queue + .write_buffer_with( + &buf.ssao_config, + 0, + (std::mem::size_of::() as u64) + .try_into() + .unwrap(), + ) + .unwrap(); + writer.copy_from_slice(cfg.as_bytes()); } - { - let mut compute_pass = - encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("ssao blur compute pass"), - timestamp_writes: None, // TODO add timestamps? - }); - compute_pass.set_pipeline(&self.blur_pipeline); - let cfg = BlurConfig { - image_size: [image_size.width(), image_size.height()], - radius: 2, - _pad: 0, - }; - { - let mut writer = gpu - .queue - .write_buffer_with( - &buf.blur_config, - 0, - (std::mem::size_of::() as u64) - .try_into() - .unwrap(), - ) - .unwrap(); - writer.copy_from_slice(cfg.as_bytes()); - } + let bg = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("ssao bind group"), + layout: &self.ssao_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buf.ssao_config.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: image.out.bind_active(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: buf.raw_occlusion.bind_active(), + }, + ], + }); + compute_pass.set_bind_group(0, Some(&bg), &[]); + compute_pass.set_bind_group(1, Some(&self.ssao_bind_group), &[]); + compute_pass.dispatch_workgroups( + image_size.width().div_ceil(8), + image_size.height().div_ceil(8), + 1, + ); - let bg = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("blur bind group"), - layout: &self.blur_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: buf.blur_config.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: buf.raw_occlusion.bind_active(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: buf.blurred_occlusion.bind_active(), - }, - ], - }); - compute_pass.set_bind_group(0, Some(&bg), &[]); - compute_pass.dispatch_workgroups( - image_size.width().div_ceil(8), - image_size.height().div_ceil(8), - 1, - ); + compute_pass.set_pipeline(&self.blur_pipeline); + let cfg = BlurConfig { + image_size: [image_size.width(), image_size.height()], + radius: 2, + _pad: 0, + }; + { + let mut writer = gpu + .queue + .write_buffer_with( + &buf.blur_config, + 0, + (std::mem::size_of::() as u64) + .try_into() + .unwrap(), + ) + .unwrap(); + writer.copy_from_slice(cfg.as_bytes()); } - gpu.queue.submit(Some(encoder.finish())); + let bg = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("blur bind group"), + layout: &self.blur_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buf.blur_config.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: buf.raw_occlusion.bind_active(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: buf.blurred_occlusion.bind_active(), + }, + ], + }); + compute_pass.set_bind_group(0, Some(&bg), &[]); + compute_pass.dispatch_workgroups( + image_size.width().div_ceil(8), + image_size.height().div_ceil(8), + 1, + ); + Ok(()) } } @@ -1278,7 +1333,7 @@ impl ColorContext { /// The output buffer is resized to fit `image` #[allow(clippy::too_many_arguments)] // what are ya gonna do? - fn submit( + fn encode( &self, image: &MergeWorkspace, world_to_model: &nalgebra::Matrix4, @@ -1287,6 +1342,7 @@ impl ColorContext { vars: &ShapeVars, out: &mut ShadeWorkspace, gpu: &Gpu, + encoder: &mut wgpu::CommandEncoder, ) -> Result<(), ColorError> { if image.image_count != shape.shape_count() { return Err(ColorError::BadShapeCount { @@ -1321,47 +1377,39 @@ impl ColorContext { let config_bg = bufs.config_bind_group(&gpu.device, &self.config_bind_group_layout); - // Create a command encoder and dispatch the compute work - let mut encoder = gpu.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { label: None }, + let mut compute_pass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, // TODO add timestamps? + }); + compute_pass.set_bind_group(0, config_bg, &[]); + + // TODO this creates a bind group for every evaluation, instead of + // caching it somewhere. However, *where* to cache it is not + // obvious, because it combines fields from two different buffer + // objects. + let image_bg = + gpu.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("color image bind group"), + layout: &self.image_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: image.out.bind_active(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: out.out.bind_active(), + }, + ], + }); + compute_pass.set_bind_group(1, &image_bg, &[]); + compute_pass.set_pipeline(self.color_pipeline.get(shape.reg_count())); + compute_pass.dispatch_workgroups( + size.width().div_ceil(8), + size.height().div_ceil(8), + 1, ); - { - let mut compute_pass = - encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: None, - timestamp_writes: None, // TODO add timestamps? - }); - compute_pass.set_bind_group(0, config_bg, &[]); - - // TODO this creates a bind group for every evaluation, instead of - // caching it somewhere. However, *where* to cache it is not - // obvious, because it combines fields from two different buffer - // objects. - let image_bg = - gpu.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("color image bind group"), - layout: &self.image_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: image.out.bind_active(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: out.out.bind_active(), - }, - ], - }); - compute_pass.set_bind_group(1, &image_bg, &[]); - compute_pass - .set_pipeline(self.color_pipeline.get(shape.reg_count())); - compute_pass.dispatch_workgroups( - size.width().div_ceil(8), - size.height().div_ceil(8), - 1, - ); - } - gpu.queue.submit(Some(encoder.finish())); Ok(()) } } diff --git a/fidget-wgpu/src/voxel/mod.rs b/fidget-wgpu/src/voxel/mod.rs index 11c4ca05..a15e7805 100644 --- a/fidget-wgpu/src/voxel/mod.rs +++ b/fidget-wgpu/src/voxel/mod.rs @@ -2132,11 +2132,10 @@ impl Context { workspace: &mut Workspace, settings: &RenderConfig, ) -> Result<(), SubmitError> { - // Create a command encoder and dispatch the compute work let mut encoder = self.gpu.device.create_command_encoder( &wgpu::CommandEncoderDescriptor { label: None }, ); - self.encode_with_vars(shape, vars, workspace, settings, &mut encoder)?; + self.encode(shape, vars, workspace, settings, &mut encoder)?; self.gpu.queue.submit(Some(encoder.finish())); Ok(()) } @@ -2144,7 +2143,7 @@ impl Context { /// Encodes a single image to be rendered on the GPU, with extra variables /// /// See [`submit`](Self::submit) for additional details. - pub fn encode_with_vars( + pub fn encode( &self, shape: &RenderShape, vars: &ShapeVars,