use std::marker::PhantomData;
use crate::{
assets::{
storage::{ProcessedAssets, RawAssetHandle},
upload::Asset,
},
ecs::system::Res,
wgpu::{
backend::WGPUBackend,
binding::BindGroupTarget,
buffers::{resolve_storage_buffer, resolve_uniform_buffer, update_buffer},
samplers::{GlobalSamplers, SamplerKind},
},
};
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum BindingInstanceEntry {
Texture(RawAssetHandle),
TextureArray(RawAssetHandle),
Cubemap(RawAssetHandle),
Sampler(SamplerKind),
Uniform(Vec<u8>),
Storage(Vec<u8>),
}
pub struct BindingInstanceDescriptor<T> {
pub target: RawAssetHandle,
pub params: Vec<(&'static str, BindingInstanceEntry)>,
_marker: PhantomData<fn() -> T>,
}
impl<T> BindingInstanceDescriptor<T> {
pub fn new(target: RawAssetHandle, params: Vec<(&'static str, BindingInstanceEntry)>) -> Self {
Self { target, params, _marker: PhantomData }
}
}
pub fn binding_index(entries: &[super::binding::BindingEntry], name: &str) -> Option<u32> {
entries.iter().find(|e| e.name == name).map(|e| e.binding)
}
pub fn build_instance_bind_group(
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
target_entries: &[super::binding::BindingEntry],
resolved: &[(&'static str, wgpu::BindingResource)],
) -> Option<wgpu::BindGroup> {
let mut entries = Vec::with_capacity(resolved.len());
for (name, resource) in resolved {
let binding = binding_index(target_entries, *name)?;
entries.push(wgpu::BindGroupEntry {
binding,
resource: resource.clone(),
})
}
Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout,
entries: &entries,
}))
}
pub struct GPUBindingInstance<T> {
pub target: RawAssetHandle,
pub bind_group: wgpu::BindGroup,
buffers: Vec<(&'static str, wgpu::Buffer)>,
_marker: PhantomData<fn() -> T>,
}
impl<T> GPUBindingInstance<T> {
pub fn update(&self, queue: &wgpu::Queue, name: &str, data: &[u8]) {
match self.buffers.iter().find(|(n, _)| *n == name) {
Some((_, buf)) => update_buffer(queue, buf, data),
None => tracing::warn!(
"GPUBindingInstance::update: no bound buffer named '{name}' — check for a typo \
against the entries in this instance's BindingInstanceDescriptor"
),
}
}
}
impl<T> Asset<WGPUBackend> for GPUBindingInstance<T>
where
T: BindGroupTarget + 'static + Send + Sync,
{
type Source = BindingInstanceDescriptor<T>;
type Deps<'a> = (
Res<'a, ProcessedAssets<T>>,
Res<'a, ProcessedAssets<super::textures::GPUTexture>>,
Res<'a, ProcessedAssets<super::texture_array::GPUTextureArray>>,
Res<'a, ProcessedAssets<super::cubemap::GPUCubemap>>,
Res<'a, GlobalSamplers>,
);
fn upload<'a>(
source: &Self::Source,
backend: &WGPUBackend,
deps: &Self::Deps<'a>,
) -> Option<Self> {
let (targets, textures, texture_arrays, cubemaps, samplers) = deps;
let target = targets.get(source.target)?;
enum Pending<'a> {
Direct(wgpu::BindingResource<'a>),
OwnedBuffer(usize),
}
let mut owned_buffers: Vec<(&'static str, wgpu::Buffer)> = Vec::new();
let mut pending: Vec<(&'static str, Pending)> = Vec::new();
for (name, entry) in &source.params {
let resource = match entry {
BindingInstanceEntry::Texture(id) => {
Pending::Direct(wgpu::BindingResource::TextureView(&textures.get(*id)?.view))
}
BindingInstanceEntry::TextureArray(id) => Pending::Direct(
wgpu::BindingResource::TextureView(&texture_arrays.get(*id)?.view),
),
BindingInstanceEntry::Cubemap(id) => {
Pending::Direct(wgpu::BindingResource::TextureView(&cubemaps.get(*id)?.view))
}
BindingInstanceEntry::Sampler(kind) => {
Pending::Direct(wgpu::BindingResource::Sampler(samplers.get(*kind)))
}
BindingInstanceEntry::Uniform(bytes) => {
let buf = resolve_uniform_buffer(&backend.device, bytes.as_slice().into());
owned_buffers.push((*name, buf));
Pending::OwnedBuffer(owned_buffers.len() - 1)
}
BindingInstanceEntry::Storage(bytes) => {
let buf = resolve_storage_buffer(&backend.device, bytes.as_slice().into());
owned_buffers.push((*name, buf));
Pending::OwnedBuffer(owned_buffers.len() - 1)
}
};
pending.push((*name, resource));
}
let resolved: Vec<(&'static str, wgpu::BindingResource)> = pending
.into_iter()
.map(|(name, p)| {
let resource = match p {
Pending::Direct(r) => r,
Pending::OwnedBuffer(i) => owned_buffers[i].1.as_entire_binding(),
};
(name, resource)
})
.collect();
let bind_group = build_instance_bind_group(
&backend.device,
target.bind_group_layout(),
target.binding_entries(),
&resolved,
)?;
Some(Self {
target: source.target,
bind_group,
buffers: owned_buffers,
_marker: PhantomData,
})
}
}
pub type GPUMaterialInstance = GPUBindingInstance<super::material::GPUMaterial>;
pub type MaterialInstanceDescriptor = BindingInstanceDescriptor<super::material::GPUMaterial>;
pub type GPUComputeInstance = GPUBindingInstance<super::compute::GPUCompute>;
pub type ComputeInstanceDescriptor = BindingInstanceDescriptor<super::compute::GPUCompute>;
crate::wgpu::plugin_macros::asset_plugin! {
MaterialInstancePlugin, GPUMaterialInstance
}
crate::wgpu::plugin_macros::asset_plugin! {
ComputeInstancePlugin, GPUComputeInstance
}