app.dhover×314 error×76all
// F04 vsync / frame pacing on Wayland (../../f04-frame-pacing.md): how steady
// is the native frame clock, and what happens to it when the window goes away?
//
// Wayland's "draw now, in sync with the display" primitive is the
// wl_surface.frame callback: request one before commit, and the compositor
// fires `done(callback_data)` when it is "a good time to start drawing a new
// frame". The demo drives a trivially cheap redraw (solid color flip, two
// alternating colors) from NOTHING but that callback — no sleep, no timer, no
// busy loop — and:
//
//   1. logs `frame_callback t=<callback_data>` for 600 consecutive measured
//      frames, then computes min/median/p99/max inter-frame delta and a
//      coarse 1 ms histogram (printed to stdout at exit). Deltas are measured
//      on the client's own MonoTime µs clock; the callback's ms-resolution
//      time argument is logged alongside for clock-identity comparison.
//   2. probes occlusion: requests xdg_toplevel.set_minimized, keeps a frame
//      callback requested, and measures — against an independent poll(2)
//      timeout clock, so a callback that never comes cannot deadlock the
//      demo — how long the frame clock simply STOPS. After ~3 s it restores
//      via set_maximized + a fresh attach/commit and logs the resumption gap.
//   3. dumps every registry global (`global iface=… version=…`), which
//      answers whether wp_presentation (the protocol that adds real
//      presentation timestamps to this "draw now" signal) is advertised.
//
// Headless caveat, prominently: under `weston --backend=headless` the repaint
// clock is synthetic (default 60 Hz; `--refresh-rate=N` overrides). The
// numbers characterize the protocol's delivery jitter, not hardware vsync.
//
// Event-loop note: instead of the scaffold's blocking wl_display_dispatch,
// this demo runs the libwayland prepare_read/poll/read_events/dispatch_pending
// pattern so the poll timeout doubles as the independent clock for phase 2.
//
// Based on the scaffold (../scaffold/app.d, findings ../../scaffold.md);
// instrumentation contract: ./instrument.d. Headless-safe: no compositor →
// SKIP, exit 0.
module 
(module) app
app
;
import c; // ImportC: <wayland-client.h> + xdg-shell glue + wsi_* wrappers
unable to read module `c` Expected 'c.d' or 'c/package.d' in one of the following import paths:
unable to read module `c` Expected 'c.d' or 'c/package.d' in one of the following import paths:
import instrument;
unable to read module `instrument` Expected 'instrument.d' or 'instrument/package.d' in one of the following import paths:
unable to read module `instrument` Expected 'instrument.d' or 'instrument/package.d' in one of the following import paths:
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.stdio

D header file for C99 <stdio.h>

pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h

Source

