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 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 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}