lib/windowing/src/wayland/present/dmabuf.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 //! Buffers another device renders, handed to the compositor as dma-bufs with explicit
  2 //! synchronization. The compositor describes which formats and modifiers it can import, and from
  3 //! which device, through dma-buf feedback (`zwp_linux_dmabuf_feedback_v1`). The caller imports each
  4 //! of its images once as a `wl_buffer` and each image's DRM timeline syncobj once as a
  5 //! `wp_linux_drm_syncobj_timeline_v1`. Every commit then names an acquire point the compositor
  6 //! waits for before reading the buffer and a release point it signals once it has stopped.
  7 //!
  8 //! The package keeps no GPU state. The caller owns the dma-bufs and timelines. Each descriptor it
  9 //! passes is duplicated for the compositor, and the caller keeps its own.
 10 
 11 const builtin = @import("builtin");
 12 const std = @import("std");
 13 const sys = @import("sys");
 14 const native = @import("wayland");
 15 const windowing = @import("../../root.zig");
 16 
 17 const core = native.protocol.core;
 18 const desktop = native.protocol.desktop;
 19 const runtime = native.runtime;
 20 
 21 const linux_dmabuf = desktop.zwp_linux_dmabuf_v1;
 22 const buffer_params = desktop.zwp_linux_buffer_params_v1;
 23 const feedback_protocol = desktop.zwp_linux_dmabuf_feedback_v1;
 24 const syncobj_manager = desktop.wp_linux_drm_syncobj_manager_v1;
 25 const syncobj_surface = desktop.wp_linux_drm_syncobj_surface_v1;
 26 const syncobj_timeline = desktop.wp_linux_drm_syncobj_timeline_v1;
 27 
 28 /// The lowest `zwp_linux_dmabuf_v1` version with feedback, and the highest this module speaks.
 29 pub const minimum_dmabuf_version: u32 = 4;
 30 pub const maximum_dmabuf_version: u32 = 5;
 31 
 32 /// Memory planes one buffer may have.
 33 pub const max_planes: u32 = 4;
 34 /// Buffers imported at once, enough for two rings of four.
 35 pub const max_buffers: u32 = 8;
 36 /// Timelines imported at once, one per buffer.
 37 pub const max_timelines: u32 = max_buffers;
 38 /// Tranches one feedback update may carry.
 39 pub const max_tranches: u32 = 8;
 40 /// Entries of the format table kept. The table's wire form spends 16 bytes on each.
 41 pub const max_table_entries: u32 = 4096;
 42 /// Table indices kept across all tranches of one update.
 43 pub const max_tranche_indices: u32 = 4096;
 44 
 45 pub const Error = error{
 46     /// The compositor lacks dma-buf feedback or explicit synchronization.
 47     Unsupported,
 48     /// Every buffer or timeline slot is in use.
 49     CapacityExceeded,
 50     /// A plane count, extent or descriptor the request cannot carry.
 51     InvalidBuffer,
 52     Unexpected,
 53 };
 54 
 55 /// One format table entry: a DRM fourcc and a modifier.
 56 pub const FormatModifier = extern struct {
 57     format: u32,
 58     pad: u32 = 0,
 59     modifier: u64,
 60 };
 61 
 62 /// A preference group of the compositor's feedback, highest preference first.
 63 pub const Tranche = struct {
 64     /// The device, as a Linux `dev_t`, whose buffers the tranche describes.
 65     device: u64,
 66     /// `scanout` (1) when the compositor can put buffers of this tranche straight on screen.
 67     flags: u32,
 68     first_index: u32,
 69     index_count: u32,
 70 };
 71 
 72 /// The compositor's latest complete dma-buf feedback for the surface.
 73 pub const Feedback = struct {
 74     main_device: u64 = 0,
 75     table: []FormatModifier = &.{},
 76     table_len: u32 = 0,
 77     indices: []u16 = &.{},
 78     index_count: u32 = 0,
 79     tranches: [max_tranches]Tranche = undefined,
 80     tranche_count: u32 = 0,
 81     /// Updates received whole, so a caller can see when the compositor changed its mind.
 82     generation: u64 = 0,
 83     /// Whether a table entry, index or tranche was dropped because it exceeded the capacity.
 84     truncated: bool = false,
 85 
 86     pub fn trancheSlice(self: *const Feedback) []const Tranche {
 87         return self.tranches[0..self.tranche_count];
 88     }
 89 
 90     pub fn entries(self: *const Feedback, tranche: Tranche) []const u16 {
 91         return self.indices[tranche.first_index..][0..tranche.index_count];
 92     }
 93 
 94     pub fn entry(self: *const Feedback, index: u16) ?FormatModifier {
 95         if (index >= self.table_len) return null;
 96         return self.table[index];
 97     }
 98 
 99     /// The modifiers of `format` in every tranche for `device`, in preference order and without
100     /// repeats. Returns how many it wrote to `out`.
101     pub fn modifiers(self: *const Feedback, format: u32, device: u64, out: []u64) usize {
102         var count: usize = 0;
103         for (self.trancheSlice()) |tranche| {
104             if (tranche.device != device) continue;
105             for (self.entries(tranche)) |index| {
106                 const value = self.entry(index) orelse continue;
107                 if (value.format != format) continue;
108                 if (std.mem.indexOfScalar(u64, out[0..count], value.modifier) != null) continue;
109                 if (count == out.len) return count;
110                 out[count] = value.modifier;
111                 count += 1;
112             }
113         }
114         return count;
115     }
116 };
117 
118 pub const Plane = struct {
119     /// Borrowed. The request carries a duplicate.
120     fd: sys.fd.Descriptor,
121     offset: u32,
122     stride: u32,
123 };
124 
125 pub const BufferDesc = struct {
126     width: u32,
127     height: u32,
128     /// DRM fourcc.
129     format: u32,
130     modifier: u64,
131     planes: []const Plane,
132 };
133 
134 /// An imported dma-buf, named by its `wl_buffer` object.
135 pub const Buffer = struct { id: u32 };
136 
137 /// An imported DRM timeline syncobj.
138 pub const Timeline = struct { id: u32 };
139 
140 /// The points one commit names on a timeline.
141 pub const SyncPoints = struct {
142     timeline: Timeline,
143     acquire: u64,
144     release: u64,
145 };
146 
147 /// One dma-buf frame: the buffer, its extent, the changed rectangle and its sync points.
148 pub const Frame = struct {
149     buffer: Buffer,
150     width: u32,
151     height: u32,
152     region: windowing.PresentRegion,
153     sync: SyncPoints,
154 };
155 
156 pub const PresentError = Error || error{
157     /// The compositor has not asked for a frame since the last one.
158     FrameNotReady,
159     /// The window is hidden, so the compositor would not show the frame.
160     NotVisible,
161     /// The frame's extent is not the window's framebuffer extent.
162     UnsupportedFormat,
163 };
164 
165 /// Feedback being received, which replaces `Feedback` when its `done` arrives.
166 const PendingFeedback = struct {
167     open: bool = false,
168     main_device: u64 = 0,
169     table_len: ?u32 = null,
170     index_count: u32 = 0,
171     tranche_count: u32 = 0,
172     tranche: Tranche = .{ .device = 0, .flags = 0, .first_index = 0, .index_count = 0 },
173 };
174 
175 pub const Dmabuf = struct {
176     allocator: std.mem.Allocator,
177     client: *runtime.Client,
178     dmabuf_id: u32,
179     manager_id: u32,
180     surface_id: u32,
181     feedback_id: ?u32 = null,
182     /// Made at the first explicit commit. Afterwards every commit of the surface needs sync points.
183     sync_surface_id: ?u32 = null,
184     feedback: Feedback = .{},
185     pending: PendingFeedback = .{},
186     pending_tranches: [max_tranches]Tranche = undefined,
187     pending_table: []FormatModifier = &.{},
188     pending_indices: []u16 = &.{},
189     buffers: [max_buffers]?u32 = @splat(null),
190     timelines: [max_timelines]?u32 = @splat(null),
191 
192     /// Asks the compositor for the surface's feedback, which arrives with the next roundtrip.
193     pub fn open(
194         allocator: std.mem.Allocator,
195         client: *runtime.Client,
196         dmabuf_id: u32,
197         manager_id: u32,
198         surface_id: u32,
199     ) (Error || error{OutOfMemory})!Dmabuf {
200         const dmabuf_object = client.object(dmabuf_id) orelse return error.Unsupported;
201         if (dmabuf_object.version < minimum_dmabuf_version) return error.Unsupported;
202         var self = Dmabuf{
203             .allocator = allocator,
204             .client = client,
205             .dmabuf_id = dmabuf_id,
206             .manager_id = manager_id,
207             .surface_id = surface_id,
208         };
209         self.feedback.table = try allocator.alloc(FormatModifier, max_table_entries);
210         errdefer allocator.free(self.feedback.table);
211         self.feedback.indices = try allocator.alloc(u16, max_tranche_indices);
212         errdefer allocator.free(self.feedback.indices);
213         self.pending_table = try allocator.alloc(FormatModifier, max_table_entries);
214         errdefer allocator.free(self.pending_table);
215         self.pending_indices = try allocator.alloc(u16, max_tranche_indices);
216         errdefer allocator.free(self.pending_indices);
217         var created: [1]u32 = undefined;
218         client.request(
219             dmabuf_id,
220             linux_dmabuf.requests.get_surface_feedback.opcode,
221             &.{ .{ .new_id = .fixed }, .{ .object = surface_id } },
222             &created,
223         ) catch return error.Unexpected;
224         self.feedback_id = created[0];
225         return self;
226     }
227 
228     pub fn deinit(self: *Dmabuf) void {
229         for (&self.buffers) |*slot| if (slot.*) |id| {
230             self.destroyObject(id, core.wl_buffer.requests.destroy.opcode);
231             slot.* = null;
232         };
233         for (&self.timelines) |*slot| if (slot.*) |id| {
234             self.destroyObject(id, syncobj_timeline.requests.destroy.opcode);
235             slot.* = null;
236         };
237         if (self.sync_surface_id) |id| self.destroyObject(id, syncobj_surface.requests.destroy.opcode);
238         if (self.feedback_id) |id| self.destroyObject(id, feedback_protocol.requests.destroy.opcode);
239         self.allocator.free(self.feedback.table);
240         self.allocator.free(self.feedback.indices);
241         self.allocator.free(self.pending_table);
242         self.allocator.free(self.pending_indices);
243         self.* = undefined;
244     }
245 
246     /// The `zwp_linux_dmabuf_v1` version bound, 4 or later.
247     pub fn version(self: *const Dmabuf) u32 {
248         const object = self.client.object(self.dmabuf_id) orelse return 0;
249         return object.version;
250     }
251 
252     /// The latest complete feedback, or null before the first one arrives.
253     pub fn currentFeedback(self: *const Dmabuf) ?*const Feedback {
254         if (self.feedback.generation == 0) return null;
255         return &self.feedback;
256     }
257 
258     /// Imports a dma-buf as a `wl_buffer`. The compositor refuses a buffer it cannot import with a
259     /// protocol error, so a caller picks the format and modifier from the feedback.
260     pub fn importBuffer(self: *Dmabuf, desc: BufferDesc) Error!Buffer {
261         if (desc.planes.len == 0 or desc.planes.len > max_planes) return error.InvalidBuffer;
262         if (desc.width == 0 or desc.height == 0) return error.InvalidBuffer;
263         if (desc.width > std.math.maxInt(i32) or desc.height > std.math.maxInt(i32)) return error.InvalidBuffer;
264         const slot = freeSlot(&self.buffers) orelse return error.CapacityExceeded;
265         var params: [1]u32 = undefined;
266         self.client.request(self.dmabuf_id, linux_dmabuf.requests.create_params.opcode, &.{.{ .new_id = .fixed }}, &params) catch
267             return error.Unexpected;
268         defer self.destroyObject(params[0], buffer_params.requests.destroy.opcode);
269         const modifier_hi: u32 = @truncate(desc.modifier >> 32);
270         const modifier_lo: u32 = @truncate(desc.modifier);
271         for (desc.planes, 0..) |plane, index| {
272             const duplicate = sys.fd.duplicate(plane.fd) catch return error.InvalidBuffer;
273             self.client.request(params[0], buffer_params.requests.add.opcode, &.{
274                 .{ .descriptor_owned = duplicate },
275                 .{ .uint = @intCast(index) },
276                 .{ .uint = plane.offset },
277                 .{ .uint = plane.stride },
278                 .{ .uint = modifier_hi },
279                 .{ .uint = modifier_lo },
280             }, &.{}) catch {
281                 sys.fd.close(duplicate);
282                 return error.Unexpected;
283             };
284         }
285         var created: [1]u32 = undefined;
286         self.client.request(params[0], buffer_params.requests.create_immed.opcode, &.{
287             .{ .new_id = .fixed },
288             .{ .int = @intCast(desc.width) },
289             .{ .int = @intCast(desc.height) },
290             .{ .uint = desc.format },
291             .{ .uint = 0 },
292         }, &created) catch return error.Unexpected;
293         slot.* = created[0];
294         return .{ .id = created[0] };
295     }
296 
297     pub fn releaseBuffer(self: *Dmabuf, buffer: Buffer) void {
298         const slot = findSlot(&self.buffers, buffer.id) orelse return;
299         self.destroyObject(buffer.id, core.wl_buffer.requests.destroy.opcode);
300         slot.* = null;
301     }
302 
303     /// Imports a DRM timeline syncobj from its descriptor.
304     pub fn importTimeline(self: *Dmabuf, fd: sys.fd.Descriptor) Error!Timeline {
305         const slot = freeSlot(&self.timelines) orelse return error.CapacityExceeded;
306         const duplicate = sys.fd.duplicate(fd) catch return error.InvalidBuffer;
307         var created: [1]u32 = undefined;
308         self.client.request(self.manager_id, syncobj_manager.requests.import_timeline.opcode, &.{
309             .{ .new_id = .fixed },
310             .{ .descriptor_owned = duplicate },
311         }, &created) catch {
312             sys.fd.close(duplicate);
313             return error.Unexpected;
314         };
315         slot.* = created[0];
316         return .{ .id = created[0] };
317     }
318 
319     pub fn destroyTimeline(self: *Dmabuf, timeline: Timeline) void {
320         const slot = findSlot(&self.timelines, timeline.id) orelse return;
321         self.destroyObject(timeline.id, syncobj_timeline.requests.destroy.opcode);
322         slot.* = null;
323     }
324 
325     pub fn ownsBuffer(self: *const Dmabuf, buffer: Buffer) bool {
326         return std.mem.indexOfScalar(?u32, &self.buffers, buffer.id) != null;
327     }
328 
329     pub fn ownsTimeline(self: *const Dmabuf, timeline: Timeline) bool {
330         return std.mem.indexOfScalar(?u32, &self.timelines, timeline.id) != null;
331     }
332 
333     /// Sends the sync points of the next commit, making the surface's syncobj object first.
334     pub fn setSyncPoints(self: *Dmabuf, sync: SyncPoints) Error!void {
335         if (sync.acquire >= sync.release) return error.InvalidBuffer;
336         if (!self.ownsTimeline(sync.timeline)) return error.InvalidBuffer;
337         const surface = self.sync_surface_id orelse blk: {
338             var created: [1]u32 = undefined;
339             self.client.request(self.manager_id, syncobj_manager.requests.get_surface.opcode, &.{
340                 .{ .new_id = .fixed },
341                 .{ .object = self.surface_id },
342             }, &created) catch return error.Unexpected;
343             self.sync_surface_id = created[0];
344             break :blk created[0];
345         };
346         inline for (.{
347             .{ syncobj_surface.requests.set_acquire_point.opcode, sync.acquire },
348             .{ syncobj_surface.requests.set_release_point.opcode, sync.release },
349         }) |point| {
350             self.client.request(surface, point[0], &.{
351                 .{ .object = sync.timeline.id },
352                 .{ .uint = @truncate(point[1] >> 32) },
353                 .{ .uint = @truncate(point[1]) },
354             }, &.{}) catch return error.Unexpected;
355         }
356     }
357 
358     /// Handles feedback events and the events of imported buffers. The compositor's
359     /// `wl_buffer.release` means nothing under explicit synchronization and is dropped.
360     pub fn dispatch(self: *Dmabuf, event: *runtime.RoutedView) !bool {
361         if (self.feedback_id == event.object_id) {
362             try self.dispatchFeedback(event);
363             return true;
364         }
365         return std.mem.indexOfScalar(?u32, &self.buffers, event.object_id) != null;
366     }
367 
368     fn dispatchFeedback(self: *Dmabuf, event: *runtime.RoutedView) !void {
369         const events = feedback_protocol.events;
370         const pending = &self.pending;
371         if (!pending.open) {
372             pending.* = .{ .open = true, .main_device = self.feedback.main_device };
373         }
374         var decoder = try event.borrowedDecoder();
375         switch (event.metadata.opcode) {
376             events.format_table.opcode => {
377                 _ = try decoder.descriptor();
378                 const size = try decoder.unsigned();
379                 try decoder.finish();
380                 const fd = try event.takeDescriptor(0);
381                 defer sys.fd.close(fd);
382                 pending.table_len = try self.readTable(fd, size);
383             },
384             events.main_device.opcode => {
385                 pending.main_device = try deviceNumber(try decoder.array());
386                 try decoder.finish();
387             },
388             events.tranche_target_device.opcode => {
389                 pending.tranche.device = try deviceNumber(try decoder.array());
390                 try decoder.finish();
391             },
392             events.tranche_flags.opcode => {
393                 pending.tranche.flags = try decoder.unsigned();
394                 try decoder.finish();
395             },
396             events.tranche_formats.opcode => {
397                 const bytes = try decoder.array();
398                 try decoder.finish();
399                 if (bytes.len % 2 != 0) return error.InvalidEvent;
400                 var offset: usize = 0;
401                 while (offset < bytes.len) : (offset += 2) {
402                     if (pending.index_count == max_tranche_indices) {
403                         self.feedback.truncated = true;
404                         break;
405                     }
406                     self.pending_indices[pending.index_count] = std.mem.readInt(u16, bytes[offset..][0..2], native_endian);
407                     pending.index_count += 1;
408                     pending.tranche.index_count += 1;
409                 }
410             },
411             events.tranche_done.opcode => {
412                 try decoder.finish();
413                 if (pending.tranche_count == max_tranches) {
414                     self.feedback.truncated = true;
415                 } else {
416                     self.pending_tranches[pending.tranche_count] = pending.tranche;
417                     pending.tranche_count += 1;
418                 }
419                 pending.tranche = .{ .device = 0, .flags = 0, .first_index = pending.index_count, .index_count = 0 };
420             },
421             events.done.opcode => {
422                 try decoder.finish();
423                 self.commitFeedback();
424             },
425             else => return error.InvalidEvent,
426         }
427     }
428 
429     fn commitFeedback(self: *Dmabuf) void {
430         const pending = &self.pending;
431         const feedback = &self.feedback;
432         if (pending.table_len) |len| {
433             @memcpy(feedback.table[0..len], self.pending_table[0..len]);
434             feedback.table_len = len;
435         }
436         feedback.main_device = pending.main_device;
437         @memcpy(feedback.indices[0..pending.index_count], self.pending_indices[0..pending.index_count]);
438         feedback.index_count = pending.index_count;
439         @memcpy(feedback.tranches[0..pending.tranche_count], self.pending_tranches[0..pending.tranche_count]);
440         feedback.tranche_count = pending.tranche_count;
441         feedback.generation += 1;
442         pending.* = .{};
443     }
444 
445     /// Copies the format table the compositor shares through `fd` into pending storage.
446     fn readTable(self: *Dmabuf, fd: sys.fd.Descriptor, size: u32) !u32 {
447         if (size == 0 or size % @sizeOf(FormatModifier) != 0) return error.InvalidEvent;
448         const mapping = sys.memory.mapPrivateFile(fd, size, .{ .read = true }, 0) catch return error.InvalidEvent;
449         defer sys.memory.unmap(mapping);
450         const available: u32 = size / @sizeOf(FormatModifier);
451         const kept = @min(available, max_table_entries);
452         if (kept < available) self.feedback.truncated = true;
453         @memcpy(std.mem.sliceAsBytes(self.pending_table[0..kept]), mapping[0 .. kept * @sizeOf(FormatModifier)]);
454         return kept;
455     }
456 
457     fn destroyObject(self: *Dmabuf, id: u32, opcode: u16) void {
458         self.client.request(id, opcode, &.{}, &.{}) catch {};
459     }
460 };
461 
462 const native_endian = builtin.cpu.arch.endian();
463 
464 /// A `dev_t` the compositor sends as an array in native byte order.
465 fn deviceNumber(bytes: []const u8) !u64 {
466     if (bytes.len != @sizeOf(u64)) return error.InvalidEvent;
467     return std.mem.readInt(u64, bytes[0..8], native_endian);
468 }
469 
470 fn freeSlot(slots: []?u32) ?*?u32 {
471     for (slots) |*slot| if (slot.* == null) return slot;
472     return null;
473 }
474 
475 fn findSlot(slots: []?u32, id: u32) ?*?u32 {
476     for (slots) |*slot| if (slot.* == id) return slot;
477     return null;
478 }
479 
480 test "feedback lists a format's modifiers for a device in tranche order without repeats" {
481     var table = [_]FormatModifier{
482         .{ .format = 0x34324258, .modifier = 0 },
483         .{ .format = 0x34324258, .modifier = 0x0200000000401b03 },
484         .{ .format = 0x34325258, .modifier = 0 },
485     };
486     var indices = [_]u16{ 1, 0, 2, 0, 9 };
487     var feedback = Feedback{ .table = &table, .table_len = table.len, .indices = &indices, .index_count = indices.len, .generation = 1 };
488     feedback.tranches[0] = .{ .device = 0xE280, .flags = 1, .first_index = 0, .index_count = 3 };
489     feedback.tranches[1] = .{ .device = 0xE280, .flags = 0, .first_index = 3, .index_count = 2 };
490     feedback.tranche_count = 2;
491     var out: [4]u64 = undefined;
492     const count = feedback.modifiers(0x34324258, 0xE280, &out);
493     try std.testing.expectEqualSlices(u64, &.{ 0x0200000000401b03, 0 }, out[0..count]);
494     try std.testing.expectEqual(@as(usize, 0), feedback.modifiers(0x34324258, 0xE281, &out));
495     try std.testing.expectEqual(@as(?FormatModifier, null), feedback.entry(9));
496 }
497 
498 test "a device number decodes from the native-order array" {
499     var encoded: [8]u8 = undefined;
500     std.mem.writeInt(u64, &encoded, 0xE280, native_endian);
501     try std.testing.expectEqual(@as(u64, 0xE280), try deviceNumber(&encoded));
502     try std.testing.expectError(error.InvalidEvent, deviceNumber(&.{ 0x80, 0xE2 }));
503 }