diff --git a/example.zig b/example.zig index d0358a1..d22f5aa 100644 --- a/example.zig +++ b/example.zig @@ -5,34 +5,44 @@ pub fn main(init: std.process.Init) !void { const io = init.io; const gpa = init.gpa; - const card = try drm.Card.openAuto(io); + const card = try drm.mode.Card.openAuto(io); defer card.close(io); + const res = try card.getResourcesAlloc(gpa); + defer res.deinit(gpa); - std.log.info("Using drm.Card.getModeAlloc...", .{}); - const mode_res_1 = try card.getModeAlloc(gpa); - defer mode_res_1.deinit(gpa); + const connector = try chooseConnector(card, gpa, res.connectors); + defer connector.deinit(gpa); + std.log.info("Selected connector {}.", .{connector.id}); - std.log.info("Min dimensions: {d}x{d}.", .{ mode_res_1.min_width, mode_res_1.min_height }); - std.log.info("Max dimensions: {d}x{d}.", .{ mode_res_1.max_width, mode_res_1.max_height }); + const mode = chooseMode(connector.modes); + std.log.info("Selected mode {}x{}@{}.", .{ mode.hdisplay, mode.vdisplay, mode.vrefresh }); - std.log.info("Got {d} fbs: {any}.", .{ mode_res_1.fbs.len, mode_res_1.fbs }); - std.log.info("Got {d} crtcs: {any}.", .{ mode_res_1.crtcs.len, mode_res_1.crtcs }); - std.log.info("Got {d} connectors: {any}.", .{ mode_res_1.connectors.len, mode_res_1.connectors }); - std.log.info("Got {d} encoders: {any}.", .{ mode_res_1.encoders.len, mode_res_1.encoders }); + const encoder = try card.getEncoder(connector.encoder_id); + std.log.info("Selected encoder {} (type: {t}).", .{ encoder.id, encoder.type }); - var fb_buf: [16]u32 = undefined; - var crtc_buf: [16]u32 = undefined; - var connector_buf: [16]u32 = undefined; - var encoder_buf: [16]u32 = undefined; - - std.log.info("Using drm.Card.getModeBuffered...", .{}); - const mode_res_2 = try card.getModeBuffered(&fb_buf, &crtc_buf, &connector_buf, &encoder_buf); - - std.log.info("Min dimensions: {d}x{d}.", .{ mode_res_2.min_width, mode_res_2.min_height }); - std.log.info("Max dimensions: {d}x{d}.", .{ mode_res_2.max_width, mode_res_2.max_height }); - - std.log.info("Got {d} fbs: {any}.", .{ mode_res_2.fbs.len, mode_res_2.fbs }); - std.log.info("Got {d} crtcs: {any}.", .{ mode_res_2.crtcs.len, mode_res_2.crtcs }); - std.log.info("Got {d} connectors: {any}.", .{ mode_res_2.connectors.len, mode_res_2.connectors }); - std.log.info("Got {d} encoders: {any}.", .{ mode_res_2.encoders.len, mode_res_2.encoders }); + const crtc = try card.getCrtc(encoder.crtc_id); + std.log.info("Selected crtc {} (mode valid: {}).", .{ crtc.id, crtc.mode != null }); +} + +fn chooseConnector( + card: drm.mode.Card, + gpa: std.mem.Allocator, + connectors: []const u32, +) !drm.mode.Connector { + for (connectors) |id| { + const connector = try card.getConnectorAlloc(gpa, id); + if (connector.connection == .connected) return connector; + connector.deinit(gpa); + } + return error.NoConnectedConnectors; +} + +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| { + const score = @as(u64, mode.hdisplay) * @as(u64, mode.vdisplay) * @as(u64, mode.vrefresh); + if (score > best_score) best = mode; + } + return best; } diff --git a/src/drm.zig b/src/drm.zig index c2ad074..e996d9d 100644 --- a/src/drm.zig +++ b/src/drm.zig @@ -3,176 +3,43 @@ const format = @import("format.zig"); const log = std.log.scoped(.drm); pub const sys = @import("sys.zig"); +pub const mode = @import("mode.zig"); pub const Format = format.Format; pub const FormatModifiers = format.FormatModifiers; pub const Card = struct { handle: std.Io.File, - pub fn openAuto(io: std.Io) !Card { + pub const OpenError = std.Io.File.OpenError || std.Io.Dir.OpenError; + + 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 }); + + const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{}); + defer dir.close(io); + + return Card{ .handle = try dir.openFile(io, path, .{ .mode = .read_write }) }; + } + + pub const OpenAutoError = OpenError || std.Io.Dir.Iterator.Error || error{NoDevicesFound}; + + pub fn openAuto(io: std.Io) OpenAutoError!Card { const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{ .iterate = true }); defer dir.close(io); - var it = dir.iterate(); - const handle = try while (try it.next(io)) |entry| { - if (entry.kind == .character_device) { - log.debug("Found device /dev/dri/{s}.", .{entry.name}); - break dir.openFile(io, entry.name, .{}); - } - } else error.NoDevicesFound; + var it = dir.iterateAssumeFirstIteration(); + while (try it.next(io)) |entry| if (entry.kind == .character_device) + return Card{ .handle = try dir.openFile(io, entry.name, .{ .mode = .read_write }) }; - return Card{ .handle = handle }; - } - - pub fn open(io: std.Io, path: []const u8) !Card { - const handle = try if (std.Io.Dir.path.isAbsolute(path)) handle: { - log.debug("Using device path {s}.", .{path}); - break :handle std.Io.Dir.openFileAbsolute(io, path, .{}); - } else handle: { - const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{}); - defer dir.close(io); - - log.debug("Using device path /dev/dri/{s}.", .{path}); - break :handle dir.openDir(io, path, .{}); - }; - - return Card{ .handle = handle }; + return error.NoDevicesFound; } pub fn close(self: Card, io: std.Io) void { self.handle.close(io); } - pub const GetModeError = sys.IoctlError; - - pub fn getModeAlloc( - self: Card, - gpa: std.mem.Allocator, - ) (GetModeError || std.mem.Allocator.Error)!ModeCard { - return while (true) { - var res = std.mem.zeroes(sys.mode.CardRes); - try sys.ioctl(self.handle.handle, .mode_getresources, &res); - - var ret = ModeCard{ - .handle = self.handle, - .min_width = res.min_width, - .max_width = res.max_width, - .min_height = res.min_height, - .max_height = res.max_height, - .fbs = &.{}, - .crtcs = &.{}, - .connectors = &.{}, - .encoders = &.{}, - }; - - ret.fbs = try gpa.alloc(u32, res.count_fbs); - errdefer gpa.free(ret.fbs); - res.fb_id_ptr = @intFromPtr(ret.fbs.ptr); - - ret.crtcs = try gpa.alloc(u32, res.count_crtcs); - errdefer gpa.free(ret.crtcs); - res.crtc_id_ptr = @intFromPtr(ret.crtcs.ptr); - - ret.connectors = try gpa.alloc(u32, res.count_connectors); - errdefer gpa.free(ret.connectors); - res.connector_id_ptr = @intFromPtr(ret.connectors.ptr); - - ret.encoders = try gpa.alloc(u32, res.count_encoders); - errdefer gpa.free(ret.encoders); - res.encoder_id_ptr = @intFromPtr(ret.encoders.ptr); - - const cached = res; - try sys.ioctl(self.handle.handle, .mode_getresources, &res); - - if (cached.count_fbs < res.count_fbs or - cached.count_crtcs < res.count_crtcs or - cached.count_connectors < res.count_connectors or - cached.count_encoders < res.count_encoders) - { - gpa.free(ret.encoders); - gpa.free(ret.connectors); - gpa.free(ret.crtcs); - gpa.free(ret.fbs); - continue; - } - - break ret; - }; - } - - pub fn getModeBuffered( - self: Card, - fbs: []u32, - crtcs: []u32, - connectors: []u32, - encoders: []u32, - ) (GetModeError || error{ - TooManyFbs, - TooManyCrtcs, - TooManyConnectors, - TooManyEncoders, - })!ModeCard { - return while (true) { - var res = std.mem.zeroes(sys.mode.CardRes); - try sys.ioctl(self.handle.handle, .mode_getresources, &res); - - var ret = ModeCard{ - .handle = self.handle, - .min_width = res.min_width, - .max_width = res.max_width, - .min_height = res.min_height, - .max_height = res.max_height, - .fbs = &.{}, - .crtcs = &.{}, - .connectors = &.{}, - .encoders = &.{}, - }; - - if (res.count_fbs > fbs.len) return error.TooManyFbs; - ret.fbs = fbs[0..res.count_fbs]; - res.fb_id_ptr = @intFromPtr(ret.fbs.ptr); - - if (res.count_crtcs > crtcs.len) return error.TooManyCrtcs; - ret.crtcs = crtcs[0..res.count_crtcs]; - res.crtc_id_ptr = @intFromPtr(ret.crtcs.ptr); - - if (res.count_connectors > connectors.len) return error.TooManyConnectors; - ret.connectors = connectors[0..res.count_connectors]; - res.connector_id_ptr = @intFromPtr(ret.connectors.ptr); - - if (res.count_encoders > encoders.len) return error.TooManyEncoders; - ret.encoders = encoders[0..res.count_encoders]; - res.encoder_id_ptr = @intFromPtr(ret.encoders.ptr); - - const cached = res; - try sys.ioctl(self.handle.handle, .mode_getresources, &res); - - if (cached.count_fbs < res.count_fbs or - cached.count_crtcs < res.count_crtcs or - cached.count_connectors < res.count_connectors or - cached.count_encoders < res.count_encoders) - continue; - - break ret; - }; - } -}; - -pub const ModeCard = struct { - handle: std.Io.File, - min_width: u32, - max_width: u32, - min_height: u32, - max_height: u32, - fbs: []const u32, - crtcs: []const u32, - connectors: []const u32, - encoders: []const u32, - - pub fn deinit(self: ModeCard, gpa: std.mem.Allocator) void { - gpa.free(self.encoders); - gpa.free(self.connectors); - gpa.free(self.crtcs); - gpa.free(self.fbs); + pub inline fn modeHandle(self: Card) mode.Card { + return .{ .handle = self.handle }; } }; diff --git a/src/mode.zig b/src/mode.zig new file mode 100644 index 0000000..8ea6626 --- /dev/null +++ b/src/mode.zig @@ -0,0 +1,330 @@ +const std = @import("std"); +const sys = @import("sys.zig"); +const base = @import("drm.zig"); +const Allocator = std.mem.Allocator; + +pub const Card = struct { + handle: std.Io.File, + + pub const OpenError = std.Io.File.OpenError || std.Io.Dir.OpenError; + + 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 }); + + const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{}); + defer dir.close(io); + + return Card{ .handle = try dir.openFile(io, path, .{ .mode = .read_write }) }; + } + + pub const OpenAutoError = OpenError || std.Io.Dir.Iterator.Error || error{NoDevicesFound}; + + pub fn openAuto(io: std.Io) OpenAutoError!Card { + const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{ .iterate = true }); + defer dir.close(io); + + var it = dir.iterateAssumeFirstIteration(); + while (try it.next(io)) |entry| if (entry.kind == .character_device) + return Card{ .handle = try dir.openFile(io, entry.name, .{ .mode = .read_write }) }; + + return error.NoDevicesFound; + } + + pub fn close(self: Card, io: std.Io) void { + self.handle.close(io); + } + + pub inline fn baseHandle(self: Card) base.Card { + return .{ .handle = self.handle }; + } + + pub const GetResourcesError = sys.IoctlError || error{OutOfMemory}; + + pub fn getResourcesAlloc(self: Card, gpa: Allocator) GetResourcesError!Resources { + while (true) { + var res = std.mem.zeroes(sys.mode.CardRes); + try sys.ioctl(self.handle.handle, .mode_getresources, &res); + + var ret = Resources{ + .min_width = res.min_width, + .max_width = res.max_width, + .min_height = res.min_height, + .max_height = res.max_height, + .fbs = &.{}, + .crtcs = &.{}, + .connectors = &.{}, + .encoders = &.{}, + }; + + ret.fbs = try gpa.alloc(u32, res.count_fbs); + errdefer gpa.free(ret.fbs); + res.fb_id_ptr = @intFromPtr(ret.fbs.ptr); + + ret.crtcs = try gpa.alloc(u32, res.count_crtcs); + errdefer gpa.free(ret.crtcs); + res.crtc_id_ptr = @intFromPtr(ret.crtcs.ptr); + + ret.connectors = try gpa.alloc(u32, res.count_connectors); + errdefer gpa.free(ret.connectors); + res.connector_id_ptr = @intFromPtr(ret.connectors.ptr); + + ret.encoders = try gpa.alloc(u32, res.count_encoders); + errdefer gpa.free(ret.encoders); + res.encoder_id_ptr = @intFromPtr(ret.encoders.ptr); + + const cached = res; + try sys.ioctl(self.handle.handle, .mode_getresources, &res); + + if (cached.count_fbs < res.count_fbs or + cached.count_crtcs < res.count_crtcs or + cached.count_connectors < res.count_connectors or + cached.count_encoders < res.count_encoders) + { + gpa.free(ret.encoders); + gpa.free(ret.connectors); + gpa.free(ret.crtcs); + gpa.free(ret.fbs); + continue; + } + + return ret; + } + } + + pub fn getResourcesBuffered( + self: Card, + fbs: []u32, + crtcs: []u32, + connectors: []u32, + encoders: []u32, + ) GetResourcesError!Resources { + var res = sys.mode.CardRes{ + .min_width = 0, + .max_width = 0, + .min_height = 0, + .max_height = 0, + .count_fbs = @intCast(fbs.len), + .fb_id_ptr = @intFromPtr(fbs.ptr), + .count_crtcs = @intCast(crtcs.len), + .crtc_id_ptr = @intFromPtr(crtcs.ptr), + .count_connectors = @intCast(connectors.len), + .connector_id_ptr = @intFromPtr(connectors.ptr), + .count_encoders = @intCast(encoders.len), + .encoder_id_ptr = @intFromPtr(encoders.ptr), + }; + try sys.ioctl(self.handle.handle, .mode_getresources, &res); + + if (res.count_fbs > fbs.len or + res.count_crtcs > crtcs.len or + res.count_connectors > connectors.len or + res.count_encoders > encoders.len) + return error.OutOfMemory; + + return Resources{ + .min_width = res.min_width, + .max_width = res.max_width, + .min_height = res.min_height, + .max_height = res.max_height, + .fbs = fbs[0..res.count_fbs], + .crtcs = crtcs[0..res.count_crtcs], + .connectors = connectors[0..res.count_connectors], + .encoders = encoders[0..res.count_encoders], + }; + } + + pub const GetConnectorError = sys.IoctlError || error{OutOfMemory}; + + pub fn getConnectorAlloc(self: Card, gpa: Allocator, id: u32) GetConnectorError!Connector { + while (true) { + var get_conn = std.mem.zeroInit(sys.mode.GetConnector, .{ .connector_id = id }); + try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn); + const cached = get_conn; + + 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); + errdefer gpa.free(modes); + + const props = try gpa.alloc(u32, get_conn.count_props); + errdefer gpa.free(props); + const prop_values = try gpa.alloc(u64, get_conn.count_props); + errdefer gpa.free(prop_values); + + get_conn.encoders_ptr = @intFromPtr(encoders.ptr); + get_conn.modes_ptr = @intFromPtr(modes.ptr); + get_conn.props_ptr = @intFromPtr(props.ptr); + get_conn.prop_values_ptr = @intFromPtr(prop_values.ptr); + + try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn); + + if (cached.count_encoders < get_conn.count_encoders or + cached.count_modes < get_conn.count_modes or + cached.count_props < get_conn.count_props) + { + gpa.free(prop_values); + gpa.free(props); + gpa.free(modes); + gpa.free(encoders); + continue; + } + + return Connector{ + .id = id, + .encoder_id = get_conn.encoder_id, + .connection = @enumFromInt(get_conn.connection), + // Unsure why libdrm does this conversion, + // but we'll follow suite for compatability. + .subpixel = @enumFromInt(get_conn.subpixel + 1), + .encoders = encoders[0..get_conn.count_encoders], + .modes = modes[0..get_conn.count_modes], + .props = props[0..get_conn.count_props], + .prop_values = prop_values[0..get_conn.count_props], + .mm_width = get_conn.mm_width, + .mm_height = get_conn.mm_height, + }; + } + } + + pub inline fn getConnectorBuffered( + self: Card, + encoders: []u32, + modes: []sys.mode.Modeinfo, + props: []u32, + prop_values: []u64, + id: u32, + ) GetConnectorError!Connector { + std.debug.assert(props.len == prop_values.len); + var get_conn = std.mem.zeroInit(sys.mode.GetConnector, .{ + .connector_id = id, + .count_encoders = @as(u32, @intCast(encoders.len)), + .encoders_ptr = @intFromPtr(encoders.ptr), + .count_modes = @as(u32, @intCast(modes.len)), + .modes_ptr = @intFromPtr(modes.ptr), + .count_props = @as(u32, @intCast(props.len)), + .props_ptr = @intFromPtr(props.ptr), + .prop_values_ptr = @intFromPtr(prop_values.ptr), + }); + try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn); + + if (get_conn.count_encoders > encoders.len or + get_conn.count_modes > modes.len or + get_conn.count_props > props.len) + return error.OutOfMemory; + + return Connector{ + .id = id, + .encoder_id = get_conn.encoder_id, + .connection = @enumFromInt(get_conn.connection), + .subpixel = @enumFromInt(get_conn.subpixel + 1), + .encoders = encoders[0..get_conn.count_encoders], + .modes = modes[0..get_conn.count_modes], + .props = props[0..get_conn.count_props], + .prop_values = prop_values[0..get_conn.count_props], + .mm_width = get_conn.mm_width, + .mm_height = get_conn.mm_height, + }; + } + + pub const GetEncoderError = sys.IoctlError; + + pub fn getEncoder(self: Card, id: u32) GetEncoderError!Encoder { + var get_encoder = std.mem.zeroInit(sys.mode.GetEncoder, .{ .encoder_id = id }); + try sys.ioctl(self.handle.handle, .mode_getencoder, &get_encoder); + + return Encoder{ + .id = id, + .type = get_encoder.encoder_type, + .crtc_id = get_encoder.crtc_id, + .possible_crtcs = get_encoder.possible_crtcs, + .possible_clones = get_encoder.possible_clones, + }; + } + + pub const GetCrtcError = sys.IoctlError; + + pub fn getCrtc(self: Card, id: u32) GetCrtcError!Crtc { + var crtc = std.mem.zeroInit(sys.mode.Crtc, .{ .crtc_id = id }); + try sys.ioctl(self.handle.handle, .mode_getcrtc, &crtc); + + return Crtc{ + .id = id, + .fb_id = crtc.fb_id, + .x = crtc.x, + .y = crtc.y, + .gamma_size = crtc.gamma_size, + .mode = if (crtc.mode_valid != 0) crtc.mode else null, + }; + } +}; + +pub const Connection = enum(u32) { + connected = 1, + disconnected = 2, + unknown = 3, +}; + +pub const Subpixel = enum(u32) { + unknown = 1, + horizontal_rgb = 2, + horizontal_bgr = 3, + vertical_rgb = 4, + vertical_bgr = 5, + none = 6, +}; + +pub const Resources = struct { + min_width: u32, + max_width: u32, + min_height: u32, + max_height: u32, + fbs: []const u32, + crtcs: []const u32, + connectors: []const u32, + encoders: []const u32, + + pub fn deinit(self: Resources, gpa: Allocator) void { + gpa.free(self.encoders); + gpa.free(self.connectors); + gpa.free(self.crtcs); + gpa.free(self.fbs); + } +}; + +pub const Connector = struct { + id: u32, + encoder_id: u32, + connection: Connection, + mm_width: u32, + mm_height: u32, + subpixel: Subpixel, + encoders: []const u32, + modes: []const sys.mode.Modeinfo, + props: []const u32, + prop_values: []const u64, + + pub fn deinit(self: Connector, gpa: Allocator) void { + gpa.free(self.prop_values); + gpa.free(self.props); + gpa.free(self.modes); + gpa.free(self.encoders); + } +}; + +pub const Encoder = struct { + id: u32, + type: sys.mode.GetEncoder.Type, + crtc_id: u32, + possible_crtcs: u32, + possible_clones: u32, +}; + +pub const Crtc = struct { + id: u32, + fb_id: u32, + x: u32, + y: u32, + gamma_size: u32, + mode: ?sys.mode.Modeinfo, +}; diff --git a/src/sys.zig b/src/sys.zig index d0554d2..e3da169 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -322,10 +322,39 @@ pub const mode = struct { vtotal: u16, vscan: u16, vrefresh: u32, - flags: u32, - type: u32, + flags: Flags, + type: Type, 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 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 Cursor = extern struct { @@ -340,10 +369,22 @@ pub const mode = struct { pub const GetEncoder = extern struct { encoder_id: u32, - encoder_type: u32, + encoder_type: Type, 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 GetConnector = extern struct {