mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
renderer/opengl: better organization of custom shader state
This commit is contained in:
@ -22,7 +22,7 @@ const math = @import("../math.zig");
|
||||
const Surface = @import("../Surface.zig");
|
||||
|
||||
const CellProgram = @import("opengl/CellProgram.zig");
|
||||
const CustomProgram = @import("opengl/CustomProgram.zig");
|
||||
const custom = @import("opengl/custom.zig");
|
||||
|
||||
const log = std.log.scoped(.grid);
|
||||
|
||||
@ -1427,16 +1427,29 @@ fn drawCustomPrograms(
|
||||
) !void {
|
||||
_ = self;
|
||||
|
||||
for (gl_state.custom_programs) |program| {
|
||||
// If we have no custom shaders then we do nothing.
|
||||
const custom_state = gl_state.custom orelse return;
|
||||
|
||||
// Bind our state that is global to all custom shaders
|
||||
const custom_bind = try custom_state.bind();
|
||||
defer custom_bind.unbind();
|
||||
|
||||
// Sync the uniform data.
|
||||
// TODO: only do this when the data has changed
|
||||
try custom_state.syncUniforms();
|
||||
|
||||
// Go through each custom shader and draw it.
|
||||
for (custom_state.programs) |program| {
|
||||
// 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);
|
||||
try gl.drawElementsInstanced(
|
||||
gl.c.GL_TRIANGLES,
|
||||
6,
|
||||
gl.c.GL_UNSIGNED_BYTE,
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1545,7 +1558,7 @@ const GLState = struct {
|
||||
cell_program: CellProgram,
|
||||
texture: gl.Texture,
|
||||
texture_color: gl.Texture,
|
||||
custom_programs: []const CustomProgram,
|
||||
custom: ?custom.State,
|
||||
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
@ -1557,21 +1570,25 @@ const GLState = struct {
|
||||
const arena_alloc = arena.allocator();
|
||||
|
||||
// Load our custom shaders
|
||||
const custom_shaders: []const [:0]const u8 = shadertoy.loadFromFiles(
|
||||
arena_alloc,
|
||||
config.custom_shaders.items,
|
||||
.glsl,
|
||||
) catch |err| err: {
|
||||
log.warn("error loading custom shaders err={}", .{err});
|
||||
break :err &.{};
|
||||
};
|
||||
const custom_state: ?custom.State = custom: {
|
||||
const shaders: []const [:0]const u8 = shadertoy.loadFromFiles(
|
||||
arena_alloc,
|
||||
config.custom_shaders.items,
|
||||
.glsl,
|
||||
) catch |err| err: {
|
||||
log.warn("error loading custom shaders err={}", .{err});
|
||||
break :err &.{};
|
||||
};
|
||||
if (shaders.len == 0) break :custom null;
|
||||
|
||||
// Create our custom programs
|
||||
const custom_programs = try CustomProgram.createList(alloc, custom_shaders);
|
||||
errdefer {
|
||||
for (custom_programs) |p| p.deinit();
|
||||
alloc.free(custom_programs);
|
||||
}
|
||||
break :custom custom.State.init(
|
||||
alloc,
|
||||
shaders,
|
||||
) catch |err| err: {
|
||||
log.warn("error initializing custom shaders err={}", .{err});
|
||||
break :err null;
|
||||
};
|
||||
};
|
||||
|
||||
// Blending for text. We use GL_ONE here because we should be using
|
||||
// premultiplied alpha for all our colors in our fragment shaders.
|
||||
@ -1628,15 +1645,14 @@ const GLState = struct {
|
||||
|
||||
return .{
|
||||
.cell_program = cell_program,
|
||||
.custom_programs = custom_programs,
|
||||
.texture = tex,
|
||||
.texture_color = tex_color,
|
||||
.custom = custom_state,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *GLState, alloc: Allocator) void {
|
||||
for (self.custom_programs) |p| p.deinit();
|
||||
alloc.free(self.custom_programs);
|
||||
if (self.custom) |v| v.deinit(alloc);
|
||||
self.texture.destroy();
|
||||
self.texture_color.destroy();
|
||||
self.cell_program.deinit();
|
||||
|
@ -1,146 +0,0 @@
|
||||
/// The OpenGL program for custom shaders.
|
||||
const CustomProgram = @This();
|
||||
|
||||
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.
|
||||
vao: gl.VertexArray,
|
||||
ebo: gl.Buffer,
|
||||
|
||||
pub const Uniforms = extern struct {
|
||||
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 {
|
||||
var programs = std.ArrayList(CustomProgram).init(alloc);
|
||||
defer programs.deinit();
|
||||
errdefer for (programs.items) |program| program.deinit();
|
||||
|
||||
for (srcs) |src| {
|
||||
try programs.append(try CustomProgram.init(src));
|
||||
}
|
||||
|
||||
return try programs.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn init(src: [:0]const u8) !CustomProgram {
|
||||
const program = try gl.Program.createVF(
|
||||
@embedFile("../shaders/custom.v.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();
|
||||
{
|
||||
var ubobind = try ubo.bind(.uniform);
|
||||
defer ubobind.unbind();
|
||||
try ubobind.setDataNull(Uniforms, .static_draw);
|
||||
}
|
||||
|
||||
// Setup our VAO for the custom shader.
|
||||
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);
|
||||
|
||||
return .{
|
||||
.program = program,
|
||||
.ubo = ubo,
|
||||
.vao = vao,
|
||||
.ebo = ebo,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: CustomProgram) void {
|
||||
self.ebo.destroy();
|
||||
self.vao.destroy();
|
||||
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();
|
||||
|
||||
const vao = try self.vao.bind();
|
||||
errdefer vao.unbind();
|
||||
|
||||
const ebo = try self.ebo.bind(.element_array);
|
||||
errdefer ebo.unbind();
|
||||
|
||||
return .{
|
||||
.program = program,
|
||||
.vao = vao,
|
||||
.ebo = ebo,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Binding = struct {
|
||||
program: gl.Program.Binding,
|
||||
vao: gl.VertexArray.Binding,
|
||||
ebo: gl.Buffer.Binding,
|
||||
|
||||
pub fn unbind(self: Binding) void {
|
||||
self.ebo.unbind();
|
||||
self.vao.unbind();
|
||||
self.program.unbind();
|
||||
}
|
||||
};
|
172
src/renderer/opengl/custom.zig
Normal file
172
src/renderer/opengl/custom.zig
Normal file
@ -0,0 +1,172 @@
|
||||
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;
|
||||
|
||||
pub const Uniforms = extern struct {
|
||||
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,
|
||||
};
|
||||
|
||||
/// The state associated with custom shaders.
|
||||
pub const State = struct {
|
||||
/// The uniform data
|
||||
uniforms: Uniforms,
|
||||
|
||||
/// The OpenGL buffers
|
||||
ubo: gl.Buffer,
|
||||
vao: gl.VertexArray,
|
||||
ebo: gl.Buffer,
|
||||
|
||||
/// The set of programs for the custom shaders.
|
||||
programs: []const Program,
|
||||
|
||||
/// The last time the frame was drawn. This is used to update
|
||||
/// the time uniform.
|
||||
last_frame_time: std.time.Instant,
|
||||
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
srcs: []const [:0]const u8,
|
||||
) !State {
|
||||
// Create our programs
|
||||
var programs = std.ArrayList(Program).init(alloc);
|
||||
defer programs.deinit();
|
||||
errdefer for (programs.items) |p| p.deinit();
|
||||
for (srcs) |src| {
|
||||
try programs.append(try Program.init(src));
|
||||
}
|
||||
|
||||
// Create our uniform buffer that is shared across all
|
||||
// custom shaders
|
||||
const ubo = try gl.Buffer.create();
|
||||
errdefer ubo.destroy();
|
||||
{
|
||||
var ubobind = try ubo.bind(.uniform);
|
||||
defer ubobind.unbind();
|
||||
try ubobind.setDataNull(Uniforms, .static_draw);
|
||||
}
|
||||
|
||||
// Setup our VAO for the custom shader.
|
||||
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);
|
||||
|
||||
return .{
|
||||
.programs = try programs.toOwnedSlice(),
|
||||
.uniforms = .{},
|
||||
.ubo = ubo,
|
||||
.vao = vao,
|
||||
.ebo = ebo,
|
||||
.last_frame_time = try std.time.Instant.now(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const State, alloc: Allocator) void {
|
||||
for (self.programs) |p| p.deinit();
|
||||
alloc.free(self.programs);
|
||||
self.ubo.destroy();
|
||||
self.ebo.destroy();
|
||||
self.vao.destroy();
|
||||
}
|
||||
|
||||
pub fn syncUniforms(self: *const State) !void {
|
||||
var ubobind = try self.ubo.bind(.uniform);
|
||||
defer ubobind.unbind();
|
||||
try ubobind.setData(self.uniforms, .static_draw);
|
||||
}
|
||||
|
||||
pub fn bind(self: *const State) !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 vao = try self.vao.bind();
|
||||
errdefer vao.unbind();
|
||||
|
||||
const ebo = try self.ebo.bind(.element_array);
|
||||
errdefer ebo.unbind();
|
||||
|
||||
return .{
|
||||
.vao = vao,
|
||||
.ebo = ebo,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Binding = struct {
|
||||
vao: gl.VertexArray.Binding,
|
||||
ebo: gl.Buffer.Binding,
|
||||
|
||||
pub fn unbind(self: Binding) void {
|
||||
self.ebo.unbind();
|
||||
self.vao.unbind();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/// A single OpenGL program (combined shaders) for custom shaders.
|
||||
pub const Program = struct {
|
||||
program: gl.Program,
|
||||
|
||||
pub fn init(src: [:0]const u8) !Program {
|
||||
const program = try gl.Program.createVF(
|
||||
@embedFile("../shaders/custom.v.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);
|
||||
|
||||
return .{ .program = program };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Program) void {
|
||||
self.program.destroy();
|
||||
}
|
||||
|
||||
pub fn bind(self: *const Program) !Binding {
|
||||
const program = try self.program.use();
|
||||
errdefer program.unbind();
|
||||
|
||||
return .{
|
||||
.program = program,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Binding = struct {
|
||||
program: gl.Program.Binding,
|
||||
|
||||
pub fn unbind(self: Binding) void {
|
||||
self.program.unbind();
|
||||
}
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user