use arrayvec::ArrayVec;
use glam::Affine3A;
use unlit_ecs::{Entity, Scope};
use unlit_wgpu::mesh::JointMatrix;
use unlit_wgpu::pipeline::{
CAMERA_BINDING, FRAME_BINDING, GlobalBindings, JOINTS_BINDING, MESH_METADATA_BINDING,
MORPH_DELTAS_BINDING, MORPH_WEIGHTS_BINDING, global_bind_group_layout,
supports_storage_buffers,
};
use unlit_wgpu::resources::{ResHandle, Resource, ResourceGraph};
use unlit_wgpu::specialize::{PipelineVariant, SpecializedPipeline, SurfaceKey};
use unlit_wgpu::texel_array::ArrayHandle;
use crate::components::{GpuMaterial, GpuMesh, MorphBinding, MorphWeights, SkinBinding, SkinPose};
pub use unlit_wgpu::resources::Rebuild;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GlobalResources {
pub camera: ResHandle<wgpu::Buffer>,
pub globals: ResHandle<wgpu::Buffer>,
pub metadata: ResHandle<ArrayHandle>,
pub joints: ResHandle<ArrayHandle>,
pub morph_weights: ResHandle<ArrayHandle>,
pub morph_deltas: ResHandle<ArrayHandle>,
}
impl GlobalResources {
pub(crate) fn declare_dependencies(
&self,
graph: &mut ResourceGraph,
group: &ResHandle<wgpu::BindGroup>,
) {
graph.add_dependency(group, &self.camera);
graph.add_dependency(group, &self.globals);
graph.add_dependency(group, &self.metadata);
graph.add_dependency(group, &self.joints);
graph.add_dependency(group, &self.morph_weights);
graph.add_dependency(group, &self.morph_deltas);
}
pub fn layout(&self, device: &wgpu::Device, bindings: GlobalBindings) -> wgpu::BindGroupLayout {
global_bind_group_layout(device, bindings, !supports_storage_buffers(device))
}
pub fn bind_group(
&self,
device: &wgpu::Device,
graph: &ResourceGraph,
layout: &wgpu::BindGroupLayout,
bindings: GlobalBindings,
) -> wgpu::BindGroup {
let camera = graph.get(&self.camera).expect("camera buffer exists");
let globals = graph.get(&self.globals).expect("globals buffer exists");
let mut entries = ArrayVec::<wgpu::BindGroupEntry<'_>, 6>::new();
entries.push(wgpu::BindGroupEntry {
binding: CAMERA_BINDING,
resource: camera.as_entire_binding(),
});
entries.push(wgpu::BindGroupEntry {
binding: FRAME_BINDING,
resource: globals.as_entire_binding(),
});
if bindings.metadata {
entries.push(wgpu::BindGroupEntry {
binding: MESH_METADATA_BINDING,
resource: graph
.get(&self.metadata)
.expect("metadata array exists")
.binding_resource(),
});
}
if bindings.joints {
entries.push(wgpu::BindGroupEntry {
binding: JOINTS_BINDING,
resource: graph
.get(&self.joints)
.expect("joints array exists")
.binding_resource(),
});
}
if bindings.morphs {
entries.push(wgpu::BindGroupEntry {
binding: MORPH_WEIGHTS_BINDING,
resource: graph
.get(&self.morph_weights)
.expect("morph weights array exists")
.binding_resource(),
});
entries.push(wgpu::BindGroupEntry {
binding: MORPH_DELTAS_BINDING,
resource: graph
.get(&self.morph_deltas)
.expect("morph deltas array exists")
.binding_resource(),
});
}
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("unlit3d::global"),
layout,
entries: &entries,
})
}
pub fn rebuild(
&self,
device: &wgpu::Device,
layout: wgpu::BindGroupLayout,
bindings: GlobalBindings,
) -> Rebuild {
let resources = self.clone();
let device = device.clone();
Rebuild::new(move |graph| {
Resource::BindGroup(resources.bind_group(&device, graph, &layout, bindings))
})
}
}
pub struct RegisteredRenderPipeline {
pub pipeline: wgpu::RenderPipeline,
pub global: Option<Rebuild>,
}
impl core::fmt::Debug for RegisteredRenderPipeline {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("RegisteredRenderPipeline")
.field("pipeline", &self.pipeline)
.field("global", &self.global)
.finish()
}
}
#[derive(Clone)]
pub(crate) struct RegisteredGlobal {
pub(crate) id: ResHandle<wgpu::BindGroup>,
}
pub struct FamilyContext<'a> {
pub device: &'a wgpu::Device,
pub resources: GlobalResources,
}
pub trait RenderPipelineKey: 'static {
type Variant: PipelineVariant<wgpu::RenderPipeline>;
fn variant(&self, draw: &DrawContext<'_>) -> Self::Variant;
}
pub struct DrawContext<'a> {
pub surface: SurfaceKey,
pub device: &'a wgpu::Device,
pub world: Scope<'a>,
pub entity: Entity,
pub mesh: &'a GpuMesh,
pub material: Option<&'a GpuMaterial>,
}
pub use unlit_wgpu::instance_stream::InstanceStreamDesc;
pub struct InstanceContext<'a> {
pub world: Scope<'a>,
pub entity: Entity,
pub world_from_local: Affine3A,
joints: &'a mut Vec<JointMatrix>,
morph_weights: &'a mut Vec<f32>,
}
impl<'a> InstanceContext<'a> {
pub(crate) fn new(
world: Scope<'a>,
entity: Entity,
world_from_local: Affine3A,
joints: &'a mut Vec<JointMatrix>,
morph_weights: &'a mut Vec<f32>,
) -> Self {
Self {
world,
entity,
world_from_local,
joints,
morph_weights,
}
}
pub fn pack_joints(&mut self) -> u32 {
let binding = self
.world
.query_one::<&SkinBinding>(self.entity)
.unwrap_or_else(|_| {
panic!(
"a skinned mesh needs a `SkinBinding` naming the entity that holds \
its `SkinPose`"
)
});
let pose = self
.world
.query_one::<&SkinPose>(binding.pose)
.unwrap_or_else(|_| {
panic!(
"the entity a skinned mesh binds to carries no `SkinPose`: {:?}",
binding.pose,
)
});
assert!(
!pose.matrices.is_empty(),
"a skinned mesh's pose needs at least one joint matrix"
);
let base = self.joints.len() as u32;
self.joints.extend_from_slice(&pose.matrices);
base
}
pub fn pack_morph_weights(&mut self, targets: u32) -> u32 {
let binding = self
.world
.query_one::<&MorphBinding>(self.entity)
.unwrap_or_else(|_| {
panic!(
"a mesh with morph targets needs a `MorphBinding` naming the entity \
that holds its `MorphWeights`"
)
});
let weights = self
.world
.query_one::<&MorphWeights>(binding.weights)
.unwrap_or_else(|_| {
panic!(
"the entity a morphed mesh binds to carries no `MorphWeights`: {:?}",
binding.weights,
)
});
assert_eq!(
weights.weights.len() as u32,
targets,
"a mesh's morph weights must hold one weight per morph target"
);
let base = self.morph_weights.len() as u32;
self.morph_weights.extend_from_slice(&weights.weights);
base
}
}
pub trait InstanceData: 'static {
fn stream(&self) -> InstanceStreamDesc;
fn write(&mut self, context: &mut InstanceContext<'_>, out: &mut [u8]);
}
impl InstanceData for () {
fn stream(&self) -> InstanceStreamDesc {
InstanceStreamDesc {
slot: 0,
array_stride: 0,
}
}
fn write(&mut self, _context: &mut InstanceContext<'_>, _out: &mut [u8]) {}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RenderPipelineId(u32);
impl RenderPipelineId {
pub(crate) fn new(index: u32) -> Self {
Self(index)
}
pub(crate) fn as_usize(self) -> usize {
self.0 as usize
}
}
pub trait RenderPipelineFactory<D> {
fn descriptor(
&self,
context: &FamilyContext<'_>,
value: &SpecializedPipeline<wgpu::RenderPipeline, D>,
) -> RegisteredRenderPipeline;
}