core/stdc/stdio.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly, Alex Rønne Petersen@standardsISO/IEC 9899:1999 (E)
stdio
:
(alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
;
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.stdlib

D header file for C99.

pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h

Source

core/stdc/stdlib.d

@copyrightCopyright Sean Kelly 2005 - 2014.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly@standardsISO/IEC 9899:1999 (E)
stdlib
:
(alias) app.getenv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogc
getenv
,
(alias) app.qsort = void core.stdc.stdlib.qsort(void* base, ulong nmemb, ulong size, extern (C) int function(const(void*), const(void*)) compar) nothrow @nogc
qsort
;
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.string

D header file for C99.

pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h

Source

core/stdc/string.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly@standardsISO/IEC 9899:1999 (E)
string
:
(alias) app.strcmp = int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogc
strcmp
;
// ----------------------------------------------------------------- tunables enum int
(constant) int app.defaultWidth = 640
defaultWidth
= 640; // used while the compositor lets us pick (0x0 configure)
enum int
(constant) int app.defaultHeight = 480
defaultHeight
= 480;
enum int
(constant) int app.measuredFrames = 600
measuredFrames
= 600; // inter-frame deltas to collect (601 callbacks)
enum long
(constant) long app.minimizedProbeUs = 3000000L
minimizedProbeUs
= 3_000_000; // hold the minimized state ~3 s
enum long
(constant) long app.restoreTimeoutUs = 3000000L
restoreTimeoutUs
= 3_000_000; // give up if no callback after restore
enum int
(constant) int app.pollTimeoutMs = 50
pollTimeoutMs
= 50; // the independent clock's tick
enum long
(constant) long app.hardCapUs = 30000000L
hardCapUs
= 30_000_000; // absolute wall-clock backstop
enum int
(constant) int app.histBuckets = 40
histBuckets
= 40; // 1 ms histogram buckets: 0..38 ms + overflow
// -------------------------------------------------------------------- state enum
(enum) app.Phase
Phase
{
(enum value) app.Phase.measure = 0
measure
, // collecting the 600 inter-frame deltas
(enum value) app.Phase.minimized = 1
minimized
, // set_minimized sent; counting the silence
(enum value) app.Phase.restoring = 2
restoring
, // set_maximized + commit sent; waiting for the clock to resume
(enum value) app.Phase.done = 3
done
,
} /// One wl_shm-backed ARGB8888 buffer. `busy` is owned by the compositor /// between wl_surface.commit and the wl_buffer.release event. struct
(struct) app.Buffer

One wl_shm-backed ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and the wl_buffer.release event.

Buffer
{ wl_buffer*
(field) _error_ app.Buffer.handle
handle
;
undefined identifier `wl_buffer`
uint*
(field) uint* app.Buffer.pixels
pixels
; // mmap'ed, shared with the compositor
(alias) object.size_t = ulong
size_t
(field) ulong app.Buffer.byteSize
byteSize
;
int
(field) int app.Buffer.width
width
,
(field) int app.Buffer.height
height
;
bool
(field) bool app.Buffer.busy
busy
;
} __gshared { wl_display*
_error_ app.g_display
g_display
;
undefined identifier `wl_display`, did you mean variable `g_display`?
wl_registry*
_error_ app.g_registry
g_registry
;
undefined identifier `wl_registry`, did you mean variable `g_registry`?
wl_compositor*
_error_ app.g_compositor
g_compositor
;
undefined identifier `wl_compositor`, did you mean variable `g_compositor`?
wl_shm*
_error_ app.g_shm
g_shm
;
undefined identifier `wl_shm`, did you mean variable `g_shm`?
wl_seat*
_error_ app.g_seat
g_seat
;
undefined identifier `wl_seat`, did you mean variable `g_seat`?
xdg_wm_base*
_error_ app.g_wmBase
g_wmBase
;
undefined identifier `xdg_wm_base`
wl_surface*
_error_ app.g_surface
g_surface
;
undefined identifier `wl_surface`, did you mean variable `g_surface`?
xdg_surface*
_error_ app.g_xdgSurface
g_xdgSurface
;
undefined identifier `xdg_surface`, did you mean variable `g_surface`?
xdg_toplevel*
_error_ app.g_toplevel
g_toplevel
;
undefined identifier `xdg_toplevel`, did you mean variable `g_toplevel`?
wl_callback*
_error_ app.g_frameCb
g_frameCb
; // at most one outstanding frame callback
undefined identifier `wl_callback`
(struct) app.Buffer

One wl_shm-backed ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and the wl_buffer.release event.

Buffer
[2]
_error_ app.g_buffers
g_buffers
; // double buffering: paint one while the other is on screen
int
(__gshared global) int app.g_width
g_width
= defaultWidth; // last *acked* size — buffers must match it
int
(__gshared global) int app.g_height
g_height
= defaultHeight;
int
(__gshared global) int app.g_pendingWidth
g_pendingWidth
; // from xdg_toplevel.configure (0 = client's choice)
int
(__gshared global) int app.g_pendingHeight
g_pendingHeight
;
bool
(__gshared global) bool app.g_configured
g_configured
; // first configure acked (may attach buffers from now on)
bool
(__gshared global) bool app.g_presented
g_presented
; // first frame callback after the first buffer commit
bool
(__gshared global) bool app.g_running
g_running
= true;
int
(__gshared global) int app.g_frames
g_frames
; // frame callbacks received, total
int
(__gshared global) int app.g_commits
g_commits
; // buffer commits sent
bool
(__gshared global) bool app.g_sawPresentation
g_sawPresentation
; // wp_presentation in the registry?
(enum) app.Phase
Phase
(__gshared global) app.Phase app.g_phase
g_phase
= Phase.
Phase.measure
measure
;
// Phase 1: pacing measurement. long[
(constant) int app.measuredFrames = 600
measuredFrames
]
(__gshared global) long[600] app.g_deltasUs
g_deltasUs
; // inter-callback deltas, client MonoTime µs
int
(__gshared global) int app.g_deltaCount
g_deltaCount
;
long
(__gshared global) long app.g_lastCbUs
g_lastCbUs
= -1; // arrival time of the previous callback (µs)
uint
(__gshared global) uint app.g_lastCbArgMs
g_lastCbArgMs
; // previous callback's own time argument (ms)
uint
(__gshared global) uint app.g_firstCbArgMs
g_firstCbArgMs
,
(__gshared global) uint app.g_finalCbArgMs
g_finalCbArgMs
; // for the callback-arg clock identity
// Phase 2: occlusion probe (all µs on the client MonoTime clock). long
(__gshared global) long app.g_minimizeReqUs
g_minimizeReqUs
= -1; // when set_minimized was requested
long
(__gshared global) long app.g_lastTickBeforeMinUs
g_lastTickBeforeMinUs
= -1; // last callback before the minimize
int
(__gshared global) int app.g_cbWhileMinimized
g_cbWhileMinimized
; // callbacks that fired during the silence
long
(__gshared global) long app.g_restoreReqUs
g_restoreReqUs
= -1; // when set_maximized + commit was sent
long
(__gshared global) long app.g_resumeCbUs
g_resumeCbUs
= -1; // first callback after the restore
} // ---------------------------------------------------------- shm buffer pool /// (Re)allocate `b` so it matches `w`×`h`. Only ever called on non-busy /// buffers, so destroying a stale-sized wl_buffer here is race-free. bool
app.ensureBuffer

(Re)allocate b so it matches w×h. Only ever called on non-busy buffers, so destroying a stale-sized wl_buffer here is race-free.

