use unlit_ecs::{Bundle, DynamicBundle, World, bundle};
use unlit_wgpu::render_attachments::RenderAttachments;
use unlit_wgpu::resources::{ResHandle, ResourceGraph, TextureView};
use unlit_wgpu::scene::ViewportRect;
use unlit_wgpu::specialize::SurfaceKey;
use crate::attachments::FrameAttachments;
use crate::components::RenderLoadOps;
use crate::order::OrderKey;
use crate::source::RenderContext;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct FramePassOrder(pub i32);
impl OrderKey for FramePassOrder {
const ORDER: &'static str = "FramePassOrder";
const SUBJECT: &'static str = "passes";
fn value(self) -> i32 {
self.0
}
}
#[derive(Debug)]
pub struct FramePass {
pub order: FramePassOrder,
pub viewport: Option<ViewportRect>,
color: Option<ResHandle<TextureView>>,
depth: Option<ResHandle<TextureView>>,
msaa: Option<ResHandle<TextureView>>,
}
impl FramePass {
pub fn new(order: FramePassOrder) -> Self {
Self {
order,
viewport: None,
color: None,
depth: None,
msaa: None,
}
}
pub fn set_attachments(
&mut self,
attachments: &FrameAttachments,
color_view: ResHandle<TextureView>,
) {
self.color = Some(color_view);
self.depth = attachments.depth_view.clone();
self.msaa = attachments.msaa_view.clone();
}
pub fn park(&mut self) {
self.color = None;
self.depth = None;
self.msaa = None;
}
pub(crate) fn attachments(
&self,
world: &World,
ctx: RenderContext,
) -> Option<RenderAttachments> {
let graph = world
.root_scope()
.query_one::<&mut ResourceGraph>(ctx.graph)
.expect("the context's resource graph exists");
let view = |handle: &Option<ResHandle<TextureView>>| {
handle
.as_ref()
.map(|id| graph.get(id).expect("pass attachment view").clone())
};
let (color, depth, msaa) = (view(&self.color), view(&self.depth), view(&self.msaa));
(color.is_some() || depth.is_some() || msaa.is_some())
.then(|| RenderAttachments::from_views(color, depth, msaa))
}
}
pub struct Pass {
pub frame_pass: FramePass,
pub load: RenderLoadOps,
}
impl Pass {
#[must_use]
pub fn new(order: FramePassOrder) -> Self {
Self {
frame_pass: FramePass::new(order),
load: RenderLoadOps::default(),
}
}
}
impl Bundle for Pass {
fn into_bundle(self) -> DynamicBundle {
bundle![self.frame_pass, self.load]
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PassInfo {
pub surface: SurfaceKey,
pub width: u32,
pub height: u32,
pub viewport: Option<ViewportRect>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_pass_spawns_its_load_ops_on_the_same_entity() {
let mut world = World::new();
let load = RenderLoadOps {
depth: wgpu::LoadOp::Load,
..RenderLoadOps::default()
};
let entity = world.root_scope_mut().spawn(Pass {
load,
..Pass::new(FramePassOrder(7))
});
let root = world.root_scope();
let pass = root
.query_one::<&FramePass>(entity)
.expect("the pass is on the entity");
assert_eq!(pass.order, FramePassOrder(7));
let spawned = root
.query_one::<&RenderLoadOps>(entity)
.expect("the load ops are on the same entity");
assert_eq!(spawned.depth, wgpu::LoadOp::Load);
}
}