renderer: big rework, graphics API abstraction layers, unified logic

This commit is very large, representing about a month of work with many
interdependent changes that don't separate cleanly in to atomic commits.

The main change here is unifying the renderer logic to a single generic
renderer, implemented on top of an abstraction layer over OpenGL/Metal.

I'll write a more complete summary of the changes in the description of
the PR.
This commit is contained in:
Qwerasd
2025-06-16 17:44:44 -06:00
parent 521872442a
commit 371d62a82c
68 changed files with 7088 additions and 7311 deletions

View File

@@ -20,6 +20,16 @@ const log = std.log.scoped(.renderer_thread);
const DRAW_INTERVAL = 8; // 120 FPS
const CURSOR_BLINK_INTERVAL = 600;
/// Whether calls to `drawFrame` must be done from the app thread.
///
/// If this is `true` then we send a `redraw_surface` message to the apprt
/// whenever we need to draw instead of calling `drawFrame` directly.
const must_draw_from_app_thread =
if (@hasDecl(apprt.App, "must_draw_from_app_thread"))
apprt.App.must_draw_from_app_thread
else
false;
/// The type used for sending messages to the IO thread. For now this is
/// hardcoded with a capacity. We can make this a comptime parameter in
/// the future if we want it configurable.
@@ -314,6 +324,16 @@ fn stopDrawTimer(self: *Thread) void {
/// Drain the mailbox.
fn drainMailbox(self: *Thread) !void {
// There's probably a more elegant way to do this...
//
// This is effectively an @autoreleasepool{} block, which we need in
// order to ensure that autoreleased objects are properly released.
const pool = if (builtin.os.tag.isDarwin())
@import("objc").AutoreleasePool.init()
else
void;
defer if (builtin.os.tag.isDarwin()) pool.deinit();
while (self.mailbox.pop()) |message| {
log.debug("mailbox message={}", .{message});
switch (message) {
@@ -432,7 +452,7 @@ fn drainMailbox(self: *Thread) !void {
self.renderer.markDirty();
},
.resize => |v| try self.renderer.setScreenSize(v),
.resize => {}, //|v| try self.renderer.setScreenSize(v),
.change_config => |config| {
defer config.alloc.destroy(config.thread);
@@ -468,20 +488,16 @@ fn drawFrame(self: *Thread, now: bool) void {
if (!self.flags.visible) return;
// If the renderer is managing a vsync on its own, we only draw
// when we're forced to via now.
// when we're forced to via `now`.
if (!now and self.renderer.hasVsync()) return;
// If we're doing single-threaded GPU calls then we just wake up the
// app thread to redraw at this point.
if (rendererpkg.Renderer == rendererpkg.OpenGL and
rendererpkg.OpenGL.single_threaded_draw)
{
if (must_draw_from_app_thread) {
_ = self.app_mailbox.push(
.{ .redraw_surface = self.surface },
.{ .instant = {} },
);
} else {
self.renderer.drawFrame(self.surface) catch |err|
self.renderer.drawFrame(false) catch |err|
log.warn("error drawing err={}", .{err});
}
}