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