Skip to main content

pebble/graphics/pipeline/
instance.rs

1use std::marker::PhantomData;
2
3use crate::{
4    assets::{handle::Handle, storage::Assets, upload::{Asset, AssetSource}},
5    ecs::resources::Read,
6    graphics::{
7        pipeline::{
8            binding::{BindGroupTarget, BindingEntry},
9            buffers::{BindGroup, BindGroupBuilder, Buffer, BufferBuilder},
10            compute::Compute,
11            cubemap::Cubemap,
12            material::Material,
13            samplers::{GlobalSamplers, SamplerKind},
14            texture_array::TextureArray,
15            textures::Texture,
16        },
17        render::Backend,
18        types::flags::BufferUsages,
19    },
20};
21
22/// One bound value in a [`BindingInstance`] — matched to its bind group slot
23/// by name at upload time.
24#[derive(Clone, PartialEq, Eq, Hash)]
25pub enum BindingInstanceEntry {
26    Texture(Handle<Texture>),
27    TextureArray(Handle<TextureArray>),
28    Cubemap(Handle<Cubemap>),
29    Sampler(SamplerKind),
30    Uniform(Vec<u8>),
31    Storage(Vec<u8>),
32}
33
34/// A bind group asset for a [`Material`]/[`Compute`] target — named
35/// textures/samplers/uniforms/storage buffers, matched to the target's
36/// declared entries by name. Usually used via its aliases
37/// [`MaterialInstance`]/[`ComputeInstance`].
38pub struct BindingInstance<T> {
39    target: Handle<T>,
40    params: Vec<(&'static str, BindingInstanceEntry)>,
41    _marker: PhantomData<fn() -> T>,
42}
43
44impl<T> BindingInstance<T>
45where
46    T: Asset<Backend>,
47    T::Processed: BindGroupTarget,
48{
49    pub fn new(target: Handle<T>) -> Self {
50        Self { target, params: Vec::new(), _marker: PhantomData }
51    }
52
53    pub fn with_texture(mut self, name: &'static str, handle: Handle<Texture>) -> Self {
54        self.params.push((name, BindingInstanceEntry::Texture(handle)));
55        self
56    }
57
58    pub fn with_texture_array(mut self, name: &'static str, handle: Handle<TextureArray>) -> Self {
59        self.params.push((name, BindingInstanceEntry::TextureArray(handle)));
60        self
61    }
62
63    pub fn with_cubemap(mut self, name: &'static str, handle: Handle<Cubemap>) -> Self {
64        self.params.push((name, BindingInstanceEntry::Cubemap(handle)));
65        self
66    }
67
68    pub fn with_sampler(mut self, name: &'static str, kind: SamplerKind) -> Self {
69        self.params.push((name, BindingInstanceEntry::Sampler(kind)));
70        self
71    }
72
73    pub fn with_uniform(mut self, name: &'static str, data: Vec<u8>) -> Self {
74        self.params.push((name, BindingInstanceEntry::Uniform(data)));
75        self
76    }
77
78    pub fn with_storage(mut self, name: &'static str, data: Vec<u8>) -> Self {
79        self.params.push((name, BindingInstanceEntry::Storage(data)));
80        self
81    }
82
83    pub fn with_param(mut self, name: &'static str, entry: BindingInstanceEntry) -> Self {
84        self.params.push((name, entry));
85        self
86    }
87
88    fn validate(&self) {
89        if self.params.is_empty() {
90            tracing::warn!(
91                "BindingInstance::new(): no params — this instance won't bind anything \
92                 against its target; did you forget to chain .with_texture(...)/.with_sampler(...)/etc.?"
93            );
94        }
95    }
96
97    pub fn build_asset(self, name: &str, assets: &mut Assets<BindingInstance<T>>) -> Handle<BindingInstance<T>>
98    where
99        BindingInstance<T>: AssetSource,
100    {
101        self.validate();
102        assets.insert(name, self)
103    }
104}
105
106/// Looks up a target's bind group slot index by entry name.
107pub fn binding_index(entries: &[BindingEntry], name: &str) -> Option<u32> {
108    entries.iter().find(|e| e.name == name).map(|e| e.binding)
109}
110
111/// The GPU-resident bind group an uploaded [`BindingInstance`] produces.
112pub struct GPUBindingInstance<T> {
113    pub target: Handle<T>,
114    pub bind_group: BindGroup,
115    buffers: Vec<(&'static str, Buffer)>,
116    _marker: PhantomData<fn() -> T>,
117}
118
119impl<T> GPUBindingInstance<T> {
120    /// Overwrites a named uniform/storage buffer's contents in place —
121    /// avoids rebuilding the whole bind group for a per-frame update.
122    pub fn update(&self, name: &str, data: &[u8]) {
123        match self.buffer(name) {
124            Some(buf) => buf.write(data),
125            None => tracing::warn!(
126                "GPUBindingInstance::update: no bound buffer named '{name}' — check for a typo \
127                 against the entries in this instance's BindingInstance"
128            ),
129        }
130    }
131
132    pub fn buffer(&self, name: &str) -> Option<&Buffer> {
133        self.buffers.iter().find(|(n, _)| *n == name).map(|(_, buf)| buf)
134    }
135}
136
137impl<T> AssetSource for BindingInstance<T>
138where
139    T: Asset<Backend>,
140    T::Processed: BindGroupTarget,
141{
142    type Processed = GPUBindingInstance<T>;
143}
144
145impl<T> Asset<Backend> for BindingInstance<T>
146where
147    T: Asset<Backend>,
148    T::Processed: BindGroupTarget,
149{
150    type Deps<'a> = (
151        Read<'a, Assets<T>>,
152        Read<'a, Assets<Texture>>,
153        Read<'a, Assets<TextureArray>>,
154        Read<'a, Assets<Cubemap>>,
155        Read<'a, GlobalSamplers>,
156    );
157
158    fn upload<'a>(&self, backend: &Backend, deps: &Self::Deps<'a>) -> Option<GPUBindingInstance<T>> {
159        let (targets, textures, texture_arrays, cubemaps, samplers) = deps;
160        let target = targets.get(self.target)?;
161
162        let owned_buffers: Vec<(&'static str, Buffer)> = self
163            .params
164            .iter()
165            .filter_map(|(name, entry)| match entry {
166                BindingInstanceEntry::Uniform(bytes) => Some((
167                    *name,
168                    BufferBuilder::with_data(bytes)
169                        .with_usage(BufferUsages::UNIFORM | BufferUsages::COPY_DST | BufferUsages::COPY_SRC)
170                        .build(backend),
171                )),
172                BindingInstanceEntry::Storage(bytes) => Some((
173                    *name,
174                    BufferBuilder::with_data(bytes)
175                        .with_usage(BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC)
176                        .build(backend),
177                )),
178                _ => None,
179            })
180            .collect();
181
182        let mut builder = BindGroupBuilder::new(target.bind_group_layout());
183        for (name, entry) in &self.params {
184            let binding = binding_index(target.binding_entries(), name)?;
185            builder = match entry {
186                BindingInstanceEntry::Texture(handle) => builder.with_texture_2d_at(binding, textures.get(*handle)?),
187                BindingInstanceEntry::TextureArray(handle) => {
188                    builder.with_texture_array_at(binding, texture_arrays.get(*handle)?)
189                }
190                BindingInstanceEntry::Cubemap(handle) => builder.with_texture_cubemap_at(binding, cubemaps.get(*handle)?),
191                BindingInstanceEntry::Sampler(kind) => builder.with_sampler_at(binding, samplers.get(*kind)),
192                BindingInstanceEntry::Uniform(_) | BindingInstanceEntry::Storage(_) => {
193                    let buf = &owned_buffers.iter().find(|(n, _)| n == name)?.1;
194                    builder.with_buffer_at(binding, buf)
195                }
196            };
197        }
198        let bind_group = builder.build(backend);
199
200        Some(GPUBindingInstance { target: self.target, bind_group, buffers: owned_buffers, _marker: PhantomData })
201    }
202}
203
204pub type GPUMaterialInstance = GPUBindingInstance<Material>;
205/// A [`Material`]'s bind group — the values a shader actually reads from
206/// (textures, samplers, uniforms) for one draw.
207pub type MaterialInstance = BindingInstance<Material>;
208
209pub type GPUComputeInstance = GPUBindingInstance<Compute>;
210/// A [`Compute`] pipeline's bind group — the buffers/textures it reads and
211/// writes for one dispatch.
212pub type ComputeInstance = BindingInstance<Compute>;