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