1use std::marker::PhantomData;
2
3use crate::{
4 assets::{
5 storage::{ProcessedAssets, RawAssetHandle},
6 upload::Asset,
7 },
8 ecs::system::Res,
9 wgpu::{
10 backend::WGPUBackend,
11 binding::BindGroupTarget,
12 buffers::{resolve_storage_buffer, resolve_uniform_buffer, update_buffer},
13 samplers::{GlobalSamplers, SamplerKind},
14 },
15};
16
17#[derive(Clone, PartialEq, Eq, Hash)]
23pub enum BindingInstanceEntry {
24 Texture(RawAssetHandle),
27 TextureArray(RawAssetHandle),
30 Cubemap(RawAssetHandle),
33 Sampler(SamplerKind),
35 Uniform(Vec<u8>),
38 Storage(Vec<u8>),
40}
41
42pub struct BindingInstanceDescriptor<T> {
52 pub target: RawAssetHandle,
55 pub params: Vec<(&'static str, BindingInstanceEntry)>,
59 _marker: PhantomData<fn() -> T>,
60}
61
62impl<T> BindingInstanceDescriptor<T> {
65 pub fn new(target: RawAssetHandle, params: Vec<(&'static str, BindingInstanceEntry)>) -> Self {
66 Self { target, params, _marker: PhantomData }
67 }
68}
69
70pub fn binding_index(entries: &[super::binding::BindingEntry], name: &str) -> Option<u32> {
72 entries.iter().find(|e| e.name == name).map(|e| e.binding)
73}
74
75pub fn build_instance_bind_group(
81 device: &wgpu::Device,
82 layout: &wgpu::BindGroupLayout,
83 target_entries: &[super::binding::BindingEntry],
84 resolved: &[(&'static str, wgpu::BindingResource)],
85) -> Option<wgpu::BindGroup> {
86 let mut entries = Vec::with_capacity(resolved.len());
87 for (name, resource) in resolved {
88 let binding = binding_index(target_entries, *name)?;
89 entries.push(wgpu::BindGroupEntry {
90 binding,
91 resource: resource.clone(),
92 })
93 }
94
95 Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
96 label: None,
97 layout,
98 entries: &entries,
99 }))
100}
101
102pub struct GPUBindingInstance<T> {
108 pub target: RawAssetHandle,
109 pub bind_group: wgpu::BindGroup,
110 buffers: Vec<(&'static str, wgpu::Buffer)>,
112 _marker: PhantomData<fn() -> T>,
113}
114
115impl<T> GPUBindingInstance<T> {
116 pub fn update(&self, queue: &wgpu::Queue, name: &str, data: &[u8]) {
122 match self.buffers.iter().find(|(n, _)| *n == name) {
123 Some((_, buf)) => update_buffer(queue, buf, data),
124 None => tracing::warn!(
125 "GPUBindingInstance::update: no bound buffer named '{name}' — check for a typo \
126 against the entries in this instance's BindingInstanceDescriptor"
127 ),
128 }
129 }
130}
131
132impl<T> Asset<WGPUBackend> for GPUBindingInstance<T>
133where
134 T: BindGroupTarget + 'static + Send + Sync,
135{
136 type Source = BindingInstanceDescriptor<T>;
137 type Deps<'a> = (
138 Res<'a, ProcessedAssets<T>>,
139 Res<'a, ProcessedAssets<super::textures::GPUTexture>>,
140 Res<'a, ProcessedAssets<super::texture_array::GPUTextureArray>>,
141 Res<'a, ProcessedAssets<super::cubemap::GPUCubemap>>,
142 Res<'a, GlobalSamplers>,
143 );
144
145 fn upload<'a>(
146 source: &Self::Source,
147 backend: &WGPUBackend,
148 deps: &Self::Deps<'a>,
149 ) -> Option<Self> {
150 let (targets, textures, texture_arrays, cubemaps, samplers) = deps;
151 let target = targets.get(source.target)?;
152
153 enum Pending<'a> {
158 Direct(wgpu::BindingResource<'a>),
159 OwnedBuffer(usize),
160 }
161
162 let mut owned_buffers: Vec<(&'static str, wgpu::Buffer)> = Vec::new();
163 let mut pending: Vec<(&'static str, Pending)> = Vec::new();
164
165 for (name, entry) in &source.params {
166 let resource = match entry {
167 BindingInstanceEntry::Texture(id) => {
168 Pending::Direct(wgpu::BindingResource::TextureView(&textures.get(*id)?.view))
169 }
170 BindingInstanceEntry::TextureArray(id) => Pending::Direct(
171 wgpu::BindingResource::TextureView(&texture_arrays.get(*id)?.view),
172 ),
173 BindingInstanceEntry::Cubemap(id) => {
174 Pending::Direct(wgpu::BindingResource::TextureView(&cubemaps.get(*id)?.view))
175 }
176 BindingInstanceEntry::Sampler(kind) => {
177 Pending::Direct(wgpu::BindingResource::Sampler(samplers.get(*kind)))
178 }
179 BindingInstanceEntry::Uniform(bytes) => {
180 let buf = resolve_uniform_buffer(&backend.device, bytes.as_slice().into());
181 owned_buffers.push((*name, buf));
182 Pending::OwnedBuffer(owned_buffers.len() - 1)
183 }
184 BindingInstanceEntry::Storage(bytes) => {
185 let buf = resolve_storage_buffer(&backend.device, bytes.as_slice().into());
186 owned_buffers.push((*name, buf));
187 Pending::OwnedBuffer(owned_buffers.len() - 1)
188 }
189 };
190 pending.push((*name, resource));
191 }
192
193 let resolved: Vec<(&'static str, wgpu::BindingResource)> = pending
194 .into_iter()
195 .map(|(name, p)| {
196 let resource = match p {
197 Pending::Direct(r) => r,
198 Pending::OwnedBuffer(i) => owned_buffers[i].1.as_entire_binding(),
199 };
200 (name, resource)
201 })
202 .collect();
203
204 let bind_group = build_instance_bind_group(
205 &backend.device,
206 target.bind_group_layout(),
207 target.binding_entries(),
208 &resolved,
209 )?;
210
211 Some(Self {
212 target: source.target,
213 bind_group,
214 buffers: owned_buffers,
215 _marker: PhantomData,
216 })
217 }
218}
219
220pub type GPUMaterialInstance = GPUBindingInstance<super::material::GPUMaterial>;
223pub type MaterialInstanceDescriptor = BindingInstanceDescriptor<super::material::GPUMaterial>;
225
226pub type GPUComputeInstance = GPUBindingInstance<super::compute::GPUCompute>;
229pub type ComputeInstanceDescriptor = BindingInstanceDescriptor<super::compute::GPUCompute>;
231
232crate::wgpu::plugin_macros::asset_plugin! {
233 MaterialInstancePlugin, GPUMaterialInstance
238}
239
240crate::wgpu::plugin_macros::asset_plugin! {
241 ComputeInstancePlugin, GPUComputeInstance
246}