use unlit_ecs::World;
use unlit_wgpu::render_attachments::RenderAttachments;
use unlit_wgpu::resources::{ResHandle, ResourceGraph, TextureView};
use unlit_wgpu::scene::full_viewport;
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<ResHandle<TextureView>>,
depth_view: Option<ResHandle<TextureView>>,
msaa_view: Option<ResHandle<TextureView>>,
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: &World,
color_view: Option<ResHandle<TextureView>>,
depth_view: Option<ResHandle<TextureView>>,
msaa_view: Option<ResHandle<TextureView>>,
) {
let graph = world
.get_mut::<ResourceGraph>(self.context.graph)
.expect("the context's resource graph exists");
let color = color_view.as_ref().map(|id| {
graph
.get(id)
.expect("color_view is a texture view in the graph")
.clone()
});
let depth = depth_view.as_ref().map(|id| {
graph
.get(id)
.expect("depth_view is a texture view in the graph")
.clone()
});
let msaa = msaa_view.as_ref().map(|id| {
graph
.get(id)
.expect("msaa_view is a texture view in the graph")
.clone()
});
let attachments = RenderAttachments::from_views(color, depth, msaa);
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: &World) {
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: &World) {
profiling::scope!("renderer.frame");
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"),
});
{
profiling::scope!("renderer.frame.build_sources");
world.for_each::<&mut Source, _>(|mut source| {
source.build_scene(world, self.context, &mut encoder);
});
}
{
profiling::scope!("renderer.frame.resolve_order");
self.order.resolve(world);
self.warnings.check(self.order.ambiguous());
}
let attachments = self.attachments(world);
{
profiling::scope!("renderer.frame.record_passes");
let mut pass = attachments.begin_pass(
&mut encoder,
load_ops.color,
load_ops.depth,
load_ops.stencil,
);
let (width, height) = self
.bound_size
.expect("set_render_target binds the target before rendering");
let whole = full_viewport(width, height);
for &entity in self.order.entities() {
world
.with_mut::<Source, _>(entity, |source| {
source.scene().record_with_viewport(&mut pass, whole)
})
.expect("the source entity exists");
}
}
{
profiling::scope!("renderer.frame.submit");
queue.submit([encoder.finish()]);
}
}
fn attachments(&self, world: &World) -> RenderAttachments {
let graph = world
.get_mut::<ResourceGraph>(self.context.graph)
.expect("the context's resource graph exists");
let color = self
.color_view
.as_ref()
.map(|id| graph.get(id).expect("bound color view").clone());
let depth = self
.depth_view
.as_ref()
.map(|id| graph.get(id).expect("bound depth view").clone());
let msaa = self
.msaa_view
.as_ref()
.map(|id| graph.get(id).expect("bound msaa view").clone());
RenderAttachments::from_views(color, depth, msaa)
}
}
fn frame_load_ops(world: &World) -> 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 = World::new();
assert_eq!(frame_load_ops(&world), RenderLoadOps::default());
}
#[test]
fn load_ops_are_read_from_any_entity() {
let mut world = World::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 = World::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());
}
}