diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 83cf4a5c6..45d8f84c2 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -148,6 +148,9 @@ layer: objc.Object, // CAMetalLayer /// a display link. display_link: ?DisplayLink = null, +/// The `CGColorSpace` that represents our current terminal color space +terminal_colorspace: *graphics.ColorSpace, + /// Custom shader state. This is only set if we have custom shaders. custom_shader_state: ?CustomShaderState = null, @@ -569,9 +572,20 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal { // color space using converted colors, which reduces, // but does not fully eliminate blending artifacts. const colorspace = try graphics.ColorSpace.createNamed(.displayP3); - errdefer colorspace.release(); + defer colorspace.release(); layer.setProperty("colorspace", colorspace); + // Create a colorspace the represents our terminal colors + // this will allow us to create e.g. `CGColor`s for things + // like the current background color. + const terminal_colorspace = try graphics.ColorSpace.createNamed( + switch (options.config.colorspace) { + .@"display-p3" => .displayP3, + .srgb => .sRGB, + }, + ); + errdefer terminal_colorspace.release(); + // Make our view layer-backed with our Metal layer. On iOS views are // always layer backed so we don't need to do this. But on iOS the // caller MUST be sure to set the layerClass to CAMetalLayer. @@ -667,6 +681,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal { // Metal stuff .layer = layer, .display_link = display_link, + .terminal_colorspace = terminal_colorspace, .custom_shader_state = null, .gpu_state = gpu_state, }; @@ -690,6 +705,8 @@ pub fn deinit(self: *Metal) void { } } + self.terminal_colorspace.release(); + self.cells.deinit(self.alloc); self.font_shaper.deinit(); @@ -1170,6 +1187,32 @@ pub fn updateFrame( @intFromFloat(@round(self.config.background_opacity * 255.0)), }; + // Update the background color on our layer + // + // TODO: Is this expensive? Should we be checking if our + // bg color has changed first before doing this work? + { + const color = graphics.c.CGColorCreate( + @ptrCast(self.terminal_colorspace), + &[4]f64{ + @as(f64, @floatFromInt(critical.bg.r)) / 255.0, + @as(f64, @floatFromInt(critical.bg.g)) / 255.0, + @as(f64, @floatFromInt(critical.bg.b)) / 255.0, + self.config.background_opacity, + }, + ); + defer graphics.c.CGColorRelease(color); + + // We use a CATransaction so that Core Animation knows that we + // updated the background color property. Otherwise it behaves + // weird, not updating the color until we resize. + const CATransaction = objc.getClass("CATransaction").?; + CATransaction.msgSend(void, "begin", .{}); + defer CATransaction.msgSend(void, "commit", .{}); + + self.layer.setProperty("backgroundColor", color); + } + // Go through our images and see if we need to setup any textures. { var image_it = self.images.iterator(); @@ -2077,6 +2120,32 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void { self.default_cursor_color = if (!config.cursor_invert) config.cursor_color else null; self.cursor_invert = config.cursor_invert; + // Update our layer's opaqueness and display sync in case they changed. + { + // We use a CATransaction so that Core Animation knows that we + // updated the opaque property. Otherwise it behaves weird, not + // properly going from opaque to transparent unless we resize. + const CATransaction = objc.getClass("CATransaction").?; + CATransaction.msgSend(void, "begin", .{}); + defer CATransaction.msgSend(void, "commit", .{}); + + self.layer.setProperty("opaque", config.background_opacity >= 1); + self.layer.setProperty("displaySyncEnabled", config.vsync); + } + + // Update our terminal colorspace if it changed + if (self.config.colorspace != config.colorspace) { + const terminal_colorspace = try graphics.ColorSpace.createNamed( + switch (config.colorspace) { + .@"display-p3" => .displayP3, + .srgb => .sRGB, + }, + ); + errdefer terminal_colorspace.release(); + self.terminal_colorspace.release(); + self.terminal_colorspace = terminal_colorspace; + } + const old_blending = self.config.blending; const old_custom_shaders = self.config.custom_shaders; diff --git a/src/renderer/shaders/cell.metal b/src/renderer/shaders/cell.metal index 58dd13755..a4e4837b6 100644 --- a/src/renderer/shaders/cell.metal +++ b/src/renderer/shaders/cell.metal @@ -280,7 +280,24 @@ fragment float4 cell_bg_fragment( } } - // We load the color for the cell, converting it appropriately, and return it. + // Load the color for the cell. + uchar4 cell_color = cells[grid_pos.y * uniforms.grid_size.x + grid_pos.x]; + + // We have special case handling for when the cell color matches the bg color. + if (all(cell_color == uniforms.bg_color)) { + // If we have any background transparency then we render bg-colored cells as + // fully transparent, since the background is handled by the layer bg color + // and we don't want to double up our bg color, but if our bg color is fully + // opaque then our layer is opaque and can't handle transparency, so we need + // to return the bg color directly instead. + if (uniforms.bg_color.a == 255) { + return bg; + } else { + return float4(0.0); + } + } + + // Convert the color and return it. // // TODO: We may want to blend the color with the background // color, rather than purely replacing it, this needs @@ -292,7 +309,7 @@ fragment float4 cell_bg_fragment( // fragment of each cell. It's not the most epxensive // operation, but it is still wasted work. return load_color( - cells[grid_pos.y * uniforms.grid_size.x + grid_pos.x], + cell_color, uniforms.use_display_p3, uniforms.use_linear_blending );