wde_wgpu/passes/command_buffer.rs
1//! Command encoder utilities to record render and compute work.
2use wgpu::Texture;
3
4use crate::{
5 buffer::Buffer, compute_pass::WComputePass, instance::RenderInstanceData, texture::TextureView
6};
7
8use super::render_pass::RenderPassInstance;
9
10/// Type of a color.
11pub type WgpuColor = wgpu::Color;
12
13/// Type of a load operation.
14pub type LoadOp<V> = wgpu::LoadOp<V>;
15
16/// Type of a store operation.
17pub type StoreOp = wgpu::StoreOp;
18
19/// Load/store operations for attachments.
20#[derive(Clone, Copy, Debug)]
21pub struct Operations<V> {
22 pub load: LoadOp<V>,
23 pub store: StoreOp
24}
25
26/// Describe the optional depth attachment of a render pass.
27pub struct RenderPassDepth<'pass> {
28 /// The depth texture. If `None`, the render pass will not have a depth texture.
29 pub texture: Option<&'pass TextureView>,
30 /// The depth operation when loading the texture. By default, load the existing texture.
31 pub load: LoadOp<f32>,
32 /// The depth operation when storing the texture. By default, store the texture.
33 pub store: StoreOp,
34 /// The stencil operation when loading the stencil buffer. By default, load the existing stencil content.
35 pub stencil_load: LoadOp<u32>,
36 /// The stencil operation when storing the stencil buffer. By default, store the stencil content.
37 pub stencil_store: StoreOp
38}
39impl Default for RenderPassDepth<'_> {
40 fn default() -> Self {
41 Self {
42 texture: None,
43 // load: wgpu::LoadOp::Clear(1.0),
44 load: wgpu::LoadOp::Load,
45 store: wgpu::StoreOp::Store,
46 stencil_load: wgpu::LoadOp::Load,
47 stencil_store: wgpu::StoreOp::Store
48 }
49 }
50}
51
52/// Describe a single color attachment of a render pass.
53pub struct RenderPassColorAttachment<'pass> {
54 /// The color texture.
55 pub texture: Option<&'pass TextureView>,
56 /// The color load operation. By default, load the existing texture.
57 pub load: LoadOp<WgpuColor>,
58 /// The color store operation. By default, store the texture.
59 pub store: StoreOp,
60 /// An optional resolve target for multi-sampled textures.
61 /// This will resolve the multi-sampled texture into the single-sampled target at the end of the render pass, when using multi-sampling.
62 pub resolve_target: Option<&'pass TextureView>
63}
64impl Default for RenderPassColorAttachment<'_> {
65 fn default() -> Self {
66 Self {
67 texture: None,
68 // load: wgpu::LoadOp::Clear(Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }),
69 load: wgpu::LoadOp::Load,
70 store: wgpu::StoreOp::Store,
71 resolve_target: None
72 }
73 }
74}
75
76/// Builder for a render pass.
77///
78/// Prefer constructing passes through [`CommandBuffer::create_render_pass`]; supply a closure
79/// that mutates this builder to add one or more color attachments and an optional depth
80/// attachment.
81#[derive(Default)]
82pub struct RenderPassBuilder<'pass> {
83 /// The depth texture of the render pass.
84 depth: RenderPassDepth<'pass>,
85 /// The color attachments of the render pass. By default, no color attachments.
86 color_attachments: Vec<RenderPassColorAttachment<'pass>>
87}
88impl<'pass> RenderPassBuilder<'pass> {
89 /// Set the depth texture of the render pass.
90 ///
91 /// # Arguments
92 ///
93 /// * `texture` - The depth texture.
94 pub fn set_depth_texture(&mut self, texture: RenderPassDepth<'pass>) {
95 self.depth = texture;
96 }
97
98 /// Add a color attachment to the render pass.
99 ///
100 /// # Arguments
101 ///
102 /// * `attachment` - The color attachment.
103 pub fn add_color_attachment(&mut self, attachment: RenderPassColorAttachment<'pass>) {
104 self.color_attachments.push(attachment);
105 }
106}
107
108/// Record commands for the GPU, spawn render/compute passes, and submit once finished.
109/// Guard rails ensure pipelines/buffers are set before draws/dispatches.
110///
111/// # Examples
112/// Clear a surface:
113/// ```rust,no_run
114/// use wde_wgpu::{
115/// command_buffer::{Color, CommandBuffer, LoadOp, RenderPassColorAttachment, StoreOp},
116/// instance::RenderInstanceData,
117/// texture::TextureView,
118/// };
119///
120/// let mut cmd = CommandBuffer::new(instance, "frame");
121/// {
122/// cmd.create_render_pass("clear", |pass| {
123/// pass.add_color_attachment(RenderPassColorAttachment {
124/// texture: Some(view),
125/// load: LoadOp::Clear(Color::BLACK),
126/// store: StoreOp::Store,
127/// });
128/// });
129/// }
130/// cmd.submit(instance);
131/// ```
132///
133/// Dispatch compute work:
134/// ```rust,no_run
135/// use wde_wgpu::{command_buffer::CommandBuffer, compute_pass::WComputePass, compute_pipeline::ComputePipeline};
136///
137/// let mut cmd = CommandBuffer::new(instance, "compute");
138/// {
139/// let mut pass: WComputePass = cmd.create_compute_pass("cull");
140/// pass.set_pipeline(pipeline).unwrap().set_bind_group(0, bind).dispatch(4, 1, 1).unwrap();
141/// }
142/// cmd.submit(instance);
143/// ```
144pub struct CommandBuffer {
145 pub label: String,
146 encoder: wgpu::CommandEncoder
147}
148
149impl std::fmt::Debug for CommandBuffer {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 f.debug_struct("CommandBuffer")
152 .field("label", &self.label)
153 .finish()
154 }
155}
156
157impl CommandBuffer {
158 /// Create a new command buffer.
159 ///
160 /// # Arguments
161 ///
162 /// * `instance` - The render instance.
163 /// * `label` - The label of the command buffer.
164 pub fn new(instance: &RenderInstanceData<'_>, label: &str) -> Self {
165 // Create command encoder
166 let command_encoder =
167 instance
168 .device
169 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
170 label: Some(format!("{}-command-encoder", label).as_str())
171 });
172
173 Self {
174 label: label.to_string(),
175 encoder: command_encoder
176 }
177 }
178
179 /// Create a new render pass.
180 ///
181 /// # Arguments
182 ///
183 /// * `label` - The label of the render pass.
184 /// * `builder_func` - The function to build the render pass. This function should return `Ok(())` if the render pass was built successfully, and `Err(String)` otherwise. If the function returns `Err`, the render pass will not be created.
185 pub fn create_render_pass<'pass>(
186 &'pass mut self,
187 label: &str,
188 builder_func: impl FnOnce(&mut RenderPassBuilder<'pass>) -> Result<(), String>
189 ) -> Result<RenderPassInstance<'pass>, String> {
190 // Run the builder function
191 let mut builder = RenderPassBuilder::default();
192 builder_func(&mut builder)?;
193
194 let mut depth_attachment = None;
195 if let Some(depth_texture) = builder.depth.texture {
196 depth_attachment = Some(wgpu::RenderPassDepthStencilAttachment {
197 view: depth_texture,
198 depth_ops: Some(wgpu::Operations {
199 load: builder.depth.load,
200 store: builder.depth.store
201 }),
202 stencil_ops: Some(wgpu::Operations {
203 load: builder.depth.stencil_load,
204 store: builder.depth.stencil_store
205 })
206 });
207 }
208
209 let color_attachments = builder
210 .color_attachments
211 .iter()
212 .map(|attachment| {
213 attachment
214 .texture
215 .map(|texture| wgpu::RenderPassColorAttachment {
216 view: texture,
217 resolve_target: attachment.resolve_target,
218 ops: wgpu::Operations {
219 load: attachment.load,
220 store: attachment.store
221 },
222 depth_slice: None
223 })
224 })
225 .collect::<Vec<_>>();
226
227 let render_pass = self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
228 label: Some(format!("{}-render-pass", label).as_str()),
229 color_attachments: &color_attachments,
230 depth_stencil_attachment: depth_attachment,
231 timestamp_writes: None,
232 occlusion_query_set: None
233 });
234
235 Ok(RenderPassInstance::new(label, render_pass))
236 }
237
238 /// Create a new compute pass.
239 ///
240 /// # Arguments
241 ///
242 /// * `label` - The label of the compute pass.
243 pub fn create_compute_pass<'pass>(&'pass mut self, label: &str) -> WComputePass<'pass> {
244 let compute_pass = self
245 .encoder
246 .begin_compute_pass(&wgpu::ComputePassDescriptor {
247 label: Some(format!("{}-compute-pass", label).as_str()),
248 timestamp_writes: None
249 });
250
251 WComputePass::new(label, compute_pass)
252 }
253
254 /// Finish and submit a command buffer.
255 ///
256 /// # Arguments
257 ///
258 /// * `instance` - The render instance.
259 pub fn submit(self, instance: &RenderInstanceData) {
260 instance
261 .queue
262 .submit(std::iter::once(self.encoder.finish()));
263 }
264
265 /// Copy a buffer to another buffer.
266 /// Please use the `copy_from_buffer` method of the buffer to copy data.
267 ///
268 /// # Arguments
269 ///
270 /// * `source` - The source buffer.
271 /// * `destination` - The destination buffer.
272 pub fn copy_buffer_to_buffer(&mut self, source: &Buffer, destination: &Buffer) {
273 self.encoder.copy_buffer_to_buffer(
274 &source.buffer,
275 0,
276 &destination.buffer,
277 0,
278 source.buffer.size()
279 );
280 }
281
282 /// Copy a buffer to another buffer with offsets.
283 /// Please use the `copy_from_buffer` method of the buffer to copy data.
284 ///
285 /// # Arguments
286 ///
287 /// * `source` - The source buffer.
288 /// * `destination` - The destination buffer.
289 /// * `source_offset` - The offset in the source buffer.
290 /// * `destination_offset` - The offset in the destination buffer.
291 /// * `size` - The size to copy.
292 pub fn copy_buffer_to_buffer_offset(
293 &mut self,
294 source: &Buffer,
295 destination: &Buffer,
296 source_offset: u64,
297 destination_offset: u64,
298 size: u64
299 ) {
300 self.encoder.copy_buffer_to_buffer(
301 &source.buffer,
302 source_offset,
303 &destination.buffer,
304 destination_offset,
305 size
306 );
307 }
308
309 /// Copy a texture to a buffer.
310 /// Please use the `copy_from_texture` method of the buffer to copy data.
311 ///
312 /// # Arguments
313 ///
314 /// * `source` - The source texture.
315 /// * `destination` - The destination buffer.
316 /// * `size` - The size of the texture.
317 pub fn copy_texture_to_buffer(
318 &mut self,
319 source: &Texture,
320 destination: &Buffer,
321 size: wgpu::Extent3d
322 ) {
323 // Create texture copy
324 let texture_copy = source.as_image_copy();
325
326 // Calculate bytes per row (must be a multiple of 256 for wgpu)
327 let bytes_per_pixel = match source.format() {
328 wgpu::TextureFormat::R8Unorm => 1,
329 wgpu::TextureFormat::Rgba8Unorm => 4,
330 _ => panic!("Unsupported texture format for copy")
331 };
332 let bytes_per_row = (size.width * bytes_per_pixel).div_ceil(256) * 256; // Align to 256 bytes
333
334 // Create buffer copy
335 let buffer_copy = wgpu::TexelCopyBufferInfo {
336 buffer: &destination.buffer,
337 layout: wgpu::TexelCopyBufferLayout {
338 offset: 0,
339 bytes_per_row: Some(bytes_per_row),
340 rows_per_image: None
341 }
342 };
343
344 // Copy texture to buffer
345 self.encoder
346 .copy_texture_to_buffer(texture_copy, buffer_copy, size);
347 }
348
349 /// Copy a single layer from a texture array to a buffer.
350 pub fn copy_texture_layer_to_buffer(
351 &mut self,
352 source: &wgpu::Texture,
353 destination: &Buffer,
354 layer: u32
355 ) {
356 let size = source.size();
357 let bytes_per_pixel = match source.format() {
358 wgpu::TextureFormat::R8Unorm => 1,
359 wgpu::TextureFormat::Rgba8Unorm => 4,
360 _ => panic!("Unsupported texture format for layered copy")
361 };
362 let bytes_per_row = (size.width * bytes_per_pixel).div_ceil(256) * 256;
363 self.encoder.copy_texture_to_buffer(
364 wgpu::TexelCopyTextureInfo {
365 texture: source,
366 mip_level: 0,
367 origin: wgpu::Origin3d {
368 x: 0,
369 y: 0,
370 z: layer
371 },
372 aspect: wgpu::TextureAspect::All
373 },
374 wgpu::TexelCopyBufferInfo {
375 buffer: &destination.buffer,
376 layout: wgpu::TexelCopyBufferLayout {
377 offset: 0,
378 bytes_per_row: Some(bytes_per_row),
379 rows_per_image: None
380 }
381 },
382 wgpu::Extent3d {
383 width: size.width,
384 height: size.height,
385 depth_or_array_layers: 1
386 }
387 );
388 }
389
390 /// Get the encoder of the command buffer.
391 ///
392 /// # Returns
393 ///
394 /// The encoder of the command buffer.
395 pub fn encoder(&mut self) -> &mut wgpu::CommandEncoder {
396 &mut self.encoder
397 }
398}