Skip to main content

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}