summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Hashimoto <mitchell.hashimoto@gmail.com>2023-11-16 21:13:55 -0800
committerMitchell Hashimoto <mitchell.hashimoto@gmail.com>2023-11-17 21:51:04 -0800
commit8576acb89ec926ef2a61e609ec6325befbd1bda7 (patch)
treeb05baad76625720db5fa7346af70ca1a8226b7ea
parent76a88e3fbe218459dcc4f194e29d95a0b09753a3 (diff)
renderer/opengl: move opengl API to pkg/opengl
-rw-r--r--build.zig2
-rw-r--r--build.zig.zon1
-rw-r--r--pkg/opengl/Buffer.zig218
-rw-r--r--pkg/opengl/Program.zig128
-rw-r--r--pkg/opengl/Shader.zig56
-rw-r--r--pkg/opengl/Texture.zig163
-rw-r--r--pkg/opengl/VertexArray.zig29
-rw-r--r--pkg/opengl/build.zig5
-rw-r--r--pkg/opengl/c.zig3
-rw-r--r--pkg/opengl/draw.zig59
-rw-r--r--pkg/opengl/errors.zig33
-rw-r--r--pkg/opengl/extensions.zig32
-rw-r--r--pkg/opengl/glad.zig45
-rw-r--r--pkg/opengl/main.zig23
-rw-r--r--src/renderer/OpenGL.zig27
-rw-r--r--src/renderer/shadertoy.zig17
16 files changed, 833 insertions, 8 deletions
diff --git a/build.zig b/build.zig
index 6146f93f5..cb60aabb7 100644
--- a/build.zig
+++ b/build.zig
@@ -663,6 +663,7 @@ fn addDeps(
.target = step.target,
.optimize = step.optimize,
});
+ const opengl_dep = b.dependency("opengl", .{});
const pixman_dep = b.dependency("pixman", .{
.target = step.target,
.optimize = step.optimize,
@@ -730,6 +731,7 @@ fn addDeps(
step.addModule("spirv_cross", spirv_cross_dep.module("spirv_cross"));
step.addModule("harfbuzz", harfbuzz_dep.module("harfbuzz"));
step.addModule("xev", libxev_dep.module("xev"));
+ step.addModule("opengl", opengl_dep.module("opengl"));
step.addModule("pixman", pixman_dep.module("pixman"));
step.addModule("ziglyph", ziglyph_dep.module("ziglyph"));
diff --git a/build.zig.zon b/build.zig.zon
index 580c8593e..309df74d1 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -32,6 +32,7 @@
.harfbuzz = .{ .path = "./pkg/harfbuzz" },
.libpng = .{ .path = "./pkg/libpng" },
.macos = .{ .path = "./pkg/macos" },
+ .opengl = .{ .path = "./pkg/opengl" },
.pixman = .{ .path = "./pkg/pixman" },
.tracy = .{ .path = "./pkg/tracy" },
.zlib = .{ .path = "./pkg/zlib" },
diff --git a/pkg/opengl/Buffer.zig b/pkg/opengl/Buffer.zig
new file mode 100644
index 000000000..b794ca4f0
--- /dev/null
+++ b/pkg/opengl/Buffer.zig
@@ -0,0 +1,218 @@
+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);
+}
diff --git a/pkg/opengl/Program.zig b/pkg/opengl/Program.zig
new file mode 100644
index 000000000..d266bd226
--- /dev/null
+++ b/pkg/opengl/Program.zig
@@ -0,0 +1,128 @@
+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});
+}
diff --git a/pkg/opengl/Shader.zig b/pkg/opengl/Shader.zig
new file mode 100644
index 000000000..beaae9e94
--- /dev/null
+++ b/pkg/opengl/Shader.zig
@@ -0,0 +1,56 @@
+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});
+}
diff --git a/pkg/opengl/Texture.zig b/pkg/opengl/Texture.zig
new file mode 100644
index 000000000..91a65b565
--- /dev/null
+++ b/pkg/opengl/Texture.zig
@@ -0,0 +1,163 @@
+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);
+}
diff --git a/pkg/opengl/VertexArray.zig b/pkg/opengl/VertexArray.zig
new file mode 100644
index 000000000..b86794042
--- /dev/null
+++ b/pkg/opengl/VertexArray.zig
@@ -0,0 +1,29 @@
+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);
+}
diff --git a/pkg/opengl/build.zig b/pkg/opengl/build.zig
new file mode 100644
index 000000000..34e5a8ab1
--- /dev/null
+++ b/pkg/opengl/build.zig
@@ -0,0 +1,5 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) !void {
+ _ = b.addModule("opengl", .{ .source_file = .{ .path = "main.zig" } });
+}
diff --git a/pkg/opengl/c.zig b/pkg/opengl/c.zig
new file mode 100644
index 000000000..8f4a0f22f
--- /dev/null
+++ b/pkg/opengl/c.zig
@@ -0,0 +1,3 @@
+pub usingnamespace @cImport({
+ @cInclude("glad/gl.h");
+});
diff --git a/pkg/opengl/draw.zig b/pkg/opengl/draw.zig
new file mode 100644
index 000000000..ea6b63103
--- /dev/null
+++ b/pkg/opengl/draw.zig
@@ -0,0 +1,59 @@
+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();
+}
diff --git a/pkg/opengl/errors.zig b/pkg/opengl/errors.zig
new file mode 100644
index 000000000..86639a53a
--- /dev/null
+++ b/pkg/opengl/errors.zig
@@ -0,0 +1,33 @@
+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;
+}
diff --git a/pkg/opengl/extensions.zig b/pkg/opengl/extensions.zig
new file mode 100644
index 000000000..ca8a4973d
--- /dev/null
+++ b/pkg/opengl/extensions.zig
@@ -0,0 +1,32 @@
+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);
+ }
+};
diff --git a/pkg/opengl/glad.zig b/pkg/opengl/glad.zig
new file mode 100644
index 000000000..4ee85c549
--- /dev/null
+++ b/pkg/opengl/glad.zig
@@ -0,0 +1,45 @@
+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);
+}
diff --git a/pkg/opengl/main.zig b/pkg/opengl/main.zig
new file mode 100644
index 000000000..79d32acea
--- /dev/null
+++ b/pkg/opengl/main.zig
@@ -0,0 +1,23 @@
+//! 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");
diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig
index 32781a7eb..ce442ed48 100644
--- a/src/renderer/OpenGL.zig
+++ b/src/renderer/OpenGL.zig
@@ -7,6 +7,7 @@ const glfw = @import("glfw");
const assert = std.debug.assert;
const testing = std.testing;
const Allocator = std.mem.Allocator;
+const ArenaAllocator = std.heap.ArenaAllocator;
const apprt = @import("../apprt.zig");
const configpkg = @import("../config.zig");
const font = @import("../font/main.zig");
@@ -14,7 +15,7 @@ const imgui = @import("imgui");
const renderer = @import("../renderer.zig");
const terminal = @import("../terminal/main.zig");
const Terminal = terminal.Terminal;
-const gl = @import("opengl/main.zig");
+const gl = @import("opengl");
const trace = @import("tracy").trace;
const math = @import("../math.zig");
const Surface = @import("../Surface.zig");
@@ -226,8 +227,10 @@ const GPUCellMode = enum(u8) {
/// configuration. This must be exported so that we don't need to
/// pass around Config pointers which makes memory management a pain.
pub const DerivedConfig = struct {
+ arena: ArenaAllocator,
+
font_thicken: bool,
- font_features: std.ArrayList([]const u8),
+ font_features: std.ArrayListUnmanaged([]const u8),
font_styles: font.Group.StyleStatus,
cursor_color: ?terminal.color.RGB,
cursor_text: ?terminal.color.RGB,
@@ -238,17 +241,21 @@ pub const DerivedConfig = struct {
selection_background: ?terminal.color.RGB,
selection_foreground: ?terminal.color.RGB,
invert_selection_fg_bg: bool,
+ custom_shaders: std.ArrayListUnmanaged([]const u8),
pub fn init(
alloc_gpa: Allocator,
config: *const configpkg.Config,
) !DerivedConfig {
+ var arena = ArenaAllocator.init(alloc_gpa);
+ errdefer arena.deinit();
+ const alloc = arena.allocator();
+
+ // Copy our shaders
+ const custom_shaders = try config.@"custom-shader".value.list.clone(alloc);
+
// Copy our font features
- var font_features = features: {
- var clone = try config.@"font-feature".list.clone(alloc_gpa);
- break :features clone.toManaged(alloc_gpa);
- };
- errdefer font_features.deinit();
+ const font_features = try config.@"font-feature".list.clone(alloc);
// Get our font styles
var font_styles = font.Group.StyleStatus.initFill(true);
@@ -287,11 +294,15 @@ pub const DerivedConfig = struct {
bg.toTerminalRGB()
else
null,
+
+ .custom_shaders = custom_shaders,
+
+ .arena = arena,
};
}
pub fn deinit(self: *DerivedConfig) void {
- self.font_features.deinit();
+ self.arena.deinit();
}
};
diff --git a/src/renderer/shadertoy.zig b/src/renderer/shadertoy.zig
index 45c225d85..92b340e76 100644
--- a/src/renderer/shadertoy.zig
+++ b/src/renderer/shadertoy.zig
@@ -335,5 +335,22 @@ test "shadertoy to msl" {
defer alloc.free(msl);
}
+test "shadertoy to glsl" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const src = try testGlslZ(alloc, test_crt);
+ defer alloc.free(src);
+
+ var spvlist = std.ArrayListAligned(u8, @alignOf(u32)).init(alloc);
+ defer spvlist.deinit();
+ try spirvFromGlsl(spvlist.writer(), null, src);
+
+ const glsl = try glslFromSpv(alloc, spvlist.items);
+ defer alloc.free(glsl);
+
+ //log.warn("glsl={s}", .{glsl});
+}
+
const test_crt = @embedFile("shaders/test_shadertoy_crt.glsl");
const test_invalid = @embedFile("shaders/test_shadertoy_invalid.glsl");