2026-05-05 13:15:00 +00:00
|
|
|
|
//! Spec v19 acceptance #5 (resize / DPI no-flicker) / Phase A Gate
|
|
|
|
|
|
//! round 2 CONCERN 2.
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! Round 1 only exercised the `resize` tracing span on an inert
|
|
|
|
|
|
//! context — that proves the span fires but doesn't pin the
|
|
|
|
|
|
//! invariant the acceptance is actually about (after a resize, a
|
|
|
|
|
|
//! draw issued through `NativeBackend` must land on a surface of
|
|
|
|
|
|
//! the new dimensions without panic). This test fills that gap
|
|
|
|
|
|
//! using a raster Skia surface as a stand-in for the GL FBO that
|
|
|
|
|
|
//! `SharedSkiaContext::resize` rebuilds — `cargo test` cannot drive
|
|
|
|
|
|
//! a real GL surface from a worker thread, but the
|
|
|
|
|
|
//! `NativeBackend::fill_rect` translation layer is identical
|
|
|
|
|
|
//! whether the canvas is raster- or GPU-backed.
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! Two passes:
|
|
|
|
|
|
//! 1. Grow 400×300 → 800×600, paint at centre, assert red.
|
|
|
|
|
|
//! 2. Shrink 800×600 → 400×300, paint at centre, assert red.
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! Plus: a tracing-span pin on `SharedSkiaContext::resize` itself
|
|
|
|
|
|
//! (already covered structurally by `tracing_spans.rs`, repeated
|
|
|
|
|
|
//! here at the new size cadence to lock the resize-grow / resize-
|
|
|
|
|
|
//! shrink invariant on round-2's stricter spec).
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! Windows is `#[ignore]`d at the test level — CI runners ship
|
|
|
|
|
|
//! without a usable GL stack, the raster path is identical across
|
|
|
|
|
|
//! macOS / Linux, and CONCERN 2 only requires non-Windows coverage.
|
|
|
|
|
|
|
2026-05-16 15:49:58 +00:00
|
|
|
|
use op_editor_ui::{Color, Point2D, Rect};
|
|
|
|
|
|
use op_host_native::{NativeBackend, SharedSkiaContext};
|
2026-05-05 13:15:00 +00:00
|
|
|
|
use tracing_test::traced_test;
|
|
|
|
|
|
|
|
|
|
|
|
/// Helper: build a raster Skia surface of `(w, h)`, paint a
|
|
|
|
|
|
/// `NativeBackend::fill_rect` covering most of it, return the
|
|
|
|
|
|
/// resulting pixel at the surface centre. Mirrors the chain
|
|
|
|
|
|
/// `SharedSkiaContext::resize` → `with_frame` →
|
|
|
|
|
|
/// `NativeBackend::fill_rect` minus the GL stack.
|
|
|
|
|
|
fn paint_at_size(backend: &mut NativeBackend, w: i32, h: i32) -> [u8; 4] {
|
|
|
|
|
|
let mut surface = skia_safe::surfaces::raster_n32_premul((w, h))
|
|
|
|
|
|
.expect("raster_n32_premul allocated for resize test");
|
|
|
|
|
|
surface.canvas().clear(skia_safe::Color::WHITE);
|
|
|
|
|
|
|
|
|
|
|
|
backend.fill_rect(
|
|
|
|
|
|
surface.canvas(),
|
|
|
|
|
|
Rect {
|
|
|
|
|
|
origin: Point2D::new(10.0, 10.0),
|
|
|
|
|
|
size: Point2D::new((w - 20) as f32, (h - 20) as f32),
|
|
|
|
|
|
},
|
|
|
|
|
|
Color::RED,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let stride = (w * 4) as usize;
|
|
|
|
|
|
let mut pixels = vec![0u8; stride * (h as usize)];
|
|
|
|
|
|
let info = skia_safe::ImageInfo::new(
|
|
|
|
|
|
(w, h),
|
|
|
|
|
|
skia_safe::ColorType::RGBA8888,
|
|
|
|
|
|
skia_safe::AlphaType::Premul,
|
|
|
|
|
|
None,
|
|
|
|
|
|
);
|
|
|
|
|
|
let read_ok = surface.read_pixels(&info, &mut pixels, stride, (0, 0));
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
read_ok,
|
|
|
|
|
|
"raster Surface::read_pixels must succeed at {}×{}",
|
|
|
|
|
|
w, h
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let cx = (w / 2) as usize;
|
|
|
|
|
|
let cy = (h / 2) as usize;
|
|
|
|
|
|
let offset = cy * stride + cx * 4;
|
|
|
|
|
|
[
|
|
|
|
|
|
pixels[offset],
|
|
|
|
|
|
pixels[offset + 1],
|
|
|
|
|
|
pixels[offset + 2],
|
|
|
|
|
|
pixels[offset + 3],
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg_attr(target_os = "windows", ignore)]
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn resize_grow_then_shrink_paints_through() {
|
|
|
|
|
|
let mut backend = NativeBackend::with_dpi(1.0);
|
|
|
|
|
|
|
|
|
|
|
|
// Initial 400×300.
|
|
|
|
|
|
let small_pixel = paint_at_size(&mut backend, 400, 300);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
small_pixel[0] > 200 && small_pixel[1] < 60 && small_pixel[2] < 60,
|
|
|
|
|
|
"400×300 centre expected red, got {:?}",
|
|
|
|
|
|
small_pixel
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Grow to 800×600 — same `NativeBackend` instance must paint
|
|
|
|
|
|
// through to a fresh-larger surface (matches the realistic
|
|
|
|
|
|
// resize path: window expanded, `SharedSkiaContext::resize`
|
|
|
|
|
|
// rebuilt the Skia surface, next frame's `fill_rect` lands on
|
|
|
|
|
|
// it).
|
|
|
|
|
|
let grown_pixel = paint_at_size(&mut backend, 800, 600);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
grown_pixel[0] > 200 && grown_pixel[1] < 60 && grown_pixel[2] < 60,
|
|
|
|
|
|
"800×600 centre expected red, got {:?}",
|
|
|
|
|
|
grown_pixel
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Shrink back to 400×300 — same invariant, opposite direction.
|
|
|
|
|
|
let shrunk_pixel = paint_at_size(&mut backend, 400, 300);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
shrunk_pixel[0] > 200 && shrunk_pixel[1] < 60 && shrunk_pixel[2] < 60,
|
|
|
|
|
|
"shrunk 400×300 centre expected red, got {:?}",
|
|
|
|
|
|
shrunk_pixel
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Pin the `resize` span emission on a `SharedSkiaContext` whose
|
|
|
|
|
|
/// fields are at the post-teardown shape (surface = None, dc =
|
|
|
|
|
|
/// None). Complements `tracing_spans.rs` by exercising both grow
|
|
|
|
|
|
/// (800×600) and shrink (400×300) paths plus the 0×0 no-op clamp.
|
|
|
|
|
|
#[cfg_attr(target_os = "windows", ignore)]
|
|
|
|
|
|
#[traced_test]
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn resize_emits_span_grow_and_shrink() {
|
|
|
|
|
|
let mut ctx = SharedSkiaContext::inert_for_lifecycle_test();
|
|
|
|
|
|
ctx.resize(800, 600).expect("resize 800x600");
|
|
|
|
|
|
ctx.resize(400, 300).expect("resize 400x300");
|
|
|
|
|
|
ctx.resize(0, 0).expect("resize 0x0 no-op clamp");
|
|
|
|
|
|
|
|
|
|
|
|
assert!(logs_contain("resize"), "resize span missing");
|
|
|
|
|
|
}
|