use draw_list::{Batcher, DrawList};
use mesh_cache::MeshCache;
use passes::Scene;
use pipelines::{FrameBindings, GpuInstance, Pipelines, Styles};
use post::Post;
use post_effects::PostPasses;
use shadows::Shadows;
use skybox::Skies;
use core::time::Duration;
use std::rc::Rc;
use crate::assets::{Assets, Textures};
use crate::gpu::{Gpu, Target, buffer};
use crate::mesh::{Draw, Meshes};
use crate::post_effect::{self, PostEffectId, PostEffects};
use crate::skybox::Skyboxes;
use crate::surface_style::{self, SurfaceStyleId, SurfaceStyles};
use crate::ui::Overlay;
use crate::{Camera, Config, Error, Light, light};
const INITIAL_INSTANCE_CAPACITY: usize = 256;
pub(crate) struct Renderer<M: Meshes, S: Skyboxes> {
meshes: MeshCache<M>,
skies: Skies<S>,
textures: Textures,
draws: DrawList<M>,
batcher: Batcher,
instances: InstanceBuffer,
frame: FrameBindings,
pipelines: Pipelines,
styles: Styles,
post_effects: PostPasses,
post: Post,
shadows: Shadows,
}
impl<M: Meshes, S: Skyboxes> Renderer<M, S> {
pub(crate) async fn new<T: SurfaceStyles, C: PostEffects>(
device: &wgpu::Device,
queue: &wgpu::Queue,
display_format: wgpu::TextureFormat,
config: &Config,
assets: Rc<Assets>,
) -> Result<Self, Error> {
let samples = post::sample_count(config.antialiasing());
let scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
let frame = FrameBindings::new(device);
let textures = Textures::new(device, queue);
let shadows = Shadows::new(device, config.shadow_resolution());
let pipelines = Pipelines::new(
device,
samples,
frame.layout(),
textures.layout(),
shadows.layout(),
);
let post = Post::new(device, display_format, samples, config.tonemap());
let skies = Skies::new(device, queue, Rc::clone(&assets), &frame);
let instances = InstanceBuffer::new(device, INITIAL_INSTANCE_CAPACITY);
if let Some(error) = scope.pop().await {
return Err(Error::msg(format!(
"the graphics device refused what the engine builds at startup: {error}"
)));
}
let styles = Styles::compile(
device,
surface_style::Declarations::of::<T>(),
samples,
frame.layout(),
textures.layout(),
shadows.layout(),
)
.await?;
let post_effects = PostPasses::compile(
device,
post_effect::Declarations::of::<C>(),
samples,
display_format,
)
.await?;
Ok(Self {
skies,
meshes: MeshCache::new(assets, config.mesh_memory()),
draws: DrawList::new(),
batcher: Batcher::new(),
instances,
pipelines,
styles,
post_effects,
post,
frame,
textures,
shadows,
})
}
pub(crate) fn set_camera(&mut self, camera: Camera) {
self.draws.set_camera(camera);
}
pub(crate) fn camera(&self) -> Camera {
self.draws.camera()
}
pub(crate) fn light(&mut self, light: Light) {
self.draws.push_light(light);
}
pub(crate) fn set_skybox(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, sky: &S) {
let id = self.skies.id(device, queue, &self.frame, sky);
self.draws.set_skybox(id);
}
pub(crate) fn set_exposure(&mut self, exposure: f32) {
self.draws.set_exposure(exposure);
}
pub(crate) fn set_bloom(&mut self, amount: f32) {
self.draws.set_bloom(amount);
}
pub(crate) fn set_surface_style(&mut self, at: SurfaceStyleId, values: &impl SurfaceStyles) {
self.draws.set_surface_style(at, |into| values.write(into));
}
pub(crate) fn set_post_effect(&mut self, at: PostEffectId, values: &impl PostEffects) {
self.draws.set_post_effect(at, |into| values.write(into));
}
pub(crate) fn start_frame(&mut self, now: Duration) {
self.draws.start(now);
}
pub(crate) fn draw(&mut self, draw: Draw<M>) {
self.draws.push(draw);
}
pub(crate) fn meshes(&mut self) -> &mut MeshCache<M> {
&mut self.meshes
}
pub(crate) fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, mesh: M) {
let id = self.meshes.id_of(&mesh);
self.meshes
.store_mut()
.upload(device, queue, &self.textures, id);
}
pub(crate) fn build_catalog(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
for id in self.meshes.build_catalog() {
self.meshes
.store_mut()
.upload(device, queue, &self.textures, id);
}
self.skies.build_catalog(device, queue, &self.frame);
}
pub(crate) fn render(&mut self, gpu: &mut Gpu, overlay: &mut Overlay) {
let Some(frame) = gpu.begin_frame() else {
self.draws.clear();
return;
};
let stats = self.render_to(gpu.device(), gpu.queue(), frame.target(), overlay);
log::trace!(
"frame drawn in {} instanced draws over {} instances",
stats.draw_calls,
stats.instances
);
gpu.present(frame);
}
pub(crate) fn render_to(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
target: &Target,
overlay: &mut Overlay,
) -> RenderStats {
self.resolve(device, queue, target);
queue.submit(self.encode(device, queue, target, overlay));
let stats = self
.batcher
.drawn()
.fold(RenderStats::NOTHING, |stats, batch| RenderStats {
draw_calls: stats.draw_calls + 1,
instances: stats.instances + batch.instances.len() as u32,
});
self.draws.clear();
self.meshes.store_mut().end_frame();
stats
}
fn resolve(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, target: &Target) {
self.post.prepare(device, target.size());
let fallback = light::default_lights();
let lights = match self.draws.lights() {
[] => &fallback[..],
submitted => submitted,
};
let camera = self.draws.camera();
self.post.set_frame(
queue,
&post::Frame {
size: target.size(),
exposure: self.draws.exposure(),
bloom: self.draws.bloom(),
},
);
self.shadows
.prepare(device, lights, camera, target.aspect());
self.batcher
.run(&self.draws, &mut self.meshes, target.aspect());
for batch in self.batcher.drawn().chain(self.batcher.casters().batches()) {
self.meshes
.store_mut()
.upload(device, queue, &self.textures, batch.mesh);
}
self.instances
.write(device, queue, self.batcher.instances());
if self
.frame
.set_palette(device, queue, self.batcher.palette())
{
self.skies.rebind(device, &self.frame);
}
for style in self.styles.ids() {
self.styles
.set_values(queue, style, self.draws.surface_style_values(style));
}
let plan = self.shadows.plan();
let lighting = self.skies.drawing(self.draws.skybox()).lighting();
self.frame
.set_frame(queue, camera, target.size(), plan.lights(), lighting);
self.frame.set_casters(queue, plan.casters());
self.frame.set_maps(queue, plan.sampled());
self.post_effects
.prepare(device, queue, target.size(), camera, &self.post);
let Self {
post_effects,
draws,
..
} = self;
post_effects.submit(queue, |effect| draws.post_effect_values(effect));
}
fn encode(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
target: &Target,
overlay: &mut Overlay,
) -> Vec<wgpu::CommandBuffer> {
let Some(hdr) = self.post.scene() else {
return Vec::new();
};
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("mirage-engine frame"),
});
let scene = Scene {
opaque: self.batcher.opaque(),
cutout: self.batcher.cutout(),
translucent: self.batcher.translucent(),
additive: self.batcher.additive(),
casters: self.batcher.casters(),
meshes: self.meshes.store(),
instances: self.instances.buffer(),
pipelines: &self.pipelines,
styles: &self.styles,
frame: &self.frame,
textures: &self.textures,
shadows: &self.shadows,
sky: self.skies.drawing(self.draws.skybox()),
};
shadows::cast(&mut encoder, &scene);
passes::forward(&mut encoder, &hdr, &scene);
self.post_effects.resolve(&mut encoder);
let drawn = self.post_effects.scene(&mut encoder);
let Some(source) = drawn.or_else(|| self.post.sampled()) else {
return Vec::new();
};
self.post
.encode(&mut encoder, source, self.post_effects.tone_mapped(target));
self.post_effects.run_tone_mapped(&mut encoder, target);
let mut buffers = overlay.encode(
device,
queue,
&mut encoder,
self.post_effects.overlaid(target),
target.size(),
);
self.post_effects.run_over_ui(&mut encoder, target);
buffers.push(encoder.finish());
buffers
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct RenderStats {
pub(crate) draw_calls: u32,
pub(crate) instances: u32,
}
impl RenderStats {
const NOTHING: Self = Self {
draw_calls: 0,
instances: 0,
};
}
struct InstanceBuffer {
buffer: wgpu::Buffer,
capacity: usize,
}
impl InstanceBuffer {
fn new(device: &wgpu::Device, capacity: usize) -> Self {
Self {
buffer: buffer(
device,
"mirage-engine instances",
(capacity * size_of::<GpuInstance>()) as wgpu::BufferAddress,
wgpu::BufferUsages::VERTEX,
),
capacity,
}
}
fn write(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, instances: &[GpuInstance]) {
if instances.is_empty() {
return;
}
if instances.len() > self.capacity {
*self = Self::new(device, instances.len().next_power_of_two());
}
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(instances));
}
fn buffer(&self) -> &wgpu::Buffer {
&self.buffer
}
}
mod draw_list;
pub(crate) mod mesh_cache;
mod passes;
mod pipelines;
mod post;
mod post_effects;
pub(crate) mod shadows;
pub(crate) mod skybox;