use std::{iter::Peekable, rc::Rc, sync::Arc};
use cranpose_ui_graphics::{BlendMode, Rect, RuntimeShader};
use crate::{
effect_renderer::{
CompositeBatchItem, CompositeSampleMode, PreparedCompositeDraw,
PreparedProjectiveComposite, PreparedShaderDraw, ProjectiveCompositeItem,
RoundedCompositeMask, ShaderCompositeBatchItem, SubstrateRegions,
},
frame_graph::FrameCommandRecorder,
offscreen::OffscreenTarget,
render::{
GpuRenderer, StoreRunBatch, ViewportUniformParams, image_draw_bounds, run_draw_bounds,
run_draw_is_visible_in_rect, supported_blend_mode, text_draw_bounds,
text_draw_is_visible_in_rect,
},
run_store::{RunDrawCall, run_has_shapes},
scene::{CompositorScene, DrawOp, DrawOpKind, RunDraw, TextDraw},
};
#[derive(Clone, Copy)]
pub(crate) struct PassTarget<'a> {
pub(crate) view: &'a wgpu::TextureView,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) offset: [f32; 2],
}
impl PassTarget<'_> {
pub(crate) fn logical_rect(&self, root_scale: f32) -> Rect {
Rect {
x: self.offset[0] / root_scale,
y: self.offset[1] / root_scale,
width: self.width as f32 / root_scale,
height: self.height as f32 / root_scale,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SourceContent {
Retained(u64),
Transient,
}
impl SourceContent {
pub(crate) fn retained(key: &impl std::hash::Hash) -> Self {
let mut hasher = cranpose_ui_graphics::FxHasher::default();
key.hash(&mut hasher);
Self::Retained(std::hash::Hasher::finish(&hasher))
}
pub(crate) fn retained_hash(self) -> Option<u64> {
match self {
Self::Retained(hash) => Some(hash),
Self::Transient => None,
}
}
pub(crate) fn derived(self, step: &impl std::hash::Hash) -> Self {
match self {
Self::Retained(hash) => Self::retained(&(hash, step)),
Self::Transient => Self::Transient,
}
}
}
#[derive(Clone)]
pub(crate) struct ResolvedComposite {
pub(crate) z_index: usize,
pub(crate) source: Rc<OffscreenTarget>,
pub(crate) content: SourceContent,
pub(crate) dest: (f32, f32, f32, f32),
pub(crate) scissor: Option<(f32, f32, f32, f32)>,
pub(crate) kind: ResolvedCompositeKind,
}
#[derive(Clone)]
pub(crate) enum ResolvedCompositeKind {
Blit {
alpha: f32,
blend_mode: BlendMode,
rounded_mask: Option<RoundedCompositeMask>,
sample_mode: CompositeSampleMode,
source_viewport: Option<(f32, f32, f32, f32)>,
},
Shader {
shader: Arc<RuntimeShader>,
layer_pixel_rect: [f32; 4],
source_region: Option<(f32, f32, f32, f32)>,
source_logical_size: Option<(f32, f32)>,
substrate_regions: SubstrateRegions,
rounded_mask: Option<RoundedCompositeMask>,
alpha: f32,
},
Projective {
dest_quad: [[f32; 2]; 4],
inverse: [[f32; 3]; 3],
alpha: f32,
blend_mode: BlendMode,
sample_mode: CompositeSampleMode,
},
}
pub(crate) struct PassSegment<'a> {
pub(crate) scene: &'a CompositorScene,
pub(crate) ops: &'a [DrawOp],
pub(crate) composites: &'a [ResolvedComposite],
pub(crate) offset: [f32; 2],
pub(crate) scissor: Option<(u32, u32, u32, u32)>,
pub(crate) first_run_window: Option<std::ops::Range<u32>>,
}
enum Item<'a> {
Run(&'a RunDraw, Option<std::ops::Range<u32>>),
Image(usize),
Text(&'a TextDraw),
Composite(&'a ResolvedComposite),
}
enum Batch<'a> {
StoreRun {
batch: StoreRunBatch,
scissor: Option<(u32, u32, u32, u32)>,
},
Arena {
chunk: usize,
uniform_slot: usize,
draws: Vec<RunDrawCall>,
scissor: Option<(u32, u32, u32, u32)>,
},
Images {
cmds: std::ops::Range<usize>,
blend_mode: BlendMode,
uniform_slot: usize,
scissor: Option<(u32, u32, u32, u32)>,
},
Glyphs {
cmds: std::ops::Range<usize>,
uniform_slot: usize,
scissor: Option<(u32, u32, u32, u32)>,
},
Composite(PreparedCompositeDraw<'a>),
Shader(PreparedShaderDraw<'a>),
Projective(PreparedProjectiveComposite<'a>),
}
pub(crate) fn scissor_in_target(
scissor: (f32, f32, f32, f32),
target_size: (u32, u32),
segment_offset: [f32; 2],
) -> Option<(u32, u32, u32, u32)> {
let (x, y, width, height) = scissor;
let left = (x - segment_offset[0]).floor().max(0.0);
let top = (y - segment_offset[1]).floor().max(0.0);
let right = (x + width - segment_offset[0])
.ceil()
.min(target_size.0 as f32);
let bottom = (y + height - segment_offset[1])
.ceil()
.min(target_size.1 as f32);
if right <= left || bottom <= top {
return None;
}
Some((
left as u32,
top as u32,
(right - left) as u32,
(bottom - top) as u32,
))
}
fn intersect_scissors(
a: Option<(u32, u32, u32, u32)>,
b: Option<(u32, u32, u32, u32)>,
) -> Option<Option<(u32, u32, u32, u32)>> {
match (a, b) {
(None, None) => Some(None),
(Some(rect), None) | (None, Some(rect)) => Some(Some(rect)),
(Some((ax, ay, aw, ah)), Some((bx, by, bw, bh))) => {
let left = ax.max(bx);
let top = ay.max(by);
let right = (ax + aw).min(bx + bw);
let bottom = (ay + ah).min(by + bh);
(right > left && bottom > top).then(|| Some((left, top, right - left, bottom - top)))
}
}
}
fn dest_in_target(dest: (f32, f32, f32, f32), segment_offset: [f32; 2]) -> (f32, f32, f32, f32) {
(
dest.0 - segment_offset[0],
dest.1 - segment_offset[1],
dest.2,
dest.3,
)
}
fn mask_in_target(
mask: Option<RoundedCompositeMask>,
segment_offset: [f32; 2],
) -> Option<RoundedCompositeMask> {
mask.map(|mask| RoundedCompositeMask {
rect: [
mask.rect[0] - segment_offset[0],
mask.rect[1] - segment_offset[1],
mask.rect[2],
mask.rect[3],
],
radii: mask.radii,
})
}
fn composite_visible(
composite: &ResolvedComposite,
target_size: (u32, u32),
segment_offset: [f32; 2],
segment_scissor: Option<(u32, u32, u32, u32)>,
) -> bool {
let (x, y, width, height) = dest_in_target(composite.dest, segment_offset);
let (left, top, right, bottom) = match segment_scissor {
Some((sx, sy, sw, sh)) => (sx as f32, sy as f32, (sx + sw) as f32, (sy + sh) as f32),
None => (0.0, 0.0, target_size.0 as f32, target_size.1 as f32),
};
if x >= right || y >= bottom || x + width <= left || y + height <= top {
return false;
}
composite
.scissor
.is_none_or(|scissor| scissor_in_target(scissor, target_size, segment_offset).is_some())
}
impl GpuRenderer {
pub(crate) fn encode_pass<'s, C: FrameCommandRecorder>(
&mut self,
recorder: &mut C,
target: PassTarget<'_>,
segments: &'s [PassSegment<'s>],
load_op: wgpu::LoadOp<wgpu::Color>,
root_scale: f32,
label: &'static str,
) -> Result<bool, String> {
let mut scratch = self.take_pass_scratch();
let device = self.device.clone();
let mut prep = PassPrep {
recorder,
device: &device,
target,
root_scale,
load_op,
batches: Vec::new(),
};
let prepared = segments
.iter()
.try_for_each(|segment| prep.segment(self, segment, &mut scratch));
let batches = prep.batches;
let image_slot = match &prepared {
Ok(()) if !scratch.image_indices.is_empty() => Some(self.upload_image_slot(
recorder,
&scratch.image_vertices,
&scratch.image_indices,
)),
_ => None,
};
let result = match prepared {
Err(error) => Err(error),
Ok(()) if batches.is_empty() => {
if matches!(load_op, wgpu::LoadOp::Clear(_)) {
self.clear_target(recorder, target.view, load_op);
}
Ok(false)
}
Ok(()) => {
let composite_draws = batches
.iter()
.filter(|batch| matches!(batch, Batch::Composite(_) | Batch::Shader(_)))
.count() as u32;
if composite_draws > 0 {
self.effect_renderer.record_composite_pass();
self.frame_stats.add_draw_calls(composite_draws);
}
let draw_result = {
let mut pass = recorder.begin_color_pass(label, target.view, load_op);
self.draw_batches(
&mut pass,
(target.width, target.height),
&batches,
&scratch.image_cmds,
&scratch.glyph_cmds,
image_slot.as_ref(),
)
};
recorder.record_pass();
draw_result.map(|()| true)
}
};
drop(batches);
self.return_pass_scratch(scratch);
result
}
fn take_pass_scratch(&mut self) -> PassScratch {
let mut scratch = PassScratch {
image_vertices: std::mem::take(&mut self.scratch_image_vertices),
image_indices: std::mem::take(&mut self.scratch_image_indices),
image_cmds: std::mem::take(&mut self.scratch_image_cmds),
glyph_cmds: std::mem::take(&mut self.scratch_glyph_cmds),
};
scratch.image_vertices.clear();
scratch.image_indices.clear();
scratch.image_cmds.clear();
scratch.glyph_cmds.clear();
scratch
}
fn return_pass_scratch(&mut self, scratch: PassScratch) {
self.scratch_image_vertices = scratch.image_vertices;
self.scratch_image_indices = scratch.image_indices;
self.scratch_image_cmds = scratch.image_cmds;
self.scratch_glyph_cmds = scratch.glyph_cmds;
}
fn draw_batches(
&mut self,
pass: &mut wgpu::RenderPass<'_>,
target_size: (u32, u32),
batches: &[Batch<'_>],
image_cmds: &[crate::render::ImageDrawCmd],
glyph_cmds: &[crate::render::GlyphDrawCmd],
image_slot: Option<&crate::render::ImageSlot>,
) -> Result<(), String> {
for batch in batches {
match batch {
Batch::StoreRun { batch, scissor } => {
self.draw_store_run(pass, batch, target_size, *scissor)?;
}
Batch::Arena {
chunk,
uniform_slot,
draws,
scissor,
} => {
self.draw_arena(pass, *chunk, *uniform_slot, draws, target_size, *scissor)?;
}
Batch::Images {
cmds,
blend_mode,
uniform_slot,
scissor,
} => {
let slot = image_slot
.ok_or_else(|| "image batch without an image slot".to_string())?;
self.draw_image_cmds(
pass,
slot,
*uniform_slot,
&image_cmds[cmds.clone()],
*blend_mode,
*scissor,
)?;
}
Batch::Glyphs {
cmds,
uniform_slot,
scissor,
} => {
self.draw_glyph_cmds(
pass,
image_slot,
*uniform_slot,
&glyph_cmds[cmds.clone()],
*scissor,
)?;
}
Batch::Composite(prepared) => {
self.effect_renderer
.draw_prepared_composite(pass, target_size, prepared);
}
Batch::Shader(prepared) => {
self.effect_renderer
.draw_prepared_shader_src_over(pass, target_size, prepared);
}
Batch::Projective(prepared) => {
self.effect_renderer.draw_prepared_projective_composite(
pass,
target_size,
prepared,
);
}
}
}
Ok(())
}
}
pub(crate) fn op_draw_bounds(
scene: &CompositorScene,
op: &DrawOp,
root_scale: f32,
) -> Option<Rect> {
match op.kind {
DrawOpKind::Run(index) => run_draw_bounds(&scene.runs[index], root_scale),
DrawOpKind::Image(index) => image_draw_bounds(&scene.images[index], root_scale),
DrawOpKind::Text(index) => text_draw_bounds(&scene.texts[index], root_scale),
DrawOpKind::Shadow(index) => {
let shadow = &scene.shadow_draws[index];
if shadow.requires_surface() {
return None;
}
shadow_caster_bounds(shadow, root_scale)
.into_iter()
.chain(
shadow
.texts
.iter()
.filter_map(|text| text_draw_bounds(text, root_scale)),
)
.reduce(|a, b| a.union(b))
}
}
}
fn shadow_caster_bounds(shadow: &crate::scene::ShadowDraw, root_scale: f32) -> Option<Rect> {
shadow
.shapes
.as_ref()
.and_then(|run| run_draw_bounds(run, root_scale))
}
pub(crate) fn op_is_visible_in_rect(
scene: &CompositorScene,
op: &DrawOp,
viewport_rect: Rect,
root_scale: f32,
) -> bool {
op_draw_bounds(scene, op, root_scale)
.is_some_and(|bounds| bounds.intersect(viewport_rect).is_some())
}
fn segment_viewport_rect(
target: PassTarget<'_>,
segment: &PassSegment<'_>,
root_scale: f32,
) -> Rect {
match segment.scissor {
Some((x, y, width, height)) => PassTarget {
offset: [segment.offset[0] + x as f32, segment.offset[1] + y as f32],
width,
height,
..target
},
None => PassTarget {
offset: segment.offset,
..target
},
}
.logical_rect(root_scale)
}
pub(crate) fn segment_draws_anything(
target: PassTarget<'_>,
segment: &PassSegment<'_>,
root_scale: f32,
) -> bool {
let viewport_rect = segment_viewport_rect(target, segment, root_scale);
merge_items(
segment,
viewport_rect,
root_scale,
(target.width, target.height),
false,
)
.next()
.is_some()
}
fn translate_inverse(inverse: [[f32; 3]; 3], offset: [f32; 2]) -> [[f32; 3]; 3] {
let mut shifted = inverse;
for row in &mut shifted {
row[2] += row[0] * offset[0] + row[1] * offset[1];
}
shifted
}
fn unshadowed_item<'a>(
scene: &'a CompositorScene,
kind: DrawOpKind,
op_index: usize,
first_run_window: &Option<std::ops::Range<u32>>,
skip_text: bool,
) -> Option<Item<'a>> {
match kind {
DrawOpKind::Run(index) => {
let run = &scene.runs[index];
run_has_shapes(run).then(|| {
let window = (op_index == 0).then(|| first_run_window.clone()).flatten();
Item::Run(run, window)
})
}
DrawOpKind::Image(index) => Some(Item::Image(index)),
DrawOpKind::Text(_) if skip_text => None,
DrawOpKind::Text(index) => Some(Item::Text(&scene.texts[index])),
DrawOpKind::Shadow(_) => None,
}
}
fn merge_items<'a>(
segment: &PassSegment<'a>,
viewport_rect: Rect,
root_scale: f32,
target_size: (u32, u32),
skip_text: bool,
) -> impl Iterator<Item = Item<'a>> + use<'a> {
let scene = segment.scene;
let mut ops = segment.ops.iter().enumerate().peekable();
let mut composites = segment.composites.iter().peekable();
let mut shadow_texts: std::slice::Iter<'a, TextDraw> = [].iter();
let offset = segment.offset;
let scissor = segment.scissor;
let first_run_window = segment.first_run_window.clone();
std::iter::from_fn(move || {
loop {
if let Some(text) = shadow_texts
.find(|text| text_draw_is_visible_in_rect(text, viewport_rect, root_scale))
{
return Some(Item::Text(text));
}
let next_z = ops.peek().map(|(_, op)| op.z_index);
if composites
.peek()
.is_some_and(|composite| next_z.is_none_or(|z| composite.z_index <= z))
{
let composite = composites.next().expect("peeked composite");
if composite_visible(composite, target_size, offset, scissor) {
return Some(Item::Composite(composite));
}
continue;
}
let (op_index, op) = ops.next()?;
if !op_is_visible_in_rect(scene, op, viewport_rect, root_scale) {
continue;
}
if let DrawOpKind::Shadow(index) = op.kind {
let shadow = &scene.shadow_draws[index];
shadow_texts = shadow.texts.iter();
if let Some(run) = unblurred_shadow_run(shadow, viewport_rect, root_scale) {
return Some(Item::Run(run, None));
}
continue;
}
if let Some(item) =
unshadowed_item(scene, op.kind, op_index, &first_run_window, skip_text)
{
return Some(item);
}
}
})
}
fn unblurred_shadow_run(
shadow: &crate::scene::ShadowDraw,
viewport_rect: Rect,
root_scale: f32,
) -> Option<&RunDraw> {
let run = shadow.shapes.as_ref()?;
run_draw_is_visible_in_rect(run, viewport_rect, root_scale).then_some(run)
}
struct PassScratch {
image_vertices: Vec<crate::render::Vertex>,
image_indices: Vec<u32>,
image_cmds: Vec<crate::render::ImageDrawCmd>,
glyph_cmds: Vec<crate::render::GlyphDrawCmd>,
}
struct PassPrep<'a, 's, C> {
recorder: &'a mut C,
device: &'a wgpu::Device,
target: PassTarget<'a>,
root_scale: f32,
load_op: wgpu::LoadOp<wgpu::Color>,
batches: Vec<Batch<'s>>,
}
impl<'s, C: FrameCommandRecorder> PassPrep<'_, 's, C> {
fn target_size(&self) -> (u32, u32) {
(self.target.width, self.target.height)
}
fn segment(
&mut self,
renderer: &mut GpuRenderer,
segment: &PassSegment<'s>,
scratch: &mut PassScratch,
) -> Result<(), String> {
let viewport = ViewportUniformParams {
width: self.target.width,
height: self.target.height,
offset: segment.offset,
};
let viewport_rect = segment_viewport_rect(self.target, segment, self.root_scale);
let uniform_slot = renderer.claim_uniform_slot(viewport);
let mut items = merge_items(
segment,
viewport_rect,
self.root_scale,
self.target_size(),
renderer.ablation.text,
)
.peekable();
let run = SegmentRun {
segment,
viewport,
uniform_slot,
};
while let Some(item) = items.peek() {
match item {
Item::Run(..) => {
self.run_items(renderer, &mut items, &run);
continue;
}
Item::Image(_) => {
self.image_run(renderer, &mut items, &run, scratch)?;
continue;
}
Item::Text(text) => self.text_item(renderer, text, &run, scratch)?,
Item::Composite(composite) => self.composite_item(renderer, composite, &run)?,
}
items.next();
}
Ok(())
}
fn run_items(
&mut self,
renderer: &mut GpuRenderer,
items: &mut Peekable<impl Iterator<Item = Item<'s>>>,
run: &SegmentRun<'s, '_>,
) {
let mut chunk: Option<usize> = None;
let close = |renderer: &mut GpuRenderer,
chunk: &mut Option<usize>,
batches: &mut Vec<Batch<'s>>| {
if let Some(open) = chunk.take() {
let draws = renderer.close_arena(open);
if !draws.is_empty() {
batches.push(Batch::Arena {
chunk: open,
uniform_slot: run.uniform_slot,
draws,
scissor: run.segment.scissor,
});
}
}
};
while let Some(Item::Run(draw, window)) =
items.next_if(|item| matches!(item, Item::Run(..)))
{
let window = window.unwrap_or(0..u32::MAX);
if renderer.run_is_stored(draw) {
close(renderer, &mut chunk, &mut self.batches);
let batch = renderer.prepare_store_run(
self.recorder,
draw,
run.viewport,
self.root_scale,
&window,
);
self.batches.push(Batch::StoreRun {
batch,
scissor: run.segment.scissor,
});
} else {
let total = draw.record_count().min(window.end);
let mut from = window.start;
while from < total {
if chunk.is_some_and(|open| !renderer.arena_accepts(open, draw)) {
close(renderer, &mut chunk, &mut self.batches);
}
let open = *chunk.get_or_insert_with(|| renderer.open_arena());
let taken = renderer.append_arena_run(open, draw, from..total, self.root_scale);
if taken == 0 {
close(renderer, &mut chunk, &mut self.batches);
continue;
}
from += taken;
}
}
}
close(renderer, &mut chunk, &mut self.batches);
}
fn image_run(
&mut self,
renderer: &mut GpuRenderer,
items: &mut Peekable<impl Iterator<Item = Item<'s>>>,
run: &SegmentRun<'s, '_>,
scratch: &mut PassScratch,
) -> Result<(), String> {
let Some(Item::Image(first)) = items.peek() else {
unreachable!("image run starts at an image");
};
let images = &run.segment.scene.images;
let blend_mode = supported_blend_mode(images[*first].blend_mode);
let cmd_start = scratch.image_cmds.len();
while let Some(Item::Image(index)) = items.next_if(|item| {
matches!(item, Item::Image(index) if supported_blend_mode(images[*index].blend_mode) == blend_mode)
}) {
let image = &images[index];
renderer.append_image_draw_cmd(
image,
run.viewport,
self.root_scale,
&mut scratch.image_vertices,
&mut scratch.image_indices,
&mut scratch.image_cmds,
)?;
}
if cmd_start < scratch.image_cmds.len() {
self.batches.push(Batch::Images {
cmds: cmd_start..scratch.image_cmds.len(),
blend_mode,
uniform_slot: run.uniform_slot,
scissor: run.segment.scissor,
});
}
Ok(())
}
fn text_item(
&mut self,
renderer: &mut GpuRenderer,
text: &'s TextDraw,
run: &SegmentRun<'s, '_>,
scratch: &mut PassScratch,
) -> Result<(), String> {
let glyph_start = scratch.glyph_cmds.len();
let drew_glyphs = renderer.append_text_glyph_draws(
std::iter::once(text),
run.viewport,
self.root_scale,
&mut scratch.image_vertices,
&mut scratch.image_indices,
&mut scratch.glyph_cmds,
)?;
if drew_glyphs {
if glyph_start < scratch.glyph_cmds.len() {
match self.batches.last_mut() {
Some(Batch::Glyphs {
cmds,
uniform_slot: slot,
..
}) if cmds.end == glyph_start && *slot == run.uniform_slot => {
cmds.end = scratch.glyph_cmds.len();
}
_ => self.batches.push(Batch::Glyphs {
cmds: glyph_start..scratch.glyph_cmds.len(),
uniform_slot: run.uniform_slot,
scissor: run.segment.scissor,
}),
}
}
return Ok(());
}
let cmd_start = scratch.image_cmds.len();
renderer.append_text_image_draw_cmds(
std::iter::once(text),
run.viewport,
self.root_scale,
&mut scratch.image_vertices,
&mut scratch.image_indices,
&mut scratch.image_cmds,
)?;
if cmd_start < scratch.image_cmds.len() {
match self.batches.last_mut() {
Some(Batch::Images {
cmds,
blend_mode,
uniform_slot: slot,
..
}) if cmds.end == cmd_start
&& *blend_mode == BlendMode::SrcOver
&& *slot == run.uniform_slot =>
{
cmds.end = scratch.image_cmds.len();
}
_ => self.batches.push(Batch::Images {
cmds: cmd_start..scratch.image_cmds.len(),
blend_mode: BlendMode::SrcOver,
uniform_slot: run.uniform_slot,
scissor: run.segment.scissor,
}),
}
}
Ok(())
}
fn composite_item(
&mut self,
renderer: &mut GpuRenderer,
composite: &'s ResolvedComposite,
run: &SegmentRun<'s, '_>,
) -> Result<(), String> {
let offset = run.segment.offset;
let own_scissor = composite
.scissor
.and_then(|scissor| scissor_in_target(scissor, self.target_size(), offset));
if composite.scissor.is_some() && own_scissor.is_none() {
return Ok(());
}
let Some(scissor) = intersect_scissors(own_scissor, run.segment.scissor) else {
return Ok(());
};
let dest = dest_in_target(composite.dest, offset);
match &composite.kind {
ResolvedCompositeKind::Blit {
alpha,
blend_mode,
rounded_mask,
sample_mode,
source_viewport,
} => {
let item = CompositeBatchItem {
source: composite.source.as_ref(),
alpha: *alpha,
scissor,
rounded_mask: mask_in_target(*rounded_mask, offset),
blend_mode: supported_blend_mode(*blend_mode),
dest_viewport: Some(dest),
source_viewport: *source_viewport,
sample_mode: *sample_mode,
};
let prepared = renderer.effect_renderer.prepare_composite_draw(
self.recorder,
self.device,
self.load_op,
&item,
);
self.batches.push(Batch::Composite(prepared));
}
ResolvedCompositeKind::Shader {
shader,
layer_pixel_rect,
source_region,
source_logical_size,
substrate_regions,
rounded_mask,
alpha,
} => {
let item = ShaderCompositeBatchItem {
source: composite.source.as_ref(),
shader: shader.as_ref(),
layer_pixel_rect: *layer_pixel_rect,
source_region: *source_region,
source_logical_size: *source_logical_size,
substrate_regions: *substrate_regions,
rounded_mask: mask_in_target(*rounded_mask, offset),
alpha: *alpha,
scissor,
dest_viewport: dest,
};
let prepared = renderer
.effect_renderer
.prepare_shader_draw(self.recorder, self.device, &item)
.ok_or_else(|| "shader composite preparation failed".to_string())?;
self.batches.push(Batch::Shader(prepared));
}
ResolvedCompositeKind::Projective {
dest_quad,
inverse,
alpha,
blend_mode,
sample_mode,
} => {
let item = ProjectiveCompositeItem {
source: composite.source.as_ref(),
viewport: self.target_size(),
dest_quad: dest_quad.map(|[x, y]| [x - offset[0], y - offset[1]]),
inverse: translate_inverse(*inverse, offset),
alpha: *alpha,
blend_mode: supported_blend_mode(*blend_mode),
sample_mode: *sample_mode,
scissor,
};
let prepared = renderer.effect_renderer.prepare_projective_composite_draw(
self.recorder,
self.device,
&item,
);
self.batches.push(Batch::Projective(prepared));
}
}
Ok(())
}
}
struct SegmentRun<'s, 'a> {
segment: &'a PassSegment<'s>,
viewport: ViewportUniformParams,
uniform_slot: usize,
}
#[cfg(test)]
mod tests {
use cranpose_ui_graphics::{Brush, Color, DrawPrimitive, Point, ShapeRecorder};
use super::*;
use crate::scene::{Placement, ShadowDraw};
#[test]
fn shadow_run_items_preserve_geometry_culling_and_first_run_window() {
let run = |x| {
let mut recorder = ShapeRecorder::default();
for y in [0.0, 8.0] {
recorder.push_primitive(DrawPrimitive::Rect {
rect: Rect {
x,
y,
width: 8.0,
height: 8.0,
},
brush: Brush::solid(Color::WHITE),
stroke: None,
});
}
RunDraw::whole(
std::sync::Arc::new(recorder),
Placement::at(Point::default(), None, None),
)
.unwrap()
};
let mut scene = CompositorScene::new();
scene.push_run(run(0.0));
for x in [16.0, 128.0] {
scene.push_shadow_draw(ShadowDraw {
shapes: Some(run(x)),
post_blur_cutouts: None,
texts: Vec::new(),
blur_radius: 0.0,
clip: None,
rounded_clip: None,
occluder: None,
z_index: 0,
});
}
let segment = PassSegment {
scene: &scene,
ops: &scene.draw_ops,
composites: &[],
offset: [0.0, 0.0],
scissor: None,
first_run_window: Some(1..2),
};
let items = merge_items(
&segment,
Rect {
x: 0.0,
y: 0.0,
width: 64.0,
height: 64.0,
},
1.0,
(64, 64),
false,
)
.collect::<Vec<_>>();
assert_eq!(items.len(), 2);
let Item::Run(ordinary, window) = &items[0] else {
panic!("expected the ordinary run")
};
assert!(std::ptr::eq(*ordinary, &scene.runs[0]));
assert_eq!(*window, Some(1..2));
let Item::Run(shadow, window) = &items[1] else {
panic!("expected the visible shadow run")
};
assert!(std::ptr::eq(
*shadow,
scene.shadow_draws[0].shapes.as_ref().unwrap()
));
assert_eq!(*window, None);
assert_eq!(shadow.record_count(), 2);
for (x, expected) in [(16.0, Some(0)), (128.0, Some(1)), (256.0, None)] {
let mut visible = merge_items(
&segment,
Rect {
x,
y: 0.0,
width: 8.0,
height: 16.0,
},
1.0,
(64, 64),
false,
);
match (visible.next(), expected) {
(Some(Item::Run(run, None)), Some(index)) => assert!(std::ptr::eq(
run,
scene.shadow_draws[index].shapes.as_ref().unwrap()
)),
(None, None) => {}
_ => panic!("incorrect first visible shadow at x={x}"),
}
assert!(visible.next().is_none());
}
}
}