const Buffer = @This(); const std = @import("std"); const c = @import("c.zig").c; const errors = @import("errors.zig"); const glad = @import("glad.zig"); id: c.GLuint, /// Create a single buffer. pub fn create() !Buffer { var vbo: c.GLuint = undefined; glad.context.GenBuffers.?(1, &vbo); return Buffer{ .id = vbo }; } /// glBindBuffer pub fn bind(self: Buffer, target: Target) !Binding { glad.context.BindBuffer.?(@intFromEnum(target), self.id); return Binding{ .id = self.id, .target = target }; } pub fn destroy(self: Buffer) void { glad.context.DeleteBuffers.?(1, &self.id); } pub fn bindBase(self: Buffer, target: Target, idx: c.GLuint) !void { glad.context.BindBufferBase.?( @intFromEnum(target), idx, self.id, ); try errors.getError(); } /// 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 { id: c.GLuint, target: Target, pub fn unbind(b: Binding) void { glad.context.BindBuffer.?(@intFromEnum(b.target), 0); } /// 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 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 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 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 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), .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 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 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 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(); } }; /// Enum for possible binding targets. pub const Target = enum(c_uint) { array = c.GL_ARRAY_BUFFER, element_array = c.GL_ELEMENT_ARRAY_BUFFER, uniform = c.GL_UNIFORM_BUFFER, _, }; /// Enum for possible buffer usages. pub const Usage = enum(c_uint) { stream_draw = c.GL_STREAM_DRAW, stream_read = c.GL_STREAM_READ, stream_copy = c.GL_STREAM_COPY, static_draw = c.GL_STATIC_DRAW, static_read = c.GL_STATIC_READ, static_copy = c.GL_STATIC_COPY, dynamic_draw = c.GL_DYNAMIC_DRAW, dynamic_read = c.GL_DYNAMIC_READ, dynamic_copy = c.GL_DYNAMIC_COPY, _, };