renderer/opengl: setup uniform buffer objects for custom shaders

This commit is contained in:
Mitchell Hashimoto
2023-11-17 15:20:13 -08:00
parent 5fc91401f2
commit 47971e7663
6 changed files with 105 additions and 32 deletions

View File

@ -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.

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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;

View File

@ -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];
}