diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 5c73a5fb4..d1eea57c7 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -65,6 +65,7 @@ buf_cells: objc.Object, // MTLBuffer buf_instance: objc.Object, // MTLBuffer pipeline: objc.Object, // MTLRenderPipelineState texture_greyscale: objc.Object, // MTLTexture +texture_color: objc.Object, // MTLTexture const GPUCell = extern struct { mode: GPUCellMode, @@ -200,6 +201,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal { const library = try initLibrary(device, @embedFile("../shaders/cell.metal")); const pipeline_state = try initPipelineState(device, library); const texture_greyscale = try initAtlasTexture(device, &font_group.atlas_greyscale); + const texture_color = try initAtlasTexture(device, &font_group.atlas_color); return Metal{ .alloc = alloc, @@ -233,6 +235,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal { .buf_instance = buf_instance, .pipeline = pipeline_state, .texture_greyscale = texture_greyscale, + .texture_color = texture_color, }; } @@ -368,6 +371,10 @@ pub fn render( try syncAtlasTexture(&self.font_group.atlas_greyscale, &self.texture_greyscale); self.font_group.atlas_greyscale.modified = false; } + if (self.font_group.atlas_color.modified) { + try syncAtlasTexture(&self.font_group.atlas_color, &self.texture_color); + self.font_group.atlas_color.modified = false; + } // MTLRenderPassDescriptor const desc = desc: { @@ -442,6 +449,14 @@ pub fn render( @as(c_ulong, 0), }, ); + encoder.msgSend( + void, + objc.sel("setFragmentTexture:atIndex:"), + .{ + self.texture_color.value, + @as(c_ulong, 1), + }, + ); encoder.msgSend( void, @@ -621,8 +636,6 @@ pub fn updateCell( // If the cell has a character, draw it if (cell.char > 0) { // Render - const face = try self.font_group.group.faceFromIndex(shaper_run.font_index); - _ = face; const glyph = try self.font_group.renderGlyph( self.alloc, shaper_run.font_index, @@ -630,16 +643,18 @@ pub fn updateCell( @floatToInt(u16, @ceil(self.cell_size.height)), ); + // If we're rendering a color font, we use the color atlas + const face = try self.font_group.group.faceFromIndex(shaper_run.font_index); + const mode: GPUCellMode = if (face.presentation == .emoji) .fg_color else .fg; + self.cells.appendAssumeCapacity(.{ - .mode = .fg, + .mode = mode, .grid_pos = .{ @intToFloat(f32, x), @intToFloat(f32, y) }, .cell_width = cell.widthLegacy(), .color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha }, .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, .glyph_size = .{ glyph.width, glyph.height }, .glyph_offset = .{ glyph.offset_x, glyph.offset_y }, - - // .mode = mode, }); } @@ -946,6 +961,7 @@ fn initAtlasTexture(device: objc.Object, atlas: *const Atlas) !objc.Object { // Determine our pixel format const pixel_format: MTLPixelFormat = switch (atlas.format) { .greyscale => .r8unorm, + .rgba => .bgra8unorm, else => @panic("unsupported atlas format for Metal texture"), }; diff --git a/src/shaders/cell.metal b/src/shaders/cell.metal index 1fc4ff9e1..72bb29a6c 100644 --- a/src/shaders/cell.metal +++ b/src/shaders/cell.metal @@ -4,6 +4,7 @@ using namespace metal; enum Mode : uint8_t { MODE_BG = 1u, MODE_FG = 2u, + MODE_FG_COLOR = 7u, MODE_CURSOR_RECT = 3u, MODE_CURSOR_RECT_HOLLOW = 4u, MODE_CURSOR_BAR = 5u, @@ -92,11 +93,21 @@ vertex VertexOut uber_vertex( out.position = uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f); break; - case MODE_FG: { + case MODE_FG: + case MODE_FG_COLOR: { float2 glyph_size = float2(input.glyph_size) * uniforms.px_scale; float2 glyph_offset = float2(input.glyph_offset) * uniforms.px_scale; - // TODO: downsampling + // If the glyph is larger than our cell, we need to downsample it. + // The "+ 3" here is to give some wiggle room for fonts that are + // BARELY over it. + float2 glyph_size_downsampled = glyph_size; + if (glyph_size_downsampled.y > cell_size.y + 2) { + // Magic 0.9 and 1.1 are padding to make emoji look better + glyph_size_downsampled.y = cell_size.y * 0.9; + glyph_size_downsampled.x = glyph_size.x * (glyph_size_downsampled.y / glyph_size.y); + glyph_offset.y = glyph_offset.y * 1.1 * (glyph_size_downsampled.y / glyph_size.y); + } // The glyph_offset.y is the y bearing, a y value that when added // to the baseline is the offset (+y is up). Our grid goes down. @@ -105,7 +116,7 @@ vertex VertexOut uber_vertex( // Calculate the final position of the cell which uses our glyph size // and glyph offset to create the correct bounding box for the glyph. - cell_pos = cell_pos + glyph_size * position + glyph_offset; + cell_pos = cell_pos + glyph_size_downsampled * position + glyph_offset; out.position = uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f); // Calculate the texture coordinate in pixels. This is NOT normalized @@ -181,7 +192,8 @@ vertex VertexOut uber_vertex( fragment float4 uber_fragment( VertexOut in [[ stage_in ]], - texture2d textureGreyscale [[ texture(0) ]] + texture2d textureGreyscale [[ texture(0) ]], + texture2d textureColor [[ texture(1) ]] ) { constexpr sampler textureSampler(address::clamp_to_edge, filter::linear); @@ -198,6 +210,13 @@ fragment float4 uber_fragment( return float4(in.color.rgb, in.color.a * a); } + case MODE_FG_COLOR: { + // Normalize the texture coordinates to [0,1] + float2 size = float2(textureColor.get_width(), textureColor.get_height()); + float2 coord = in.tex_coord / size; + return textureColor.sample(textureSampler, coord); + } + case MODE_CURSOR_RECT: return in.color;