metal: color textures

This commit is contained in:
Mitchell Hashimoto
2022-10-30 19:55:47 -07:00
parent ee45d363a9
commit 666833f12f
2 changed files with 44 additions and 9 deletions

View File

@ -65,6 +65,7 @@ buf_cells: objc.Object, // MTLBuffer
buf_instance: objc.Object, // MTLBuffer buf_instance: objc.Object, // MTLBuffer
pipeline: objc.Object, // MTLRenderPipelineState pipeline: objc.Object, // MTLRenderPipelineState
texture_greyscale: objc.Object, // MTLTexture texture_greyscale: objc.Object, // MTLTexture
texture_color: objc.Object, // MTLTexture
const GPUCell = extern struct { const GPUCell = extern struct {
mode: GPUCellMode, 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 library = try initLibrary(device, @embedFile("../shaders/cell.metal"));
const pipeline_state = try initPipelineState(device, library); const pipeline_state = try initPipelineState(device, library);
const texture_greyscale = try initAtlasTexture(device, &font_group.atlas_greyscale); const texture_greyscale = try initAtlasTexture(device, &font_group.atlas_greyscale);
const texture_color = try initAtlasTexture(device, &font_group.atlas_color);
return Metal{ return Metal{
.alloc = alloc, .alloc = alloc,
@ -233,6 +235,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
.buf_instance = buf_instance, .buf_instance = buf_instance,
.pipeline = pipeline_state, .pipeline = pipeline_state,
.texture_greyscale = texture_greyscale, .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); try syncAtlasTexture(&self.font_group.atlas_greyscale, &self.texture_greyscale);
self.font_group.atlas_greyscale.modified = false; 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 // MTLRenderPassDescriptor
const desc = desc: { const desc = desc: {
@ -442,6 +449,14 @@ pub fn render(
@as(c_ulong, 0), @as(c_ulong, 0),
}, },
); );
encoder.msgSend(
void,
objc.sel("setFragmentTexture:atIndex:"),
.{
self.texture_color.value,
@as(c_ulong, 1),
},
);
encoder.msgSend( encoder.msgSend(
void, void,
@ -621,8 +636,6 @@ pub fn updateCell(
// If the cell has a character, draw it // If the cell has a character, draw it
if (cell.char > 0) { if (cell.char > 0) {
// Render // Render
const face = try self.font_group.group.faceFromIndex(shaper_run.font_index);
_ = face;
const glyph = try self.font_group.renderGlyph( const glyph = try self.font_group.renderGlyph(
self.alloc, self.alloc,
shaper_run.font_index, shaper_run.font_index,
@ -630,16 +643,18 @@ pub fn updateCell(
@floatToInt(u16, @ceil(self.cell_size.height)), @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(.{ self.cells.appendAssumeCapacity(.{
.mode = .fg, .mode = mode,
.grid_pos = .{ @intToFloat(f32, x), @intToFloat(f32, y) }, .grid_pos = .{ @intToFloat(f32, x), @intToFloat(f32, y) },
.cell_width = cell.widthLegacy(), .cell_width = cell.widthLegacy(),
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha }, .color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
.glyph_size = .{ glyph.width, glyph.height }, .glyph_size = .{ glyph.width, glyph.height },
.glyph_offset = .{ glyph.offset_x, glyph.offset_y }, .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 // Determine our pixel format
const pixel_format: MTLPixelFormat = switch (atlas.format) { const pixel_format: MTLPixelFormat = switch (atlas.format) {
.greyscale => .r8unorm, .greyscale => .r8unorm,
.rgba => .bgra8unorm,
else => @panic("unsupported atlas format for Metal texture"), else => @panic("unsupported atlas format for Metal texture"),
}; };

View File

@ -4,6 +4,7 @@ using namespace metal;
enum Mode : uint8_t { enum Mode : uint8_t {
MODE_BG = 1u, MODE_BG = 1u,
MODE_FG = 2u, MODE_FG = 2u,
MODE_FG_COLOR = 7u,
MODE_CURSOR_RECT = 3u, MODE_CURSOR_RECT = 3u,
MODE_CURSOR_RECT_HOLLOW = 4u, MODE_CURSOR_RECT_HOLLOW = 4u,
MODE_CURSOR_BAR = 5u, 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); out.position = uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
break; break;
case MODE_FG: { case MODE_FG:
case MODE_FG_COLOR: {
float2 glyph_size = float2(input.glyph_size) * uniforms.px_scale; float2 glyph_size = float2(input.glyph_size) * uniforms.px_scale;
float2 glyph_offset = float2(input.glyph_offset) * 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 // 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. // 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 // Calculate the final position of the cell which uses our glyph size
// and glyph offset to create the correct bounding box for the glyph. // 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); 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 // Calculate the texture coordinate in pixels. This is NOT normalized
@ -181,7 +192,8 @@ vertex VertexOut uber_vertex(
fragment float4 uber_fragment( fragment float4 uber_fragment(
VertexOut in [[ stage_in ]], VertexOut in [[ stage_in ]],
texture2d<float> textureGreyscale [[ texture(0) ]] texture2d<float> textureGreyscale [[ texture(0) ]],
texture2d<float> textureColor [[ texture(1) ]]
) { ) {
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear); 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); 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: case MODE_CURSOR_RECT:
return in.color; return in.color;