Step 1a Task 2 (spec v19 §3 / §5.2.1, plan v7).
- `SharedSkiaContext`: own GL stack + Skia DirectContext + Surface,
`Option<>`-field idempotent teardown, `with_frame(|canvas, glow|)`
callback, lifecycle hooks (on_pause/on_resume/on_low_memory) with
Android surface drop contract; tracing spans + events on every
per-frame entry point.
- `GlContextProvider` trait + `GlutinProvider` desktop impl + iOS /
Android stubs; trait carries no `Send` bound (per spec §3.1).
- `CanvasViewportStub::render_into(&Canvas)` deliberately pollutes
STENCIL_TEST + blend func to verify chrome-paint isolation.
- `NativeBackend`: frame-scoped methods mirroring OP `RenderBackend`
trait surface (no direct trait impl in 1a; Step 1c+ wraps via
`WithCanvas<'a>` newtype). Translates `fill_rect / stroke_rect /
draw_text / clip_rect / save / restore / translate` to
`jian_core::render::DrawOp` and submits via
`jian_skia::SkiaBackend::draw_on_canvas`. Public `draw_op` helper +
`to_jian_color` / `to_jian_rect` converters.
- Tests:
- `teardown_idempotent.rs` — teardown × 3 + lifecycle hook idempotence.
- `memory_loop.rs` — 100 × create/begin_frame/present/teardown × 3
with sysinfo RSS budget < 5 %.
- `tracing_spans.rs` — `tracing-test` (no-env-filter) catches
begin_frame / with_frame / present / resize / teardown / on_pause /
on_resume / on_low_memory events.
- `raster_composition.rs` — chrome-only fill_rect on raster surface,
pixel-asserts red + black + untouched-bg.
- `raster_text_smoke.rs` — "Hello 你好" through textlayout feature,
asserts visible glyph rasterisation.
- `gpu_smoke.rs` — Linux EGL pbuffer (non-ignored) + macOS invisible
winit window (graceful inconclusive when off main thread; full
path runs from `cargo run --example basic_window`) + Windows
`#[ignore]` per spec §8.1.
- `gpu_chrome_stub_composition.rs` — chrome+stub on the same GL
surface, asserts chrome pixels survive stub's GL pollution.
- Cargo.toml: add `jian-core` direct dep + `tracing` / `thiserror`
workspace deps; dev-deps `sysinfo`, `tracing-test` (with
`no-env-filter`), Linux-only `khronos-egl` + `libloading`.
`cargo build`, `cargo test`, `cargo clippy --all-targets -- -D warnings`,
`cargo fmt --all -- --check` all green on macOS.
95 lines
3.1 KiB
Rust
95 lines
3.1 KiB
Rust
//! Spec v19 acceptance #2 (chrome region) / plan v7 Task 2 Step 16d.
|
||
//!
|
||
//! Round 5 BLOCK-R5-2 fix: this test covers **chrome only** on a raster
|
||
//! surface. The chrome+stub composition lives in
|
||
//! `gpu_chrome_stub_composition.rs` because `CanvasViewportStub` needs
|
||
//! a real `glow::Context` to mutate GL state, which only the GPU smoke
|
||
//! provider has. Here we just prove that `NativeBackend` writes the
|
||
//! pixels widgets ask for at the right coordinates — independent of
|
||
//! GL state.
|
||
|
||
use openpencil_shell_core::{Color, Point2D, Rect};
|
||
use openpencil_shell_native::NativeBackend;
|
||
|
||
fn pixel_at(pixels: &[u8], stride: usize, x: i32, y: i32) -> [u8; 4] {
|
||
let offset = (y as usize) * stride + (x as usize) * 4;
|
||
[
|
||
pixels[offset],
|
||
pixels[offset + 1],
|
||
pixels[offset + 2],
|
||
pixels[offset + 3],
|
||
]
|
||
}
|
||
|
||
#[test]
|
||
fn chrome_rects_paint_at_expected_coordinates() {
|
||
// 500 × 400 raster surface. RGBA8888 / N32 premul matches what
|
||
// `surfaces::wrap_backend_render_target` would set for a GL surface
|
||
// — the per-component byte order is identical on little-endian.
|
||
let mut surface =
|
||
skia_safe::surfaces::raster_n32_premul((500, 400)).expect("raster_n32_premul allocated");
|
||
|
||
// Clear to white so chrome paints stand out and "background"
|
||
// assertions know what to expect.
|
||
surface.canvas().clear(skia_safe::Color::WHITE);
|
||
|
||
let mut backend = NativeBackend::with_dpi(1.0);
|
||
let canvas = surface.canvas();
|
||
|
||
// Chrome rect 1: red, top-left chrome.
|
||
backend.fill_rect(
|
||
canvas,
|
||
Rect {
|
||
origin: Point2D::new(50.0, 50.0),
|
||
size: Point2D::new(100.0, 100.0),
|
||
},
|
||
Color::RED,
|
||
);
|
||
|
||
// Chrome rect 2: black, separate region — verifies multi-fill
|
||
// isn't polluted by the first call.
|
||
backend.fill_rect(
|
||
canvas,
|
||
Rect {
|
||
origin: Point2D::new(250.0, 50.0),
|
||
size: Point2D::new(50.0, 50.0),
|
||
},
|
||
Color::BLACK,
|
||
);
|
||
|
||
// Read pixels back via `Surface::read_pixels`; raster surfaces
|
||
// expose CPU memory directly so the bytes match what we'd see on
|
||
// a GL surface for the same draw.
|
||
let stride = 500 * 4;
|
||
let mut pixels = vec![0u8; stride * 400];
|
||
let info = skia_safe::ImageInfo::new(
|
||
(500, 400),
|
||
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");
|
||
|
||
// (a) red chrome interior — middle of the (50,50)-(150,150) rect.
|
||
assert_eq!(
|
||
pixel_at(&pixels, stride, 75, 75),
|
||
[255, 0, 0, 255],
|
||
"red chrome"
|
||
);
|
||
|
||
// (b) black chrome interior — middle of the (250,50)-(300,100) rect.
|
||
assert_eq!(
|
||
pixel_at(&pixels, stride, 275, 75),
|
||
[0, 0, 0, 255],
|
||
"black chrome"
|
||
);
|
||
|
||
// (c) untouched background pixel — well outside both rects.
|
||
assert_eq!(
|
||
pixel_at(&pixels, stride, 400, 300),
|
||
[255, 255, 255, 255],
|
||
"background unchanged"
|
||
);
|
||
}
|