renderer/opengl: extract cell program state to dedicated struct

This commit is contained in:
Mitchell Hashimoto
2023-11-17 08:52:34 -08:00
parent 46dd084ee9
commit fb0929a11b
18 changed files with 250 additions and 930 deletions

View File

@ -36,7 +36,7 @@ pub const Binding = struct {
/// 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 inline fn setData(
pub fn setData(
b: Binding,
data: anytype,
usage: Usage,
@ -48,7 +48,7 @@ pub const Binding = struct {
/// 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 inline fn setSubData(
pub fn setSubData(
b: Binding,
offset: usize,
data: anytype,
@ -61,7 +61,7 @@ pub const Binding = struct {
/// Sets the buffer data with a null buffer that is expected to be
/// filled in the future using subData. This requires the type just so
/// we can setup the data size.
pub inline fn setDataNull(
pub fn setDataNull(
b: Binding,
comptime T: type,
usage: Usage,
@ -71,7 +71,7 @@ pub const Binding = struct {
}
/// Same as setDataNull but lets you manually specify the buffer size.
pub inline fn setDataNullManual(
pub fn setDataNullManual(
b: Binding,
size: usize,
usage: Usage,
@ -106,7 +106,7 @@ pub const Binding = struct {
};
}
pub inline fn enableAttribArray(_: Binding, idx: c.GLuint) !void {
pub fn enableAttribArray(_: Binding, idx: c.GLuint) !void {
glad.context.EnableVertexAttribArray.?(idx);
}
@ -158,7 +158,7 @@ pub const Binding = struct {
try errors.getError();
}
pub inline fn attributeAdvanced(
pub fn attributeAdvanced(
_: Binding,
idx: c.GLuint,
size: c.GLint,
@ -177,7 +177,7 @@ pub const Binding = struct {
try errors.getError();
}
pub inline fn attributeIAdvanced(
pub fn attributeIAdvanced(
_: Binding,
idx: c.GLuint,
size: c.GLint,
@ -194,25 +194,24 @@ pub const Binding = struct {
try errors.getError();
}
pub inline fn unbind(b: *Binding) void {
pub fn unbind(b: Binding) void {
glad.context.BindBuffer.?(@intFromEnum(b.target), 0);
b.* = undefined;
}
};
/// Create a single buffer.
pub inline fn create() !Buffer {
pub fn create() !Buffer {
var vbo: c.GLuint = undefined;
glad.context.GenBuffers.?(1, &vbo);
return Buffer{ .id = vbo };
}
/// glBindBuffer
pub inline fn bind(v: Buffer, target: Target) !Binding {
pub fn bind(v: Buffer, target: Target) !Binding {
glad.context.BindBuffer.?(@intFromEnum(target), v.id);
return Binding{ .target = target };
}
pub inline fn destroy(v: Buffer) void {
pub fn destroy(v: Buffer) void {
glad.context.DeleteBuffers.?(1, &v.id);
}

View File

@ -11,23 +11,22 @@ const glad = @import("glad.zig");
id: c.GLuint,
const Binding = struct {
pub inline fn unbind(_: Binding) void {
pub const Binding = struct {
pub fn unbind(_: Binding) void {
glad.context.UseProgram.?(0);
}
};
pub inline fn create() !Program {
pub fn create() !Program {
const id = glad.context.CreateProgram.?();
if (id == 0) try errors.mustError();
log.debug("program created id={}", .{id});
return Program{ .id = id };
return .{ .id = id };
}
/// Create a program from a vertex and fragment shader source. This will
/// compile and link the vertex and fragment shader.
pub inline fn createVF(vsrc: [:0]const u8, fsrc: [:0]const u8) !Program {
pub fn createVF(vsrc: [:0]const u8, fsrc: [:0]const u8) !Program {
const vs = try Shader.create(c.GL_VERTEX_SHADER);
try vs.setSourceAndCompile(vsrc);
defer vs.destroy();
@ -44,12 +43,18 @@ pub inline fn createVF(vsrc: [:0]const u8, fsrc: [:0]const u8) !Program {
return p;
}
pub inline fn attachShader(p: Program, s: Shader) !void {
pub fn destroy(p: Program) void {
assert(p.id != 0);
glad.context.DeleteProgram.?(p.id);
log.debug("program destroyed id={}", .{p.id});
}
pub fn attachShader(p: Program, s: Shader) !void {
glad.context.AttachShader.?(p.id, s.id);
try errors.getError();
}
pub inline fn link(p: Program) !void {
pub fn link(p: Program) !void {
glad.context.LinkProgram.?(p.id);
// Check if linking succeeded
@ -67,14 +72,14 @@ pub inline fn link(p: Program) !void {
return error.CompileFailed;
}
pub inline fn use(p: Program) !Binding {
pub fn use(p: Program) !Binding {
glad.context.UseProgram.?(p.id);
try errors.getError();
return Binding{};
return .{};
}
/// Requires the program is currently in use.
pub inline fn setUniform(
pub fn setUniform(
p: Program,
n: [:0]const u8,
value: anytype,
@ -115,14 +120,8 @@ pub inline fn setUniform(
//
// NOTE(mitchellh): we can add a dynamic version that uses an allocator
// if we ever need it.
pub inline fn getInfoLog(s: Program) [512]u8 {
pub fn getInfoLog(s: Program) [512]u8 {
var msg: [512]u8 = undefined;
glad.context.GetProgramInfoLog.?(s.id, msg.len, null, &msg);
return msg;
}
pub inline fn destroy(p: Program) void {
assert(p.id != 0);
glad.context.DeleteProgram.?(p.id);
log.debug("program destroyed id={}", .{p.id});
}

View File

@ -7,23 +7,26 @@ const errors = @import("errors.zig");
id: c.GLuint,
/// Create a single vertex array object.
pub inline fn create() !VertexArray {
pub fn create() !VertexArray {
var vao: c.GLuint = undefined;
glad.context.GenVertexArrays.?(1, &vao);
return VertexArray{ .id = vao };
}
// Unbind any active vertex array.
pub inline fn unbind() !void {
glad.context.BindVertexArray.?(0);
}
/// glBindVertexArray
pub inline fn bind(v: VertexArray) !void {
pub fn bind(v: VertexArray) !Binding {
glad.context.BindVertexArray.?(v.id);
try errors.getError();
return .{};
}
pub inline fn destroy(v: VertexArray) void {
pub fn destroy(v: VertexArray) void {
glad.context.DeleteVertexArrays.?(1, &v.id);
}
pub const Binding = struct {
pub fn unbind(self: Binding) void {
_ = self;
glad.context.BindVertexArray.?(0);
}
};

View File

@ -6,7 +6,7 @@ const assert = std.debug.assert;
const cimgui = @import("cimgui");
const c = @import("c.zig");
const key = @import("key.zig");
const gl = @import("../../renderer/opengl/main.zig");
const gl = @import("opengl");
const input = @import("../../input.zig");
const log = std.log.scoped(.gtk_imgui_widget);

View File

@ -21,6 +21,8 @@ const trace = @import("tracy").trace;
const math = @import("../math.zig");
const Surface = @import("../Surface.zig");
const CellProgram = @import("opengl/CellProgram.zig");
const log = std.log.scoped(.grid);
/// The runtime can request a single-threaded draw by setting this boolean
@ -132,7 +134,7 @@ const SetScreenSize = struct {
);
// Update the projection uniform within our shader
try gl_state.program.setUniform(
try gl_state.cell_program.program.setUniform(
"projection",
// 2D orthographic projection with the full w/h
@ -152,18 +154,18 @@ const SetFontSize = struct {
fn apply(self: SetFontSize, r: *const OpenGL) !void {
const gl_state = r.gl_state orelse return error.OpenGLUninitialized;
try gl_state.program.setUniform(
try gl_state.cell_program.program.setUniform(
"cell_size",
@Vector(2, f32){
@floatFromInt(self.metrics.cell_width),
@floatFromInt(self.metrics.cell_height),
},
);
try gl_state.program.setUniform(
try gl_state.cell_program.program.setUniform(
"strikethrough_position",
@as(f32, @floatFromInt(self.metrics.strikethrough_position)),
);
try gl_state.program.setUniform(
try gl_state.cell_program.program.setUniform(
"strikethrough_thickness",
@as(f32, @floatFromInt(self.metrics.strikethrough_thickness)),
);
@ -1471,17 +1473,9 @@ pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
);
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
// Setup our VAO
try gl_state.vao.bind();
defer gl.VertexArray.unbind() catch null;
// Bind EBO
var ebobind = try gl_state.ebo.bind(.ElementArrayBuffer);
defer ebobind.unbind();
// Bind VBO and set data
var binding = try gl_state.vbo.bind(.ArrayBuffer);
defer binding.unbind();
// Bind our cell program state, buffers
const bind = try gl_state.cell_program.bind();
defer bind.unbind();
// Bind our textures
try gl.Texture.active(gl.c.GL_TEXTURE0);
@ -1492,10 +1486,6 @@ pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
var texbind1 = try gl_state.texture_color.bind(.@"2D");
defer texbind1.unbind();
// Pick our shader to use
const pbind = try gl_state.program.use();
defer pbind.unbind();
// If we have deferred operations, run them.
if (self.deferred_screen_size) |v| {
try v.apply(self);
@ -1506,8 +1496,8 @@ pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
self.deferred_font_size = null;
}
try self.drawCells(binding, self.cells_bg);
try self.drawCells(binding, self.cells);
try self.drawCells(bind.vbo, self.cells_bg);
try self.drawCells(bind.vbo, self.cells);
// Swap our window buffers
switch (apprt.runtime) {
@ -1573,10 +1563,7 @@ fn drawCells(
/// easy to create/destroy these as a set in situations i.e. where the
/// OpenGL context is replaced.
const GLState = struct {
program: gl.Program,
vao: gl.VertexArray,
ebo: gl.Buffer,
vbo: gl.Buffer,
cell_program: CellProgram,
texture: gl.Texture,
texture_color: gl.Texture,
@ -1614,74 +1601,6 @@ const GLState = struct {
try gl.enable(gl.c.GL_BLEND);
try gl.blendFunc(gl.c.GL_ONE, gl.c.GL_ONE_MINUS_SRC_ALPHA);
// Shader
const program = try gl.Program.createVF(
@embedFile("shaders/cell.v.glsl"),
@embedFile("shaders/cell.f.glsl"),
);
// Set our cell dimensions
const pbind = try program.use();
defer pbind.unbind();
// Set all of our texture indexes
try program.setUniform("text", 0);
try program.setUniform("text_color", 1);
// Setup our VAO
const vao = try gl.VertexArray.create();
errdefer vao.destroy();
try vao.bind();
defer gl.VertexArray.unbind() catch null;
// Element buffer (EBO)
const ebo = try gl.Buffer.create();
errdefer ebo.destroy();
var ebobind = try ebo.bind(.ElementArrayBuffer);
defer ebobind.unbind();
try ebobind.setData([6]u8{
0, 1, 3, // Top-left triangle
1, 2, 3, // Bottom-right triangle
}, .StaticDraw);
// Vertex buffer (VBO)
const vbo = try gl.Buffer.create();
errdefer vbo.destroy();
var vbobind = try vbo.bind(.ArrayBuffer);
defer vbobind.unbind();
var offset: usize = 0;
try vbobind.attributeAdvanced(0, 2, gl.c.GL_UNSIGNED_SHORT, false, @sizeOf(GPUCell), offset);
offset += 2 * @sizeOf(u16);
try vbobind.attributeAdvanced(1, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(GPUCell), offset);
offset += 2 * @sizeOf(u32);
try vbobind.attributeAdvanced(2, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(GPUCell), offset);
offset += 2 * @sizeOf(u32);
try vbobind.attributeAdvanced(3, 2, gl.c.GL_INT, false, @sizeOf(GPUCell), offset);
offset += 2 * @sizeOf(i32);
try vbobind.attributeAdvanced(4, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(GPUCell), offset);
offset += 4 * @sizeOf(u8);
try vbobind.attributeAdvanced(5, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(GPUCell), offset);
offset += 4 * @sizeOf(u8);
try vbobind.attributeIAdvanced(6, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(GPUCell), offset);
offset += 1 * @sizeOf(u8);
try vbobind.attributeIAdvanced(7, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(GPUCell), offset);
try vbobind.enableAttribArray(0);
try vbobind.enableAttribArray(1);
try vbobind.enableAttribArray(2);
try vbobind.enableAttribArray(3);
try vbobind.enableAttribArray(4);
try vbobind.enableAttribArray(5);
try vbobind.enableAttribArray(6);
try vbobind.enableAttribArray(7);
try vbobind.attributeDivisor(0, 1);
try vbobind.attributeDivisor(1, 1);
try vbobind.attributeDivisor(2, 1);
try vbobind.attributeDivisor(3, 1);
try vbobind.attributeDivisor(4, 1);
try vbobind.attributeDivisor(5, 1);
try vbobind.attributeDivisor(6, 1);
try vbobind.attributeDivisor(7, 1);
// Build our texture
const tex = try gl.Texture.create();
errdefer tex.destroy();
@ -1724,11 +1643,12 @@ const GLState = struct {
);
}
// Build our cell renderer
const cell_program = try CellProgram.init();
errdefer cell_program.deinit();
return .{
.program = program,
.vao = vao,
.ebo = ebo,
.vbo = vbo,
.cell_program = cell_program,
.texture = tex,
.texture_color = tex_color,
};
@ -1737,9 +1657,6 @@ const GLState = struct {
pub fn deinit(self: *GLState) void {
self.texture.destroy();
self.texture_color.destroy();
self.vbo.destroy();
self.ebo.destroy();
self.vao.destroy();
self.program.destroy();
self.cell_program.deinit();
}
};

View File

@ -1,218 +0,0 @@
const Buffer = @This();
const std = @import("std");
const c = @import("c.zig");
const errors = @import("errors.zig");
const glad = @import("glad.zig");
id: c.GLuint,
/// Enum for possible binding targets.
pub const Target = enum(c_uint) {
ArrayBuffer = c.GL_ARRAY_BUFFER,
ElementArrayBuffer = c.GL_ELEMENT_ARRAY_BUFFER,
_,
};
/// Enum for possible buffer usages.
pub const Usage = enum(c_uint) {
StreamDraw = c.GL_STREAM_DRAW,
StreamRead = c.GL_STREAM_READ,
StreamCopy = c.GL_STREAM_COPY,
StaticDraw = c.GL_STATIC_DRAW,
StaticRead = c.GL_STATIC_READ,
StaticCopy = c.GL_STATIC_COPY,
DynamicDraw = c.GL_DYNAMIC_DRAW,
DynamicRead = c.GL_DYNAMIC_READ,
DynamicCopy = c.GL_DYNAMIC_COPY,
_,
};
/// 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 {
target: Target,
/// 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 inline fn setData(
b: Binding,
data: anytype,
usage: Usage,
) !void {
const info = dataInfo(&data);
glad.context.BufferData.?(@intFromEnum(b.target), info.size, info.ptr, @intFromEnum(usage));
try errors.getError();
}
/// 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 inline fn setSubData(
b: Binding,
offset: usize,
data: anytype,
) !void {
const info = dataInfo(data);
glad.context.BufferSubData.?(@intFromEnum(b.target), @intCast(offset), info.size, info.ptr);
try errors.getError();
}
/// Sets the buffer data with a null buffer that is expected to be
/// filled in the future using subData. This requires the type just so
/// we can setup the data size.
pub inline fn setDataNull(
b: Binding,
comptime T: type,
usage: Usage,
) !void {
glad.context.BufferData.?(@intFromEnum(b.target), @sizeOf(T), null, @intFromEnum(usage));
try errors.getError();
}
/// Same as setDataNull but lets you manually specify the buffer size.
pub inline fn setDataNullManual(
b: Binding,
size: usize,
usage: Usage,
) !void {
glad.context.BufferData.?(@intFromEnum(b.target), @intCast(size), null, @intFromEnum(usage));
try errors.getError();
}
fn dataInfo(data: anytype) struct {
size: isize,
ptr: *const anyopaque,
} {
return switch (@typeInfo(@TypeOf(data))) {
.Pointer => |ptr| switch (ptr.size) {
.One => .{
.size = @sizeOf(ptr.child) * data.len,
.ptr = data,
},
.Slice => .{
.size = @intCast(@sizeOf(ptr.child) * data.len),
.ptr = data.ptr,
},
else => {
std.log.err("invalid buffer data pointer size: {}", .{ptr.size});
unreachable;
},
},
else => {
std.log.err("invalid buffer data type: {s}", .{@tagName(@typeInfo(@TypeOf(data)))});
unreachable;
},
};
}
pub inline fn enableAttribArray(_: Binding, idx: c.GLuint) !void {
glad.context.EnableVertexAttribArray.?(idx);
}
/// Shorthand for vertexAttribPointer that is specialized towards the
/// common use case of specifying an array of homogeneous types that
/// don't need normalization. This also enables the attribute at idx.
pub fn attribute(
b: Binding,
idx: c.GLuint,
size: c.GLint,
comptime T: type,
offset: usize,
) !void {
const info: struct {
// Type of the each component in the array.
typ: c.GLenum,
// The byte offset between each full set of attributes.
stride: c.GLsizei,
// The size of each component used in calculating the offset.
offset: usize,
} = switch (@typeInfo(T)) {
.Array => |ary| .{
.typ = switch (ary.child) {
f32 => c.GL_FLOAT,
else => @compileError("unsupported array child type"),
},
.offset = @sizeOf(ary.child),
.stride = @sizeOf(T),
},
else => @compileError("unsupported type"),
};
try b.attributeAdvanced(
idx,
size,
info.typ,
false,
info.stride,
offset * info.offset,
);
try b.enableAttribArray(idx);
}
/// VertexAttribDivisor
pub fn attributeDivisor(_: Binding, idx: c.GLuint, divisor: c.GLuint) !void {
glad.context.VertexAttribDivisor.?(idx, divisor);
try errors.getError();
}
pub inline fn attributeAdvanced(
_: Binding,
idx: c.GLuint,
size: c.GLint,
typ: c.GLenum,
normalized: bool,
stride: c.GLsizei,
offset: usize,
) !void {
const normalized_c: c.GLboolean = if (normalized) c.GL_TRUE else c.GL_FALSE;
const offsetPtr = if (offset > 0)
@as(*const anyopaque, @ptrFromInt(offset))
else
null;
glad.context.VertexAttribPointer.?(idx, size, typ, normalized_c, stride, offsetPtr);
try errors.getError();
}
pub inline fn attributeIAdvanced(
_: Binding,
idx: c.GLuint,
size: c.GLint,
typ: c.GLenum,
stride: c.GLsizei,
offset: usize,
) !void {
const offsetPtr = if (offset > 0)
@as(*const anyopaque, @ptrFromInt(offset))
else
null;
glad.context.VertexAttribIPointer.?(idx, size, typ, stride, offsetPtr);
try errors.getError();
}
pub inline fn unbind(b: *Binding) void {
glad.context.BindBuffer.?(@intFromEnum(b.target), 0);
b.* = undefined;
}
};
/// Create a single buffer.
pub inline fn create() !Buffer {
var vbo: c.GLuint = undefined;
glad.context.GenBuffers.?(1, &vbo);
return Buffer{ .id = vbo };
}
/// glBindBuffer
pub inline fn bind(v: Buffer, target: Target) !Binding {
glad.context.BindBuffer.?(@intFromEnum(target), v.id);
return Binding{ .target = target };
}
pub inline fn destroy(v: Buffer) void {
glad.context.DeleteBuffers.?(1, &v.id);
}

View File

@ -0,0 +1,183 @@
/// The OpenGL program for rendering terminal cells.
const CellProgram = @This();
const std = @import("std");
const gl = @import("opengl");
program: gl.Program,
vao: gl.VertexArray,
ebo: gl.Buffer,
vbo: gl.Buffer,
/// The raw structure that maps directly to the buffer sent to the vertex shader.
/// This must be "extern" so that the field order is not reordered by the
/// Zig compiler.
const Cell = extern struct {
/// vec2 grid_coord
grid_col: u16,
grid_row: u16,
/// vec2 glyph_pos
glyph_x: u32 = 0,
glyph_y: u32 = 0,
/// vec2 glyph_size
glyph_width: u32 = 0,
glyph_height: u32 = 0,
/// vec2 glyph_size
glyph_offset_x: i32 = 0,
glyph_offset_y: i32 = 0,
/// vec4 fg_color_in
fg_r: u8,
fg_g: u8,
fg_b: u8,
fg_a: u8,
/// vec4 bg_color_in
bg_r: u8,
bg_g: u8,
bg_b: u8,
bg_a: u8,
/// uint mode
mode: CellMode,
/// The width in grid cells that a rendering takes.
grid_width: u8,
};
const CellMode = enum(u8) {
bg = 1,
fg = 2,
fg_color = 7,
strikethrough = 8,
// Non-exhaustive because masks change it
_,
/// Apply a mask to the mode.
pub fn mask(self: CellMode, m: CellMode) CellMode {
return @enumFromInt(@intFromEnum(self) | @intFromEnum(m));
}
};
pub fn init() !CellProgram {
// Load and compile our shaders.
const program = try gl.Program.createVF(
@embedFile("../shaders/cell.v.glsl"),
@embedFile("../shaders/cell.f.glsl"),
);
// Set our cell dimensions
const pbind = try program.use();
defer pbind.unbind();
// Set all of our texture indexes
try program.setUniform("text", 0);
try program.setUniform("text_color", 1);
// 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(.ElementArrayBuffer);
defer ebobind.unbind();
try ebobind.setData([6]u8{
0, 1, 3, // Top-left triangle
1, 2, 3, // Bottom-right triangle
}, .StaticDraw);
// Vertex buffer (VBO)
const vbo = try gl.Buffer.create();
errdefer vbo.destroy();
var vbobind = try vbo.bind(.ArrayBuffer);
defer vbobind.unbind();
var offset: usize = 0;
try vbobind.attributeAdvanced(0, 2, gl.c.GL_UNSIGNED_SHORT, false, @sizeOf(Cell), offset);
offset += 2 * @sizeOf(u16);
try vbobind.attributeAdvanced(1, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(Cell), offset);
offset += 2 * @sizeOf(u32);
try vbobind.attributeAdvanced(2, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(Cell), offset);
offset += 2 * @sizeOf(u32);
try vbobind.attributeAdvanced(3, 2, gl.c.GL_INT, false, @sizeOf(Cell), offset);
offset += 2 * @sizeOf(i32);
try vbobind.attributeAdvanced(4, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(Cell), offset);
offset += 4 * @sizeOf(u8);
try vbobind.attributeAdvanced(5, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(Cell), offset);
offset += 4 * @sizeOf(u8);
try vbobind.attributeIAdvanced(6, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(Cell), offset);
offset += 1 * @sizeOf(u8);
try vbobind.attributeIAdvanced(7, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(Cell), offset);
try vbobind.enableAttribArray(0);
try vbobind.enableAttribArray(1);
try vbobind.enableAttribArray(2);
try vbobind.enableAttribArray(3);
try vbobind.enableAttribArray(4);
try vbobind.enableAttribArray(5);
try vbobind.enableAttribArray(6);
try vbobind.enableAttribArray(7);
try vbobind.attributeDivisor(0, 1);
try vbobind.attributeDivisor(1, 1);
try vbobind.attributeDivisor(2, 1);
try vbobind.attributeDivisor(3, 1);
try vbobind.attributeDivisor(4, 1);
try vbobind.attributeDivisor(5, 1);
try vbobind.attributeDivisor(6, 1);
try vbobind.attributeDivisor(7, 1);
return .{
.program = program,
.vao = vao,
.ebo = ebo,
.vbo = vbo,
};
}
pub fn bind(self: CellProgram) !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(.ElementArrayBuffer);
errdefer ebo.unbind();
const vbo = try self.vbo.bind(.ArrayBuffer);
errdefer vbo.unbind();
return .{
.program = program,
.vao = vao,
.ebo = ebo,
.vbo = vbo,
};
}
pub fn deinit(self: CellProgram) 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();
}
};

View File

@ -1,128 +0,0 @@
const Program = @This();
const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.opengl);
const c = @import("c.zig");
const Shader = @import("Shader.zig");
const errors = @import("errors.zig");
const glad = @import("glad.zig");
id: c.GLuint,
const Binding = struct {
pub inline fn unbind(_: Binding) void {
glad.context.UseProgram.?(0);
}
};
pub inline fn create() !Program {
const id = glad.context.CreateProgram.?();
if (id == 0) try errors.mustError();
log.debug("program created id={}", .{id});
return Program{ .id = id };
}
/// Create a program from a vertex and fragment shader source. This will
/// compile and link the vertex and fragment shader.
pub inline fn createVF(vsrc: [:0]const u8, fsrc: [:0]const u8) !Program {
const vs = try Shader.create(c.GL_VERTEX_SHADER);
try vs.setSourceAndCompile(vsrc);
defer vs.destroy();
const fs = try Shader.create(c.GL_FRAGMENT_SHADER);
try fs.setSourceAndCompile(fsrc);
defer fs.destroy();
const p = try create();
try p.attachShader(vs);
try p.attachShader(fs);
try p.link();
return p;
}
pub inline fn attachShader(p: Program, s: Shader) !void {
glad.context.AttachShader.?(p.id, s.id);
try errors.getError();
}
pub inline fn link(p: Program) !void {
glad.context.LinkProgram.?(p.id);
// Check if linking succeeded
var success: c_int = undefined;
glad.context.GetProgramiv.?(p.id, c.GL_LINK_STATUS, &success);
if (success == c.GL_TRUE) {
log.debug("program linked id={}", .{p.id});
return;
}
log.err("program link failure id={} message={s}", .{
p.id,
std.mem.sliceTo(&p.getInfoLog(), 0),
});
return error.CompileFailed;
}
pub inline fn use(p: Program) !Binding {
glad.context.UseProgram.?(p.id);
try errors.getError();
return Binding{};
}
/// Requires the program is currently in use.
pub inline fn setUniform(
p: Program,
n: [:0]const u8,
value: anytype,
) !void {
const loc = glad.context.GetUniformLocation.?(
p.id,
@ptrCast(n.ptr),
);
if (loc < 0) {
return error.UniformNameInvalid;
}
try errors.getError();
// Perform the correct call depending on the type of the value.
switch (@TypeOf(value)) {
comptime_int => glad.context.Uniform1i.?(loc, value),
f32 => glad.context.Uniform1f.?(loc, value),
@Vector(2, f32) => glad.context.Uniform2f.?(loc, value[0], value[1]),
@Vector(3, f32) => glad.context.Uniform3f.?(loc, value[0], value[1], value[2]),
@Vector(4, f32) => glad.context.Uniform4f.?(loc, value[0], value[1], value[2], value[3]),
[4]@Vector(4, f32) => glad.context.UniformMatrix4fv.?(
loc,
1,
c.GL_FALSE,
@ptrCast(&value),
),
else => {
log.warn("unsupported uniform type {}", .{@TypeOf(value)});
unreachable;
},
}
try errors.getError();
}
/// getInfoLog returns the info log for this program. This attempts to
/// keep the log fully stack allocated and is therefore limited to a max
/// amount of elements.
//
// NOTE(mitchellh): we can add a dynamic version that uses an allocator
// if we ever need it.
pub inline fn getInfoLog(s: Program) [512]u8 {
var msg: [512]u8 = undefined;
glad.context.GetProgramInfoLog.?(s.id, msg.len, null, &msg);
return msg;
}
pub inline fn destroy(p: Program) void {
assert(p.id != 0);
glad.context.DeleteProgram.?(p.id);
log.debug("program destroyed id={}", .{p.id});
}

View File

@ -1,56 +0,0 @@
const Shader = @This();
const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.opengl);
const c = @import("c.zig");
const errors = @import("errors.zig");
const glad = @import("glad.zig");
id: c.GLuint,
pub inline fn create(typ: c.GLenum) errors.Error!Shader {
const id = glad.context.CreateShader.?(typ);
if (id == 0) {
try errors.mustError();
unreachable;
}
log.debug("shader created id={}", .{id});
return Shader{ .id = id };
}
/// Set the source and compile a shader.
pub inline fn setSourceAndCompile(s: Shader, source: [:0]const u8) !void {
glad.context.ShaderSource.?(s.id, 1, &@as([*c]const u8, @ptrCast(source)), null);
glad.context.CompileShader.?(s.id);
// Check if compilation succeeded
var success: c_int = undefined;
glad.context.GetShaderiv.?(s.id, c.GL_COMPILE_STATUS, &success);
if (success == c.GL_TRUE) return;
log.err("shader compilation failure id={} message={s}", .{
s.id,
std.mem.sliceTo(&s.getInfoLog(), 0),
});
return error.CompileFailed;
}
/// getInfoLog returns the info log for this shader. This attempts to
/// keep the log fully stack allocated and is therefore limited to a max
/// amount of elements.
//
// NOTE(mitchellh): we can add a dynamic version that uses an allocator
// if we ever need it.
pub inline fn getInfoLog(s: Shader) [512]u8 {
var msg: [512]u8 = undefined;
glad.context.GetShaderInfoLog.?(s.id, msg.len, null, &msg);
return msg;
}
pub inline fn destroy(s: Shader) void {
assert(s.id != 0);
glad.context.DeleteShader.?(s.id);
log.debug("shader destroyed id={}", .{s.id});
}

View File

@ -1,163 +0,0 @@
const Texture = @This();
const std = @import("std");
const c = @import("c.zig");
const errors = @import("errors.zig");
const glad = @import("glad.zig");
id: c.GLuint,
pub inline fn active(target: c.GLenum) !void {
glad.context.ActiveTexture.?(target);
try errors.getError();
}
/// Enun for possible texture binding targets.
pub const Target = enum(c_uint) {
@"1D" = c.GL_TEXTURE_1D,
@"2D" = c.GL_TEXTURE_2D,
@"3D" = c.GL_TEXTURE_3D,
@"1DArray" = c.GL_TEXTURE_1D_ARRAY,
@"2DArray" = c.GL_TEXTURE_2D_ARRAY,
Rectangle = c.GL_TEXTURE_RECTANGLE,
CubeMap = c.GL_TEXTURE_CUBE_MAP,
Buffer = c.GL_TEXTURE_BUFFER,
@"2DMultisample" = c.GL_TEXTURE_2D_MULTISAMPLE,
@"2DMultisampleArray" = c.GL_TEXTURE_2D_MULTISAMPLE_ARRAY,
};
/// Enum for possible texture parameters.
pub const Parameter = enum(c_uint) {
BaseLevel = c.GL_TEXTURE_BASE_LEVEL,
CompareFunc = c.GL_TEXTURE_COMPARE_FUNC,
CompareMode = c.GL_TEXTURE_COMPARE_MODE,
LodBias = c.GL_TEXTURE_LOD_BIAS,
MinFilter = c.GL_TEXTURE_MIN_FILTER,
MagFilter = c.GL_TEXTURE_MAG_FILTER,
MinLod = c.GL_TEXTURE_MIN_LOD,
MaxLod = c.GL_TEXTURE_MAX_LOD,
MaxLevel = c.GL_TEXTURE_MAX_LEVEL,
SwizzleR = c.GL_TEXTURE_SWIZZLE_R,
SwizzleG = c.GL_TEXTURE_SWIZZLE_G,
SwizzleB = c.GL_TEXTURE_SWIZZLE_B,
SwizzleA = c.GL_TEXTURE_SWIZZLE_A,
WrapS = c.GL_TEXTURE_WRAP_S,
WrapT = c.GL_TEXTURE_WRAP_T,
WrapR = c.GL_TEXTURE_WRAP_R,
};
/// Internal format enum for texture images.
pub const InternalFormat = enum(c_int) {
Red = c.GL_RED,
RGBA = c.GL_RGBA,
// There are so many more that I haven't filled in.
_,
};
/// Format for texture images
pub const Format = enum(c_uint) {
Red = c.GL_RED,
BGRA = c.GL_BGRA,
// There are so many more that I haven't filled in.
_,
};
/// Data type for texture images.
pub const DataType = enum(c_uint) {
UnsignedByte = c.GL_UNSIGNED_BYTE,
// There are so many more that I haven't filled in.
_,
};
pub const Binding = struct {
target: Target,
pub inline fn unbind(b: *Binding) void {
glad.context.BindTexture.?(@intFromEnum(b.target), 0);
b.* = undefined;
}
pub fn generateMipmap(b: Binding) void {
glad.context.GenerateMipmap.?(@intFromEnum(b.target));
}
pub fn parameter(b: Binding, name: Parameter, value: anytype) !void {
switch (@TypeOf(value)) {
c.GLint => glad.context.TexParameteri.?(
@intFromEnum(b.target),
@intFromEnum(name),
value,
),
else => unreachable,
}
}
pub fn image2D(
b: Binding,
level: c.GLint,
internal_format: InternalFormat,
width: c.GLsizei,
height: c.GLsizei,
border: c.GLint,
format: Format,
typ: DataType,
data: ?*const anyopaque,
) !void {
glad.context.TexImage2D.?(
@intFromEnum(b.target),
level,
@intFromEnum(internal_format),
width,
height,
border,
@intFromEnum(format),
@intFromEnum(typ),
data,
);
}
pub fn subImage2D(
b: Binding,
level: c.GLint,
xoffset: c.GLint,
yoffset: c.GLint,
width: c.GLsizei,
height: c.GLsizei,
format: Format,
typ: DataType,
data: ?*const anyopaque,
) !void {
glad.context.TexSubImage2D.?(
@intFromEnum(b.target),
level,
xoffset,
yoffset,
width,
height,
@intFromEnum(format),
@intFromEnum(typ),
data,
);
}
};
/// Create a single texture.
pub inline fn create() !Texture {
var id: c.GLuint = undefined;
glad.context.GenTextures.?(1, &id);
return Texture{ .id = id };
}
/// glBindTexture
pub inline fn bind(v: Texture, target: Target) !Binding {
glad.context.BindTexture.?(@intFromEnum(target), v.id);
try errors.getError();
return Binding{ .target = target };
}
pub inline fn destroy(v: Texture) void {
glad.context.DeleteTextures.?(1, &v.id);
}

View File

@ -1,29 +0,0 @@
const VertexArray = @This();
const c = @import("c.zig");
const glad = @import("glad.zig");
const errors = @import("errors.zig");
id: c.GLuint,
/// Create a single vertex array object.
pub inline fn create() !VertexArray {
var vao: c.GLuint = undefined;
glad.context.GenVertexArrays.?(1, &vao);
return VertexArray{ .id = vao };
}
// Unbind any active vertex array.
pub inline fn unbind() !void {
glad.context.BindVertexArray.?(0);
}
/// glBindVertexArray
pub inline fn bind(v: VertexArray) !void {
glad.context.BindVertexArray.?(v.id);
try errors.getError();
}
pub inline fn destroy(v: VertexArray) void {
glad.context.DeleteVertexArrays.?(1, &v.id);
}

View File

@ -1,3 +0,0 @@
pub usingnamespace @cImport({
@cInclude("glad/gl.h");
});

View File

@ -1,59 +0,0 @@
const c = @import("c.zig");
const errors = @import("errors.zig");
const glad = @import("glad.zig");
pub fn clearColor(r: f32, g: f32, b: f32, a: f32) void {
glad.context.ClearColor.?(r, g, b, a);
}
pub fn clear(mask: c.GLbitfield) void {
glad.context.Clear.?(mask);
}
pub fn drawArrays(mode: c.GLenum, first: c.GLint, count: c.GLsizei) !void {
glad.context.DrawArrays.?(mode, first, count);
try errors.getError();
}
pub fn drawElements(mode: c.GLenum, count: c.GLsizei, typ: c.GLenum, offset: usize) !void {
const offsetPtr = if (offset == 0) null else @as(*const anyopaque, @ptrFromInt(offset));
glad.context.DrawElements.?(mode, count, typ, offsetPtr);
try errors.getError();
}
pub fn drawElementsInstanced(
mode: c.GLenum,
count: c.GLsizei,
typ: c.GLenum,
primcount: usize,
) !void {
glad.context.DrawElementsInstanced.?(mode, count, typ, null, @intCast(primcount));
try errors.getError();
}
pub fn enable(cap: c.GLenum) !void {
glad.context.Enable.?(cap);
try errors.getError();
}
pub fn frontFace(mode: c.GLenum) !void {
glad.context.FrontFace.?(mode);
try errors.getError();
}
pub fn blendFunc(sfactor: c.GLenum, dfactor: c.GLenum) !void {
glad.context.BlendFunc.?(sfactor, dfactor);
try errors.getError();
}
pub fn viewport(x: c.GLint, y: c.GLint, width: c.GLsizei, height: c.GLsizei) !void {
glad.context.Viewport.?(x, y, width, height);
}
pub fn pixelStore(mode: c.GLenum, value: anytype) !void {
switch (@typeInfo(@TypeOf(value))) {
.ComptimeInt, .Int => glad.context.PixelStorei.?(mode, value),
else => unreachable,
}
try errors.getError();
}

View File

@ -1,33 +0,0 @@
const std = @import("std");
const c = @import("c.zig");
const glad = @import("glad.zig");
pub const Error = error{
InvalidEnum,
InvalidValue,
InvalidOperation,
InvalidFramebufferOperation,
OutOfMemory,
Unknown,
};
/// getError returns the error (if any) from the last OpenGL operation.
pub fn getError() Error!void {
return switch (glad.context.GetError.?()) {
c.GL_NO_ERROR => {},
c.GL_INVALID_ENUM => Error.InvalidEnum,
c.GL_INVALID_VALUE => Error.InvalidValue,
c.GL_INVALID_OPERATION => Error.InvalidOperation,
c.GL_INVALID_FRAMEBUFFER_OPERATION => Error.InvalidFramebufferOperation,
c.GL_OUT_OF_MEMORY => Error.OutOfMemory,
else => Error.Unknown,
};
}
/// mustError just calls getError but always results in an error being returned.
/// If getError has no error, then Unknown is returned.
pub fn mustError() Error!void {
try getError();
return Error.Unknown;
}

View File

@ -1,32 +0,0 @@
const std = @import("std");
const c = @import("c.zig");
const errors = @import("errors.zig");
const glad = @import("glad.zig");
/// Returns the number of extensions.
pub fn len() !u32 {
var n: c.GLint = undefined;
glad.context.GetIntegerv.?(c.GL_NUM_EXTENSIONS, &n);
try errors.getError();
return @intCast(n);
}
/// Returns an iterator for the extensions.
pub fn iterator() !Iterator {
return Iterator{ .len = try len() };
}
/// Iterator for the available extensions.
pub const Iterator = struct {
/// The total number of extensions.
len: c.GLuint = 0,
i: c.GLuint = 0,
pub fn next(self: *Iterator) !?[]const u8 {
if (self.i >= self.len) return null;
const res = glad.context.GetStringi.?(c.GL_EXTENSIONS, self.i);
try errors.getError();
self.i += 1;
return std.mem.sliceTo(res, 0);
}
};

View File

@ -1,45 +0,0 @@
const std = @import("std");
const c = @import("c.zig");
pub const Context = c.GladGLContext;
/// This is the current context. Set this var manually prior to calling
/// any of this package's functions. I know its nasty to have a global but
/// this makes it match OpenGL API styles where it also operates on a
/// threadlocal global.
pub threadlocal var context: Context = undefined;
/// Initialize Glad. This is guaranteed to succeed if no errors are returned.
/// The getProcAddress param is an anytype so that we can accept multiple
/// forms of the function depending on what we're interfacing with.
pub fn load(getProcAddress: anytype) !c_int {
const GlProc = *const fn () callconv(.C) void;
const GlfwFn = *const fn ([*:0]const u8) callconv(.C) ?GlProc;
const res = switch (@TypeOf(getProcAddress)) {
// glfw
GlfwFn => c.gladLoadGLContext(&context, @ptrCast(getProcAddress)),
// null proc address means that we are just loading the globally
// pointed gl functions
@TypeOf(null) => c.gladLoaderLoadGLContext(&context),
// try as-is. If this introduces a compiler error, then add a new case.
else => c.gladLoadGLContext(&context, getProcAddress),
};
if (res == 0) return error.GLInitFailed;
return res;
}
pub fn unload() void {
c.gladLoaderUnloadGLContext(&context);
context = undefined;
}
pub fn versionMajor(res: c_uint) c_uint {
return c.GLAD_VERSION_MAJOR(res);
}
pub fn versionMinor(res: c_uint) c_uint {
return c.GLAD_VERSION_MINOR(res);
}

View File

@ -1,23 +0,0 @@
//! OpenGL bindings.
//!
//! These are purpose-built for usage within this program. While they closely
//! align with the OpenGL C APIs, they aren't meant to be general purpose,
//! they aren't meant to have 100% API coverage, and they aren't meant to
//! be hyper-performant.
//!
//! For performance-intensive or unsupported aspects of OpenGL, the C
//! API is exposed via the `c` constant.
//!
//! WARNING: Lots of performance improvements that we can make with Zig
//! comptime help. I'm deferring this until later but have some fun ideas.
pub const c = @import("c.zig");
pub const glad = @import("glad.zig");
pub usingnamespace @import("draw.zig");
pub const ext = @import("extensions.zig");
pub const Buffer = @import("Buffer.zig");
pub const Program = @import("Program.zig");
pub const Shader = @import("Shader.zig");
pub const Texture = @import("Texture.zig");
pub const VertexArray = @import("VertexArray.zig");

View File

@ -198,13 +198,21 @@ pub fn mslFromSpv(alloc: Allocator, spv: []const u8) ![:0]const u8 {
/// Convert SPIR-V binary to GLSL..
pub fn glslFromSpv(alloc: Allocator, spv: []const u8) ![:0]const u8 {
// Our minimum version for shadertoy shaders is OpenGL 4.2 because
// Spirv-Cross generates binding locations for uniforms which is
// only supported in OpenGL 4.2 and above.
//
// If we can figure out a way to NOT do this then we can lower this
// version.
const GLSL_VERSION = 420;
const c = spvcross.c;
return try spvCross(alloc, c.SPVC_BACKEND_GLSL, spv, (struct {
fn setOptions(options: c.spvc_compiler_options) error{SpvcFailed}!void {
if (c.spvc_compiler_options_set_uint(
options,
c.SPVC_COMPILER_OPTION_GLSL_VERSION,
430,
GLSL_VERSION,
) != c.SPVC_SUCCESS) {
return error.SpvcFailed;
}