diff --git a/shaders/text.f.glsl b/shaders/text.f.glsl index 03858922a..b87f02ebb 100644 --- a/shaders/text.f.glsl +++ b/shaders/text.f.glsl @@ -1,9 +1,13 @@ -#version 120 +#version 330 core -varying vec2 texcoord; -uniform sampler2D tex; -uniform vec4 color; +in vec2 TexCoords; +out vec4 color; -void main(void) { - gl_FragColor = vec4(1, 1, 1, texture2D(tex, texcoord).r) * color; +uniform sampler2D text; +uniform vec3 textColor; + +void main() +{ + vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); + color = vec4(textColor, 1.0) * sampled; } diff --git a/shaders/text.v.glsl b/shaders/text.v.glsl index 63be28019..2d0aba729 100644 --- a/shaders/text.v.glsl +++ b/shaders/text.v.glsl @@ -1,9 +1,10 @@ -#version 120 +#version 330 core -attribute vec4 coord; -varying vec2 texcoord; +layout (location = 0) in vec4 vertex; // +out vec2 TexCoords; -void main(void) { - gl_Position = vec4(coord.xy, 0, 1); - texcoord = coord.zw; +void main() +{ + gl_Position = vec4(vertex.xy, 0.0, 1.0); + TexCoords = vertex.zw; } diff --git a/src/App.zig b/src/App.zig index 09c6f8808..9a0982f8e 100644 --- a/src/App.zig +++ b/src/App.zig @@ -12,17 +12,12 @@ const log = std.log; window: glfw.Window, -glprog: gl.Program, -vao: gl.VertexArray, +text: TextRenderer, /// Initialize the main app instance. This creates the main window, sets /// up the renderer state, compiles the shaders, etc. This is the primary /// "startup" logic. pub fn init(alloc: std.mem.Allocator) !App { - // Setup our text renderer - var texter = try TextRenderer.init(alloc); - defer texter.deinit(); - // Create our window const window = try glfw.Window.create(640, 480, "ghostty", null, null, .{ .context_version_major = 3, @@ -45,54 +40,22 @@ pub fn init(alloc: std.mem.Allocator) !App { }).callback); // Blending for text + gl.c.glEnable(gl.c.GL_CULL_FACE); gl.c.glEnable(gl.c.GL_BLEND); gl.c.glBlendFunc(gl.c.GL_SRC_ALPHA, gl.c.GL_ONE_MINUS_SRC_ALPHA); - // Compile our shaders - const vs = try gl.Shader.create(gl.c.GL_VERTEX_SHADER); - try vs.setSourceAndCompile(vs_source); - errdefer vs.destroy(); - - const fs = try gl.Shader.create(gl.c.GL_FRAGMENT_SHADER); - try fs.setSourceAndCompile(fs_source); - errdefer fs.destroy(); - - // Link our shader program - const program = try gl.Program.create(); - errdefer program.destroy(); - try program.attachShader(vs); - try program.attachShader(fs); - try program.link(); - vs.destroy(); - fs.destroy(); - - // Create our bufer or vertices - const vertices = [_]f32{ - -0.5, -0.5, 0.0, // left - 0.5, -0.5, 0.0, // right - 0.0, 0.5, 0.0, // top - }; - const vao = try gl.VertexArray.create(); - //defer vao.destroy(); - const vbo = try gl.Buffer.create(); - //defer vbo.destroy(); - try vao.bind(); - var binding = try vbo.bind(gl.c.GL_ARRAY_BUFFER); - try binding.setData(&vertices, gl.c.GL_STATIC_DRAW); - try binding.vertexAttribPointer(0, 3, gl.c.GL_FLOAT, false, 3 * @sizeOf(f32), null); - try binding.enableVertexAttribArray(0); - binding.unbind(); - try gl.VertexArray.unbind(); + // Setup our text renderer + var texter = try TextRenderer.init(alloc); + errdefer texter.deinit(); return App{ .window = window, - .glprog = program, - - .vao = vao, + .text = texter, }; } pub fn deinit(self: *App) void { + self.text.deinit(); self.window.destroy(); self.* = undefined; } @@ -103,9 +66,7 @@ pub fn run(self: App) !void { gl.clearColor(0.2, 0.3, 0.3, 1.0); gl.clear(gl.c.GL_COLOR_BUFFER_BIT); - try self.glprog.use(); - try self.vao.bind(); - try gl.drawArrays(gl.c.GL_TRIANGLES, 0, 3); + try self.text.render("hello", 25.0, 25.0, 1.0, .{ 0.5, 0.8, 0.2 }); try self.window.swapBuffers(); try glfw.waitEvents(); diff --git a/src/TextRenderer.zig b/src/TextRenderer.zig index 2a78ea598..b28ec1ae4 100644 --- a/src/TextRenderer.zig +++ b/src/TextRenderer.zig @@ -8,12 +8,15 @@ alloc: std.mem.Allocator, ft: ftc.FT_Library, face: ftc.FT_Face, chars: CharList, +vao: gl.VertexArray = undefined, +vbo: gl.Buffer = undefined, +program: gl.Program = undefined, const CharList = std.ArrayListUnmanaged(Char); const Char = struct { tex: gl.Texture, - size: @Vector(2, c_uint), - bearing: @Vector(2, c_int), + size: @Vector(2, f32), + bearing: @Vector(2, f32), advance: c_uint, }; @@ -82,22 +85,42 @@ pub fn init(alloc: std.mem.Allocator) !TextRenderer { chars.appendAssumeCapacity(.{ .tex = tex, .size = .{ - face.*.glyph.*.bitmap.width, - face.*.glyph.*.bitmap.rows, + @intToFloat(f32, face.*.glyph.*.bitmap.width), + @intToFloat(f32, face.*.glyph.*.bitmap.rows), }, .bearing = .{ - face.*.glyph.*.bitmap_left, - face.*.glyph.*.bitmap_top, + @intToFloat(f32, face.*.glyph.*.bitmap_left), + @intToFloat(f32, face.*.glyph.*.bitmap_top), }, .advance = @intCast(c_uint, face.*.glyph.*.advance.x), }); } + // Configure VAO/VBO for glyph rendering + const vao = try gl.VertexArray.create(); + const vbo = try gl.Buffer.create(); + try vao.bind(); + var binding = try vbo.bind(gl.c.GL_ARRAY_BUFFER); + try binding.setDataType([6 * 4]f32, gl.c.GL_DYNAMIC_DRAW); + try binding.enableVertexAttribArray(0); + try binding.vertexAttribPointer(0, 4, gl.c.GL_FLOAT, false, 4 * @sizeOf(f32), null); + binding.unbind(); + try gl.VertexArray.unbind(); + + // Create our shader + const program = try gl.Program.createVF( + @embedFile("../shaders/text.v.glsl"), + @embedFile("../shaders/text.f.glsl"), + ); + return TextRenderer{ .alloc = alloc, .ft = ft, .face = face, .chars = chars, + .program = program, + .vao = vao, + .vbo = vbo, }; } @@ -113,4 +136,59 @@ pub fn deinit(self: *TextRenderer) void { self.* = undefined; } +pub fn render( + self: TextRenderer, + text: []const u8, + x: f32, + y: f32, + scale: f32, + color: @Vector(3, f32), +) !void { + try self.program.use(); + try self.program.setUniform("textColor", color); + try gl.Texture.active(gl.c.GL_TEXTURE0); + try self.vao.bind(); + + std.log.info("---", .{}); + var curx: f32 = x; + for (text) |c| { + const char = self.chars.items[c]; + + const xpos = curx + (char.bearing[0] * scale); + const ypos = y + (char.bearing[1] * scale); + const w = char.size[0] * scale; + const h = char.size[1] * scale; + + std.log.info("CHARACTER INFO ch={} xpos={} ypos={} w={} h={}", .{ + c, + xpos, + ypos, + w, + h, + }); + + const vert = [6][4]f32{ + .{ xpos, ypos + h, 0.0, 0.0 }, + .{ xpos, ypos, 0.0, 1.0 }, + .{ xpos + w, ypos, 1.0, 1.0 }, + + .{ xpos, ypos + h, 0.0, 0.0 }, + .{ xpos + w, ypos, 1.0, 1.0 }, + .{ xpos + w, ypos + h, 1.0, 0.0 }, + }; + + var texbind = try char.tex.bind(gl.c.GL_TEXTURE_2D); + defer texbind.unbind(); + var bind = try self.vbo.bind(gl.c.GL_ARRAY_BUFFER); + try bind.setSubData(0, vert); + bind.unbind(); + + try gl.drawArrays(gl.c.GL_TRIANGLES, 0, 6); + + curx += @intToFloat(f32, char.advance >> 6) * scale; + } + + try gl.VertexArray.unbind(); +} + const face_ttf = @embedFile("../fonts/Inconsolata-Regular.ttf"); diff --git a/src/opengl/Buffer.zig b/src/opengl/Buffer.zig index 692a3dc60..31102cc5f 100644 --- a/src/opengl/Buffer.zig +++ b/src/opengl/Buffer.zig @@ -19,11 +19,40 @@ pub const Binding = struct { data: anytype, usage: c.GLenum, ) !void { - // Determine the size and pointer to the given data. - const info: struct { - size: isize, - ptr: *const anyopaque, - } = switch (@typeInfo(@TypeOf(data))) { + const info = dataInfo(data); + c.glBufferData(b.target, info.size, info.ptr, usage); + try errors.getError(); + } + + /// Sets the data of this bound buffer. The data can be any array-like + /// type. The size of the data is automatically determined based on the type. + pub inline fn setSubData( + b: Binding, + offset: usize, + data: anytype, + ) !void { + const info = dataInfo(data); + c.glBufferSubData(b.target, @intCast(c_long, offset), info.size, info.ptr); + try errors.getError(); + } + + /// Sets the buffer data with a null buffer that is expected to be + /// filled in the future using subData. This requires the type just so + /// we can setup the data size. + pub inline fn setDataType( + b: Binding, + comptime T: type, + usage: c.GLenum, + ) !void { + c.glBufferData(b.target, @sizeOf(T), null, usage); + try errors.getError(); + } + + fn dataInfo(data: anytype) struct { + size: isize, + ptr: *const anyopaque, + } { + return switch (@typeInfo(@TypeOf(data))) { .Array => |ary| .{ .size = @sizeOf(ary.child) * ary.len, .ptr = &data, @@ -47,9 +76,6 @@ pub const Binding = struct { unreachable; }, }; - - c.glBufferData(b.target, info.size, info.ptr, usage); - try errors.getError(); } pub inline fn enableVertexAttribArray(_: Binding, idx: c.GLuint) !void { diff --git a/src/opengl/Program.zig b/src/opengl/Program.zig index 84a90dd80..1a95dc258 100644 --- a/src/opengl/Program.zig +++ b/src/opengl/Program.zig @@ -18,6 +18,25 @@ pub inline fn create() !Program { return Program{ .id = id }; } +/// Create a program from a vertex and fragment shader source. This will +/// compile and link the vertex and fragment shader. +pub inline fn createVF(vsrc: [:0]const u8, fsrc: [:0]const u8) !Program { + const vs = try Shader.create(c.GL_VERTEX_SHADER); + try vs.setSourceAndCompile(vsrc); + defer vs.destroy(); + + const fs = try Shader.create(c.GL_FRAGMENT_SHADER); + try fs.setSourceAndCompile(fsrc); + defer fs.destroy(); + + const p = try create(); + try p.attachShader(vs); + try p.attachShader(fs); + try p.link(); + + return p; +} + pub inline fn attachShader(p: Program, s: Shader) !void { c.glAttachShader(p.id, s.id); try errors.getError(); @@ -55,6 +74,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. switch (@TypeOf(value)) { + @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]), else => unreachable, } diff --git a/src/opengl/Texture.zig b/src/opengl/Texture.zig index 4e57548a5..d3af48ad6 100644 --- a/src/opengl/Texture.zig +++ b/src/opengl/Texture.zig @@ -6,6 +6,10 @@ const errors = @import("errors.zig"); id: c.GLuint, +pub inline fn active(target: c.GLenum) !void { + c.glActiveTexture(target); +} + pub const Binding = struct { target: c.GLenum,