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 Surface = @import("../Surface.zig");
|
||||||
|
|
||||||
const CellProgram = @import("opengl/CellProgram.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);
|
const log = std.log.scoped(.grid);
|
||||||
|
|
||||||
@ -1427,16 +1427,29 @@ fn drawCustomPrograms(
|
|||||||
) !void {
|
) !void {
|
||||||
_ = self;
|
_ = 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
|
// Bind our cell program state, buffers
|
||||||
const bind = try program.bind();
|
const bind = try program.bind();
|
||||||
defer bind.unbind();
|
defer bind.unbind();
|
||||||
|
|
||||||
// Sync the uniform data.
|
try gl.drawElementsInstanced(
|
||||||
// TODO: only do this when the data has changed
|
gl.c.GL_TRIANGLES,
|
||||||
try program.syncUniforms();
|
6,
|
||||||
|
gl.c.GL_UNSIGNED_BYTE,
|
||||||
try gl.drawElementsInstanced(gl.c.GL_TRIANGLES, 6, gl.c.GL_UNSIGNED_BYTE, 1);
|
1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1545,7 +1558,7 @@ const GLState = struct {
|
|||||||
cell_program: CellProgram,
|
cell_program: CellProgram,
|
||||||
texture: gl.Texture,
|
texture: gl.Texture,
|
||||||
texture_color: gl.Texture,
|
texture_color: gl.Texture,
|
||||||
custom_programs: []const CustomProgram,
|
custom: ?custom.State,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
@ -1557,7 +1570,8 @@ const GLState = struct {
|
|||||||
const arena_alloc = arena.allocator();
|
const arena_alloc = arena.allocator();
|
||||||
|
|
||||||
// Load our custom shaders
|
// Load our custom shaders
|
||||||
const custom_shaders: []const [:0]const u8 = shadertoy.loadFromFiles(
|
const custom_state: ?custom.State = custom: {
|
||||||
|
const shaders: []const [:0]const u8 = shadertoy.loadFromFiles(
|
||||||
arena_alloc,
|
arena_alloc,
|
||||||
config.custom_shaders.items,
|
config.custom_shaders.items,
|
||||||
.glsl,
|
.glsl,
|
||||||
@ -1565,13 +1579,16 @@ const GLState = struct {
|
|||||||
log.warn("error loading custom shaders err={}", .{err});
|
log.warn("error loading custom shaders err={}", .{err});
|
||||||
break :err &.{};
|
break :err &.{};
|
||||||
};
|
};
|
||||||
|
if (shaders.len == 0) break :custom null;
|
||||||
|
|
||||||
// Create our custom programs
|
break :custom custom.State.init(
|
||||||
const custom_programs = try CustomProgram.createList(alloc, custom_shaders);
|
alloc,
|
||||||
errdefer {
|
shaders,
|
||||||
for (custom_programs) |p| p.deinit();
|
) catch |err| err: {
|
||||||
alloc.free(custom_programs);
|
log.warn("error initializing custom shaders err={}", .{err});
|
||||||
}
|
break :err null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Blending for text. We use GL_ONE here because we should be using
|
// Blending for text. We use GL_ONE here because we should be using
|
||||||
// premultiplied alpha for all our colors in our fragment shaders.
|
// premultiplied alpha for all our colors in our fragment shaders.
|
||||||
@ -1628,15 +1645,14 @@ const GLState = struct {
|
|||||||
|
|
||||||
return .{
|
return .{
|
||||||
.cell_program = cell_program,
|
.cell_program = cell_program,
|
||||||
.custom_programs = custom_programs,
|
|
||||||
.texture = tex,
|
.texture = tex,
|
||||||
.texture_color = tex_color,
|
.texture_color = tex_color,
|
||||||
|
.custom = custom_state,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *GLState, alloc: Allocator) void {
|
pub fn deinit(self: *GLState, alloc: Allocator) void {
|
||||||
for (self.custom_programs) |p| p.deinit();
|
if (self.custom) |v| v.deinit(alloc);
|
||||||
alloc.free(self.custom_programs);
|
|
||||||
self.texture.destroy();
|
self.texture.destroy();
|
||||||
self.texture_color.destroy();
|
self.texture_color.destroy();
|
||||||
self.cell_program.deinit();
|
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