442 lines
20 KiB
Rust
442 lines
20 KiB
Rust
//! Composition and ownership only: the headless executor is not a pixel proof.
|
|
use makepad_hal::events::event_tag as tag;
|
|
use makepad_hal::session::{ExecRecord, HOST};
|
|
use makepad_hal::*;
|
|
|
|
const HOST_WINDOW: Id = Id::new(3, 1);
|
|
const APP_WINDOW: Id = Id::new(7, 1);
|
|
const PANEL: [f32; 4] = [24.0, 24.0, 720.0, 480.0];
|
|
|
|
fn setup() -> (std::sync::Arc<Hal>, u32) {
|
|
let hal = Hal::new();
|
|
let req = OpenReq { backend: backend_id::HEADLESS, app_key: 0,
|
|
flags: open_flags::RETAIN_WINDOWS, dpi_hint: 2.0, parent: 0 };
|
|
assert_eq!(hal.open_host(&req).backend, backend_id::HEADLESS);
|
|
let mut host = CmdWriter::new(HOST);
|
|
host.window_create(HOST_WINDOW, window_kind::NORMAL, Id::NONE, [0.0, 0.0, 1000.0, 700.0], false);
|
|
hal.submit(&host.buf());
|
|
let sid = hal.open(&OpenReq { app_key: 99, ..req }).session;
|
|
assert_ne!(sid, HOST);
|
|
let mut app = CmdWriter::new(sid);
|
|
app.window_create(APP_WINDOW, window_kind::NORMAL, Id::NONE, [0.0; 4], true);
|
|
hal.submit(&app.buf());
|
|
hal.with_world(|w| w.trace_exec = true);
|
|
frame(&hal, sid, APP_WINDOW, 11, target::WINDOW);
|
|
take(&hal, HOST);
|
|
take(&hal, sid);
|
|
(hal, sid)
|
|
}
|
|
|
|
fn frame(hal: &Hal, sid: u32, window: Id, serial: u64, kind: u32) {
|
|
let mut cmd = CmdWriter::new(sid);
|
|
cmd.frame_begin(serial, FrameMeta::default());
|
|
// Two app passes must both finish before the retained host panel is composed.
|
|
for _ in 0..2 {
|
|
cmd.pass(Pass { viewport: [0.0, 0.0, 2000.0, 1400.0],
|
|
load: [pass_load::CLEAR; 5], ..Default::default() });
|
|
}
|
|
cmd.frame_end(window, kind);
|
|
hal.submit(&cmd.buf());
|
|
}
|
|
|
|
fn overlay(hal: &Hal, sid: u32, on: bool) {
|
|
let mut cmd = CmdWriter::new(sid);
|
|
cmd.window_overlay(HOST_WINDOW, on, PANEL, Id::NONE);
|
|
hal.submit(&cmd.buf());
|
|
}
|
|
|
|
fn last(hal: &Hal) -> ExecRecord {
|
|
hal.with_world(|w| w.exec_log.last().unwrap().clone())
|
|
}
|
|
|
|
fn take(hal: &Hal, sid: u32) -> Vec<(RawEvent, Vec<u8>)> {
|
|
hal.with_world(|w| {
|
|
let mut result = Vec::new();
|
|
w.session_mut(sid).unwrap().events.drain_with(|event, payload| {
|
|
result.push((event, payload.to_vec()));
|
|
});
|
|
result
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn host_surface_stays_after_every_app_frame_without_replacing_the_base() {
|
|
let (hal, sid) = setup();
|
|
assert_eq!(last(&hal).layers, [(sid, APP_WINDOW, 11)]);
|
|
overlay(&hal, HOST, true);
|
|
// The app may be idle or parked: a host frame alone reveals the overlay.
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
assert_eq!(last(&hal).layers, [(sid, APP_WINDOW, 11), (HOST, HOST_WINDOW, 101)]);
|
|
for serial in 12..15 {
|
|
frame(&hal, sid, APP_WINDOW, serial, target::WINDOW);
|
|
assert_eq!(last(&hal).layers, [(sid, APP_WINDOW, serial), (HOST, HOST_WINDOW, 101)]);
|
|
}
|
|
frame(&hal, HOST, HOST_WINDOW, 102, target::WINDOW);
|
|
assert_eq!(last(&hal).layers, [(sid, APP_WINDOW, 14), (HOST, HOST_WINDOW, 102)]);
|
|
hal.with_world(|w| {
|
|
let host = &w.session(HOST).unwrap().windows.get(HOST, HOST_WINDOW).unwrap().val;
|
|
assert_eq!(host.presenter, sid);
|
|
assert_eq!(host.presentations, 4, "overlay serials do not pass the app's first-frame gate");
|
|
assert!(host.borrower_presented);
|
|
assert_eq!(w.session(sid).unwrap().stats.passes, 8);
|
|
assert_eq!(w.session(HOST).unwrap().stats.passes, 4);
|
|
});
|
|
assert!(take(&hal, HOST).iter().any(|(e, _)| e.tag == tag::FrameDone));
|
|
assert!(take(&hal, sid).iter().any(|(e, _)| e.tag == tag::FrameDone));
|
|
}
|
|
|
|
#[test]
|
|
fn dismiss_restores_the_idle_base_and_rejects_late_boot_frames() {
|
|
let (hal, sid) = setup();
|
|
overlay(&hal, HOST, true);
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
overlay(&hal, HOST, false);
|
|
hal.with_world(|w| {
|
|
let host = &w.session(HOST).unwrap().windows.get(HOST, HOST_WINDOW).unwrap().val;
|
|
assert!(host.overlay.is_none());
|
|
assert_eq!(host.base_frame, Some((sid, APP_WINDOW, 11)));
|
|
});
|
|
let before = hal.with_world(|w| w.exec_log.len());
|
|
frame(&hal, HOST, HOST_WINDOW, 102, target::WINDOW);
|
|
assert_eq!(hal.with_world(|w| w.exec_log.len()), before);
|
|
frame(&hal, sid, APP_WINDOW, 12, target::WINDOW);
|
|
assert_eq!(last(&hal).layers, [(sid, APP_WINDOW, 12)]);
|
|
}
|
|
|
|
#[test]
|
|
fn input_payloads_go_to_the_overlay_but_geometry_and_frame_beats_reach_the_app() {
|
|
let (hal, sid) = setup();
|
|
overlay(&hal, HOST, true);
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
take(&hal, HOST);
|
|
let payload = "edited text: λ".as_bytes();
|
|
let mut text = RawEvent::text(APP_WINDOW, input::text::TYPED,
|
|
Range { off: 0, len: payload.len() as u32 });
|
|
text.time_ns = 1234;
|
|
let inputs = [
|
|
RawEvent::mouse_down(APP_WINDOW, 30.0, 40.0, 0, 0),
|
|
RawEvent::key_down(APP_WINDOW, 1, false, 0),
|
|
RawEvent::scroll(APP_WINDOW, 30.0, 40.0, 0.0, 12.0, 0, true, 0),
|
|
RawEvent::drag_query(APP_WINDOW, 30.0, 40.0),
|
|
];
|
|
hal.with_world(|w| {
|
|
for event in inputs { w.window_event(sid, event, &[]); }
|
|
w.window_event(sid, text, payload);
|
|
w.window_event(sid, RawEvent::frame_ready(APP_WINDOW, 77), &[]);
|
|
w.window_event(sid, RawEvent::geom(APP_WINDOW, 2.0, 3.0, 900.0, 650.0, 2.0, 0), &[]);
|
|
});
|
|
let host = take(&hal, HOST);
|
|
assert_eq!(host.len(), inputs.len() + 2);
|
|
assert!(host.iter().all(|(event, _)| event.window == HOST_WINDOW));
|
|
let received = host.iter().find(|(event, _)| event.tag == tag::Text).unwrap();
|
|
assert_eq!(received.0.time_ns, 1234);
|
|
assert_eq!(received.1, payload);
|
|
let app = take(&hal, sid);
|
|
assert_eq!(app.iter().map(|(e, _)| e.tag).collect::<Vec<_>>(), [tag::FrameReady, tag::Geom]);
|
|
assert!(app.iter().all(|(event, _)| event.window == APP_WINDOW));
|
|
overlay(&hal, HOST, false);
|
|
hal.with_world(|w| w.window_event(sid, text, payload));
|
|
assert!(take(&hal, HOST).is_empty());
|
|
// the dismissed panel asks the app for a frame (its last one stayed under the panel)
|
|
let app = take(&hal, sid);
|
|
assert_eq!(app.iter().map(|(e, _)| e.tag).collect::<Vec<_>>(), [tag::Redraw, tag::Text]);
|
|
assert!(app.iter().all(|(event, _)| event.window == APP_WINDOW));
|
|
assert_eq!(app[1].1, payload);
|
|
}
|
|
|
|
fn shown(hal: &Hal) -> Vec<(u32, Id, u64)> {
|
|
last(hal).shown
|
|
}
|
|
|
|
fn layer_on(hal: &Hal) -> bool {
|
|
hal.with_world(|w| w.session(HOST).unwrap().windows.get(HOST, HOST_WINDOW).unwrap().val.layer_on)
|
|
}
|
|
|
|
/// The 166 report (2026-10-25): the run's view over a running Sandbox and the game alternated
|
|
/// frame by frame, because the backend (Vulkan) composes no panels and both presented. The panel
|
|
/// and the app never both reach the window: while the panel is up only its frames do, the app's
|
|
/// frames render under it and are not seen; after a dismiss only the app's.
|
|
#[test]
|
|
fn a_composed_panel_and_the_app_never_both_reach_the_window() {
|
|
let (hal, sid) = setup();
|
|
assert_eq!(shown(&hal), [(sid, APP_WINDOW, 11)]);
|
|
overlay(&hal, HOST, true);
|
|
assert!(layer_on(&hal), "the executor composes the panel through the window's layer");
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
assert_eq!(shown(&hal), [(HOST, HOST_WINDOW, 101)]);
|
|
// the app keeps drawing (a game at 120 fps): its frames finish but do not reach the window
|
|
for serial in 12..16 {
|
|
frame(&hal, sid, APP_WINDOW, serial, target::WINDOW);
|
|
assert_eq!(shown(&hal), [], "app frame {serial} under the panel");
|
|
frame(&hal, HOST, HOST_WINDOW, 90 + serial, target::WINDOW);
|
|
assert_eq!(shown(&hal), [(HOST, HOST_WINDOW, 90 + serial)]);
|
|
}
|
|
assert_eq!(frames_done(&take(&hal, sid)), 4, "the app's frames are answered: it is not stalled");
|
|
// a dismiss: the app owns the window again, and draws a new frame for it
|
|
overlay(&hal, HOST, false);
|
|
assert!(!layer_on(&hal));
|
|
assert!(take(&hal, sid).iter().any(|(e, _)| e.tag == tag::Redraw));
|
|
let before = hal.with_world(|w| w.exec_log.len());
|
|
frame(&hal, HOST, HOST_WINDOW, 120, target::WINDOW);
|
|
assert_eq!(hal.with_world(|w| w.exec_log.len()), before, "late view frames are refused");
|
|
frame(&hal, sid, APP_WINDOW, 16, target::WINDOW);
|
|
assert_eq!(shown(&hal), [(sid, APP_WINDOW, 16)]);
|
|
}
|
|
|
|
#[test]
|
|
fn badges_wait_under_a_composed_panel_and_come_back_after_it() {
|
|
let (hal, sid) = setup();
|
|
badges(&hal, 21, true);
|
|
frame(&hal, sid, APP_WINDOW, 12, target::WINDOW);
|
|
assert_eq!(shown(&hal), [(sid, APP_WINDOW, 12), (HOST, HOST_WINDOW, 21)]);
|
|
take(&hal, HOST);
|
|
overlay(&hal, HOST, true);
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
assert_eq!(shown(&hal), [(HOST, HOST_WINDOW, 101)]);
|
|
// a new badge frame (the ring turning) is kept and answered, not drawn over the panel
|
|
badges(&hal, 22, true);
|
|
assert_eq!(frames_done(&take(&hal, HOST)), 2);
|
|
frame(&hal, sid, APP_WINDOW, 13, target::WINDOW);
|
|
assert_eq!(shown(&hal), []);
|
|
overlay(&hal, HOST, false);
|
|
assert!(layer_on(&hal), "the badges keep the layer");
|
|
frame(&hal, sid, APP_WINDOW, 14, target::WINDOW);
|
|
assert_eq!(shown(&hal), [(sid, APP_WINDOW, 14), (HOST, HOST_WINDOW, 22)]);
|
|
}
|
|
|
|
/// A restart (R in the view, or a crash and relaunch) ends the loan with the panel up: the layer
|
|
/// goes with it, and the next borrower's frames reach the window as they are.
|
|
#[test]
|
|
fn a_restart_under_a_composed_panel_gives_the_next_app_the_window() {
|
|
let (hal, sid) = setup();
|
|
overlay(&hal, HOST, true);
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
assert!(layer_on(&hal));
|
|
let mut cmd = CmdWriter::new(sid);
|
|
cmd.window_close(APP_WINDOW);
|
|
hal.submit(&cmd.buf());
|
|
assert!(!layer_on(&hal), "the panel went with the loan");
|
|
// the boot draws its load screen in the meantime, as it is
|
|
frame(&hal, HOST, HOST_WINDOW, 102, target::WINDOW);
|
|
assert_eq!(shown(&hal), [(HOST, HOST_WINDOW, 102)]);
|
|
let next_window = Id::new(APP_WINDOW.index, 2);
|
|
cmd.clear();
|
|
cmd.window_create(next_window, window_kind::NORMAL, Id::NONE, [0.0; 4], true);
|
|
hal.submit(&cmd.buf());
|
|
frame(&hal, sid, next_window, 12, target::WINDOW);
|
|
assert_eq!(shown(&hal), [(sid, next_window, 12)]);
|
|
// and its input is its own once it presented
|
|
take(&hal, HOST);
|
|
take(&hal, sid);
|
|
hal.with_world(|w| w.window_event(sid, RawEvent::drag_query(next_window, 30.0, 40.0), &[]));
|
|
assert!(take(&hal, HOST).is_empty());
|
|
assert_eq!(take(&hal, sid)[0].0.tag, tag::DragQuery);
|
|
}
|
|
|
|
/// A session ends (the domain's process side is torn down without closing its window): the
|
|
/// world takes the loan back; the layer follows on the window's next frame.
|
|
#[test]
|
|
fn a_borrower_that_ends_under_a_composed_panel_takes_the_layer_on_the_next_frame() {
|
|
let (hal, sid) = setup();
|
|
overlay(&hal, HOST, true);
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
hal.with_world(|w| { w.finish_close(sid, makepad_hal::session::CloseAs::Crash("test".into())); });
|
|
frame(&hal, HOST, HOST_WINDOW, 102, target::WINDOW);
|
|
assert!(!layer_on(&hal));
|
|
assert_eq!(shown(&hal), [(HOST, HOST_WINDOW, 102)]);
|
|
}
|
|
|
|
#[test]
|
|
fn overlay_is_per_window_and_only_the_host_can_control_it() {
|
|
let (hal, sid) = setup();
|
|
overlay(&hal, sid, true);
|
|
assert_eq!(hal.with_world(|w| w.session(sid).unwrap().stats.denied), 1);
|
|
overlay(&hal, HOST, true);
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
let other = Id::new(8, 1);
|
|
let mut cmd = CmdWriter::new(sid);
|
|
cmd.window_create(other, window_kind::NORMAL, Id::NONE, [0.0, 0.0, 320.0, 240.0], false);
|
|
hal.submit(&cmd.buf());
|
|
take(&hal, HOST);
|
|
take(&hal, sid);
|
|
hal.with_world(|w| w.window_event(sid, RawEvent::mouse_down(other, 10.0, 10.0, 0, 0), &[]));
|
|
assert!(take(&hal, HOST).is_empty());
|
|
assert_eq!(take(&hal, sid)[0].0.window, other);
|
|
frame(&hal, sid, other, 12, target::WINDOW);
|
|
assert_eq!(last(&hal).layers, [(sid, other, 12)]);
|
|
// A forged internal target flag cannot make an app frame into a host frame.
|
|
let before = hal.with_world(|w| w.exec_log.len());
|
|
frame(&hal, sid, APP_WINDOW, 13, target::WINDOW | target::OVERLAY);
|
|
assert_eq!(hal.with_world(|w| w.exec_log.len()), before);
|
|
assert_eq!(hal.with_world(|w| w.session(sid).unwrap().stats.bad_args), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn releasing_the_borrower_clears_overlay_ownership_before_readoption() {
|
|
let (hal, sid) = setup();
|
|
overlay(&hal, HOST, true);
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
let mut cmd = CmdWriter::new(sid);
|
|
cmd.window_close(APP_WINDOW);
|
|
hal.submit(&cmd.buf());
|
|
hal.with_world(|w| {
|
|
let host = w.session(HOST).unwrap().windows.get(HOST, HOST_WINDOW).unwrap();
|
|
assert_eq!(host.lent_to, 0);
|
|
assert!(host.val.overlay.is_none());
|
|
});
|
|
frame(&hal, HOST, HOST_WINDOW, 102, target::WINDOW);
|
|
assert_eq!(last(&hal).layers, [(HOST, HOST_WINDOW, 102)]);
|
|
let next_window = Id::new(APP_WINDOW.index, 2);
|
|
cmd.clear();
|
|
cmd.window_create(next_window, window_kind::NORMAL, Id::NONE, [0.0; 4], true);
|
|
hal.submit(&cmd.buf());
|
|
frame(&hal, sid, next_window, 12, target::WINDOW);
|
|
assert_eq!(last(&hal).layers, [(sid, next_window, 12)]);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_overlay_rectangles_leave_app_input_and_composition_intact() {
|
|
let (hal, sid) = setup();
|
|
let mut cmd = CmdWriter::new(HOST);
|
|
cmd.window_overlay(HOST_WINDOW, true, [0.0, 0.0, 0.0, 40.0], Id::NONE);
|
|
cmd.window_overlay(HOST_WINDOW, true, [f32::NAN, 0.0, 40.0, 40.0], Id::NONE);
|
|
cmd.window_overlay(Id::new(HOST_WINDOW.index, 2), true, PANEL, Id::NONE);
|
|
hal.submit(&cmd.buf());
|
|
hal.with_world(|w| {
|
|
let host = w.session(HOST).unwrap();
|
|
assert_eq!(host.stats.bad_args, 2);
|
|
assert_eq!(host.stats.stale_ids, 1);
|
|
assert!(host.windows.get(HOST, HOST_WINDOW).unwrap().val.overlay.is_none());
|
|
w.window_event(sid, RawEvent::key_up(APP_WINDOW, 1, 0), &[]);
|
|
});
|
|
assert!(take(&hal, HOST).is_empty());
|
|
assert_eq!(take(&hal, sid)[0].0.tag, tag::KeyUp);
|
|
frame(&hal, sid, APP_WINDOW, 12, target::WINDOW);
|
|
assert_eq!(last(&hal).layers, [(sid, APP_WINDOW, 12)]);
|
|
}
|
|
|
|
#[test]
|
|
fn a_window_layer_leaves_input_with_the_app_and_needs_its_texture() {
|
|
let (hal, sid) = setup();
|
|
const LAYER: Id = Id::new(20, 1);
|
|
let mut cmd = CmdWriter::new(HOST);
|
|
// no such texture yet: refused
|
|
cmd.window_overlay(HOST_WINDOW, true, [0.0; 4], LAYER);
|
|
cmd.texture_create(LAYER, usage::RENDER_TARGET | usage::SAMPLED, format::BGRA8, 2000, 1400, [1, 1], Attr::default());
|
|
cmd.window_overlay(HOST_WINDOW, true, [0.0; 4], LAYER);
|
|
hal.submit(&cmd.buf());
|
|
hal.with_world(|w| {
|
|
let host = w.session(HOST).unwrap();
|
|
assert_eq!(host.stats.bad_args, 1);
|
|
assert_eq!(host.windows.get(HOST, HOST_WINDOW).unwrap().val.overlay.as_ref().map(|o| o.layer), Some(LAYER));
|
|
w.window_event(sid, RawEvent::key_up(APP_WINDOW, 1, 0), &[]);
|
|
});
|
|
assert!(take(&hal, HOST).is_empty(), "under a window layer input stays with the app");
|
|
assert_eq!(take(&hal, sid)[0].0.tag, tag::KeyUp);
|
|
// session 0's frames go on while the app keeps drawing; neither replaces the other's record
|
|
frame(&hal, HOST, HOST_WINDOW, 101, target::WINDOW);
|
|
frame(&hal, sid, APP_WINDOW, 12, target::WINDOW);
|
|
assert_eq!(last(&hal).layers, [(sid, APP_WINDOW, 12), (HOST, HOST_WINDOW, 101)]);
|
|
}
|
|
|
|
#[test]
|
|
fn a_hover_badge_sees_the_pointer_over_it_but_never_takes_input() {
|
|
let (hal, sid) = setup();
|
|
const BADGE: Id = Id::new(9, 1);
|
|
// (the headless backend composites no badges: the routing is the world's, set as a badge
|
|
// with `window_style::BADGE_HOVER` sets it)
|
|
hal.with_world(|w| {
|
|
let host = w.session_mut(HOST).unwrap().windows.get_mut(HOST, HOST_WINDOW).unwrap();
|
|
host.val.badge_hover = Some((BADGE, [960.0, 2.0, 24.0, 24.0]));
|
|
});
|
|
let events = [
|
|
// over the app, onto the badge, a click there, a drag query and a scroll there, off it
|
|
RawEvent::mouse_move(APP_WINDOW, 500.0, 10.0, 0.0, 0.0, 0),
|
|
RawEvent::mouse_move(APP_WINDOW, 970.0, 12.0, 0.0, 0.0, 0),
|
|
RawEvent::drag_query(APP_WINDOW, 970.0, 12.0),
|
|
RawEvent::mouse_down(APP_WINDOW, 970.0, 12.0, 1, 0),
|
|
RawEvent::mouse_up(APP_WINDOW, 970.0, 12.0, 1, 0),
|
|
RawEvent::scroll(APP_WINDOW, 970.0, 12.0, 0.0, 3.0, 0, true, 0),
|
|
RawEvent::mouse_move(APP_WINDOW, 800.0, 12.0, 0.0, 0.0, 0),
|
|
];
|
|
hal.with_world(|w| for e in events { w.window_event(sid, e, &[]) });
|
|
// session 0 sees the hover only: the move over the badge and the leave
|
|
let host: Vec<_> = take(&hal, HOST).iter().map(|(e, _)| (e.tag, e.window)).collect();
|
|
assert_eq!(host, [(tag::MouseMove, BADGE), (tag::MouseLeave, BADGE)]);
|
|
// the app gets every event, the press, the caption question and the scroll over the badge too
|
|
let app: Vec<_> = take(&hal, sid).iter().map(|(e, _)| (e.tag, e.window)).collect();
|
|
assert_eq!(app, events.iter().map(|e| (e.tag, APP_WINDOW)).collect::<Vec<_>>());
|
|
}
|
|
|
|
#[test]
|
|
fn the_apps_caption_is_the_host_windows() {
|
|
let (hal, sid) = setup();
|
|
let mut app = CmdWriter::new(sid);
|
|
app.window_caption(APP_WINDOW, 0.0, 28.0, [7.0, 6.0, 54.0, 16.0], true);
|
|
hal.submit(&app.buf());
|
|
hal.with_world(|w| {
|
|
let host = &w.session(HOST).unwrap().windows.get(HOST, HOST_WINDOW).unwrap().val;
|
|
assert_eq!(host.app_caption, Some(AppCaption { top: 0.0, height: 28.0, buttons: [7.0, 6.0, 54.0, 16.0], dark: true }));
|
|
});
|
|
}
|
|
|
|
/// Session 0's badge layer frame of the host window: one pass, a draw when `draws` (its uniforms
|
|
/// borrowed from a buffer gone after the submit).
|
|
fn badges(hal: &Hal, serial: u64, draws: bool) {
|
|
let mut cmd = CmdWriter::new(HOST);
|
|
cmd.frame_begin(serial, FrameMeta::default());
|
|
cmd.pass(Pass { viewport: [0.0, 0.0, 2000.0, 1400.0], ..Default::default() });
|
|
let uniforms: Vec<u8> = (0..32).collect();
|
|
if draws {
|
|
let mut d = Draw::default();
|
|
d.uniforms[0] = Slice::from_bytes(&uniforms);
|
|
cmd.draw(d);
|
|
}
|
|
cmd.frame_end(HOST_WINDOW, target::WINDOW | target::BADGES);
|
|
hal.submit(&cmd.buf());
|
|
drop(uniforms);
|
|
}
|
|
|
|
fn frames_done(events: &[(RawEvent, Vec<u8>)]) -> usize {
|
|
events.iter().filter(|(e, _)| e.tag == tag::FrameDone).count()
|
|
}
|
|
|
|
#[test]
|
|
fn a_badge_layer_is_kept_over_every_frame_until_one_without_draws() {
|
|
let (hal, sid) = setup();
|
|
badges(&hal, 21, true);
|
|
let kept = hal.with_world(|w| w.session(HOST).unwrap().windows.get(HOST, HOST_WINDOW).unwrap().val.badges.clone()).expect("kept");
|
|
// the layer owns what its draw borrowed
|
|
let view = kept.view();
|
|
assert_eq!(unsafe { view.draws[0].uniforms[0].as_bytes() }, (0..32).collect::<Vec<u8>>().as_slice());
|
|
assert_eq!((view.frames[0].passes.off, view.frames[0].draws.off), (0, 0));
|
|
assert_eq!(frames_done(&take(&hal, HOST)), 1);
|
|
// the app's frames are drawn under it: the app hears of them, session 0 does not again
|
|
frame(&hal, sid, APP_WINDOW, 12, target::WINDOW);
|
|
frame(&hal, sid, APP_WINDOW, 13, target::WINDOW);
|
|
assert_eq!(frames_done(&take(&hal, sid)), 2);
|
|
assert_eq!(frames_done(&take(&hal, HOST)), 0);
|
|
assert_eq!(hal.with_world(|w| w.session(HOST).unwrap().frame), 21);
|
|
// a frame without draws ends the layer
|
|
badges(&hal, 22, false);
|
|
assert_eq!(frames_done(&take(&hal, HOST)), 1);
|
|
assert!(hal.with_world(|w| w.session(HOST).unwrap().windows.get(HOST, HOST_WINDOW).unwrap().val.badges.is_none()));
|
|
}
|
|
|
|
#[test]
|
|
fn only_session_0_has_badge_layers_and_a_badge_has_no_frames() {
|
|
let (hal, sid) = setup();
|
|
let bad = |hal: &Hal, sid: u32| hal.with_world(|w| w.session(sid).unwrap().stats.bad_args);
|
|
let before = bad(&hal, sid);
|
|
frame(&hal, sid, APP_WINDOW, 12, target::WINDOW | target::BADGES);
|
|
frame(&hal, sid, APP_WINDOW, 13, target::WINDOW | target::REPLAY);
|
|
assert_eq!(bad(&hal, sid), before + 2);
|
|
const BADGE: Id = Id::new(9, 1);
|
|
let mut host = CmdWriter::new(HOST);
|
|
host.window_create(BADGE, window_kind::BADGE | window_style::BADGE_HOVER, HOST_WINDOW, [960.0, 2.0, 24.0, 24.0], false);
|
|
hal.submit(&host.buf());
|
|
assert!(hal.with_world(|w| w.session(HOST).unwrap().windows.get(HOST, HOST_WINDOW).unwrap().val.badge_hover.is_some()));
|
|
let before = bad(&hal, HOST);
|
|
frame(&hal, HOST, BADGE, 30, target::WINDOW);
|
|
assert_eq!(bad(&hal, HOST), before + 1);
|
|
}
|