const std = @import("std"); const c_allocator = @import("std").heap.c_allocator; const c = @import("c.zig").c; const cfg = @import("config.zig"); const util = @import("util.zig"); var image_unit_map = [_]bool{false} ** 1024; pub const Texture = struct { pub const Type = enum(c.GLenum) { TEXTURE_1D = c.GL_TEXTURE_1D, TEXTURE_2D = c.GL_TEXTURE_2D, TEXTURE_3D = c.GL_TEXTURE_3D, TEXTURE_CUBE_MAP = c.GL_TEXTURE_CUBE_MAP, TEXTURE_RECTANGLE = c.GL_TEXTURE_RECTANGLE, TEXTURE_2D_MULTISAMPLE = c.GL_TEXTURE_2D_MULTISAMPLE, TEXTURE_1D_ARRAY = c.GL_TEXTURE_1D_ARRAY, TEXTURE_2D_ARRAY = c.GL_TEXTURE_2D_ARRAY, TEXTURE_CUBE_MAP_ARRAY = c.GL_TEXTURE_CUBE_MAP_ARRAY, TEXTURE_BUFFER = c.GL_TEXTURE_BUFFER, TEXTURE_2D_MULTISAMPLE_ARRAY = c.GL_TEXTURE_2D_MULTISAMPLE_ARRAY, }; type: Type, id: c.GLuint, image_unit: ?usize, pub fn create(texture_type: Type) Texture { var self: Texture = .{ .type = texture_type, .id = undefined, .image_unit = null, }; c.glGenTextures(1, &self.id); c.glBindTexture(@intFromEnum(self.type), self.id); defer c.glBindTexture(@intFromEnum(self.type), 0); c.glTexParameteri(@intFromEnum(self.type), c.GL_TEXTURE_MIN_FILTER, c.GL_LINEAR); c.glTexParameteri(@intFromEnum(self.type), c.GL_TEXTURE_MAG_FILTER, c.GL_LINEAR); return self; } pub fn bind(self: *Texture) usize { if (self.image_unit == null) { for (image_unit_map[0..], 1..) |*slot, i| { if (slot.*) continue; slot.* = true; self.image_unit = @intCast(i); break; } } if (self.image_unit) |image_unit| { c.glActiveTexture(@intCast(c.GL_TEXTURE0 + image_unit)); defer c.glActiveTexture(@intCast(c.GL_TEXTURE0)); c.glBindTexture(@intFromEnum(self.type), self.id); return image_unit; } else unreachable; } pub fn destroy(self: *const Texture) void { c.glDeleteTextures(1, &self.id); if (self.image_unit) |image_unit| { image_unit_map[image_unit] = false; } } pub fn allocate( self: *const Texture, width: i32, height: i32, depth: i32, format: c.GLenum, ) void { c.glBindTexture(@intFromEnum(self.type), self.id); defer c.glBindTexture(@intFromEnum(self.type), 0); switch (self.type) { .TEXTURE_2D => c.glTexStorage2D( @intFromEnum(self.type), 1, format, width, height, ), .TEXTURE_2D_ARRAY, .TEXTURE_3D => c.glTexStorage3D( @intFromEnum(self.type), 1, format, width, height, depth, ), else => unreachable, } } pub fn setLayer( self: *const Texture, width: i32, height: i32, layer: i32, format: c.GLenum, data: [*]const u8, ) void { c.glBindTexture(@intFromEnum(self.type), self.id); defer c.glBindTexture(@intFromEnum(self.type), 0); switch (self.type) { .TEXTURE_2D => c.glTexSubImage2D( @intFromEnum(self.type), 0, 0, 0, width, height, format, c.GL_UNSIGNED_BYTE, data, ), .TEXTURE_2D_ARRAY, .TEXTURE_3D => c.glTexSubImage3D( @intFromEnum(self.type), 0, 0, 0, layer, width, height, 1, format, c.GL_UNSIGNED_BYTE, data, ), else => unreachable, } } pub fn setLayerCompressed( self: *const Texture, width: i32, height: i32, layer: i32, format: c.GLenum, data: []const u8, ) void { c.glBindTexture(@intFromEnum(self.type), self.id); defer c.glBindTexture(@intFromEnum(self.type), 0); switch (self.type) { .TEXTURE_2D => c.glCompressedTexSubImage2D( @intFromEnum(self.type), 0, 0, 0, width, height, format, @intCast(data.len), data.ptr, ), .TEXTURE_2D_ARRAY, .TEXTURE_3D => c.glCompressedTexSubImage3D( @intFromEnum(self.type), 0, 0, 0, layer, width, height, 1, format, @intCast(data.len), data.ptr, ), else => unreachable, } } }; const UniformType = enum(c.GLint) { FLOAT = c.GL_FLOAT, FLOAT_VEC2 = c.GL_FLOAT_VEC2, FLOAT_VEC3 = c.GL_FLOAT_VEC3, FLOAT_VEC4 = c.GL_FLOAT_VEC4, DOUBLE = c.GL_DOUBLE, DOUBLE_VEC2 = c.GL_DOUBLE_VEC2, DOUBLE_VEC3 = c.GL_DOUBLE_VEC3, DOUBLE_VEC4 = c.GL_DOUBLE_VEC4, INT = c.GL_INT, INT_VEC2 = c.GL_INT_VEC2, INT_VEC3 = c.GL_INT_VEC3, INT_VEC4 = c.GL_INT_VEC4, UNSIGNED_INT = c.GL_UNSIGNED_INT, UNSIGNED_INT_VEC2 = c.GL_UNSIGNED_INT_VEC2, UNSIGNED_INT_VEC3 = c.GL_UNSIGNED_INT_VEC3, UNSIGNED_INT_VEC4 = c.GL_UNSIGNED_INT_VEC4, BOOL = c.GL_BOOL, BOOL_VEC2 = c.GL_BOOL_VEC2, BOOL_VEC3 = c.GL_BOOL_VEC3, BOOL_VEC4 = c.GL_BOOL_VEC4, FLOAT_MAT2 = c.GL_FLOAT_MAT2, FLOAT_MAT3 = c.GL_FLOAT_MAT3, FLOAT_MAT4 = c.GL_FLOAT_MAT4, FLOAT_MAT2x3 = c.GL_FLOAT_MAT2x3, FLOAT_MAT2x4 = c.GL_FLOAT_MAT2x4, FLOAT_MAT3x2 = c.GL_FLOAT_MAT3x2, FLOAT_MAT3x4 = c.GL_FLOAT_MAT3x4, FLOAT_MAT4x2 = c.GL_FLOAT_MAT4x2, FLOAT_MAT4x3 = c.GL_FLOAT_MAT4x3, DOUBLE_MAT2 = c.GL_DOUBLE_MAT2, DOUBLE_MAT3 = c.GL_DOUBLE_MAT3, DOUBLE_MAT4 = c.GL_DOUBLE_MAT4, DOUBLE_MAT2x3 = c.GL_DOUBLE_MAT2x3, DOUBLE_MAT2x4 = c.GL_DOUBLE_MAT2x4, DOUBLE_MAT3x2 = c.GL_DOUBLE_MAT3x2, DOUBLE_MAT3x4 = c.GL_DOUBLE_MAT3x4, DOUBLE_MAT4x2 = c.GL_DOUBLE_MAT4x2, DOUBLE_MAT4x3 = c.GL_DOUBLE_MAT4x3, SAMPLER_1D = c.GL_SAMPLER_1D, SAMPLER_2D = c.GL_SAMPLER_2D, SAMPLER_3D = c.GL_SAMPLER_3D, SAMPLER_CUBE = c.GL_SAMPLER_CUBE, SAMPLER_1D_SHADOW = c.GL_SAMPLER_1D_SHADOW, SAMPLER_2D_SHADOW = c.GL_SAMPLER_2D_SHADOW, SAMPLER_1D_ARRAY = c.GL_SAMPLER_1D_ARRAY, SAMPLER_2D_ARRAY = c.GL_SAMPLER_2D_ARRAY, SAMPLER_1D_ARRAY_SHADOW = c.GL_SAMPLER_1D_ARRAY_SHADOW, SAMPLER_2D_ARRAY_SHADOW = c.GL_SAMPLER_2D_ARRAY_SHADOW, SAMPLER_2D_MULTISAMPLE = c.GL_SAMPLER_2D_MULTISAMPLE, SAMPLER_2D_MULTISAMPLE_ARRAY = c.GL_SAMPLER_2D_MULTISAMPLE_ARRAY, SAMPLER_CUBE_SHADOW = c.GL_SAMPLER_CUBE_SHADOW, SAMPLER_BUFFER = c.GL_SAMPLER_BUFFER, SAMPLER_2D_RECT = c.GL_SAMPLER_2D_RECT, SAMPLER_2D_RECT_SHADOW = c.GL_SAMPLER_2D_RECT_SHADOW, INT_SAMPLER_1D = c.GL_INT_SAMPLER_1D, INT_SAMPLER_2D = c.GL_INT_SAMPLER_2D, INT_SAMPLER_3D = c.GL_INT_SAMPLER_3D, INT_SAMPLER_CUBE = c.GL_INT_SAMPLER_CUBE, INT_SAMPLER_1D_ARRAY = c.GL_INT_SAMPLER_1D_ARRAY, INT_SAMPLER_2D_ARRAY = c.GL_INT_SAMPLER_2D_ARRAY, INT_SAMPLER_2D_MULTISAMPLE = c.GL_INT_SAMPLER_2D_MULTISAMPLE, INT_SAMPLER_2D_MULTISAMPLE_ARRAY = c.GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY, INT_SAMPLER_BUFFER = c.GL_INT_SAMPLER_BUFFER, INT_SAMPLER_2D_RECT = c.GL_INT_SAMPLER_2D_RECT, UNSIGNED_INT_SAMPLER_1D = c.GL_UNSIGNED_INT_SAMPLER_1D, UNSIGNED_INT_SAMPLER_2D = c.GL_UNSIGNED_INT_SAMPLER_2D, UNSIGNED_INT_SAMPLER_3D = c.GL_UNSIGNED_INT_SAMPLER_3D, UNSIGNED_INT_SAMPLER_CUBE = c.GL_UNSIGNED_INT_SAMPLER_CUBE, UNSIGNED_INT_SAMPLER_1D_ARRAY = c.GL_UNSIGNED_INT_SAMPLER_1D_ARRAY, UNSIGNED_INT_SAMPLER_2D_ARRAY = c.GL_UNSIGNED_INT_SAMPLER_2D_ARRAY, UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = c.GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE, UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY = c.GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY, UNSIGNED_INT_SAMPLER_BUFFER = c.GL_UNSIGNED_INT_SAMPLER_BUFFER, UNSIGNED_INT_SAMPLER_2D_RECT = c.GL_UNSIGNED_INT_SAMPLER_2D_RECT, pub fn nestedType(self: UniformType) ?UniformType { return switch (self) { .FLOAT_VEC2, .FLOAT_VEC3, .FLOAT_VEC4 => .FLOAT, .DOUBLE_VEC2, .DOUBLE_VEC3, .DOUBLE_VEC4 => .DOUBLE, .INT_VEC2, .INT_VEC3, .INT_VEC4 => .INT, .UNSIGNED_INT_VEC2, .UNSIGNED_INT_VEC3, .UNSIGNED_INT_VEC4 => .UNSIGNED_INT, .BOOL_VEC2, .BOOL_VEC3, .BOOL_VEC4 => .BOOL, .FLOAT_MAT2, .FLOAT_MAT3x2, .FLOAT_MAT4x2 => .FLOAT_VEC2, .FLOAT_MAT2x3, .FLOAT_MAT3, .FLOAT_MAT4x3 => .FLOAT_VEC3, .FLOAT_MAT2x4, .FLOAT_MAT3x4, .FLOAT_MAT4 => .FLOAT_VEC4, .DOUBLE_MAT2, .DOUBLE_MAT3x2, .DOUBLE_MAT4x2 => .DOUBLE_VEC2, .DOUBLE_MAT2x3, .DOUBLE_MAT3, .DOUBLE_MAT4x3 => .DOUBLE_VEC3, .DOUBLE_MAT2x4, .DOUBLE_MAT3x4, .DOUBLE_MAT4 => .DOUBLE_VEC4, else => null, }; } pub fn asType(self: UniformType) type { return switch (self) { .FLOAT => f32, .FLOAT_VEC2 => [2]f32, .FLOAT_VEC3 => [3]f32, .FLOAT_VEC4 => [4]f32, .DOUBLE => f64, .DOUBLE_VEC2 => [2]f64, .DOUBLE_VEC3 => [3]f64, .DOUBLE_VEC4 => [4]f64, .INT => c_int, // i32 but bug .INT_VEC2 => [2]c_int, .INT_VEC3 => [3]c_int, .INT_VEC4 => [4]c_int, .UNSIGNED_INT => c_uint, .UNSIGNED_INT_VEC2 => [2]c_uint, .UNSIGNED_INT_VEC3 => [3]c_uint, .UNSIGNED_INT_VEC4 => [4]c_uint, .BOOL => c_uint, .BOOL_VEC2 => [2]c_uint, .BOOL_VEC3 => [3]c_uint, .BOOL_VEC4 => [4]c_uint, .FLOAT_MAT2 => [2][2]f32, .FLOAT_MAT3 => [3][3]f32, .FLOAT_MAT4 => [4][4]f32, .FLOAT_MAT2x3 => [2][3]f32, .FLOAT_MAT2x4 => [2][4]f32, .FLOAT_MAT3x2 => [3][2]f32, .FLOAT_MAT3x4 => [3][4]f32, .FLOAT_MAT4x2 => [4][2]f32, .FLOAT_MAT4x3 => [4][3]f32, .DOUBLE_MAT2 => [2][2]f64, .DOUBLE_MAT3 => [3][3]f64, .DOUBLE_MAT4 => [4][4]f64, .DOUBLE_MAT2x3 => [2][3]f64, .DOUBLE_MAT2x4 => [2][4]f64, .DOUBLE_MAT3x2 => [3][2]f64, .DOUBLE_MAT3x4 => [3][4]f64, .DOUBLE_MAT4x2 => [4][2]f64, .DOUBLE_MAT4x3 => [4][3]f64, .SAMPLER_1D, .SAMPLER_2D, .SAMPLER_3D, .SAMPLER_CUBE, .SAMPLER_1D_SHADOW, .SAMPLER_2D_SHADOW, .SAMPLER_1D_ARRAY, .SAMPLER_2D_ARRAY, .SAMPLER_1D_ARRAY_SHADOW, .SAMPLER_2D_ARRAY_SHADOW, .SAMPLER_2D_MULTISAMPLE, .SAMPLER_2D_MULTISAMPLE_ARRAY, .SAMPLER_CUBE_SHADOW, .SAMPLER_BUFFER, .SAMPLER_2D_RECT, .SAMPLER_2D_RECT_SHADOW, .INT_SAMPLER_1D, .INT_SAMPLER_2D, .INT_SAMPLER_3D, .INT_SAMPLER_CUBE, .INT_SAMPLER_1D_ARRAY, .INT_SAMPLER_2D_ARRAY, .INT_SAMPLER_2D_MULTISAMPLE, .INT_SAMPLER_2D_MULTISAMPLE_ARRAY, .INT_SAMPLER_BUFFER, .INT_SAMPLER_2D_RECT, .UNSIGNED_INT_SAMPLER_1D, .UNSIGNED_INT_SAMPLER_2D, .UNSIGNED_INT_SAMPLER_3D, .UNSIGNED_INT_SAMPLER_CUBE, .UNSIGNED_INT_SAMPLER_1D_ARRAY, .UNSIGNED_INT_SAMPLER_2D_ARRAY, .UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE, .UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY, .UNSIGNED_INT_SAMPLER_BUFFER, .UNSIGNED_INT_SAMPLER_2D_RECT, => ?*Texture, }; } }; pub const UniformPointer = union(UniformType) { FLOAT: []f32, FLOAT_VEC2: [][2]f32, FLOAT_VEC3: [][3]f32, FLOAT_VEC4: [][4]f32, DOUBLE: []f64, DOUBLE_VEC2: [][2]f64, DOUBLE_VEC3: [][3]f64, DOUBLE_VEC4: [][4]f64, INT: []c_int, // i32 but bug INT_VEC2: [][2]c_int, INT_VEC3: [][3]c_int, INT_VEC4: [][4]c_int, UNSIGNED_INT: []c_uint, UNSIGNED_INT_VEC2: [][2]c_uint, UNSIGNED_INT_VEC3: [][3]c_uint, UNSIGNED_INT_VEC4: [][4]c_uint, BOOL: []c_uint, BOOL_VEC2: [][2]c_uint, BOOL_VEC3: [][3]c_uint, BOOL_VEC4: [][4]c_uint, FLOAT_MAT2: [][2][2]f32, FLOAT_MAT3: [][3][3]f32, FLOAT_MAT4: [][4][4]f32, FLOAT_MAT2x3: [][2][3]f32, FLOAT_MAT2x4: [][2][4]f32, FLOAT_MAT3x2: [][3][2]f32, FLOAT_MAT3x4: [][3][4]f32, FLOAT_MAT4x2: [][4][2]f32, FLOAT_MAT4x3: [][4][3]f32, DOUBLE_MAT2: [][2][2]f64, DOUBLE_MAT3: [][3][3]f64, DOUBLE_MAT4: [][4][4]f64, DOUBLE_MAT2x3: [][2][3]f64, DOUBLE_MAT2x4: [][2][4]f64, DOUBLE_MAT3x2: [][3][2]f64, DOUBLE_MAT3x4: [][3][4]f64, DOUBLE_MAT4x2: [][4][2]f64, DOUBLE_MAT4x3: [][4][3]f64, SAMPLER_1D: []?*Texture, SAMPLER_2D: []?*Texture, SAMPLER_3D: []?*Texture, SAMPLER_CUBE: []?*Texture, SAMPLER_1D_SHADOW: []?*Texture, SAMPLER_2D_SHADOW: []?*Texture, SAMPLER_1D_ARRAY: []?*Texture, SAMPLER_2D_ARRAY: []?*Texture, SAMPLER_1D_ARRAY_SHADOW: []?*Texture, SAMPLER_2D_ARRAY_SHADOW: []?*Texture, SAMPLER_2D_MULTISAMPLE: []?*Texture, SAMPLER_2D_MULTISAMPLE_ARRAY: []?*Texture, SAMPLER_CUBE_SHADOW: []?*Texture, SAMPLER_BUFFER: []?*Texture, SAMPLER_2D_RECT: []?*Texture, SAMPLER_2D_RECT_SHADOW: []?*Texture, INT_SAMPLER_1D: []?*Texture, INT_SAMPLER_2D: []?*Texture, INT_SAMPLER_3D: []?*Texture, INT_SAMPLER_CUBE: []?*Texture, INT_SAMPLER_1D_ARRAY: []?*Texture, INT_SAMPLER_2D_ARRAY: []?*Texture, INT_SAMPLER_2D_MULTISAMPLE: []?*Texture, INT_SAMPLER_2D_MULTISAMPLE_ARRAY: []?*Texture, INT_SAMPLER_BUFFER: []?*Texture, INT_SAMPLER_2D_RECT: []?*Texture, UNSIGNED_INT_SAMPLER_1D: []?*Texture, UNSIGNED_INT_SAMPLER_2D: []?*Texture, UNSIGNED_INT_SAMPLER_3D: []?*Texture, UNSIGNED_INT_SAMPLER_CUBE: []?*Texture, UNSIGNED_INT_SAMPLER_1D_ARRAY: []?*Texture, UNSIGNED_INT_SAMPLER_2D_ARRAY: []?*Texture, UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: []?*Texture, UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: []?*Texture, UNSIGNED_INT_SAMPLER_BUFFER: []?*Texture, UNSIGNED_INT_SAMPLER_2D_RECT: []?*Texture, pub fn arrayLength(self: *const UniformPointer) usize { return switch (self.*) { inline else => |e| e.len, }; } pub fn flatten(self: *const UniformPointer) UniformPointer { switch (self.*) { inline else => |value, tag| { if (comptime tag.nestedType()) |inner| { const Inner = inner.asType(); const inside: [*]Inner = @ptrCast(&value[0][0]); const len = value.len * value[0].len; return @unionInit(UniformPointer, @tagName(inner), inside[0..len]); } else { return self.*; } }, } } pub fn index(self: *const UniformPointer, i: usize) !UniformPointer { const array_len = self.arrayLength(); if (array_len > 1) { if (i >= array_len) return error.invalidIndex; return switch (self.*) { inline else => |value, tag| @unionInit(UniformPointer, @tagName(tag), value[i .. i + 1]), }; } const length: usize = switch (self.*) { .FLOAT_VEC2, .DOUBLE_VEC2, .INT_VEC2, .BOOL_VEC2, .UNSIGNED_INT_VEC2 => 2, .FLOAT_VEC3, .DOUBLE_VEC3, .INT_VEC3, .UNSIGNED_INT_VEC3 => 3, .BOOL_VEC3, .FLOAT_VEC4, .DOUBLE_VEC4, .INT_VEC4, .UNSIGNED_INT_VEC4, .BOOL_VEC4 => 4, else => return error.notIndexable, }; if (i >= length) { return error.invalidIndex; } return switch (self.*) { inline .FLOAT_VEC2, .FLOAT_VEC3, .FLOAT_VEC4 => |v| .{ .FLOAT = &v[i] }, inline .DOUBLE_VEC2, .DOUBLE_VEC3, .DOUBLE_VEC4 => |v| .{ .DOUBLE = &v[i] }, inline .INT_VEC2, .INT_VEC3, .INT_VEC4 => |v| .{ .INT = &v[i] }, inline .UNSIGNED_INT_VEC2, .UNSIGNED_INT_VEC3, .UNSIGNED_INT_VEC4 => |v| .{ .UNSIGNED_INT = &v[i] }, inline .BOOL_VEC2, .BOOL_VEC3, .BOOL_VEC4 => |v| .{ .BOOL = &v[i] }, else => unreachable, }; } }; pub const CachedUniform = struct { value: UniformPointer, location: c.GLint, pub fn init( allocator: std.mem.Allocator, shader: ShaderProgram, name: []const u8, ) !CachedUniform { var index: c.GLuint = undefined; const nameZ = try util.tmpZ(name); c.glGetUniformIndices(shader.program_id, 1, &nameZ.ptr, &index); if (index == c.GL_INVALID_INDEX) return error.invalidUniform; var _u_type: c.GLint = undefined; var u_size: c.GLint = undefined; c.glGetActiveUniformsiv(shader.program_id, 1, &index, c.GL_UNIFORM_TYPE, &_u_type); c.glGetActiveUniformsiv(shader.program_id, 1, &index, c.GL_UNIFORM_SIZE, &u_size); switch (@as(UniformType, @enumFromInt(_u_type))) { inline else => |u_type| { const location = try shader.uniformLocation(nameZ); const buffer = try allocator.alloc(u_type.asType(), @intCast(u_size)); errdefer allocator.free(buffer); const value = @unionInit(UniformPointer, @tagName(u_type), buffer); var self = CachedUniform{ .value = value, .location = location }; self.getShaderValue(shader); return self; }, } } pub fn deinit(self: *CachedUniform, allocator: std.mem.Allocator) void { switch (self.value) { inline else => |buffer| allocator.free(buffer), } } pub fn tryMove( self: *CachedUniform, dest: *CachedUniform, allocator: std.mem.Allocator, ) bool { if (@as(UniformType, self.value) != dest.value or self.value.arrayLength() != dest.value.arrayLength()) { self.deinit(allocator); return false; } std.mem.swap(UniformPointer, &self.value, &dest.value); self.deinit(allocator); return true; } fn flatPointer(comptime Inner: type, value: anytype) [*]Inner { switch (@typeInfo(@TypeOf(value))) { .pointer => |pointer| { if (pointer.child == Inner) return @ptrCast(value); return flatPointer(Inner, &value[0]); }, inline else => @compileError("type not found in flatPointer"), } } pub fn setShaderValue(self: *const CachedUniform, shader: ShaderProgram) void { const program = shader.program_id; const location = self.location; switch (self.value) { .FLOAT => |value| c.glProgramUniform1fv(program, location, @intCast(value.len), value.ptr), .FLOAT_VEC2 => |value| c.glProgramUniform2fv(program, location, @intCast(value.len), flatPointer(f32, value)), .FLOAT_VEC3 => |value| c.glProgramUniform3fv(program, location, @intCast(value.len), flatPointer(f32, value)), .FLOAT_VEC4 => |value| c.glProgramUniform4fv(program, location, @intCast(value.len), flatPointer(f32, value)), .DOUBLE => |value| c.glProgramUniform1dv(program, location, @intCast(value.len), value.ptr), .DOUBLE_VEC2 => |value| c.glProgramUniform1dv(program, location, @intCast(value.len), flatPointer(f64, value)), .DOUBLE_VEC3 => |value| c.glProgramUniform2dv(program, location, @intCast(value.len), flatPointer(f64, value)), .DOUBLE_VEC4 => |value| c.glProgramUniform3dv(program, location, @intCast(value.len), flatPointer(f64, value)), .INT => |value| c.glProgramUniform1iv(program, location, @intCast(value.len), value.ptr), .INT_VEC2 => |value| c.glProgramUniform2iv(program, location, @intCast(value.len), flatPointer(c_int, value)), .INT_VEC3 => |value| c.glProgramUniform3iv(program, location, @intCast(value.len), flatPointer(c_int, value)), .INT_VEC4 => |value| c.glProgramUniform4iv(program, location, @intCast(value.len), flatPointer(c_int, value)), .UNSIGNED_INT => |value| c.glProgramUniform1uiv(program, location, @intCast(value.len), value.ptr), .UNSIGNED_INT_VEC2 => |value| c.glProgramUniform2uiv(program, location, @intCast(value.len), flatPointer(c_uint, value)), .UNSIGNED_INT_VEC3 => |value| c.glProgramUniform3uiv(program, location, @intCast(value.len), flatPointer(c_uint, value)), .UNSIGNED_INT_VEC4 => |value| c.glProgramUniform4uiv(program, location, @intCast(value.len), flatPointer(c_uint, value)), .BOOL => |value| c.glProgramUniform1uiv(program, location, @intCast(value.len), value.ptr), .BOOL_VEC2 => |value| c.glProgramUniform2uiv(program, location, @intCast(value.len), flatPointer(c_uint, value)), .BOOL_VEC3 => |value| c.glProgramUniform3uiv(program, location, @intCast(value.len), flatPointer(c_uint, value)), .BOOL_VEC4 => |value| c.glProgramUniform4uiv(program, location, @intCast(value.len), flatPointer(c_uint, value)), .FLOAT_MAT2 => |value| c.glProgramUniformMatrix2fv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f32, value)), .FLOAT_MAT3 => |value| c.glProgramUniformMatrix3fv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f32, value)), .FLOAT_MAT4 => |value| c.glProgramUniformMatrix4fv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f32, value)), .FLOAT_MAT2x3 => |value| c.glProgramUniformMatrix2x3fv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f32, value)), .FLOAT_MAT2x4 => |value| c.glProgramUniformMatrix2x4fv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f32, value)), .FLOAT_MAT3x2 => |value| c.glProgramUniformMatrix3x2fv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f32, value)), .FLOAT_MAT3x4 => |value| c.glProgramUniformMatrix3x4fv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f32, value)), .FLOAT_MAT4x2 => |value| c.glProgramUniformMatrix4x2fv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f32, value)), .FLOAT_MAT4x3 => |value| c.glProgramUniformMatrix4x3fv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f32, value)), .DOUBLE_MAT2 => |value| c.glProgramUniformMatrix2dv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f64, value)), .DOUBLE_MAT3 => |value| c.glProgramUniformMatrix3dv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f64, value)), .DOUBLE_MAT4 => |value| c.glProgramUniformMatrix4dv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f64, value)), .DOUBLE_MAT2x3 => |value| c.glProgramUniformMatrix2x3dv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f64, value)), .DOUBLE_MAT2x4 => |value| c.glProgramUniformMatrix2x4dv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f64, value)), .DOUBLE_MAT3x2 => |value| c.glProgramUniformMatrix3x2dv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f64, value)), .DOUBLE_MAT3x4 => |value| c.glProgramUniformMatrix3x4dv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f64, value)), .DOUBLE_MAT4x2 => |value| c.glProgramUniformMatrix4x2dv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f64, value)), .DOUBLE_MAT4x3 => |value| c.glProgramUniformMatrix4x3dv(program, location, @intCast(value.len), c.GL_FALSE, flatPointer(f64, value)), .SAMPLER_1D, .SAMPLER_2D, .SAMPLER_3D, .SAMPLER_CUBE, .SAMPLER_1D_SHADOW, .SAMPLER_2D_SHADOW, .SAMPLER_1D_ARRAY, .SAMPLER_2D_ARRAY, .SAMPLER_1D_ARRAY_SHADOW, .SAMPLER_2D_ARRAY_SHADOW, .SAMPLER_2D_MULTISAMPLE, .SAMPLER_2D_MULTISAMPLE_ARRAY, .SAMPLER_CUBE_SHADOW, .SAMPLER_BUFFER, .SAMPLER_2D_RECT, .SAMPLER_2D_RECT_SHADOW, .INT_SAMPLER_1D, .INT_SAMPLER_2D, .INT_SAMPLER_3D, .INT_SAMPLER_CUBE, .INT_SAMPLER_1D_ARRAY, .INT_SAMPLER_2D_ARRAY, .INT_SAMPLER_2D_MULTISAMPLE, .INT_SAMPLER_2D_MULTISAMPLE_ARRAY, .INT_SAMPLER_BUFFER, .INT_SAMPLER_2D_RECT, .UNSIGNED_INT_SAMPLER_1D, .UNSIGNED_INT_SAMPLER_2D, .UNSIGNED_INT_SAMPLER_3D, .UNSIGNED_INT_SAMPLER_CUBE, .UNSIGNED_INT_SAMPLER_1D_ARRAY, .UNSIGNED_INT_SAMPLER_2D_ARRAY, .UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE, .UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY, .UNSIGNED_INT_SAMPLER_BUFFER, .UNSIGNED_INT_SAMPLER_2D_RECT, => |values| { for (values) |value| { if (value) |texture| { const index = texture.bind(); c.glProgramUniform1i(program, location, @intCast(index)); } } }, } } pub fn getShaderValue(self: *CachedUniform, shader: ShaderProgram) void { const program = shader.program_id; const location = self.location; switch (self.value) { inline .FLOAT, .FLOAT_VEC2, .FLOAT_VEC3, .FLOAT_VEC4, .FLOAT_MAT2, .FLOAT_MAT3, .FLOAT_MAT4, .FLOAT_MAT2x3, .FLOAT_MAT2x4, .FLOAT_MAT3x2, .FLOAT_MAT3x4, .FLOAT_MAT4x2, .FLOAT_MAT4x3, => |value| c.glGetUniformfv(program, location, flatPointer(f32, value)), inline .DOUBLE, .DOUBLE_VEC2, .DOUBLE_VEC3, .DOUBLE_VEC4, .DOUBLE_MAT2, .DOUBLE_MAT3, .DOUBLE_MAT4, .DOUBLE_MAT2x3, .DOUBLE_MAT2x4, .DOUBLE_MAT3x2, .DOUBLE_MAT3x4, .DOUBLE_MAT4x2, .DOUBLE_MAT4x3, => |value| c.glGetUniformdv(program, location, flatPointer(f64, value)), inline .INT, .INT_VEC2, .INT_VEC3, .INT_VEC4, => |value| c.glGetUniformiv(program, location, flatPointer(c_int, value)), inline .UNSIGNED_INT, .UNSIGNED_INT_VEC2, .UNSIGNED_INT_VEC3, .UNSIGNED_INT_VEC4, .BOOL, .BOOL_VEC2, .BOOL_VEC3, .BOOL_VEC4, => |value| c.glGetUniformuiv(program, location, flatPointer(c_uint, value)), .SAMPLER_1D, .SAMPLER_2D, .SAMPLER_3D, .SAMPLER_CUBE, .SAMPLER_1D_SHADOW, .SAMPLER_2D_SHADOW, .SAMPLER_1D_ARRAY, .SAMPLER_2D_ARRAY, .SAMPLER_1D_ARRAY_SHADOW, .SAMPLER_2D_ARRAY_SHADOW, .SAMPLER_2D_MULTISAMPLE, .SAMPLER_2D_MULTISAMPLE_ARRAY, .SAMPLER_CUBE_SHADOW, .SAMPLER_BUFFER, .SAMPLER_2D_RECT, .SAMPLER_2D_RECT_SHADOW, .INT_SAMPLER_1D, .INT_SAMPLER_2D, .INT_SAMPLER_3D, .INT_SAMPLER_CUBE, .INT_SAMPLER_1D_ARRAY, .INT_SAMPLER_2D_ARRAY, .INT_SAMPLER_2D_MULTISAMPLE, .INT_SAMPLER_2D_MULTISAMPLE_ARRAY, .INT_SAMPLER_BUFFER, .INT_SAMPLER_2D_RECT, .UNSIGNED_INT_SAMPLER_1D, .UNSIGNED_INT_SAMPLER_2D, .UNSIGNED_INT_SAMPLER_3D, .UNSIGNED_INT_SAMPLER_CUBE, .UNSIGNED_INT_SAMPLER_1D_ARRAY, .UNSIGNED_INT_SAMPLER_2D_ARRAY, .UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE, .UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY, .UNSIGNED_INT_SAMPLER_BUFFER, .UNSIGNED_INT_SAMPLER_2D_RECT, => |values| { for (values) |*value| { value.* = null; } }, } } }; fn errorCallback(err: c_int, description: [*c]const u8) callconv(.c) void { std.debug.panic("Error {}: {s}\n", .{ err, description }); } fn setupTestContext() !*c.GLFWwindow { _ = c.glfwSetErrorCallback(errorCallback); try std.testing.expectEqual(c.GL_TRUE, c.glfwInit()); c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 4); c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MINOR, 2); c.glfwWindowHint(c.GLFW_OPENGL_FORWARD_COMPAT, c.GL_TRUE); c.glfwWindowHint(c.GLFW_OPENGL_DEBUG_CONTEXT, c.GL_TRUE); c.glfwWindowHint(c.GLFW_OPENGL_PROFILE, c.GLFW_OPENGL_CORE_PROFILE); c.glfwWindowHint(c.GLFW_DEPTH_BITS, 0); c.glfwWindowHint(c.GLFW_STENCIL_BITS, 0); c.glfwWindowHint(c.GLFW_VISIBLE, c.GLFW_FALSE); const window = c.glfwCreateWindow(800, 600, "glsl-view-test", null, null) orelse { std.debug.panic("unable to create window\n", .{}); }; c.glfwMakeContextCurrent(window); c.glfwSwapInterval(1); return window; } fn teardownTestContext(window: *c.GLFWwindow) void { c.glfwDestroyWindow(window); } test "shared uniform" { const allocator = std.testing.allocator; const ctx = try setupTestContext(); defer teardownTestContext(ctx); const shader1 = try ShaderProgram.create( \\#version 330 core \\ \\uniform vec2 test_a; \\uniform vec4 test_b[4]; \\uniform vec3 test_c = vec3(3, 2, 1); \\ \\layout(location = 0) in vec2 position; \\out vec2 uv; \\ \\void main() { \\ uv = position * test_a * test_c.xy; \\ gl_Position = vec4(position, test_b[3]); \\} , \\#version 330 core \\ \\in vec2 uv; \\out vec4 color; \\ \\uniform sampler2D colorTexture; \\ \\void main(){ \\ color = texture(colorTexture, uv); \\} ); defer shader1.destroy(); var test_a1 = try CachedUniform.init(allocator, shader1, "test_a"); var test_b1 = try CachedUniform.init(allocator, shader1, "test_b"); var test_c1 = try CachedUniform.init(allocator, shader1, "test_c"); try std.testing.expectEqual(1, test_a1.value.FLOAT_VEC2.len); test_a1.value.FLOAT_VEC2[0][0] = 1; test_a1.value.FLOAT_VEC2[0][1] = 2; try std.testing.expectEqualSlices(f32, &.{ 1, 2 }, test_a1.value.flatten().FLOAT); try std.testing.expectEqual(4, test_b1.value.FLOAT_VEC4.len); try std.testing.expectEqual(1, test_c1.value.FLOAT_VEC3.len); test_c1.getShaderValue(shader1); try std.testing.expectEqualSlices(f32, &.{ 3, 2, 1 }, &test_c1.value.FLOAT_VEC3[0]); test_c1.deinit(allocator); // iteration 2 const shader2 = try ShaderProgram.create( \\#version 330 core \\ \\uniform vec2 test_a; \\uniform vec2 test_b[4]; \\ \\layout(location = 0) in vec2 position; \\out vec2 uv; \\ \\void main() { \\ uv = position * test_a; \\ gl_Position = vec4(position, test_b[0]); \\} , \\#version 330 core \\ \\in vec2 uv; \\out vec4 color; \\ \\uniform sampler2D colorTexture; \\ \\void main(){ \\ color = texture(colorTexture, uv); \\} ); defer shader2.destroy(); var test_a2 = try CachedUniform.init(allocator, shader2, "test_a"); var test_b2 = try CachedUniform.init(allocator, shader2, "test_b"); try std.testing.expectEqual(true, test_a1.tryMove(&test_a2, allocator)); try std.testing.expectEqualSlices(f32, &.{ 1, 2 }, test_a2.value.flatten().FLOAT); try std.testing.expectEqual(false, test_b1.tryMove(&test_b2, allocator)); test_a2.deinit(allocator); test_b2.deinit(allocator); } pub const ShaderProgram = struct { program_id: c.GLuint, vert_id: c.GLuint, frag_id: c.GLuint, pub fn bind(self: ShaderProgram) void { c.glUseProgram(self.program_id); } pub fn uniformLocation(self: ShaderProgram, nameZ: [:0]const u8) !c.GLint { const id = c.glGetUniformLocation(self.program_id, nameZ); if (id == -1) return error.invalidUniform; return id; } fn compileShader(shader_id: c.GLuint, source: []const u8, name: []const u8) !void { const source_ptr: ?[*]const u8 = source.ptr; const source_len = @as(c.GLint, @intCast(source.len)); c.glShaderSource(shader_id, 1, &source_ptr, &source_len); c.glCompileShader(shader_id); var ok: c.GLint = undefined; c.glGetShaderiv(shader_id, c.GL_COMPILE_STATUS, &ok); if (ok != 0) return; var error_size: c.GLint = undefined; c.glGetShaderiv(shader_id, c.GL_INFO_LOG_LENGTH, &error_size); const message = try c_allocator.alloc(u8, @as(usize, @intCast(error_size))); c.glGetShaderInfoLog(shader_id, error_size, &error_size, message.ptr); std.debug.print( "Error compiling {s} shader:\n{s}\n", .{ name, @as([*:0]const u8, @ptrCast(message.ptr)) }, ); return error.CompileError; } fn linkProgram(self: ShaderProgram) !void { c.glLinkProgram(self.program_id); var ok: c.GLint = undefined; c.glGetProgramiv(self.program_id, c.GL_LINK_STATUS, &ok); if (ok != 0) return; var error_size: c.GLint = undefined; c.glGetProgramiv(self.program_id, c.GL_INFO_LOG_LENGTH, &error_size); const message = try c_allocator.alloc(u8, @as(usize, @intCast(error_size))); c.glGetProgramInfoLog(self.program_id, error_size, &error_size, message.ptr); std.debug.print("Error linking shader program: {s}\n", .{@as([*:0]const u8, @ptrCast(message.ptr))}); return error.LinkError; } pub fn create(vert_source: []const u8, frag_source: []const u8) !ShaderProgram { const self: ShaderProgram = .{ .program_id = c.glCreateProgram(), .vert_id = c.glCreateShader(c.GL_VERTEX_SHADER), .frag_id = c.glCreateShader(c.GL_FRAGMENT_SHADER), }; errdefer self.destroy(); c.glAttachShader(self.program_id, self.vert_id); c.glAttachShader(self.program_id, self.frag_id); try compileShader(self.vert_id, vert_source, "vertex"); try compileShader(self.frag_id, frag_source, "fragment"); try self.linkProgram(); return self; } pub fn destroy(self: *const ShaderProgram) void { c.glDeleteShader(self.frag_id); c.glDeleteShader(self.vert_id); c.glDeleteProgram(self.program_id); } pub fn loadString(self: *ShaderProgram, code: []const u8) !void { try compileShader(self.frag_id, code, "fragment"); try self.linkProgram(); } fn escape_dquote(str: []const u8) []const u8 { const state = struct { var buf: [1024]u8 = undefined; }; const len = std.mem.replacementSize(u8, str, "\"", "\\\""); _ = std.mem.replace(u8, str, "\"", "\\\"", state.buf[0..len]); return state.buf[0..len]; } // given a file and the directory it is in, resolve references fn readFile(writer: anytype, dir: []const u8, filename: []const u8) !void { const file_dir = try std.fs.path.resolve(c_allocator, &[_][]const u8{ dir, std.fs.path.dirname(filename) orelse "", }); defer c_allocator.free(file_dir); const file_path = try std.fs.path.resolve(c_allocator, &[_][]const u8{ dir, filename }); defer c_allocator.free(file_path); var file = try std.fs.cwd().openFile(file_path, .{}); defer file.close(); var reader = file.reader(); var line_number: u32 = 1; var buf: [1024]u8 = undefined; var wrote_line = false; while (try reader.readUntilDelimiterOrEof(&buf, '\n')) |line| { if (std.mem.startsWith(u8, line, "#pragma ")) { var parts = std.mem.splitScalar(u8, line, ' '); _ = parts.next(); const pragma = parts.next().?; if (std.mem.eql(u8, pragma, "include")) { var include_path = parts.next().?; if (include_path[0] != '"' or include_path[include_path.len - 1] != '"') { std.debug.print("{s}:{}: Invalid #pragma include\n", .{ file_path, line_number }); std.debug.print("{s}:{}: {s}\n", .{ file_path, line_number, line }); continue; } include_path = include_path[1 .. include_path.len - 1]; readFile(writer, file_dir, include_path) catch unreachable; _ = try writer.print("#line {d} \"{s}\"\n", .{ line_number, escape_dquote(filename) }); continue; } else { std.debug.print("{s}:{}: Unknown #pragma directive '{s}'\n", .{ file_path, line_number, pragma }); std.debug.print("{s}:{}: {s}\n", .{ file_path, line_number, line }); } } if (!std.mem.startsWith(u8, line, "#version") and !wrote_line) { _ = try writer.print("#line {d} \"{s}\"\n", .{ line_number - 1, escape_dquote(filename) }); wrote_line = true; } _ = try writer.write(line); _ = try writer.write("\n"); line_number += 1; } } pub fn loadFile(self: *ShaderProgram, filename: []const u8, size: u64) !void { var buffer = try std.ArrayList(u8).initCapacity(c_allocator, @as(usize, @intCast(size)) + 150); defer buffer.deinit(); const writer = buffer.writer(); _ = try writer.write( \\#version 330 core \\#extension GL_ARB_shading_language_include : require \\ ); try readFile(writer, "", filename); try self.loadString(buffer.items); } }; pub const Thread = struct { thread: std.Thread, window: *c.GLFWwindow, quit: bool, has_quit: bool, pub fn init( allocator: std.mem.Allocator, constants: *const Constants, comptime f: anytype, args: anytype, ) !*Thread { const thread = try allocator.create(Thread); errdefer allocator.destroy(thread); thread.* = .{ .thread = undefined, .window = undefined, .quit = false, .has_quit = false, }; thread.thread = try std.Thread.spawn( .{}, Thread.runner(f), .{ thread, constants, args }, ); return thread; } pub const InitFn = fn (*Thread, *const Constants, anytype) void; fn runner(comptime f: anytype) InitFn { const Impl = struct { pub fn init(self: *Thread, constants: *const Constants, args: anytype) void { c.glfwWindowHint(c.GLFW_VISIBLE, c.GLFW_FALSE); self.window = c.glfwCreateWindow(200, 200, "glsl-view Thread", null, constants.main_window) orelse unreachable; c.glfwMakeContextCurrent(self.window); @call(.auto, f, args) catch |err| { std.debug.print("Error in thread: {}\n", .{err}); }; self.has_quit = true; } }; return Impl.init; } pub fn deinit(self: *Thread, allocator: std.mem.Allocator) void { if (!self.has_quit) { self.quit = true; self.thread.join(); self.has_quit = true; } c.glfwDestroyWindow(self.window); allocator.destroy(self); } }; pub const UniformCache = struct { allocator: std.mem.Allocator, uniforms: std.StringHashMap(CachedUniform), shader: *ShaderProgram, pub fn init(allocator: std.mem.Allocator, shader: *ShaderProgram) UniformCache { return UniformCache{ .allocator = allocator, .uniforms = std.StringHashMap(CachedUniform).init(allocator), .shader = shader, }; } pub fn deinit(self: *UniformCache) void { var uit = self.uniforms.iterator(); while (uit.next()) |entry| { entry.value_ptr.deinit(self.allocator); self.allocator.free(entry.key_ptr.*); } self.uniforms.deinit(); } pub fn get(self: *UniformCache, name: []const u8) !?*CachedUniform { if (!self.uniforms.contains(name)) { var uniform = CachedUniform.init(self.allocator, self.shader.*, name) catch return null; errdefer uniform.deinit(self.allocator); const cloned_name = try self.allocator.dupe(u8, name); errdefer self.allocator.free(cloned_name); try self.uniforms.put(cloned_name, uniform); } if (self.uniforms.getPtr(name)) |ptr| { return ptr; } return null; } pub fn refresh(self: *UniformCache) !void { var old_uniforms = self.uniforms.move(); { var it = old_uniforms.iterator(); while (it.next()) |old_entry| { if (try self.get(old_entry.key_ptr.*)) |uniform| { if (old_entry.value_ptr.tryMove(uniform, self.allocator)) { uniform.setShaderValue(self.shader.*); } } else { old_entry.value_ptr.deinit(self.allocator); } } } { var it = old_uniforms.iterator(); while (it.next()) |entry| { self.allocator.free(entry.key_ptr.*); } old_uniforms.deinit(); } } }; pub const FramebufferObject = struct { buffer_id: c.GLuint, texture_id: c.GLuint, pub fn bind(self: FramebufferObject) void { c.glBindFramebuffer(c.GL_FRAMEBUFFER, self.buffer_id); } pub fn unbind(self: FramebufferObject) void { _ = self; c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); } pub fn read(self: FramebufferObject, width: c.GLint, height: c.GLint, data: []u8) void { _ = self; c.glReadBuffer(c.GL_COLOR_ATTACHMENT0); c.glReadnPixels( 0, 0, width, height, c.GL_RGBA, c.GL_UNSIGNED_BYTE, @intCast(data.len), data.ptr, ); } pub fn create(width: i32, height: i32) !FramebufferObject { var self: FramebufferObject = undefined; c.glGenFramebuffers(1, &self.buffer_id); c.glBindFramebuffer(c.GL_FRAMEBUFFER, self.buffer_id); defer c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); c.glGenTextures(1, &self.texture_id); c.glBindTexture(c.GL_TEXTURE_2D, self.texture_id); defer c.glBindTexture(c.GL_TEXTURE_2D, 0); c.glTexImage2D( c.GL_TEXTURE_2D, 0, c.GL_RGBA, width, height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, null, ); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_LINEAR); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_LINEAR); c.glFramebufferTexture2D( c.GL_FRAMEBUFFER, c.GL_COLOR_ATTACHMENT0, c.GL_TEXTURE_2D, self.texture_id, 0, ); if (c.glCheckFramebufferStatus(c.GL_FRAMEBUFFER) != c.GL_FRAMEBUFFER_COMPLETE) return error.FBONotComplete; return self; } pub fn destroy(self: *FramebufferObject) void { c.glDeleteFramebuffers(1, &self.*.buffer_id); c.glDeleteTextures(1, &self.*.texture_id); } }; pub const VertexObject = struct { array_id: c.GLuint, buffer_id: c.GLuint, vertex_size: i32, vertex_count: i32, pub fn bind(self: VertexObject, attrib: u32) void { c.glBindBuffer(c.GL_ARRAY_BUFFER, self.buffer_id); c.glEnableVertexAttribArray(attrib); c.glVertexAttribPointer(attrib, self.vertex_size, c.GL_FLOAT, c.GL_FALSE, 0, null); } pub fn draw(self: VertexObject) void { c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, self.vertex_count); } pub fn create(comptime T: type, vertices: []const T, vertex_size: i32) VertexObject { var self: VertexObject = undefined; self.vertex_size = vertex_size; self.vertex_count = @as(i32, @intCast(vertices.len)); c.glGenVertexArrays(1, &self.array_id); c.glBindVertexArray(self.array_id); c.glGenBuffers(1, &self.buffer_id); c.glBindBuffer(c.GL_ARRAY_BUFFER, self.buffer_id); c.glBufferData( c.GL_ARRAY_BUFFER, @as(c_long, @intCast(@sizeOf(T) * vertices.len)), @as(*const anyopaque, @ptrCast(vertices.ptr)), c.GL_STATIC_DRAW, ); return self; } pub fn destroy(self: *VertexObject) void { defer c.glDeleteBuffers(1, &self.*.buffer_id); defer c.glDeleteVertexArrays(1, &self.*.array_id); } }; pub const Constants = struct { texture_shader: ShaderProgram, normalized_quad: VertexObject, main_window: *c.GLFWwindow, aspect: f32, config: *cfg.Config, pub fn create(main_window: *c.GLFWwindow, config: *cfg.Config) !Constants { c.glfwMakeContextCurrent(main_window); const shader = try ShaderProgram.create( \\#version 330 core \\ \\layout(location = 0) in vec2 position; \\out vec2 uv; \\ \\void main() { \\ uv = position / 2.0 + 0.5; \\ uv.y *= -1.0; \\ gl_Position = vec4(position, 0, 1); \\} , \\#version 330 core \\ \\in vec2 uv; \\out vec4 color; \\ \\uniform sampler2D colorTexture; \\ \\void main(){ \\ color = texture(colorTexture, uv); \\} ); const vertices = [_]c.GLfloat{ -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, }; return Constants{ .main_window = main_window, .texture_shader = shader, .normalized_quad = VertexObject.create(f32, vertices[0..], 2), .config = config, .aspect = @as(f32, @floatFromInt(config.width)) / @as(f32, @floatFromInt(config.height)), }; } pub fn destroy(self: *Constants) void { self.normalized_quad.destroy(); self.texture_shader.destroy(); } };