172 lines
6.7 KiB
Zig
172 lines
6.7 KiB
Zig
//! User-space display client: talk to the display service (query the mode, and — from D3
|
||
//! — create layers, draw, and present) without hand-rolling the IPC. The `runtime.block`
|
||
//! shape: a cached `.display` lookup with a boot-race retry, then extern-struct request/
|
||
//! reply marshalling. See system/services/display/ and docs/display.md.
|
||
|
||
const std = @import("std");
|
||
const ipc = @import("ipc.zig");
|
||
const system = @import("system.zig");
|
||
const protocol = @import("display-protocol");
|
||
|
||
/// The display's current mode, as `info()` reports it.
|
||
pub const Info = struct {
|
||
width: u32,
|
||
height: u32,
|
||
pitch: u32, // bytes per row (may exceed width*4; see docs/framebuffer.md)
|
||
format: u32, // a device-abi DisplayFormat value (0 = rgbx, 1 = bgrx)
|
||
};
|
||
|
||
/// The service endpoint, looked up once and cached.
|
||
var handle: ?ipc.Handle = null;
|
||
|
||
/// Look up the display service, retrying while it comes up (a client races its
|
||
/// registration at boot). Returns the endpoint, or null if it never appears.
|
||
fn service() ?ipc.Handle {
|
||
if (handle) |h| return h;
|
||
var attempts: usize = 0;
|
||
while (attempts < 100) : (attempts += 1) {
|
||
if (ipc.lookup(.display)) |h| {
|
||
handle = h;
|
||
return h;
|
||
}
|
||
system.sleep(50);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// Send one request, receive its reply; true on a zero status. `out` receives the reply
|
||
/// so callers can read `info`/`layer` fields on success.
|
||
fn transact(request: protocol.Request, out: *protocol.Reply) bool {
|
||
const h = service() orelse return false;
|
||
var req = request;
|
||
var reply: [protocol.reply_size]u8 = undefined;
|
||
const len = ipc.call(h, std.mem.asBytes(&req), &reply) catch return false;
|
||
if (len < protocol.reply_size) return false;
|
||
out.* = std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]);
|
||
return out.status == 0;
|
||
}
|
||
|
||
/// The display's current mode, or null if the service never came up.
|
||
pub fn info() ?Info {
|
||
var reply: protocol.Reply = undefined;
|
||
if (!transact(.{ .operation = @intFromEnum(protocol.Operation.info) }, &reply)) return null;
|
||
return .{ .width = reply.width, .height = reply.height, .pitch = reply.pitch, .format = reply.format };
|
||
}
|
||
|
||
/// Composite the dirty layers and flush the frame to the screen.
|
||
pub fn present() bool {
|
||
var reply: protocol.Reply = undefined;
|
||
return transact(.{ .operation = @intFromEnum(protocol.Operation.present) }, &reply);
|
||
}
|
||
|
||
/// The mode, cached after the first `info()` so `color()` doesn't round-trip per pixel.
|
||
var mode: ?Info = null;
|
||
|
||
fn cachedInfo() ?Info {
|
||
if (mode) |m| return m;
|
||
const i = info() orelse return null;
|
||
mode = i;
|
||
return i;
|
||
}
|
||
|
||
/// The native pixel value for an 8-bit-per-channel colour, in the display's format. A
|
||
/// client packs colours through this so it never has to know the byte order itself.
|
||
pub fn color(r: u8, g: u8, b: u8) u32 {
|
||
const format = if (cachedInfo()) |i| i.format else 0;
|
||
return protocol.pack(format, r, g, b);
|
||
}
|
||
|
||
/// A handle to a server-owned layer: a positioned, z-ordered surface the client draws
|
||
/// into by command. Create with `createLayer`; drawing and moves take effect on the next
|
||
/// `present`. Coordinates are signed (a layer may sit partly off-screen).
|
||
pub const Layer = struct {
|
||
id: u32,
|
||
|
||
/// Fill a rectangle of this layer (layer-local coordinates) with a native `colour`.
|
||
pub fn fill(self: Layer, x: i32, y: i32, w: u32, h: u32, colour: u32) bool {
|
||
var reply: protocol.Reply = undefined;
|
||
return transact(.{
|
||
.operation = @intFromEnum(protocol.Operation.fill_rect),
|
||
.layer = self.id,
|
||
.x = @bitCast(x),
|
||
.y = @bitCast(y),
|
||
.width = w,
|
||
.height = h,
|
||
.colour = colour,
|
||
}, &reply);
|
||
}
|
||
|
||
/// Copy a `w`×`h` tile of native pixels (row-major, little-endian bytes) into this
|
||
/// layer at (`x`, `y`). The tile rides inline in the request, so `w*h*4` must fit
|
||
/// `protocol.maximum_payload`.
|
||
pub fn blitTile(self: Layer, x: i32, y: i32, w: u32, h: u32, pixels: []const u8) bool {
|
||
var request = protocol.Request{
|
||
.operation = @intFromEnum(protocol.Operation.blit_tile),
|
||
.layer = self.id,
|
||
.x = @bitCast(x),
|
||
.y = @bitCast(y),
|
||
.width = w,
|
||
.height = h,
|
||
};
|
||
const header = std.mem.asBytes(&request);
|
||
if (header.len + pixels.len > protocol.message_maximum) return false;
|
||
var buffer: [protocol.message_maximum]u8 = undefined;
|
||
@memcpy(buffer[0..header.len], header);
|
||
@memcpy(buffer[header.len..][0..pixels.len], pixels);
|
||
const h_svc = service() orelse return false;
|
||
var reply: [protocol.reply_size]u8 = undefined;
|
||
const len = ipc.call(h_svc, buffer[0 .. header.len + pixels.len], &reply) catch return false;
|
||
if (len < protocol.reply_size) return false;
|
||
return std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]).status == 0;
|
||
}
|
||
|
||
/// Move / restack / show or hide the layer.
|
||
pub fn configure(self: Layer, x: i32, y: i32, z: u32, visible: bool) bool {
|
||
var reply: protocol.Reply = undefined;
|
||
return transact(.{
|
||
.operation = @intFromEnum(protocol.Operation.configure_layer),
|
||
.layer = self.id,
|
||
.x = @bitCast(x),
|
||
.y = @bitCast(y),
|
||
.z = z,
|
||
.visible = if (visible) 1 else 0,
|
||
}, &reply);
|
||
}
|
||
|
||
/// Mark a rectangle of this layer (layer-local) dirty for the next present — for when
|
||
/// the layer's pixels changed without a drawing call the compositor already tracked.
|
||
pub fn damage(self: Layer, x: i32, y: i32, w: u32, h: u32) bool {
|
||
var reply: protocol.Reply = undefined;
|
||
return transact(.{
|
||
.operation = @intFromEnum(protocol.Operation.damage),
|
||
.layer = self.id,
|
||
.x = @bitCast(x),
|
||
.y = @bitCast(y),
|
||
.width = w,
|
||
.height = h,
|
||
}, &reply);
|
||
}
|
||
|
||
/// Release the layer and its surface.
|
||
pub fn destroy(self: Layer) bool {
|
||
var reply: protocol.Reply = undefined;
|
||
return transact(.{ .operation = @intFromEnum(protocol.Operation.destroy_layer), .layer = self.id }, &reply);
|
||
}
|
||
};
|
||
|
||
/// Create a server-owned layer of `w`×`h` pixels at screen (`x`, `y`) with stacking order
|
||
/// `z` (higher is nearer the front), initially visible. Returns a handle, or null.
|
||
pub fn createLayer(x: i32, y: i32, w: u32, h: u32, z: u32) ?Layer {
|
||
var reply: protocol.Reply = undefined;
|
||
if (!transact(.{
|
||
.operation = @intFromEnum(protocol.Operation.create_layer),
|
||
.x = @bitCast(x),
|
||
.y = @bitCast(y),
|
||
.width = w,
|
||
.height = h,
|
||
.z = z,
|
||
.visible = 1,
|
||
}, &reply)) return null;
|
||
return .{ .id = reply.layer };
|
||
}
|