lots of progress running a surface but still crashes

This commit is contained in:
Mitchell Hashimoto
2023-02-17 15:49:19 -08:00
parent 55b05b22bb
commit ff9af8a07b
12 changed files with 156 additions and 61 deletions

View File

@ -147,6 +147,9 @@ pub fn build(b: *std.build.Builder) !void {
lib.linkLibC();
lib.addOptions("build_options", exe_options);
// See the comment in this file
lib.addCSourceFile("src/renderer/metal_workaround.c", &.{});
// Create a single static lib with all our dependencies merged
var lib_list = try addDeps(b, lib, true);
try lib_list.append(.{ .generated = &lib.output_path_source });
@ -172,6 +175,9 @@ pub fn build(b: *std.build.Builder) !void {
lib.linkLibC();
lib.addOptions("build_options", exe_options);
// See the comment in this file
lib.addCSourceFile("src/renderer/metal_workaround.c", &.{});
// Create a single static lib with all our dependencies merged
var lib_list = try addDeps(b, lib, true);
try lib_list.append(.{ .generated = &lib.output_path_source });
@ -425,10 +431,12 @@ fn addDeps(
}
// stb_image_resize
_ = try stb_image_resize.link(b, step, .{});
const stb_image_resize_step = try stb_image_resize.link(b, step, .{});
try static_libs.append(.{ .generated = &stb_image_resize_step.output_path_source });
// utf8proc
_ = try utf8proc.link(b, step);
const utf8proc_step = try utf8proc.link(b, step);
try static_libs.append(.{ .generated = &utf8proc_step.output_path_source });
// Imgui, we have to do this later since we need some information
const imgui_backends = if (step.target.isDarwin())
@ -499,7 +507,7 @@ fn addDeps(
// Pixman
const pixman_step = try pixman.link(b, step, .{});
_ = pixman_step;
try static_libs.append(.{ .generated = &pixman_step.output_path_source });
// Only Linux gets fontconfig
if (enable_fontconfig) {

View File

@ -27,7 +27,10 @@ typedef struct {
void *userdata;
ghostty_runtime_wakeup_cb wakeup_cb;
} ghostty_runtime_config_s;
typedef struct {} ghostty_surface_config_s;
typedef struct {
void *nsview;
double scale_factor;
} ghostty_surface_config_s;
// Opaque types
typedef void *ghostty_app_t;
@ -44,12 +47,12 @@ void ghostty_config_free(ghostty_config_t);
void ghostty_config_load_string(ghostty_config_t, const char *, uintptr_t);
void ghostty_config_finalize(ghostty_config_t);
ghostty_app_t ghostty_app_new(ghostty_runtime_config_s *, ghostty_config_t);
ghostty_app_t ghostty_app_new(const ghostty_runtime_config_s *, ghostty_config_t);
void ghostty_app_free(ghostty_app_t);
int ghostty_app_tick(ghostty_app_t);
ghostty_surface_t ghostty_surface_new(ghostty_app_t, ghostty_surface_config_s*);
void ghostty_surface_free(ghostty_app_t, ghostty_surface_t);
void ghostty_surface_free(ghostty_surface_t);
#ifdef __cplusplus
}

View File

@ -9,8 +9,8 @@
/* Begin PBXBuildFile section */
A507573E299FF33C009D7DC7 /* TerminalSurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A507573D299FF33C009D7DC7 /* TerminalSurfaceView.swift */; };
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.swift */; };
A55685E029A03A9F004303CE /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55685DF29A03A9F004303CE /* AppError.swift */; };
A5B30535299BEAAA0047F10C /* GhosttyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B30534299BEAAA0047F10C /* GhosttyApp.swift */; };
A5B30537299BEAAA0047F10C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B30536299BEAAA0047F10C /* ContentView.swift */; };
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
A5D495A2299BEC7E00DD1313 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
/* End PBXBuildFile section */
@ -18,9 +18,9 @@
/* Begin PBXFileReference section */
A507573D299FF33C009D7DC7 /* TerminalSurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSurfaceView.swift; sourceTree = "<group>"; };
A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = "<group>"; };
A55685DF29A03A9F004303CE /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; };
A5B30534299BEAAA0047F10C /* GhosttyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyApp.swift; sourceTree = "<group>"; };
A5B30536299BEAAA0047F10C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
@ -43,9 +43,9 @@
children = (
A5D495A0299BEC2200DD1313 /* Preview Content */,
A5B30534299BEAAA0047F10C /* GhosttyApp.swift */,
A5B30536299BEAAA0047F10C /* ContentView.swift */,
A535B9D9299C569B0017E2E4 /* ErrorView.swift */,
A507573D299FF33C009D7DC7 /* TerminalSurfaceView.swift */,
A55685DF29A03A9F004303CE /* AppError.swift */,
);
path = Sources;
sourceTree = "<group>";
@ -153,8 +153,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
A507573E299FF33C009D7DC7 /* TerminalSurfaceView.swift in Sources */,
A5B30537299BEAAA0047F10C /* ContentView.swift in Sources */,
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
A5B30535299BEAAA0047F10C /* GhosttyApp.swift in Sources */,
);

