diff --git a/build.zig b/build.zig index 4c77fb92a..424bf08f6 100644 --- a/build.zig +++ b/build.zig @@ -230,9 +230,12 @@ fn addDeps( try glfw.link(b, step, glfw_opts); // Imgui, we have to do this later since we need some information - const imgui_backends = [_][]const u8{ "glfw", "opengl3" }; + const imgui_backends = if (step.target.isDarwin()) + &[_][]const u8{ "glfw", "opengl3", "metal" } + else + &[_][]const u8{ "glfw", "opengl3" }; var imgui_opts: imgui.Options = .{ - .backends = &imgui_backends, + .backends = imgui_backends, .freetype = .{ .enabled = true }, }; diff --git a/pkg/imgui/build.zig b/pkg/imgui/build.zig index ddb0f4fb0..c67fd70d5 100644 --- a/pkg/imgui/build.zig +++ b/pkg/imgui/build.zig @@ -101,11 +101,17 @@ pub fn buildImgui( lib.addCSourceFiles(srcs, flags.items); if (opt.backends) |backends| { for (backends) |backend| { + const ext = if (std.mem.eql(u8, "metal", backend)) ext: { + // Metal requires some extra frameworks + step.linkFramework("QuartzCore"); + break :ext "mm"; + } else "cpp"; + var buf: [4096]u8 = undefined; const path = try std.fmt.bufPrint( &buf, - "{s}imgui/backends/imgui_impl_{s}.cpp", - .{ root, backend }, + "{s}imgui/backends/imgui_impl_{s}.{s}", + .{ root, backend, ext }, ); lib.addCSourceFile(path, flags.items); diff --git a/pkg/imgui/impl_glfw.zig b/pkg/imgui/impl_glfw.zig index cb27b86fb..d8dd00a37 100644 --- a/pkg/imgui/impl_glfw.zig +++ b/pkg/imgui/impl_glfw.zig @@ -13,6 +13,10 @@ pub const ImplGlfw = struct { return ImGui_ImplGlfw_InitForOpenGL(win, install_callbacks); } + pub fn initForOther(win: *GLFWWindow, install_callbacks: bool) bool { + return ImGui_ImplGlfw_InitForOther(win, install_callbacks); + } + pub fn shutdown() void { return ImGui_ImplGlfw_Shutdown(); } @@ -23,6 +27,7 @@ pub const ImplGlfw = struct { extern "c" fn glfwGetError(?*const anyopaque) c_int; extern "c" fn ImGui_ImplGlfw_InitForOpenGL(*GLFWWindow, bool) bool; + extern "c" fn ImGui_ImplGlfw_InitForOther(*GLFWWindow, bool) bool; extern "c" fn ImGui_ImplGlfw_Shutdown() void; extern "c" fn ImGui_ImplGlfw_NewFrame() void; }; diff --git a/pkg/imgui/impl_metal.zig b/pkg/imgui/impl_metal.zig new file mode 100644 index 000000000..3b228f611 --- /dev/null +++ b/pkg/imgui/impl_metal.zig @@ -0,0 +1,31 @@ +const std = @import("std"); +const c = @import("c.zig"); +const imgui = @import("main.zig"); +const Allocator = std.mem.Allocator; + +pub const ImplMetal = struct { + pub fn init(device: ?*anyopaque) bool { + return ImGui_ImplMetal_Init(device); + } + + pub fn shutdown() void { + return ImGui_ImplMetal_Shutdown(); + } + + pub fn newFrame(render_pass_desc: ?*anyopaque) void { + return ImGui_ImplMetal_NewFrame(render_pass_desc); + } + + pub fn renderDrawData( + data: *imgui.DrawData, + command_buffer: ?*anyopaque, + command_encoder: ?*anyopaque, + ) void { + ImGui_ImplMetal_RenderDrawData(data, command_buffer, command_encoder); + } + + extern "c" fn ImGui_ImplMetal_Init(?*anyopaque) bool; + extern "c" fn ImGui_ImplMetal_Shutdown() void; + extern "c" fn ImGui_ImplMetal_NewFrame(?*anyopaque) void; + extern "c" fn ImGui_ImplMetal_RenderDrawData(*imgui.DrawData, ?*anyopaque, ?*anyopaque) void; +}; diff --git a/pkg/imgui/main.zig b/pkg/imgui/main.zig index 3a071a380..424e015cb 100644 --- a/pkg/imgui/main.zig +++ b/pkg/imgui/main.zig @@ -7,6 +7,7 @@ pub usingnamespace @import("io.zig"); pub usingnamespace @import("style.zig"); pub usingnamespace @import("impl_glfw.zig"); +pub usingnamespace @import("impl_metal.zig"); pub usingnamespace @import("impl_opengl3.zig"); test { diff --git a/src/DevMode.zig b/src/DevMode.zig index 7e1e73842..7698aedcb 100644 --- a/src/DevMode.zig +++ b/src/DevMode.zig @@ -9,6 +9,7 @@ const assert = std.debug.assert; const Atlas = @import("Atlas.zig"); const Window = @import("Window.zig"); +const renderer = @import("renderer.zig"); /// If this is false, the rest of the terminal will be compiled without /// dev mode support at all. @@ -27,9 +28,10 @@ window: ?*Window = null, /// Update the state associated with the dev mode. This should generally /// only be called paired with a render since it otherwise wastes CPU /// cycles. +/// +/// Note: renderers should call their implementation "newFrame" functions +/// prior to this. pub fn update(self: *const DevMode) !void { - imgui.ImplOpenGL3.newFrame(); - imgui.ImplGlfw.newFrame(); imgui.newFrame(); if (imgui.begin("dev mode", null, .{})) { @@ -42,16 +44,27 @@ pub fn update(self: *const DevMode) !void { helpMarker("The number of glyphs loaded and rendered into a " ++ "font atlas currently."); + const Renderer = @TypeOf(window.renderer); if (imgui.treeNode("Atlas: Greyscale", .{ .default_open = true })) { defer imgui.treePop(); const atlas = &window.font_group.atlas_greyscale; - try self.atlasInfo(atlas, @intCast(usize, window.renderer.texture.id)); + const tex = switch (Renderer) { + renderer.OpenGL => @intCast(usize, window.renderer.texture.id), + renderer.Metal => @ptrToInt(window.renderer.texture_greyscale.value), + else => @compileError("renderer unsupported, add it!"), + }; + try self.atlasInfo(atlas, tex); } if (imgui.treeNode("Atlas: Color (Emoji)", .{ .default_open = true })) { defer imgui.treePop(); const atlas = &window.font_group.atlas_color; - try self.atlasInfo(atlas, @intCast(usize, window.renderer.texture_color.id)); + const tex = switch (Renderer) { + renderer.OpenGL => @intCast(usize, window.renderer.texture_color.id), + renderer.Metal => @ptrToInt(window.renderer.texture_color.value), + else => @compileError("renderer unsupported, add it!"), + }; + try self.atlasInfo(atlas, tex); } } } diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index d1eea57c7..63f19ee60 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -13,11 +13,13 @@ const builtin = @import("builtin"); const glfw = @import("glfw"); const objc = @import("objc"); const macos = @import("macos"); +const imgui = @import("imgui"); const Atlas = @import("../Atlas.zig"); const font = @import("../font/main.zig"); const terminal = @import("../terminal/main.zig"); const renderer = @import("../renderer.zig"); const math = @import("../math.zig"); +const DevMode = @import("../DevMode.zig"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Terminal = terminal.Terminal; @@ -240,6 +242,11 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal { } pub fn deinit(self: *Metal) void { + if (DevMode.enabled) { + imgui.ImplMetal.shutdown(); + imgui.ImplGlfw.shutdown(); + } + self.cells.deinit(self.alloc); self.font_shaper.deinit(); @@ -256,6 +263,15 @@ pub fn finalizeInit(self: *const Metal, window: glfw.Window) !void { const contentView = objc.Object.fromId(nswindow.getProperty(?*anyopaque, "contentView").?); contentView.setProperty("layer", self.swapchain.value); contentView.setProperty("wantsLayer", true); + + if (DevMode.enabled) { + // Initialize for our window + assert(imgui.ImplGlfw.initForOther( + @ptrCast(*imgui.ImplGlfw.GLFWWindow, window.handle), + true, + )); + assert(imgui.ImplMetal.init(self.device.value)); + } } /// Callback called by renderer.Thread when it begins. @@ -285,6 +301,7 @@ pub fn render( const Critical = struct { bg: terminal.color.RGB, screen_size: ?renderer.ScreenSize, + devmode: bool, }; // Update all our data as tightly as possible within the mutex. @@ -323,6 +340,7 @@ pub fn render( break :critical .{ .bg = self.background, .screen_size = state.resize_screen, + .devmode = if (state.devmode) |dm| dm.visible else false, }; }; @@ -470,6 +488,27 @@ pub fn render( @as(c_ulong, self.cells.items.len), }, ); + + // Build our devmode draw data. This sucks because it requires we + // lock our state mutex but the metal imgui implementation requires + // access to all this stuff. + if (critical.devmode) { + state.mutex.lock(); + defer state.mutex.unlock(); + + if (state.devmode) |dm| { + if (dm.visible) { + imgui.ImplMetal.newFrame(desc.value); + imgui.ImplGlfw.newFrame(); + try dm.update(); + imgui.ImplMetal.renderDrawData( + try dm.render(), + buffer.value, + encoder.value, + ); + } + } + } } buffer.msgSend(void, objc.sel("presentDrawable:"), .{surface.value}); diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 323406431..ee10e17a0 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -483,6 +483,8 @@ pub fn render( const devmode_data = devmode_data: { if (state.devmode) |dm| { if (dm.visible) { + imgui.ImplOpenGL3.newFrame(); + imgui.ImplGlfw.newFrame(); try dm.update(); break :devmode_data try dm.render(); } diff --git a/src/renderer/cursor.zig b/src/renderer/cursor.zig new file mode 100644 index 000000000..df0ab1bc4 --- /dev/null +++ b/src/renderer/cursor.zig @@ -0,0 +1,19 @@ +const terminal = @import("../terminal/main.zig"); + +/// Available cursor styles for drawing that renderers must support. +pub const CursorStyle = enum { + box, + box_hollow, + bar, + + /// Create a cursor style from the terminal style request. + pub fn fromTerminal(style: terminal.CursorStyle) ?CursorStyle { + return switch (style) { + .blinking_block, .steady_block => .box, + .blinking_bar, .steady_bar => .bar, + .blinking_underline, .steady_underline => null, // TODO + .default => .box, + else => null, + }; + } +};