ensureBuffer
(ref
(struct) app.Buffer

One wl_shm-backed ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and the wl_buffer.release event.

Buffer
(parameter) Buffer b
b
, int
(parameter) int w
w
, int
(parameter) int h
h
) nothrow @nogc
{ if (b.
b.handle
handle
!is null && (b.
b.width
width
!= w || b.
b.height
height
!= h))
{ wsi_buffer_destroy(b.
b.handle
handle
);
munmap(b.
b.pixels
pixels
, b.
b.byteSize
byteSize
);
b = Buffer.init; } if (b.
b.handle
handle
!is null)
return true; immutable
_error_ stride
stride
= w * 4;
immutable
_error_ size
size
= cast(
(unresolved type) size_t
size_t
)(stride) * h;
immutable
_error_ fd
fd
= memfd_create("wsi-f04", MFD_CLOEXEC);
if (fd < 0) return false; if (ftruncate(fd, cast(long) size) != 0) { close(fd); return false; } void*
_error_ mem
mem
= mmap(null, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mem is cast(void*)-1) { close(fd); return false; } // The pool can be destroyed right after create_buffer: the buffer keeps // the backing memory alive, and the compositor has dup'ed the fd. wl_shm_pool*
_error_ pool
pool
= wsi_shm_create_pool(g_shm, fd, cast(int) size);
b.
b.handle
handle
= wsi_shm_pool_create_buffer(pool, 0, w, h, stride, WL_SHM_FORMAT_ARGB8888);
wsi_shm_pool_destroy(pool); close(fd); wsi_buffer_add_listener(b.
b.handle
handle
, &g_bufferListener, &b);
b.
b.pixels
pixels
= cast(uint*) mem;
b.
b.byteSize
byteSize
= size;
b.
b.width
width
= w;
b.
b.height
height
= h;
b.
b.busy
busy
= false;
instrEvent("buffer_alloc", "size=%dx%d bytes=%zu", w, h, size); return true; } /// The trivially cheap frame: a full-window solid fill alternating between /// two colors, so every presented frame is distinguishable but costs only a /// memset-grade loop. void
app.paint

The trivially cheap frame: a full-window solid fill alternating between two colors, so every presented frame is distinguishable but costs only a memset-grade loop.

paint
(ref
(struct) app.Buffer

One wl_shm-backed ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and the wl_buffer.release event.

Buffer
(parameter) Buffer b
b
, int frame) nothrow @nogc
{ immutable uint
_error_ color
color
= (frame & 1) ? 0xff20_6080 : 0xff80_4020;
immutable
_error_ n
n
= cast(
(unresolved type) size_t
size_t
) b.
b.width
width
* b.
b.height
height
;
foreach (
(parameter) i
i
; 0 .. n)
b.
b.pixels
pixels
[i] = color;
} /// Paint into a free buffer and commit it, requesting the next frame callback. void
void app.render() nothrow @nogc

Paint into a free buffer and commit it, requesting the next frame callback.