View File

@ -0,0 +1,3 @@
enum AppError: Error {
case surfaceCreateError
}

View File

@ -1,19 +0,0 @@
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

View File

@ -20,7 +20,7 @@ struct GhosttyApp: App {
case .error:
ErrorView()
case .ready:
TerminalSurfaceView()
TerminalSurfaceView(app: ghostty.app!)
}
}
}
@ -37,6 +37,10 @@ class GhosttyState: ObservableObject {
/// The ghostty global configuration.
var config: ghostty_config_t? = nil
/// The ghostty app instance. We only have one of these for the entire app, although I guess
/// in theory you can have multiple... I don't know why you would...
var app: ghostty_app_t? = nil
init() {
// Initialize ghostty global state. This happens once per process.
guard ghostty_init() == GHOSTTY_SUCCESS else {
@ -51,6 +55,7 @@ class GhosttyState: ObservableObject {
readiness = .error
return
}
self.config = cfg;
// TODO: we'd probably do some config loading here... for now we'd
// have to do this synchronously. When we support config updating we can do
@ -59,11 +64,29 @@ class GhosttyState: ObservableObject {
// Finalize will make our defaults available.
ghostty_config_finalize(cfg)
config = cfg
readiness = .ready
// Create our "runtime" config. The "runtime" is the configuration that ghostty
// uses to interface with the application runtime environment.
var runtime_cfg = ghostty_runtime_config_s(
userdata: nil,
wakeup_cb: { userdata in GhosttyState.wakeup() })
// Create the ghostty app.
guard let app = ghostty_app_new(&runtime_cfg, cfg) else {
GhosttyApp.logger.critical("ghostty_app_new failed")
readiness = .error
return
}
self.app = app
self.readiness = .ready
}
static func wakeup() {
// TODO
}
deinit {
ghostty_app_free(app)
ghostty_config_free(config)
}
}

View File

@ -1,4 +1,6 @@
import OSLog
import SwiftUI
import GhosttyKit
/// A surface is terminology in Ghostty for a terminal surface, or a place where a terminal is actually drawn
/// and interacted with. The word "surface" is used because a surface may represent a window, a tab,
@ -8,7 +10,11 @@ import SwiftUI
/// since that is what the Metal renderer in Ghostty expects. In the future, it may make more sense to
/// wrap an MTKView and use that, but for legacy reasons we didn't do that to begin with.
struct TerminalSurfaceView: NSViewRepresentable {
@StateObject private var state = TerminalSurfaceState()
@StateObject private var state: TerminalSurfaceState
init(app: ghostty_app_t) {
self._state = StateObject(wrappedValue: TerminalSurfaceState(app))
}
func makeNSView(context: Context) -> TerminalSurfaceView_Real {
// We need the view as part of the state to be created previously because
@ -24,10 +30,33 @@ struct TerminalSurfaceView: NSViewRepresentable {
/// The state for the terminal surface view.
class TerminalSurfaceState: ObservableObject {
var view: TerminalSurfaceView_Real;
static let logger = Logger(
subsystem: Bundle.main.bundleIdentifier!,
category: String(describing: TerminalSurfaceState.self)
)
init() {
var view: TerminalSurfaceView_Real
private var surface: ghostty_surface_t? = nil
private var error: Error? = nil
init(_ app: ghostty_app_t) {
view = TerminalSurfaceView_Real()
var surface_cfg = ghostty_surface_config_s(
nsview: Unmanaged.passUnretained(view).toOpaque(),
scale_factor: 2.0)
guard let surface = ghostty_surface_new(app, &surface_cfg) else {
self.error = AppError.surfaceCreateError
return
}
self.surface = surface;
}
deinit {
if let surface = self.surface {
ghostty_surface_free(surface)
}
}
}
@ -52,8 +81,10 @@ class TerminalSurfaceView_Real: NSView {
}
}
struct TerminalSurfaceView_Previews: PreviewProvider {
/*
struct TerminalSurfaceView_Previews: PreviewProvider {
static var previews: some View {
TerminalSurfaceView()
}
}
}
*/

View File

@ -138,7 +138,7 @@ const srcs = &.{
root ++ "pixman/pixman-region16.c",
root ++ "pixman/pixman-region32.c",
root ++ "pixman/pixman-solid-fill.c",
root ++ "pixman/pixman-timer.c",
//root ++ "pixman/pixman-timer.c",
root ++ "pixman/pixman-trap.c",
root ++ "pixman/pixman-utils.c",
};

View File

@ -387,8 +387,9 @@ pub const CAPI = struct {
app: *App,
opts: *const apprt.runtime.Window.Options,
) !*Window {
_ = opts;
const w = try app.newWindow(.{});
const w = try app.newWindow(.{
.runtime = opts.*,
});
return w;
}

View File

@ -8,6 +8,7 @@ const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const objc = @import("objc");
const apprt = @import("../apprt.zig");
const CoreApp = @import("../App.zig");
const CoreWindow = @import("../Window.zig");
@ -45,15 +46,25 @@ pub const App = struct {
};
pub const Window = struct {
nsview: objc.Object,
scale_factor: f64,
pub const Options = extern struct {
id: usize = 0,
/// The pointer to the backing NSView for the surface.
nsview: *anyopaque = undefined,
/// The scale factor of the screen.
scale_factor: f64 = 1,
};
pub fn init(app: *const CoreApp, core_win: *CoreWindow, opts: Options) !Window {
_ = app;
_ = core_win;
_ = opts;
return .{};
return .{
.nsview = objc.Object.fromId(opts.nsview),
.scale_factor = opts.scale_factor,
};
}
pub fn deinit(self: *Window) void {
@ -67,7 +78,10 @@ pub const Window = struct {
pub fn getSize(self: *const Window) !apprt.WindowSize {
_ = self;
return apprt.WindowSize{ .width = 1, .height = 1 };
// Initially our window will have a zero size. Until we can determine
// the size of the window, we just send down this value.
return apprt.WindowSize{ .width = 800, .height = 600 };
}
pub fn setSizeLimits(self: *Window, min: apprt.WindowSize, max_: ?apprt.WindowSize) !void {

View File

@ -305,22 +305,42 @@ pub fn deinit(self: *Metal) void {
/// This is called just prior to spinning up the renderer thread for
/// final main thread setup requirements.
pub fn finalizeWindowInit(self: *const Metal, win: apprt.runtime.Window) !void {
// Set our window backing layer to be our swapchain
const nswindow = switch (apprt.runtime) {
apprt.glfw => objc.Object.fromId(glfwNative.getCocoaWindow(win.window).?),
apprt.embedded => @panic("TODO"),
const Info = struct {
view: objc.Object,
scaleFactor: f64,
};
// Get the view and scale factor for our surface.
const info: Info = switch (apprt.runtime) {
apprt.glfw => info: {
// Everything in glfw is window-oriented so we grab the backing
// window, then derive everything from that.
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(win.window).?);
const contentView = objc.Object.fromId(nswindow.getProperty(?*anyopaque, "contentView").?);
const scaleFactor = nswindow.getProperty(macos.graphics.c.CGFloat, "backingScaleFactor");
break :info .{
.view = contentView,
.scaleFactor = scaleFactor,
};
},
apprt.embedded => .{
.view = win.nsview,
.scaleFactor = win.scale_factor,
},
else => @compileError("unsupported apprt for metal"),
};
const contentView = objc.Object.fromId(nswindow.getProperty(?*anyopaque, "contentView").?);
contentView.setProperty("layer", self.swapchain.value);
contentView.setProperty("wantsLayer", true);
// Make our view layer-backed with our Metal layer
info.view.setProperty("layer", self.swapchain.value);
info.view.setProperty("wantsLayer", true);
// Ensure that our metal layer has a content scale set to match the
// scale factor of the window. This avoids magnification issues leading
// to blurry rendering.
const layer = contentView.getProperty(objc.Object, "layer");
const scaleFactor = nswindow.getProperty(macos.graphics.c.CGFloat, "backingScaleFactor");
layer.setProperty("contentsScale", scaleFactor);
const layer = info.view.getProperty(objc.Object, "layer");
layer.setProperty("contentsScale", info.scaleFactor);
}
/// This is called if this renderer runs DevMode.

View File

@ -106,10 +106,10 @@ pub const Padding = struct {
const padding_bot = space_bot - padding_top;
return .{
.top = padding_top,
.bottom = padding_bot,
.right = padding_right,
.left = padding_left,
.top = @max(0, padding_top),
.bottom = @max(0, padding_bot),
.right = @max(0, padding_right),
.left = @max(0, padding_left),
};
}
@ -124,6 +124,17 @@ pub const Padding = struct {
}
};
test "Padding balanced on zero" {
// On some systems, our screen can be zero-sized for a bit, and we
// don't want to end up with negative padding.
const testing = std.testing;
const grid: GridSize = .{ .columns = 100, .rows = 37 };
const cell: CellSize = .{ .width = 10, .height = 20 };
const screen: ScreenSize = .{ .width = 0, .height = 0 };
const padding = Padding.balanced(screen, grid, cell);
try testing.expectEqual(padding, .{});
}
test "GridSize update exact" {
const testing = std.testing;