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}