vsync'd modesetting!

This commit is contained in:
Jackson Netherwood-Imig
2026-01-26 10:30:40 -08:00
parent 13b38e4967
commit f40252c485
5 changed files with 362 additions and 116 deletions
+5
View File
@@ -20,4 +20,9 @@ pub fn build(b: *std.Build) void {
}); });
example.root_module.addImport("drm", drm); example.root_module.addImport("drm", drm);
b.installArtifact(example); 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);
} }
+1 -1
View File
@@ -37,7 +37,7 @@ fn chooseConnector(
return error.NoConnectedConnectors; 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]; var best = modes[0];
const best_score = @as(u64, best.hdisplay) * @as(u64, best.vdisplay) * @as(u64, best.vrefresh); const best_score = @as(u64, best.hdisplay) * @as(u64, best.vdisplay) * @as(u64, best.vrefresh);
for (modes) |mode| { for (modes) |mode| {
+74 -1
View File
@@ -14,7 +14,7 @@ pub const Card = struct {
pub fn open(io: std.Io, path: []const u8) OpenError!Card { pub fn open(io: std.Io, path: []const u8) OpenError!Card {
if (std.fs.path.isAbsolute(path)) 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", .{}); const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{});
defer dir.close(io); defer dir.close(io);
@@ -42,4 +42,77 @@ pub const Card = struct {
pub inline fn modeHandle(self: Card) mode.Card { pub inline fn modeHandle(self: Card) mode.Card {
return .{ .handle = self.handle }; 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());
}
+119 -10
View File
@@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const sys = @import("sys.zig"); const sys = @import("sys.zig");
const base = @import("drm.zig"); const base = @import("drm.zig");
const fmt = @import("format.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
pub const Card = struct { pub const Card = struct {
@@ -10,7 +11,7 @@ pub const Card = struct {
pub fn open(io: std.Io, path: []const u8) OpenError!Card { pub fn open(io: std.Io, path: []const u8) OpenError!Card {
if (std.fs.path.isAbsolute(path)) 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", .{}); const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{});
defer dir.close(io); defer dir.close(io);
@@ -144,7 +145,7 @@ pub const Card = struct {
const encoders = try gpa.alloc(u32, get_conn.count_encoders); const encoders = try gpa.alloc(u32, get_conn.count_encoders);
errdefer gpa.free(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); errdefer gpa.free(modes);
const props = try gpa.alloc(u32, get_conn.count_props); 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], .prop_values = prop_values[0..get_conn.count_props],
.mm_width = get_conn.mm_width, .mm_width = get_conn.mm_width,
.mm_height = get_conn.mm_height, .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, self: Card,
encoders: []u32, encoders: []u32,
modes: []sys.mode.Modeinfo, modes: []sys.mode.ModeInfo,
props: []u32, props: []u32,
prop_values: []u64, prop_values: []u64,
id: u32, id: u32,
@@ -224,12 +227,12 @@ pub const Card = struct {
.prop_values = prop_values[0..get_conn.count_props], .prop_values = prop_values[0..get_conn.count_props],
.mm_width = get_conn.mm_width, .mm_width = get_conn.mm_width,
.mm_height = get_conn.mm_height, .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) sys.IoctlError!Encoder {
pub fn getEncoder(self: Card, id: u32) GetEncoderError!Encoder {
var get_encoder = std.mem.zeroInit(sys.mode.GetEncoder, .{ .encoder_id = id }); var get_encoder = std.mem.zeroInit(sys.mode.GetEncoder, .{ .encoder_id = id });
try sys.ioctl(self.handle.handle, .mode_getencoder, &get_encoder); 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, .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) { pub const Connection = enum(u32) {
@@ -300,9 +399,11 @@ pub const Connector = struct {
mm_height: u32, mm_height: u32,
subpixel: Subpixel, subpixel: Subpixel,
encoders: []const u32, encoders: []const u32,
modes: []const sys.mode.Modeinfo, modes: []const sys.mode.ModeInfo,
props: []const u32, props: []const u32,
prop_values: []const u64, prop_values: []const u64,
type: sys.mode.ConnectorType,
type_id: u32,
pub fn deinit(self: Connector, gpa: Allocator) void { pub fn deinit(self: Connector, gpa: Allocator) void {
gpa.free(self.prop_values); gpa.free(self.prop_values);
@@ -314,7 +415,7 @@ pub const Connector = struct {
pub const Encoder = struct { pub const Encoder = struct {
id: u32, id: u32,
type: sys.mode.GetEncoder.Type, type: sys.mode.EncoderType,
crtc_id: u32, crtc_id: u32,
possible_crtcs: u32, possible_crtcs: u32,
possible_clones: u32, possible_clones: u32,
@@ -326,5 +427,13 @@ pub const Crtc = struct {
x: u32, x: u32,
y: u32, y: u32,
gamma_size: 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,
}; };
+163 -104
View File
@@ -83,8 +83,8 @@ pub const Request = enum(u32) {
mode_getcrtc = drm_iowr(0xA1, mode.Crtc), mode_getcrtc = drm_iowr(0xA1, mode.Crtc),
mode_setcrtc = drm_iowr(0xA2, mode.Crtc), mode_setcrtc = drm_iowr(0xA2, mode.Crtc),
mode_cursor = drm_iowr(0xA3, mode.Cursor), mode_cursor = drm_iowr(0xA3, mode.Cursor),
mode_getgamma = drm_iowr(0xA4, mode.Crtc.Lut), mode_getgamma = drm_iowr(0xA4, mode.CrtcLut),
mode_setgamma = drm_iowr(0xA5, mode.Crtc.Lut), mode_setgamma = drm_iowr(0xA5, mode.CrtcLut),
mode_getencoder = drm_iowr(0xA6, mode.GetEncoder), mode_getencoder = drm_iowr(0xA6, mode.GetEncoder),
mode_getconnector = drm_iowr(0xA7, mode.GetConnector), mode_getconnector = drm_iowr(0xA7, mode.GetConnector),
mode_getproperty = drm_iowr(0xAA, mode.GetProperty), mode_getproperty = drm_iowr(0xAA, mode.GetProperty),
@@ -183,31 +183,31 @@ pub const IrqBusid = extern struct {
pub const Map = extern struct { pub const Map = extern struct {
offset: c_ulong, offset: c_ulong,
size: c_ulong, size: c_ulong,
type: Type, type: MapType,
flags: Flags, flags: MapFlags,
handle: *anyopaque, handle: *anyopaque,
mtrr: c_int, mtrr: c_int,
};
pub const Type = enum(c_int) { pub const MapType = enum(c_int) {
frame_buffer = 0, frame_buffer = 0,
registers = 1, registers = 1,
shm = 2, shm = 2,
agp = 3, agp = 3,
scatter_gather = 4, scatter_gather = 4,
consistent = 5, consistent = 5,
}; };
pub const Flags = packed struct(c_int) { pub const MapFlags = packed struct(c_int) {
restricted: bool = false, restricted: bool = false,
read_only: bool = false, read_only: bool = false,
locked: bool = false, locked: bool = false,
kernel: bool = false, kernel: bool = false,
write_combining: bool = false, write_combining: bool = false,
contains_lock: bool = false, contains_lock: bool = false,
removable: bool = false, removable: bool = false,
driver: bool = false, driver: bool = false,
_: @Int(.signed, @bitSizeOf(c_int) - 8) = 0, _: @Int(.signed, @bitSizeOf(c_int) - 8) = 0,
};
}; };
pub const Client = extern struct { pub const Client = extern struct {
@@ -222,29 +222,29 @@ pub const Client = extern struct {
pub const Stats = extern struct { pub const Stats = extern struct {
count: c_ulong, count: c_ulong,
data: [15]Stat, data: [15]Stat,
};
pub const Stat = extern struct { pub const Stat = extern struct {
value: c_ulong, value: c_ulong,
type: Type, type: StatType,
};
pub const Type = enum(c_int) { pub const StatType = enum(c_int) {
lock, lock,
opens, opens,
closes, closes,
ioctls, ioctls,
locks, locks,
unlocks, unlocks,
value, value,
byte, byte,
count, count,
irq, irq,
primary, primary,
secondary, secondary,
dma, dma,
special, special,
missed, missed,
};
};
}; };
pub const SetVersion = extern struct { pub const SetVersion = extern struct {
@@ -256,13 +256,13 @@ pub const SetVersion = extern struct {
pub const Ctx = extern struct { pub const Ctx = extern struct {
handle: Context, handle: Context,
flags: Flags, flags: CtxFlags,
};
pub const Flags = packed struct(c_int) { pub const CtxFlags = packed struct(c_int) {
preserved: bool = false, preserved: bool = false,
@"2d_only": bool = false, @"2d_only": bool = false,
_: @Int(.signed, @bitSizeOf(c_int) - 2) = 0, _: @Int(.signed, @bitSizeOf(c_int) - 2) = 0,
};
}; };
pub const CtxRes = extern struct { pub const CtxRes = extern struct {
@@ -270,6 +270,33 @@ pub const CtxRes = extern struct {
contexts: [*]Ctx, 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 mode = struct {
pub const display_mode_len = 32; pub const display_mode_len = 32;
pub const prop_name_len = 32; pub const prop_name_len = 32;
@@ -298,18 +325,18 @@ pub const mode = struct {
y: u32, y: u32,
gamma_size: u32, gamma_size: u32,
mode_valid: u32, mode_valid: u32,
mode: Modeinfo, mode: ModeInfo,
pub const Lut = extern struct {
crtc_id: u32,
gamma_size: u32,
red: u64,
green: u64,
blue: u64,
};
}; };
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, clock: u32,
hdisplay: u16, hdisplay: u16,
hsync_start: u16, hsync_start: u16,
@@ -322,39 +349,39 @@ pub const mode = struct {
vtotal: u16, vtotal: u16,
vscan: u16, vscan: u16,
vrefresh: u32, vrefresh: u32,
flags: Flags, flags: ModeFlags,
type: Type, type: ModeType,
name: [display_mode_len]u8, name: [display_mode_len]u8,
};
pub const Flags = packed struct(u32) { pub const ModeType = packed struct(u32) {
phsync: bool = false, _deprecated0: u1 = 0,
nhsync: bool = false, _deprecated1: u1 = 0,
pvsync: bool = false, _deprecated2: u1 = 0,
nvsync: bool = false, preferred: bool = false,
interlace: bool = false, _deprecated4: u1 = 0,
dblscan: bool = false, userdef: bool = false,
csync: bool = false, driver: bool = false,
pcsync: bool = false, _: u25 = 0,
ncsync: bool = false, };
hskew: bool = false,
_deprecated10: u1 = 0,
_deprecated11: u1 = 0,
dblclk: bool = false,
clkdiv2: bool = false,
_: u18 = 0,
};
pub const Type = packed struct(u32) { pub const ModeFlags = packed struct(u32) {
_deprecated0: u1 = 0, phsync: bool = false,
_deprecated1: u1 = 0, nhsync: bool = false,
_deprecated2: u1 = 0, pvsync: bool = false,
preferred: bool = false, nvsync: bool = false,
_deprecated4: u1 = 0, interlace: bool = false,
userdef: bool = false, dblscan: bool = false,
driver: bool = false, csync: bool = false,
_: u25 = 0, 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 { pub const Cursor = extern struct {
@@ -369,22 +396,22 @@ pub const mode = struct {
pub const GetEncoder = extern struct { pub const GetEncoder = extern struct {
encoder_id: u32, encoder_id: u32,
encoder_type: Type, encoder_type: EncoderType,
crtc_id: u32, crtc_id: u32,
possible_crtcs: u32, possible_crtcs: u32,
possible_clones: u32, possible_clones: u32,
};
pub const Type = enum(u32) { pub const EncoderType = enum(u32) {
none = 0, none = 0,
dac = 1, dac = 1,
tmds = 2, tmds = 2,
lvds = 3, lvds = 3,
tvdac = 4, tvdac = 4,
virtual = 5, virtual = 5,
dsi = 6, dsi = 6,
dpmst = 7, dpmst = 7,
dpi = 8, dpi = 8,
};
}; };
pub const GetConnector = extern struct { pub const GetConnector = extern struct {
@@ -397,7 +424,7 @@ pub const mode = struct {
count_encoders: u32, count_encoders: u32,
encoder_id: u32, encoder_id: u32,
connector_id: u32, connector_id: u32,
connector_type: u32, connector_type: ConnectorType,
connector_type_id: u32, connector_type_id: u32,
connection: u32, connection: u32,
mm_width: u32, mm_width: u32,
@@ -406,6 +433,30 @@ pub const mode = struct {
pad: u32, 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 { pub const GetProperty = extern struct {
values_ptr: u64, values_ptr: u64,
enum_blob_ptr: u64, enum_blob_ptr: u64,
@@ -441,11 +492,19 @@ pub const mode = struct {
pub const CrtcPageFlip = extern struct { pub const CrtcPageFlip = extern struct {
crtc_id: u32, crtc_id: u32,
fb_id: u32, fb_id: u32,
flags: u32, flags: PageFlipFlags,
reserved: u32, reserved: u32,
user_data: u64, 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 { pub const FbDirtyCmd = extern struct {
fb_id: u32, fb_id: u32,
flags: u32, flags: u32,
@@ -513,7 +572,7 @@ pub const mode = struct {
handles: [4]u32, handles: [4]u32,
pitches: [4]u32, pitches: [4]u32,
offsets: [4]u32, offsets: [4]u32,
modifier: [4]u64, modifiers: [4]u64,
}; };
pub const ObjGetProperties = extern struct { pub const ObjGetProperties = extern struct {