mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
renderer/opengl: extract cell program state to dedicated struct
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
@ -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});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
183
src/renderer/opengl/CellProgram.zig
Normal file
183
src/renderer/opengl/CellProgram.zig
Normal 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();
|
||||
}
|
||||
};
|
@ -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});
|
||||
}
|
@ -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});
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
pub usingnamespace @cImport({
|
||||
@cInclude("glad/gl.h");
|
||||
});
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
@ -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);
|
||||
}
|
@ -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");
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user