use std::sync::Arc;
use std::time::{Duration, Instant};
use super::super::testing::LiveImageMirror;
use super::super::*;
use super::PARK_MAX_TEXTURE;
use crate::geometry::{Point, Rect};
use crate::wake::{CountingWaker, RedrawWaker, WakeKind};
use crate::{Canvas, DrawCommand, RenderFrame};
fn pixels(w: u32, h: u32, seed: u8) -> Vec<u8> {
let mut px = Vec::with_capacity((w * h * 4) as usize);
for y in 0..h {
for x in 0..w {
px.extend_from_slice(&[x as u8, y as u8, seed, 255]);
}
}
px
}
fn live(w: u32, h: u32) -> (LiveImageSource, LiveImageWriter) {
let source = LiveImageSource::builder(LivePixelFormat::Rgba8)
.label("pass")
.build();
let writer = source.writer();
writer
.write_frame(w, h, &pixels(w, h, 0), (w * 4) as usize)
.unwrap();
(source, writer)
}
fn widget(source: &LiveImageSource) -> (Arc<CountingWaker>, LiveImageConsumer) {
let waker = Arc::new(CountingWaker::new());
let consumer = source.attach(Some(waker.clone() as Arc<dyn RedrawWaker>));
(waker, consumer)
}
fn lay_out(consumer: &LiveImageConsumer) {
let _ = consumer.take_geometry();
consumer.record_layout_meta(consumer.source().meta());
}
fn draw() -> LiveImageDraw {
LiveImageDraw::new(
Rect::new(0.0, 0.0, 64.0, 64.0),
Rect::new(0.0, 0.0, 64.0, 64.0),
)
}
fn frame(consumers: &[&LiveImageConsumer]) -> RenderFrame {
frame_with(consumers, draw())
}
fn frame_with(consumers: &[&LiveImageConsumer], d: LiveImageDraw) -> RenderFrame {
let mut canvas = Canvas::new();
for consumer in consumers {
lay_out(consumer);
canvas.draw_live_image(consumer, &d);
}
canvas.into_render_frame()
}
fn texture(mirror: &LiveImageMirror, source: &LiveImageSource) -> Vec<u8> {
mirror
.pixels(source.id())
.map(|(_, _, p)| p.to_vec())
.expect("a texture")
}
fn current(source: &LiveImageSource) -> Vec<u8> {
let (_, consumer) = widget(source);
let read = match consumer.try_read() {
super::super::internal::ReadAttempt::Locked(read) => read,
super::super::internal::ReadAttempt::Busy => panic!("locked"),
};
let (w, h) = read.size().expect("a buffer");
let mut tight = Vec::with_capacity((w * h * 4) as usize);
for y in 0..h as usize {
tight.extend_from_slice(
&read.pixels()[y * read.stride()..y * read.stride() + w as usize * 4],
);
}
tight
}
#[test]
fn the_first_frame_uploads_whole_and_draws_it() {
let (source, _writer) = live(8, 6);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!((report.full_uploads, report.partial_uploads), (1, 0));
assert_eq!(report.bytes, 8 * 6 * 4);
assert_eq!(texture(&mirror, &source), pixels(8, 6, 0));
let d = mirror.decisions()[0];
assert!(d.draw && !d.deferred);
assert_eq!(d.generation, source.generation());
let s = consumer.stats().attachment;
assert_eq!((s.frames_drawn, s.uploads, s.window_generation), (1, 1, 1));
assert!(s.observed);
assert_eq!(source.displayed_generation(), 1);
}
#[test]
fn a_pixel_commit_uploads_only_its_rects_and_an_idle_frame_nothing() {
let (source, writer) = live(16, 16);
let (waker, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
writer
.write_rect(PixelRect::new(2, 3, 2, 2), &[9; 16], 8)
.unwrap();
assert_eq!(waker.count_of(WakeKind::Draw), 1);
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!((report.full_uploads, report.partial_uploads), (0, 1));
assert_eq!(report.rects, vec![PixelRect::new(2, 3, 2, 2)]);
assert_eq!(report.bytes, 2 * 2 * 4);
assert_eq!(texture(&mirror, &source), current(&source));
let guard = writer.lock().unwrap();
let idle = mirror.consume(&frame(&[&consumer]));
assert_eq!(idle, Default::default());
drop(guard);
assert_eq!(mirror.stats().contended, 0);
}
#[test]
fn a_frame_that_has_not_seen_a_commit_wakes_again_after_it() {
let (source, writer) = live(4, 4);
let (waker, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
for _ in 0..100 {
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.unwrap();
}
assert_eq!(waker.count(), 1, "a hundred commits, one wake");
mirror.consume(&frame(&[&consumer]));
writer
.write_rect(PixelRect::new(1, 0, 1, 1), &[1; 4], 4)
.unwrap();
assert_eq!(
waker.count(),
2,
"the frame took the flag: the next commit wakes"
);
}
#[test]
fn a_source_larger_than_the_device_draws_background_and_wakes_no_more() {
let (source, writer) = live(65, 3);
let (waker, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.set_max_dimension(64);
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.full_uploads, 0);
assert_eq!(mirror.texture_count(), 0);
let d = mirror.decisions()[0];
assert!(!d.draw && d.deferred);
let before = waker.count();
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.unwrap();
assert_eq!(
waker.count(),
before,
"its pixel flag is parked: no redraw per commit"
);
}
#[test]
fn a_busy_lock_draws_the_held_texture_and_its_unlock_wakes() {
let (source, writer) = live(4, 4);
let (waker, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[7; 4], 4)
.unwrap();
let before = waker.count();
let (held, release) = (
Arc::new(std::sync::Barrier::new(2)),
Arc::new(std::sync::Barrier::new(2)),
);
let holder = {
let (writer, held, release) = (writer.clone(), held.clone(), release.clone());
std::thread::spawn(move || {
let guard = writer.lock().unwrap();
held.wait();
release.wait();
drop(guard);
})
};
held.wait();
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.contended, 1);
assert_eq!(report.deferred, 1);
assert!(mirror.decisions()[0].draw, "the held texture still draws");
assert_eq!(consumer.stats().attachment.deferred_frames, 1);
release.wait();
holder.join().unwrap();
assert_eq!(waker.count(), before + 1, "the unlock woke the window");
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.partial_uploads, 1);
assert_eq!(texture(&mirror, &source), current(&source));
}
#[test]
fn two_busy_frames_in_a_row_make_the_third_wait_for_the_lock() {
let (source, writer) = live(4, 4);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.set_refresh_interval(Duration::from_secs(5));
mirror.consume(&frame(&[&consumer]));
let committed = Arc::new(std::sync::Barrier::new(2));
let (go, done) = (
std::sync::mpsc::channel::<()>(),
std::sync::mpsc::channel::<()>(),
);
writer
.write_rect(PixelRect::new(2, 2, 1, 1), &[1; 4], 4)
.unwrap();
let producer = {
let (writer, committed) = (writer.clone(), committed.clone());
let (go, done) = (go.1, done.0);
std::thread::spawn(move || {
for _ in 0..2 {
let mut guard = writer.lock().unwrap();
guard.fill_rect(PixelRect::new(0, 0, 1, 1), [3; 4]).unwrap();
committed.wait();
go.recv().unwrap();
guard.commit();
}
let mut guard = writer.lock().unwrap();
guard.fill_rect(PixelRect::new(1, 1, 1, 1), [4; 4]).unwrap();
committed.wait();
std::thread::sleep(Duration::from_millis(5));
guard.commit();
done.send(()).unwrap();
})
};
for _ in 0..2 {
committed.wait();
mirror.consume(&frame(&[&consumer]));
go.0.send(()).unwrap();
}
assert_eq!(mirror.stats().contended, 2);
let waits = mirror.stats().blocking_waits;
committed.wait();
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(
mirror.stats().blocking_waits,
waits + 1,
"the third frame waited"
);
assert_eq!(report.contended, 0);
assert!(report.partial_uploads + report.full_uploads == 1);
done.1.recv().unwrap();
producer.join().unwrap();
}
#[test]
fn a_size_changed_after_layout_draws_the_held_texture() {
let (source, writer) = live(4, 4);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
let mut canvas = Canvas::new();
lay_out(&consumer);
canvas.draw_live_image(&consumer, &draw());
let f = canvas.into_render_frame();
writer.write_frame(5, 4, &pixels(5, 4, 1), 20).unwrap();
let report = mirror.consume(&f);
assert_eq!(mirror.stats().stale_deferrals, 1);
assert_eq!(report.deferred, 1);
assert!(mirror.decisions()[0].draw, "the 4 × 4 texture still draws");
assert_eq!(mirror.pixels(source.id()).unwrap().0, 4);
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.full_uploads, 1);
assert_eq!(texture(&mirror, &source), pixels(5, 4, 1));
assert_eq!(
mirror.texture_count(),
1,
"the old texture went with the swap"
);
}
#[test]
fn a_texture_staged_for_a_size_that_went_back_is_dropped_not_filled() {
let (source, writer) = live(8, 8);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
writer.write_frame(4, 4, &pixels(4, 4, 1), 16).unwrap();
let (held, release) = (
Arc::new(std::sync::Barrier::new(2)),
Arc::new(std::sync::Barrier::new(2)),
);
let holder = {
let (writer, held, release) = (writer.clone(), held.clone(), release.clone());
std::thread::spawn(move || {
let guard = writer.lock().unwrap();
held.wait();
release.wait();
drop(guard);
})
};
held.wait();
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.deferred, 1, "the 4 x 4 frame met the transaction");
assert_eq!(
mirror.texture_count(),
2,
"precondition: the 8 x 8 texture and a staged 4 x 4 one"
);
release.wait();
holder.join().unwrap();
writer.write_frame(8, 8, &pixels(8, 8, 2), 32).unwrap();
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.deferred, 0);
assert_eq!(texture(&mirror, &source), pixels(8, 8, 2));
assert_eq!(
mirror.texture_count(),
1,
"the staged 4 x 4 texture is gone, released"
);
}
fn timed(mirror: &LiveImageMirror) -> (usize, usize) {
let t = mirror.last_timings();
(t.lock_holds.len(), t.commit_to_upload.len())
}
#[test]
fn each_lock_hold_and_each_completed_upload_is_timed() {
let (source, writer) = live(16, 16);
let (_, consumer) = widget(&source);
let (other_source, _other_writer) = live(4, 4);
let (_, other) = widget(&other_source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer, &other]));
assert_eq!(
timed(&mirror),
(2, 2),
"two sources, each locked and uploaded"
);
mirror.consume(&frame(&[&consumer, &other]));
assert_eq!(timed(&mirror), (0, 0), "nothing new: no lock taken");
writer
.write_rect(PixelRect::new(0, 0, 2, 2), &[5; 16], 8)
.unwrap();
std::thread::sleep(Duration::from_millis(20));
mirror.consume(&frame(&[&consumer, &other]));
assert_eq!(timed(&mirror), (1, 1));
assert!(
mirror.last_timings().commit_to_upload[0] >= Duration::from_millis(20),
"from the commit, not from the frame: {:?}",
mirror.last_timings()
);
let mut canvas = Canvas::new();
lay_out(&consumer);
canvas.draw_live_image(&consumer, &draw());
let stale = canvas.into_render_frame();
writer.write_frame(16, 20, &pixels(16, 20, 1), 64).unwrap();
mirror.consume(&stale);
assert_eq!(
timed(&mirror),
(1, 0),
"a stale size is a hold without an upload"
);
mirror.set_staging_budget(16 * 4 * 5);
let mut partial = 0;
loop {
let report = mirror.consume(&frame(&[&consumer]));
if report.full_uploads == 1 {
assert_eq!(timed(&mirror), (1, 1), "the completing frame");
break;
}
assert_eq!(timed(&mirror), (1, 0), "a partial frame");
partial += 1;
assert!(partial < 10, "the fill makes progress");
}
assert_eq!(partial, 3);
writer.write_frame(16, 20, &pixels(16, 20, 2), 64).unwrap();
mirror.consume(&frame(&[&consumer]));
assert_eq!(timed(&mirror), (1, 0), "a plan too large for one frame");
}
#[test]
fn a_busy_lock_times_nothing() {
let (source, writer) = live(4, 4);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[7; 4], 4)
.unwrap();
let guard = writer.lock().unwrap();
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.contended, 1);
assert_eq!(timed(&mirror), (0, 0));
drop(guard);
}
#[test]
fn a_frame_without_the_source_frees_a_streaming_texture() {
let (source, writer) = live(4, 4);
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.unwrap();
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
assert_eq!(mirror.texture_count(), 1);
let report = mirror.consume(&RenderFrame::new());
assert_eq!(report.pruned, 1);
let s = mirror.stats();
assert_eq!(
(s.textures, s.textures_parked, s.bytes, s.bytes_parked),
(0, 0, 0, 0)
);
assert!(!source.is_observed(), "out of the frame: unobserved");
}
#[test]
fn a_small_one_commit_texture_parks_and_comes_back_without_an_upload() {
let (source, _writer) = live(8, 8);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
mirror.consume(&RenderFrame::new());
let s = mirror.stats();
assert_eq!(
(s.textures, s.textures_parked, s.bytes_parked),
(0, 1, 8 * 8 * 4)
);
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(
report.full_uploads + report.partial_uploads,
0,
"reused, not uploaded"
);
assert_eq!(texture(&mirror, &source), pixels(8, 8, 0));
assert_eq!(mirror.stats().textures_parked, 0);
}
#[test]
fn the_park_budget_of_zero_frees_at_once_and_a_dead_source_frees_its_texture() {
let (source, writer) = live(8, 8);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.set_park_budget(0);
mirror.consume(&frame(&[&consumer]));
mirror.consume(&RenderFrame::new());
assert_eq!(mirror.stats().textures_parked, 0, "strict release");
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
mirror.consume(&RenderFrame::new());
assert_eq!(mirror.stats().textures_parked, 1);
drop((consumer, writer, source));
mirror.consume(&RenderFrame::new());
assert_eq!(mirror.stats().textures_parked, 0, "its source is gone");
}
#[test]
fn the_pool_parks_only_small_textures_and_evicts_the_oldest_past_its_budget() {
let mut mirror = LiveImageMirror::new();
let side = ((PARK_MAX_TEXTURE / 4) as f64).sqrt() as u32 + 1;
let (big, _bw) = live(side, side);
let (_, big_c) = widget(&big);
mirror.consume(&frame(&[&big_c]));
mirror.consume(&RenderFrame::new());
assert_eq!(mirror.stats().textures_parked, 0);
let sources: Vec<_> = (0..5).map(|_| live(1024, 1024)).collect();
for (source, _) in &sources {
let (_, c) = widget(source);
mirror.consume(&frame(&[&c]));
mirror.consume(&RenderFrame::new());
}
let s = mirror.stats();
assert_eq!(s.textures_parked, 4);
assert_eq!(s.bytes_parked, 4 * 1024 * 1024 * 4);
let (_, first) = widget(&sources[0].0);
let report = mirror.consume(&frame(&[&first]));
assert_eq!(report.full_uploads, 1, "the oldest was evicted");
}
#[test]
fn a_paused_source_keeps_its_texture_and_parks_its_flag() {
let (source, writer) = live(4, 4);
let (waker, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
let paused = draw().paused(true);
let report = mirror.consume(&frame_with(&[&consumer], paused));
assert_eq!(report.paused, 1);
let before = waker.count();
for _ in 0..100 {
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[5; 4], 4)
.unwrap();
}
assert_eq!(waker.count(), before, "a paused window is not woken");
let report = mirror.consume(&frame_with(&[&consumer], paused));
assert_eq!(report.full_uploads + report.partial_uploads, 0);
assert_eq!(texture(&mirror, &source), pixels(4, 4, 0), "the held frame");
let s = consumer.stats();
assert!(s.attachment.paused);
assert_eq!(s.attachment.paused_frames, 2);
assert_eq!(s.source.paused_attachments, 1);
assert!(!source.is_displayed());
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!((report.full_uploads, report.partial_uploads), (1, 0));
assert_eq!(texture(&mirror, &source), current(&source));
assert!(!consumer.stats().attachment.paused);
}
#[test]
fn one_live_widget_keeps_a_shared_source_uploading() {
let (source, writer) = live(4, 4);
let waker = Arc::new(CountingWaker::new());
let a = source.attach(Some(waker.clone() as Arc<dyn RedrawWaker>));
let b = source.attach(Some(waker.clone() as Arc<dyn RedrawWaker>));
let mut mirror = LiveImageMirror::new();
let mixed = |mirror: &mut LiveImageMirror| {
let mut canvas = Canvas::new();
lay_out(&a);
lay_out(&b);
canvas.draw_live_image(&a, &draw().paused(true));
canvas.draw_live_image(&b, &draw());
mirror.consume(&canvas.into_render_frame())
};
assert_eq!(mixed(&mut mirror).full_uploads, 1);
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[5; 4], 4)
.unwrap();
let report = mixed(&mut mirror);
assert_eq!(report.partial_uploads, 1, "one texture, one upload");
assert_eq!(report.paused, 0);
assert_eq!(mirror.texture_count(), 1);
for c in [&a, &b] {
let s = c.stats().attachment;
assert!(!s.paused);
assert_eq!(s.paused_frames, 0);
}
}
#[test]
fn a_pause_never_shows_background_where_a_picture_can_be() {
let (source, writer) = live(4, 4);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
let paused = draw().paused(true);
let report = mirror.consume(&frame_with(&[&consumer], paused));
assert_eq!(
report.full_uploads, 1,
"mounted paused: the first frame still shows"
);
writer.write_frame(6, 4, &pixels(6, 4, 2), 24).unwrap();
let report = mirror.consume(&frame_with(&[&consumer], paused));
assert_eq!(report.full_uploads, 1, "a resize while paused uploads once");
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.unwrap();
let report = mirror.consume(&frame_with(&[&consumer], paused));
assert_eq!(
report.full_uploads + report.partial_uploads,
0,
"then holds"
);
writer.clear().unwrap();
let report = mirror.consume(&frame_with(&[&consumer], paused));
assert_eq!(report.pruned, 1, "cleared: the texture goes");
assert_eq!(mirror.texture_count(), 0);
}
#[test]
fn a_capture_uploads_through_a_pause_and_counts_as_a_capture() {
let (source, writer) = live(4, 4);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
let paused = draw().paused(true);
mirror.consume(&frame_with(&[&consumer], paused));
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[8; 4], 4)
.unwrap();
let displayed = source.displayed_generation();
let report = mirror.capture(&frame_with(&[&consumer], paused));
assert_eq!(report.partial_uploads, 1);
assert_eq!(texture(&mirror, &source), current(&source));
let s = consumer.stats();
assert_eq!(s.attachment.captures, 1);
assert_eq!(
s.attachment.frames_drawn, 1,
"the capture is no frame drawn"
);
assert!(s.attachment.paused, "still paused");
assert_eq!(s.attachment.window_generation, source.generation());
assert_eq!(
source.displayed_generation(),
displayed,
"a capture displays nothing"
);
}
#[test]
fn a_capture_waits_for_a_held_lock() {
let (source, writer) = live(4, 4);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.unwrap();
let held = Arc::new(std::sync::Barrier::new(2));
let holder = {
let (writer, held) = (writer.clone(), held.clone());
std::thread::spawn(move || {
let mut guard = writer.lock().unwrap();
held.wait();
std::thread::sleep(Duration::from_millis(50));
guard.fill_rect(PixelRect::new(3, 3, 1, 1), [9; 4]).unwrap();
guard.commit()
})
};
held.wait();
let waits = mirror.stats().blocking_waits;
let report = mirror.capture(&frame(&[&consumer]));
let committed = holder.join().unwrap();
assert_eq!(mirror.stats().blocking_waits, waits + 1);
assert_eq!(report.contended, 0, "no busy row in a capture");
assert_eq!(mirror.decisions()[0].generation, committed);
assert_eq!(texture(&mirror, &source), current(&source));
}
#[test]
fn a_refused_texture_draws_background_and_retries_after_a_second_or_a_resize() {
let (source, writer) = live(4, 4);
let (waker, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.fail_next_texture();
let t0 = Instant::now();
let f = frame(&[&consumer]);
mirror.consume_at(&f, t0);
assert_eq!(mirror.stats().alloc_failures, 1);
assert!(!mirror.decisions()[0].draw);
let before = waker.count();
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.unwrap();
assert_eq!(waker.count(), before, "parked: no redraw per commit");
mirror.consume_at(&frame(&[&consumer]), t0 + Duration::from_millis(500));
assert_eq!(mirror.texture_count(), 0, "within the backoff, not retried");
mirror.consume_at(&frame(&[&consumer]), t0 + Duration::from_secs(1));
assert_eq!(mirror.texture_count(), 1, "retried after a second");
mirror.fail_next_texture();
writer.write_frame(5, 5, &pixels(5, 5, 0), 20).unwrap();
mirror.consume_at(&frame(&[&consumer]), t0 + Duration::from_secs(2));
assert_eq!(mirror.stats().alloc_failures, 2);
writer.write_frame(6, 6, &pixels(6, 6, 0), 24).unwrap();
mirror.consume_at(&frame(&[&consumer]), t0 + Duration::from_secs(2));
assert_eq!(texture(&mirror, &source), pixels(6, 6, 0));
}
#[test]
fn a_lost_device_stops_the_counters_and_parks_the_flags() {
let (source, writer) = live(4, 4);
let (waker, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
mirror.set_device_lost(true);
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.unwrap();
let before = consumer.stats().attachment;
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.partial_uploads, 0);
assert!(mirror.stats().device_lost);
let after = consumer.stats().attachment;
assert_eq!(
(after.frames_drawn, after.uploads, after.window_generation),
(
before.frames_drawn,
before.uploads,
before.window_generation
),
"nothing advances: a probe sees the stream stall"
);
let wakes = waker.count();
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[2; 4], 4)
.unwrap();
assert_eq!(
waker.count(),
wakes,
"and the window is not woken per commit"
);
}
#[test]
fn a_whole_frame_above_the_budget_fills_over_several_frames() {
let (source, writer) = live(16, 16);
let (waker, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
mirror.set_staging_budget(16 * 4 * 5); writer.write_frame(16, 20, &pixels(16, 20, 3), 64).unwrap();
let mut frames = 0;
loop {
let before = waker.count();
let report = mirror.consume(&frame(&[&consumer]));
frames += 1;
if report.full_uploads == 1 {
break;
}
assert_eq!(
waker.count(),
before + 1,
"each partial frame asks for the next"
);
let d = mirror.decisions()[0];
assert!(
!d.draw && d.deferred,
"no texture of the new size shows half-filled"
);
if frames == 2 {
writer
.write_rect(PixelRect::new(1, 1, 1, 1), &[0xEE; 4], 4)
.unwrap();
}
assert!(frames < 10, "the fill makes progress");
}
assert_eq!(frames, 4, "twenty rows, five a frame");
assert_eq!(texture(&mirror, &source), current(&source));
assert!(mirror.decisions()[0].draw);
assert_eq!(mirror.texture_count(), 1);
assert_eq!(mirror.stats().progressive_bands, 4);
}
#[test]
fn a_plan_above_the_budget_fills_a_second_texture_and_never_tears() {
let (source, writer) = live(16, 16);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
let before = texture(&mirror, &source);
mirror.set_staging_budget(16 * 4 * 4);
writer.write_frame(16, 16, &pixels(16, 16, 9), 64).unwrap();
let mut frames = 0;
loop {
let report = mirror.consume(&frame(&[&consumer]));
frames += 1;
if report.full_uploads == 1 {
break;
}
assert_eq!(texture(&mirror, &source), before, "the old picture, whole");
assert!(frames < 10);
}
assert_eq!(texture(&mirror, &source), pixels(16, 16, 9));
assert_eq!(
mirror.texture_count(),
1,
"the old texture went at the swap"
);
}
#[test]
fn no_room_for_a_second_texture_uploads_in_place_on_the_next_frame() {
let (source, writer) = live(16, 16);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
mirror.set_staging_budget(64);
writer.write_frame(16, 16, &pixels(16, 16, 4), 64).unwrap();
mirror.fail_next_texture();
mirror.consume(&frame(&[&consumer]));
assert_eq!(mirror.stats().alloc_failures, 1);
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.full_uploads, 1);
assert_eq!(texture(&mirror, &source), pixels(16, 16, 4));
}
#[test]
fn a_capture_completes_a_fill_in_progress() {
let (source, writer) = live(8, 8);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.set_staging_budget(8 * 4 * 2);
writer.write_frame(8, 9, &pixels(8, 9, 5), 32).unwrap();
mirror.consume(&frame(&[&consumer]));
assert_eq!(mirror.stats().uploads_full, 0, "spread");
let report = mirror.capture(&frame(&[&consumer]));
assert_eq!(report.full_uploads, 1, "every band before the draw");
assert_eq!(texture(&mirror, &source), pixels(8, 9, 5));
}
#[test]
fn a_fill_whose_damage_grows_to_the_whole_frame_restarts_then_completes() {
let (source, writer) = live(8, 8);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.set_staging_budget(8 * 4 * 4); writer.write_frame(8, 10, &pixels(8, 10, 1), 32).unwrap();
let mut seed = 1;
let mut frames = 0;
while mirror.stats().uploads_full == 0 {
mirror.consume(&frame(&[&consumer]));
seed += 1;
writer.write_frame(8, 10, &pixels(8, 10, seed), 32).unwrap();
frames += 1;
assert!(
frames < 30,
"a fill must end even against a full-frame producer"
);
}
let shown = texture(&mirror, &source);
let g = shown[2];
assert!(
shown.chunks(4).all(|p| p[2] == g),
"one generation throughout"
);
}
#[test]
fn bands_cut_a_large_upload_without_changing_its_pixels() {
let (source, writer) = live(37, 23);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.set_band_bytes(37 * 4 * 5);
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report.rects.len(), 5, "23 rows, five a band");
assert_eq!(mirror.stats().upload_calls, 5);
assert_eq!(texture(&mirror, &source), pixels(37, 23, 0));
writer
.write_rect(PixelRect::new(3, 2, 10, 12), &vec![0xAA; 10 * 12 * 4], 40)
.unwrap();
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(
report.rects,
vec![PixelRect::new(3, 2, 10, 12)],
"a narrower rect fits more rows in a band: 12 rows of 40 bytes, one"
);
assert_eq!(texture(&mirror, &source), current(&source));
}
#[test]
fn two_widgets_of_one_source_share_a_texture_and_an_upload() {
let (source, writer) = live(4, 4);
let waker = Arc::new(CountingWaker::new());
let a = source.attach(Some(waker.clone() as Arc<dyn RedrawWaker>));
let b = source.attach(Some(waker.clone() as Arc<dyn RedrawWaker>));
let mut mirror = LiveImageMirror::new();
assert_eq!(mirror.consume(&frame(&[&a, &b])).full_uploads, 1);
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.unwrap();
assert_eq!(mirror.consume(&frame(&[&a, &b])).partial_uploads, 1);
assert_eq!(mirror.texture_count(), 1);
assert!(mirror.decisions().iter().all(|d| d.draw));
}
#[test]
fn two_painted_sizes_in_one_frame_keep_the_sources_own() {
let (source, writer) = live(4, 4);
let (_, a) = widget(&source);
let (_, b) = widget(&source);
lay_out(&a);
writer.write_frame(6, 4, &pixels(6, 4, 0), 24).unwrap();
lay_out(&b);
let mut canvas = Canvas::new();
canvas.draw_live_image(&a, &draw());
canvas.draw_live_image(&b, &draw());
let mut mirror = LiveImageMirror::new();
mirror.consume(&canvas.into_render_frame());
let d = mirror.decisions();
assert!(
!d[0].draw && d[0].deferred,
"the stale size draws background"
);
assert!(d[1].draw, "the source's own size wins");
assert_eq!(mirror.pixels(source.id()).unwrap().0, 6);
let (source, writer) = live(6, 4);
let (_, a) = widget(&source);
let (_, b) = widget(&source);
lay_out(&a);
writer.write_frame(4, 4, &pixels(4, 4, 0), 16).unwrap();
lay_out(&b);
let mut canvas = Canvas::new();
canvas.draw_live_image(&a, &draw());
canvas.draw_live_image(&b, &draw());
let mut mirror = LiveImageMirror::new();
mirror.consume(&canvas.into_render_frame());
let d = mirror.decisions();
assert!(!d[0].draw && d[1].draw, "{d:?}");
assert_eq!(mirror.pixels(source.id()).unwrap().0, 4);
}
#[test]
fn nothing_to_show_is_observed_and_uploads_nothing() {
let source = LiveImageSource::new(LivePixelFormat::Rgba8);
let _writer = source.writer();
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
let report = mirror.consume(&frame(&[&consumer]));
assert_eq!(report, Default::default());
assert_eq!(mirror.decisions()[0], Default::default());
assert!(source.is_observed(), "a placeholder counts as observed");
assert!(
source.is_displayed(),
"and as displayed: a producer throttling on it produces the first frame"
);
consumer.detach();
assert!(!source.is_observed());
}
#[test]
fn dropping_the_pass_unobserves_its_consumers() {
let (source, _writer) = live(4, 4);
let (_, consumer) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&consumer]));
assert!(source.is_observed());
drop(mirror);
assert!(!source.is_observed(), "the window closed");
}
#[test]
fn an_embedded_frame_shifts_its_live_quads_and_merge_shifts_their_commands() {
let (source, _writer) = live(4, 4);
let (_, consumer) = widget(&source);
lay_out(&consumer);
let mut inner = Canvas::new();
inner.draw_live_image(&consumer, &draw());
let inner = inner.into_render_frame();
let mut outer = Canvas::new();
outer.draw_live_image(&consumer, &draw());
outer.draw_render_frame(&inner, Point::new(10.0, 20.0));
let f = outer.into_render_frame();
assert_eq!(f.live_images.len(), 2);
assert_eq!(f.live_images[1].screen, [10.0, 20.0, 64.0, 64.0]);
assert_eq!(
f.draw_order,
vec![DrawCommand::LiveImage(0), DrawCommand::LiveImage(1)]
);
let mut merged = RenderFrame::new();
merged.merge(&f);
merged.merge(&f);
assert_eq!(merged.draw_order[3], DrawCommand::LiveImage(3));
assert_eq!(consumer.stats().attachment.paints, 2, "one per draw call");
}
#[test]
fn a_quad_is_cropped_by_its_texture_coordinates_and_stamped_from_layout() {
let (source, writer) = live(4, 2);
let (_, consumer) = widget(&source);
lay_out(&consumer);
writer.write_frame(8, 2, &pixels(8, 2, 0), 32).unwrap();
let mut canvas = Canvas::new();
let d = LiveImageDraw::new(
Rect::new(-50.0, 0.0, 200.0, 100.0),
Rect::new(0.0, 0.0, 100.0, 100.0),
)
.filter(ScalingFilter::Nearest);
canvas.draw_live_image(&consumer, &d);
let f = canvas.into_render_frame();
let q = &f.live_images[0];
assert_eq!(q.screen, [0.0, 0.0, 100.0, 100.0]);
assert_eq!(q.uv, [[0.25, 0.0], [0.75, 0.0], [0.75, 1.0], [0.25, 1.0]]);
assert_eq!(q.filter, ScalingFilter::Nearest);
assert_eq!(
q.painted,
Some((4, 2)),
"the laid-out size, not the newer one"
);
}
use super::super::internal::{mip_bytes, mip_levels};
use crate::resample::{downsample_half, downsample_half_opaque};
fn reference_chain(px: &[u8], w: u32, h: u32, opaque: bool) -> Vec<(u32, u32, Vec<u8>)> {
let mut levels = Vec::new();
let (mut cw, mut ch, mut cur) = (w, h, px.to_vec());
for k in 1..mip_levels(w, h) {
let (nw, nh, next) = if opaque && k == 1 {
downsample_half_opaque(&cur, cw, ch)
} else {
downsample_half(&cur, cw, ch)
};
levels.push((nw, nh, next.clone()));
(cw, ch, cur) = (nw, nh, next);
}
levels
}
fn assert_chain(mirror: &LiveImageMirror, source: &LiveImageSource, opaque: bool) {
let (w, h, level0) = mirror.pixels(source.id()).expect("a texture");
let reference = reference_chain(level0, w, h, opaque);
assert!(!reference.is_empty());
for (k, (rw, rh, rpx)) in reference.iter().enumerate() {
let (lw, lh, lpx) = mirror
.mip_level(source.id(), k as u32 + 1)
.expect("the level");
assert_eq!((lw, lh), (*rw, *rh), "level {}", k + 1);
assert_eq!(lpx, rpx.as_slice(), "level {}", k + 1);
}
}
fn trilinear() -> LiveImageDraw {
draw().filter(ScalingFilter::Trilinear)
}
fn speckled(w: u32, h: u32, seed: u8) -> Vec<u8> {
let mut px = pixels(w, h, seed);
for (i, texel) in px.chunks_mut(4).enumerate() {
if i % 3 == 0 {
texel[3] = 0;
} else if i % 5 == 0 {
texel[3] = 128;
}
}
px
}
#[test]
fn a_trilinear_quad_stages_a_mipped_texture_and_builds_its_chain() {
let (source, writer) = live(19, 7);
writer
.write_frame(19, 7, &speckled(19, 7, 3), 19 * 4)
.unwrap();
let (_, c) = widget(&source);
let mut mirror = LiveImageMirror::new();
let report = mirror.consume(&frame_with(&[&c], trilinear()));
assert_eq!(report.full_uploads, 1);
assert_eq!(report.mip_rebuilds, vec![None], "the whole chain");
assert_eq!(mirror.stats().bytes, mip_bytes(19, 7, mip_levels(19, 7)));
assert_eq!(mirror.stats().mip_updates, 1);
assert_chain(&mirror, &source, false);
}
#[test]
fn a_rect_rebuilds_only_its_footprint_and_matches_a_whole_rebuild() {
let (source, writer) = live(37, 23);
let (_, c) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame_with(&[&c], trilinear()));
let rect = PixelRect::new(5, 9, 11, 3);
writer
.write_rect(rect, &speckled(11, 3, 7), 11 * 4)
.unwrap();
let report = mirror.consume(&frame_with(&[&c], trilinear()));
assert_eq!((report.full_uploads, report.partial_uploads), (0, 1));
assert_eq!(report.mip_rebuilds, vec![Some(rect)]);
assert_chain(&mirror, &source, false);
}
#[test]
fn commits_folded_into_one_upload_rebuild_the_chain_once() {
let (source, writer) = live(16, 16);
let (_, c) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame_with(&[&c], trilinear()));
for (i, rect) in [PixelRect::new(0, 0, 2, 2), PixelRect::new(12, 12, 4, 4)]
.into_iter()
.enumerate()
{
writer
.write_rect(
rect,
&speckled(rect.width, rect.height, i as u8),
rect.width as usize * 4,
)
.unwrap();
}
let report = mirror.consume(&frame_with(&[&c], trilinear()));
assert_eq!(
report.mip_rebuilds,
vec![Some(PixelRect::new(0, 0, 16, 16))]
);
assert_eq!(mirror.stats().mip_updates, 2);
assert_chain(&mirror, &source, false);
}
#[test]
fn a_linear_quad_shares_the_mipped_texture_of_a_trilinear_one() {
let (source, _writer) = live(20, 10);
let (_, thumb) = widget(&source);
let (_, main) = widget(&source);
let mut canvas = Canvas::new();
lay_out(&thumb);
lay_out(&main);
canvas.draw_live_image(&thumb, &trilinear());
canvas.draw_live_image(&main, &draw());
let mut mirror = LiveImageMirror::new();
let report = mirror.consume(&canvas.into_render_frame());
assert_eq!(report.full_uploads, 1, "one upload for both");
assert_eq!(mirror.texture_count(), 1);
assert!(mirror.decisions().iter().all(|d| d.draw));
}
#[test]
fn a_texture_keeps_its_chain_when_trilinear_goes_and_catches_up_when_it_returns() {
let (source, writer) = live(16, 8);
let (_, c) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame_with(&[&c], trilinear()));
let mipped = mirror.stats().bytes;
for seed in 1..=3 {
writer
.write_rect(
PixelRect::new(seed, 1, 2, 2),
&speckled(2, 2, seed as u8),
8,
)
.unwrap();
let report = mirror.consume(&frame(&[&c]));
assert_eq!((report.full_uploads, report.partial_uploads), (0, 1));
assert!(report.mip_rebuilds.is_empty());
}
assert_eq!(mirror.stats().bytes, mipped, "the levels stay");
let report = mirror.consume(&frame_with(&[&c], trilinear()));
assert_eq!((report.full_uploads, report.partial_uploads), (0, 0));
assert_eq!(report.mip_rebuilds, vec![Some(PixelRect::new(1, 1, 4, 2))]);
assert_chain(&mirror, &source, false);
}
#[test]
fn trilinear_on_a_texture_without_levels_restages_it_with_them() {
let (source, _writer) = live(16, 8);
let (_, c) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame(&[&c]));
assert_eq!(mirror.stats().bytes, 16 * 8 * 4);
let report = mirror.consume(&frame_with(&[&c], trilinear()));
assert_eq!(report.full_uploads, 1, "a new shape: one whole upload");
assert_eq!(report.mip_rebuilds, vec![None]);
assert_eq!(mirror.stats().bytes, mip_bytes(16, 8, mip_levels(16, 8)));
assert_eq!(mirror.texture_count(), 1, "the old one went");
assert_chain(&mirror, &source, false);
}
#[test]
fn a_new_size_drops_the_levels_no_quad_samples() {
let (source, writer) = live(16, 8);
let (_, c) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame_with(&[&c], trilinear()));
writer.write_frame(8, 8, &pixels(8, 8, 1), 32).unwrap();
let report = mirror.consume(&frame(&[&c]));
assert_eq!(report.full_uploads, 1);
assert_eq!(mirror.stats().bytes, 8 * 8 * 4, "restaged as `Linear` asks");
assert!(mirror.mip_level(source.id(), 1).is_none());
}
#[test]
fn an_opaque_source_builds_an_opaque_chain() {
let source = LiveImageSource::new(LivePixelFormat::Rgbx8);
let writer = source.writer();
let mut px = pixels(12, 6, 9);
for texel in px.chunks_mut(4) {
texel[3] = 0; }
writer.write_frame(12, 6, &px, 48).unwrap();
let (_, c) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame_with(&[&c], trilinear()));
assert_chain(&mirror, &source, true);
let (_, _, level1) = mirror.mip_level(source.id(), 1).unwrap();
assert!(
level1.chunks(4).all(|t| t[3] == 255),
"every level above 0 opaque"
);
assert!(level1.chunks(4).any(|t| t[0] != 0), "and not black");
}
#[test]
fn a_paused_trilinear_source_keeps_its_chain_without_rebuilding() {
let (source, writer) = live(16, 8);
let (_, c) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame_with(&[&c], trilinear()));
writer.write_frame(16, 8, &pixels(16, 8, 4), 64).unwrap();
let report = mirror.consume(&frame_with(&[&c], trilinear().paused(true)));
assert_eq!((report.full_uploads, report.paused), (0, 1));
assert!(report.mip_rebuilds.is_empty());
}
#[test]
fn a_texture_restaged_for_a_large_upload_keeps_its_levels() {
let (source, writer) = live(32, 32);
let (_, c) = widget(&source);
let mut mirror = LiveImageMirror::new();
mirror.consume(&frame_with(&[&c], trilinear()));
let mipped = mirror.stats().bytes;
mirror.set_staging_budget(32 * 4 * 8);
writer.write_frame(32, 32, &pixels(32, 32, 6), 128).unwrap();
for _ in 0..8 {
mirror.consume(&frame(&[&c]));
}
assert_eq!(
texture(&mirror, &source),
pixels(32, 32, 6),
"the fill completed"
);
assert_eq!(mirror.texture_count(), 1);
assert_eq!(
mirror.stats().bytes,
mipped,
"the replacement has the levels"
);
let report = mirror.consume(&frame_with(&[&c], trilinear()));
assert_eq!((report.full_uploads, report.partial_uploads), (0, 0));
assert_eq!(report.mip_rebuilds, vec![None]);
assert_chain(&mirror, &source, false);
}
fn two_windows() -> (LiveImageMirror, LiveImageMirror) {
let mut a = LiveImageMirror::new();
let b = a.on_same_device();
(a, b)
}
#[test]
fn a_second_window_copies_the_frame_the_first_uploaded() {
let (source, writer) = live(16, 12);
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (mut a, mut b) = two_windows();
assert_eq!(a.consume(&frame(&[&ca])).full_uploads, 1);
let report = b.consume(&frame(&[&cb]));
assert_eq!(
(report.full_uploads, report.partial_uploads, report.bytes),
(0, 0, 0),
"nothing read from the source"
);
assert_eq!(b.stats().sibling_copies, 1);
assert_eq!(texture(&b, &source), pixels(16, 12, 0));
let d = b.decisions()[0];
assert!(d.draw && !d.deferred);
let s = cb.stats().attachment;
assert_eq!(
(s.uploads, s.window_generation),
(1, source.generation()),
"the window's texture was brought up to date"
);
writer
.write_rect(PixelRect::new(3, 4, 2, 2), &[7; 16], 8)
.unwrap();
assert_eq!(a.consume(&frame(&[&ca])).partial_uploads, 1);
assert_eq!(b.consume(&frame(&[&cb])).bytes, 0);
assert_eq!(b.stats().sibling_copies, 2);
assert_eq!(texture(&b, &source), current(&source));
assert_eq!(texture(&a, &source), current(&source));
assert_eq!(b.consume(&frame(&[&cb])), Default::default());
assert_eq!(b.stats().sibling_copies, 2);
assert_eq!(a.stats().sibling_copies, 0);
}
#[test]
fn only_the_latest_commit_is_copied_and_the_window_holding_it_is_copied_from() {
let (source, writer) = live(16, 12);
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (mut a, mut b) = two_windows();
a.consume(&frame(&[&ca]));
b.consume(&frame(&[&cb]));
writer
.write_rect(PixelRect::new(0, 0, 2, 2), &[1; 16], 8)
.unwrap();
a.consume(&frame(&[&ca]));
writer
.write_rect(PixelRect::new(8, 8, 2, 2), &[2; 16], 8)
.unwrap();
let report = b.consume(&frame(&[&cb]));
assert_eq!(report.partial_uploads, 1);
assert_eq!(report.bytes, 2 * (2 * 2 * 4), "both commits' rects");
assert_eq!(b.stats().sibling_copies, 1, "the first frame's copy only");
assert_eq!(texture(&b, &source), current(&source));
let report = a.consume(&frame(&[&ca]));
assert_eq!((report.partial_uploads, report.bytes), (0, 0));
assert_eq!(a.stats().sibling_copies, 1);
assert_eq!(texture(&a, &source), current(&source));
}
#[test]
fn a_copy_takes_no_lock() {
let (source, writer) = live(8, 8);
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (mut a, mut b) = two_windows();
a.consume(&frame(&[&ca]));
let guard = writer.lock().unwrap();
let report = b.consume(&frame(&[&cb]));
drop(guard);
assert_eq!((report.contended, report.deferred), (0, 0));
assert_eq!(b.stats().sibling_copies, 1);
assert!(b.decisions()[0].draw);
assert!(b.last_timings().lock_holds.is_empty());
}
#[test]
fn a_window_laid_out_at_another_size_does_not_copy() {
let (source, writer) = live(4, 4);
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (mut a, mut b) = two_windows();
a.consume(&frame(&[&ca]));
b.consume(&frame(&[&cb]));
let mut canvas = Canvas::new();
lay_out(&cb);
canvas.draw_live_image(&cb, &draw());
let at_4x4 = canvas.into_render_frame();
writer.write_frame(5, 4, &pixels(5, 4, 1), 20).unwrap();
assert_eq!(a.consume(&frame(&[&ca])).full_uploads, 1);
let report = b.consume(&at_4x4);
assert_eq!(b.stats().sibling_copies, 1, "the first frame's copy only");
assert_eq!(report.deferred, 1, "the 4 × 4 texture still draws");
assert_eq!(b.stats().stale_deferrals, 1);
let report = b.consume(&frame(&[&cb]));
assert_eq!((report.full_uploads, report.deferred), (0, 0));
assert_eq!(b.stats().sibling_copies, 2);
assert_eq!(texture(&b, &source), pixels(5, 4, 1));
assert_eq!(b.texture_count(), 1, "the 4 × 4 texture went");
}
#[test]
fn a_paused_window_keeps_its_picture_while_the_other_moves_on() {
let (source, writer) = live(8, 8);
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (mut a, mut b) = two_windows();
a.consume(&frame(&[&ca]));
let held = b.consume(&frame(&[&cb]));
assert_eq!(held.full_uploads, 0);
writer.write_frame(8, 8, &pixels(8, 8, 5), 32).unwrap();
a.consume(&frame(&[&ca]));
let report = b.consume(&frame_with(&[&cb], draw().paused(true)));
assert_eq!(report.paused, 1);
assert_eq!(b.stats().sibling_copies, 1, "the first frame's copy only");
assert_eq!(texture(&b, &source), pixels(8, 8, 0), "the held frame");
assert_eq!(texture(&a, &source), pixels(8, 8, 5));
}
#[test]
fn a_fill_spread_over_frames_is_finished_by_a_copy() {
let (source, writer) = live(16, 10);
writer.write_frame(16, 10, &pixels(16, 10, 4), 64).unwrap();
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (mut a, mut b) = two_windows();
b.set_staging_budget(16 * 4 * 3); let report = b.consume(&frame(&[&cb]));
assert_eq!(report.deferred, 1);
assert_eq!(b.stats().progressive_bands, 1);
assert_eq!(a.consume(&frame(&[&ca])).full_uploads, 1);
let report = b.consume(&frame(&[&cb]));
assert_eq!((report.deferred, report.bytes), (0, 0));
assert_eq!(b.stats().sibling_copies, 1);
assert_eq!(b.stats().progressive_bands, 1, "no band after the copy");
assert_eq!(texture(&b, &source), pixels(16, 10, 4));
assert_eq!(b.texture_count(), 1);
}
#[test]
fn a_copied_texture_builds_its_own_chain() {
let (source, writer) = live(19, 7);
writer
.write_frame(19, 7, &speckled(19, 7, 3), 19 * 4)
.unwrap();
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (mut a, mut b) = two_windows();
a.consume(&frame(&[&ca]));
let report = b.consume(&frame_with(&[&cb], trilinear()));
assert_eq!(report.full_uploads, 0);
assert_eq!(b.stats().sibling_copies, 1);
assert_eq!(report.mip_rebuilds, vec![None], "the whole chain");
assert_eq!(b.stats().bytes, mip_bytes(19, 7, mip_levels(19, 7)));
assert_chain(&b, &source, false);
writer
.write_rect(PixelRect::new(4, 2, 3, 3), &[200; 36], 12)
.unwrap();
a.consume(&frame(&[&ca]));
let report = b.consume(&frame_with(&[&cb], trilinear()));
assert_eq!((report.full_uploads, report.partial_uploads), (0, 0));
assert_eq!(b.stats().sibling_copies, 2);
assert_eq!(report.mip_rebuilds, vec![None]);
assert_eq!(texture(&b, &source), current(&source));
assert_chain(&b, &source, false);
}
#[test]
fn a_second_texture_for_a_large_plan_goes_when_a_copy_catches_up() {
let (source, writer) = live(16, 10);
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (mut a, mut b) = two_windows();
a.consume(&frame(&[&ca]));
b.consume(&frame(&[&cb]));
b.set_staging_budget(16 * 4 * 3);
writer.write_frame(16, 10, &pixels(16, 10, 6), 64).unwrap();
let report = b.consume(&frame(&[&cb]));
assert_eq!(report.deferred, 1);
assert_eq!(b.texture_count(), 2);
a.consume(&frame(&[&ca]));
let report = b.consume(&frame(&[&cb]));
assert_eq!((report.deferred, report.bytes), (0, 0));
assert_eq!(b.stats().sibling_copies, 2);
assert_eq!(b.texture_count(), 1);
assert_eq!(texture(&b, &source), pixels(16, 10, 6));
}
#[test]
fn a_window_of_another_thread_is_not_copied_from() {
let (source, _writer) = live(8, 8);
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (a, mut b) = two_windows();
let a = std::thread::spawn(move || {
let mut a = a;
assert_eq!(a.consume(&frame(&[&ca])).full_uploads, 1);
a
})
.join()
.unwrap();
assert_eq!(b.consume(&frame(&[&cb])).full_uploads, 1);
assert_eq!(b.stats().sibling_copies, 0);
drop(a);
}
#[test]
fn a_window_that_closed_or_dropped_the_texture_is_not_copied_from() {
let (source, writer) = live(8, 8);
let (_, ca) = widget(&source);
let (_, cb) = widget(&source);
let (mut a, mut b) = two_windows();
a.consume(&frame(&[&ca]));
a.consume(&RenderFrame::new());
assert_eq!(b.consume(&frame(&[&cb])).full_uploads, 1);
assert_eq!(b.stats().sibling_copies, 0);
writer.write_frame(8, 8, &pixels(8, 8, 2), 32).unwrap();
let mut c = b.on_same_device();
let (_, cc) = widget(&source);
b.consume(&frame(&[&cb]));
drop(b);
assert_eq!(c.consume(&frame(&[&cc])).full_uploads, 1);
assert_eq!(c.stats().sibling_copies, 0);
}