mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
grid render a few cells
This commit is contained in:
8
shaders/cell.f.glsl
Normal file
8
shaders/cell.f.glsl
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#version 330 core
|
||||||
|
|
||||||
|
/// The background color for this cell.
|
||||||
|
flat in vec4 bg_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = bg_color;
|
||||||
|
}
|
31
shaders/cell.v.glsl
Normal file
31
shaders/cell.v.glsl
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#version 330 core
|
||||||
|
|
||||||
|
// The grid coordinates (x, y) where x < columns and y < rows
|
||||||
|
layout (location = 0) in vec2 grid_coord;
|
||||||
|
|
||||||
|
// The background color for this cell in RGBA (0 to 1.0)
|
||||||
|
layout (location = 1) in vec4 bg_color_in;
|
||||||
|
|
||||||
|
// The background color for this cell in RGBA (0 to 1.0)
|
||||||
|
flat out vec4 bg_color;
|
||||||
|
|
||||||
|
uniform vec2 cell_dims;
|
||||||
|
uniform mat4 projection;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Top-left cell coordinates converted to world space
|
||||||
|
vec2 cell_pos = cell_dims * grid_coord;
|
||||||
|
|
||||||
|
// Turn the cell position into a vertex point depending on the
|
||||||
|
// gl_VertexID. Since we use instanced drawing, we have 4 vertices
|
||||||
|
// for each corner of the cell. We can use gl_VertexID to determine
|
||||||
|
// which one we're looking at. Using this, we can use 1 or 0 to keep
|
||||||
|
// or discard the value for the vertex.
|
||||||
|
vec2 position;
|
||||||
|
position.x = (gl_VertexID == 0 || gl_VertexID == 1) ? 1. : 0.;
|
||||||
|
position.y = (gl_VertexID == 0 || gl_VertexID == 3) ? 0. : 1.;
|
||||||
|
cell_pos = cell_pos + cell_dims * position;
|
||||||
|
|
||||||
|
gl_Position = projection * vec4(cell_pos, 1.0, 1.0);
|
||||||
|
bg_color = bg_color_in;
|
||||||
|
}
|
18
src/App.zig
18
src/App.zig
@ -8,6 +8,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
const glfw = @import("glfw");
|
const glfw = @import("glfw");
|
||||||
const gl = @import("opengl.zig");
|
const gl = @import("opengl.zig");
|
||||||
const TextRenderer = @import("TextRenderer.zig");
|
const TextRenderer = @import("TextRenderer.zig");
|
||||||
|
const Grid = @import("Grid.zig");
|
||||||
|
|
||||||
const log = std.log;
|
const log = std.log;
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ alloc: Allocator,
|
|||||||
window: glfw.Window,
|
window: glfw.Window,
|
||||||
|
|
||||||
text: TextRenderer,
|
text: TextRenderer,
|
||||||
|
grid: Grid,
|
||||||
|
|
||||||
/// Initialize the main app instance. This creates the main window, sets
|
/// Initialize the main app instance. This creates the main window, sets
|
||||||
/// up the renderer state, compiles the shaders, etc. This is the primary
|
/// up the renderer state, compiles the shaders, etc. This is the primary
|
||||||
@ -43,14 +45,21 @@ pub fn init(alloc: Allocator) !App {
|
|||||||
gl.glad.versionMinor(version),
|
gl.glad.versionMinor(version),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Blending for text
|
// Culling, probably not necessary. We have to change the winding
|
||||||
|
// order since our 0,0 is top-left.
|
||||||
gl.c.glEnable(gl.c.GL_CULL_FACE);
|
gl.c.glEnable(gl.c.GL_CULL_FACE);
|
||||||
|
gl.c.glFrontFace(gl.c.GL_CW);
|
||||||
|
|
||||||
|
// Blending for text
|
||||||
gl.c.glEnable(gl.c.GL_BLEND);
|
gl.c.glEnable(gl.c.GL_BLEND);
|
||||||
gl.c.glBlendFunc(gl.c.GL_SRC_ALPHA, gl.c.GL_ONE_MINUS_SRC_ALPHA);
|
gl.c.glBlendFunc(gl.c.GL_SRC_ALPHA, gl.c.GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
// Setup our text renderer
|
// Setup our text renderer
|
||||||
var texter = try TextRenderer.init(alloc);
|
var texter = try TextRenderer.init(alloc);
|
||||||
errdefer texter.deinit();
|
errdefer texter.deinit(alloc);
|
||||||
|
|
||||||
|
const grid = try Grid.init(alloc);
|
||||||
|
try grid.setScreenSize(.{ .width = 3000, .height = 1666 });
|
||||||
|
|
||||||
window.setSizeCallback((struct {
|
window.setSizeCallback((struct {
|
||||||
fn callback(_: glfw.Window, width: i32, height: i32) void {
|
fn callback(_: glfw.Window, width: i32, height: i32) void {
|
||||||
@ -63,6 +72,7 @@ pub fn init(alloc: Allocator) !App {
|
|||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.window = window,
|
.window = window,
|
||||||
.text = texter,
|
.text = texter,
|
||||||
|
.grid = grid,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,8 +88,8 @@ pub fn run(self: App) !void {
|
|||||||
gl.clearColor(0.2, 0.3, 0.3, 1.0);
|
gl.clearColor(0.2, 0.3, 0.3, 1.0);
|
||||||
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
try self.text.render("sh $ /bin/bash -c \"echo hello\"", 25.0, 25.0, .{ 0.5, 0.8, 0.2 });
|
try self.grid.render();
|
||||||
//try self.text.render("hi", 25.0, 25.0, .{ 0.5, 0.8, 0.2 });
|
//try self.text.render("sh $ /bin/bash -c \"echo hello\"", 25.0, 25.0, .{ 0.5, 0.8, 0.2 });
|
||||||
|
|
||||||
try self.window.swapBuffers();
|
try self.window.swapBuffers();
|
||||||
try glfw.waitEvents();
|
try glfw.waitEvents();
|
||||||
|
165
src/Grid.zig
Normal file
165
src/Grid.zig
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
//! Represents a single terminal grid.
|
||||||
|
const Grid = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const Atlas = @import("Atlas.zig");
|
||||||
|
const FontAtlas = @import("FontAtlas.zig");
|
||||||
|
const gl = @import("opengl.zig");
|
||||||
|
const gb = @import("gb_math.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.grid);
|
||||||
|
|
||||||
|
/// The dimensions of a single "cell" in the terminal grid.
|
||||||
|
///
|
||||||
|
/// The dimensions are dependent on the current loaded set of font glyphs.
|
||||||
|
/// We calculate the width based on the widest character and the height based
|
||||||
|
/// on the height requirement for an underscore (the "lowest" -- visually --
|
||||||
|
/// character).
|
||||||
|
///
|
||||||
|
/// The units for the width and height are in world space. They have to
|
||||||
|
/// be normalized using the screen projection.
|
||||||
|
///
|
||||||
|
/// TODO(mitchellh): we should recalculate cell dimensions when new glyphs
|
||||||
|
/// are loaded.
|
||||||
|
const CellDim = struct {
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The dimensions of the screen that the grid is rendered to. This is the
|
||||||
|
/// terminal screen, so it is likely a subset of the window size. The dimensions
|
||||||
|
/// should be in pixels.
|
||||||
|
const ScreenDim = struct {
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Current cell dimensions for this grid.
|
||||||
|
cell_dims: CellDim,
|
||||||
|
|
||||||
|
/// Shader program for cell rendering.
|
||||||
|
program: gl.Program,
|
||||||
|
|
||||||
|
pub fn init(alloc: Allocator) !Grid {
|
||||||
|
// Initialize our font atlas. We will initially populate the
|
||||||
|
// font atlas with all the visible ASCII characters since they are common.
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Load all visible ASCII characters and build our cell width based on
|
||||||
|
// the widest character that we see.
|
||||||
|
const cell_width: f32 = cell_width: {
|
||||||
|
var cell_width: f32 = 0;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break :cell_width cell_width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 dimensions w={d} h={d}", .{ cell_width, cell_height });
|
||||||
|
|
||||||
|
// Create our shader
|
||||||
|
const program = try gl.Program.createVF(
|
||||||
|
@embedFile("../shaders/cell.v.glsl"),
|
||||||
|
@embedFile("../shaders/cell.f.glsl"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set our cell dimensions
|
||||||
|
const pbind = try program.use();
|
||||||
|
defer pbind.unbind();
|
||||||
|
try program.setUniform("cell_dims", @Vector(2, f32){ cell_width, cell_height });
|
||||||
|
|
||||||
|
return Grid{
|
||||||
|
.cell_dims = .{ .width = cell_width, .height = cell_height },
|
||||||
|
.program = program,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the screen size for rendering. This will update the projection
|
||||||
|
/// used for the shader so that the scaling of the grid is correct.
|
||||||
|
pub fn setScreenSize(self: Grid, dim: ScreenDim) !void {
|
||||||
|
// Create a 2D orthographic projection matrix with the full width/height.
|
||||||
|
var projection: gb.gbMat4 = undefined;
|
||||||
|
gb.gb_mat4_ortho2d(
|
||||||
|
&projection,
|
||||||
|
0,
|
||||||
|
@intToFloat(f32, dim.width),
|
||||||
|
@intToFloat(f32, dim.height),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the projection uniform within our shader
|
||||||
|
const bind = try self.program.use();
|
||||||
|
defer bind.unbind();
|
||||||
|
try self.program.setUniform("projection", projection);
|
||||||
|
|
||||||
|
log.debug("screen size w={d} h={d}", .{ dim.width, dim.height });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(self: Grid) !void {
|
||||||
|
const pbind = try self.program.use();
|
||||||
|
defer pbind.unbind();
|
||||||
|
|
||||||
|
// Setup our VAO
|
||||||
|
const vao = try gl.VertexArray.create();
|
||||||
|
defer vao.destroy();
|
||||||
|
try vao.bind();
|
||||||
|
|
||||||
|
// Element buffer (EBO)
|
||||||
|
const ebo = try gl.Buffer.create();
|
||||||
|
defer ebo.destroy();
|
||||||
|
var ebobinding = try ebo.bind(.ElementArrayBuffer);
|
||||||
|
defer ebobinding.unbind();
|
||||||
|
try ebobinding.setData([6]u32{
|
||||||
|
0, 1, 3,
|
||||||
|
1, 2, 3,
|
||||||
|
}, .StaticDraw);
|
||||||
|
|
||||||
|
// Vertex buffer (VBO)
|
||||||
|
const vbo = try gl.Buffer.create();
|
||||||
|
defer vbo.destroy();
|
||||||
|
var binding = try vbo.bind(.ArrayBuffer);
|
||||||
|
defer binding.unbind();
|
||||||
|
try binding.setData([_][6]f32{
|
||||||
|
.{ 0, 0, 1, 0, 0, 1 },
|
||||||
|
.{ 1, 0, 0, 1, 0, 1 },
|
||||||
|
.{ 2, 0, 0, 0, 1, 1 },
|
||||||
|
}, .StaticDraw);
|
||||||
|
try binding.attribute(0, 2, [6]f32, 0);
|
||||||
|
try binding.attribute(1, 4, [6]f32, 2);
|
||||||
|
try binding.attributeDivisor(0, 1);
|
||||||
|
try binding.attributeDivisor(1, 1);
|
||||||
|
|
||||||
|
try gl.drawElementsInstanced(
|
||||||
|
gl.c.GL_TRIANGLES,
|
||||||
|
6,
|
||||||
|
gl.c.GL_UNSIGNED_INT,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
try gl.VertexArray.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
const face_ttf = @embedFile("../fonts/FiraCode-Regular.ttf");
|
@ -99,6 +99,7 @@ pub fn init(alloc: std.mem.Allocator) !TextRenderer {
|
|||||||
// Update the initialize size so we have some projection. We
|
// Update the initialize size so we have some projection. We
|
||||||
// expect this will get updated almost immediately.
|
// expect this will get updated almost immediately.
|
||||||
try res.setScreenSize(3000, 1666);
|
try res.setScreenSize(3000, 1666);
|
||||||
|
//try res.setScreenSize(1432, 874);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,12 @@ pub const Binding = struct {
|
|||||||
try b.enableAttribArray(idx);
|
try b.enableAttribArray(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// VertexAttribDivisor
|
||||||
|
pub fn attributeDivisor(_: Binding, idx: c.GLuint, divisor: c.GLuint) !void {
|
||||||
|
c.glVertexAttribDivisor(idx, divisor);
|
||||||
|
try errors.getError();
|
||||||
|
}
|
||||||
|
|
||||||
pub inline fn attributeAdvanced(
|
pub inline fn attributeAdvanced(
|
||||||
_: Binding,
|
_: Binding,
|
||||||
idx: c.GLuint,
|
idx: c.GLuint,
|
||||||
|
@ -82,6 +82,7 @@ pub inline fn setUniform(p: Program, n: [:0]const u8, value: anytype) !void {
|
|||||||
|
|
||||||
// Perform the correct call depending on the type of the value.
|
// Perform the correct call depending on the type of the value.
|
||||||
switch (@TypeOf(value)) {
|
switch (@TypeOf(value)) {
|
||||||
|
@Vector(2, f32) => c.glUniform2f(loc, value[0], value[1]),
|
||||||
@Vector(3, f32) => c.glUniform3f(loc, value[0], value[1], value[2]),
|
@Vector(3, f32) => c.glUniform3f(loc, value[0], value[1], value[2]),
|
||||||
@Vector(4, f32) => c.glUniform4f(loc, value[0], value[1], value[2], value[3]),
|
@Vector(4, f32) => c.glUniform4f(loc, value[0], value[1], value[2], value[3]),
|
||||||
gb.gbMat4 => c.glUniformMatrix4fv(
|
gb.gbMat4 => c.glUniformMatrix4fv(
|
||||||
|
@ -20,6 +20,16 @@ pub fn drawElements(mode: c.GLenum, count: c.GLsizei, typ: c.GLenum, offset: usi
|
|||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn drawElementsInstanced(
|
||||||
|
mode: c.GLenum,
|
||||||
|
count: c.GLsizei,
|
||||||
|
typ: c.GLenum,
|
||||||
|
primcount: usize,
|
||||||
|
) !void {
|
||||||
|
c.glDrawElementsInstanced(mode, count, typ, null, @intCast(c.GLsizei, primcount));
|
||||||
|
try errors.getError();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn viewport(x: c.GLint, y: c.GLint, width: c.GLsizei, height: c.GLsizei) !void {
|
pub fn viewport(x: c.GLint, y: c.GLint, width: c.GLsizei, height: c.GLsizei) !void {
|
||||||
c.glViewport(x, y, width, height);
|
c.glViewport(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user