ghostty/src/opengl/Buffer.zig
2022-04-05 08:57:44 -07:00

189 lines
5.6 KiB
Zig

const Buffer = @This();
const std = @import("std");
const c = @import("c.zig");
const errors = @import("errors.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);
c.glBufferData(@enumToInt(b.target), info.size, info.ptr, @enumToInt(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);
c.glBufferSubData(@enumToInt(b.target), @intCast(c_long, 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 {
c.glBufferData(@enumToInt(b.target), @sizeOf(T), null, @enumToInt(usage));
try errors.getError();
}
fn dataInfo(data: anytype) struct {
size: isize,
ptr: *const anyopaque,
} {
return switch (@typeInfo(@TypeOf(data))) {
.Array => .{
.size = @sizeOf(@TypeOf(data)),
.ptr = &data,
},
.Pointer => |ptr| switch (ptr.size) {
.One => .{
.size = @sizeOf(ptr.child) * data.len,
.ptr = data,
},
.Slice => .{
.size = @intCast(isize, @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 {
c.glEnableVertexAttribArray(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"),
};
const offsetPtr = if (offset > 0)
@intToPtr(*const anyopaque, offset * info.offset)
else
null;
try b.attributeAdvanced(
idx,
size,
info.typ,
false,
info.stride,
offsetPtr,
);
try b.enableAttribArray(idx);
}
pub inline fn attributeAdvanced(
_: Binding,
idx: c.GLuint,
size: c.GLint,
typ: c.GLenum,
normalized: bool,
stride: c.GLsizei,
ptr: ?*const anyopaque,
) !void {
const normalized_c: c.GLboolean = if (normalized) c.GL_TRUE else c.GL_FALSE;
c.glVertexAttribPointer(idx, size, typ, normalized_c, stride, ptr);
try errors.getError();
}
pub inline fn unbind(b: *Binding) void {
c.glBindBuffer(@enumToInt(b.target), 0);
b.* = undefined;
}
};
/// Create a single buffer.
pub inline fn create() !Buffer {
var vbo: c.GLuint = undefined;
c.glGenBuffers(1, &vbo);
return Buffer{ .id = vbo };
}
/// glBindBuffer
pub inline fn bind(v: Buffer, target: Target) !Binding {
c.glBindBuffer(@enumToInt(target), v.id);
return Binding{ .target = target };
}
pub inline fn destroy(v: Buffer) void {
c.glDeleteBuffers(1, &v.id);
}