// macOS/AppKit F04 demo — vsync / frame pacing (../../../features/f04-frame-pacing.md).
// Redraw of a trivially cheap frame (solid color flip) is paced by the platform
// frame clock: a CADisplayLink obtained from the view via
// displayLinkWithTarget:selector: (macOS 14+; the older CVDisplayLink C API is
// deprecated since macOS 15) and added to the main run loop in
// NSRunLoopCommonModes. Every fire logs
// frame_callback t=<link.timestamp> target=<link.targetTimestamp> path=displaylink
// until 600 frames are paced; at exit the demo prints min/median/p99/max
// inter-frame delta and a coarse jitter histogram.
//
// Fallback chain (so the demo always completes headless / screen-locked, where
// the WindowServer may never drive the link):
// 1. NSView responds to displayLinkWithTarget:selector:? no -> NSTimer path.
// 2. CADisplayLink silent for >3 s (watchdog, ignoring the deliberate hidden
// phase)? -> invalidate it and fall back to a ~16 ms NSTimer; every frame
// logs path=displaylink|timer so the two are never conflated.
//
// Occlusion probe: ~6 s in (or at frame 300), the window is sent orderOut: for
// ~3 s, then makeKeyAndOrderFront: again — Apple documents the view display link
// as following the view's window/display visibility, so the fires should pause;
// the demo logs vis_change, frames observed while hidden, the resume gap, and
// windowDidChangeOcclusionState:. NSScreen.maximumFramesPerSecond is logged at
// startup as the nominal cadence reference.
// Findings: ../../f04-frame-pacing.md; recipe and build command: ../../scaffold.md.
//
// Always bounded: exits 0 after 600 paced frames + the occlusion probe, or at a
// hard ~30 s deadline, whichever comes first. Headless-safe: prints `SKIP:` and
// exits 0 when there is no window server (CGMainDisplayID() == 0).
module (module) appapp;
import (package) corecore.(module) core.attributeThis module contains UDA's (User Defined Attributes) either used in
the runtime or special UDA's recognized by compiler.
Attribute Name, Linkage, Description
gnuAbiTag, C++,
Declares an ABI tag on a C++ symbol.
mustuse,,
Ensures that values of a struct or union type are not discarded.
optional, Objective-C,
Makes an Objective-C interface method optional.
selector, Objective-C,
Attaches an Objective-C selector to a method.
standalone,,
Marks a shared module constructor as not depending on any
other module constructor being run first.
weak,,
Specifies that a global symbol should be emitted with weak linkage.
Source
core/attribute.d
attribute : selector;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdioD header file for C99 <stdio.h>
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h
Source
core/stdc/stdio.d
stdio : (alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdlibD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h
Source
core/stdc/stdlib.d
stdlib : (alias) app.malloc = void* core.stdc.stdlib.malloc(ulong size) nothrow @nogcmalloc, (alias) app.free = void core.stdc.stdlib.free(void* ptr) nothrow @nogcfree, (alias) app.qsort = void core.stdc.stdlib.qsort(void* base, ulong nmemb, ulong size, extern (C) int function(const(void*), const(void*)) compar) nothrow @nogcqsort;
import instrument : instr, instrInit, instrNowUs;
import objc.autorelease : autoreleasepool_push, autoreleasepool_pop; // objective-d
// Objective-C runtime selector lookup (objective-d wraps libobjc; declaring
// sel_registerName ourselves would clash with objc.rt's typed declaration).
import objc.rt : SEL;
// --- C-level declarations -----------------------------------------------------
// CoreGraphics C API — linked transitively via the Cocoa umbrella framework.
extern (C) nothrow @nogc
{
uint uint app.CGMainDisplayID() nothrow @nogcCGMainDisplayID();
void* void* app.CGColorSpaceCreateDeviceRGB() nothrow @nogcCGColorSpaceCreateDeviceRGB();
void void app.CGColorSpaceRelease(void* space) nothrow @nogcCGColorSpaceRelease(void* (parameter) void* spacespace);
void* void* app.CGDataProviderCreateWithData(void* info, const(void)* data, ulong size, void* releaseCallback) nothrow @nogcCGDataProviderCreateWithData(void* (parameter) void* infoinfo, const(void)* (parameter) const(void)* datadata, (alias) object.size_t = ulongsize_t (parameter) ulong sizesize,
void* (parameter) void* releaseCallbackreleaseCallback);
void void app.CGDataProviderRelease(void* provider) nothrow @nogcCGDataProviderRelease(void* (parameter) void* providerprovider);
void* void* app.CGImageCreate(ulong width, ulong height, ulong bitsPerComponent, ulong bitsPerPixel, ulong bytesPerRow, void* colorSpace, uint bitmapInfo, void* provider, const(double)* decode, bool shouldInterpolate, int intent) nothrow @nogcCGImageCreate((alias) object.size_t = ulongsize_t (parameter) ulong widthwidth, (alias) object.size_t = ulongsize_t (parameter) ulong heightheight, (alias) object.size_t = ulongsize_t (parameter) ulong bitsPerComponentbitsPerComponent,
(alias) object.size_t = ulongsize_t (parameter) ulong bitsPerPixelbitsPerPixel, (alias) object.size_t = ulongsize_t (parameter) ulong bytesPerRowbytesPerRow, void* (parameter) void* colorSpacecolorSpace, uint (parameter) uint bitmapInfobitmapInfo,
void* (parameter) void* providerprovider, const(double)* (parameter) const(double)* decodedecode, bool (parameter) bool shouldInterpolateshouldInterpolate, int (parameter) int intentintent);
void void app.CGImageRelease(void* image) nothrow @nogcCGImageRelease(void* (parameter) void* imageimage);
void void app.CGContextDrawImage(void* ctx, app.NSRect rect, void* image) nothrow @nogcCGContextDrawImage(void* (parameter) void* ctxctx, (struct) app.NSRectNSRect (parameter) app.NSRect rectrect, void* (parameter) void* imageimage);
}
enum uint (constant) uint app.kCGImageAlphaNoneSkipFirst = 6ukCGImageAlphaNoneSkipFirst = 6; // XRGB: alpha byte present, ignored
enum uint (constant) uint app.kCGBitmapByteOrder32Little = 8192ukCGBitmapByteOrder32Little = 2 << 12; // 32-bit little-endian packing
enum int (constant) int app.kCGRenderingIntentDefault = 0kCGRenderingIntentDefault = 0;
// Cocoa geometry (CGFloat == double on 64-bit). Plain C structs, not ObjC objects.
extern (C) struct (struct) app.NSPointNSPoint
{
double (field) double app.NSPoint.xx, (field) double app.NSPoint.yy;
}
extern (C) struct (struct) app.NSSizeNSSize
{
double (field) double app.NSSize.widthwidth, (field) double app.NSSize.heightheight;
}
extern (C) struct (struct) app.NSRectNSRect
{
(struct) app.NSPointNSPoint (field) app.NSPoint app.NSRect.originorigin;
(struct) app.NSSizeNSSize (field) app.NSSize app.NSRect.sizesize;
}
// --- AppKit/QuartzCore class declarations --------------------------------------
extern (Objective-C):
extern class (class) app.NSObjectNSObject
{
bool app.NSObject.respondsToSelectorrespondsToSelector(SEL sel) @selector("respondsToSelector:");
}
extern class (class) app.NSStringNSString : (class) app.NSObjectNSObject
{
static (class) app.NSStringNSString app.NSString app.NSString.alloc()alloc() @selector("alloc");
(class) app.NSStringNSString app.NSString app.NSString.initWithUTF8String(const(char)* s)initWithUTF8String(const(char)* (parameter) const(char)* ss) @selector("initWithUTF8String:");
}
extern class (class) app.NSResponderNSResponder : (class) app.NSObjectNSObject
{
}
extern class (class) app.NSEventNSEvent : (class) app.NSObjectNSObject
{
static (class) app.NSEventNSEvent app.NSEvent app.NSEvent.otherEventWithType(long type, app.NSPoint location, ulong modifierFlags, double timestamp, long windowNumber, app.NSObject context, short subtype, long data1, long data2)otherEventWithType(long (parameter) long typetype, (struct) app.NSPointNSPoint (parameter) app.NSPoint locationlocation, ulong (parameter) ulong modifierFlagsmodifierFlags,
double (parameter) double timestamptimestamp, long (parameter) long windowNumberwindowNumber, (class) app.NSObjectNSObject (parameter) app.NSObject contextcontext, short (parameter) short subtypesubtype,
long (parameter) long data1data1, long (parameter) long data2data2)
@selector("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:");
}
extern class (class) app.NSTimerNSTimer : (class) app.NSObjectNSObject
{
static (class) app.NSTimerNSTimer app.NSTimer.timerWithTimeIntervaltimerWithTimeInterval(double interval, (class) app.NSObjectNSObject (parameter) NSObject targettarget,
SEL sel, (class) app.NSObjectNSObject userInfo, bool repeats)
@selector("timerWithTimeInterval:target:selector:userInfo:repeats:");
void void app.NSTimer.invalidate()invalidate() @selector("invalidate");
}
extern class (class) app.NSRunLoopNSRunLoop : (class) app.NSObjectNSObject
{
static (class) app.NSRunLoopNSRunLoop app.NSRunLoop app.NSRunLoop.currentRunLoop()currentRunLoop() @selector("currentRunLoop");
void void app.NSRunLoop.addTimer(app.NSTimer timer, app.NSString mode)addTimer((class) app.NSTimerNSTimer (parameter) app.NSTimer timertimer, (class) app.NSStringNSString (parameter) app.NSString modemode) @selector("addTimer:forMode:");
}
// QuartzCore's frame clock (-framework QuartzCore). The macOS 14+ instance comes
// from -[NSView displayLinkWithTarget:selector:]; timestamps are CFTimeInterval
// seconds on the mach_absolute_time/CACurrentMediaTime timebase.
extern class (class) app.CADisplayLinkCADisplayLink : (class) app.NSObjectNSObject
{
double double app.CADisplayLink.timestamp()timestamp() @selector("timestamp");
double double app.CADisplayLink.targetTimestamp()targetTimestamp() @selector("targetTimestamp");
double double app.CADisplayLink.duration()duration() @selector("duration");
bool bool app.CADisplayLink.isPaused()isPaused() @selector("isPaused");
void void app.CADisplayLink.addToRunLoop(app.NSRunLoop runLoop, app.NSString mode)addToRunLoop((class) app.NSRunLoopNSRunLoop (parameter) app.NSRunLoop runLooprunLoop, (class) app.NSStringNSString (parameter) app.NSString modemode) @selector("addToRunLoop:forMode:");
void void app.CADisplayLink.invalidate()invalidate() @selector("invalidate");
}
extern class (class) app.NSScreenNSScreen : (class) app.NSObjectNSObject
{
static (class) app.NSScreenNSScreen app.NSScreen app.NSScreen.mainScreen()mainScreen() @selector("mainScreen");
long long app.NSScreen.maximumFramesPerSecond()maximumFramesPerSecond() @selector("maximumFramesPerSecond");
}
extern class (class) app.NSGraphicsContextNSGraphicsContext : (class) app.NSObjectNSObject
{
static (class) app.NSGraphicsContextNSGraphicsContext app.NSGraphicsContext app.NSGraphicsContext.currentContext()currentContext() @selector("currentContext");
void* void* app.NSGraphicsContext.CGContext()CGContext() @selector("CGContext"); // CGContextRef
}
extern class (class) app.NSApplicationNSApplication : (class) app.NSResponderNSResponder
{
static (class) app.NSApplicationNSApplication app.NSApplication app.NSApplication.sharedApplication()sharedApplication() @selector("sharedApplication");
void void app.NSApplication.setActivationPolicy(long policy)setActivationPolicy(long (parameter) long policypolicy) @selector("setActivationPolicy:");
void void app.NSApplication.activateIgnoringOtherApps(bool flag)activateIgnoringOtherApps(bool (parameter) bool flagflag) @selector("activateIgnoringOtherApps:");
void void app.NSApplication.run()run() @selector("run");
void void app.NSApplication.stop(app.NSObject sender)stop((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("stop:");
void void app.NSApplication.postEvent(app.NSEvent event, bool atStart)postEvent((class) app.NSEventNSEvent (parameter) app.NSEvent eventevent, bool (parameter) bool atStartatStart) @selector("postEvent:atStart:");
void void app.NSApplication.terminate(app.NSObject sender)terminate((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("terminate:");
}
extern class (class) app.NSViewNSView : (class) app.NSResponderNSResponder
{
void void app.NSView.setNeedsDisplay(bool flag)setNeedsDisplay(bool (parameter) bool flagflag) @selector("setNeedsDisplay:");
(struct) app.NSRectNSRect app.NSRect app.NSView.bounds()bounds() @selector("bounds");
// macOS 14.0+ — gated at runtime via respondsToSelector:.
(class) app.CADisplayLinkCADisplayLink app.NSView.displayLinkWithTargetdisplayLinkWithTarget((class) app.NSObjectNSObject (parameter) NSObject targettarget, SEL sel)
@selector("displayLinkWithTarget:selector:");
}
extern class (class) app.NSWindowNSWindow : (class) app.NSResponderNSResponder
{
static (class) app.NSWindowNSWindow app.NSWindow app.NSWindow.alloc()alloc() @selector("alloc");
(class) app.NSWindowNSWindow app.NSWindow app.NSWindow.initWithContentRect(app.NSRect contentRect, ulong styleMask, ulong backing, bool defer)initWithContentRect((struct) app.NSRectNSRect (parameter) app.NSRect contentRectcontentRect, ulong (parameter) ulong styleMaskstyleMask,
ulong (parameter) ulong backingbacking, bool (parameter) bool deferdefer) @selector("initWithContentRect:styleMask:backing:defer:");
void void app.NSWindow.setTitle(app.NSString title)setTitle((class) app.NSStringNSString (parameter) app.NSString titletitle) @selector("setTitle:");
void void app.NSWindow.setContentView(app.NSView view)setContentView((class) app.NSViewNSView (parameter) app.NSView viewview) @selector("setContentView:");
void void app.NSWindow.setDelegate(app.NSObject anObject)setDelegate((class) app.NSObjectNSObject (parameter) app.NSObject anObjectanObject) @selector("setDelegate:");
void void app.NSWindow.makeKeyAndOrderFront(app.NSObject sender)makeKeyAndOrderFront((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("makeKeyAndOrderFront:");
void void app.NSWindow.orderOut(app.NSObject sender)orderOut((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("orderOut:");
ulong ulong app.NSWindow.occlusionState()occlusionState() @selector("occlusionState");
double double app.NSWindow.backingScaleFactor()backingScaleFactor() @selector("backingScaleFactor");
}
// --- The custom NSView subclass, defined in D ----------------------------------
// Scaffold recipe: bodied @selector methods are the implementation; the display
// link / timer / delegate callbacks are selector-only (no base declaration).
class (class) app.FlipViewFlipView : (class) app.NSViewNSView
{
static (class) app.FlipViewFlipView app.FlipView app.FlipView.alloc()alloc() @selector("alloc");
(class) app.FlipViewFlipView app.FlipView app.FlipView.initWithFrame(app.NSRect frame)initWithFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameframe) @selector("initWithFrame:");
void void app.FlipView.drawRect(app.NSRect dirtyRect)drawRect((struct) app.NSRectNSRect (parameter) app.NSRect dirtyRectdirtyRect) @selector("drawRect:")
{
onDrawRect(this.this.boundsbounds());
}
// CADisplayLink fire — the platform frame clock.
void void app.FlipView.displayTick(app.CADisplayLink link)displayTick((class) app.CADisplayLinkCADisplayLink (parameter) app.CADisplayLink linklink) @selector("displayTick:")
{
onFrame(link.link.timestamptimestamp(), link.link.targetTimestamptargetTimestamp(), Path.Path.displaylinkdisplaylink);
}
// Fallback frame clock: a ~16 ms NSTimer in NSRunLoopCommonModes.
void void app.FlipView.timerTick(app.NSTimer timer)timerTick((class) app.NSTimerNSTimer (parameter) app.NSTimer timertimer) @selector("timerTick:")
{
immutable _error_ tt = instrNowUs() / 1e6;
onFrame(t, t + fallbackIntervalMs / 1000.0, Path.Path.timertimer);
}
// 100 ms state-machine scheduler (watchdog, occlusion probe, deadline).
void void app.FlipView.schedTick(app.NSTimer timer)schedTick((class) app.NSTimerNSTimer (parameter) app.NSTimer timertimer) @selector("schedTick:")
{
onSchedule();
}
// NSWindowDelegate: occlusion-state transitions (orderOut:/orderFront:).
void void app.FlipView.windowDidChangeOcclusionState(app.NSObject notification)windowDidChangeOcclusionState((class) app.NSObjectNSObject (parameter) app.NSObject notificationnotification)
@selector("windowDidChangeOcclusionState:")
{
instr("vis_change", "state=occlusion occlusion_state=%lu visible=%d",
g_win.g_win.occlusionStateocclusionState(),
(g_win.g_win.occlusionStateocclusionState() & NSWindowOcclusionStateVisible) != 0 ? 1 : 0);
}
void void app.FlipView.windowWillClose(app.NSObject notification)windowWillClose((class) app.NSObjectNSObject (parameter) app.NSObject notificationnotification) @selector("windowWillClose:")
{
instr("close_requested");
g_app.g_app.terminateterminate(null);
}
}
extern (D): // back to D linkage for the rest of the module
enum (constant) int app.NSApplicationActivationPolicyRegular = 0NSApplicationActivationPolicyRegular = 0;
enum : ulong
{
(enum value) app.NSWindowStyleMaskTitled = 1LUNSWindowStyleMaskTitled = 1,
(enum value) app.NSWindowStyleMaskClosable = 2LUNSWindowStyleMaskClosable = 2,
(enum value) app.NSWindowStyleMaskResizable = 8LUNSWindowStyleMaskResizable = 8,
}
enum ulong (constant) ulong app.NSBackingStoreBuffered = 2LUNSBackingStoreBuffered = 2;
enum long (constant) long app.NSEventTypeApplicationDefined = 15LNSEventTypeApplicationDefined = 15;
enum ulong (constant) ulong app.NSWindowOcclusionStateVisible = 2LUNSWindowOcclusionStateVisible = 1UL << 1;
// --- Demo state -----------------------------------------------------------------
enum (enum) app.PathPath : int
{
(enum value) app.Path.displaylink = 0displaylink = 0,
(enum value) app.Path.timer = 1timer = 1,
}
immutable (alias) object.string = stringstring[2] (immutable global) immutable(string[2]) app.pathNamespathNames = ["displaylink", "timer"];
enum (constant) int app.targetFrames = 600targetFrames = 600; // per the F04 spec
enum (constant) int app.fallbackIntervalMs = 16fallbackIntervalMs = 16; // NSTimer fallback cadence
enum (constant) int app.watchdogSilenceMs = 3000watchdogSilenceMs = 3000;
enum (constant) int app.hideAtMs = 6000hideAtMs = 6000; // occlusion probe: orderOut: ...
enum (constant) int app.hideAtFrame = 300hideAtFrame = 300; // ... or at frame 300, whichever first
enum (constant) int app.hiddenForMs = 3000hiddenForMs = 3000;
enum (constant) int app.deadlineMs = 30000deadlineMs = 30_000; // hard stop: the demo always completes
__gshared (class) app.NSApplicationNSApplication (__gshared global) app.NSApplication app.g_appg_app;
__gshared (class) app.NSWindowNSWindow (__gshared global) app.NSWindow app.g_wing_win;
__gshared (class) app.NSRunLoopNSRunLoop (__gshared global) app.NSRunLoop app.g_runLoopg_runLoop;
__gshared (class) app.NSStringNSString (__gshared global) app.NSString app.g_modeCommong_modeCommon;
__gshared (class) app.FlipViewFlipView (__gshared global) app.FlipView app.g_viewg_view;
__gshared (class) app.CADisplayLinkCADisplayLink (__gshared global) app.CADisplayLink app.g_linkg_link;
__gshared uint* (__gshared global) uint* app.g_bufg_buf;
__gshared (alias) object.size_t = ulongsize_t (__gshared global) ulong app.g_bufWg_bufW, (__gshared global) ulong app.g_bufHg_bufH;
__gshared (enum) app.PathPath (__gshared global) app.Path app.g_pathg_path = Path.Path.displaylinkdisplaylink;
__gshared int (__gshared global) int app.g_framesg_frames, (__gshared global) int app.g_drawsg_draws;
__gshared int[2] (__gshared global) int[2] app.g_framesPerPathg_framesPerPath;
__gshared double (__gshared global) double app.g_lastTsg_lastTs = 0;
__gshared bool (__gshared global) bool app.g_skipNextDeltag_skipNextDelta; // after path switch / un-hide: gap is not jitter
__gshared double[2048] (__gshared global) double[2048] app.g_deltasg_deltas; // seconds; capped, extra frames only counted
__gshared int (__gshared global) int app.g_deltaCountg_deltaCount;
__gshared bool (__gshared global) bool app.g_hiddeng_hidden, (__gshared global) bool app.g_hideDoneg_hideDone, (__gshared global) bool app.g_showDoneg_showDone, (__gshared global) bool app.g_fellBackg_fellBack, (__gshared global) bool app.g_finishedg_finished;
__gshared ulong (__gshared global) ulong app.g_hideAtUsg_hideAtUs, (__gshared global) ulong app.g_lastFrameUsg_lastFrameUs;
__gshared int (__gshared global) int app.g_framesAtHideg_framesAtHide, (__gshared global) int app.g_hiddenFramesg_hiddenFrames;
__gshared double (__gshared global) double app.g_resumeGapMsg_resumeGapMs = -1;
void void app.onFrame(double ts, double target, app.Path path)onFrame(double (parameter) double tsts, double (parameter) double targettarget, (enum) app.PathPath (parameter) app.Path pathpath)
{
if (g_finished || path != g_path)
return; // ignore stragglers from an invalidated clock
++g_frames;
++g_framesPerPath[path];
g_lastFrameUs = instrNowUs();
if (g_hidden)
++g_hiddenFrames;
instr("frame_callback", "t=%.6f target=%.6f path=%.*s",
ts, target, cast(int) pathNames[path].length, pathNames[path].ptr);
if (g_lastTs > 0)
{
immutable _error_ deltadelta = ts - g_lastTs;
if (g_skipNextDelta)
{
g_skipNextDelta = false;
if (g_showDone && g_resumeGapMs < 0)
{
g_resumeGapMs = delta * 1000.0;
instr("resume_gap", "gap_ms=%.1f", g_resumeGapMs);
}
}
else if (g_deltaCount < cast(int) g_deltas.length)
g_deltas[g_deltaCount++] = delta;
}
g_lastTs = ts;
g_view.g_view.setNeedsDisplaysetNeedsDisplay(true); // solid color flip — the trivially cheap frame
// Don't finish while the occlusion probe is mid-flight; onSchedule's
// deadline covers the pathological cases.
if (g_frames >= targetFrames && g_showDone)
finish("frames");
}
void void app.onSchedule()onSchedule()
{
if (g_finished)
return;
immutable _error_ nowMsnowMs = instrNowUs() / 1000;
// Watchdog: the display link never fired / went silent (and not because we
// hid the window on purpose) -> fall back to the NSTimer frame clock.
if (g_path == Path.Path.displaylinkdisplaylink && !g_fellBack && !g_hidden)
{
immutable _error_ sinceLastsinceLast = g_frames == 0 ? nowMs : (instrNowUs() - g_lastFrameUs) / 1000;
if (sinceLast > watchdogSilenceMs)
{
g_fellBack = true;
instr("watchdog",
"displaylink_silent_ms=%llu frames_so_far=%d link_paused=%d occlusion_state=%lu action=fallback_timer",
cast(ulong) sinceLast, g_frames,
(g_link !is null && g_link.g_link.isPausedisPaused()) ? 1 : 0, g_win.g_win.occlusionStateocclusionState());
if (g_link !is null)
g_link.g_link.invalidateinvalidate();
startTimerPath();
}
}
// Occlusion probe: hide for ~3 s mid-run, then show again.
if (!g_hideDone && (nowMs >= hideAtMs || g_frames >= hideAtFrame))
{
g_hideDone = true;
g_hidden = true;
g_hideAtUs = instrNowUs();
g_framesAtHide = g_frames;
instr("vis_change", "state=hidden api=orderOut frames=%d link_paused=%d",
g_frames, (g_link !is null && g_path == Path.Path.displaylinkdisplaylink && g_link.g_link.isPausedisPaused()) ? 1 : 0);
g_win.g_win.orderOutorderOut(null);
}
else if (g_hideDone && !g_showDone && instrNowUs() >= g_hideAtUs + hiddenForMs * 1000)
{
g_showDone = true;
g_hidden = false;
g_skipNextDelta = true;
instr("vis_change",
"state=visible api=makeKeyAndOrderFront hidden_frames=%d link_paused=%d",
g_hiddenFrames,
(g_link !is null && g_path == Path.Path.displaylinkdisplaylink && g_link.g_link.isPausedisPaused()) ? 1 : 0);
g_win.g_win.makeKeyAndOrderFrontmakeKeyAndOrderFront(null);
}
if (nowMs >= deadlineMs)
finish("deadline");
else if (g_frames >= targetFrames && g_showDone)
finish("frames");
}
void void app.startTimerPath()startTimerPath()
{
g_path = Path.Path.timertimer;
g_lastTs = 0; // timer timestamps are on the demo's own monotonic epoch
g_skipNextDelta = true;
instr("path_select", "path=timer interval_ms=%d", fallbackIntervalMs);
(unresolved type) NSTimerNSTimer _error_ tt = NSTimer.NSTimer.timerWithTimeIntervaltimerWithTimeInterval(fallbackIntervalMs / 1000.0, g_view,
SEL.register("timerTick:"), null, true);
g_runLoop.g_runLoop.addTimeraddTimer(t, g_modeCommon);
}
extern (C) int int app.cmpDouble(const(void)* a, const(void)* b) nothrow @nogccmpDouble(const(void)* (parameter) const(void)* aa, const(void)* (parameter) const(void)* bb) nothrow @nogc
{
immutable _error_ xx = *cast(const double*) a, _error_ yy = *cast(const double*) b;
return (x > y) - (x < y);
}
void void app.finish(const(char)* reason)finish(const(char)* (parameter) const(char)* reasonreason)
{
if (g_finished)
return;
g_finished = true;
instr("step", "name=finish reason=%s", reason);
if (g_deltaCount > 0)
{
qsort(g_deltas.ptr, g_deltaCount, double.sizeof, &cmpDouble);
immutable _error_ minMsminMs = g_deltas[0] * 1000.0;
immutable _error_ medMsmedMs = g_deltas[g_deltaCount / 2] * 1000.0;
immutable _error_ p99Msp99Ms = g_deltas[(g_deltaCount * 99) / 100 >= g_deltaCount
? g_deltaCount - 1 : (g_deltaCount * 99) / 100] * 1000.0;
immutable _error_ maxMsmaxMs = g_deltas[g_deltaCount - 1] * 1000.0;
instr("stats", "n=%d min_ms=%.3f median_ms=%.3f p99_ms=%.3f max_ms=%.3f",
g_deltaCount, minMs, medMs, p99Ms, maxMs);
// Coarse jitter histogram (inter-frame delta, ms).
static immutable double[9] double[9] edgesedges = [2, 4, 6, 9, 13, 18, 25, 40, 100];
int[10] _error_ bucketsbuckets;
foreach ((parameter) ii; 0 .. g_deltaCount)
{
immutable _error_ msms = g_deltas[i] * 1000.0;
(unresolved type) size_tsize_t _error_ bb = edges.length;
foreach ((parameter) jj, (parameter) ee; edges)
if (ms < e)
{
b = j;
break;
}
++buckets[b];
}
foreach ((parameter) jj, (parameter) countcount; buckets)
{
if (count == 0)
continue;
if (j == 0)
instr("histogram", "bucket_ms=<%.0f count=%d", edges[0], count);
else if (j == edges.length)
instr("histogram", "bucket_ms=>=%.0f count=%d", edges[$ - 1], count);
else
instr("histogram", "bucket_ms=%.0f-%.0f count=%d",
edges[j - 1], edges[j], count);
}
}
else
instr("stats", "n=0");
instr("loop_exit",
"frames=%d frames_displaylink=%d frames_timer=%d draws=%d hidden_frames=%d fell_back=%d",
g_frames, g_framesPerPath[Path.Path.displaylinkdisplaylink], g_framesPerPath[Path.Path.timertimer],
g_draws, g_hiddenFrames, g_fellBack ? 1 : 0);
g_app.g_app.stopstop(null);
// stop: is only checked after an event dispatch; post a synthetic event so
// [NSApp run] wakes up and exits (scaffold finding).
(unresolved type) NSEventNSEvent _error_ evev = NSEvent.NSEvent.otherEventWithTypeotherEventWithType(NSEventTypeApplicationDefined,
NSPoint(0, 0), 0, 0, 0, null, 0, 0, 0);
g_app.g_app.postEventpostEvent(ev, true);
}
void void app.onDrawRect(app.NSRect bounds)onDrawRect((struct) app.NSRectNSRect (parameter) app.NSRect boundsbounds)
{
immutable double _error_ scalescale = g_win.g_win.backingScaleFactorbackingScaleFactor();
immutable (unresolved type) size_tsize_t _error_ pwpw = cast((unresolved type) size_tsize_t) (bounds.bounds.sizesize.bounds.size.widthwidth * scale);
immutable (unresolved type) size_tsize_t _error_ phph = cast((unresolved type) size_tsize_t) (bounds.bounds.sizesize.bounds.size.heightheight * scale);
if (pw == 0 || ph == 0)
return;
if (pw != g_bufW || ph != g_bufH)
{
free(g_buf);
g_buf = cast(uint*) malloc(pw * ph * 4);
g_bufW = pw;
g_bufH = ph;
}
// The cheapest possible frame: a solid color flipping per paced frame.
immutable uint _error_ colorcolor = (g_frames & 1) ? 0xFFFF_FFFF : 0xFF10_1018;
foreach ((parameter) ii; 0 .. pw * ph)
g_buf[i] = color;
void* _error_ cscs = CGColorSpaceCreateDeviceRGB();
void* _error_ dpdp = CGDataProviderCreateWithData(null, g_buf, pw * ph * 4, null);
void* _error_ imgimg = CGImageCreate(pw, ph, 8, 32, pw * 4, cs,
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, dp, null, false,
kCGRenderingIntentDefault);
void* _error_ ctxctx = NSGraphicsContext.NSGraphicsContext.currentContextcurrentContext().NSGraphicsContext.currentContext().CGContextCGContext();
CGContextDrawImage(ctx, bounds, img);
CGImageRelease(img);
CGDataProviderRelease(dp);
CGColorSpaceRelease(cs);
++g_draws;
}
int int D main()main()
{
instrInit("APPKIT_F04");
instr("init_start", "target_frames=%d deadline_ms=%d", targetFrames, deadlineMs);
if (CGMainDisplayID() == 0)
{
printf("SKIP: no macOS window server (CGMainDisplayID == 0)\n");
return 0;
}
auto _error_ poolpool = autoreleasepool_push();
scope (exit)
autoreleasepool_pop(pool);
g_app = NSApplication.NSApplication.sharedApplicationsharedApplication();
g_app.g_app.setActivationPolicysetActivationPolicy(NSApplicationActivationPolicyRegular);
(unresolved type) NSScreenNSScreen _error_ screenscreen = NSScreen.NSScreen.mainScreenmainScreen();
instr("screen_info", "max_fps=%ld",
screen !is null ? screen.screen.maximumFramesPerSecondmaximumFramesPerSecond() : -1);
immutable _error_ contentRectcontentRect = NSRect(NSPoint(120, 120), NSSize(480, 320));
g_win = NSWindow.NSWindow.allocalloc().NSWindow.alloc().initWithContentRectinitWithContentRect(contentRect,
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable,
NSBackingStoreBuffered, false);
g_win.g_win.setTitlesetTitle(NSString.NSString.allocalloc().NSString.alloc().initWithUTF8StringinitWithUTF8String("wsi-f04-frame-pacing"));
instr("window_created", "scale=%.1f occlusion_state=%lu",
g_win.g_win.backingScaleFactorbackingScaleFactor(), g_win.g_win.occlusionStateocclusionState());
g_view = FlipView.FlipView.allocalloc().FlipView.alloc().initWithFrameinitWithFrame(contentRect);
g_win.g_win.setContentViewsetContentView(g_view);
g_win.g_win.setDelegatesetDelegate(g_view);
g_win.g_win.makeKeyAndOrderFrontmakeKeyAndOrderFront(null);
g_app.g_app.activateIgnoringOtherAppsactivateIgnoringOtherApps(true);
g_runLoop = NSRunLoop.NSRunLoop.currentRunLoopcurrentRunLoop();
g_modeCommon = NSString.NSString.allocalloc().NSString.alloc().initWithUTF8StringinitWithUTF8String("kCFRunLoopCommonModes");
// Frame-clock selection: CADisplayLink via the macOS 14+ NSView API, else
// the NSTimer fallback. The link is created unscheduled and must be added
// to a run loop explicitly; common modes so it survives tracking loops (F03).
if (g_view.g_view.respondsToSelectorrespondsToSelector(SEL.register("displayLinkWithTarget:selector:")))
{
g_link = g_view.g_view.displayLinkWithTargetdisplayLinkWithTarget(g_view, SEL.register("displayTick:"));
if (g_link !is null)
{
g_link.g_link.addToRunLoopaddToRunLoop(g_runLoop, g_modeCommon);
instr("path_select", "path=displaylink api=NSView_displayLinkWithTarget paused=%d",
g_link.g_link.isPausedisPaused() ? 1 : 0);
}
}
if (g_link is null)
{
instr("path_select", "path=timer reason=no_displaylink_api");
startTimerPath();
}
// The 100 ms scheduler (watchdog / occlusion probe / deadline), common modes.
(unresolved type) NSTimerNSTimer _error_ schedsched = NSTimer.NSTimer.timerWithTimeIntervaltimerWithTimeInterval(0.1, g_view,
SEL.register("schedTick:"), null, true);
g_runLoop.g_runLoop.addTimeraddTimer(sched, g_modeCommon);
instr("step", "name=NSApp_run");
g_app.g_app.runrun();
free(g_buf);
return g_finished ? 0 : 1;
}