dev/perf-host-desktop-frame-pacing-and-presentation #19
@ -20,6 +20,72 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow};
|
|||||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||||
use winit::window::{Window, WindowAttributes, WindowId};
|
use winit::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|
||||||
|
const IDLE_HOST_POLL_DT: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct PresentationState {
|
||||||
|
latest_published_frame: u64,
|
||||||
|
last_presented_frame: Option<u64>,
|
||||||
|
host_invalidated: bool,
|
||||||
|
redraw_requested: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PresentationState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
latest_published_frame: 0,
|
||||||
|
last_presented_frame: None,
|
||||||
|
host_invalidated: true,
|
||||||
|
redraw_requested: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PresentationState {
|
||||||
|
fn note_published_frame(&mut self, frame_index: u64) {
|
||||||
|
if frame_index > self.latest_published_frame {
|
||||||
|
self.latest_published_frame = frame_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalidate_host_surface(&mut self) {
|
||||||
|
self.host_invalidated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_redraw(&self) -> bool {
|
||||||
|
self.host_invalidated || self.last_presented_frame != Some(self.latest_published_frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_request_redraw(&mut self) -> bool {
|
||||||
|
if self.needs_redraw() && !self.redraw_requested {
|
||||||
|
self.redraw_requested = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_presented(&mut self) {
|
||||||
|
self.redraw_requested = false;
|
||||||
|
self.host_invalidated = false;
|
||||||
|
self.last_presented_frame = Some(self.latest_published_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_control_flow(
|
||||||
|
now: Instant,
|
||||||
|
machine_running: bool,
|
||||||
|
accumulator: Duration,
|
||||||
|
frame_target_dt: Duration,
|
||||||
|
) -> ControlFlow {
|
||||||
|
if machine_running {
|
||||||
|
let remaining = frame_target_dt.saturating_sub(accumulator);
|
||||||
|
ControlFlow::WaitUntil(now + remaining)
|
||||||
|
} else {
|
||||||
|
ControlFlow::WaitUntil(now + IDLE_HOST_POLL_DT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The Desktop implementation of the PROMETEU Runtime.
|
/// The Desktop implementation of the PROMETEU Runtime.
|
||||||
///
|
///
|
||||||
/// This struct acts as the physical "chassis" of the virtual console. It is
|
/// This struct acts as the physical "chassis" of the virtual console. It is
|
||||||
@ -69,6 +135,8 @@ pub struct HostRunner {
|
|||||||
audio: HostAudio,
|
audio: HostAudio,
|
||||||
/// Last known pause state to sync with audio.
|
/// Last known pause state to sync with audio.
|
||||||
last_paused_state: bool,
|
last_paused_state: bool,
|
||||||
|
/// Tracks whether a new logical frame or host-only surface invalidation requires presentation.
|
||||||
|
presentation: PresentationState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostRunner {
|
impl HostRunner {
|
||||||
@ -105,6 +173,7 @@ impl HostRunner {
|
|||||||
overlay_enabled: false,
|
overlay_enabled: false,
|
||||||
audio: HostAudio::new(),
|
audio: HostAudio::new(),
|
||||||
last_paused_state: false,
|
last_paused_state: false,
|
||||||
|
presentation: PresentationState::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +192,20 @@ impl HostRunner {
|
|||||||
w.request_redraw();
|
w.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn invalidate_host_surface(&mut self) {
|
||||||
|
self.presentation.invalidate_host_surface();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_redraw_if_needed(&mut self) {
|
||||||
|
if self.presentation.should_request_redraw() {
|
||||||
|
self.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn machine_running(&self) -> bool {
|
||||||
|
!self.firmware.os.paused && !self.debugger.waiting_for_start
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationHandler for HostRunner {
|
impl ApplicationHandler for HostRunner {
|
||||||
@ -157,7 +240,9 @@ impl ApplicationHandler for HostRunner {
|
|||||||
eprintln!("[HostAudio] Disabled: {}", err);
|
eprintln!("[HostAudio] Disabled: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
event_loop.set_control_flow(ControlFlow::Poll);
|
self.invalidate_host_surface();
|
||||||
|
self.request_redraw_if_needed();
|
||||||
|
event_loop.set_control_flow(ControlFlow::Wait);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
||||||
@ -168,11 +253,15 @@ impl ApplicationHandler for HostRunner {
|
|||||||
|
|
||||||
WindowEvent::Resized(size) => {
|
WindowEvent::Resized(size) => {
|
||||||
self.resize_surface(size.width, size.height);
|
self.resize_surface(size.width, size.height);
|
||||||
|
self.invalidate_host_surface();
|
||||||
|
self.request_redraw_if_needed();
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowEvent::ScaleFactorChanged { .. } => {
|
WindowEvent::ScaleFactorChanged { .. } => {
|
||||||
let size = self.window().inner_size();
|
let size = self.window().inner_size();
|
||||||
self.resize_surface(size.width, size.height);
|
self.resize_surface(size.width, size.height);
|
||||||
|
self.invalidate_host_surface();
|
||||||
|
self.request_redraw_if_needed();
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
@ -198,6 +287,8 @@ impl ApplicationHandler for HostRunner {
|
|||||||
|
|
||||||
if pixels.render().is_err() {
|
if pixels.render().is_err() {
|
||||||
event_loop.exit();
|
event_loop.exit();
|
||||||
|
} else {
|
||||||
|
self.presentation.mark_presented();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,11 +298,15 @@ impl ApplicationHandler for HostRunner {
|
|||||||
|
|
||||||
if is_down && code == KeyCode::KeyD && self.debugger.waiting_for_start {
|
if is_down && code == KeyCode::KeyD && self.debugger.waiting_for_start {
|
||||||
self.debugger.waiting_for_start = false;
|
self.debugger.waiting_for_start = false;
|
||||||
|
self.invalidate_host_surface();
|
||||||
|
self.request_redraw_if_needed();
|
||||||
println!("[Debugger] Execution started!");
|
println!("[Debugger] Execution started!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_down && code == KeyCode::F1 {
|
if is_down && code == KeyCode::F1 {
|
||||||
self.overlay_enabled = !self.overlay_enabled;
|
self.overlay_enabled = !self.overlay_enabled;
|
||||||
|
self.invalidate_host_surface();
|
||||||
|
self.request_redraw_if_needed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,7 +317,11 @@ impl ApplicationHandler for HostRunner {
|
|||||||
|
|
||||||
/// Called by `winit` when the application is idle and ready to perform updates.
|
/// Called by `winit` when the application is idle and ready to perform updates.
|
||||||
/// This is where the core execution loop lives.
|
/// This is where the core execution loop lives.
|
||||||
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
|
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
let was_debugger_connected = self.debugger.stream.is_some();
|
||||||
|
let was_waiting_for_start = self.debugger.waiting_for_start;
|
||||||
|
let was_paused = self.firmware.os.paused;
|
||||||
|
|
||||||
// 1. Process pending debug commands from the network.
|
// 1. Process pending debug commands from the network.
|
||||||
self.debugger.check_commands(&mut self.firmware, &mut self.hardware);
|
self.debugger.check_commands(&mut self.firmware, &mut self.hardware);
|
||||||
|
|
||||||
@ -283,6 +382,15 @@ impl ApplicationHandler for HostRunner {
|
|||||||
self.stats.record_frame();
|
self.stats.record_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.presentation.note_published_frame(self.firmware.os.logical_frame_index);
|
||||||
|
|
||||||
|
if was_debugger_connected != self.debugger.stream.is_some()
|
||||||
|
|| was_waiting_for_start != self.debugger.waiting_for_start
|
||||||
|
|| was_paused != self.firmware.os.paused
|
||||||
|
{
|
||||||
|
self.invalidate_host_surface();
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Feedback and Synchronization.
|
// 4. Feedback and Synchronization.
|
||||||
self.audio.update_stats(&mut self.stats);
|
self.audio.update_stats(&mut self.stats);
|
||||||
|
|
||||||
@ -298,9 +406,15 @@ impl ApplicationHandler for HostRunner {
|
|||||||
};
|
};
|
||||||
self.log_sink.process_events(new_events);
|
self.log_sink.process_events(new_events);
|
||||||
|
|
||||||
// 5. Request redraw so the host surface can present the latest machine frame
|
// 5. Request redraw only when a new logical frame was published or the
|
||||||
// and, when enabled, compose the overlay in the host-only RGBA surface.
|
// host-owned presentation surface became invalid.
|
||||||
self.request_redraw();
|
self.request_redraw_if_needed();
|
||||||
|
event_loop.set_control_flow(desired_control_flow(
|
||||||
|
Instant::now(),
|
||||||
|
self.machine_running(),
|
||||||
|
self.accumulator,
|
||||||
|
self.frame_target_dt,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,6 +427,70 @@ mod tests {
|
|||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn presentation_state_requires_initial_redraw_only_once() {
|
||||||
|
let mut state = PresentationState::default();
|
||||||
|
|
||||||
|
assert!(state.should_request_redraw());
|
||||||
|
assert!(!state.should_request_redraw());
|
||||||
|
|
||||||
|
state.mark_presented();
|
||||||
|
|
||||||
|
assert!(!state.should_request_redraw());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn presentation_state_requests_redraw_for_new_frame_publication() {
|
||||||
|
let mut state = PresentationState::default();
|
||||||
|
state.mark_presented();
|
||||||
|
|
||||||
|
state.note_published_frame(1);
|
||||||
|
|
||||||
|
assert!(state.should_request_redraw());
|
||||||
|
state.mark_presented();
|
||||||
|
assert_eq!(state.last_presented_frame, Some(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn presentation_state_requests_redraw_for_host_invalidation_without_new_frame() {
|
||||||
|
let mut state = PresentationState::default();
|
||||||
|
state.mark_presented();
|
||||||
|
|
||||||
|
state.invalidate_host_surface();
|
||||||
|
|
||||||
|
assert!(state.should_request_redraw());
|
||||||
|
state.mark_presented();
|
||||||
|
assert!(!state.needs_redraw());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn desired_control_flow_waits_until_next_logical_deadline_while_running() {
|
||||||
|
let now = Instant::now();
|
||||||
|
let flow =
|
||||||
|
desired_control_flow(now, true, Duration::from_millis(5), Duration::from_millis(16));
|
||||||
|
|
||||||
|
match flow {
|
||||||
|
ControlFlow::WaitUntil(deadline) => {
|
||||||
|
assert_eq!(deadline.duration_since(now), Duration::from_millis(11));
|
||||||
|
}
|
||||||
|
other => panic!("unexpected control flow: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn desired_control_flow_uses_idle_poll_when_machine_is_not_running() {
|
||||||
|
let now = Instant::now();
|
||||||
|
let flow =
|
||||||
|
desired_control_flow(now, false, Duration::from_millis(5), Duration::from_millis(16));
|
||||||
|
|
||||||
|
match flow {
|
||||||
|
ControlFlow::WaitUntil(deadline) => {
|
||||||
|
assert_eq!(deadline.duration_since(now), IDLE_HOST_POLL_DT);
|
||||||
|
}
|
||||||
|
other => panic!("unexpected control flow: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn host_debugger_maps_cert_events_from_host_owned_sources() {
|
fn host_debugger_maps_cert_events_from_host_owned_sources() {
|
||||||
let telemetry = TelemetryFrame {
|
let telemetry = TelemetryFrame {
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
{"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"workflow/agendas/AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"workflow/agendas/AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[{"id":"AGD-0007","file":"workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0005","file":"workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0005","file":"workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0026","file":"lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[{"id":"AGD-0007","file":"workflow/agendas/AGD-0007-perf-runtime-telemetry-hot-path.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0005","file":"workflow/decisions/DEC-0005-perf-push-based-telemetry-model.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0005","file":"workflow/plans/PLN-0005-perf-push-based-telemetry-implementation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0026","file":"lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0009","status":"open","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0008","file":"workflow/agendas/AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0009","status":"open","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0008","file":"workflow/agendas/AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0010","status":"in_progress","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0009","file":"workflow/agendas/AGD-0009-perf-host-desktop-frame-pacing-and-presentation.md","status":"in_progress","created_at":"2026-03-27","updated_at":"2026-04-20"}],"decisions":[{"id":"DEC-0019","file":"DEC-0019-host-desktop-frame-pacing-and-presentation.md","status":"accepted","created_at":"2026-04-20","updated_at":"2026-04-20","ref_agenda":"AGD-0009"}],"plans":[{"id":"PLN-0036","file":"PLN-0036-host-desktop-frame-pacing-and-presentation.md","status":"review","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0019"]}],"lessons":[]}
|
{"type":"discussion","id":"DSC-0010","status":"in_progress","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0009","file":"workflow/agendas/AGD-0009-perf-host-desktop-frame-pacing-and-presentation.md","status":"in_progress","created_at":"2026-03-27","updated_at":"2026-04-20"}],"decisions":[{"id":"DEC-0019","file":"DEC-0019-host-desktop-frame-pacing-and-presentation.md","status":"accepted","created_at":"2026-04-20","updated_at":"2026-04-20","ref_agenda":"AGD-0009"}],"plans":[{"id":"PLN-0036","file":"PLN-0036-host-desktop-frame-pacing-and-presentation.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0019"]}],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0011","status":"open","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0010","file":"workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0011","status":"open","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0010","file":"workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0012","status":"done","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0034","file":"lessons/DSC-0012-perf-runtime-introspection-syscalls/LSN-0034-host-owned-debug-boundaries.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19"}]}
|
{"type":"discussion","id":"DSC-0012","status":"done","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0034","file":"lessons/DSC-0012-perf-runtime-introspection-syscalls/LSN-0034-host-owned-debug-boundaries.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19"}]}
|
||||||
{"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
id: PLN-0036
|
id: PLN-0036
|
||||||
ticket: perf-host-desktop-frame-pacing-and-presentation
|
ticket: perf-host-desktop-frame-pacing-and-presentation
|
||||||
title: Plan - Host Desktop Frame Pacing and Presentation
|
title: Plan - Host Desktop Frame Pacing and Presentation
|
||||||
status: review
|
status: done
|
||||||
created: 2026-04-20
|
created: 2026-04-20
|
||||||
completed:
|
completed: 2026-04-20
|
||||||
tags: [perf, host, desktop, frame-pacing, presentation, debug]
|
tags: [perf, host, desktop, frame-pacing, presentation, debug]
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -131,6 +131,16 @@ The host may:
|
|||||||
- observe buffers separately
|
- observe buffers separately
|
||||||
- identify excessive redraw
|
- identify excessive redraw
|
||||||
|
|
||||||
|
Host-owned overlay or inspection surfaces MUST observe the last published logical frame rather than force continuous redraw to probe for changes.
|
||||||
|
|
||||||
|
When overlay, debugger, or other host-only diagnostics become visually stable:
|
||||||
|
|
||||||
|
- the host MUST preserve the last valid visible image;
|
||||||
|
- the host MUST NOT keep recomposing the surface by continuous polling alone;
|
||||||
|
- additional redraw is only justified by a newly published logical frame or a host-owned invalidation event such as overlay toggle, resize, expose, or explicit debugger-visible state transition.
|
||||||
|
|
||||||
|
Paused or breakpointed execution does not grant permission to swap logical machine buffers just to sustain a host-only HUD.
|
||||||
|
|
||||||
## 6 Time Profiling (Cycles)
|
## 6 Time Profiling (Cycles)
|
||||||
|
|
||||||
### 6.1 Per-Frame Measurement
|
### 6.1 Per-Frame Measurement
|
||||||
|
|||||||
@ -127,6 +127,14 @@ The platform layer:
|
|||||||
- **may overlay technical HUDs without modifying the logical framebuffer**
|
- **may overlay technical HUDs without modifying the logical framebuffer**
|
||||||
- may transport the logical framebuffer into a host presentation surface where a host-only overlay layer is composed
|
- may transport the logical framebuffer into a host presentation surface where a host-only overlay layer is composed
|
||||||
|
|
||||||
|
Host presentation SHOULD be driven by published logical frames and explicit host-owned invalidation, not by perpetual redraw polling.
|
||||||
|
|
||||||
|
In particular:
|
||||||
|
|
||||||
|
- a stable logical frame MAY remain visible across multiple host wakeups without recomposition;
|
||||||
|
- a host MAY redraw the same logical frame again when its own surface is invalidated by resize, expose, or host-only overlay/debug changes;
|
||||||
|
- a host MUST NOT invent intermediate logical frames or require continuous redraw merely to discover whether a new logical frame exists.
|
||||||
|
|
||||||
## 9 Debug and Inspection Isolation
|
## 9 Debug and Inspection Isolation
|
||||||
|
|
||||||
To preserve portability and certification purity, technical inspection tools (like the Debug Overlay) are moved to the Host layer.
|
To preserve portability and certification purity, technical inspection tools (like the Debug Overlay) are moved to the Host layer.
|
||||||
@ -144,6 +152,8 @@ Inspection is facilitated by a lockless, push-based atomic interface:
|
|||||||
2. **Asynchronous Observation:** The Host layer reads snapshots of these counters at its own display frequency.
|
2. **Asynchronous Observation:** The Host layer reads snapshots of these counters at its own display frequency.
|
||||||
3. **Loop Purity:** This ensures that the VM execution loop remains deterministic and free from synchronization overhead (locks) that could vary across host architectures.
|
3. **Loop Purity:** This ensures that the VM execution loop remains deterministic and free from synchronization overhead (locks) that could vary across host architectures.
|
||||||
|
|
||||||
|
Reading host-owned telemetry does not imply a perpetual presentation loop. The host may wake on its own schedule for inspection purposes while still presenting the logical framebuffer only when a published frame or host-owned invalidation requires it.
|
||||||
|
|
||||||
## 10 File System and Persistence
|
## 10 File System and Persistence
|
||||||
|
|
||||||
PROMETEU defines a **sandbox logical filesystem**:
|
PROMETEU defines a **sandbox logical filesystem**:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user