metal: setup the render loop, draw the background color

This commit is contained in:
Mitchell Hashimoto
2022-10-28 15:46:05 -07:00
parent 07271a6cfd
commit 090bab6798
3 changed files with 147 additions and 6 deletions

View File

@ -223,7 +223,10 @@ fn addDeps(
_ = try utf8proc.link(b, step); _ = try utf8proc.link(b, step);
// Glfw // Glfw
const glfw_opts: glfw.Options = .{ .metal = false, .opengl = false }; const glfw_opts: glfw.Options = .{
.metal = step.target.isDarwin(),
.opengl = false,
};
try glfw.link(b, step, glfw_opts); try glfw.link(b, step, glfw_opts);
// Imgui, we have to do this later since we need some information // Imgui, we have to do this later since we need some information

View File

@ -54,7 +54,7 @@ pub const Object = struct {
break :getter objc.sel(val); break :getter objc.sel(val);
} else objc.sel(n); } else objc.sel(n);
self.msgSend(T, getter, .{}); return self.msgSend(T, getter, .{});
} }
}; };

View File

@ -2,12 +2,19 @@
pub const Metal = @This(); pub const Metal = @This();
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const glfw = @import("glfw"); const glfw = @import("glfw");
const objc = @import("objc");
const font = @import("../font/main.zig"); const font = @import("../font/main.zig");
const terminal = @import("../terminal/main.zig"); const terminal = @import("../terminal/main.zig");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
// Get native API access on certain platforms so we can do more customization.
const glfwNative = glfw.Native(.{
.cocoa = builtin.os.tag == .macos,
});
const log = std.log.scoped(.metal); const log = std.log.scoped(.metal);
/// Current cell dimensions for this grid. /// Current cell dimensions for this grid.
@ -19,6 +26,11 @@ foreground: terminal.color.RGB,
/// Default background color /// Default background color
background: terminal.color.RGB, background: terminal.color.RGB,
/// Metal objects
device: objc.Object, // MTLDevice
queue: objc.Object, // MTLCommandQueue
swapchain: objc.Object, // CAMetalLayer
/// Returns the hints that we want for this /// Returns the hints that we want for this
pub fn windowHints() glfw.Window.Hints { pub fn windowHints() glfw.Window.Hints {
return .{ return .{
@ -32,9 +44,23 @@ pub fn windowHints() glfw.Window.Hints {
/// window surface as necessary. /// window surface as necessary.
pub fn windowInit(window: glfw.Window) !void { pub fn windowInit(window: glfw.Window) !void {
_ = window; _ = window;
// We don't do anything else here because we want to set everything
// else up during actual initialization.
} }
pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal { pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
// Initialize our metal stuff
const device = objc.Object.fromId(MTLCreateSystemDefaultDevice());
const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{});
const swapchain = swapchain: {
const CAMetalLayer = objc.Class.getClass("CAMetalLayer").?;
const swapchain = CAMetalLayer.msgSend(objc.Object, objc.sel("layer"), .{});
swapchain.setProperty("device", device.value);
swapchain.setProperty("opaque", true);
break :swapchain swapchain;
};
// Get our cell metrics based on a regular font ascii 'M'. Why 'M'? // 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 // Doesn't matter, any normal ASCII will do we're just trying to make
// sure we use the regular font. // sure we use the regular font.
@ -49,6 +75,9 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
.cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height }, .cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height },
.background = .{ .r = 0, .g = 0, .b = 0 }, .background = .{ .r = 0, .g = 0, .b = 0 },
.foreground = .{ .r = 255, .g = 255, .b = 255 }, .foreground = .{ .r = 255, .g = 255, .b = 255 },
.device = device,
.queue = queue,
.swapchain = swapchain,
}; };
} }
@ -59,19 +88,26 @@ pub fn deinit(self: *Metal) void {
/// This is called just prior to spinning up the renderer thread for /// This is called just prior to spinning up the renderer thread for
/// final main thread setup requirements. /// final main thread setup requirements.
pub fn finalizeInit(self: *const Metal, window: glfw.Window) !void { pub fn finalizeInit(self: *const Metal, window: glfw.Window) !void {
_ = self; // Set our window backing layer to be our swapchain
_ = window; const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(window).?);
const contentView = objc.Object.fromId(nswindow.getProperty(?*anyopaque, "contentView").?);
contentView.setProperty("layer", self.swapchain.value);
contentView.setProperty("wantsLayer", true);
} }
/// Callback called by renderer.Thread when it begins. /// Callback called by renderer.Thread when it begins.
pub fn threadEnter(self: *const Metal, window: glfw.Window) !void { pub fn threadEnter(self: *const Metal, window: glfw.Window) !void {
_ = self; _ = self;
_ = window; _ = window;
// Metal requires no per-thread state.
} }
/// Callback called by renderer.Thread when it exits. /// Callback called by renderer.Thread when it exits.
pub fn threadExit(self: *const Metal) void { pub fn threadExit(self: *const Metal) void {
_ = self; _ = self;
// Metal requires no per-thread state.
} }
/// The primary render callback that is completely thread-safe. /// The primary render callback that is completely thread-safe.
@ -80,7 +116,109 @@ pub fn render(
window: glfw.Window, window: glfw.Window,
state: *renderer.State, state: *renderer.State,
) !void { ) !void {
_ = self;
_ = window; _ = window;
_ = state;
// Data we extract out of the critical area.
const Critical = struct {
bg: terminal.color.RGB,
};
// Update all our data as tightly as possible within the mutex.
const critical: Critical = critical: {
state.mutex.lock();
defer state.mutex.unlock();
// Swap bg/fg if the terminal is reversed
const bg = self.background;
const fg = self.foreground;
defer {
self.background = bg;
self.foreground = fg;
} }
if (state.terminal.modes.reverse_colors) {
self.background = fg;
self.foreground = bg;
}
break :critical .{
.bg = self.background,
};
};
// @autoreleasepool {}
const pool = objc_autoreleasePoolPush();
defer objc_autoreleasePoolPop(pool);
// Get our surface (CAMetalDrawable)
const surface = self.swapchain.msgSend(objc.Object, objc.sel("nextDrawable"), .{});
// MTLRenderPassDescriptor
const MTLRenderPassDescriptor = objc.Class.getClass("MTLRenderPassDescriptor").?;
const desc = desc: {
const desc = MTLRenderPassDescriptor.msgSend(
objc.Object,
objc.sel("renderPassDescriptor"),
.{},
);
// Set our color attachment to be our drawable surface.
const attachments = objc.Object.fromId(desc.getProperty(?*anyopaque, "colorAttachments"));
{
const attachment = attachments.msgSend(
objc.Object,
objc.sel("objectAtIndexedSubscript:"),
.{@as(c_ulong, 0)},
);
attachment.setProperty("loadAction", @enumToInt(MTLLoadAction.clear));
attachment.setProperty("storeAction", @enumToInt(MTLStoreAction.store));
attachment.setProperty("texture", surface.getProperty(objc.c.id, "texture").?);
attachment.setProperty("clearColor", MTLClearColor{
.red = @intToFloat(f32, critical.bg.r) / 255,
.green = @intToFloat(f32, critical.bg.g) / 255,
.blue = @intToFloat(f32, critical.bg.b) / 255,
.alpha = 1.0,
});
}
break :desc desc;
};
// Command buffer (MTLCommandBuffer)
const buffer = self.queue.msgSend(objc.Object, objc.sel("commandBuffer"), .{});
// MTLRenderCommandEncoder
const encoder = buffer.msgSend(
objc.Object,
objc.sel("renderCommandEncoderWithDescriptor:"),
.{desc.value},
);
encoder.msgSend(void, objc.sel("endEncoding"), .{});
buffer.msgSend(void, objc.sel("presentDrawable:"), .{surface.value});
buffer.msgSend(void, objc.sel("commit"), .{});
}
/// https://developer.apple.com/documentation/metal/mtlloadaction?language=objc
const MTLLoadAction = enum(c_ulong) {
dont_care = 0,
load = 1,
clear = 2,
};
/// https://developer.apple.com/documentation/metal/mtlstoreaction?language=objc
const MTLStoreAction = enum(c_ulong) {
dont_care = 0,
store = 1,
};
const MTLClearColor = extern struct {
red: f64,
green: f64,
blue: f64,
alpha: f64,
};
extern "c" fn MTLCreateSystemDefaultDevice() ?*anyopaque;
extern "c" fn objc_autoreleasePoolPush() ?*anyopaque;
extern "c" fn objc_autoreleasePoolPop(?*anyopaque) void;