use std::sync::{Arc, Mutex};
use cranpose_ui_graphics::{
BRUSH_KIND_LINEAR, BlendMode, FRAGMENT_KIND_FILL, RecordLane, RecordSegment,
};
use super::{KeyedBuild, ShapePipelines};
use crate::{
ablation::ShapeAblation,
pipeline_compiler::PipelineCompiler,
pipeline_recorder::PipelineRecorder,
render::{RunTier, SegmentClip, ShapeDepth, ShapePipelineKey, ShapeTurns, ShapeVariant},
};
struct ListingBuilder(Arc<Mutex<Vec<ShapePipelineKey>>>);
impl KeyedBuild for ListingBuilder {
type Output = ShapePipelineKey;
fn build(&self, key: ShapePipelineKey) -> ShapePipelineKey {
self.0.lock().expect("built keys").push(key);
key
}
}
fn pipelines() -> (
ShapePipelines<ListingBuilder>,
Arc<Mutex<Vec<ShapePipelineKey>>>,
) {
let built = Arc::new(Mutex::new(Vec::new()));
let pipelines = ShapePipelines::new(
ListingBuilder(Arc::clone(&built)),
wgpu::Backend::Vulkan,
&PipelineCompiler::spawn(wgpu::Backend::Vulkan),
[],
PipelineRecorder::default(),
);
(pipelines, built)
}
fn fill(tier: RunTier, gradient: bool) -> ShapePipelineKey {
let segment = RecordSegment {
lane: RecordLane::Shapes,
start: 0,
count: 1,
blend: BlendMode::SrcOver,
gradient,
vertex_gradient: false,
brushes: if gradient { 1 << BRUSH_KIND_LINEAR } else { 1 },
kinds: 1 << FRAGMENT_KIND_FILL,
band_class: 0,
interiors: false,
occluders: false,
bare_interiors: false,
};
ShapePipelineKey {
blend_mode: BlendMode::SrcOver,
tier,
variant: ShapeVariant::of_segment(
&segment,
SegmentClip::Tested,
ShapeAblation::default(),
false,
(true, true),
),
turns: ShapeTurns::None,
depth: ShapeDepth::Off,
}
}
fn built(list: &Mutex<Vec<ShapePipelineKey>>) -> Vec<ShapePipelineKey> {
list.lock().expect("built keys").clone()
}
#[test]
fn draws_build_their_own_pipelines_until_a_general_one_would_pay() {
let (mut pipelines, list) = pipelines();
let keys = [
fill(RunTier::Store, false),
fill(RunTier::Arena, false),
fill(RunTier::Store, true),
fill(RunTier::Arena, true),
];
let general = keys[0].general();
assert!(
keys.iter()
.all(|key| key.general() == general && *key != general),
"the keys share one general pipeline: {keys:?}"
);
pipelines.ensure(keys[0], 6);
pipelines.ensure(keys[1], 6);
assert_eq!(built(&list), keys[..2]);
assert_eq!(pipelines.get(keys[1]), Some((&keys[1], false)));
pipelines.ensure(keys[2], 6);
assert_eq!(built(&list), [keys[0], keys[1], general]);
assert_eq!(pipelines.get(keys[2]), Some((&general, true)));
pipelines.ensure(keys[3], 6);
assert_eq!(built(&list).len(), 3, "the built general stands in at once");
assert_eq!(pipelines.get(keys[3]), Some((&general, true)));
}
struct BrowserBuilder {
built: KeyList,
landed: KeyList,
}
impl KeyedBuild for BrowserBuilder {
type Output = (ShapePipelineKey, KeyList);
fn build(&self, key: ShapePipelineKey) -> Self::Output {
self.built.lock().expect("built keys").push(key);
(key, Arc::clone(&self.landed))
}
fn landed((key, landed): &Self::Output) -> bool {
landed.lock().expect("landed keys").contains(key)
}
}
type KeyList = Arc<Mutex<Vec<ShapePipelineKey>>>;
fn browser_pipelines() -> (ShapePipelines<BrowserBuilder>, KeyList, KeyList) {
let built = Arc::new(Mutex::new(Vec::new()));
let landed = Arc::new(Mutex::new(Vec::new()));
let pipelines = ShapePipelines::new(
BrowserBuilder {
built: Arc::clone(&built),
landed: Arc::clone(&landed),
},
wgpu::Backend::BrowserWebGpu,
&PipelineCompiler::spawn(wgpu::Backend::BrowserWebGpu),
[],
PipelineRecorder::default(),
);
(pipelines, built, landed)
}
#[test]
fn browser_draws_take_the_universal_pipeline_until_their_own_compiled_in_the_background() {
let (mut pipelines, list, landed) = browser_pipelines();
let plain = fill(RunTier::Arena, false);
let gradient = fill(RunTier::Arena, true);
let general = plain.universal();
assert_eq!(general, gradient.universal());
pipelines.ensure(plain, 4);
pipelines.ensure(gradient, 4);
pipelines.request_wanted();
assert_eq!(
built(&list),
vec![general],
"the first screen compiles one universal pipeline for both"
);
let drawn = |pipelines: &ShapePipelines<BrowserBuilder>, key| {
pipelines
.get(key)
.map(|((built, _), fallback)| (*built, fallback))
};
assert_eq!(drawn(&pipelines, plain), Some((general, true)));
pipelines.begin_frame();
pipelines.ensure(plain, 4);
pipelines.ensure(gradient, 4);
pipelines.build_in_background();
pipelines.request_wanted();
assert_eq!(
built(&list),
vec![general, plain, gradient],
"once the browser compiles in the background, each specialized one builds"
);
assert_eq!(
drawn(&pipelines, plain),
Some((general, true)),
"until it compiled, the universal one draws"
);
landed.lock().expect("landed keys").push(plain);
assert_eq!(drawn(&pipelines, plain), Some((plain, false)));
assert_eq!(drawn(&pipelines, gradient), Some((general, true)));
}
#[test]
fn a_browser_builds_the_universal_pipeline_for_a_recorded_first_screen() {
let (mut pipelines, list, _) = browser_pipelines();
let plain = fill(RunTier::Arena, false);
pipelines.build_ahead(plain);
assert_eq!(built(&list), vec![plain.universal()]);
}