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