diff --git a/src/Window.zig b/src/Window.zig index 02e8adc5c..41f493bc4 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -36,7 +36,7 @@ const log = std.log.scoped(.window); const WRITE_REQ_PREALLOC = std.math.pow(usize, 2, 5); // The renderer implementation to use. -const Renderer = renderer.OpenGL; +const Renderer = renderer.Renderer; /// Allocator alloc: Allocator, diff --git a/src/renderer.zig b/src/renderer.zig index a5d62a725..8d3e899a4 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -7,11 +7,21 @@ //! APIs. The renderers in this package assume that the renderer is already //! setup (OpenGL has a context, Vulkan has a surface, etc.) +const builtin = @import("builtin"); + pub usingnamespace @import("renderer/size.zig"); +pub const Metal = @import("renderer/Metal.zig"); pub const OpenGL = @import("renderer/OpenGL.zig"); pub const Thread = @import("renderer/Thread.zig"); pub const State = @import("renderer/State.zig"); +/// The implementation to use for the renderer. This is comptime chosen +/// so that every build has exactly one renderer implementation. +pub const Renderer = switch (builtin.os.tag) { + .macos => Metal, + else => OpenGL, +}; + test { @import("std").testing.refAllDecls(@This()); } diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig new file mode 100644 index 000000000..53765bb21 --- /dev/null +++ b/src/renderer/Metal.zig @@ -0,0 +1,86 @@ +//! Renderer implementation for Metal. +pub const Metal = @This(); + +const std = @import("std"); +const glfw = @import("glfw"); +const font = @import("../font/main.zig"); +const terminal = @import("../terminal/main.zig"); +const renderer = @import("../renderer.zig"); +const Allocator = std.mem.Allocator; + +const log = std.log.scoped(.metal); + +/// Current cell dimensions for this grid. +cell_size: renderer.CellSize, + +/// Default foreground color +foreground: terminal.color.RGB, + +/// Default background color +background: terminal.color.RGB, + +/// Returns the hints that we want for this +pub fn windowHints() glfw.Window.Hints { + return .{ + .client_api = .no_api, + // .cocoa_graphics_switching = builtin.os.tag == .macos, + // .cocoa_retina_framebuffer = true, + }; +} + +/// This is called early right after window creation to setup our +/// window surface as necessary. +pub fn windowInit(window: glfw.Window) !void { + _ = window; +} + +pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal { + // Get our cell metrics based on a regular font ascii 'M'. Why 'M'? + // Doesn't matter, any normal ASCII will do we're just trying to make + // sure we use the regular font. + const metrics = metrics: { + const index = (try font_group.indexForCodepoint(alloc, 'M', .regular, .text)).?; + const face = try font_group.group.faceFromIndex(index); + break :metrics face.metrics; + }; + log.debug("cell dimensions={}", .{metrics}); + + return Metal{ + .cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height }, + .background = .{ .r = 0, .g = 0, .b = 0 }, + .foreground = .{ .r = 255, .g = 255, .b = 255 }, + }; +} + +pub fn deinit(self: *Metal) void { + self.* = undefined; +} + +/// This is called just prior to spinning up the renderer thread for +/// final main thread setup requirements. +pub fn finalizeInit(self: *const Metal, window: glfw.Window) !void { + _ = self; + _ = window; +} + +/// Callback called by renderer.Thread when it begins. +pub fn threadEnter(self: *const Metal, window: glfw.Window) !void { + _ = self; + _ = window; +} + +/// Callback called by renderer.Thread when it exits. +pub fn threadExit(self: *const Metal) void { + _ = self; +} + +/// The primary render callback that is completely thread-safe. +pub fn render( + self: *Metal, + window: glfw.Window, + state: *renderer.State, +) !void { + _ = self; + _ = window; + _ = state; +} diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index 9a6b71605..5fb10420a 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -31,7 +31,7 @@ render_h: libuv.Timer, window: glfw.Window, /// The underlying renderer implementation. -renderer: *renderer.OpenGL, +renderer: *renderer.Renderer, /// Pointer to the shared state that is used to generate the final render. state: *renderer.State, @@ -42,7 +42,7 @@ state: *renderer.State, pub fn init( alloc: Allocator, window: glfw.Window, - renderer_impl: *renderer.OpenGL, + renderer_impl: *renderer.Renderer, state: *renderer.State, ) !Thread { // We always store allocator pointer on the loop data so that @@ -143,16 +143,11 @@ pub fn threadMain(self: *Thread) void { } fn threadMain_(self: *Thread) !void { - // Get a copy to our allocator - // const alloc_ptr = self.loop.getData(Allocator).?; - // const alloc = alloc_ptr.*; - // Run our thread start/end callbacks. This is important because some // renderers have to do per-thread setup. For example, OpenGL has to set // some thread-local state since that is how it works. - const Renderer = RendererType(); - if (@hasDecl(Renderer, "threadEnter")) try self.renderer.threadEnter(self.window); - defer if (@hasDecl(Renderer, "threadExit")) self.renderer.threadExit(); + try self.renderer.threadEnter(self.window); + defer self.renderer.threadExit(); // Set up our async handler to support rendering self.wakeup.setData(self); @@ -199,14 +194,3 @@ fn renderCallback(h: *libuv.Timer) void { fn stopCallback(h: *libuv.Async) void { h.loop().stop(); } - -// This is unnecessary right now but is logic we'll need for when we -// abstract renderers out. -fn RendererType() type { - const self: Thread = undefined; - return switch (@typeInfo(@TypeOf(self.renderer))) { - .Pointer => |p| p.child, - .Struct => |s| s, - else => unreachable, - }; -}