wde_wgpu/passes/
command_buffer.rs

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