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:
@@ -126,41 +126,72 @@ pub const Gop = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// A display mode the native backend can switch to.
|
||||
pub const Mode = scanout_protocol.Mode;
|
||||
|
||||
/// The native virtio-gpu backend: the compositor composes into a **shared** scanout surface
|
||||
/// (an `shm` region the driver created and handed over) and `present` asks the driver to put
|
||||
/// a frame on the panel over its `.scanout` endpoint. Unlike GOP there is no local copy — the
|
||||
/// surface *is* the device's resource backing, so compositing writes land straight where the
|
||||
/// driver transfers-and-flushes from (x86 DMA is cache-coherent, so the cacheable shared pages
|
||||
/// need no explicit flush). Built by the display service when a driver announces (V4); mode-set
|
||||
/// and vsync stay off until V5.
|
||||
/// need no explicit flush). Built by the display service when a driver announces (V4). The
|
||||
/// surface is sized to the driver's largest mode, so `stride` (its row width) is fixed while
|
||||
/// `width`/`height` — the active mode — change under `setMode` (V5).
|
||||
pub const VirtioGpu = struct {
|
||||
pixels: [*]u32, // the shared scanout surface, mapped into the compositor
|
||||
width: u32,
|
||||
stride: u32, // the surface's row stride in pixels (the driver's max mode width) — fixed
|
||||
width: u32, // the active mode
|
||||
height: u32,
|
||||
format: u32,
|
||||
scanout: ipc.Handle, // the driver's present channel (looked up on `.scanout`)
|
||||
scanout: ipc.Handle, // the driver's present + mode channel (looked up on `.scanout`)
|
||||
|
||||
pub fn info(self: *const VirtioGpu) Info {
|
||||
return .{ .width = self.width, .height = self.height, .pitch = self.width * 4, .format = self.format };
|
||||
return .{ .width = self.width, .height = self.height, .pitch = self.stride * 4, .format = self.format };
|
||||
}
|
||||
pub fn surface(self: *const VirtioGpu) Surface {
|
||||
return .{ .pixels = self.pixels, .stride = self.width, .width = self.width, .height = self.height };
|
||||
return .{ .pixels = self.pixels, .stride = self.stride, .width = self.width, .height = self.height };
|
||||
}
|
||||
/// Ask the driver to present. The composited pixels are already in the shared surface, so
|
||||
/// this is a single request over `.scanout`; the driver transfers + flushes. V4 presents
|
||||
/// the whole surface (the damage-rect fast path is a later refinement).
|
||||
/// this is a single request over `.scanout`; the driver transfers + fenced-flushes.
|
||||
pub fn present(self: *const VirtioGpu, damage: Rect) void {
|
||||
_ = damage;
|
||||
var request = scanout_protocol.Request{
|
||||
.operation = @intFromEnum(scanout_protocol.Operation.present),
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = self.width,
|
||||
.height = self.height,
|
||||
};
|
||||
var reply: [scanout_protocol.reply_size]u8 = undefined;
|
||||
_ = ipc.call(self.scanout, std.mem.asBytes(&request), &reply) catch {};
|
||||
}
|
||||
/// Fill `out` with the driver's offered modes; returns how many were written.
|
||||
pub fn modes(self: *const VirtioGpu, out: []Mode) usize {
|
||||
var request = scanout_protocol.Request{ .operation = @intFromEnum(scanout_protocol.Operation.get_modes) };
|
||||
var reply: [scanout_protocol.modes_reply_size]u8 = undefined;
|
||||
const n = ipc.call(self.scanout, std.mem.asBytes(&request), &reply) catch return 0;
|
||||
if (n < scanout_protocol.modes_reply_size) return 0;
|
||||
const answer = std.mem.bytesToValue(scanout_protocol.ModesReply, reply[0..scanout_protocol.modes_reply_size]);
|
||||
if (answer.status != 0) return 0;
|
||||
const count = @min(@min(answer.count, scanout_protocol.max_modes), out.len);
|
||||
for (0..count) |i| out[i] = answer.modes[i];
|
||||
return count;
|
||||
}
|
||||
/// Change the scanout resolution. On success the active `width`/`height` update (the shared
|
||||
/// surface — sized to the max mode — is unchanged, so `stride` stays put).
|
||||
pub fn setMode(self: *VirtioGpu, w: u32, h: u32) bool {
|
||||
if (w == 0 or h == 0 or w > self.stride) return false;
|
||||
var request = scanout_protocol.Request{
|
||||
.operation = @intFromEnum(scanout_protocol.Operation.set_mode),
|
||||
.width = w,
|
||||
.height = h,
|
||||
};
|
||||
var reply: [scanout_protocol.reply_size]u8 = undefined;
|
||||
const n = ipc.call(self.scanout, std.mem.asBytes(&request), &reply) catch return false;
|
||||
if (n < scanout_protocol.reply_size) return false;
|
||||
if (std.mem.bytesToValue(scanout_protocol.Reply, reply[0..scanout_protocol.reply_size]).status != 0) return false;
|
||||
self.width = w;
|
||||
self.height = h;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/// The pluggable scanout backend. A tagged union so the compositor holds one value and
|
||||
@@ -184,18 +215,33 @@ pub const Backend = union(enum) {
|
||||
inline else => |*b| b.present(damage),
|
||||
}
|
||||
}
|
||||
/// Whether this backend supports runtime mode-setting (GOP: no; virtio-gpu: not until V5).
|
||||
/// The modes this backend can switch to (none for GOP); returns how many were written.
|
||||
pub fn modes(self: *const Backend, out: []Mode) usize {
|
||||
return switch (self.*) {
|
||||
.virtio => |*v| v.modes(out),
|
||||
.gop => 0,
|
||||
};
|
||||
}
|
||||
/// Change the resolution; false if this backend can't mode-set or the mode was refused.
|
||||
pub fn setMode(self: *Backend, w: u32, h: u32) bool {
|
||||
return switch (self.*) {
|
||||
.virtio => |*v| v.setMode(w, h),
|
||||
.gop => false,
|
||||
};
|
||||
}
|
||||
/// Whether this backend supports runtime mode-setting (GOP: no; virtio-gpu: yes, V5).
|
||||
pub fn canModeSet(self: *const Backend) bool {
|
||||
return switch (self.*) {
|
||||
.gop => false,
|
||||
.virtio => false,
|
||||
.virtio => true,
|
||||
};
|
||||
}
|
||||
/// Whether this backend has a vblank/fence for tear-free present (not until V5).
|
||||
/// Whether this backend has a vblank/fence for tear-free present (virtio-gpu: yes, V5 — every
|
||||
/// flush is fenced, so the device signals completion when the frame is actually on screen).
|
||||
pub fn hasVsync(self: *const Backend) bool {
|
||||
return switch (self.*) {
|
||||
.gop => false,
|
||||
.virtio => false,
|
||||
.virtio => true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user