render
() nothrow @nogc
{
(struct) app.Buffer

One wl_shm-backed ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and the wl_buffer.release event.

Buffer
*
(local variable) _error_ buf
buf
= null;
foreach (ref
(parameter) b
b
; g_buffers)
if (!b.
b.busy
busy
)
{ buf = &b; break; } if (buf is null) { // Both buffers held by the compositor: drop this frame. The // wl_buffer.release handler re-renders as soon as one comes back. instrEvent("frame_skipped", "reason=all_buffers_busy");
undefined identifier `instrEvent`, did you mean import `instrument`?
return; } if (!ensureBuffer(*buf, g_width, g_height)) {
(__gshared global) bool app.g_running
g_running
= false;
return; } paint(*buf, g_frames); // F02 contract: the buffer we are about to commit matches the acked size. assert(buf.
(field) _error_ buf.width
width
==
(__gshared global) int app.g_width
g_width
&& buf.
(field) _error_ buf.height
height
==
(__gshared global) int app.g_height
g_height
,
"committed buffer size does not match the acked configure size"); wsi_surface_attach(g_surface, buf.
buf.handle
handle
, 0, 0);
undefined identifier `wsi_surface_attach`
wsi_surface_damage_buffer(g_surface, 0, 0, buf.
buf.width
width
, buf.
buf.height
height
);
undefined identifier `wsi_surface_damage_buffer`
void app.requestFrameCallback() nothrow @nogc
requestFrameCallback
();
wsi_surface_commit(g_surface);
undefined identifier `wsi_surface_commit`
buf.
buf.busy
busy
= true;
(__gshared global) int app.g_commits
g_commits
++;
if (
(__gshared global) int app.g_commits
g_commits
== 1)
instrEvent("first_commit", "size=%dx%d", buf.
buf.width
width
, buf.
buf.height
height
);
undefined identifier `instrEvent`, did you mean import `instrument`?
} void
void app.requestFrameCallback() nothrow @nogc
requestFrameCallback
() nothrow @nogc
{ if (g_frameCb !is null) // keep exactly one in flight return; g_frameCb = wsi_surface_frame(g_surface); wsi_callback_add_listener(g_frameCb, &g_frameListener, null);
undefined identifier `wsi_callback_add_listener`
} // ---------------------------------------------------------------- listeners // All callbacks are `extern (C) nothrow @nogc` to match the listener // function-pointer types the ImportC `#pragma attribute` stamped in c.c. extern (C) void
app.onGlobal
onGlobal
(void* data, wl_registry* reg, uint name,
undefined identifier `wl_registry`, did you mean variable `g_registry`?
const(char)* iface, uint ver) nothrow @nogc { static uint
uint capped(uint advertised, uint want) nothrow @nogc
capped
(uint
(parameter) uint advertised
advertised
, uint
(parameter) uint want
want
) nothrow @nogc
{ return advertised < want ? advertised : want; } // Full registry dump: F04 needs to know whether wp_presentation — the // protocol that upgrades "draw now" to real presentation timestamps — is // among the advertised globals. instrEvent("global", "iface=%s version=%u", iface, ver); if (strcmp(iface, "wp_presentation") == 0) g_sawPresentation = true; if (strcmp(iface, wl_compositor_interface.name) == 0) g_compositor = cast(wl_compositor*) wsi_registry_bind(reg, name, &wl_compositor_interface, capped(ver, 4)); // v4: wl_surface.damage_buffer else if (strcmp(iface, wl_shm_interface.name) == 0) g_shm = cast(wl_shm*) wsi_registry_bind(reg, name, &wl_shm_interface, 1); else if (strcmp(iface, xdg_wm_base_interface.name) == 0) g_wmBase = cast(xdg_wm_base*) wsi_registry_bind(reg, name, &xdg_wm_base_interface, 1); else if (strcmp(iface, wl_seat_interface.name) == 0) g_seat = cast(wl_seat*) wsi_registry_bind(reg, name, &wl_seat_interface, capped(ver, 2)); // v2: name event } extern (C) void
app.onGlobalRemove
onGlobalRemove
(void* data, wl_registry* reg, uint name) nothrow @nogc
undefined identifier `wl_registry`, did you mean variable `g_registry`?
{ } extern (C) void
app.onWmBasePing
onWmBasePing
(void* data, xdg_wm_base*
(parameter) xdg_wm_base* b
b
, uint serial) nothrow @nogc
undefined identifier `xdg_wm_base`
{ wsi_wm_base_pong(b, serial); // liveness check — answer or be killed } extern (C) void
app.onSeatCapabilities
onSeatCapabilities
(void* data, wl_seat* s, uint caps) nothrow @nogc
undefined identifier `wl_seat`, did you mean variable `g_seat`?
{ } extern (C) void
app.onSeatName
onSeatName
(void* data, wl_seat* s, const(char)* name) nothrow @nogc
undefined identifier `wl_seat`, did you mean variable `g_seat`?
{ } /// xdg_toplevel.configure: latch the suggested size; applied at /// xdg_surface.configure. extern (C) void
app.onToplevelConfigure

xdg_toplevel.configure: latch the suggested size; applied at xdg_surface.configure.

onToplevelConfigure
(void* data, xdg_toplevel* t, int
(parameter) int w
w
, int
(parameter) int h
h
,
undefined identifier `xdg_toplevel`, did you mean variable `g_toplevel`?
wl_array* states) nothrow @nogc
undefined identifier `wl_array`
{ g_pendingWidth = w; g_pendingHeight = h; instrEvent("xdg_toplevel_configure", "size=%dx%d", w, h); } extern (C) void
app.onXdgSurfaceConfigure
onXdgSurfaceConfigure
(void* data, xdg_surface* s, uint serial) nothrow @nogc
undefined identifier `xdg_surface`, did you mean variable `g_surface`?
{ immutable
_error_ w
w
= g_pendingWidth > 0 ? g_pendingWidth : defaultWidth;
immutable
_error_ h
h
= g_pendingHeight > 0 ? g_pendingHeight : defaultHeight;
instrEvent("configure", "serial=%u size=%dx%d", serial, w, h); wsi_xdg_surface_ack_configure(s, serial); immutable
_error_ resized
resized
= w != g_width || h != g_height;
g_width = w; g_height = h; if (!g_configured) { g_configured = true; instrFirstConfigure(); render(); // the first buffer commit — the window becomes visible } else if (resized && g_phase != Phase.
Phase.minimized
minimized
)
{ instrResize(w, h, 1); render(); // commit a matching buffer right away, don't wait a frame } } extern (C) void
app.onToplevelClose
onToplevelClose
(void* data, xdg_toplevel* t) nothrow @nogc
undefined identifier `xdg_toplevel`, did you mean variable `g_toplevel`?
{ instrCloseRequested(); g_running = false; } // Bound at xdg_wm_base v1, so these v4/v5 events never arrive; the listener // struct generated from wayland-protocols 1.47 still has the slots to fill. extern (C) void
app.onToplevelConfigureBounds
onToplevelConfigureBounds
(void* data, xdg_toplevel* t, int
(parameter) int w
w
, int
(parameter) int h
h
) nothrow @nogc
undefined identifier `xdg_toplevel`, did you mean variable `g_toplevel`?
{ } extern (C) void
app.onToplevelWmCapabilities
onToplevelWmCapabilities
(void* data, xdg_toplevel* t, wl_array* caps) nothrow @nogc
undefined identifier `xdg_toplevel`, did you mean variable `g_toplevel`?
undefined identifier `wl_array`
{ } /// wl_buffer.release: the compositor no longer reads the buffer; reuse it. extern (C) void
app.onBufferRelease

wl_buffer.release: the compositor no longer reads the buffer; reuse it.

onBufferRelease
(void* data, wl_buffer*
(parameter) wl_buffer* b
b
) nothrow @nogc
undefined identifier `wl_buffer`
{ auto
_error_ buf
buf
= cast(
(unresolved type) Buffer
Buffer
*) data;
buf.
buf.busy
busy
= false;
// If a frame was dropped because both buffers were busy, recover here. if (g_running && g_configured && g_frameCb is null && g_phase != Phase.
Phase.minimized
minimized
)
render(); } /// wl_surface.frame `done` — the frame clock tick under measurement. /// `timeMs` is the callback's own time argument: milliseconds, "with an /// undefined base" per the spec — its clock identity is an F04 finding. extern (C) void
app.onFrameDone

wl_surface.frame done — the frame clock tick under measurement. timeMs is the callback's own time argument: milliseconds, "with an undefined base" per the spec — its clock identity is an F04 finding.

onFrameDone
(void* data, wl_callback* cb, uint timeMs) nothrow @nogc
undefined identifier `wl_callback`
{ wsi_callback_destroy(cb); // one-shot object g_frameCb = null; g_frames++; immutable
_error_ now
now
= instrNowUs();
instrEvent("frame_callback", "t=%u", timeMs); if (!g_presented) { g_presented = true; g_firstCbArgMs = timeMs; instrFirstPixelPresented(); } final switch (g_phase) { case Phase.
Phase.measure
measure
:
if (g_lastCbUs >= 0 && g_deltaCount < measuredFrames) { g_deltasUs[g_deltaCount] = now - g_lastCbUs; g_deltaCount++; } g_lastCbUs = now; g_lastCbArgMs = timeMs; if (g_deltaCount >= measuredFrames) { // Phase 1 complete → start the occlusion probe. Keep a frame // callback requested across the minimize so any tick the // compositor did deliver to a minimized surface would be seen. g_finalCbArgMs = timeMs; g_lastTickBeforeMinUs = now; g_phase = Phase.
Phase.minimized
minimized
;
g_minimizeReqUs = now; wsi_toplevel_set_minimized(g_toplevel); requestFrameCallback(); wsi_surface_commit(g_surface); // commit makes the frame request current instrEvent("vis_change", "state=minimized_requested"); } else render(); break; case Phase.
Phase.minimized
minimized
:
// The probe's point: does this ever fire while minimized? g_cbWhileMinimized++; instrEvent("frame_callback_while_minimized", "t=%u n=%d", timeMs, g_cbWhileMinimized); requestFrameCallback(); wsi_surface_commit(g_surface); break; case Phase.
Phase.restoring
restoring
:
g_resumeCbUs = now; instrEvent("vis_change", "state=restored resume_after_us=%lld silence_us=%lld", now - g_restoreReqUs, now - g_lastTickBeforeMinUs); g_phase = Phase.
Phase.done
done
;
g_running = false; break; case Phase.
Phase.done
done
:
break; } } __gshared wl_registry_listener
_error_ app.g_registryListener
g_registryListener
= {&onGlobal, &onGlobalRemove};
undefined identifier `wl_registry_listener`
__gshared xdg_wm_base_listener
_error_ app.g_wmBaseListener
g_wmBaseListener
= {&onWmBasePing};
undefined identifier `xdg_wm_base_listener`
__gshared wl_seat_listener
_error_ app.g_seatListener
g_seatListener
= {&onSeatCapabilities, &onSeatName};
undefined identifier `wl_seat_listener`
__gshared xdg_surface_listener
_error_ app.g_xdgSurfaceListener
g_xdgSurfaceListener
= {&onXdgSurfaceConfigure};
undefined identifier `xdg_surface_listener`
__gshared xdg_toplevel_listener
_error_ app.g_toplevelListener
g_toplevelListener
= {
undefined identifier `xdg_toplevel_listener`
&onToplevelConfigure, &onToplevelClose, &onToplevelConfigureBounds, &onToplevelWmCapabilities }; __gshared wl_buffer_listener
_error_ app.g_bufferListener
g_bufferListener
= {&onBufferRelease};
undefined identifier `wl_buffer_listener`
__gshared wl_callback_listener
_error_ app.g_frameListener
g_frameListener
= {&onFrameDone};
undefined identifier `wl_callback_listener`
// ----------------------------------------------- poll-based event pump // The libwayland prepare_read/read_events pattern (thread-safe variant of // wl_display_dispatch) with a poll(2) timeout — the timeout is the // *independent clock* that lets phase 2 measure a frame clock that has // stopped without deadlocking on a callback that never comes. /// Pump once: dispatch pending events, flush, wait up to `timeoutMs`. /// Returns false on connection error. bool
bool app.pumpOnce(int timeoutMs) nothrow @nogc

Pump once: dispatch pending events, flush, wait up to timeoutMs. Returns false on connection error.

pumpOnce
(int
(parameter) int timeoutMs
timeoutMs
) nothrow @nogc
{ while (wl_display_prepare_read(g_display) != 0)
undefined identifier `wl_display_prepare_read`
if (wl_display_dispatch_pending(g_display) < 0)
undefined identifier `wl_display_dispatch_pending`
return false; wl_display_flush(g_display);
undefined identifier `wl_display_flush`
pollfd
(local variable) _error_ pfd
pfd
;
undefined identifier `pollfd`
pfd.
pfd.fd
fd
= wl_display_get_fd(g_display);
pfd.events = POLLIN; immutable
(local variable) immutable(_error_) r
r
= poll(&pfd, 1, timeoutMs);
undefined identifier `poll`
if (r <= 0) // timeout (the independent clock ticked) or error { wl_display_cancel_read(g_display);
undefined identifier `wl_display_cancel_read`
return r == 0; } if (wl_display_read_events(g_display) < 0)
undefined identifier `wl_display_read_events`
return false; return wl_display_dispatch_pending(g_display) >= 0;
undefined identifier `wl_display_dispatch_pending`
} // ------------------------------------------------------------------ stats extern (C) int
int app.cmpLong(const(void)* a, const(void)* b) nothrow @nogc
cmpLong
(const(void)*
(parameter) const(void)* a
a
, const(void)*
(parameter) const(void)* b
b
) nothrow @nogc
{ immutable
(local variable) immutable(long) x
x
= *cast(const(long)*)
(parameter) const(void)* a
a
;
immutable
(local variable) immutable(long) y
y
= *cast(const(long)*)
(parameter) const(void)* b
b
;
return (
(local variable) immutable(long) x
x
>
(local variable) immutable(long) y
y
) - (
(local variable) immutable(long) x
x
<
(local variable) immutable(long) y
y
);
} /// min/median/p99/max + 1 ms histogram over the recorded deltas, to stdout. void
void app.printStats() nothrow @nogc

min/median/p99/max + 1 ms histogram over the recorded deltas, to stdout.

printStats
() nothrow @nogc
{ if (
(__gshared global) int app.g_deltaCount
g_deltaCount
== 0)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("stats: no deltas recorded\n");
return; }
void core.stdc.stdlib.qsort(void* base, ulong nmemb, ulong size, extern (C) int function(const(void*), const(void*)) compar) nothrow @nogc
qsort
(
(__gshared global) long[600] app.g_deltasUs
g_deltasUs
.
(constant) long* long[600].ptr = &g_deltasUs
ptr
,
(__gshared global) int app.g_deltaCount
g_deltaCount
, long.
(constant) ulong long.sizeof = 8LU
sizeof
, &
int app.cmpLong(const(void)* a, const(void)* b) nothrow @nogc
cmpLong
);
immutable long
(local variable) immutable(long) min
min
=
(__gshared global) long[600] app.g_deltasUs
g_deltasUs
[0];
immutable long
(local variable) immutable(long) max
max
=
(__gshared global) long[600] app.g_deltasUs
g_deltasUs
[
(__gshared global) int app.g_deltaCount
g_deltaCount
- 1];
immutable long
(local variable) immutable(long) median
median
=
(__gshared global) long[600] app.g_deltasUs
g_deltasUs
[
(__gshared global) int app.g_deltaCount
g_deltaCount
/ 2];
immutable
(alias) object.size_t = ulong
size_t
(local variable) immutable(ulong) p99Idx
p99Idx
= cast(
(alias) object.size_t = ulong
size_t
)(
(__gshared global) int app.g_deltaCount
g_deltaCount
- 1) * 99 / 100;
immutable long
(local variable) immutable(long) p99
p99
=
(__gshared global) long[600] app.g_deltasUs
g_deltasUs
[
(local variable) immutable(ulong) p99Idx
p99Idx
];
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("stats source=monotonic_us n=%d min_us=%lld median_us=%lld p99_us=%lld max_us=%lld\n",
(__gshared global) int app.g_deltaCount
g_deltaCount
,
(local variable) immutable(long) min
min
,
(local variable) immutable(long) median
median
,
(local variable) immutable(long) p99
p99
,
(local variable) immutable(long) max
max
);
// The callback's own ms-resolution argument, as a cross-check of the // measurement window: (last - first) / n ≈ the same mean period.
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("stats source=callback_arg_ms first=%u last=%u span_ms=%u mean_period_ms=%.3f\n",
(__gshared global) uint app.g_firstCbArgMs
g_firstCbArgMs
,
(__gshared global) uint app.g_finalCbArgMs
g_finalCbArgMs
,
(__gshared global) uint app.g_finalCbArgMs
g_finalCbArgMs
-
(__gshared global) uint app.g_firstCbArgMs
g_firstCbArgMs
,
cast(double)(
(__gshared global) uint app.g_finalCbArgMs
g_finalCbArgMs
-
(__gshared global) uint app.g_firstCbArgMs
g_firstCbArgMs
) /
(__gshared global) int app.g_deltaCount
g_deltaCount
);
int[
(constant) int app.histBuckets = 40
histBuckets
]
(local variable) int[40] hist
hist
;
foreach (
(local variable) int i
i
; 0 ..
(__gshared global) int app.g_deltaCount
g_deltaCount
)
{ auto
(local variable) long bucket
bucket
=
(__gshared global) long[600] app.g_deltasUs
g_deltasUs
[
(local variable) int i
i
] / 1000; // 1 ms buckets
if (
(local variable) long bucket
bucket
>=
(constant) int app.histBuckets = 40
histBuckets
)
(local variable) long bucket
bucket
=
(constant) int app.histBuckets = 40
histBuckets
- 1;
(local variable) int[40] hist
hist
[cast(
(alias) object.size_t = ulong
size_t
)
(local variable) long bucket
bucket
]++;
} foreach (
(local variable) int i
i
; 0 ..
(constant) int app.histBuckets = 40
histBuckets
)
if (
(local variable) int[40] hist
hist
[
(local variable) int i
i
] != 0)
{ if (
(local variable) int i
i
==
(constant) int app.histBuckets = 40
histBuckets
- 1)
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("hist bucket_ms=%d+ count=%d\n",
(local variable) int i
i
,
(local variable) int[40] hist
hist
[
(local variable) int i
i
]);
else
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("hist bucket_ms=%d-%d count=%d ",
(local variable) int i
i
,
(local variable) int i
i
+ 1,
(local variable) int[40] hist
hist
[
(local variable) int i
i
]);
if (
(local variable) int i
i
<
(constant) int app.histBuckets = 40
histBuckets
- 1)
{ foreach (
(local variable) int _
_
; 0 ..
(local variable) int[40] hist
hist
[
(local variable) int i
i
] / 10 + 1)
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("#");
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("\n");
} } } // ----------------------------------------------------------------- teardown void
void app.teardown() nothrow @nogc
teardown
() nothrow @nogc
{ foreach (ref
(parameter) b
b
; g_buffers)
if (b.
b.handle
handle
!is null)
{ wsi_buffer_destroy(b.
b.handle
handle
);
munmap(b.
b.pixels
pixels
, b.
b.byteSize
byteSize
);
b = Buffer.init; } if (g_frameCb !is null) wsi_callback_destroy(g_frameCb);
undefined identifier `wsi_callback_destroy`
if (g_toplevel !is null) wsi_toplevel_destroy(g_toplevel);
undefined identifier `wsi_toplevel_destroy`
if (g_xdgSurface !is null) wsi_xdg_surface_destroy(g_xdgSurface);
undefined identifier `wsi_xdg_surface_destroy`
if (g_surface !is null) wsi_surface_destroy(g_surface);
undefined identifier `wsi_surface_destroy`
if (g_wmBase !is null) wsi_wm_base_destroy(g_wmBase);
undefined identifier `wsi_wm_base_destroy`
// The remaining globals have no destructor request at the bound versions; // destroying the client-side proxy is the correct cleanup. if (g_seat !is null) wl_proxy_destroy(cast(wl_proxy*) g_seat);
undefined identifier `wl_proxy_destroy`
if (g_shm !is null) wl_proxy_destroy(cast(wl_proxy*) g_shm);
undefined identifier `wl_proxy_destroy`
if (g_compositor !is null) wl_proxy_destroy(cast(wl_proxy*) g_compositor);
undefined identifier `wl_proxy_destroy`
if (g_registry !is null) wl_proxy_destroy(cast(wl_proxy*) g_registry);
undefined identifier `wl_proxy_destroy`
wl_display_disconnect(g_display);
undefined identifier `wl_display_disconnect`
} // --------------------------------------------------------------------- main int
int D main()
main
()
{ instrInit("f04_wayland");
undefined identifier `instrInit`
// The pacing demo is meaningless without its bounded run; WSI_AUTO_EXIT // is accepted for runner symmetry but the demo always auto-terminates. cast(void)
char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogc
getenv
("WSI_AUTO_EXIT");
// 1. Connect (SKIP cleanly on headless hosts without a compositor). g_display = wl_display_connect(null); if (g_display is null) {
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("SKIP: no Wayland compositor (wl_display_connect returned null)\n");
return 0; } // 2. Registry: dump every global (the wp_presentation question) and bind // the ones a window needs. g_registry = wsi_display_get_registry(g_display); wsi_registry_add_listener(g_registry, &g_registryListener, null);
undefined identifier `wsi_registry_add_listener`
wl_display_roundtrip(g_display); // blocks; onGlobal binds during this
undefined identifier `wl_display_roundtrip`
if (g_compositor is null || g_shm is null || g_wmBase is null) {
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("SKIP: compositor lacks a required global (wl_compositor/wl_shm/xdg_wm_base)\n");
void app.teardown() nothrow @nogc
teardown
();
return 0; } wsi_wm_base_add_listener(g_wmBase, &g_wmBaseListener, null);
undefined identifier `wsi_wm_base_add_listener`
if (g_seat !is null) wsi_seat_add_listener(g_seat, &g_seatListener, null);
undefined identifier `wsi_seat_add_listener`
// 3. Window object tree: wl_surface → xdg_surface → xdg_toplevel. g_surface = wsi_compositor_create_surface(g_compositor); g_xdgSurface = wsi_wm_base_get_xdg_surface(g_wmBase, g_surface); wsi_xdg_surface_add_listener(g_xdgSurface, &g_xdgSurfaceListener, null);
undefined identifier `wsi_xdg_surface_add_listener`
g_toplevel = wsi_xdg_surface_get_toplevel(g_xdgSurface); wsi_toplevel_add_listener(g_toplevel, &g_toplevelListener, null);
undefined identifier `wsi_toplevel_add_listener`
wsi_toplevel_set_title(g_toplevel, "wsi-f04-frame-pacing");
undefined identifier `wsi_toplevel_set_title`
wsi_toplevel_set_app_id(g_toplevel, "wsi-f04-frame-pacing");
undefined identifier `wsi_toplevel_set_app_id`
instrWindowCreated();
undefined identifier `instrWindowCreated`
// 4. The mandatory initial no-buffer commit; first pixel happens in the // configure handler. wsi_surface_commit(g_surface);
undefined identifier `wsi_surface_commit`
// 5. Poll-pumped event loop. The poll timeout is the independent clock: // when the frame clock stops (phase 2), the loop still ticks every // pollTimeoutMs and the wall-clock checks below run. while (
(__gshared global) bool app.g_running
g_running
)
{ if (!
bool app.pumpOnce(int timeoutMs) nothrow @nogc

Pump once: dispatch pending events, flush, wait up to timeoutMs. Returns false on connection error.

pumpOnce
(
(constant) int app.pollTimeoutMs = 50
pollTimeoutMs
))
break; immutable
(local variable) immutable(_error_) now
now
= instrNowUs();
undefined identifier `instrNowUs`
if (
(__gshared global) app.Phase app.g_phase
g_phase
==
(enum) app.Phase
Phase
.
(enum value) app.Phase.minimized = 1
minimized
&& now -
(__gshared global) long app.g_minimizeReqUs
g_minimizeReqUs
>= minimizedProbeUs)
{ // ~3 s of silence measured — restore. xdg-shell has no // unset_minimized; un-minimizing is the compositor's (user's) // call. set_maximized + a fresh attach/commit is the strongest // client-side "make me visible again" available.
(__gshared global) app.Phase app.g_phase
g_phase
=
(enum) app.Phase
Phase
.
(enum value) app.Phase.restoring = 2
restoring
;
(__gshared global) long app.g_restoreReqUs
g_restoreReqUs
= now;
instrEvent("vis_change",
undefined identifier `instrEvent`, did you mean import `instrument`?
"state=restore_requested minimized_for_us=%lld cb_while_minimized=%d", now - g_minimizeReqUs, g_cbWhileMinimized); wsi_toplevel_set_maximized(g_toplevel);
undefined identifier `wsi_toplevel_set_maximized`
void app.render() nothrow @nogc

Paint into a free buffer and commit it, requesting the next frame callback.

render
(); // fresh attach + damage + frame request + commit
} else if (
(__gshared global) app.Phase app.g_phase
g_phase
==
(enum) app.Phase
Phase
.
(enum value) app.Phase.restoring = 2
restoring
&& now -
(__gshared global) long app.g_restoreReqUs
g_restoreReqUs
>= restoreTimeoutUs)
{ instrEvent("vis_change", "state=restore_timeout silence_us=%lld",
undefined identifier `instrEvent`, did you mean import `instrument`?
now - g_lastTickBeforeMinUs);
(__gshared global) bool app.g_running
g_running
= false;
} if (now > hardCapUs) { instrEvent("hard_cap_hit");
undefined identifier `instrEvent`, did you mean import `instrument`?
(__gshared global) bool app.g_running
g_running
= false;
} } // 6. Teardown + the numbers.
void app.teardown() nothrow @nogc
teardown
();
void app.printStats() nothrow @nogc

min/median/p99/max + 1 ms histogram over the recorded deltas, to stdout.

printStats
();
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("occlusion: minimized_for_us=%lld frame_callbacks_while_minimized=%d ",
(__gshared global) long app.g_restoreReqUs
g_restoreReqUs
>= 0 ?
(__gshared global) long app.g_restoreReqUs
g_restoreReqUs
-
(__gshared global) long app.g_minimizeReqUs
g_minimizeReqUs
: -1,
(__gshared global) int app.g_cbWhileMinimized
g_cbWhileMinimized
);
if (
(__gshared global) long app.g_resumeCbUs
g_resumeCbUs
>= 0)
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("resumed_after_restore_us=%lld total_silence_us=%lld\n",
(__gshared global) long app.g_resumeCbUs
g_resumeCbUs
-
(__gshared global) long app.g_restoreReqUs
g_restoreReqUs
,
(__gshared global) long app.g_resumeCbUs
g_resumeCbUs
-
(__gshared global) long app.g_lastTickBeforeMinUs
g_lastTickBeforeMinUs
);
else
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("resume=NEVER\n");
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("wp_presentation advertised: %s\n",
(__gshared global) bool app.g_sawPresentation
g_sawPresentation
? "yes".
(constant) immutable(char)* "yes".ptr = "yes"
ptr
: "no".
(constant) immutable(char)* "no".ptr = "no"
ptr
);
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("ok: %d frame callbacks, %d commits, %d measured deltas\n",
(__gshared global) int app.g_frames
g_frames
,
(__gshared global) int app.g_commits
g_commits
,
(__gshared global) int app.g_deltaCount
g_deltaCount
);
return
(__gshared global) int app.g_deltaCount
g_deltaCount
==
(constant) int app.measuredFrames = 600
measuredFrames
? 0 : 1;
}