From 64cacce1cf6f8f3129c294601a0122bbe1f0f1ad Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 19 Nov 2023 22:33:06 -0800 Subject: [PATCH] renderer/opengl: setup image uniforms --- src/renderer/OpenGL.zig | 55 +++++++---- src/renderer/opengl/ImageProgram.zig | 134 +++++++++++++++++++++++++++ src/renderer/shaders/image.f.glsl | 11 +++ src/renderer/shaders/image.v.glsl | 44 +++++++++ 4 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 src/renderer/opengl/ImageProgram.zig create mode 100644 src/renderer/shaders/image.f.glsl create mode 100644 src/renderer/shaders/image.v.glsl diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index ab1ee27d0..bcd8c8046 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -22,6 +22,7 @@ const math = @import("../math.zig"); const Surface = @import("../Surface.zig"); const CellProgram = @import("opengl/CellProgram.zig"); +const ImageProgram = @import("opengl/ImageProgram.zig"); const gl_image = @import("opengl/image.zig"); const custom = @import("opengl/custom.zig"); const Image = gl_image.Image; @@ -148,17 +149,22 @@ const SetScreenSize = struct { ); // Update the projection uniform within our shader - try gl_state.cell_program.program.setUniform( - "projection", + inline for (.{ "cell_program", "image_program" }) |name| { + const program = @field(gl_state, name); + const bind = try program.program.use(); + defer bind.unbind(); + try program.program.setUniform( + "projection", - // 2D orthographic projection with the full w/h - math.ortho2d( - -1 * @as(f32, @floatFromInt(padding.left)), - @floatFromInt(padded_size.width + padding.right), - @floatFromInt(padded_size.height + padding.bottom), - -1 * @as(f32, @floatFromInt(padding.top)), - ), - ); + // 2D orthographic projection with the full w/h + math.ortho2d( + -1 * @as(f32, @floatFromInt(padding.left)), + @floatFromInt(padded_size.width + padding.right), + @floatFromInt(padded_size.height + padding.bottom), + -1 * @as(f32, @floatFromInt(padding.top)), + ), + ); + } // Update our custom shader resolution if (gl_state.custom) |*custom_state| { @@ -173,13 +179,21 @@ const SetFontSize = struct { fn apply(self: SetFontSize, r: *const OpenGL) !void { const gl_state = r.gl_state orelse return error.OpenGLUninitialized; - try gl_state.cell_program.program.setUniform( - "cell_size", - @Vector(2, f32){ - @floatFromInt(self.metrics.cell_width), - @floatFromInt(self.metrics.cell_height), - }, - ); + inline for (.{ "cell_program", "image_program" }) |name| { + const program = @field(gl_state, name); + const bind = try program.program.use(); + defer bind.unbind(); + try program.program.setUniform( + "cell_size", + @Vector(2, f32){ + @floatFromInt(self.metrics.cell_width), + @floatFromInt(self.metrics.cell_height), + }, + ); + } + + const bind = try gl_state.cell_program.program.use(); + defer bind.unbind(); try gl_state.cell_program.program.setUniform( "strikethrough_position", @as(f32, @floatFromInt(self.metrics.strikethrough_position)), @@ -1743,6 +1757,7 @@ fn drawCells( /// OpenGL context is replaced. const GLState = struct { cell_program: CellProgram, + image_program: ImageProgram, texture: gl.Texture, texture_color: gl.Texture, custom: ?custom.State, @@ -1830,8 +1845,13 @@ const GLState = struct { const cell_program = try CellProgram.init(); errdefer cell_program.deinit(); + // Build our image renderer + const image_program = try ImageProgram.init(); + errdefer image_program.deinit(); + return .{ .cell_program = cell_program, + .image_program = image_program, .texture = tex, .texture_color = tex_color, .custom = custom_state, @@ -1842,6 +1862,7 @@ const GLState = struct { if (self.custom) |v| v.deinit(alloc); self.texture.destroy(); self.texture_color.destroy(); + self.image_program.deinit(); self.cell_program.deinit(); } }; diff --git a/src/renderer/opengl/ImageProgram.zig b/src/renderer/opengl/ImageProgram.zig new file mode 100644 index 000000000..e53891818 --- /dev/null +++ b/src/renderer/opengl/ImageProgram.zig @@ -0,0 +1,134 @@ +/// The OpenGL program for rendering terminal cells. +const ImageProgram = @This(); + +const std = @import("std"); +const gl = @import("opengl"); + +program: gl.Program, +vao: gl.VertexArray, +ebo: gl.Buffer, +vbo: gl.Buffer, + +pub const Input = extern struct { + /// vec2 grid_coord + grid_col: u16, + grid_row: u16, + + /// vec2 cell_offset + cell_offset_x: u32 = 0, + cell_offset_y: u32 = 0, + + /// vec4 source_rect + source_x: u32 = 0, + source_y: u32 = 0, + source_width: u32 = 0, + source_height: u32 = 0, + + /// vec2 dest_size + dest_width: u32 = 0, + dest_height: u32 = 0, +}; + +pub fn init() !ImageProgram { + // Load and compile our shaders. + const program = try gl.Program.createVF( + @embedFile("../shaders/image.v.glsl"), + @embedFile("../shaders/image.f.glsl"), + ); + errdefer program.destroy(); + + // Set our program uniforms + const pbind = try program.use(); + defer pbind.unbind(); + + // Set all of our texture indexes + try program.setUniform("image", 0); + + // Setup our VAO + const vao = try gl.VertexArray.create(); + errdefer vao.destroy(); + const vaobind = try vao.bind(); + defer vaobind.unbind(); + + // Element buffer (EBO) + const ebo = try gl.Buffer.create(); + errdefer ebo.destroy(); + var ebobind = try ebo.bind(.element_array); + defer ebobind.unbind(); + try ebobind.setData([6]u8{ + 0, 1, 3, // Top-left triangle + 1, 2, 3, // Bottom-right triangle + }, .static_draw); + + // Vertex buffer (VBO) + const vbo = try gl.Buffer.create(); + errdefer vbo.destroy(); + var vbobind = try vbo.bind(.array); + defer vbobind.unbind(); + var offset: usize = 0; + try vbobind.attributeAdvanced(0, 2, gl.c.GL_UNSIGNED_SHORT, false, @sizeOf(Input), offset); + offset += 2 * @sizeOf(u16); + try vbobind.attributeAdvanced(1, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(Input), offset); + offset += 2 * @sizeOf(u32); + try vbobind.attributeAdvanced(2, 4, gl.c.GL_UNSIGNED_INT, false, @sizeOf(Input), offset); + offset += 4 * @sizeOf(u32); + try vbobind.attributeAdvanced(3, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(Input), offset); + offset += 2 * @sizeOf(u32); + try vbobind.enableAttribArray(0); + try vbobind.enableAttribArray(1); + try vbobind.enableAttribArray(2); + try vbobind.enableAttribArray(3); + try vbobind.attributeDivisor(0, 1); + try vbobind.attributeDivisor(1, 1); + try vbobind.attributeDivisor(2, 1); + try vbobind.attributeDivisor(3, 1); + + return .{ + .program = program, + .vao = vao, + .ebo = ebo, + .vbo = vbo, + }; +} + +pub fn bind(self: ImageProgram) !Binding { + const program = try self.program.use(); + errdefer program.unbind(); + + const vao = try self.vao.bind(); + errdefer vao.unbind(); + + const ebo = try self.ebo.bind(.element_array); + errdefer ebo.unbind(); + + const vbo = try self.vbo.bind(.array); + errdefer vbo.unbind(); + + return .{ + .program = program, + .vao = vao, + .ebo = ebo, + .vbo = vbo, + }; +} + +pub fn deinit(self: ImageProgram) void { + self.vbo.destroy(); + self.ebo.destroy(); + self.vao.destroy(); + self.program.destroy(); +} + +pub const Binding = struct { + program: gl.Program.Binding, + vao: gl.VertexArray.Binding, + ebo: gl.Buffer.Binding, + vbo: gl.Buffer.Binding, + + pub fn unbind(self: Binding) void { + self.vbo.unbind(); + self.ebo.unbind(); + self.vao.unbind(); + self.program.unbind(); + } +}; diff --git a/src/renderer/shaders/image.f.glsl b/src/renderer/shaders/image.f.glsl new file mode 100644 index 000000000..9373abe11 --- /dev/null +++ b/src/renderer/shaders/image.f.glsl @@ -0,0 +1,11 @@ +#version 330 core + +in vec2 tex_coords; + +layout(location = 0) out vec4 out_FragColor; + +uniform sampler2D image; + +void main() { + out_FragColor = texture(image, tex_coords); +} diff --git a/src/renderer/shaders/image.v.glsl b/src/renderer/shaders/image.v.glsl new file mode 100644 index 000000000..e3d07ca9e --- /dev/null +++ b/src/renderer/shaders/image.v.glsl @@ -0,0 +1,44 @@ +#version 330 core + +layout (location = 0) in vec2 grid_pos; +layout (location = 1) in vec2 cell_offset; +layout (location = 2) in vec4 source_rect; +layout (location = 3) in vec2 dest_size; + +out vec2 tex_coord; + +uniform sampler2D image; +uniform vec2 cell_size; +uniform mat4 projection; + +void main() { + // The size of the image in pixels + vec2 image_size = textureSize(image, 0); + + // 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. + // + // 0 = top-right + // 1 = bot-right + // 2 = bot-left + // 3 = top-left + vec2 position; + position.x = (gl_VertexID == 0 || gl_VertexID == 1) ? 1. : 0.; + position.y = (gl_VertexID == 0 || gl_VertexID == 3) ? 0. : 1.; + + // The texture coordinates start at our source x/y, then add the width/height + // as enabled by our instance id, then normalize to [0, 1] + tex_coord = source_rect.xy; + tex_coord += source_rect.zw * position; + tex_coord /= image_size; + + // The position of our image starts at the top-left of the grid cell and + // adds the source rect width/height components. + vec2 image_pos = (cell_size * grid_pos) + cell_offset; + image_pos += dest_size * position; + + gl_Position = projection * vec4(image_pos.xy, 0, 1.0); +}