mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
221 lines
6.5 KiB
Zig
221 lines
6.5 KiB
Zig
const TextRenderer = @This();
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const ftc = @import("freetype/c.zig");
|
|
const gl = @import("opengl.zig");
|
|
const gb = @import("gb_math.zig");
|
|
const Atlas = @import("Atlas.zig");
|
|
const FontAtlas = @import("FontAtlas.zig");
|
|
|
|
const log = std.log.scoped(.text_renderer);
|
|
|
|
alloc: std.mem.Allocator,
|
|
projection: gb.gbMat4 = undefined,
|
|
|
|
font: FontAtlas,
|
|
atlas: Atlas,
|
|
|
|
program: gl.Program,
|
|
tex: gl.Texture,
|
|
|
|
const CharList = std.ArrayListUnmanaged(Char);
|
|
const Char = struct {
|
|
tex: gl.Texture,
|
|
size: @Vector(2, f32),
|
|
bearing: @Vector(2, f32),
|
|
advance: c_uint,
|
|
};
|
|
|
|
pub fn init(alloc: std.mem.Allocator) !TextRenderer {
|
|
var atlas = try Atlas.init(alloc, 512);
|
|
errdefer atlas.deinit(alloc);
|
|
var font = try FontAtlas.init(atlas);
|
|
errdefer font.deinit(alloc);
|
|
try font.loadFaceFromMemory(face_ttf, 30);
|
|
|
|
// We'll calculate the cell width as the widest glyph advance
|
|
// in the set of visible ASCII characters.
|
|
var cell_width: f32 = 0;
|
|
|
|
// Load all visible ASCII characters.
|
|
var i: u8 = 32;
|
|
while (i <= 126) : (i += 1) {
|
|
const glyph = try font.addGlyph(alloc, i);
|
|
if (glyph.advance_x > cell_width) {
|
|
cell_width = @ceil(glyph.advance_x);
|
|
}
|
|
}
|
|
|
|
// The cell height is the vertical height required to render underscore
|
|
// '_' which should live at the bottom of a cell.
|
|
const cell_height: f32 = cell_height: {
|
|
// TODO(render): kitty does a calculation based on other font
|
|
// metrics that we probably want to research more. For now, this is
|
|
// fine.
|
|
assert(font.ft_face != null);
|
|
const glyph = font.getGlyph('_').?;
|
|
var res: i32 = font.ft_face.*.ascender >> 6;
|
|
res -= glyph.offset_y;
|
|
res += @intCast(i32, glyph.height);
|
|
break :cell_height @intToFloat(f32, res);
|
|
};
|
|
|
|
log.debug("cell size w={d} h={d}", .{ cell_width, cell_height });
|
|
|
|
// Build our texture
|
|
const tex = try gl.Texture.create();
|
|
errdefer tex.destroy();
|
|
const binding = try tex.bind(.@"2D");
|
|
try binding.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE);
|
|
try binding.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE);
|
|
try binding.parameter(.MinFilter, gl.c.GL_LINEAR);
|
|
try binding.parameter(.MagFilter, gl.c.GL_LINEAR);
|
|
try binding.image2D(
|
|
0,
|
|
.Red,
|
|
@intCast(c_int, atlas.size),
|
|
@intCast(c_int, atlas.size),
|
|
0,
|
|
.Red,
|
|
.UnsignedByte,
|
|
atlas.data.ptr,
|
|
);
|
|
|
|
// Create our shader
|
|
const program = try gl.Program.createVF(
|
|
@embedFile("../shaders/text-atlas.v.glsl"),
|
|
@embedFile("../shaders/text-atlas.f.glsl"),
|
|
);
|
|
|
|
var res = TextRenderer{
|
|
.alloc = alloc,
|
|
.font = font,
|
|
.atlas = atlas,
|
|
.program = program,
|
|
.tex = tex,
|
|
};
|
|
|
|
// Update the initialize size so we have some projection. We
|
|
// expect this will get updated almost immediately.
|
|
try res.setScreenSize(3000, 1666);
|
|
//try res.setScreenSize(1432, 874);
|
|
|
|
return res;
|
|
}
|
|
|
|
pub fn deinit(self: *TextRenderer, alloc: std.mem.Allocator) void {
|
|
self.font.deinit(alloc);
|
|
self.atlas.deinit(alloc);
|
|
self.* = undefined;
|
|
}
|
|
|
|
pub fn setScreenSize(self: *TextRenderer, w: i32, h: i32) !void {
|
|
gb.gb_mat4_ortho2d(
|
|
&self.projection,
|
|
0,
|
|
@intToFloat(f32, w),
|
|
0,
|
|
@intToFloat(f32, h),
|
|
);
|
|
|
|
const bind = try self.program.use();
|
|
defer bind.unbind();
|
|
try self.program.setUniform("projection", self.projection);
|
|
}
|
|
|
|
pub fn render(
|
|
self: TextRenderer,
|
|
text: []const u8,
|
|
x: f32,
|
|
y: f32,
|
|
color: @Vector(3, f32),
|
|
) !void {
|
|
const r = color[0];
|
|
const g = color[1];
|
|
const b = color[2];
|
|
const a: f32 = 1.0;
|
|
|
|
var vertices: std.ArrayListUnmanaged([4][9]f32) = .{};
|
|
try vertices.ensureUnusedCapacity(self.alloc, text.len);
|
|
defer vertices.deinit(self.alloc);
|
|
|
|
var indices: std.ArrayListUnmanaged([6]u32) = .{};
|
|
try indices.ensureUnusedCapacity(self.alloc, text.len);
|
|
defer indices.deinit(self.alloc);
|
|
|
|
var curx: f32 = x;
|
|
for (text) |c, i| {
|
|
if (self.font.getGlyph(c)) |glyph_ptr| {
|
|
const glyph = glyph_ptr.*;
|
|
const kerning = 0; // for now
|
|
curx += kerning;
|
|
|
|
const x0 = curx + @intToFloat(f32, glyph.offset_x);
|
|
const y0 = y + @intToFloat(f32, glyph.offset_y);
|
|
const x1 = x0 + @intToFloat(f32, glyph.width);
|
|
const y1 = y0 - @intToFloat(f32, glyph.height);
|
|
const s0 = glyph.s0;
|
|
const t0 = glyph.t0;
|
|
const s1 = glyph.s1;
|
|
const t1 = glyph.t1;
|
|
|
|
//std.log.info("CHAR ch={} x0={} y0={} x1={} y1={}", .{ c, x0, y0, x1, y1 });
|
|
|
|
const vert = [4][9]f32{
|
|
.{ x0, y0, 0, s0, t0, r, g, b, a },
|
|
.{ x0, y1, 0, s0, t1, r, g, b, a },
|
|
.{ x1, y1, 0, s1, t1, r, g, b, a },
|
|
.{ x1, y0, 0, s1, t0, r, g, b, a },
|
|
};
|
|
|
|
vertices.appendAssumeCapacity(vert);
|
|
|
|
const idx = @intCast(u32, 4 * i);
|
|
indices.appendAssumeCapacity([6]u32{
|
|
idx, idx + 1, idx + 2, // 0, 1, 2
|
|
idx, idx + 2, idx + 3, // 0, 2, 3
|
|
});
|
|
|
|
curx += glyph.advance_x;
|
|
}
|
|
}
|
|
|
|
const pbind = try self.program.use();
|
|
defer pbind.unbind();
|
|
|
|
// Bind our texture and set our data
|
|
try gl.Texture.active(gl.c.GL_TEXTURE0);
|
|
var texbind = try self.tex.bind(.@"2D");
|
|
defer texbind.unbind();
|
|
|
|
// Configure VAO/VBO for glyph rendering
|
|
const vao = try gl.VertexArray.create();
|
|
defer vao.destroy();
|
|
try vao.bind();
|
|
|
|
// Array buffer
|
|
const vbo = try gl.Buffer.create();
|
|
defer vbo.destroy();
|
|
var binding = try vbo.bind(.ArrayBuffer);
|
|
defer binding.unbind();
|
|
try binding.setData(vertices.items, .DynamicDraw);
|
|
try binding.attribute(0, 3, [9]f32, 0);
|
|
try binding.attribute(1, 2, [9]f32, 3);
|
|
try binding.attribute(2, 4, [9]f32, 5);
|
|
|
|
// Element buffer
|
|
const ebo = try gl.Buffer.create();
|
|
defer ebo.destroy();
|
|
var ebobinding = try ebo.bind(.ElementArrayBuffer);
|
|
defer ebobinding.unbind();
|
|
try ebobinding.setData(indices.items, .DynamicDraw);
|
|
|
|
//try gl.drawArrays(gl.c.GL_TRIANGLES, 0, @intCast(c_int, vertices.items.len * 6));
|
|
try gl.drawElements(gl.c.GL_TRIANGLES, @intCast(c_int, indices.items.len * 6), gl.c.GL_UNSIGNED_INT, 0);
|
|
try gl.VertexArray.unbind();
|
|
}
|
|
|
|
const face_ttf = @embedFile("../fonts/FiraCode-Regular.ttf");
|
|
//const face_ttf = @embedFile("../fonts/Inconsolata-Regular.ttf");
|