use core::cmp::Ordering;
use core::marker::PhantomData;
use arrayvec::ArrayVec;
use unlit_ecs::{LocalWorld, TypeIdHashMap};
use unlit_wgpu::specialize::{Specializable, Specializer, SurfaceKey, Variants};
use unlit_wgpu::pipeline::{GLOBAL_GROUP, INSTANCE_SLOT, MATERIAL_GROUP, MESH_GROUP};
use unlit_wgpu::scene::{DrawEntry, DrawRange, MAX_VERTEX_BUFFERS, Scene, VertexBufferBinding};
use crate::bounds::FrustumPlanes;
use crate::components::{Camera, GpuMaterial, GpuMesh, GpuPipeline, ZSortedDrawing};
use crate::culling::{VisibleMesh, collect_visible};
use crate::pipeline::{
DrawKey, FamilyContext, PipelineDesc, PipelineFactory, PipelineId, PipelineKey, RenderResources,
};
pub(crate) struct Family<T, S, F, K>
where
T: Specializable + 'static,
S: Specializer<T>,
F: PipelineFactory<T>,
{
variants: Variants<T, S>,
factory: F,
registered: Vec<PipelineId>,
_key: PhantomData<fn() -> K>,
}
impl<T, S, F, K> Family<T, S, F, K>
where
T: Specializable + 'static,
S: Specializer<T>,
F: PipelineFactory<T>,
{
pub(crate) fn new(device: &wgpu::Device, specializer: S, factory: F) -> Self {
Self {
variants: Variants::new(device, specializer),
factory,
registered: Vec::new(),
_key: PhantomData,
}
}
}
pub(crate) struct VisibleEntry {
pub(crate) mesh: VisibleMesh,
pub(crate) pipeline_id: PipelineId,
pub(crate) sort_key: u64,
pub(crate) depth: f32,
pub(crate) z_sorted: bool,
}
pub(crate) struct FamilyFrame<'a> {
pub(crate) world: &'a LocalWorld,
pub(crate) meshes: &'a [VisibleMesh],
pub(crate) camera: &'a Camera,
pub(crate) surface: SurfaceKey,
pub(crate) device: &'a wgpu::Device,
pub(crate) resources: &'a RenderResources,
}
struct Resolver<'a, 'f, T, S, F, K>
where
T: Specializable + 'static,
S: Specializer<T>,
F: PipelineFactory<T>,
K: PipelineKey<Pipeline = T>,
S::Key: From<(K, DrawKey)>,
{
frame: &'a FamilyFrame<'f>,
variants: &'a mut Variants<T, S>,
factory: &'a F,
registered: &'a mut Vec<PipelineId>,
visible: &'a mut Vec<VisibleEntry>,
register: &'a mut dyn FnMut(PipelineDesc) -> PipelineId,
_key: PhantomData<fn() -> K>,
}
impl<T, S, F, K> Resolver<'_, '_, T, S, F, K>
where
T: Specializable + 'static,
S: Specializer<T>,
F: PipelineFactory<T>,
K: PipelineKey<Pipeline = T>,
S::Key: From<(K, DrawKey)>,
{
fn collect(&mut self) {
for &candidate in self.frame.meshes {
let entity = candidate.entity;
let Some(pipeline) = self.frame.world.get::<GpuPipeline<K>>(entity) else {
continue;
};
let Some(mesh) = self.frame.world.get::<GpuMesh>(entity) else {
continue;
};
self.push(candidate, &pipeline, &mesh);
}
}
fn push(&mut self, mesh: VisibleMesh, pipeline: &GpuPipeline<K>, gpu_mesh: &GpuMesh) {
let entity = mesh.entity;
let instance = mesh.instance;
let draw = DrawKey::for_mesh(self.frame.surface, gpu_mesh);
let key = S::Key::from((pipeline.key().clone(), draw));
let ordinal = self
.variants
.specialize(|| pipeline.key().base_descriptor(), key);
let pipeline_id = match self.registered.get(ordinal as usize) {
Some(&id) => id,
None => {
let context = FamilyContext {
device: self.frame.device,
resources: self.frame.resources,
};
let desc = self
.factory
.descriptor(&context, self.variants.get(ordinal));
let id = (self.register)(desc);
self.registered.push(id);
id
}
};
let centre = glam::Vec3A::from(instance.translation());
let depth = (centre - glam::Vec3A::from(self.frame.camera.position)).length();
let sort_key = match self.frame.world.get::<GpuMaterial>(entity) {
Some(material) => material.sort_key(),
None => 0,
};
self.visible.push(VisibleEntry {
mesh,
pipeline_id,
sort_key,
depth,
z_sorted: self.frame.world.has::<ZSortedDrawing>(entity),
});
}
}
pub(crate) trait AnyFamily {
fn collect_and_resolve(
&mut self,
frame: &FamilyFrame<'_>,
visible: &mut Vec<VisibleEntry>,
register: &mut dyn FnMut(PipelineDesc) -> PipelineId,
);
}
impl<T, S, F, K> AnyFamily for Family<T, S, F, K>
where
T: Specializable + 'static,
S: Specializer<T>,
F: PipelineFactory<T>,
K: PipelineKey<Pipeline = T>,
S::Key: From<(K, DrawKey)>,
{
fn collect_and_resolve(
&mut self,
frame: &FamilyFrame<'_>,
visible: &mut Vec<VisibleEntry>,
register: &mut dyn FnMut(PipelineDesc) -> PipelineId,
) {
Resolver {
frame,
variants: &mut self.variants,
factory: &self.factory,
registered: &mut self.registered,
visible,
register,
_key: PhantomData,
}
.collect();
}
}
pub(crate) struct SceneFrame<'a> {
pub(crate) families: &'a mut TypeIdHashMap<Box<dyn AnyFamily>>,
pub(crate) meshes: &'a mut Vec<VisibleMesh>,
pub(crate) visible: &'a mut Vec<VisibleEntry>,
pub(crate) register: &'a mut dyn FnMut(PipelineDesc) -> PipelineId,
}
pub(crate) fn collect_and_sort_visible(
frame: SceneFrame<'_>,
world: &LocalWorld,
camera: &Camera,
surface: SurfaceKey,
device: &wgpu::Device,
resources: &RenderResources,
) {
let SceneFrame {
families,
meshes,
visible,
register,
} = frame;
let frustum = FrustumPlanes::from_clip_from_world(camera.clip_from_world);
collect_visible(world, &frustum, meshes);
visible.clear();
let frame = FamilyFrame {
world,
meshes,
camera,
surface,
device,
resources,
};
for family in families.values_mut() {
family.collect_and_resolve(&frame, visible, register);
}
visible.sort_unstable_by(|a, b| {
a.z_sorted
.cmp(&b.z_sorted)
.then_with(|| a.pipeline_id.cmp(&b.pipeline_id))
.then_with(|| {
if a.z_sorted {
b.depth.partial_cmp(&a.depth).unwrap_or(Ordering::Equal)
} else {
a.sort_key.cmp(&b.sort_key)
}
})
});
}
pub(crate) struct PipelineHandles {
pub(crate) pipeline: wgpu::RenderPipeline,
pub(crate) global: Option<wgpu::BindGroup>,
}
#[derive(Clone, Copy, PartialEq)]
pub(crate) struct DrawShape {
pub(crate) indexed: bool,
pub(crate) count: u32,
pub(crate) first: u32,
pub(crate) base_vertex: u32,
}
pub(crate) struct EntryHandles {
pub(crate) mesh_bg: Option<wgpu::BindGroup>,
pub(crate) material_bg: Option<wgpu::BindGroup>,
pub(crate) vertex_buffers: ArrayVec<VertexBufferBinding, MAX_VERTEX_BUFFERS>,
pub(crate) index_buffer: Option<(wgpu::Buffer, wgpu::IndexFormat)>,
pub(crate) shape: DrawShape,
}
fn same_draw(
a: &VisibleEntry,
a_handles: &EntryHandles,
b: &VisibleEntry,
b_handles: &EntryHandles,
) -> bool {
!a.z_sorted
&& !b.z_sorted
&& a.pipeline_id == b.pipeline_id
&& a_handles.shape == b_handles.shape
&& a_handles.mesh_bg == b_handles.mesh_bg
&& a_handles.material_bg == b_handles.material_bg
&& a_handles.index_buffer == b_handles.index_buffer
&& a_handles.vertex_buffers == b_handles.vertex_buffers
}
pub(crate) fn assemble_scene(
scene: &mut Scene,
visible: &[VisibleEntry],
pipelines: &[PipelineHandles],
handles: &[EntryHandles],
instance_buffer: &wgpu::Buffer,
) {
let mut start = 0;
while start < visible.len() {
let mut end = start + 1;
while end < visible.len()
&& same_draw(
&visible[end - 1],
&handles[end - 1],
&visible[end],
&handles[end],
)
{
end += 1;
}
let entry = &visible[start];
let handle = &handles[start];
let pipeline = &pipelines[entry.pipeline_id.as_usize()];
let first = handle.shape.first;
let range = if handle.shape.indexed {
DrawRange::indexed(first..first + handle.shape.count)
.with_base_vertex(handle.shape.base_vertex as i32)
.with_instances(start as u32..end as u32)
} else {
DrawRange::vertices(first..first + handle.shape.count)
.with_instances(start as u32..end as u32)
};
let mut draw = DrawEntry::new(&pipeline.pipeline, range);
if let Some(global) = &pipeline.global {
draw = draw.with_bind_group(GLOBAL_GROUP, global);
}
if let Some(material_bg) = &handle.material_bg {
draw = draw.with_bind_group(MATERIAL_GROUP, material_bg);
}
if let Some(mesh_bg) = &handle.mesh_bg {
draw = draw.with_bind_group(MESH_GROUP, mesh_bg);
}
for (slot, buffer, range) in &handle.vertex_buffers {
draw = draw.with_vertex_buffer_range(*slot, buffer, range.clone());
}
if let Some((buffer, format)) = &handle.index_buffer {
draw = draw.with_index_buffer(buffer, *format);
}
draw = draw.with_vertex_buffer(INSTANCE_SLOT, instance_buffer);
scene.push(draw);
start = end;
}
}