Skip to main content

pebble/wgpu/
render_bundle.rs

1use crate::wgpu::{
2    backend::WGPUBackend, buffer::Buffer, buffers::BindGroup, material::RenderPipeline,
3    render_pass::IndexFormat, texture_format::TextureFormat,
4};
5
6/// Builds a [`RenderBundleEncoder`] — the color/depth-stencil formats and
7/// sample count it (and every render pass it's later executed in via
8/// [`RenderPass::execute_bundles`](super::render_pass::RenderPass::execute_bundles))
9/// must match exactly. Fields are private — chain the setters below, then
10/// [`build`](Self::build).
11///
12/// ```ignore
13/// let mut encoder = RenderBundleEncoderBuilder::new()
14///     .label("quad-bundle-encoder")
15///     .color_formats(vec![Some(backend.surface_format())])
16///     .sample_count(backend.sample_count())
17///     .build(&backend);
18/// ```
19pub struct RenderBundleEncoderBuilder<'a> {
20    label: Option<&'a str>,
21    /// One entry per color attachment the bundle will be executed against,
22    /// in the same order — `None` for an attachment slot the bundle doesn't
23    /// touch.
24    color_formats: Vec<Option<TextureFormat>>,
25    /// `None` if the render pass(es) this bundle runs in have no depth
26    /// attachment.
27    depth_stencil_format: Option<TextureFormat>,
28    /// Whether this bundle only reads the depth aspect (never writes it).
29    depth_read_only: bool,
30    /// Whether this bundle only reads the stencil aspect (never writes it).
31    stencil_read_only: bool,
32    /// Must match the sample count of every attachment the bundle is
33    /// executed against — see [`TextureBuilder::sample_count`](super::texture_view::TextureBuilder::sample_count).
34    sample_count: u32,
35}
36
37impl<'a> Default for RenderBundleEncoderBuilder<'a> {
38    fn default() -> Self {
39        Self {
40            label: None,
41            color_formats: Vec::new(),
42            depth_stencil_format: None,
43            depth_read_only: false,
44            stencil_read_only: false,
45            sample_count: 1,
46        }
47    }
48}
49
50impl<'a> RenderBundleEncoderBuilder<'a> {
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
56        self.label = label.into();
57        self
58    }
59
60    pub fn color_formats(mut self, formats: Vec<Option<TextureFormat>>) -> Self {
61        self.color_formats = formats;
62        self
63    }
64
65    pub fn depth_stencil_format(mut self, format: TextureFormat) -> Self {
66        self.depth_stencil_format = Some(format);
67        self
68    }
69
70    pub fn depth_read_only(mut self, read_only: bool) -> Self {
71        self.depth_read_only = read_only;
72        self
73    }
74
75    pub fn stencil_read_only(mut self, read_only: bool) -> Self {
76        self.stencil_read_only = read_only;
77        self
78    }
79
80    /// Must match the sample count of every attachment the bundle is
81    /// executed against. `1` (no multisampling) by default.
82    pub fn sample_count(mut self, count: u32) -> Self {
83        self.sample_count = count;
84        self
85    }
86
87    /// Logs a WARN for a bundle with neither a color nor a depth/stencil
88    /// attachment configured — it wouldn't be executable against any real
89    /// render pass, almost certainly a forgotten `.color_formats(...)`.
90    fn validate(&self) {
91        if self.color_formats.is_empty() && self.depth_stencil_format.is_none() {
92            tracing::warn!(
93                "RenderBundleEncoderBuilder: no color_formats and no depth_stencil_format — \
94                 this bundle has no attachments to execute against; did you forget to call \
95                 .color_formats(...)?"
96            );
97        }
98    }
99
100    /// Consume the builder and start recording a [`RenderBundleEncoder`].
101    pub fn build(self, backend: &'a WGPUBackend) -> RenderBundleEncoder<'a> {
102        self.validate();
103
104        let color_formats: Vec<Option<wgpu::TextureFormat>> =
105            self.color_formats.iter().map(|f| f.map(Into::into)).collect();
106        let depth_stencil = self.depth_stencil_format.map(|format| wgpu::RenderBundleDepthStencil {
107            format: format.into(),
108            depth_read_only: self.depth_read_only,
109            stencil_read_only: self.stencil_read_only,
110        });
111        let raw = backend.device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
112            label: self.label,
113            color_formats: &color_formats,
114            depth_stencil,
115            sample_count: self.sample_count,
116            multiview: None,
117        });
118        RenderBundleEncoder::new(raw)
119    }
120}
121
122/// Records a reusable sequence of draw calls — build via
123/// [`RenderBundleEncoderBuilder`], record with the same
124/// `set_pipeline`/`set_bind_group`/`set_vertex_buffer`/
125/// `set_index_buffer`/`draw`/`draw_indexed` shape as
126/// [`RenderPass`](super::render_pass::RenderPass), then
127/// [`finish`](Self::finish) into a [`RenderBundle`]. Re-executing a bundle
128/// via [`RenderPass::execute_bundles`](super::render_pass::RenderPass::execute_bundles)
129/// is often cheaper than re-recording the same draws by hand every frame —
130/// worth it once you have many draw calls that don't change pipeline/bind
131/// group/buffers from one frame to the next (static scene geometry, say).
132pub struct RenderBundleEncoder<'a> {
133    raw: wgpu::RenderBundleEncoder<'a>,
134}
135
136impl<'a> RenderBundleEncoder<'a> {
137    pub(crate) fn new(raw: wgpu::RenderBundleEncoder<'a>) -> Self {
138        Self { raw }
139    }
140
141    pub fn set_pipeline(&mut self, pipeline: &'a RenderPipeline) {
142        self.raw.set_pipeline(pipeline.raw());
143    }
144
145    /// `offsets` is the dynamic-offset slice for any dynamic-offset entries
146    /// in this bind group's layout — see
147    /// [`RenderPass::set_bind_group`](super::render_pass::RenderPass::set_bind_group).
148    pub fn set_bind_group(&mut self, index: u32, bind_group: &'a BindGroup, offsets: &[u32]) {
149        self.raw.set_bind_group(index, Some(bind_group.raw()), offsets);
150    }
151
152    /// Binds `buffer` in its entirety at vertex slot `slot`.
153    pub fn set_vertex_buffer(&mut self, slot: u32, buffer: &'a Buffer) {
154        self.raw.set_vertex_buffer(slot, buffer.raw().slice(..));
155    }
156
157    /// Binds `buffer` in its entirety as the index buffer.
158    pub fn set_index_buffer(&mut self, buffer: &'a Buffer, format: IndexFormat) {
159        self.raw.set_index_buffer(buffer.raw().slice(..), format.into());
160    }
161
162    pub fn draw(&mut self, vertices: std::ops::Range<u32>, instances: std::ops::Range<u32>) {
163        self.raw.draw(vertices, instances);
164    }
165
166    pub fn draw_indexed(
167        &mut self,
168        indices: std::ops::Range<u32>,
169        base_vertex: i32,
170        instances: std::ops::Range<u32>,
171    ) {
172        self.raw.draw_indexed(indices, base_vertex, instances);
173    }
174
175    /// Stops recording and returns the replayable [`RenderBundle`].
176    pub fn finish(self, label: Option<&str>) -> RenderBundle {
177        RenderBundle(self.raw.finish(&wgpu::RenderBundleDescriptor { label }))
178    }
179}
180
181/// A pre-recorded, replayable sequence of draw calls — built via
182/// [`RenderBundleEncoder::finish`], replayed via
183/// [`RenderPass::execute_bundles`](super::render_pass::RenderPass::execute_bundles).
184/// There's no way to reach the underlying `wgpu::RenderBundle` from outside
185/// this crate.
186pub struct RenderBundle(wgpu::RenderBundle);
187
188impl RenderBundle {
189    pub(crate) fn raw(&self) -> &wgpu::RenderBundle {
190        &self.0
191    }
192}