//! The display wire protocol — what a client says to the display service over //! `/protocol/display`. The compositor owns the framebuffer and an ordered stack of //! **layers**; a client creates layers, draws into them with these operations, marks damage, //! and asks for a `present`. v1 surfaces are server-owned (a client draws by command); //! shared-memory surfaces are a later milestone (docs/display.md). //! //! **`Header.target` is the layer** on every verb that names one — the field that used to be //! `Request.layer`. `info`, `present`, `set_mode`, `get_modes` and `attach_scanout` address //! the compositor itself, so they leave it 0. //! //! Every verb carries its own request type. The single overloaded 40-byte request this //! protocol used to have is gone, and with it the field abuse it invited: `attach_scanout` //! spent `x` on a stride, `y` on a refresh rate and `colour` on a pixel format, which no //! reader could have guessed and no compiler could have caught. const envelope = @import("envelope"); const std = @import("std"); /// The answer to `info()`: the display's current mode. pub const Info = extern struct { width: u32 = 0, height: u32 = 0, pitch: u32 = 0, // bytes per row (may exceed width*4) format: u32 = 0, // a device-abi DisplayFormat value (0 = rgbx, 1 = bgrx) }; /// `create_layer(...)`: a new server-owned surface. Coordinates are signed — a layer may sit /// partly off-screen. pub const CreateLayer = extern struct { x: i32, y: i32, width: u32, height: u32, z: u32 = 0, // stacking order (higher = nearer the front) visible: u32 = 1, }; /// The layer a `create_layer` established — the integer later packets put in `Header.target`. pub const Created = extern struct { layer: u32 }; /// `configure_layer(...)` on `Header.target`: move, restack, show, or hide it. pub const ConfigureLayer = extern struct { x: i32, y: i32, z: u32 = 0, visible: u32 = 1, // 0 hides the layer }; /// `fill_rect(...)` on `Header.target`: fill a layer-local rectangle with a native pixel value. pub const FillRect = extern struct { x: i32, y: i32, width: u32, height: u32, colour: u32, }; /// `blit_tile(...)` on `Header.target`: copy a `width`×`height` tile of native pixels /// (row-major, little-endian) into the layer. The pixels ride inline as the packet's tail, /// up to `maximum_payload`. pub const BlitTile = extern struct { x: i32, y: i32, width: u32, height: u32, }; /// `damage(...)` on `Header.target`: mark a layer-local region dirty for the next present. pub const Damage = extern struct { x: i32, y: i32, width: u32, height: u32, }; /// `attach_scanout(...)` + the shared surface as the call's capability: a native scanout /// driver announces itself. The compositor maps the surface, opens the driver's /// `/protocol/scanout` present channel, and upgrades off the GOP floor (docs/display-v2.md /// V4). Each field says what it is, which the old shared request could not. pub const AttachScanout = extern struct { /// The surface's row stride in pixels (it is sized to the driver's largest mode). stride: u32, /// The active mode within that surface. width: u32, height: u32, /// A device-abi DisplayFormat value. format: u32, /// The panel refresh rate from the driver's EDID read (0 = unknown); it paces the /// compositor's frame clock. refresh_hz: u32 = 0, }; /// `set_mode(width, height)`: change the display resolution — only a native backend that /// reports `canModeSet` honours it; on the GOP floor it fails (docs/display-v2.md V5). pub const SetMode = extern struct { width: u32, height: u32 }; /// One selectable display mode. pub const Mode = extern struct { width: u32, height: u32 }; pub const max_modes = 4; /// The answer to `get_modes`: the resolutions the display can switch to (empty on GOP). pub const Modes = extern struct { count: u32 = 0, _padding: u32 = 0, modes: [max_modes]Mode = @splat(.{ .width = 0, .height = 0 }), }; pub const Protocol = envelope.Define(.{ .name = "display", .version = 1, .operations = &.{ .{ .name = "info", .reply = Info }, .{ .name = "create_layer", .request = CreateLayer, .reply = Created }, .{ .name = "configure_layer", .request = ConfigureLayer }, .{ .name = "destroy_layer" }, .{ .name = "fill_rect", .request = FillRect }, .{ .name = "blit_tile", .request = BlitTile }, .{ .name = "damage", .request = Damage }, .{ .name = "present" }, .{ .name = "attach_scanout", .request = AttachScanout }, .{ .name = "set_mode", .request = SetMode }, .{ .name = "get_modes", .reply = Modes }, }, }); pub const Operation = Protocol.Operation; pub const message_maximum: usize = Protocol.message_maximum; /// The largest inline pixel tile a `blit_tile` may carry: the call floor less the header and /// this verb's own fixed part — 224 bytes, up to 56 pixels, enough for a cursor or a small /// sprite. Larger bitmaps are the deferred shared-memory surface path (docs/display.md). /// Per-verb rather than protocol-wide, because with per-operation requests there is no /// single "request size" to subtract any more. pub const maximum_payload: usize = envelope.packet_maximum - envelope.prefix_size - @sizeOf(BlitTile); /// Pack an 8-bit-per-channel colour into the display's native 32-bit pixel for `format` /// (a device-abi `DisplayFormat`: 0 = rgbx, 1 = bgrx). Shared so a `colour` in a /// `fill_rect` request means the same thing to the client that sends it and the /// compositor that paints it. Little-endian memory, reserved byte 0: rgbx puts red in /// the low byte, bgrx puts blue there. pub fn pack(format: u32, r: u8, g: u8, b: u8) u32 { const rr: u32 = r; const gg: u32 = g; const bb: u32 = b; return switch (format) { 1 => bb | (gg << 8) | (rr << 16), // bgrx else => rr | (gg << 8) | (bb << 16), // rgbx }; } test "pack encodes native byte order for rgbx and bgrx" { // rgbx: red in the low byte, blue in byte 2. try std.testing.expectEqual(@as(u32, 0x0000_00AA), pack(0, 0xAA, 0, 0)); try std.testing.expectEqual(@as(u32, 0x00AA_0000), pack(0, 0, 0, 0xAA)); // bgrx: blue in the low byte, red in byte 2. try std.testing.expectEqual(@as(u32, 0x0000_00AA), pack(1, 0, 0, 0xAA)); try std.testing.expectEqual(@as(u32, 0x00AA_0000), pack(1, 0xAA, 0, 0)); try std.testing.expectEqual(@as(u32, 0x0000_3020), pack(0, 0x20, 0x30, 0)); // green in byte 1 } test "the layer rides the header, and the blit tile grew with the split" { var buffer: [message_maximum]u8 = undefined; const pixels = [_]u8{0xFF} ** 16; const packet = Protocol.encodeRequest(.blit_tile, 3, .{ .x = 1, .y = 2, .width = 2, .height = 2 }, &pixels, &buffer).?; try std.testing.expectEqual(@as(u64, 3), envelope.headerOf(packet).?.target); try std.testing.expectEqual(@as(i32, 1), Protocol.decodeRequest(.blit_tile, packet).?.x); try std.testing.expectEqual(@as(usize, 16), Protocol.requestTail(.blit_tile, packet).len); // 216 bytes under the old 40-byte shared request; the header plus this // verb's own four fields is 32. try std.testing.expectEqual(@as(usize, 224), maximum_payload); }