openpencil/crates/openpencil-shell-native/tests/raster_composition.rs
Kayshen-X c1ce879582 feat(shell-native): SharedSkiaContext + NativeBackend over Jian DrawOp
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.
2026-05-05 21:06:00 +08:00

95 lines
3.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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"
);
}