mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
metal: setup the render loop, draw the background color
This commit is contained in:
@ -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
|
||||||
|
@ -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, .{});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user