From 8d062a90b1ffbe9e00334fa3e9e939406bd32141 Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Sun, 2 Feb 2025 16:00:15 +0100 Subject: screen: add vsync and double buffering --- src/screen/drm/card.zig | 26 ++++++++-- src/screen/drm/crtc/root.zig | 32 +++++++++++- src/screen/drm/event.zig | 31 +++++++++++ src/screen/drm/frame-buffer/page-flip.zig | 8 +++ src/screen/drm/frame-buffer/root.zig | 41 +++++++++++++++ src/screen/drm/request.zig | 2 + src/screen/main.zig | 86 ++++++++++++++++++++++++------- 7 files changed, 202 insertions(+), 24 deletions(-) create mode 100644 src/screen/drm/event.zig create mode 100644 src/screen/drm/frame-buffer/page-flip.zig (limited to 'src/screen') diff --git a/src/screen/drm/card.zig b/src/screen/drm/card.zig index d66a5f1..6147cf2 100644 --- a/src/screen/drm/card.zig +++ b/src/screen/drm/card.zig @@ -7,7 +7,8 @@ const Resources = @import("resources.zig").Resources; const Connector = @import("connector/root.zig").Connector; const Encoder = @import("encoder/root.zig").Encoder; const Crtc = @import("crtc/root.zig").Crtc; -const FrameBuffer = @import("frame-buffer/root.zig").FrameBuffer; +const DoubleBuffer = @import("frame-buffer/root.zig").DoubleBuffer; +const Event = @import("event.zig").Event; pub const Card = struct { @@ -25,6 +26,7 @@ pub const Card = struct { .mode = .read_write, .lock_nonblocking = true, }), + .allocator = allocator, }; } @@ -54,7 +56,25 @@ pub const Card = struct { return Crtc.init(self, id); } - pub fn create_frame_buffer(self: *Card, width: u32, height: u32, bpp: u32) !FrameBuffer { - return FrameBuffer.init(self, width, height, bpp); + pub fn create_double_buffer(self: *Card, width: u32, height: u32, bpp: u32) !DoubleBuffer { + return DoubleBuffer.init(self, width, height, bpp); + } + + pub fn poll_event(self: *Card, timeout: i32) !?Event { + var pollfd = os.pollfd { + .fd = self.file.handle, + .events = os.POLL.IN, + .revents = 0 + }; + + try cerror.from_usize(os.poll(@ptrCast(&pollfd), 1, timeout)); + + if ((pollfd.revents & os.POLL.IN) != 0) { + var event = std.mem.zeroes(Event); + try cerror.from_usize(os.read(self.file.handle, @ptrCast(&event), @sizeOf(Event))); + return event; + } + + return null; } }; diff --git a/src/screen/drm/crtc/root.zig b/src/screen/drm/crtc/root.zig index 3c9d6f9..f1f1d5c 100644 --- a/src/screen/drm/crtc/root.zig +++ b/src/screen/drm/crtc/root.zig @@ -2,7 +2,9 @@ const std = @import("std"); const Card = @import("../card.zig").Card; const Drm = @import("../request.zig").Drm; const Connector = @import("../connector/root.zig").Connector; -const FrameBuffer = @import("../frame-buffer/root.zig").FrameBuffer; +const fb = @import("../frame-buffer/root.zig"); +const FrameBuffer = fb.FrameBuffer; +const DoubleBuffer = fb.DoubleBuffer; const Mode = @import("../connector/mode.zig").Mode; pub const RawCrtc = extern struct { @@ -17,6 +19,22 @@ pub const RawCrtc = extern struct { mode: Mode, }; +const PageFlipEvent = packed struct(u32) { + event: bool, + is_async: bool, + absolute: bool, + relative: bool, + __padding: u28, +}; + +const PageFlip = extern struct { + crtc_id: u32, + buffer_id: u32, + flags: PageFlipEvent, + __reserved: u32, + user_data: u64, +}; + pub const Crtc = struct { const Self = @This(); @@ -65,8 +83,18 @@ pub const Crtc = struct { }; try Drm.set_crtc.request(self.card.file.handle, RawCrtc, &crtc); + } + + pub fn page_flip(self: *Self, double_buffer: *DoubleBuffer) !void { + double_buffer.swap(); + + var flip = std.mem.zeroInit(PageFlip, .{ + .buffer_id = double_buffer.crtc_buffer().id, + .crtc_id = self.id, + .flags = std.mem.zeroInit(PageFlipEvent, .{ .event = true }), + }); - std.debug.print("crtc = {any}\n", .{self}); + try Drm.page_flip.request(self.card.file.handle, PageFlip, &flip); } pub fn detach(self: *Self) void { diff --git a/src/screen/drm/event.zig b/src/screen/drm/event.zig new file mode 100644 index 0000000..201ced3 --- /dev/null +++ b/src/screen/drm/event.zig @@ -0,0 +1,31 @@ + +pub const EventType = enum(u32) { + vblank = 1, + page_flip_complete = 2, + crtc_sequence = 3, +}; + +pub const VBlankEvent = extern struct { + user_data: u64, + tv_sec: u32, + tv_usec: u32, + sequence: u32, + crtc_id: u32, +}; + +pub const CrtcSequenceEvent = extern struct { + user_data: u64, + time_ns: i64, + sequence: u64, +}; + +pub const EventPayload = extern union { + vblank: VBlankEvent, + crtc_sequence: CrtcSequenceEvent, +}; + +pub const Event = extern struct { + type: EventType, + length: u32, + payload: EventPayload, +}; diff --git a/src/screen/drm/frame-buffer/page-flip.zig b/src/screen/drm/frame-buffer/page-flip.zig new file mode 100644 index 0000000..2e41e61 --- /dev/null +++ b/src/screen/drm/frame-buffer/page-flip.zig @@ -0,0 +1,8 @@ + +pub const PageFlip = packed struct(u32) { + event: bool, + is_async: bool, + absolute: bool, + relative: bool, + _padding: u28, +} diff --git a/src/screen/drm/frame-buffer/root.zig b/src/screen/drm/frame-buffer/root.zig index eb7b47b..0083711 100644 --- a/src/screen/drm/frame-buffer/root.zig +++ b/src/screen/drm/frame-buffer/root.zig @@ -33,6 +33,43 @@ const MapDumb = extern struct { offset: i64, }; +pub const DoubleBuffer = struct { + const Self = @This(); + + back: FrameBuffer, + front: FrameBuffer, + + pub fn init(card: *Card, width: u32, height: u32, bpp: u32) !Self { + return .{ + .back = try FrameBuffer.init(card, width, height, bpp), + .front = try FrameBuffer.init(card, width, height, bpp), + }; + } + + pub fn swap(self: *Self) void { + const back_data = self.back.data; + self.back.data = self.front.data; + self.front.data = back_data; + + const back_id = self.back.id; + self.back.id = self.front.id; + self.front.id = back_id; + } + + pub fn deinit(self: *Self) void { + self.back.deinit(); + self.front.deinit(); + } + + pub fn buffer(self: *Self) *FrameBuffer { + return &self.back; + } + + pub fn crtc_buffer(self: *Self) *FrameBuffer { + return &self.front; + } +}; + pub const Pixel = packed struct(u32) { blue: u8, green: u8, @@ -127,6 +164,10 @@ pub const FrameBuffer = struct { self.data[x + self.width * y] = pixel; } + pub fn fill(self: *Self, pixel: Pixel) void { + @memset(self.data[0..self.canvas_size], pixel); + } + pub fn deinit(self: *Self) void { cerror.from_usize( os.munmap(@ptrCast(@volatileCast(self.data)), self.data_size) diff --git a/src/screen/drm/request.zig b/src/screen/drm/request.zig index 9e0a045..5daf92f 100644 --- a/src/screen/drm/request.zig +++ b/src/screen/drm/request.zig @@ -15,6 +15,8 @@ pub const Drm = enum(u8) { get_encoder = 0xa6, get_connector = 0xa7, remove_frame_buffer = 0xaf, + page_flip = 0xb0, + dirty_frame_buffer = 0xb1, create_dumb = 0xb2, map_dumb = 0xb3, destroy_dumb = 0xb4, diff --git a/src/screen/main.zig b/src/screen/main.zig index 1a33fd0..f11f628 100644 --- a/src/screen/main.zig +++ b/src/screen/main.zig @@ -1,5 +1,6 @@ const std = @import("std"); const drm = @import("drm/card.zig"); +const Event = @import("drm/event.zig").Event; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; @@ -38,35 +39,82 @@ pub fn main() !void { std.debug.print("crtc = {}\n", .{ crtc.id }); - var framebuffer = try card.create_frame_buffer(mode.horizontal.size, mode.vertical.size, 32); - defer framebuffer.deinit(); + var double_buffer = try card.create_double_buffer(mode.horizontal.size, mode.vertical.size, 32); + defer double_buffer.deinit(); - std.debug.print("framebuffer = {}, {}\n", .{ framebuffer.id, framebuffer.canvas_size }); + std.debug.print("buffer = {}, {}x{}, stride = {}\n", .{ + double_buffer.buffer().id, + double_buffer.buffer().width, + double_buffer.buffer().height, + double_buffer.buffer().stride, + }); - try crtc.attach(&framebuffer, &connector, mode); + try crtc.attach(double_buffer.crtc_buffer(), &connector, mode); - var frame: u24 = 0; + const Pos = struct { x: f32, y: f32 }; + + var vec = Pos { .x = 10, .y = 10 }; + var pos = Pos { .x = 0, .y = 0 }; + var delta: f32 = 0; + const size = 100; + + const width: f32 = @floatFromInt(double_buffer.buffer().width); + const height: f32 = @floatFromInt(double_buffer.buffer().height); while (true) { - for (0..framebuffer.width) |x| { - for (0..framebuffer.height) |y| { - const raster_x = (frame + x) / 100 * 100; - const raster_y = y / 100 * 100; - const color = ((raster_x + raster_y) + 0x129453) * 0x329120; - framebuffer.set(@intCast(x), @intCast(y), .{ - .red = @intCast(color & 0xff), - .green = @intCast((color >> 8) & 0xff), - .blue = @intCast((color >> 16) & 0xff), + const start = try std.time.Instant.now(); + + double_buffer.buffer().fill(.{ .red = 0, .green = 0, .blue = 0 }); + + pos.x += vec.x * delta; + pos.y += vec.y * delta; + + if (pos.x < 0) { + pos.x = -pos.x; + vec.x = -vec.x; + } else if (pos.x + size >= width) { + pos.x = width - size; + vec.x = -vec.x; + } + + if (pos.y < 0) { + pos.y = -pos.y; + vec.y = -vec.y; + } else if (pos.y + size >= height) { + pos.y = height - size; + vec.y = -vec.y; + } + + for (0..size) |w| { + for (0..size) |h| { + double_buffer.buffer().set( + @as(u32, @intFromFloat(pos.x)) + @as(u32, @intCast(w)), + @as(u32, @intFromFloat(pos.y)) + @as(u32, @intCast(h)), + .{ + .red = 0xff, + .green = 0, + .blue = 0, }); } } - frame, _ = @addWithOverflow(frame, 1); + const end = try std.time.Instant.now(); + const elapsed = (@as(f32, @floatFromInt(end.since(start))) / std.time.ns_per_s); + std.debug.print("FPS: {d:.2}\r", .{ 1 / elapsed }); + delta = elapsed * 60; - try crtc.attach(&framebuffer, &connector, mode); + try crtc.page_flip(&double_buffer); - std.time.sleep(@intFromFloat(1000000000 / mode.frame_rate())); - } + while (true) { + var event: ?Event = null; + while (event == null) { + event = try card.poll_event(5000); + } - while (true) { std.time.sleep(5000000000); } + switch ((event orelse unreachable).type) { + .page_flip_complete => break, + else => {}, + } + } + } } -- cgit v1.2.3-70-g09d2