use unlit_ecs::LocalWorld;
use unlit_wgpu::render_attachments::RenderAttachments;
use unlit_wgpu::resources::{ResourceGraph, ResourceId};
use unlit_wgpu::specialize::SurfaceKey;
use crate::components::RenderLoadOps;
use crate::source::{
FrameTarget, OrderWarnings, RenderContext, Source, SourceOrder, set_frame_target,
unset_frame_target,
};
pub struct Renderer {
context: RenderContext,
color_view: Option<ResourceId>,
depth_view: Option<ResourceId>,
msaa_view: Option<ResourceId>,
surface: Option<SurfaceKey>,
bound_size: Option<(u32, u32)>,
warnings: OrderWarnings,
order: SourceOrder,
}
impl Renderer {
pub fn new(ctx: RenderContext) -> Self {
Self {
context: ctx,
color_view: None,
depth_view: None,
msaa_view: None,
surface: None,
bound_size: None,
warnings: OrderWarnings::default(),
order: SourceOrder::default(),
}
}
pub fn context(&self) -> RenderContext {
self.context
}
pub fn set_render_target(
&mut self,
world: &LocalWorld,
color_view: Option<ResourceId>,
depth_view: Option<ResourceId>,
msaa_view: Option<ResourceId>,
) {
let graph = world
.get_mut::<ResourceGraph>(self.context.graph)
.expect("the context's resource graph exists");
let color = color_view.map(|id| {
graph
.get_texture_view(id)
.expect("color_view is a texture view in the graph")
.clone()
});
let depth = depth_view.map(|id| {
graph
.get_texture_view(id)
.expect("depth_view is a texture view in the graph")
.clone()
});
let msaa = msaa_view.map(|id| {
graph
.get_texture_view(id)
.expect("msaa_view is a texture view in the graph")
.clone()
});
let attachments = RenderAttachments::from_views(color, depth, msaa);
let attachments = match color_view.and_then(|id| graph.get_texture_view_format(id)) {
Some(format) => attachments.with_color_format(format),
None => attachments,
};
self.surface = Some(attachments.surface_key());
self.bound_size = Some((attachments.width(), attachments.height()));
self.color_view = color_view;
self.depth_view = depth_view;
self.msaa_view = msaa_view;
drop(graph);
set_frame_target(world, self.frame_target());
}
pub fn unset_render_target(&mut self, world: &LocalWorld) {
self.color_view = None;
self.depth_view = None;
self.msaa_view = None;
self.surface = None;
self.bound_size = None;
unset_frame_target(world);
}
pub fn frame_target(&self) -> FrameTarget {
let surface = self
.surface
.expect("set_render_target binds the target before rendering");
let (width, height) = self
.bound_size
.expect("set_render_target binds the target before rendering");
FrameTarget {
surface,
width,
height,
}
}
pub fn render(&mut self, world: &LocalWorld) {
let load_ops = frame_load_ops(world);
let device = world
.get::<wgpu::Device>(self.context.device)
.expect("the context's device resource exists")
.clone();
let queue = world
.get::<wgpu::Queue>(self.context.queue)
.expect("the context's queue resource exists")
.clone();
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("unlit3d::encoder"),
});
world.for_each::<&mut Source, _>(|mut source| {
source.build_scene(world, self.context, &mut encoder);
});
self.order.resolve(world);
self.warnings.check(self.order.ambiguous());
let attachments = self.attachments(world);
{
let mut pass = attachments.begin_pass(
&mut encoder,
load_ops.color,
load_ops.depth,
load_ops.stencil,
);
for &entity in self.order.entities() {
world
.with_mut::<Source, _>(entity, |source| source.scene().record(&mut pass))
.expect("the source entity exists");
}
}
queue.submit([encoder.finish()]);
}
fn attachments(&self, world: &LocalWorld) -> RenderAttachments {
let graph = world
.get_mut::<ResourceGraph>(self.context.graph)
.expect("the context's resource graph exists");
let color = self.color_view.map(|id| {
graph
.get_texture_view(id)
.expect("bound color view")
.clone()
});
let depth = self.depth_view.map(|id| {
graph
.get_texture_view(id)
.expect("bound depth view")
.clone()
});
let msaa = self
.msaa_view
.map(|id| graph.get_texture_view(id).expect("bound msaa view").clone());
RenderAttachments::from_views(color, depth, msaa)
}
}
fn frame_load_ops(world: &LocalWorld) -> RenderLoadOps {
world
.query::<&RenderLoadOps>()
.next()
.map_or_else(RenderLoadOps::default, |(_, ops)| *ops)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_world_without_load_ops_clears_with_the_defaults() {
let world = LocalWorld::new();
assert_eq!(frame_load_ops(&world), RenderLoadOps::default());
}
#[test]
fn load_ops_are_read_from_any_entity() {
let mut world = LocalWorld::new();
world.spawn((RenderLoadOps {
color: wgpu::LoadOp::Load,
..Default::default()
},));
assert_eq!(frame_load_ops(&world).color, wgpu::LoadOp::Load);
}
#[test]
fn the_first_load_ops_entity_wins() {
let mut world = LocalWorld::new();
let first = world.spawn((RenderLoadOps {
depth: wgpu::LoadOp::Load,
..Default::default()
},));
world.spawn((RenderLoadOps {
depth: wgpu::LoadOp::Clear(1.0),
..Default::default()
},));
assert_eq!(frame_load_ops(&world).depth, wgpu::LoadOp::Load);
assert!(world.get::<RenderLoadOps>(first).is_some());
}
}