display: runtime mode-setting, EDID, and fenced (vsync) present (v2 V5)
The native backend can now change resolution and presents tear-free.
Mode-setting without churn. The driver sizes its scanout resource + shared surface to the
largest mode it offers and treats a mode change as re-pointing the scanout rectangle within
that surface — so the resource, its backing, and the shared mapping never change, and the
surface's row stride (the max width) is fixed while the active width/height move. The
compositor is handed that stride in the announce and composes at it; a smaller mode just
paints the top-left rectangle. This sidesteps the surface re-share a true resolution change
would otherwise need (the service harness can't reply with a capability).
- scanout-protocol gains get_modes + set_mode; the driver offers {640x480, 800x600} and
re-points set_scanout on set_mode.
- backend.VirtioGpu carries the surface stride, exposes modes()/setMode(), and reports
canModeSet = hasVsync = true.
- runtime.display gains modes()/setMode() (display-protocol get_modes/set_mode, forwarded to
the backend) — the client-facing API.
- EDID: the driver negotiates VIRTIO_GPU_F_EDID when the device offers it, reads the monitor's
EDID, and logs its preferred mode (parsed from the first detailed timing descriptor).
- vsync: every resource_flush is fenced (VIRTIO_GPU_FLAG_FENCE); the device signals the fence
when the frame is on screen, which the used-ring ack the synchronous present already waits
on gates — so a completed present is a tear-free one.
After the native upgrade the compositor runs a one-shot mode-set self-check: query the modes,
switch to a different one, re-composite, and confirm the backend reports the new geometry —
the gate's markers.
Gate: python3 test/qemu_test.py display-modeset (reuses the display-native boot) — "display:
mode set to 800x600, verified" + "display: vsync present ok", passing 3/3. host tests,
display-service, display-demo, shm, virtio-gpu, and display-native still pass.
This commit is contained in:
@@ -39,6 +39,11 @@ var service_endpoint: ipc.Handle = 0;
|
||||
/// whole screen into the shared surface and reads a pixel back to confirm the frame landed.
|
||||
var pending_native_verify: bool = false;
|
||||
|
||||
/// Set alongside it: after the native present is verified, run the mode-set self-check once
|
||||
/// (query the driver's modes, switch to a different one, confirm the geometry changed) — the
|
||||
/// serial proof the runtime-resolution-change + fenced-present paths work (V5).
|
||||
var pending_modeset_check: bool = false;
|
||||
|
||||
/// The wallpaper the compositor clears damaged regions to before painting layers.
|
||||
var background: u32 = 0;
|
||||
|
||||
@@ -214,14 +219,15 @@ fn verifyNativePresent() void {
|
||||
/// present channel, switch the backend to virtio-gpu, and queue a full-screen repaint. The
|
||||
/// present is deferred to a timer (see `service_endpoint`) so it happens after this reply
|
||||
/// unblocks the driver and it starts serving `.scanout`.
|
||||
fn attachScanout(width: u32, height: u32, format: u32, capability: ?ipc.Handle, reply: []u8) usize {
|
||||
fn attachScanout(stride: u32, width: u32, height: u32, format: u32, capability: ?ipc.Handle, reply: []u8) usize {
|
||||
const cap = capability orelse return fail(reply);
|
||||
if (width == 0 or height == 0) return fail(reply);
|
||||
if (width == 0 or height == 0 or stride < width) return fail(reply);
|
||||
const mapped = runtime.shm.map(cap) orelse return fail(reply);
|
||||
const scanout = ipc.lookup(.scanout) orelse return fail(reply);
|
||||
|
||||
backend = .{ .virtio = .{
|
||||
.pixels = @ptrCast(@alignCast(mapped)),
|
||||
.stride = stride,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.format = format,
|
||||
@@ -230,11 +236,53 @@ fn attachScanout(width: u32, height: u32, format: u32, capability: ?ipc.Handle,
|
||||
background = protocol.pack(format, 0x20, 0x30, 0x48); // re-pack the wallpaper for the mode
|
||||
addDamage(screenRect()); // the whole new surface must be painted
|
||||
pending_native_verify = true;
|
||||
pending_modeset_check = true;
|
||||
_ = system.timerOnce(service_endpoint, 50); // present once the driver is serving .scanout
|
||||
_ = system.write("display: scanout upgraded to virtio-gpu\n");
|
||||
return ok(reply);
|
||||
}
|
||||
|
||||
/// After the native upgrade is verified, prove the runtime-resolution-change and fenced-present
|
||||
/// paths: query the driver's modes, switch to one that differs from the current, re-composite
|
||||
/// the whole screen at the new size, and confirm the backend now reports that geometry. The
|
||||
/// present goes through the driver's fenced flush, so a clean present is a vsync present.
|
||||
fn modesetSelfCheck() void {
|
||||
if (!backend.canModeSet()) return;
|
||||
var mode_list: [4]backend_mod.Mode = undefined;
|
||||
const count = backend.modes(&mode_list);
|
||||
if (count == 0) {
|
||||
_ = system.write("display: mode-set self-check: no modes reported\n");
|
||||
return;
|
||||
}
|
||||
const current = backend.info();
|
||||
var target: ?backend_mod.Mode = null;
|
||||
for (mode_list[0..count]) |m| {
|
||||
if (m.width != current.width or m.height != current.height) {
|
||||
target = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const wanted = target orelse {
|
||||
_ = system.write("display: mode-set self-check: no alternate mode offered\n");
|
||||
return;
|
||||
};
|
||||
if (!backend.setMode(wanted.width, wanted.height)) {
|
||||
_ = system.write("display: mode set FAILED\n");
|
||||
return;
|
||||
}
|
||||
addDamage(screenRect()); // repaint the whole screen at the new resolution, then present it
|
||||
present();
|
||||
|
||||
const now = backend.info();
|
||||
if (now.width == wanted.width and now.height == wanted.height) {
|
||||
var line: [80]u8 = undefined;
|
||||
_ = system.write(std.fmt.bufPrint(&line, "display: mode set to {d}x{d}, verified\n", .{ now.width, now.height }) catch "display: mode set, verified\n");
|
||||
if (backend.hasVsync()) _ = system.write("display: vsync present ok\n");
|
||||
} else {
|
||||
_ = system.write("display: mode set FAILED (geometry unchanged)\n");
|
||||
}
|
||||
}
|
||||
|
||||
// --- startup self-check -----------------------------------------------------
|
||||
|
||||
/// Prove the compositor wiring on the real backend: two overlapping opaque layers,
|
||||
@@ -351,17 +399,42 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?ipc.Han
|
||||
return ok(reply);
|
||||
},
|
||||
@intFromEnum(protocol.Operation.attach_scanout) => {
|
||||
return attachScanout(request.width, request.height, request.colour, capability, reply);
|
||||
return attachScanout(request.x, request.width, request.height, request.colour, capability, reply);
|
||||
},
|
||||
@intFromEnum(protocol.Operation.set_mode) => {
|
||||
if (!backend.setMode(request.width, request.height)) return fail(reply);
|
||||
addDamage(screenRect()); // repaint the whole screen at the new resolution
|
||||
present();
|
||||
return ok(reply);
|
||||
},
|
||||
@intFromEnum(protocol.Operation.get_modes) => {
|
||||
var list: [4]backend_mod.Mode = undefined;
|
||||
const count = backend.modes(&list);
|
||||
var response = protocol.ModesReply{ .status = 0, .count = @intCast(count), .modes = undefined };
|
||||
for (0..protocol.max_modes) |i| {
|
||||
response.modes[i] = if (i < count)
|
||||
.{ .width = list[i].width, .height = list[i].height }
|
||||
else
|
||||
.{ .width = 0, .height = 0 };
|
||||
}
|
||||
const bytes = std.mem.asBytes(&response);
|
||||
@memcpy(reply[0..bytes.len], bytes);
|
||||
return bytes.len;
|
||||
},
|
||||
else => return fail(reply),
|
||||
}
|
||||
}
|
||||
|
||||
/// The only notification the compositor arms is the post-attach present timer: repaint the
|
||||
/// screen into the freshly attached native surface and verify the frame landed.
|
||||
/// screen into the freshly attached native surface, verify the frame landed, then run the
|
||||
/// one-shot mode-set self-check (V5).
|
||||
fn onNotification(badge: u64) void {
|
||||
_ = badge;
|
||||
present();
|
||||
present(); // native present + verify (first timer fire after the upgrade)
|
||||
if (pending_modeset_check) {
|
||||
pending_modeset_check = false;
|
||||
modesetSelfCheck();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() void {
|
||||
|
||||
Reference in New Issue
Block a user