use draw_list::{Batcher, DrawList};
use mesh_cache::{GpuMeshes, HandedMeshes};
use passes::Scene;
use pipelines::{FrameBindings, GpuInstance, Pipelines, Styles};
use post::Post;
use post_effects::PostPasses;
use shadows::Shadows;
use skybox::{GpuSkies, SkyId};
use crate::assets::Textures;
use crate::gpu::{Gpu, Target, buffer};
use crate::post_effect;
use crate::skybox::Resident;
use crate::surface_style;
use crate::ui::{Changes, Painter};
use crate::{Config, Error, light};
const INITIAL_INSTANCE_CAPACITY: usize = 256;
pub(crate) struct Renderer {
meshes: GpuMeshes,
skies: GpuSkies,
textures: Textures,
batcher: Batcher,
instances: InstanceBuffer,
frame: FrameBindings,
pipelines: Pipelines,
styles: Styles,
post_effects: PostPasses,
post: Post,
shadows: Shadows,
}
impl Renderer {
pub(crate) async fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
display_format: wgpu::TextureFormat,
config: &Config,
styles: Vec<surface_style::Declaration>,
effects: Vec<post_effect::Declaration>,
) -> 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 = GpuSkies::new(device, queue, &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,
styles,
samples,
frame.layout(),
textures.layout(),
shadows.layout(),
)
.await?;
let post_effects = PostPasses::compile(device, effects, samples, display_format).await?;
Ok(Self {
skies,
meshes: GpuMeshes::new(),
batcher: Batcher::new(),
instances,
pipelines,
styles,
post_effects,
post,
frame,
textures,
shadows,
})
}
pub(crate) fn take(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
meshes: HandedMeshes,
skies: Vec<(SkyId, Resident)>,
ui: Changes,
painter: &mut Painter,
) {
self.meshes.take(meshes);
self.skies.receive(device, queue, &self.frame, skies);
painter.take(device, queue, ui);
}
pub(crate) fn render(&mut self, gpu: &mut Gpu, draws: &DrawList, painter: &mut Painter) {
let Some(frame) = gpu.begin_frame() else {
return;
};
let stats = self.render_to(gpu.device(), gpu.queue(), frame.target(), draws, painter);
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,
draws: &DrawList,
painter: &mut Painter,
) -> RenderStats {
self.resolve(device, queue, target, draws);
queue.submit(self.encode(device, queue, target, draws, painter));
self.batcher
.drawn()
.fold(RenderStats::NOTHING, |stats, batch| RenderStats {
draw_calls: stats.draw_calls + 1,
instances: stats.instances + batch.instances.len() as u32,
})
}
fn resolve(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
target: &Target,
draws: &DrawList,
) {
self.post.prepare(device, target.size());
let fallback = light::default_lights();
let lights = match draws.lights() {
[] => &fallback[..],
submitted => submitted,
};
let camera = draws.camera();
self.post.set_frame(
queue,
&post::Frame {
size: target.size(),
exposure: draws.exposure(),
bloom: draws.bloom(),
},
);
self.shadows
.prepare(device, lights, camera, target.aspect());
self.batcher.run(draws, &self.meshes, target.aspect());
for batch in self.batcher.drawn().chain(self.batcher.casters().batches()) {
self.meshes
.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, draws.surface_style_values(style));
}
let plan = self.shadows.plan();
let lighting = self.skies.drawing(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);
self.post_effects
.submit(queue, |effect| draws.post_effect_values(effect));
}
fn encode(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
target: &Target,
draws: &DrawList,
painter: &mut Painter,
) -> 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,
instances: self.instances.buffer(),
pipelines: &self.pipelines,
styles: &self.styles,
frame: &self.frame,
textures: &self.textures,
shadows: &self.shadows,
sky: self.skies.drawing(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 = painter.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
}
}
pub(crate) 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;