use crate::frame::{
Bindings, ContextAttachments, Draw, FramePlan, Geometry, Mesh, PassTarget, TextureCopy,
};
use crate::gpu_timer::GpuTimer;
use crate::host_buffer::{HostBuffer, UniformSlot};
use crate::pipelines::{CompiledPipelines, PipelineKey};
pub(crate) struct Encoder<'r> {
pub host: &'r HostBuffer,
pub pipelines: &'r CompiledPipelines<'r>,
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct EncodeStats {
pub draw_calls: u32,
pub pipeline_switches: u32,
}
impl Encoder<'_> {
pub fn encode(
&self,
encoder: &mut wgpu::CommandEncoder,
plan: &FramePlan,
timer: &GpuTimer,
) -> EncodeStats {
let mut stats = EncodeStats::default();
for (index, pass) in plan.passes.iter().enumerate() {
encode_copies(encoder, &pass.pre_copies);
let timing = timer.pass_writes(index, plan.passes.len());
let mut recorder = DrawRecorder {
pass: begin_render_pass(encoder, &pass.target, timing),
host: self.host,
pipelines: self.pipelines,
bound: None,
stats: &mut stats,
};
for draw in pass.target.draws() {
recorder.draw(draw);
}
}
stats
}
}
struct DrawRecorder<'e, 'r> {
pass: wgpu::RenderPass<'e>,
host: &'r HostBuffer,
pipelines: &'r CompiledPipelines<'r>,
bound: Option<PipelineKey>,
stats: &'r mut EncodeStats,
}
impl DrawRecorder<'_, '_> {
fn draw(&mut self, draw: &Draw) {
if let Some(stencil) = &draw.stencil {
self.use_pipeline(stencil.key);
self.bind_uniforms(stencil.uniforms);
self.draw_mesh(stencil.mesh);
}
self.use_pipeline(draw.key);
self.bind_uniforms(draw.uniforms);
self.bind(&draw.bindings);
match draw.geometry {
Geometry::Quad => {
self.pass.draw(0..6, 0..1);
self.stats.draw_calls += 1;
}
Geometry::Mesh(mesh) => self.draw_mesh(mesh),
}
}
fn use_pipeline(&mut self, key: PipelineKey) {
if self.bound != Some(key) {
self.pass.set_pipeline(self.pipelines.get(&key));
self.bound = Some(key);
self.stats.pipeline_switches += 1;
}
}
fn bind_uniforms(&mut self, uniforms: UniformSlot) {
self.pass
.set_bind_group(0, self.host.bind_group(uniforms.block), &[uniforms.offset]);
}
fn bind(&mut self, bindings: &Bindings) {
match bindings {
Bindings::Plain => {}
Bindings::Textured(texture) => self.pass.set_bind_group(1, texture, &[]),
Bindings::Blurred { texture, kernel } => {
self.pass.set_bind_group(1, texture, &[]);
self.pass.set_bind_group(
2,
self.host.kernel_bind_group(kernel.block),
&[kernel.offset],
);
}
Bindings::Runtime {
images,
uniforms,
source,
} => {
if let Some(images) = images {
self.pass.set_bind_group(1, images, &[]);
}
if let Some(uniforms) = uniforms {
self.pass.set_bind_group(
2,
self.host.effect_bind_group(uniforms.block),
&[uniforms.offset],
);
}
if let Some(source) = source {
self.pass.set_bind_group(3, source, &[]);
}
}
}
}
fn draw_mesh(&mut self, mesh: Mesh) {
let slot = mesh.slot;
let buffer = self.host.vertex_buffer(slot.block);
self.pass
.set_vertex_buffer(0, buffer.slice(slot.offset..slot.offset + slot.bytes));
self.pass.draw(0..mesh.vertices, 0..1);
self.stats.draw_calls += 1;
}
}
fn begin_render_pass<'e>(
encoder: &'e mut wgpu::CommandEncoder,
target: &PassTarget,
timing: Option<wgpu::RenderPassTimestampWrites<'_>>,
) -> wgpu::RenderPass<'e> {
let (color, depth) = match target {
PassTarget::Context {
attachments, clear, ..
} => context_attachments(attachments, *clear),
PassTarget::Filter { view, .. } => (filter_attachment(view), None),
};
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("valo.pass"),
color_attachments: &[Some(color)],
depth_stencil_attachment: depth,
timestamp_writes: timing,
occlusion_query_set: None,
multiview_mask: None,
});
render_pass.set_stencil_reference(0);
render_pass
}
fn encode_copies(encoder: &mut wgpu::CommandEncoder, copies: &[TextureCopy]) {
for copy in copies {
encoder.copy_texture_to_texture(
copy.src.as_image_copy(),
copy.dst.as_image_copy(),
wgpu::Extent3d {
width: copy.size[0],
height: copy.size[1],
depth_or_array_layers: 1,
},
);
}
}
fn context_attachments(
attachments: &ContextAttachments,
clear: valo_geometry::Color,
) -> (
wgpu::RenderPassColorAttachment<'_>,
Option<wgpu::RenderPassDepthStencilAttachment<'_>>,
) {
let color = wgpu::RenderPassColorAttachment {
view: &attachments.msaa,
depth_slice: None,
resolve_target: Some(&attachments.resolve),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(premultiplied(clear)),
store: wgpu::StoreOp::Discard,
},
};
let depth = wgpu::RenderPassDepthStencilAttachment {
view: &attachments.depth,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0.0),
store: wgpu::StoreOp::Discard,
}),
stencil_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0),
store: wgpu::StoreOp::Discard,
}),
};
(color, Some(depth))
}
fn filter_attachment(view: &wgpu::TextureView) -> wgpu::RenderPassColorAttachment<'_> {
wgpu::RenderPassColorAttachment {
view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
}
}
fn premultiplied(color: valo_geometry::Color) -> wgpu::Color {
let [r, g, b, a] = color.premultiplied();
wgpu::Color {
r: r as f64,
g: g as f64,
b: b as f64,
a: a as f64,
}
}