Skip to main content

pebble/graphics/pipeline/
params.rs

1use crate::{
2    assets::{handle::Handle, storage::Assets},
3    graphics::{
4        pipeline::{
5            binding::{BindGroupLayout, BindingEntry},
6            buffers::{BindGroup, BindGroupBuilder, Buffer, BufferBuilder, DynamicBuffer},
7            cubemap::Cubemap,
8            samplers::{GlobalSamplers, SamplerKind},
9            texture_array::TextureArray,
10            texture_view::TextureView,
11            textures::Texture,
12        },
13        render::Backend,
14        types::flags::BufferUsages,
15    },
16};
17
18/// One bound value in a [`BindGroupParams`] — matched to its bind group slot
19/// by name at upload time.
20#[derive(Clone)]
21pub enum BindingValue {
22    Texture(Handle<Texture>),
23    TextureArray(Handle<TextureArray>),
24    Cubemap(Handle<Cubemap>),
25    TextureView(TextureView),
26    Sampler(SamplerKind),
27    Uniform(Vec<u8>),
28    Storage(Vec<u8>),
29    Buffer(Buffer),
30    DynamicBuffer(DynamicBuffer),
31}
32
33/// A [`Material`](super::material::Material)/[`Compute`](super::compute::Compute)'s
34/// named bind group values — textures/samplers/uniforms/storage buffers,
35/// matched to the pipeline's own declared entries by name at upload time.
36/// `Material`/`Compute` each hold one of these and expose their `with_*`
37/// methods as thin delegates, so this is rarely named directly.
38#[derive(Clone, Default)]
39pub struct BindGroupParams {
40    params: Vec<(&'static str, BindingValue)>,
41}
42
43impl BindGroupParams {
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    pub fn with_texture(mut self, name: &'static str, handle: Handle<Texture>) -> Self {
49        self.params.push((name, BindingValue::Texture(handle)));
50        self
51    }
52
53    pub fn with_texture_array(mut self, name: &'static str, handle: Handle<TextureArray>) -> Self {
54        self.params.push((name, BindingValue::TextureArray(handle)));
55        self
56    }
57
58    pub fn with_cubemap(mut self, name: &'static str, handle: Handle<Cubemap>) -> Self {
59        self.params.push((name, BindingValue::Cubemap(handle)));
60        self
61    }
62
63    /// Binds an already-built [`TextureView`] directly — e.g. one mip level
64    /// from [`GPUTexture::get_view`](crate::graphics::pipeline::textures::GPUTexture::get_view),
65    /// or a standalone render target from
66    /// [`Texture::empty`](crate::graphics::pipeline::textures::Texture::empty).
67    /// Unlike `.with_texture`/`.with_texture_array`/`.with_cubemap`, no
68    /// `Handle` lookup happens at upload time — `view` must already exist.
69    pub fn with_texture_view(mut self, name: &'static str, view: TextureView) -> Self {
70        self.params.push((name, BindingValue::TextureView(view)));
71        self
72    }
73
74    pub fn with_sampler(mut self, name: &'static str, kind: SamplerKind) -> Self {
75        self.params.push((name, BindingValue::Sampler(kind)));
76        self
77    }
78
79    pub fn with_uniform(mut self, name: &'static str, data: Vec<u8>) -> Self {
80        self.params.push((name, BindingValue::Uniform(data)));
81        self
82    }
83
84    pub fn with_storage(mut self, name: &'static str, data: Vec<u8>) -> Self {
85        self.params.push((name, BindingValue::Storage(data)));
86        self
87    }
88
89    /// Same as [`with_uniform`](Self::with_uniform), but takes a typed
90    /// value instead of pre-packed bytes — uses `encase` to lay it out with
91    /// correct WGSL `uniform` (std140) alignment. `T` is usually a struct
92    /// deriving `encase::ShaderType`.
93    pub fn with_uniform_value<T>(self, name: &'static str, value: &T) -> Self
94    where
95        T: encase::ShaderType + encase::internal::WriteInto,
96    {
97        let mut buffer = encase::UniformBuffer::new(Vec::new());
98        buffer
99            .write(value)
100            .expect("encase: failed to write uniform value — this shouldn't happen for a #[derive(ShaderType)] struct");
101        self.with_uniform(name, buffer.into_inner())
102    }
103
104    /// Same as [`with_storage`](Self::with_storage), but takes a typed
105    /// value instead of pre-packed bytes — uses `encase` to lay it out with
106    /// correct WGSL `storage` (std430) alignment.
107    pub fn with_storage_value<T>(self, name: &'static str, value: &T) -> Self
108    where
109        T: encase::ShaderType + encase::internal::WriteInto,
110    {
111        let mut buffer = encase::StorageBuffer::new(Vec::new());
112        buffer
113            .write(value)
114            .expect("encase: failed to write storage value — this shouldn't happen for a #[derive(ShaderType)] struct");
115        self.with_storage(name, buffer.into_inner())
116    }
117
118    /// Binds an existing [`Buffer`] instead of uploading raw bytes — for a
119    /// buffer you already built yourself (e.g. one a compute pass writes
120    /// to, then another pass reads from). Unlike `.with_uniform`/`.with_storage`,
121    /// no buffer is created here; `buffer` must already carry the usage
122    /// flags this binding needs (`BufferUsages::UNIFORM` or `::STORAGE`,
123    /// matching how the target's own entry for `name` was declared).
124    pub fn with_buffer(mut self, name: &'static str, buffer: Buffer) -> Self {
125        self.params.push((name, BindingValue::Buffer(buffer)));
126        self
127    }
128
129    /// Binds an existing [`DynamicBuffer`] — the dynamic-offset counterpart
130    /// to `.with_buffer`. The target's own entry for `name` must have been
131    /// declared with `BindingKind::dynamic_uniform_buffer`/`dynamic_storage_buffer`
132    /// (`has_dynamic_offset: true`) to match, or bind group creation fails
133    /// validation.
134    pub fn with_dynamic_buffer(mut self, name: &'static str, buffer: DynamicBuffer) -> Self {
135        self.params.push((name, BindingValue::DynamicBuffer(buffer)));
136        self
137    }
138
139    pub fn with_param(mut self, name: &'static str, entry: BindingValue) -> Self {
140        self.params.push((name, entry));
141        self
142    }
143
144    pub(crate) fn is_empty(&self) -> bool {
145        self.params.is_empty()
146    }
147}
148
149/// Looks up a target's bind group slot index by entry name.
150pub(crate) fn binding_index(entries: &[BindingEntry], name: &str) -> Option<u32> {
151    entries.iter().find(|e| e.name == name).map(|e| e.binding)
152}
153
154/// The GPU-resident buffers a [`BindGroupParams`] resolves into, alongside
155/// the [`BindGroup`] itself — returned by [`build_bind_group`] for a
156/// [`Material`](super::material::Material)/[`Compute`](super::compute::Compute)
157/// to fold into its own processed form.
158pub(crate) struct BuiltBindGroup {
159    pub bind_group: BindGroup,
160    pub buffers: Vec<(&'static str, Buffer)>,
161    pub dynamic_buffers: Vec<(&'static str, DynamicBuffer)>,
162}
163
164/// Resolves `params` against `layout`/`entries` (a pipeline's own bind group
165/// shape) into a real `BindGroup` — the shared body of `Material::upload`/
166/// `Compute::upload`, since both need the exact same "named values → bind
167/// group" resolution once their pipeline is in hand.
168pub(crate) fn build_bind_group(
169    backend: &Backend,
170    params: &BindGroupParams,
171    layout: &BindGroupLayout,
172    entries: &[BindingEntry],
173    textures: &Assets<Texture>,
174    texture_arrays: &Assets<TextureArray>,
175    cubemaps: &Assets<Cubemap>,
176    samplers: &GlobalSamplers,
177) -> Option<BuiltBindGroup> {
178    // buffers backing `Uniform`/`Storage` entries are built fresh here; a
179    // `Buffer` entry already exists — just cloned (cheap: it's a handle to
180    // the same GPU buffer) so the result can still look it up by name later
181    // via `.update()`/`.buffer()`.
182    let owned_buffers: Vec<(&'static str, Buffer)> = params
183        .params
184        .iter()
185        .filter_map(|(name, entry)| match entry {
186            BindingValue::Uniform(bytes) => Some((
187                *name,
188                BufferBuilder::with_data(bytes)
189                    .with_usage(BufferUsages::UNIFORM | BufferUsages::COPY_DST | BufferUsages::COPY_SRC)
190                    .build(backend),
191            )),
192            BindingValue::Storage(bytes) => Some((
193                *name,
194                BufferBuilder::with_data(bytes)
195                    .with_usage(BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC)
196                    .build(backend),
197            )),
198            BindingValue::Buffer(buffer) => Some((*name, buffer.clone())),
199            _ => None,
200        })
201        .collect();
202
203    // same idea as `owned_buffers`, for `.with_dynamic_buffer` entries —
204    // kept separate since binding one uses `with_dynamic_buffer_at`, not
205    // `with_buffer_at`.
206    let owned_dynamic_buffers: Vec<(&'static str, DynamicBuffer)> = params
207        .params
208        .iter()
209        .filter_map(|(name, entry)| match entry {
210            BindingValue::DynamicBuffer(buffer) => Some((*name, buffer.clone())),
211            _ => None,
212        })
213        .collect();
214
215    let mut builder = BindGroupBuilder::new(layout);
216    for (name, entry) in &params.params {
217        let binding = binding_index(entries, name)?;
218        builder = match entry {
219            BindingValue::Texture(handle) => builder.with_texture_2d_at(binding, textures.get(*handle)?),
220            BindingValue::TextureArray(handle) => builder.with_texture_array_at(binding, texture_arrays.get(*handle)?),
221            BindingValue::Cubemap(handle) => builder.with_texture_cubemap_at(binding, cubemaps.get(*handle)?),
222            BindingValue::TextureView(view) => builder.with_texture_view_at(binding, view),
223            BindingValue::Sampler(kind) => builder.with_sampler_at(binding, samplers.get(*kind)),
224            BindingValue::Uniform(_) | BindingValue::Storage(_) | BindingValue::Buffer(_) => {
225                let buf = &owned_buffers.iter().find(|(n, _)| n == name)?.1;
226                builder.with_buffer_at(binding, buf)
227            }
228            BindingValue::DynamicBuffer(_) => {
229                let buf = &owned_dynamic_buffers.iter().find(|(n, _)| n == name)?.1;
230                builder.with_dynamic_buffer_at(binding, buf)
231            }
232        };
233    }
234
235    Some(BuiltBindGroup { bind_group: builder.build(backend), buffers: owned_buffers, dynamic_buffers: owned_dynamic_buffers })
236}