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