renderer/opengl: move opengl API to pkg/opengl

This commit is contained in:
Mitchell Hashimoto
2023-11-16 21:13:55 -08:00
parent 76a88e3fbe
commit 8576acb89e
16 changed files with 833 additions and 8 deletions

View File

@ -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"));

View File

@ -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" },

218
pkg/opengl/Buffer.zig Normal file
View File

@ -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);
}

128
pkg/opengl/Program.zig Normal file
View File

@ -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});
}

56
pkg/opengl/Shader.zig Normal file
View File

@ -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});
}

163
pkg/opengl/Texture.zig Normal file
View File

@ -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);
}

View File

@ -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);
}

5
pkg/opengl/build.zig Normal file
View File

@ -0,0 +1,5 @@
const std = @import("std");
pub fn build(b: *std.Build) !void {
_ = b.addModule("opengl", .{ .source_file = .{ .path = "main.zig" } });
}

3
pkg/opengl/c.zig Normal file
View File

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

59
pkg/opengl/draw.zig Normal file
View File

@ -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();
}

33
pkg/opengl/errors.zig Normal file
View File

@ -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;
}

32
pkg/opengl/extensions.zig Normal file
View File

@ -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);
}
};

45
pkg/opengl/glad.zig Normal file
View File

@ -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);
}

23
pkg/opengl/main.zig Normal file
View File

@ -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");

View File

@ -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();
}
};

View File

@ -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");