renderer: fix color glyph rendering under OpenGL

Also changes color atlas to always use an sRGB internal format so that
the texture reads automatically linearize the colors.

Renames the misleading `rgba` atlas format to `bgra`, since both
FreeType and CoreText are set up to draw color glyphs in bgra.
This commit is contained in:
Qwerasd
2025-06-23 17:31:49 -06:00
parent f5439c860a
commit 41ae32814f
8 changed files with 25 additions and 22 deletions

View File

@@ -50,15 +50,18 @@ modified: std.atomic.Value(usize) = .{ .raw = 0 },
resized: std.atomic.Value(usize) = .{ .raw = 0 }, resized: std.atomic.Value(usize) = .{ .raw = 0 },
pub const Format = enum(u8) { pub const Format = enum(u8) {
/// 1 byte per pixel grayscale.
grayscale = 0, grayscale = 0,
rgb = 1, /// 3 bytes per pixel BGR.
rgba = 2, bgr = 1,
/// 4 bytes per pixel BGRA.
bgra = 2,
pub fn depth(self: Format) u8 { pub fn depth(self: Format) u8 {
return switch (self) { return switch (self) {
.grayscale => 1, .grayscale => 1,
.rgb => 3, .bgr => 3,
.rgba => 4, .bgra => 4,
}; };
} }
}; };

View File

@@ -79,7 +79,7 @@ pub fn init(
var atlas_grayscale = try Atlas.init(alloc, 512, .grayscale); var atlas_grayscale = try Atlas.init(alloc, 512, .grayscale);
errdefer atlas_grayscale.deinit(alloc); errdefer atlas_grayscale.deinit(alloc);
var atlas_color = try Atlas.init(alloc, 512, .rgba); var atlas_color = try Atlas.init(alloc, 512, .bgra);
errdefer atlas_color.deinit(alloc); errdefer atlas_color.deinit(alloc);
var result: SharedGrid = .{ var result: SharedGrid = .{

View File

@@ -391,7 +391,7 @@ pub const Face = struct {
const format: ?font.Atlas.Format = switch (bitmap_ft.pixel_mode) { const format: ?font.Atlas.Format = switch (bitmap_ft.pixel_mode) {
freetype.c.FT_PIXEL_MODE_MONO => null, freetype.c.FT_PIXEL_MODE_MONO => null,
freetype.c.FT_PIXEL_MODE_GRAY => .grayscale, freetype.c.FT_PIXEL_MODE_GRAY => .grayscale,
freetype.c.FT_PIXEL_MODE_BGRA => .rgba, freetype.c.FT_PIXEL_MODE_BGRA => .bgra,
else => { else => {
log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap_ft.pixel_mode }); log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap_ft.pixel_mode });
@panic("unsupported pixel mode"); @panic("unsupported pixel mode");

View File

@@ -347,7 +347,7 @@ pub fn initAtlasTexture(
) Texture.Error!Texture { ) Texture.Error!Texture {
const pixel_format: mtl.MTLPixelFormat = switch (atlas.format) { const pixel_format: mtl.MTLPixelFormat = switch (atlas.format) {
.grayscale => .r8unorm, .grayscale => .r8unorm,
.rgba => .bgra8unorm, .bgra => .bgra8unorm_srgb,
else => @panic("unsupported atlas format for Metal texture"), else => @panic("unsupported atlas format for Metal texture"),
}; };

View File

@@ -440,7 +440,7 @@ pub fn initAtlasTexture(
const format: gl.Texture.Format, const internal_format: gl.Texture.InternalFormat = const format: gl.Texture.Format, const internal_format: gl.Texture.InternalFormat =
switch (atlas.format) { switch (atlas.format) {
.grayscale => .{ .red, .red }, .grayscale => .{ .red, .red },
.rgba => .{ .rgba, .srgba }, .bgra => .{ .bgra, .srgba },
else => @panic("unsupported atlas format for OpenGL texture"), else => @panic("unsupported atlas format for OpenGL texture"),
}; };

View File

@@ -336,7 +336,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
const color = try api.initAtlasTexture(&.{ const color = try api.initAtlasTexture(&.{
.data = undefined, .data = undefined,
.size = 1, .size = 1,
.format = .rgba, .format = .bgra,
}); });
errdefer color.deinit(); errdefer color.deinit();

View File

@@ -87,19 +87,19 @@ void main() {
case MODE_TEXT_COLOR: case MODE_TEXT_COLOR:
{ {
// For now, we assume that color glyphs // For now, we assume that color glyphs
// are already premultiplied sRGB colors. // are already premultiplied linear colors.
vec4 color = texture(atlas_color, in_data.tex_coord); vec4 color = texture(atlas_color, in_data.tex_coord);
// If we aren't doing linear blending, we can return this right away. // If we are doing linear blending, we can return this right away.
if (!use_linear_blending) { if (use_linear_blending) {
out_FragColor = color; out_FragColor = color;
return; return;
} }
// Otherwise we need to linearize the color. Since the alpha is // Otherwise we need to unlinearize the color. Since the alpha is
// premultiplied, we need to divide it out before linearizing. // premultiplied, we need to divide it out before unlinearizing.
color.rgb /= vec3(color.a); color.rgb /= vec3(color.a);
color = linearize(color); color = unlinearize(color);
color.rgb *= vec3(color.a); color.rgb *= vec3(color.a);
out_FragColor = color; out_FragColor = color;

View File

@@ -553,19 +553,19 @@ fragment float4 cell_text_fragment(
} }
case MODE_TEXT_COLOR: { case MODE_TEXT_COLOR: {
// For now, we assume that color glyphs are // For now, we assume that color glyphs
// already premultiplied Display P3 colors. // are already premultiplied linear colors.
float4 color = textureColor.sample(textureSampler, in.tex_coord); float4 color = textureColor.sample(textureSampler, in.tex_coord);
// If we aren't doing linear blending, we can return this right away. // If we're doing linear blending, we can return this right away.
if (!uniforms.use_linear_blending) { if (uniforms.use_linear_blending) {
return color; return color;
} }
// Otherwise we need to linearize the color. Since the alpha is // Otherwise we need to unlinearize the color. Since the alpha is
// premultiplied, we need to divide it out before linearizing. // premultiplied, we need to divide it out before unlinearizing.
color.rgb /= color.a; color.rgb /= color.a;
color = linearize(color); color = unlinearize(color);
color.rgb *= color.a; color.rgb *= color.a;
return color; return color;