lib/windowing/src/presentation.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 pub const default_retained_frame_byte_count: usize = 3840 * 2160 * 4;
  4 
  5 pub const Limits = struct {
  6     pending_feedback_count: usize = 256,
  7     retained_outcome_count: usize = 256,
  8     active_buffer_count: usize = 3,
  9     retired_buffer_count: usize = 3,
 10     retained_frame_byte_count: usize = default_retained_frame_byte_count,
 11 };
 12 
 13 pub const CapacityError = error{
 14     FrameStorageEmpty,
 15     CapacityOverflow,
 16 };
 17 
 18 pub const Capacity = struct {
 19     retained_frame_byte_count: usize,
 20     x11_packed_region_bytes: usize,
 21     wayland_pending_frame_bytes: usize,
 22     cocoa_frame_slot_count: usize,
 23     cocoa_frame_storage_bytes: usize,
 24     maximum_backend_frame_storage_bytes: usize,
 25 
 26     pub fn derive(limits: Limits) CapacityError!Capacity {
 27         if (limits.retained_frame_byte_count == 0) return error.FrameStorageEmpty;
 28         const cocoa_frame_slot_count = 2;
 29         const cocoa_frame_storage_bytes = std.math.mul(
 30             usize,
 31             limits.retained_frame_byte_count,
 32             cocoa_frame_slot_count,
 33         ) catch return error.CapacityOverflow;
 34         return .{
 35             .retained_frame_byte_count = limits.retained_frame_byte_count,
 36             .x11_packed_region_bytes = limits.retained_frame_byte_count,
 37             .wayland_pending_frame_bytes = limits.retained_frame_byte_count,
 38             .cocoa_frame_slot_count = cocoa_frame_slot_count,
 39             .cocoa_frame_storage_bytes = cocoa_frame_storage_bytes,
 40             .maximum_backend_frame_storage_bytes = cocoa_frame_storage_bytes,
 41         };
 42     }
 43 };
 44 
 45 pub const StorageStatus = struct {
 46     retired_buffer_capacity_rejection_count: u64 = 0,
 47     frame_capacity_rejection_count: u64 = 0,
 48     wayland_output_capacity_rejection_count: u64 = 0,
 49     /// The count of presents in which at least one accumulator ran out of room
 50     /// and fell back to a single covering rectangle. The Wayland backend raises
 51     /// the count in `Presenter.markFrame` by one per present, whatever the
 52     /// number of accumulators that collapsed within it. A present whose
 53     /// accumulator collapsed still reaches the compositor and still carries the
 54     /// right pixels, at the cost of sending more of them. The count stops at
 55     /// the largest value the field holds. A caller reads the field through the
 56     /// presentation storage status the window reports to know when the backend
 57     /// sent more pixels than the regions it reported cover.
 58     damage_capacity_collapse_count: u64 = 0,
 59 };
 60 
 61 pub const Status = enum {
 62     unavailable,
 63     active,
 64     failed,
 65 };
 66 
 67 pub const TimestampError = error{InvalidTimestamp};
 68 
 69 pub const Timestamp = struct {
 70     seconds: u64,
 71     nanoseconds: u32,
 72 
 73     pub fn init(seconds: u64, nanoseconds: u32) TimestampError!Timestamp {
 74         if (nanoseconds >= std.time.ns_per_s) return error.InvalidTimestamp;
 75         return .{
 76             .seconds = seconds,
 77             .nanoseconds = nanoseconds,
 78         };
 79     }
 80 
 81     pub fn fromNanoseconds(nanoseconds: u64) Timestamp {
 82         return .{
 83             .seconds = nanoseconds / std.time.ns_per_s,
 84             .nanoseconds = @intCast(nanoseconds % std.time.ns_per_s),
 85         };
 86     }
 87 
 88     pub fn totalNanoseconds(self: Timestamp) u128 {
 89         return @as(u128, self.seconds) * std.time.ns_per_s + self.nanoseconds;
 90     }
 91 
 92     pub fn elapsedSince(self: Timestamp, earlier: Timestamp) ?u128 {
 93         const current = self.totalNanoseconds();
 94         const start = earlier.totalNanoseconds();
 95         return if (current >= start) current - start else null;
 96     }
 97 };
 98 
 99 pub const Kind = packed struct(u32) {
100     vsync: bool = false,
101     hw_clock: bool = false,
102     hw_completion: bool = false,
103     zero_copy: bool = false,
104     _: u28 = 0,
105 };
106 
107 pub const Submission = struct {
108     id: u64,
109     clock_id: u32,
110     timestamp: Timestamp,
111 };
112 
113 pub const Presented = struct {
114     submission: Submission,
115     timestamp: Timestamp,
116     refresh_nanoseconds: u32,
117     sequence: u64,
118     kind: Kind,
119     synchronized_outputs: u32,
120 
121     pub fn latencyNanoseconds(self: Presented) ?u128 {
122         return self.timestamp.elapsedSince(self.submission.timestamp);
123     }
124 };
125 
126 pub const Discarded = struct {
127     submission: Submission,
128 };
129 
130 pub const Outcome = union(enum) {
131     presented: Presented,
132     discarded: Discarded,
133 
134     pub fn submission(self: Outcome) Submission {
135         return switch (self) {
136             .presented => |value| value.submission,
137             .discarded => |value| value.submission,
138         };
139     }
140 };
141 
142 pub fn rgba8ByteCount(width: u32, height: u32) ?usize {
143     if (width == 0 or height == 0) return null;
144     const pixels = std.math.mul(usize, width, height) catch return null;
145     return std.math.mul(usize, pixels, 4) catch null;
146 }
147 
148 test "presentation capacity derives exact backend frame storage" {
149     const capacity = try Capacity.derive(.{ .retained_frame_byte_count = 13 });
150     try std.testing.expectEqual(@as(usize, 13), capacity.retained_frame_byte_count);
151     try std.testing.expectEqual(@as(usize, 13), capacity.x11_packed_region_bytes);
152     try std.testing.expectEqual(@as(usize, 13), capacity.wayland_pending_frame_bytes);
153     try std.testing.expectEqual(@as(usize, 2), capacity.cocoa_frame_slot_count);
154     try std.testing.expectEqual(@as(usize, 26), capacity.cocoa_frame_storage_bytes);
155     try std.testing.expectEqual(@as(usize, 26), capacity.maximum_backend_frame_storage_bytes);
156     try std.testing.expectError(error.FrameStorageEmpty, Capacity.derive(.{
157         .retained_frame_byte_count = 0,
158     }));
159     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
160         .retained_frame_byte_count = std.math.maxInt(usize),
161     }));
162 }
163 
164 test "RGBA frame bytes reject empty and overflowing dimensions" {
165     try std.testing.expectEqual(@as(?usize, 48), rgba8ByteCount(4, 3));
166     try std.testing.expectEqual(@as(?usize, null), rgba8ByteCount(0, 3));
167     try std.testing.expectEqual(
168         @as(?usize, null),
169         rgba8ByteCount(std.math.maxInt(u32), std.math.maxInt(u32)),
170     );
171 }
172 
173 test "timestamps validate their nanosecond component" {
174     const timestamp = try Timestamp.init(0x0123_4567_89ab_cdef, 987_654_321);
175     try std.testing.expectEqual(@as(u64, 0x0123_4567_89ab_cdef), timestamp.seconds);
176     try std.testing.expectEqual(@as(u32, 987_654_321), timestamp.nanoseconds);
177     try std.testing.expectError(error.InvalidTimestamp, Timestamp.init(0, std.time.ns_per_s));
178 }
179 
180 test "presented outcomes derive latency in the compositor clock domain" {
181     const submitted = Timestamp.fromNanoseconds(4_000_000_005);
182     const presented = Timestamp.fromNanoseconds(4_016_666_672);
183     const outcome = Presented{
184         .submission = .{ .id = 1, .clock_id = 7, .timestamp = submitted },
185         .timestamp = presented,
186         .refresh_nanoseconds = 16_666_667,
187         .sequence = 3,
188         .kind = .{ .vsync = true },
189         .synchronized_outputs = 1,
190     };
191     try std.testing.expectEqual(@as(?u128, 16_666_667), outcome.latencyNanoseconds());
192 }