display: pluggable scanout backend seam (v2 V1)
Extract scanout from the compositor into backend.zig — a `Backend` tagged union with info()/surface()/present(damage) and canModeSet/hasVsync flags. The v1 GOP path becomes `backend.Gop` (claim the display node, WC-map the LFB, keep the cacheable back buffer, present = the damage-rect WC copy); display.zig now composes into backend.surface() and calls backend.present(damage), with no LFB or framebuffer geometry left in the compositor core. The selection decision is the pure chooseKind(native_available), split from the syscall-bound bring-up, ready for the native-if-present branch at V4. Pure refactor: display-service, display-demo, and zig build test all pass unchanged.
This commit is contained in:
parent
f3342118f5
commit
9333d0572f
|
|
@ -40,21 +40,22 @@ used ring**. Those two together (pixel-readback + flush-ack) are the automated s
|
|||
|
||||
---
|
||||
|
||||
## V1 — The scanout backend seam (refactor, no behaviour change)
|
||||
## V1 — The scanout backend seam (refactor, no behaviour change) ✅
|
||||
|
||||
Extract scanout from the compositor so today's path becomes one backend among future ones.
|
||||
|
||||
- [ ] A `Backend` interface in `system/services/display/`: `surface() -> {ptr, pitch,
|
||||
format, width, height}`, `present(damage: Rect)`, and capability flags
|
||||
(`canModeSet`, `hasVsync`, both false for now).
|
||||
- [ ] Wrap the v1 GOP path as `GopBackend` (claim the `display` node, WC-map the LFB,
|
||||
`present` = the current damage-rect WC copy). The compositor composes into
|
||||
`backend.surface()` and calls `backend.present(damage)` — no direct LFB references
|
||||
left in the compositor core.
|
||||
- [ ] Pure backend-selection logic factored so it's host-testable.
|
||||
- [x] `system/services/display/backend.zig`: a `Backend` tagged union with `info()`,
|
||||
`surface()` (the cacheable compose target), `present(damage)`, and capability flags
|
||||
(`canModeSet`/`hasVsync`, both false for GOP).
|
||||
- [x] The v1 GOP path is now `backend.Gop` (claims the `display` node, WC-maps the LFB,
|
||||
keeps the cacheable back buffer, `present` = the damage-rect WC copy). display.zig
|
||||
composes into `backend.surface()` and calls `backend.present(damage)` — no LFB or
|
||||
framebuffer geometry left in the compositor core.
|
||||
- [x] The selection decision is the pure `chooseKind(native_available)` (gop unless a
|
||||
native driver announced), split from the syscall-bound `select()`/`Gop.init()`.
|
||||
|
||||
**Gate:** `display-service` + `display-demo` still pass unchanged (pure refactor; GOP is
|
||||
the only backend), and `zig build test` stays green.
|
||||
**Gate (met):** `display-service` + `display-demo` pass **unchanged** (pure refactor; GOP
|
||||
is the only backend), and `zig build test` stays green.
|
||||
|
||||
## V2 — The `shm` cross-process memory capability (kernel)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
//! The compositor's **scanout backend** — how a finished frame reaches the panel
|
||||
//! (docs/display-v2.md). The compositor composes its layer stack into the backend's
|
||||
//! cacheable `surface()` and calls `present(damage)`; everything device-specific lives
|
||||
//! here. Today there is one backend, `Gop` — the firmware framebuffer: a cacheable back
|
||||
//! buffer streamed write-combining to the linear framebuffer. A native virtio-gpu backend
|
||||
//! slots in beside it later (V4); the compositor never learns which is active.
|
||||
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const compositor = @import("compositor.zig");
|
||||
|
||||
const system = runtime.system;
|
||||
const device = runtime.device;
|
||||
const Rect = compositor.Rect;
|
||||
const Surface = compositor.Surface;
|
||||
|
||||
/// The current display mode, as a backend reports it.
|
||||
pub const Info = struct { width: u32, height: u32, pitch: u32, format: u32 };
|
||||
|
||||
/// Enumeration scratch — a `DeviceDescriptor` is large, and only one scan is ever needed.
|
||||
var device_table: [64]device.DeviceDescriptor = undefined;
|
||||
|
||||
/// The GOP framebuffer backend: claims the kernel-seeded `display` device, maps the linear
|
||||
/// framebuffer write-combining as the front buffer, and keeps a cacheable back buffer of
|
||||
/// the same geometry as the compose target. `present` streams the damaged rectangle from
|
||||
/// the back buffer to the LFB (sequential WC writes; the LFB is never read). No mode-set,
|
||||
/// no vsync — the portable floor (docs/display-v2.md).
|
||||
pub const Gop = struct {
|
||||
device_id: u64,
|
||||
front: [*]volatile u8, // the LFB (write-combining)
|
||||
back: [*]u8, // cacheable compose target, same geometry
|
||||
width: u32,
|
||||
height: u32,
|
||||
pitch: u32,
|
||||
format: u32,
|
||||
|
||||
fn findDisplay() ?device.DeviceDescriptor {
|
||||
const total = device.enumerate(&device_table);
|
||||
const n = @min(total, device_table.len);
|
||||
for (device_table[0..n]) |d| {
|
||||
if (d.class == @intFromEnum(device.DeviceClass.display)) return d;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Claim the framebuffer (retrying while discovery catches up), map the LFB, and
|
||||
/// allocate the back buffer. Null if there is no framebuffer or a mapping fails.
|
||||
pub fn init() ?Gop {
|
||||
var tries: u32 = 0;
|
||||
const found = while (tries < 100) : (tries += 1) {
|
||||
if (findDisplay()) |d| break d;
|
||||
system.sleep(50);
|
||||
} else {
|
||||
_ = system.write("display: no framebuffer device (headless?)\n");
|
||||
return null;
|
||||
};
|
||||
|
||||
if (!device.claim(found.id)) {
|
||||
_ = system.write("display: could not claim the framebuffer\n");
|
||||
return null;
|
||||
}
|
||||
// Resource 0 is the framebuffer memory window; the kernel maps it write-combining
|
||||
// because the resource carries that flag (docs/display-plan.md D1).
|
||||
const front_base = device.mmioMap(found.id, 0) orelse {
|
||||
_ = system.write("display: could not map the framebuffer\n");
|
||||
return null;
|
||||
};
|
||||
const geometry = found.display;
|
||||
const size = @as(usize, geometry.height) * geometry.pitch;
|
||||
const back_base = system.mmap(size, system.PROT_READ | system.PROT_WRITE);
|
||||
if (system.mmapFailed(back_base)) {
|
||||
_ = system.write("display: could not allocate the back buffer\n");
|
||||
return null;
|
||||
}
|
||||
return .{
|
||||
.device_id = found.id,
|
||||
.front = @ptrFromInt(front_base),
|
||||
.back = @ptrFromInt(back_base),
|
||||
.width = geometry.width,
|
||||
.height = geometry.height,
|
||||
.pitch = geometry.pitch,
|
||||
.format = geometry.format,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn info(self: *const Gop) Info {
|
||||
return .{ .width = self.width, .height = self.height, .pitch = self.pitch, .format = self.format };
|
||||
}
|
||||
|
||||
/// The cacheable compose target (the back buffer).
|
||||
pub fn surface(self: *const Gop) Surface {
|
||||
return .{
|
||||
.pixels = @ptrCast(@alignCast(self.back)),
|
||||
.stride = self.pitch / 4, // pitch is bytes; a 32-bpp row is pitch/4 pixels
|
||||
.width = self.width,
|
||||
.height = self.height,
|
||||
};
|
||||
}
|
||||
|
||||
/// Stream the damaged rectangle from the back buffer to the write-combining LFB, row by
|
||||
/// row (sequential writes — what WC memory wants; the LFB is never read).
|
||||
pub fn present(self: *const Gop, damage: Rect) void {
|
||||
const c = damage.intersect(.{ .x = 0, .y = 0, .w = @intCast(self.width), .h = @intCast(self.height) });
|
||||
if (c.isEmpty()) return;
|
||||
var y: i32 = c.y;
|
||||
while (y < c.bottom()) : (y += 1) {
|
||||
const off = @as(usize, @intCast(y)) * self.pitch;
|
||||
const src: [*]const u32 = @ptrCast(@alignCast(self.back + off));
|
||||
const dst: [*]volatile u32 = @ptrCast(@alignCast(self.front + off));
|
||||
var x: i32 = c.x;
|
||||
while (x < c.right()) : (x += 1) dst[@intCast(x)] = src[@intCast(x)];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// The pluggable scanout backend. A tagged union so the compositor holds one value and
|
||||
/// dispatches without caring which is active; a `virtio` variant joins `gop` at V4.
|
||||
pub const Backend = union(enum) {
|
||||
gop: Gop,
|
||||
|
||||
pub fn info(self: *const Backend) Info {
|
||||
return switch (self.*) {
|
||||
inline else => |*b| b.info(),
|
||||
};
|
||||
}
|
||||
pub fn surface(self: *const Backend) Surface {
|
||||
return switch (self.*) {
|
||||
inline else => |*b| b.surface(),
|
||||
};
|
||||
}
|
||||
pub fn present(self: *const Backend, damage: Rect) void {
|
||||
switch (self.*) {
|
||||
inline else => |*b| b.present(damage),
|
||||
}
|
||||
}
|
||||
/// Whether this backend supports runtime mode-setting (GOP: no; a native driver: yes).
|
||||
pub fn canModeSet(self: *const Backend) bool {
|
||||
return switch (self.*) {
|
||||
.gop => false,
|
||||
};
|
||||
}
|
||||
/// Whether this backend has a vblank/fence for tear-free present (GOP: no).
|
||||
pub fn hasVsync(self: *const Backend) bool {
|
||||
return switch (self.*) {
|
||||
.gop => false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Which backend to use. The pure selection *decision* is `chooseKind`; `select` below
|
||||
/// binds it to the (syscall-bound) bring-up.
|
||||
pub const Kind = enum { gop, virtio };
|
||||
|
||||
/// The selection decision, factored out of bring-up so it stays pure and host-testable:
|
||||
/// prefer a native driver when one has announced itself (docs/display-v2.md V4), else the
|
||||
/// GOP floor. Trivial today; it grows real inputs when native detection lands.
|
||||
pub fn chooseKind(native_available: bool) Kind {
|
||||
return if (native_available) .virtio else .gop;
|
||||
}
|
||||
|
||||
/// Pick and bring up the best available backend. Today the GOP framebuffer is the only one
|
||||
/// (`chooseKind(false)` → `.gop`), so this is `Gop.init()`. V4 adds the native-if-present
|
||||
/// branch, with GOP as the floor.
|
||||
pub fn select() ?Backend {
|
||||
return switch (chooseKind(false)) {
|
||||
.gop => .{ .gop = Gop.init() orelse return null },
|
||||
.virtio => unreachable, // no native detection yet (V4)
|
||||
};
|
||||
}
|
||||
|
||||
test "selection prefers native when present, else the gop floor" {
|
||||
try std.testing.expectEqual(Kind.gop, chooseKind(false));
|
||||
try std.testing.expectEqual(Kind.virtio, chooseKind(true));
|
||||
}
|
||||
|
|
@ -1,43 +1,32 @@
|
|||
//! /system/services/display — the display service (docs/display.md). A ring-3 process
|
||||
//! that claims the framebuffer the kernel seeded (docs/display-plan.md D1), owns it as a
|
||||
//! **write-combining front buffer**, composites an ordered stack of **layers** into a
|
||||
//! **cacheable back buffer**, and presents finished frames — the GUI track's compositor,
|
||||
//! the sibling of the input service. Reached by name over `ServiceId.display`.
|
||||
//! /system/services/display — the display service (docs/display.md, docs/display-v2.md).
|
||||
//! A ring-3 compositor: it composes an ordered stack of **layers** into a cacheable
|
||||
//! surface and presents finished frames. Scanout — how a frame reaches the panel — is a
|
||||
//! pluggable **backend** ([backend.zig](backend.zig)): the GOP framebuffer today, a native
|
||||
//! virtio-gpu driver later; this file never learns which is active. It owns the layer stack
|
||||
//! and damage tracking; the pixel math is the pure, host-tested
|
||||
//! [compositor.zig](compositor.zig).
|
||||
//!
|
||||
//! A layer is a server-owned surface (its own cacheable buffer) with a screen position,
|
||||
//! z-order, and visibility. Clients create layers and draw into them by command
|
||||
//! (`fill_rect`, `blit_tile`), mark `damage`, and ask for a `present`; the compositor
|
||||
//! repaints only the damaged region — clear it, paint the visible layers bottom-to-top,
|
||||
//! flush it to the screen. The pixel math lives in the pure, host-tested
|
||||
//! [compositor.zig](compositor.zig); this file wires real surfaces and the framebuffer to
|
||||
//! it. Shared-memory client surfaces are a later milestone (docs/display.md).
|
||||
//! z-order, and visibility. Clients create layers, draw into them by command (`fill_rect`,
|
||||
//! `blit_tile`), mark `damage`, and ask for a `present`; the compositor repaints only the
|
||||
//! damaged region — clear it, paint the visible layers bottom-to-top into the backend's
|
||||
//! surface, then `backend.present(damage)`. Shared-memory client surfaces are later
|
||||
//! (docs/display-v2.md).
|
||||
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const compositor = @import("compositor.zig");
|
||||
const backend_mod = @import("backend.zig");
|
||||
|
||||
const protocol = runtime.display_protocol;
|
||||
const ipc = runtime.ipc;
|
||||
const system = runtime.system;
|
||||
const device = runtime.device;
|
||||
const Rect = compositor.Rect;
|
||||
const Surface = compositor.Surface;
|
||||
|
||||
/// The claimed framebuffer and its off-screen twin. The front buffer is the LFB —
|
||||
/// write-combining, so it is **only ever written**, never read; all compositing happens
|
||||
/// in the cacheable back buffer, which is then streamed to the front (docs/display.md).
|
||||
const Display = struct {
|
||||
device_id: u64,
|
||||
front: [*]volatile u8, // the LFB (write-combining)
|
||||
back: [*]u8, // cacheable, same geometry
|
||||
width: u32,
|
||||
height: u32,
|
||||
pitch: u32, // bytes per row (shared by both buffers)
|
||||
format: u32, // a device-abi DisplayFormat value
|
||||
frames: u64 = 0,
|
||||
};
|
||||
|
||||
var display: Display = undefined;
|
||||
/// The active scanout backend — GOP today, a native driver when one is present.
|
||||
var backend: backend_mod.Backend = undefined;
|
||||
var frames: u64 = 0;
|
||||
|
||||
/// The wallpaper the compositor clears damaged regions to before painting layers.
|
||||
var background: u32 = 0;
|
||||
|
|
@ -60,23 +49,11 @@ const Layer = struct {
|
|||
var layers: [maximum_layers]Layer = [_]Layer{.{}} ** maximum_layers;
|
||||
var damage: Rect = Rect.empty;
|
||||
|
||||
/// Enumeration buffer kept off the stack — a `DeviceDescriptor` is large, and this
|
||||
/// service only ever needs one scan.
|
||||
var device_table: [64]device.DeviceDescriptor = undefined;
|
||||
|
||||
// --- geometry helpers -------------------------------------------------------
|
||||
|
||||
fn screenRect() Rect {
|
||||
return .{ .x = 0, .y = 0, .w = @intCast(display.width), .h = @intCast(display.height) };
|
||||
}
|
||||
|
||||
fn backSurface() Surface {
|
||||
return .{
|
||||
.pixels = @ptrCast(@alignCast(display.back)),
|
||||
.stride = display.pitch / 4, // pitch is bytes; a 32-bpp row is pitch/4 pixels
|
||||
.width = display.width,
|
||||
.height = display.height,
|
||||
};
|
||||
const m = backend.info();
|
||||
return .{ .x = 0, .y = 0, .w = @intCast(m.width), .h = @intCast(m.height) };
|
||||
}
|
||||
|
||||
fn layerScreenRect(l: *const Layer) Rect {
|
||||
|
|
@ -159,11 +136,11 @@ fn destroyLayer(id: u32) bool {
|
|||
|
||||
// --- compositing + present --------------------------------------------------
|
||||
|
||||
/// Repaint the damaged region `clip` of the back buffer: clear it to the background, then
|
||||
/// paint every visible layer that overlaps it, bottom to top (ascending z).
|
||||
/// Repaint the damaged region `clip` of the backend's compose surface: clear it to the
|
||||
/// background, then paint every visible layer that overlaps it, bottom to top (ascending z).
|
||||
fn compositeInto(clip: Rect) void {
|
||||
const back = backSurface();
|
||||
compositor.fillRect(back, clip, background);
|
||||
const target = backend.surface();
|
||||
compositor.fillRect(target, clip, background);
|
||||
|
||||
// z-order the used, visible layers (n ≤ 16; a plain insertion sort of indices).
|
||||
var order: [maximum_layers]u32 = undefined;
|
||||
|
|
@ -184,56 +161,42 @@ fn compositeInto(clip: Rect) void {
|
|||
|
||||
for (order[0..n]) |i| {
|
||||
const l = layers[i];
|
||||
compositor.composite(back, l.x, l.y, l.surface, clip);
|
||||
compositor.composite(target, l.x, l.y, l.surface, clip);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream the damaged rectangle from the cacheable back buffer to the write-combining
|
||||
/// front buffer, row by row (sequential writes — what WC memory wants; we never read the
|
||||
/// front buffer). Only the visible width of each row is touched.
|
||||
fn flushRect(rect: Rect) void {
|
||||
const c = rect.intersect(screenRect());
|
||||
if (c.isEmpty()) return;
|
||||
var y: i32 = c.y;
|
||||
while (y < c.bottom()) : (y += 1) {
|
||||
const off = @as(usize, @intCast(y)) * display.pitch;
|
||||
const src: [*]const u32 = @ptrCast(@alignCast(display.back + off));
|
||||
const dst: [*]volatile u32 = @ptrCast(@alignCast(display.front + off));
|
||||
var x: i32 = c.x;
|
||||
while (x < c.right()) : (x += 1) dst[@intCast(x)] = src[@intCast(x)];
|
||||
}
|
||||
}
|
||||
|
||||
/// Composite and flush the accumulated damage, then clear it. A no-op when nothing is
|
||||
/// dirty. The frame counter advances regardless, so callers can name frames.
|
||||
/// Composite the accumulated damage into the backend's surface, hand it to the backend to
|
||||
/// put on screen, then clear the damage. A no-op when nothing is dirty. The frame counter
|
||||
/// advances regardless, so callers can name frames.
|
||||
fn present() void {
|
||||
const dirty = damage.intersect(screenRect());
|
||||
if (!dirty.isEmpty()) {
|
||||
compositeInto(dirty);
|
||||
flushRect(dirty);
|
||||
backend.present(dirty);
|
||||
}
|
||||
damage = Rect.empty;
|
||||
display.frames += 1;
|
||||
frames += 1;
|
||||
}
|
||||
|
||||
// --- startup self-check -----------------------------------------------------
|
||||
|
||||
/// Prove the compositor wiring on the real framebuffer: two overlapping opaque layers,
|
||||
/// Prove the compositor wiring on the real backend: two overlapping opaque layers,
|
||||
/// composited, must show the top layer in the overlap and the bottom layer outside it.
|
||||
/// Exercises the whole path — mmap surfaces, the z-sort, damage, composite into the back
|
||||
/// buffer — and reads the composited result back. Cleans up after itself.
|
||||
/// Exercises the whole path — mmap surfaces, the z-sort, damage, composite into the
|
||||
/// backend surface — and reads the composited result back. Cleans up after itself.
|
||||
fn selfCheck() void {
|
||||
const red = protocol.pack(display.format, 0xC0, 0x20, 0x20);
|
||||
const green = protocol.pack(display.format, 0x20, 0xC0, 0x20);
|
||||
const format = backend.info().format;
|
||||
const red = protocol.pack(format, 0xC0, 0x20, 0x20);
|
||||
const green = protocol.pack(format, 0x20, 0xC0, 0x20);
|
||||
const bottom = createLayer(100, 100, 80, 80, 0, true) orelse return fail_check("create");
|
||||
const top = createLayer(140, 140, 80, 80, 1, true) orelse return fail_check("create");
|
||||
_ = fillLayer(bottom, Rect.init(0, 0, 80, 80), red);
|
||||
_ = fillLayer(top, Rect.init(0, 0, 80, 80), green);
|
||||
present();
|
||||
|
||||
const back = backSurface();
|
||||
const overlap = back.pixels[@as(usize, 150) * back.stride + 150]; // in both layers → top
|
||||
const bottom_only = back.pixels[@as(usize, 110) * back.stride + 110]; // bottom only
|
||||
const surface = backend.surface();
|
||||
const overlap = surface.pixels[@as(usize, 150) * surface.stride + 150]; // in both → top
|
||||
const bottom_only = surface.pixels[@as(usize, 110) * surface.stride + 110]; // bottom only
|
||||
|
||||
_ = destroyLayer(top);
|
||||
_ = destroyLayer(bottom);
|
||||
|
|
@ -252,67 +215,22 @@ fn fail_check(_: []const u8) void {
|
|||
|
||||
// --- service ----------------------------------------------------------------
|
||||
|
||||
/// The framebuffer node the kernel seeded (`DeviceClass.display`), or null if none.
|
||||
fn findDisplay() ?device.DeviceDescriptor {
|
||||
const total = device.enumerate(&device_table);
|
||||
const n = @min(total, device_table.len);
|
||||
for (device_table[0..n]) |d| {
|
||||
if (d.class == @intFromEnum(device.DeviceClass.display)) return d;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn initialise(endpoint: ipc.Handle) bool {
|
||||
_ = endpoint;
|
||||
|
||||
// Find the framebuffer, retrying while device discovery catches up with our spawn.
|
||||
var tries: u32 = 0;
|
||||
const found = while (tries < 100) : (tries += 1) {
|
||||
if (findDisplay()) |d| break d;
|
||||
system.sleep(50);
|
||||
} else {
|
||||
_ = system.write("display: no framebuffer device (headless?)\n");
|
||||
return false; // clean exit: nothing to drive
|
||||
};
|
||||
// Pick the scanout backend (GOP today). It logs the reason on failure.
|
||||
backend = backend_mod.select() orelse return false;
|
||||
const mode = backend.info();
|
||||
background = protocol.pack(mode.format, 0x20, 0x30, 0x48); // a dark slate wallpaper
|
||||
|
||||
if (!device.claim(found.id)) {
|
||||
_ = system.write("display: could not claim the framebuffer\n");
|
||||
return false;
|
||||
}
|
||||
// Resource 0 is the framebuffer memory window; the kernel maps it write-combining
|
||||
// because the resource carries that flag (docs/display-plan.md D1).
|
||||
const front_base = device.mmioMap(found.id, 0) orelse {
|
||||
_ = system.write("display: could not map the framebuffer\n");
|
||||
return false;
|
||||
};
|
||||
|
||||
const geometry = found.display;
|
||||
const size = @as(usize, geometry.height) * geometry.pitch;
|
||||
const back_base = system.mmap(size, system.PROT_READ | system.PROT_WRITE);
|
||||
if (system.mmapFailed(back_base)) {
|
||||
_ = system.write("display: could not allocate the back buffer\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
display = .{
|
||||
.device_id = found.id,
|
||||
.front = @ptrFromInt(front_base),
|
||||
.back = @ptrFromInt(back_base),
|
||||
.width = geometry.width,
|
||||
.height = geometry.height,
|
||||
.pitch = geometry.pitch,
|
||||
.format = geometry.format,
|
||||
};
|
||||
background = protocol.pack(display.format, 0x20, 0x30, 0x48); // a dark slate wallpaper
|
||||
|
||||
// Clear the whole screen through the back buffer → present path (double buffering:
|
||||
// no direct-to-LFB drawing).
|
||||
// Clear the whole screen through the compose surface → present path (double buffering:
|
||||
// no direct-to-scanout drawing).
|
||||
addDamage(screenRect());
|
||||
present();
|
||||
|
||||
var line: [96]u8 = undefined;
|
||||
_ = system.write(std.fmt.bufPrint(&line, "display: online {d}x{d} pitch {d} format {d}\n", .{
|
||||
display.width, display.height, display.pitch, display.format,
|
||||
mode.width, mode.height, mode.pitch, mode.format,
|
||||
}) catch "display: online\n");
|
||||
_ = system.write("display: presented frame 0\n");
|
||||
|
||||
|
|
@ -343,13 +261,10 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?ipc.Han
|
|||
// Switch on the raw operation value — an out-of-range one must fail cleanly, not
|
||||
// panic an `@enumFromInt`.
|
||||
switch (request.operation) {
|
||||
@intFromEnum(protocol.Operation.info) => return writeReply(reply, .{
|
||||
.status = 0,
|
||||
.width = display.width,
|
||||
.height = display.height,
|
||||
.pitch = display.pitch,
|
||||
.format = display.format,
|
||||
}),
|
||||
@intFromEnum(protocol.Operation.info) => {
|
||||
const m = backend.info();
|
||||
return writeReply(reply, .{ .status = 0, .width = m.width, .height = m.height, .pitch = m.pitch, .format = m.format });
|
||||
},
|
||||
@intFromEnum(protocol.Operation.create_layer) => {
|
||||
// x/y are signed coordinates carried in the u32 wire fields — reinterpret the
|
||||
// bits (@bitCast), don't range-check (@intCast) which a negative would fail.
|
||||
|
|
|
|||
Loading…
Reference in New Issue