#![cfg(feature = "gpu")]
use std::any::Any;
use std::fmt;
use crate::engine::error::{ECSError, ECSResult, ExecutionError};
use crate::engine::types::GPUResourceID;
use crate::gpu::GPUContext;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GPUBindingDesc {
pub read_only: bool,
}
impl GPUBindingDesc {
#[inline]
pub fn key(self) -> u8 {
if self.read_only {
1
} else {
2
}
}
}
pub trait GPUResource: Send + Sync {
fn name(&self) -> &str;
fn create_gpu(&mut self, ctx: &GPUContext) -> ECSResult<()>;
fn upload(&mut self, ctx: &GPUContext) -> ECSResult<()>;
fn download(&mut self, ctx: &GPUContext) -> ECSResult<()>;
fn automatic_download(&self) -> bool {
true
}
fn bindings(&self) -> &[GPUBindingDesc];
fn encode_bind_group_entries<'a>(
&'a self,
base: u32,
out: &mut Vec<wgpu::BindGroupEntry<'a>>,
) -> ECSResult<()>;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
#[cfg(feature = "gpu")]
struct GPUResourceEntry {
resource: Box<dyn GPUResource>,
created: bool,
cpu_dirty: bool,
pending_download: bool,
}
#[cfg(feature = "gpu")]
#[derive(Default)]
pub struct GPUResourceRegistry {
entries: Vec<GPUResourceEntry>,
next_id: GPUResourceID,
binding_generation: u64,
}
#[cfg(feature = "gpu")]
impl GPUResourceRegistry {
#[inline]
pub fn new() -> Self {
Self::default()
}
pub fn register<R: GPUResource + 'static>(&mut self, r: R) -> ECSResult<GPUResourceID> {
let id = self.next_id;
self.next_id = self.next_id.checked_add(1).ok_or_else(|| {
ECSError::from(ExecutionError::GpuDispatchFailed {
message: "GPU resource id space exhausted".into(),
})
})?;
self.entries.push(GPUResourceEntry {
resource: Box::new(r),
created: false,
cpu_dirty: true,
pending_download: false,
});
Ok(id)
}
#[inline]
pub(crate) fn binding_generation(&self) -> u64 {
self.binding_generation
}
#[inline]
pub fn mark_cpu_dirty(&mut self, id: GPUResourceID) -> ECSResult<()> {
let e = self.entries.get_mut(id as usize).ok_or_else(|| {
ECSError::from(ExecutionError::GpuDispatchFailed {
message: format!("missing gpu resource id {id}").into(),
})
})?;
e.cpu_dirty = true;
Ok(())
}
#[inline]
pub fn mark_pending_download(&mut self, id: GPUResourceID) -> ECSResult<()> {
let e = self.entries.get_mut(id as usize).ok_or_else(|| {
ECSError::from(ExecutionError::GpuDispatchFailed {
message: format!("missing gpu resource id {id}").into(),
})
})?;
e.pending_download = true;
Ok(())
}
pub fn ensure_created(&mut self, context: &GPUContext) -> ECSResult<()> {
for e in &mut self.entries {
if !e.created {
e.resource.create_gpu(context)?;
e.created = true;
self.binding_generation = self.binding_generation.wrapping_add(1);
}
}
Ok(())
}
pub fn upload_dirty(&mut self, context: &GPUContext) -> ECSResult<()> {
for e in &mut self.entries {
if e.cpu_dirty {
e.resource.upload(context)?;
e.cpu_dirty = false;
self.binding_generation = self.binding_generation.wrapping_add(1);
}
}
Ok(())
}
pub fn download_pending(&mut self, context: &GPUContext) -> ECSResult<()> {
for e in &mut self.entries {
if e.pending_download {
if e.resource.automatic_download() {
e.resource.download(context)?;
}
e.pending_download = false;
}
}
Ok(())
}
pub fn download_pending_filtered(
&mut self,
context: &GPUContext,
ids: &[GPUResourceID],
) -> ECSResult<()> {
for &id in ids {
if let Some(e) = self.entries.get_mut(id as usize) {
if e.pending_download {
if e.resource.automatic_download() {
e.resource.download(context)?;
}
e.pending_download = false;
}
}
}
Ok(())
}
pub fn flattened_binding_descs(&self, ids: &[GPUResourceID]) -> Vec<GPUBindingDesc> {
let mut out = Vec::new();
for &id in ids {
if let Some(e) = self.entries.get(id as usize) {
out.extend_from_slice(e.resource.bindings());
}
}
out
}
pub fn append_bind_group_entries<'a>(
&'a self,
ids: &[GPUResourceID],
base_binding: u32,
out: &mut Vec<wgpu::BindGroupEntry<'a>>,
) -> ECSResult<u32> {
let mut cursor = base_binding;
for &id in ids {
let e = self.entries.get(id as usize).ok_or_else(|| {
ECSError::from(ExecutionError::GpuDispatchFailed {
message: format!("missing gpu resource id {id}").into(),
})
})?;
e.resource.encode_bind_group_entries(cursor, out)?;
cursor += e.resource.bindings().len() as u32;
}
Ok(cursor)
}
pub fn get_mut_typed<R: 'static>(&mut self, id: GPUResourceID) -> Option<&mut R> {
self.entries
.get_mut(id as usize)
.and_then(|e| e.resource.as_any_mut().downcast_mut::<R>())
}
pub fn get_typed<R: 'static>(&self, id: GPUResourceID) -> Option<&R> {
self.entries
.get(id as usize)
.and_then(|e| e.resource.as_any().downcast_ref::<R>())
}
}
#[cfg(feature = "gpu")]
impl fmt::Debug for GPUResourceRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GPUResourceRegistry")
.field("entry_count", &self.entries.len())
.field("next_id", &self.next_id)
.finish()
}
}