Skip to main content

pebble/wgpu/
material_instance.rs

1use crate::{
2    app::App,
3    assets::{
4        plugin::AssetPlugin,
5        storage::{ProcessedAssets, RawAssetHandle},
6        upload::Asset,
7    },
8    ecs::{plugin::Plugin, system::Res},
9    wgpu::{
10        backend::WGPUBackend,
11        material::{GPUMaterial, MaterialBindingEntry},
12        samplers::{GlobalSamplers, SamplerKind},
13    },
14};
15
16#[derive(Clone, PartialEq, Eq, Hash)]
17pub enum MaterialInstanceBindingEntry {
18    Texture(RawAssetHandle),
19    TextureArray(RawAssetHandle),
20    Cubemap(RawAssetHandle),
21    Sampler(SamplerKind),
22    Uniform(Vec<u8>),
23    Storage(Vec<u8>),
24}
25
26pub struct MaterialInstanceDescriptor {
27    pub material: RawAssetHandle,
28    pub params: Vec<(&'static str, MaterialInstanceBindingEntry)>,
29}
30
31pub fn binding_index(entries: &[MaterialBindingEntry], name: &str) -> Option<u32> {
32    entries
33        .iter()
34        .position(|e| e.name == name)
35        .map(|i| i as u32)
36}
37
38pub fn build_instance_build_group(
39    device: &wgpu::Device,
40    layout: &wgpu::BindGroupLayout,
41    material_entries: &[MaterialBindingEntry],
42    resolved: &[(&'static str, wgpu::BindingResource)],
43) -> Option<wgpu::BindGroup> {
44    let mut entries = Vec::with_capacity(resolved.len());
45    for (name, resource) in resolved {
46        let binding = binding_index(material_entries, *name)?;
47        entries.push(wgpu::BindGroupEntry {
48            binding,
49            resource: resource.clone(),
50        })
51    }
52
53    Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
54        label: None,
55        layout,
56        entries: &entries,
57    }))
58}
59
60pub struct GPUMaterialInstance {
61    pub material: RawAssetHandle,
62    pub bind_group: wgpu::BindGroup,
63    /// Uniform/storage buffers created for this instance's bindings — kept
64    /// alive here since `bind_group` only holds GPU-side references to them.
65    _owned_buffers: Vec<wgpu::Buffer>,
66}
67
68impl Asset<WGPUBackend> for GPUMaterialInstance {
69    type Source = MaterialInstanceDescriptor;
70    type Deps<'a> = (
71        Res<'a, ProcessedAssets<GPUMaterial>>,
72        Res<'a, ProcessedAssets<super::textures::GPUTexture>>,
73        Res<'a, ProcessedAssets<super::texture_array::GPUTextureArray>>,
74        Res<'a, ProcessedAssets<super::cubemap::GPUCubemap>>,
75        Res<'a, GlobalSamplers>,
76    );
77
78    fn upload<'a>(
79        source: &MaterialInstanceDescriptor,
80        backend: &WGPUBackend,
81        deps: &Self::Deps<'a>,
82    ) -> Option<Self> {
83        let (materials, textures, texture_arrays, cubemaps, samplers) = deps;
84        let material = materials.get(source.material)?;
85
86        // Two passes: first resolve every binding, deferring uniform/storage
87        // buffers to an index into `owned_buffers` rather than taking a
88        // reference immediately — a `BindingResource` borrowed from a Vec
89        // slot can't coexist with later pushes into that same Vec.
90        enum Pending<'a> {
91            Direct(wgpu::BindingResource<'a>),
92            OwnedBuffer(usize),
93        }
94
95        let mut owned_buffers: Vec<wgpu::Buffer> = Vec::new();
96        let mut pending: Vec<(&'static str, Pending)> = Vec::new();
97
98        for (name, entry) in &source.params {
99            let resource = match entry {
100                MaterialInstanceBindingEntry::Texture(id) => {
101                    Pending::Direct(wgpu::BindingResource::TextureView(&textures.get(*id)?.view))
102                }
103                MaterialInstanceBindingEntry::TextureArray(id) => Pending::Direct(
104                    wgpu::BindingResource::TextureView(&texture_arrays.get(*id)?.view),
105                ),
106                MaterialInstanceBindingEntry::Cubemap(id) => {
107                    Pending::Direct(wgpu::BindingResource::TextureView(&cubemaps.get(*id)?.view))
108                }
109                MaterialInstanceBindingEntry::Sampler(kind) => {
110                    Pending::Direct(wgpu::BindingResource::Sampler(samplers.get(*kind)))
111                }
112                MaterialInstanceBindingEntry::Uniform(bytes) => {
113                    use wgpu::util::DeviceExt;
114                    let buf =
115                        backend
116                            .device
117                            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
118                                label: None,
119                                contents: bytes,
120                                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
121                            });
122                    owned_buffers.push(buf);
123                    Pending::OwnedBuffer(owned_buffers.len() - 1)
124                }
125                MaterialInstanceBindingEntry::Storage(bytes) => {
126                    use wgpu::util::DeviceExt;
127                    let buf =
128                        backend
129                            .device
130                            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
131                                label: None,
132                                contents: bytes,
133                                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
134                            });
135                    owned_buffers.push(buf);
136                    Pending::OwnedBuffer(owned_buffers.len() - 1)
137                }
138            };
139            pending.push((*name, resource));
140        }
141
142        let resolved: Vec<(&'static str, wgpu::BindingResource)> = pending
143            .into_iter()
144            .map(|(name, p)| {
145                let resource = match p {
146                    Pending::Direct(r) => r,
147                    Pending::OwnedBuffer(i) => owned_buffers[i].as_entire_binding(),
148                };
149                (name, resource)
150            })
151            .collect();
152
153        let bind_group = build_instance_build_group(
154            &backend.device,
155            &material.layout,
156            &material.entries,
157            &resolved,
158        )?;
159
160        Some(Self {
161            material: source.material,
162            bind_group,
163            _owned_buffers: owned_buffers,
164        })
165    }
166}
167
168pub struct MaterialInstancePlugin;
169impl MaterialInstancePlugin {
170    pub fn new() -> Self {
171        Self
172    }
173}
174impl Plugin for MaterialInstancePlugin {
175    fn build(&self, app: &mut App) {
176        app.add_plugin(AssetPlugin::<WGPUBackend, GPUMaterialInstance>::new());
177    }
178}