2026-05-16 15:49:58 +00:00
|
|
|
//! Shared test helpers for `op-host-native`.
|
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 13:06:00 +00:00
|
|
|
//!
|
|
|
|
|
//! Spec v19 §9 / plan v7 Task 2 Step 16a-16f: this module supplies
|
fix(shell-native): Phase A Gate round 1 fixes (Task 2 patches)
Applies Codex Phase A Gate round 1 review (3 BLOCK + 2 CONCERN + 1 NIT)
against the Task 2 SharedSkiaContext + NativeBackend implementation.
BLOCK 1 — `ProviderError::from_msg` `pub(crate)` blocked the Linux EGL
pbuffer test helper from constructing typed provider errors. Promoted
to `pub` so out-of-tree provider impls (test pbuffer, future Step 1f
mobile providers) can produce diagnostically-identical errors.
BLOCK 2 — Linux GPU smoke + chrome-stub-composition tests silently
returned `Ok(())` on EGL pbuffer setup failure, turning acceptance #3 /
#4 into false positives on hosted CI without GPU. Now gated by
`STEP1A_REQUIRE_GPU=1`: real-GPU runners panic on setup failure;
dev / hostless runs surface an explicit `INCONCLUSIVE` marker before
returning. Mirrors the macOS `catch_unwind` skip path in the same file.
BLOCK 3 — `tests/memory_loop.rs` was running 100 cycles against
`SharedSkiaContext::inert_for_test()` (every Option<> field None), so
the RSS budget proved nothing about real allocation lifecycle. Renamed
constructor to `inert_for_lifecycle_test()` (clearer intent) and split
the test into:
- Phase 0 warmup (100 inert + 100 raster) so Skia's lazy
glyph/path/binding caches are populated before measurement;
- Phase 1 lifecycle idempotence (100 inert);
- Phase 2 real-resource cycle: raster surface on macOS / Windows
(winit::EventLoop main-thread-only on macOS; Win Actions runner
has no GPU per spec §8.1), full EGL pbuffer + GL surface on Linux
when `STEP1A_REQUIRE_GPU=1`, raster fallback otherwise.
Budget kept at 5 % per acceptance #6 with a 1.5 MB absolute floor to
absorb macOS sysinfo's coarse RSS sampling jitter on small baselines.
CONCERN 1 — `GlContextProvider` had three non-spec methods (`resize`,
`size`, `default_framebuffer_id`). Audit:
- `resize`: actually used by `SharedSkiaContext::resize` (window /
pbuffer resize → Skia FBO rewrap). KEPT, spec mini-patch
documented in comment, escalation needed for spec v19 → v19.1.
- `default_framebuffer_id`: used by `SharedSkiaContext::new` /
`resize` for the FBO id Skia wraps; iOS EAGL provider (Step 1f)
will need non-zero values. KEPT, same escalation path.
- `size`: unused anywhere. DELETED (YAGNI), along with the unused
`size: (u32, u32)` field on `GlutinProvider` and the iOS / Android
stub impls.
CONCERN 2 — `glow_handle: Option<Arc<glow::Context>>` deviates from
spec v19 lines 120-125 + 191 (`Arc<glow::Context>`). Real lifecycle
needs the handle droppable: teardown releases the loaded function
table, `inert_for_lifecycle_test` has no GL backing, Step 1f Android
`on_pause` must drop alongside the EGL context. KEPT as Option<Arc>,
spec mini-patch documented for v19.1 escalation.
NIT 1 — Removed Task 1 link-check helper `placeholder()`. Task 2's
full re-export chain (`SharedSkiaContext`, `NativeBackend`, …)
already proves shell-core ↔ shell-native linkage; placeholder is
YAGNI now.
Verification (macOS local):
- cargo build -p openpencil-shell-native: clean
- cargo test -p openpencil-shell-native: 12/12 pass (8 binaries)
- cargo clippy -p openpencil-shell-native --tests --all-targets
-- -D warnings: clean
- cargo fmt -p openpencil-shell-native -- --check: clean
- memory_loop stress 8 consecutive runs: 8/8 pass
2026-05-05 13:09:00 +00:00
|
|
|
//! - `setup_headless_context()` for tests that pin lifecycle
|
|
|
|
|
//! idempotence on a fully torn-down context (teardown, tracing);
|
|
|
|
|
//! - `RasterCycle` for tests that need real Skia resources (RSS
|
|
|
|
|
//! sanity loop) without spinning up a GL stack — this is the
|
|
|
|
|
//! macOS / Windows / no-GPU-Linux memory-loop path;
|
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 13:06:00 +00:00
|
|
|
//! - `egl_pbuffer::EglPbufferProvider` (Linux only) for the GPU-smoke
|
|
|
|
|
//! tests, providing an off-screen GL context via Mesa softpipe.
|
|
|
|
|
|
|
|
|
|
#![allow(dead_code)]
|
|
|
|
|
|
2026-05-16 15:49:58 +00:00
|
|
|
use op_host_native::SharedSkiaContext;
|
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 13:06:00 +00:00
|
|
|
|
|
|
|
|
/// Returns a `SharedSkiaContext` whose every `Option<>` field is
|
|
|
|
|
/// `None`. The tests that exercise idempotence + tracing don't care
|
|
|
|
|
/// what's behind the handles — they care that the public API behaves
|
|
|
|
|
/// the same on a torn-down context. Avoids the per-test cost of
|
|
|
|
|
/// spinning up an EGL pbuffer / winit window.
|
fix(shell-native): Phase A Gate round 1 fixes (Task 2 patches)
Applies Codex Phase A Gate round 1 review (3 BLOCK + 2 CONCERN + 1 NIT)
against the Task 2 SharedSkiaContext + NativeBackend implementation.
BLOCK 1 — `ProviderError::from_msg` `pub(crate)` blocked the Linux EGL
pbuffer test helper from constructing typed provider errors. Promoted
to `pub` so out-of-tree provider impls (test pbuffer, future Step 1f
mobile providers) can produce diagnostically-identical errors.
BLOCK 2 — Linux GPU smoke + chrome-stub-composition tests silently
returned `Ok(())` on EGL pbuffer setup failure, turning acceptance #3 /
#4 into false positives on hosted CI without GPU. Now gated by
`STEP1A_REQUIRE_GPU=1`: real-GPU runners panic on setup failure;
dev / hostless runs surface an explicit `INCONCLUSIVE` marker before
returning. Mirrors the macOS `catch_unwind` skip path in the same file.
BLOCK 3 — `tests/memory_loop.rs` was running 100 cycles against
`SharedSkiaContext::inert_for_test()` (every Option<> field None), so
the RSS budget proved nothing about real allocation lifecycle. Renamed
constructor to `inert_for_lifecycle_test()` (clearer intent) and split
the test into:
- Phase 0 warmup (100 inert + 100 raster) so Skia's lazy
glyph/path/binding caches are populated before measurement;
- Phase 1 lifecycle idempotence (100 inert);
- Phase 2 real-resource cycle: raster surface on macOS / Windows
(winit::EventLoop main-thread-only on macOS; Win Actions runner
has no GPU per spec §8.1), full EGL pbuffer + GL surface on Linux
when `STEP1A_REQUIRE_GPU=1`, raster fallback otherwise.
Budget kept at 5 % per acceptance #6 with a 1.5 MB absolute floor to
absorb macOS sysinfo's coarse RSS sampling jitter on small baselines.
CONCERN 1 — `GlContextProvider` had three non-spec methods (`resize`,
`size`, `default_framebuffer_id`). Audit:
- `resize`: actually used by `SharedSkiaContext::resize` (window /
pbuffer resize → Skia FBO rewrap). KEPT, spec mini-patch
documented in comment, escalation needed for spec v19 → v19.1.
- `default_framebuffer_id`: used by `SharedSkiaContext::new` /
`resize` for the FBO id Skia wraps; iOS EAGL provider (Step 1f)
will need non-zero values. KEPT, same escalation path.
- `size`: unused anywhere. DELETED (YAGNI), along with the unused
`size: (u32, u32)` field on `GlutinProvider` and the iOS / Android
stub impls.
CONCERN 2 — `glow_handle: Option<Arc<glow::Context>>` deviates from
spec v19 lines 120-125 + 191 (`Arc<glow::Context>`). Real lifecycle
needs the handle droppable: teardown releases the loaded function
table, `inert_for_lifecycle_test` has no GL backing, Step 1f Android
`on_pause` must drop alongside the EGL context. KEPT as Option<Arc>,
spec mini-patch documented for v19.1 escalation.
NIT 1 — Removed Task 1 link-check helper `placeholder()`. Task 2's
full re-export chain (`SharedSkiaContext`, `NativeBackend`, …)
already proves shell-core ↔ shell-native linkage; placeholder is
YAGNI now.
Verification (macOS local):
- cargo build -p openpencil-shell-native: clean
- cargo test -p openpencil-shell-native: 12/12 pass (8 binaries)
- cargo clippy -p openpencil-shell-native --tests --all-targets
-- -D warnings: clean
- cargo fmt -p openpencil-shell-native -- --check: clean
- memory_loop stress 8 consecutive runs: 8/8 pass
2026-05-05 13:09:00 +00:00
|
|
|
///
|
|
|
|
|
/// **Do not use for resource-accounting tests** — see
|
|
|
|
|
/// `RasterCycle` below. (Codex Phase A Gate round 1 BLOCK 3.)
|
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 13:06:00 +00:00
|
|
|
pub fn setup_headless_context() -> SharedSkiaContext {
|
fix(shell-native): Phase A Gate round 1 fixes (Task 2 patches)
Applies Codex Phase A Gate round 1 review (3 BLOCK + 2 CONCERN + 1 NIT)
against the Task 2 SharedSkiaContext + NativeBackend implementation.
BLOCK 1 — `ProviderError::from_msg` `pub(crate)` blocked the Linux EGL
pbuffer test helper from constructing typed provider errors. Promoted
to `pub` so out-of-tree provider impls (test pbuffer, future Step 1f
mobile providers) can produce diagnostically-identical errors.
BLOCK 2 — Linux GPU smoke + chrome-stub-composition tests silently
returned `Ok(())` on EGL pbuffer setup failure, turning acceptance #3 /
#4 into false positives on hosted CI without GPU. Now gated by
`STEP1A_REQUIRE_GPU=1`: real-GPU runners panic on setup failure;
dev / hostless runs surface an explicit `INCONCLUSIVE` marker before
returning. Mirrors the macOS `catch_unwind` skip path in the same file.
BLOCK 3 — `tests/memory_loop.rs` was running 100 cycles against
`SharedSkiaContext::inert_for_test()` (every Option<> field None), so
the RSS budget proved nothing about real allocation lifecycle. Renamed
constructor to `inert_for_lifecycle_test()` (clearer intent) and split
the test into:
- Phase 0 warmup (100 inert + 100 raster) so Skia's lazy
glyph/path/binding caches are populated before measurement;
- Phase 1 lifecycle idempotence (100 inert);
- Phase 2 real-resource cycle: raster surface on macOS / Windows
(winit::EventLoop main-thread-only on macOS; Win Actions runner
has no GPU per spec §8.1), full EGL pbuffer + GL surface on Linux
when `STEP1A_REQUIRE_GPU=1`, raster fallback otherwise.
Budget kept at 5 % per acceptance #6 with a 1.5 MB absolute floor to
absorb macOS sysinfo's coarse RSS sampling jitter on small baselines.
CONCERN 1 — `GlContextProvider` had three non-spec methods (`resize`,
`size`, `default_framebuffer_id`). Audit:
- `resize`: actually used by `SharedSkiaContext::resize` (window /
pbuffer resize → Skia FBO rewrap). KEPT, spec mini-patch
documented in comment, escalation needed for spec v19 → v19.1.
- `default_framebuffer_id`: used by `SharedSkiaContext::new` /
`resize` for the FBO id Skia wraps; iOS EAGL provider (Step 1f)
will need non-zero values. KEPT, same escalation path.
- `size`: unused anywhere. DELETED (YAGNI), along with the unused
`size: (u32, u32)` field on `GlutinProvider` and the iOS / Android
stub impls.
CONCERN 2 — `glow_handle: Option<Arc<glow::Context>>` deviates from
spec v19 lines 120-125 + 191 (`Arc<glow::Context>`). Real lifecycle
needs the handle droppable: teardown releases the loaded function
table, `inert_for_lifecycle_test` has no GL backing, Step 1f Android
`on_pause` must drop alongside the EGL context. KEPT as Option<Arc>,
spec mini-patch documented for v19.1 escalation.
NIT 1 — Removed Task 1 link-check helper `placeholder()`. Task 2's
full re-export chain (`SharedSkiaContext`, `NativeBackend`, …)
already proves shell-core ↔ shell-native linkage; placeholder is
YAGNI now.
Verification (macOS local):
- cargo build -p openpencil-shell-native: clean
- cargo test -p openpencil-shell-native: 12/12 pass (8 binaries)
- cargo clippy -p openpencil-shell-native --tests --all-targets
-- -D warnings: clean
- cargo fmt -p openpencil-shell-native -- --check: clean
- memory_loop stress 8 consecutive runs: 8/8 pass
2026-05-05 13:09:00 +00:00
|
|
|
SharedSkiaContext::inert_for_lifecycle_test()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Real-resource cycle helper for the memory-loop test (Codex Phase A
|
|
|
|
|
/// Gate round 1 BLOCK 3 fix).
|
|
|
|
|
///
|
|
|
|
|
/// Runs `iterations` of `{ build raster Skia surface → paint via
|
|
|
|
|
/// `NativeBackend::fill_rect` → drop the surface }` against a real
|
|
|
|
|
/// `skia_safe::Surface`. This exercises the same Skia allocation
|
|
|
|
|
/// paths (`raster_n32_premul`, `Canvas::draw_rect`) that the GPU
|
|
|
|
|
/// path goes through after `wrap_backend_render_target`, so the RSS
|
|
|
|
|
/// budget assertion measures real driver-side accounting rather than
|
|
|
|
|
/// a phantom no-op.
|
|
|
|
|
///
|
|
|
|
|
/// We intentionally do **not** route through `SharedSkiaContext`
|
|
|
|
|
/// here: that struct's full lifecycle requires a `GlContextProvider`,
|
|
|
|
|
/// and on hosted-CI macOS / Windows runners the only realistic
|
|
|
|
|
/// no-display GL stack is a winit window (which can't run on a
|
|
|
|
|
/// worker thread, see `gpu_smoke.rs`). Raster surfaces are the
|
|
|
|
|
/// largest non-GL allocation we can hammer in a `cargo test`
|
|
|
|
|
/// process; they're enough to flush a leak in the Skia bindings or
|
|
|
|
|
/// our `to_jian_*` translation layer.
|
|
|
|
|
pub fn raster_memory_cycle(iterations: usize, side: i32) {
|
2026-05-16 15:49:58 +00:00
|
|
|
use op_editor_ui::{Color, Point2D, Rect};
|
|
|
|
|
use op_host_native::NativeBackend;
|
fix(shell-native): Phase A Gate round 1 fixes (Task 2 patches)
Applies Codex Phase A Gate round 1 review (3 BLOCK + 2 CONCERN + 1 NIT)
against the Task 2 SharedSkiaContext + NativeBackend implementation.
BLOCK 1 — `ProviderError::from_msg` `pub(crate)` blocked the Linux EGL
pbuffer test helper from constructing typed provider errors. Promoted
to `pub` so out-of-tree provider impls (test pbuffer, future Step 1f
mobile providers) can produce diagnostically-identical errors.
BLOCK 2 — Linux GPU smoke + chrome-stub-composition tests silently
returned `Ok(())` on EGL pbuffer setup failure, turning acceptance #3 /
#4 into false positives on hosted CI without GPU. Now gated by
`STEP1A_REQUIRE_GPU=1`: real-GPU runners panic on setup failure;
dev / hostless runs surface an explicit `INCONCLUSIVE` marker before
returning. Mirrors the macOS `catch_unwind` skip path in the same file.
BLOCK 3 — `tests/memory_loop.rs` was running 100 cycles against
`SharedSkiaContext::inert_for_test()` (every Option<> field None), so
the RSS budget proved nothing about real allocation lifecycle. Renamed
constructor to `inert_for_lifecycle_test()` (clearer intent) and split
the test into:
- Phase 0 warmup (100 inert + 100 raster) so Skia's lazy
glyph/path/binding caches are populated before measurement;
- Phase 1 lifecycle idempotence (100 inert);
- Phase 2 real-resource cycle: raster surface on macOS / Windows
(winit::EventLoop main-thread-only on macOS; Win Actions runner
has no GPU per spec §8.1), full EGL pbuffer + GL surface on Linux
when `STEP1A_REQUIRE_GPU=1`, raster fallback otherwise.
Budget kept at 5 % per acceptance #6 with a 1.5 MB absolute floor to
absorb macOS sysinfo's coarse RSS sampling jitter on small baselines.
CONCERN 1 — `GlContextProvider` had three non-spec methods (`resize`,
`size`, `default_framebuffer_id`). Audit:
- `resize`: actually used by `SharedSkiaContext::resize` (window /
pbuffer resize → Skia FBO rewrap). KEPT, spec mini-patch
documented in comment, escalation needed for spec v19 → v19.1.
- `default_framebuffer_id`: used by `SharedSkiaContext::new` /
`resize` for the FBO id Skia wraps; iOS EAGL provider (Step 1f)
will need non-zero values. KEPT, same escalation path.
- `size`: unused anywhere. DELETED (YAGNI), along with the unused
`size: (u32, u32)` field on `GlutinProvider` and the iOS / Android
stub impls.
CONCERN 2 — `glow_handle: Option<Arc<glow::Context>>` deviates from
spec v19 lines 120-125 + 191 (`Arc<glow::Context>`). Real lifecycle
needs the handle droppable: teardown releases the loaded function
table, `inert_for_lifecycle_test` has no GL backing, Step 1f Android
`on_pause` must drop alongside the EGL context. KEPT as Option<Arc>,
spec mini-patch documented for v19.1 escalation.
NIT 1 — Removed Task 1 link-check helper `placeholder()`. Task 2's
full re-export chain (`SharedSkiaContext`, `NativeBackend`, …)
already proves shell-core ↔ shell-native linkage; placeholder is
YAGNI now.
Verification (macOS local):
- cargo build -p openpencil-shell-native: clean
- cargo test -p openpencil-shell-native: 12/12 pass (8 binaries)
- cargo clippy -p openpencil-shell-native --tests --all-targets
-- -D warnings: clean
- cargo fmt -p openpencil-shell-native -- --check: clean
- memory_loop stress 8 consecutive runs: 8/8 pass
2026-05-05 13:09:00 +00:00
|
|
|
|
|
|
|
|
let mut backend = NativeBackend::with_dpi(1.0);
|
|
|
|
|
for _ in 0..iterations {
|
|
|
|
|
let mut surface = skia_safe::surfaces::raster_n32_premul((side, side))
|
|
|
|
|
.expect("raster_n32_premul allocated");
|
|
|
|
|
// Fill a real rectangle to prevent the optimiser from eliding
|
|
|
|
|
// the surface and to force Skia to actually touch the GPU
|
|
|
|
|
// (raster) backing memory.
|
|
|
|
|
backend.fill_rect(
|
|
|
|
|
surface.canvas(),
|
|
|
|
|
Rect {
|
|
|
|
|
origin: Point2D::new(10.0, 10.0),
|
|
|
|
|
size: Point2D::new((side - 20) as f32, (side - 20) as f32),
|
|
|
|
|
},
|
|
|
|
|
Color::RED,
|
|
|
|
|
);
|
|
|
|
|
// Surface drops here — Skia must release its raster backing.
|
|
|
|
|
drop(surface);
|
|
|
|
|
}
|
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 13:06:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
pub mod egl_pbuffer {
|
|
|
|
|
//! `EglPbufferProvider` — a `GlContextProvider` backed by a Mesa
|
|
|
|
|
//! softpipe EGL pbuffer. No display server / X11 / Wayland needed,
|
|
|
|
|
//! so the test runs on hosted Linux runners (GitHub Actions
|
|
|
|
|
//! `ubuntu-latest` ships Mesa). Spec §9.2 + plan v7 Task 2 Step
|
|
|
|
|
//! 16f Linux path.
|
|
|
|
|
//!
|
|
|
|
|
//! The provider is tiny on purpose — it only does what the GPU
|
|
|
|
|
//! smoke + chrome-stub composition tests need, and it intentionally
|
|
|
|
|
//! does not try to unify with the desktop `GlutinProvider` API
|
|
|
|
|
//! beyond the `GlContextProvider` trait.
|
|
|
|
|
use std::ffi::c_void;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
use khronos_egl as egl;
|
2026-05-16 15:49:58 +00:00
|
|
|
use op_host_native::{GlContextProvider, ProviderError, ProviderResult};
|
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 13:06:00 +00:00
|
|
|
|
|
|
|
|
type Egl = egl::DynamicInstance<egl::EGL1_4>;
|
|
|
|
|
|
|
|
|
|
pub struct EglPbufferProvider {
|
|
|
|
|
egl: Arc<Egl>,
|
|
|
|
|
display: egl::Display,
|
|
|
|
|
context: egl::Context,
|
|
|
|
|
surface: egl::Surface,
|
|
|
|
|
glow: Arc<glow::Context>,
|
|
|
|
|
released: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EglPbufferProvider {
|
|
|
|
|
pub fn new(size: (u32, u32)) -> ProviderResult<Self> {
|
|
|
|
|
let egl: Arc<Egl> = unsafe {
|
|
|
|
|
Arc::new(
|
|
|
|
|
egl::DynamicInstance::<egl::EGL1_4>::load_required()
|
|
|
|
|
.map_err(|e| ProviderError::from_msg(format!("libEGL load: {e}")))?,
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-05 14:21:00 +00:00
|
|
|
// SAFETY: khronos-egl 6.x marks `get_display` unsafe (it dereferences
|
|
|
|
|
// a raw display pointer). DEFAULT_DISPLAY is a well-known sentinel
|
|
|
|
|
// (NULL on most Linux platforms) handled correctly by libEGL.
|
|
|
|
|
let display = unsafe { egl.get_display(egl::DEFAULT_DISPLAY) }
|
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 13:06:00 +00:00
|
|
|
.ok_or_else(|| ProviderError::from_msg("no default EGL display"))?;
|
|
|
|
|
egl.initialize(display)
|
|
|
|
|
.map_err(|e| ProviderError::from_msg(format!("EGL init: {e}")))?;
|
|
|
|
|
egl.bind_api(egl::OPENGL_API)
|
|
|
|
|
.map_err(|e| ProviderError::from_msg(format!("EGL bind_api: {e}")))?;
|
|
|
|
|
|
|
|
|
|
let attribs = [
|
|
|
|
|
egl::SURFACE_TYPE,
|
|
|
|
|
egl::PBUFFER_BIT,
|
|
|
|
|
egl::RENDERABLE_TYPE,
|
|
|
|
|
egl::OPENGL_BIT,
|
|
|
|
|
egl::RED_SIZE,
|
|
|
|
|
8,
|
|
|
|
|
egl::GREEN_SIZE,
|
|
|
|
|
8,
|
|
|
|
|
egl::BLUE_SIZE,
|
|
|
|
|
8,
|
|
|
|
|
egl::ALPHA_SIZE,
|
|
|
|
|
8,
|
|
|
|
|
egl::DEPTH_SIZE,
|
|
|
|
|
0,
|
|
|
|
|
egl::STENCIL_SIZE,
|
|
|
|
|
8,
|
|
|
|
|
egl::NONE,
|
|
|
|
|
];
|
|
|
|
|
let config = egl
|
|
|
|
|
.choose_first_config(display, &attribs)
|
|
|
|
|
.map_err(|e| ProviderError::from_msg(format!("choose_config: {e}")))?
|
|
|
|
|
.ok_or_else(|| ProviderError::from_msg("no matching EGL config"))?;
|
|
|
|
|
|
|
|
|
|
let context_attribs = [egl::NONE];
|
|
|
|
|
let context = egl
|
|
|
|
|
.create_context(display, config, None, &context_attribs)
|
|
|
|
|
.map_err(|e| ProviderError::from_msg(format!("create_context: {e}")))?;
|
|
|
|
|
|
|
|
|
|
let pbuffer_attribs = [
|
|
|
|
|
egl::WIDTH,
|
|
|
|
|
size.0 as i32,
|
|
|
|
|
egl::HEIGHT,
|
|
|
|
|
size.1 as i32,
|
|
|
|
|
egl::NONE,
|
|
|
|
|
];
|
|
|
|
|
let surface = egl
|
|
|
|
|
.create_pbuffer_surface(display, config, &pbuffer_attribs)
|
|
|
|
|
.map_err(|e| ProviderError::from_msg(format!("create_pbuffer: {e}")))?;
|
|
|
|
|
|
|
|
|
|
egl.make_current(display, Some(surface), Some(surface), Some(context))
|
|
|
|
|
.map_err(|e| ProviderError::from_msg(format!("make_current: {e}")))?;
|
|
|
|
|
|
|
|
|
|
let egl_for_loader = Arc::clone(&egl);
|
|
|
|
|
let glow_ctx = unsafe {
|
|
|
|
|
glow::Context::from_loader_function(move |sym| {
|
|
|
|
|
egl_for_loader
|
|
|
|
|
.get_proc_address(sym)
|
|
|
|
|
.map(|p| p as *const c_void)
|
|
|
|
|
.unwrap_or(std::ptr::null())
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
|
egl,
|
|
|
|
|
display,
|
|
|
|
|
context,
|
|
|
|
|
surface,
|
|
|
|
|
glow: Arc::new(glow_ctx),
|
|
|
|
|
released: false,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GlContextProvider for EglPbufferProvider {
|
|
|
|
|
fn make_current(&mut self) -> ProviderResult<()> {
|
|
|
|
|
self.egl
|
|
|
|
|
.make_current(
|
|
|
|
|
self.display,
|
|
|
|
|
Some(self.surface),
|
|
|
|
|
Some(self.surface),
|
|
|
|
|
Some(self.context),
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| ProviderError::from_msg(format!("make_current: {e}")))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn swap_buffers(&mut self) -> ProviderResult<()> {
|
|
|
|
|
// Pbuffers don't have a real "swap" — the EGL spec defines
|
|
|
|
|
// it as a no-op. Treat the call as success so the present
|
|
|
|
|
// path still runs to completion.
|
|
|
|
|
self.egl
|
|
|
|
|
.swap_buffers(self.display, self.surface)
|
|
|
|
|
.map_err(|e| ProviderError::from_msg(format!("swap_buffers: {e}")))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn glow(&self) -> Arc<glow::Context> {
|
|
|
|
|
self.glow.clone()
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:15:00 +00:00
|
|
|
/// EGL pbuffer default FBO = 0 (matches `GlutinProvider`).
|
|
|
|
|
/// Phase A Gate round 2 CONCERN 1 fix — explicit override
|
|
|
|
|
/// required, no trait default body.
|
|
|
|
|
fn default_framebuffer_id(&self) -> u32 {
|
|
|
|
|
0
|
|
|
|
|
}
|
|
|
|
|
|
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 13:06:00 +00:00
|
|
|
fn resize(&mut self, _w: u32, _h: u32) -> ProviderResult<()> {
|
|
|
|
|
// Pbuffer can't be resized once created — tests build the
|
|
|
|
|
// surface at the size they need.
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn release(&mut self) -> ProviderResult<()> {
|
|
|
|
|
if self.released {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
// Make-not-current first; some Mesa builds segfault if the
|
|
|
|
|
// pbuffer surface drops while still bound.
|
|
|
|
|
let _ = self.egl.make_current(self.display, None, None, None);
|
|
|
|
|
let _ = self.egl.destroy_surface(self.display, self.surface);
|
|
|
|
|
let _ = self.egl.destroy_context(self.display, self.context);
|
|
|
|
|
// Keep the EGL display open for other tests in the same
|
|
|
|
|
// process; eglTerminate is process-global on Mesa and a
|
|
|
|
|
// shared display is normal.
|
|
|
|
|
self.released = true;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for EglPbufferProvider {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
let _ = self.release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|