From f40252c485ea9ff6d8b213990881f9ac37b79ac1 Mon Sep 17 00:00:00 2001 From: Jackson Netherwood-Imig Date: Mon, 26 Jan 2026 10:30:40 -0800 Subject: [PATCH] vsync'd modesetting! --- build.zig | 5 + example.zig | 2 +- src/drm.zig | 75 ++++++++++++++- src/mode.zig | 129 +++++++++++++++++++++++-- src/sys.zig | 267 +++++++++++++++++++++++++++++++-------------------- 5 files changed, 362 insertions(+), 116 deletions(-) diff --git a/build.zig b/build.zig index 47d1bc3..706f60f 100644 --- a/build.zig +++ b/build.zig @@ -20,4 +20,9 @@ pub fn build(b: *std.Build) void { }); example.root_module.addImport("drm", drm); b.installArtifact(example); + + const test_exe = b.addTest(.{ .root_module = drm }); + const run_tests = b.addRunArtifact(test_exe); + const test_step = b.step("test", "Run tests."); + test_step.dependOn(&run_tests.step); } diff --git a/example.zig b/example.zig index d22f5aa..ac3432e 100644 --- a/example.zig +++ b/example.zig @@ -37,7 +37,7 @@ fn chooseConnector( return error.NoConnectedConnectors; } -fn chooseMode(modes: []const drm.sys.mode.Modeinfo) drm.sys.mode.Modeinfo { +fn chooseMode(modes: []const drm.sys.mode.ModeInfo) drm.sys.mode.ModeInfo { var best = modes[0]; const best_score = @as(u64, best.hdisplay) * @as(u64, best.vdisplay) * @as(u64, best.vrefresh); for (modes) |mode| { diff --git a/src/drm.zig b/src/drm.zig index e996d9d..c1deb76 100644 --- a/src/drm.zig +++ b/src/drm.zig @@ -14,7 +14,7 @@ pub const Card = struct { pub fn open(io: std.Io, path: []const u8) OpenError!Card { if (std.fs.path.isAbsolute(path)) - return std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_write }); + return Card{ .handle = try std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_write }) }; const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{}); defer dir.close(io); @@ -42,4 +42,77 @@ pub const Card = struct { pub inline fn modeHandle(self: Card) mode.Card { return .{ .handle = self.handle }; } + + pub fn handleEvents( + self: Card, + io: std.Io, + callback: fn (Card, Event, anytype) anyerror!void, + args: anytype, + ) anyerror!void { + var buf: [4096]u8 = undefined; + const read = try self.handle.readStreaming(io, &.{&buf}); + if (read == 0) return error.EndOfStream; + + var offset: usize = 0; + while (offset < read) { + if (read - offset < @sizeOf(sys.Event)) return error.IncompleteEvent; + + const ev: *align(1) const sys.Event = std.mem.bytesAsValue( + sys.Event, + buf[offset..][0..@sizeOf(sys.Event)], + ); + + offset += ev.length; + + const event = Event.parse(ev); + try callback(self, event, args); + } + } }; + +pub const Event = union(enum) { + vblank: Vblank, + flip_complete: Vblank, + crtc_sequence: CrtcSequence, + + pub fn parse(event: *align(1) const sys.Event) Event { + return switch (event.type) { + inline .vblank, .flip_complete => |tag| @unionInit(Event, @tagName(tag), ev: { + const vblank: *align(1) const sys.EventVblank = @ptrCast(@alignCast(event)); + break :ev .{ + .user_data = @ptrFromInt(vblank.user_data), + .sec = vblank.tv_sec, + .usec = vblank.tv_usec, + .sequence = vblank.sequence, + .crtc_id = vblank.crtc_id, + }; + }), + .crtc_sequence => Event{ .crtc_sequence = ev: { + const crtc_sequence: *align(1) const sys.EventCrtcSequence = @ptrCast(@alignCast(event)); + break :ev .{ + .user_data = @ptrFromInt(crtc_sequence.user_data), + .ns = crtc_sequence.time_ns, + .sequence = crtc_sequence.sequence, + }; + } }, + }; + } + + pub const Vblank = struct { + user_data: *anyopaque, + sec: u32, + usec: u32, + sequence: u32, + crtc_id: u32, + }; + + pub const CrtcSequence = struct { + user_data: *anyopaque, + ns: i64, + sequence: u64, + }; +}; + +test { + std.testing.refAllDeclsRecursive(@This()); +} diff --git a/src/mode.zig b/src/mode.zig index 8ea6626..ed0f6c8 100644 --- a/src/mode.zig +++ b/src/mode.zig @@ -1,6 +1,7 @@ const std = @import("std"); const sys = @import("sys.zig"); const base = @import("drm.zig"); +const fmt = @import("format.zig"); const Allocator = std.mem.Allocator; pub const Card = struct { @@ -10,7 +11,7 @@ pub const Card = struct { pub fn open(io: std.Io, path: []const u8) OpenError!Card { if (std.fs.path.isAbsolute(path)) - return std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_write }); + return Card{ .handle = try std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_write }) }; const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{}); defer dir.close(io); @@ -144,7 +145,7 @@ pub const Card = struct { const encoders = try gpa.alloc(u32, get_conn.count_encoders); errdefer gpa.free(encoders); - const modes = try gpa.alloc(sys.mode.Modeinfo, get_conn.count_modes); + const modes = try gpa.alloc(sys.mode.ModeInfo, get_conn.count_modes); errdefer gpa.free(modes); const props = try gpa.alloc(u32, get_conn.count_props); @@ -183,14 +184,16 @@ pub const Card = struct { .prop_values = prop_values[0..get_conn.count_props], .mm_width = get_conn.mm_width, .mm_height = get_conn.mm_height, + .type = get_conn.connector_type, + .type_id = get_conn.connector_type_id, }; } } - pub inline fn getConnectorBuffered( + pub fn getConnectorBuffered( self: Card, encoders: []u32, - modes: []sys.mode.Modeinfo, + modes: []sys.mode.ModeInfo, props: []u32, prop_values: []u64, id: u32, @@ -224,12 +227,12 @@ pub const Card = struct { .prop_values = prop_values[0..get_conn.count_props], .mm_width = get_conn.mm_width, .mm_height = get_conn.mm_height, + .type = get_conn.connector_type, + .type_id = get_conn.connector_type_id, }; } - pub const GetEncoderError = sys.IoctlError; - - pub fn getEncoder(self: Card, id: u32) GetEncoderError!Encoder { + pub fn getEncoder(self: Card, id: u32) sys.IoctlError!Encoder { var get_encoder = std.mem.zeroInit(sys.mode.GetEncoder, .{ .encoder_id = id }); try sys.ioctl(self.handle.handle, .mode_getencoder, &get_encoder); @@ -257,6 +260,102 @@ pub const Card = struct { .mode = if (crtc.mode_valid != 0) crtc.mode else null, }; } + + pub fn setCrtc( + self: Card, + id: u32, + fb_id: u32, + x: u32, + y: u32, + connectors: []u32, + mode: ?sys.mode.ModeInfo, + ) sys.IoctlError!void { + var crtc = std.mem.zeroInit(sys.mode.Crtc, .{ + .crtc_id = id, + .fb_id = fb_id, + .x = x, + .y = y, + .count_connectors = @as(u32, @intCast(connectors.len)), + .set_connectors_ptr = @intFromPtr(connectors.ptr), + }); + if (mode) |m| { + crtc.mode_valid = 1; + crtc.mode = m; + } + try sys.ioctl(self.handle.handle, .mode_setcrtc, &crtc); + } + + pub fn createDumbBuffer(self: Card, width: u32, height: u32, bpp: u32) sys.IoctlError!DumbBuffer { + var create = std.mem.zeroInit(sys.mode.CreateDumb, .{ + .width = width, + .height = height, + .bpp = bpp, + }); + try sys.ioctl(self.handle.handle, .mode_create_dumb, &create); + + return DumbBuffer{ + .handle = create.handle, + .width = create.width, + .height = create.height, + .stride = create.pitch, + .size = @intCast(create.size), + }; + } + + pub fn destroyDumbBuffer(self: Card, handle: u32) sys.IoctlError!void { + var destroy = sys.mode.DestroyDumb{ .handle = handle }; + try sys.ioctl(self.handle.handle, .mode_destroy_dumb, &destroy); + } + + pub fn mapDumbBuffer(self: Card, dumb: DumbBuffer, offset: usize) sys.IoctlError!usize { + var map = sys.mode.MapDumb{ .handle = dumb.handle, .pad = 0, .offset = offset }; + try sys.ioctl(self.handle.handle, .mode_map_dumb, &map); + return @intCast(map.offset); + } + + pub fn addFb2( + self: Card, + width: u32, + height: u32, + format: fmt.Format, + handles: [4]u32, + pitches: [4]u32, + offsets: [4]u32, + flags: u32, // FIXME: add flags type + ) sys.IoctlError!u32 { + var cmd = std.mem.zeroInit(sys.mode.FbCmd2, .{ + .width = width, + .height = height, + .pixel_format = @intFromEnum(format), + .handles = handles, + .pitches = pitches, + .offsets = offsets, + .flags = flags, + }); + try sys.ioctl(self.handle.handle, .mode_addfb2, &cmd); + return cmd.fb_id; + } + + pub fn removeFb(self: Card, fb: u32) sys.IoctlError!void { + try sys.ioctl(self.handle.handle, .mode_rmfb, &fb); + } + + pub fn pageFlip( + self: Card, + crtc_id: u32, + fb_id: u32, + flags: sys.mode.PageFlipFlags, + data: ?*anyopaque, + ) sys.IoctlError!void { + var flip = sys.mode.CrtcPageFlip{ + .crtc_id = crtc_id, + .fb_id = fb_id, + .flags = flags, + .reserved = 0, + .user_data = @intFromPtr(data), + }; + try sys.ioctl(self.handle.handle, .mode_page_flip, &flip); + } }; pub const Connection = enum(u32) { @@ -300,9 +399,11 @@ pub const Connector = struct { mm_height: u32, subpixel: Subpixel, encoders: []const u32, - modes: []const sys.mode.Modeinfo, + modes: []const sys.mode.ModeInfo, props: []const u32, prop_values: []const u64, + type: sys.mode.ConnectorType, + type_id: u32, pub fn deinit(self: Connector, gpa: Allocator) void { gpa.free(self.prop_values); @@ -314,7 +415,7 @@ pub const Connector = struct { pub const Encoder = struct { id: u32, - type: sys.mode.GetEncoder.Type, + type: sys.mode.EncoderType, crtc_id: u32, possible_crtcs: u32, possible_clones: u32, @@ -326,5 +427,13 @@ pub const Crtc = struct { x: u32, y: u32, gamma_size: u32, - mode: ?sys.mode.Modeinfo, + mode: ?sys.mode.ModeInfo, +}; + +pub const DumbBuffer = struct { + handle: u32, + width: u32, + height: u32, + stride: u32, + size: usize, }; diff --git a/src/sys.zig b/src/sys.zig index e3da169..3edb016 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -83,8 +83,8 @@ pub const Request = enum(u32) { mode_getcrtc = drm_iowr(0xA1, mode.Crtc), mode_setcrtc = drm_iowr(0xA2, mode.Crtc), mode_cursor = drm_iowr(0xA3, mode.Cursor), - mode_getgamma = drm_iowr(0xA4, mode.Crtc.Lut), - mode_setgamma = drm_iowr(0xA5, mode.Crtc.Lut), + mode_getgamma = drm_iowr(0xA4, mode.CrtcLut), + mode_setgamma = drm_iowr(0xA5, mode.CrtcLut), mode_getencoder = drm_iowr(0xA6, mode.GetEncoder), mode_getconnector = drm_iowr(0xA7, mode.GetConnector), mode_getproperty = drm_iowr(0xAA, mode.GetProperty), @@ -183,31 +183,31 @@ pub const IrqBusid = extern struct { pub const Map = extern struct { offset: c_ulong, size: c_ulong, - type: Type, - flags: Flags, + type: MapType, + flags: MapFlags, handle: *anyopaque, mtrr: c_int, +}; - pub const Type = enum(c_int) { - frame_buffer = 0, - registers = 1, - shm = 2, - agp = 3, - scatter_gather = 4, - consistent = 5, - }; +pub const MapType = enum(c_int) { + frame_buffer = 0, + registers = 1, + shm = 2, + agp = 3, + scatter_gather = 4, + consistent = 5, +}; - pub const Flags = packed struct(c_int) { - restricted: bool = false, - read_only: bool = false, - locked: bool = false, - kernel: bool = false, - write_combining: bool = false, - contains_lock: bool = false, - removable: bool = false, - driver: bool = false, - _: @Int(.signed, @bitSizeOf(c_int) - 8) = 0, - }; +pub const MapFlags = packed struct(c_int) { + restricted: bool = false, + read_only: bool = false, + locked: bool = false, + kernel: bool = false, + write_combining: bool = false, + contains_lock: bool = false, + removable: bool = false, + driver: bool = false, + _: @Int(.signed, @bitSizeOf(c_int) - 8) = 0, }; pub const Client = extern struct { @@ -222,29 +222,29 @@ pub const Client = extern struct { pub const Stats = extern struct { count: c_ulong, data: [15]Stat, +}; - pub const Stat = extern struct { - value: c_ulong, - type: Type, +pub const Stat = extern struct { + value: c_ulong, + type: StatType, +}; - pub const Type = enum(c_int) { - lock, - opens, - closes, - ioctls, - locks, - unlocks, - value, - byte, - count, - irq, - primary, - secondary, - dma, - special, - missed, - }; - }; +pub const StatType = enum(c_int) { + lock, + opens, + closes, + ioctls, + locks, + unlocks, + value, + byte, + count, + irq, + primary, + secondary, + dma, + special, + missed, }; pub const SetVersion = extern struct { @@ -256,13 +256,13 @@ pub const SetVersion = extern struct { pub const Ctx = extern struct { handle: Context, - flags: Flags, + flags: CtxFlags, +}; - pub const Flags = packed struct(c_int) { - preserved: bool = false, - @"2d_only": bool = false, - _: @Int(.signed, @bitSizeOf(c_int) - 2) = 0, - }; +pub const CtxFlags = packed struct(c_int) { + preserved: bool = false, + @"2d_only": bool = false, + _: @Int(.signed, @bitSizeOf(c_int) - 2) = 0, }; pub const CtxRes = extern struct { @@ -270,6 +270,33 @@ pub const CtxRes = extern struct { contexts: [*]Ctx, }; +pub const Event = extern struct { + type: EventType, + length: u32, +}; + +pub const EventType = enum(u32) { + vblank = 0x01, + flip_complete = 0x02, + crtc_sequence = 0x03, +}; + +pub const EventVblank = extern struct { + base: Event, + user_data: u64, + tv_sec: u32, + tv_usec: u32, + sequence: u32, + crtc_id: u32, +}; + +pub const EventCrtcSequence = extern struct { + base: Event, + user_data: u64, + time_ns: i64, + sequence: u64, +}; + pub const mode = struct { pub const display_mode_len = 32; pub const prop_name_len = 32; @@ -298,18 +325,18 @@ pub const mode = struct { y: u32, gamma_size: u32, mode_valid: u32, - mode: Modeinfo, - - pub const Lut = extern struct { - crtc_id: u32, - gamma_size: u32, - red: u64, - green: u64, - blue: u64, - }; + mode: ModeInfo, }; - pub const Modeinfo = extern struct { + pub const CrtcLut = extern struct { + crtc_id: u32, + gamma_size: u32, + red: u64, + green: u64, + blue: u64, + }; + + pub const ModeInfo = extern struct { clock: u32, hdisplay: u16, hsync_start: u16, @@ -322,39 +349,39 @@ pub const mode = struct { vtotal: u16, vscan: u16, vrefresh: u32, - flags: Flags, - type: Type, + flags: ModeFlags, + type: ModeType, name: [display_mode_len]u8, + }; - pub const Flags = packed struct(u32) { - phsync: bool = false, - nhsync: bool = false, - pvsync: bool = false, - nvsync: bool = false, - interlace: bool = false, - dblscan: bool = false, - csync: bool = false, - pcsync: bool = false, - ncsync: bool = false, - hskew: bool = false, - _deprecated10: u1 = 0, - _deprecated11: u1 = 0, - dblclk: bool = false, - clkdiv2: bool = false, - _: u18 = 0, - }; + pub const ModeType = packed struct(u32) { + _deprecated0: u1 = 0, + _deprecated1: u1 = 0, + _deprecated2: u1 = 0, + preferred: bool = false, + _deprecated4: u1 = 0, + userdef: bool = false, + driver: bool = false, + _: u25 = 0, + }; - pub const Type = packed struct(u32) { - _deprecated0: u1 = 0, - _deprecated1: u1 = 0, - _deprecated2: u1 = 0, - preferred: bool = false, - _deprecated4: u1 = 0, - userdef: bool = false, - driver: bool = false, - _: u25 = 0, - }; + pub const ModeFlags = packed struct(u32) { + phsync: bool = false, + nhsync: bool = false, + pvsync: bool = false, + nvsync: bool = false, + interlace: bool = false, + dblscan: bool = false, + csync: bool = false, + pcsync: bool = false, + ncsync: bool = false, + hskew: bool = false, + _deprecated10: u1 = 0, + _deprecated11: u1 = 0, + dblclk: bool = false, + clkdiv2: bool = false, + _: u18 = 0, }; pub const Cursor = extern struct { @@ -369,22 +396,22 @@ pub const mode = struct { pub const GetEncoder = extern struct { encoder_id: u32, - encoder_type: Type, + encoder_type: EncoderType, crtc_id: u32, possible_crtcs: u32, possible_clones: u32, + }; - pub const Type = enum(u32) { - none = 0, - dac = 1, - tmds = 2, - lvds = 3, - tvdac = 4, - virtual = 5, - dsi = 6, - dpmst = 7, - dpi = 8, - }; + pub const EncoderType = enum(u32) { + none = 0, + dac = 1, + tmds = 2, + lvds = 3, + tvdac = 4, + virtual = 5, + dsi = 6, + dpmst = 7, + dpi = 8, }; pub const GetConnector = extern struct { @@ -397,7 +424,7 @@ pub const mode = struct { count_encoders: u32, encoder_id: u32, connector_id: u32, - connector_type: u32, + connector_type: ConnectorType, connector_type_id: u32, connection: u32, mm_width: u32, @@ -406,6 +433,30 @@ pub const mode = struct { pad: u32, }; + pub const ConnectorType = enum(u32) { + unknown = 0, + vga = 1, + dvi_i = 2, + dvi_d = 3, + dvi_a = 4, + composite = 5, + svideo = 6, + lvds = 7, + component = 8, + din = 9, + display_port = 10, + hdmi_a = 11, + hdmi_b = 12, + tv = 13, + e_dp = 14, + virtual = 15, + dsi = 16, + dpi = 17, + writeback = 18, + spi = 19, + usb = 20, + }; + pub const GetProperty = extern struct { values_ptr: u64, enum_blob_ptr: u64, @@ -441,11 +492,19 @@ pub const mode = struct { pub const CrtcPageFlip = extern struct { crtc_id: u32, fb_id: u32, - flags: u32, + flags: PageFlipFlags, reserved: u32, user_data: u64, }; + pub const PageFlipFlags = packed struct(u32) { + event: bool = false, + async: bool = false, + target_absolute: bool = false, + target_relative: bool = false, + _: u28 = 0, + }; + pub const FbDirtyCmd = extern struct { fb_id: u32, flags: u32, @@ -513,7 +572,7 @@ pub const mode = struct { handles: [4]u32, pitches: [4]u32, offsets: [4]u32, - modifier: [4]u64, + modifiers: [4]u64, }; pub const ObjGetProperties = extern struct {