mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-12 10:48:39 +03:00
renderer/opengl: move opengl API to pkg/opengl
This commit is contained in:
@ -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"));
|
||||
|
||||
|
@ -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
218
pkg/opengl/Buffer.zig
Normal 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
128
pkg/opengl/Program.zig
Normal 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
56
pkg/opengl/Shader.zig
Normal 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
163
pkg/opengl/Texture.zig
Normal 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);
|
||||
}
|
29
pkg/opengl/VertexArray.zig
Normal file
29
pkg/opengl/VertexArray.zig
Normal 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
5
pkg/opengl/build.zig
Normal 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
3
pkg/opengl/c.zig
Normal file
@ -0,0 +1,3 @@
|
||||
pub usingnamespace @cImport({
|
||||
@cInclude("glad/gl.h");
|
||||
});
|
59
pkg/opengl/draw.zig
Normal file
59
pkg/opengl/draw.zig
Normal 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
33
pkg/opengl/errors.zig
Normal 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
32
pkg/opengl/extensions.zig
Normal 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
45
pkg/opengl/glad.zig
Normal 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
23
pkg/opengl/main.zig
Normal 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");
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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");
|
||||
|
Reference in New Issue
Block a user