use std::sync::Arc;
use crate::end_frame_prepare::prepare_all;
use crate::pipelines::WgpuPipelines;
use crate::{LastEndFrameStats, WgpuGfxCtx};
#[derive(Clone)]
pub(crate) struct PreparedSlice {
pub buf: Arc<wgpu::Buffer>,
pub offset: u64,
pub size: u64,
}
impl PreparedSlice {
#[inline]
pub fn wgpu_slice(&self) -> wgpu::BufferSlice<'_> {
self.buf.slice(self.offset..self.offset + self.size)
}
#[inline]
pub fn uniform_binding(&self) -> wgpu::BufferBinding<'_> {
wgpu::BufferBinding {
buffer: &self.buf,
offset: self.offset,
size: std::num::NonZeroU64::new(self.size),
}
}
}
pub(crate) enum Prepared {
Clear(wgpu::Color),
Solid {
vb: PreparedSlice,
ib: PreparedSlice,
index_count: u32,
bg0: wgpu::BindGroup,
clip: Option<[i32; 4]>,
},
AaSolid {
vb: PreparedSlice,
ib: PreparedSlice,
index_count: u32,
bg0: wgpu::BindGroup,
clip: Option<[i32; 4]>,
},
AaTexture {
vb: PreparedSlice,
ib: PreparedSlice,
index_count: u32,
bg0: wgpu::BindGroup,
bg1: Arc<wgpu::BindGroup>,
clip: Option<[i32; 4]>,
},
Gradient {
_ramp_tex: wgpu::Texture,
_ramp_view: wgpu::TextureView,
vb: PreparedSlice,
ib: PreparedSlice,
index_count: u32,
bg0: wgpu::BindGroup,
bg1: wgpu::BindGroup,
clip: Option<[i32; 4]>,
},
Textured {
_texture: Arc<wgpu::Texture>,
_view: wgpu::TextureView,
vb: PreparedSlice,
bg0: wgpu::BindGroup,
bg1: wgpu::BindGroup,
clip: Option<[i32; 4]>,
},
LcdMask {
_texture: Arc<wgpu::Texture>,
_view: wgpu::TextureView,
vb: PreparedSlice,
ib: PreparedSlice,
bg0s: [wgpu::BindGroup; 3],
bg1: wgpu::BindGroup,
clip: Option<[i32; 4]>,
flatten: bool,
},
LcbMask {
_color_tex: Arc<wgpu::Texture>,
_color_view: wgpu::TextureView,
_alpha_tex: Arc<wgpu::Texture>,
_alpha_view: wgpu::TextureView,
vb: PreparedSlice,
ib: PreparedSlice,
bg0s: [wgpu::BindGroup; 3],
bg1: wgpu::BindGroup,
clip: Option<[i32; 4]>,
flatten: bool,
},
PushLayer {
_texture: Arc<wgpu::Texture>,
view: wgpu::TextureView,
size: (u32, u32),
},
PopLayer {
_texture: Arc<wgpu::Texture>,
_view: wgpu::TextureView,
vb: PreparedSlice,
bg0: wgpu::BindGroup,
bg1: wgpu::BindGroup,
parent_clip: Option<[i32; 4]>,
},
PopLayerMasked {
_texture: Arc<wgpu::Texture>,
_view: wgpu::TextureView,
vb: PreparedSlice,
ib: PreparedSlice,
index_count: u32,
bg0: wgpu::BindGroup,
bg1: wgpu::BindGroup,
parent_clip: Option<[i32; 4]>,
},
CompositeLayer {
_texture: Arc<wgpu::Texture>,
_view: wgpu::TextureView,
vb: PreparedSlice,
bg0: wgpu::BindGroup,
bg1: wgpu::BindGroup,
parent_clip: Option<[i32; 4]>,
},
Custom {
renderer: crate::custom_render::SharedCustomRenderer,
screen_rect: agg_gui::Rect,
parent_clip: Option<[i32; 4]>,
},
}
impl WgpuGfxCtx {
pub(crate) fn flush_to_surface(&mut self, surface_view: &wgpu::TextureView) {
let commands = std::mem::take(&mut self.commands);
let command_count = commands.len() as u32;
self.frame_arenas.begin_frame();
let t_prepare = web_time::Instant::now();
let prepared = prepare_all(
&self.device,
&self.queue,
&self.pipelines,
&mut self.frame_arenas,
&commands,
self.viewport,
&self.aa_step_bg1,
);
let prepare_us = t_prepare.elapsed().as_micros().min(u32::MAX as u128) as u32;
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("frame"),
});
let t_execute = web_time::Instant::now();
execute_prepared(
&self.device,
&self.queue,
self.surface_format,
&mut encoder,
surface_view,
&self.pipelines,
&prepared,
self.viewport,
);
let execute_us = t_execute.elapsed().as_micros().min(u32::MAX as u128) as u32;
let t_submit = web_time::Instant::now();
self.queue.submit(std::iter::once(encoder.finish()));
let submit_us = t_submit.elapsed().as_micros().min(u32::MAX as u128) as u32;
self.last_end_frame_stats = LastEndFrameStats {
prepare_us,
execute_us,
submit_us,
command_count,
};
}
}
fn execute_prepared<'a>(
device: &wgpu::Device,
queue: &wgpu::Queue,
surface_format: wgpu::TextureFormat,
encoder: &mut wgpu::CommandEncoder,
surface_view: &'a wgpu::TextureView,
pipelines: &WgpuPipelines,
prepared: &'a [Prepared],
surface_viewport: (f32, f32),
) {
let init_clear = match prepared.first() {
Some(Prepared::Clear(c)) => Some(*c),
_ => None,
};
let mut target_stack: Vec<(&'a wgpu::TextureView, (f32, f32))> =
vec![(surface_view, surface_viewport)];
let mut load_op: wgpu::LoadOp<wgpu::Color> = match init_clear {
Some(c) => wgpu::LoadOp::Clear(c),
None => wgpu::LoadOp::Load,
};
type PendingComposite<'p> = (
&'p PreparedSlice,
Option<(&'p PreparedSlice, u32)>,
&'p wgpu::BindGroup,
&'p wgpu::BindGroup,
Option<[i32; 4]>,
);
let mut pending_composite: Option<PendingComposite<'a>> = None;
let mut i = 0usize;
while i < prepared.len() || pending_composite.is_some() {
let &(target_view, target_vp) = target_stack.last().unwrap();
{
let mut pass = begin_pass(encoder, target_view, load_op);
pass.set_viewport(0.0, 0.0, target_vp.0, target_vp.1, 0.0, 1.0);
if let Some((vb, indexed, bg0, bg1, parent_clip)) = pending_composite.take() {
if apply_clip(&mut pass, parent_clip, target_vp) {
match indexed {
None => {
pass.set_pipeline(&pipelines.layer_pipeline);
pass.set_bind_group(0, bg0, &[]);
pass.set_bind_group(1, bg1, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.draw(0..6, 0..1);
}
Some((ib, index_count)) if index_count > 0 => {
pass.set_pipeline(&pipelines.layer_mesh_pipeline);
pass.set_bind_group(0, bg0, &[]);
pass.set_bind_group(1, bg1, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.set_index_buffer(ib.wgpu_slice(), wgpu::IndexFormat::Uint32);
pass.draw_indexed(0..index_count, 0, 0..1);
}
Some(_) => {}
}
}
}
while i < prepared.len() {
match &prepared[i] {
Prepared::PushLayer { .. }
| Prepared::PopLayer { .. }
| Prepared::PopLayerMasked { .. }
| Prepared::Custom { .. } => break,
other => {
execute_one(&mut pass, pipelines, other, target_vp);
i += 1;
}
}
}
}
load_op = wgpu::LoadOp::Load;
if i < prepared.len() {
match &prepared[i] {
Prepared::PushLayer { view, size, .. } => {
target_stack.push((view, (size.0 as f32, size.1 as f32)));
load_op = wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT);
i += 1;
}
Prepared::PopLayer {
vb,
bg0,
bg1,
parent_clip,
..
} => {
target_stack.pop();
pending_composite = Some((vb, None, bg0, bg1, *parent_clip));
i += 1;
}
Prepared::PopLayerMasked {
vb,
ib,
index_count,
bg0,
bg1,
parent_clip,
..
} => {
target_stack.pop();
pending_composite =
Some((vb, Some((ib, *index_count)), bg0, bg1, *parent_clip));
i += 1;
}
Prepared::Custom {
renderer,
screen_rect,
parent_clip,
} => {
let target_size = (target_vp.0 as u32, target_vp.1 as u32);
let ctx = crate::custom_render::WgpuCustomRenderCtx {
device,
queue,
encoder,
target_view,
target_size,
surface_format,
screen_rect: *screen_rect,
parent_clip: *parent_clip,
pipelines,
};
renderer.borrow_mut().render(ctx);
i += 1;
}
_ => unreachable!("loop only breaks on pass-boundary commands"),
}
}
}
}
fn execute_one(
pass: &mut wgpu::RenderPass,
pipelines: &WgpuPipelines,
item: &Prepared,
vp: (f32, f32),
) {
match item {
Prepared::Clear(_) => {
}
Prepared::Solid {
vb,
ib,
index_count,
bg0,
clip,
} => {
if !apply_clip(pass, *clip, vp) {
return;
}
pass.set_pipeline(&pipelines.solid_pipeline);
pass.set_bind_group(0, bg0, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.set_index_buffer(ib.wgpu_slice(), wgpu::IndexFormat::Uint32);
pass.draw_indexed(0..*index_count, 0, 0..1);
}
Prepared::AaSolid {
vb,
ib,
index_count,
bg0,
clip,
} => {
if !apply_clip(pass, *clip, vp) {
return;
}
pass.set_pipeline(&pipelines.aa_solid_pipeline);
pass.set_bind_group(0, bg0, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.set_index_buffer(ib.wgpu_slice(), wgpu::IndexFormat::Uint32);
pass.draw_indexed(0..*index_count, 0, 0..1);
}
Prepared::AaTexture {
vb,
ib,
index_count,
bg0,
bg1,
clip,
} => {
if !apply_clip(pass, *clip, vp) {
return;
}
pass.set_pipeline(&pipelines.aa_texture_pipeline);
pass.set_bind_group(0, bg0, &[]);
pass.set_bind_group(1, &**bg1, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.set_index_buffer(ib.wgpu_slice(), wgpu::IndexFormat::Uint32);
pass.draw_indexed(0..*index_count, 0, 0..1);
}
Prepared::Gradient {
vb,
ib,
index_count,
bg0,
bg1,
clip,
..
} => {
if !apply_clip(pass, *clip, vp) {
return;
}
pass.set_pipeline(&pipelines.gradient_pipeline);
pass.set_bind_group(0, bg0, &[]);
pass.set_bind_group(1, bg1, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.set_index_buffer(ib.wgpu_slice(), wgpu::IndexFormat::Uint32);
pass.draw_indexed(0..*index_count, 0, 0..1);
}
Prepared::Textured {
vb, bg0, bg1, clip, ..
} => {
if !apply_clip(pass, *clip, vp) {
return;
}
pass.set_pipeline(&pipelines.tex_pipeline);
pass.set_bind_group(0, bg0, &[]);
pass.set_bind_group(1, bg1, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.draw(0..6, 0..1);
}
Prepared::LcdMask {
vb,
ib,
bg0s,
bg1,
clip,
flatten,
..
} => {
if !apply_clip(pass, *clip, vp) {
return;
}
pass.set_bind_group(1, bg1, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.set_index_buffer(ib.wgpu_slice(), wgpu::IndexFormat::Uint32);
if *flatten {
pass.set_pipeline(&pipelines.text_gray);
pass.set_bind_group(0, &bg0s[0], &[]);
pass.draw_indexed(0..6, 0, 0..1);
} else {
let lcd_pipelines = [&pipelines.lcd_r, &pipelines.lcd_g, &pipelines.lcd_b];
for ch in 0..3 {
pass.set_pipeline(lcd_pipelines[ch]);
pass.set_bind_group(0, &bg0s[ch], &[]);
pass.draw_indexed(0..6, 0, 0..1);
}
}
}
Prepared::LcbMask {
vb,
ib,
bg0s,
bg1,
clip,
flatten,
..
} => {
if !apply_clip(pass, *clip, vp) {
return;
}
pass.set_bind_group(1, bg1, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.set_index_buffer(ib.wgpu_slice(), wgpu::IndexFormat::Uint32);
if *flatten {
pass.set_pipeline(&pipelines.lcb_flatten);
pass.set_bind_group(0, &bg0s[0], &[]);
pass.draw_indexed(0..6, 0, 0..1);
} else {
let lcb_pipelines = [&pipelines.lcb_r, &pipelines.lcb_g, &pipelines.lcb_b];
for ch in 0..3 {
pass.set_pipeline(lcb_pipelines[ch]);
pass.set_bind_group(0, &bg0s[ch], &[]);
pass.draw_indexed(0..6, 0, 0..1);
}
}
}
Prepared::CompositeLayer {
vb,
bg0,
bg1,
parent_clip,
..
} => {
if !apply_clip(pass, *parent_clip, vp) {
return;
}
pass.set_pipeline(&pipelines.layer_pipeline);
pass.set_bind_group(0, bg0, &[]);
pass.set_bind_group(1, bg1, &[]);
pass.set_vertex_buffer(0, vb.wgpu_slice());
pass.draw(0..6, 0..1);
}
Prepared::PushLayer { .. }
| Prepared::PopLayer { .. }
| Prepared::PopLayerMasked { .. }
| Prepared::Custom { .. } => {}
}
}
fn begin_pass<'a>(
encoder: &'a mut wgpu::CommandEncoder,
view: &'a wgpu::TextureView,
load: wgpu::LoadOp<wgpu::Color>,
) -> wgpu::RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
})
}
fn apply_clip(pass: &mut wgpu::RenderPass, clip: Option<[i32; 4]>, vp: (f32, f32)) -> bool {
let vp_w = vp.0 as u32;
let vp_h = vp.1 as u32;
if let Some(scissor) = clip {
let (x, y, w, h) = WgpuGfxCtx::yup_to_ydown_scissor(scissor, vp_h);
let w = w.min(vp_w.saturating_sub(x));
let h = h.min(vp_h.saturating_sub(y));
if w > 0 && h > 0 {
pass.set_scissor_rect(x, y, w, h);
true
} else {
false
}
} else {
pass.set_scissor_rect(0, 0, vp_w, vp_h);
true
}
}
#[cfg(test)]
pub(crate) fn clip_yields_visible_pixels(clip: Option<[i32; 4]>, vp: (f32, f32)) -> bool {
let vp_w = vp.0 as u32;
let vp_h = vp.1 as u32;
match clip {
None => vp_w > 0 && vp_h > 0,
Some(scissor) => {
let (x, y, w, h) = WgpuGfxCtx::yup_to_ydown_scissor(scissor, vp_h);
let w = w.min(vp_w.saturating_sub(x));
let h = h.min(vp_h.saturating_sub(y));
w > 0 && h > 0
}
}
}
#[cfg(test)]
mod tests {
use super::clip_yields_visible_pixels;
#[test]
fn zero_height_clip_skips_draw() {
assert!(!clip_yields_visible_pixels(
Some([0, 0, 200, 0]),
(400.0, 300.0)
));
}
#[test]
fn zero_width_clip_skips_draw() {
assert!(!clip_yields_visible_pixels(
Some([0, 0, 0, 100]),
(400.0, 300.0)
));
}
#[test]
fn ordinary_clip_passes() {
assert!(clip_yields_visible_pixels(
Some([10, 10, 100, 50]),
(400.0, 300.0)
));
}
#[test]
fn no_clip_passes_when_viewport_is_non_empty() {
assert!(clip_yields_visible_pixels(None, (400.0, 300.0)));
}
#[test]
fn clip_entirely_outside_viewport_is_rejected() {
assert!(!clip_yields_visible_pixels(
Some([400, 0, 50, 50]),
(400.0, 300.0)
));
}
}