From 47971e7663567c98b622d7bf128ab9956f3b8402 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 17 Nov 2023 15:20:13 -0800 Subject: [PATCH] renderer/opengl: setup uniform buffer objects for custom shaders --- pkg/opengl/Buffer.zig | 30 ++++++++---- pkg/opengl/Program.zig | 9 ++++ src/renderer/OpenGL.zig | 5 ++ src/renderer/opengl/CustomProgram.zig | 57 +++++++++++++++++----- src/renderer/shaders/shadertoy_prefix.glsl | 10 ++-- src/renderer/shaders/temp.f.glsl | 26 ++++++++-- 6 files changed, 105 insertions(+), 32 deletions(-) diff --git a/pkg/opengl/Buffer.zig b/pkg/opengl/Buffer.zig index fca94e0fc..a8ba099d3 100644 --- a/pkg/opengl/Buffer.zig +++ b/pkg/opengl/Buffer.zig @@ -15,21 +15,35 @@ pub fn create() !Buffer { } /// glBindBuffer -pub fn bind(v: Buffer, target: Target) !Binding { - glad.context.BindBuffer.?(@intFromEnum(target), v.id); - return Binding{ .target = target }; +pub fn bind(self: Buffer, target: Target) !Binding { + glad.context.BindBuffer.?(@intFromEnum(target), self.id); + return Binding{ .id = self.id, .target = target }; } -pub fn destroy(v: Buffer) void { - glad.context.DeleteBuffers.?(1, &v.id); +pub fn destroy(self: Buffer) void { + glad.context.DeleteBuffers.?(1, &self.id); +} + +pub fn bindBase(self: Buffer, target: Target, idx: c.GLuint) !void { + glad.context.BindBufferBase.?( + @intFromEnum(target), + idx, + self.id, + ); + try errors.getError(); } /// Binding is a bound buffer. By using this for functions that operate /// on bound buffers, you can easily defer unbinding and in safety-enabled /// modes verify that unbound buffers are never accessed. pub const Binding = struct { + id: c.GLuint, target: Target, + pub fn unbind(b: Binding) void { + glad.context.BindBuffer.?(@intFromEnum(b.target), 0); + } + /// 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 fn setData( @@ -103,7 +117,7 @@ pub const Binding = struct { return switch (@typeInfo(@TypeOf(data))) { .Pointer => |ptr| switch (ptr.size) { .One => .{ - .size = @sizeOf(ptr.child) * data.len, + .size = @sizeOf(ptr.child), .ptr = data, }, .Slice => .{ @@ -209,10 +223,6 @@ pub const Binding = struct { glad.context.VertexAttribIPointer.?(idx, size, typ, stride, offsetPtr); try errors.getError(); } - - pub fn unbind(b: Binding) void { - glad.context.BindBuffer.?(@intFromEnum(b.target), 0); - } }; /// Enum for possible binding targets. diff --git a/pkg/opengl/Program.zig b/pkg/opengl/Program.zig index e8a691f17..3a2f2036a 100644 --- a/pkg/opengl/Program.zig +++ b/pkg/opengl/Program.zig @@ -78,6 +78,15 @@ pub fn use(p: Program) !Binding { return .{}; } +pub fn uniformBlockBinding( + self: Program, + index: c.GLuint, + binding: c.GLuint, +) !void { + glad.context.UniformBlockBinding.?(self.id, index, binding); + try errors.getError(); +} + /// Requires the program is currently in use. pub fn setUniform( p: Program, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 4a71c7b41..c71508597 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1431,6 +1431,11 @@ fn drawCustomPrograms( // Bind our cell program state, buffers const bind = try program.bind(); defer bind.unbind(); + + // Sync the uniform data. + // TODO: only do this when the data has changed + try program.syncUniforms(); + try gl.drawElementsInstanced(gl.c.GL_TRIANGLES, 6, gl.c.GL_UNSIGNED_BYTE, 1); } } diff --git a/src/renderer/opengl/CustomProgram.zig b/src/renderer/opengl/CustomProgram.zig index e783463bf..2013e44fd 100644 --- a/src/renderer/opengl/CustomProgram.zig +++ b/src/renderer/opengl/CustomProgram.zig @@ -5,8 +5,22 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const gl = @import("opengl"); +/// The "INDEX" is the index into the global GL state and the +/// "BINDING" is the binding location in the shader. +const UNIFORM_INDEX: gl.c.GLuint = 0; +const UNIFORM_BINDING: gl.c.GLuint = 0; + +/// The uniform state. Whenever this is modified this should be +/// synced to the buffer. The draw/bind calls don't automatically +/// sync this so this should be done whenever the state is modified. +uniforms: Uniforms = .{}, + +/// The actual shader program. program: gl.Program, +/// The uniform buffer that is updated with our uniform data. +ubo: gl.Buffer, + /// This VAO is used for all custom shaders. It contains a single quad /// by using an EBO. The vertex ID (gl_VertexID) can be used to determine the /// position of the vertex. @@ -14,16 +28,16 @@ vao: gl.VertexArray, ebo: gl.Buffer, pub const Uniforms = extern struct { - resolution: [3]f32 align(16), - time: f32 align(4), - time_delta: f32 align(4), - frame_rate: f32 align(4), - frame: i32 align(4), - channel_time: [4][4]f32 align(16), - channel_resolution: [4][4]f32 align(16), - mouse: [4]f32 align(16), - date: [4]f32 align(16), - sample_rate: f32 align(4), + resolution: [3]f32 align(16) = .{ 0, 0, 0 }, + time: f32 align(4) = 1, + time_delta: f32 align(4) = 1, + frame_rate: f32 align(4) = 1, + frame: i32 align(4) = 1, + channel_time: [4][4]f32 align(16) = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4, + channel_resolution: [4][4]f32 align(16) = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4, + mouse: [4]f32 align(16) = .{ 0, 0, 0, 0 }, + date: [4]f32 align(16) = .{ 0, 0, 0, 0 }, + sample_rate: f32 align(4) = 1, }; pub fn createList(alloc: Allocator, srcs: []const [:0]const u8) ![]const CustomProgram { @@ -39,14 +53,16 @@ pub fn createList(alloc: Allocator, srcs: []const [:0]const u8) ![]const CustomP } pub fn init(src: [:0]const u8) !CustomProgram { - _ = src; const program = try gl.Program.createVF( @embedFile("../shaders/custom.v.glsl"), - //src, - @embedFile("../shaders/temp.f.glsl"), + src, + //@embedFile("../shaders/temp.f.glsl"), ); errdefer program.destroy(); + // Map our uniform buffer to the global GL state + try program.uniformBlockBinding(UNIFORM_INDEX, UNIFORM_BINDING); + // Create our uniform buffer that is shared across all custom shaders const ubo = try gl.Buffer.create(); errdefer ubo.destroy(); @@ -74,6 +90,7 @@ pub fn init(src: [:0]const u8) !CustomProgram { return .{ .program = program, + .ubo = ubo, .vao = vao, .ebo = ebo, }; @@ -85,7 +102,21 @@ pub fn deinit(self: CustomProgram) void { self.program.destroy(); } +pub fn syncUniforms(self: CustomProgram) !void { + var ubobind = try self.ubo.bind(.uniform); + defer ubobind.unbind(); + try ubobind.setData(self.uniforms, .static_draw); +} + pub fn bind(self: CustomProgram) !Binding { + // Move our uniform buffer into proper global index. Note that + // in theory we can do this globally once and never worry about + // it again. I don't think we're high-performance enough at all + // to worry about that and this makes it so you can just move + // around CustomProgram usage without worrying about clobbering + // the global state. + try self.ubo.bindBase(.uniform, UNIFORM_INDEX); + const program = try self.program.use(); errdefer program.unbind(); diff --git a/src/renderer/shaders/shadertoy_prefix.glsl b/src/renderer/shaders/shadertoy_prefix.glsl index ff3749ff2..d62e19605 100644 --- a/src/renderer/shaders/shadertoy_prefix.glsl +++ b/src/renderer/shaders/shadertoy_prefix.glsl @@ -13,10 +13,12 @@ layout(binding = 0) uniform Globals { uniform float iSampleRate; }; -layout(binding = 0) uniform sampler2D iChannel0; -layout(binding = 1) uniform sampler2D iChannel1; -layout(binding = 2) uniform sampler2D iChannel2; -layout(binding = 3) uniform sampler2D iChannel3; +layout(binding = 1) uniform sampler2D iChannel0; + +// These are unused currently by Ghostty: +// layout(binding = 1) uniform sampler2D iChannel1; +// layout(binding = 2) uniform sampler2D iChannel2; +// layout(binding = 3) uniform sampler2D iChannel3; layout(location = 0) in vec4 gl_FragCoord; layout(location = 0) out vec4 _fragColor; diff --git a/src/renderer/shaders/temp.f.glsl b/src/renderer/shaders/temp.f.glsl index 632a2f73b..b97b34549 100644 --- a/src/renderer/shaders/temp.f.glsl +++ b/src/renderer/shaders/temp.f.glsl @@ -1,12 +1,28 @@ -#version 330 core +#version 430 core -layout(location = 0) out vec4 out_FragColor; +layout(binding = 0, std140) uniform Globals +{ + vec3 iResolution; + float iTime; + float iTimeDelta; + float iFrameRate; + int iFrame; + float iChannelTime[4]; + vec3 iChannelResolution[4]; + vec4 iMouse; + vec4 iDate; + float iSampleRate; +} _89; + +layout(binding = 1) uniform sampler2D iChannel0; + +layout(location = 0) out vec4 _fragColor; void main() { // red - //out_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + _fragColor = vec4(_89.iSampleRate, 0.0, 0.0, 1.0); // maze - vec4 I = gl_FragCoord; - out_FragColor = vec4(3)*modf(I*.1,I)[int(length(I)*1e4)&1]; + //vec4 I = gl_FragCoord; + //_fragColor = vec4(3)*modf(I*.1,I)[int(length(I)*1e4)&1]; }