aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-01-22 16:11:33 +0000
committers-ol <s-ol@users.noreply.github.com>2020-01-22 16:11:33 +0000
commit25e9c84b55593da4950b2c9ccac818c606a5321f (patch)
tree27f949b82aeeb725025d9099cf7a14767b4a0135
downloadglsl-view-25e9c84b55593da4950b2c9ccac818c606a5321f.tar.gz
glsl-view-25e9c84b55593da4950b2c9ccac818c606a5321f.zip
initial commit
-rw-r--r--.gitignore1
-rw-r--r--build.zig18
-rw-r--r--config.yaml13
-rw-r--r--src/c.zig5
-rw-r--r--src/config.zig339
-rw-r--r--src/debug_gl.zig16
-rw-r--r--src/main.zig83
-rw-r--r--src/output.zig76
-rw-r--r--src/shader.zig118
9 files changed, 669 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2040c29
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+zig-cache
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..33a955c
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,18 @@
+const Builder = @import("std").build.Builder;
+
+pub fn build(b: *Builder) void {
+ const mode = b.standardReleaseOptions();
+ const exe = b.addExecutable("t", "src/main.zig");
+ exe.setBuildMode(mode);
+ exe.linkSystemLibrary("c");
+ exe.linkSystemLibrary("yaml");
+ exe.linkSystemLibrary("glfw");
+ exe.linkSystemLibrary("epoxy");
+ exe.install();
+
+ const run_cmd = exe.run();
+ run_cmd.step.dependOn(b.getInstallStep());
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+}
diff --git a/config.yaml b/config.yaml
new file mode 100644
index 0000000..fcac769
--- /dev/null
+++ b/config.yaml
@@ -0,0 +1,13 @@
+fragment: swag.frag
+width: 480
+height: 270
+outputs:
+ - type: window
+ width: 600
+ height: 400
+ fullscreen: false
+parameters:
+ - name: camera
+ type: mat4
+ - name: bg
+ type: vec4:rgba
diff --git a/src/c.zig b/src/c.zig
new file mode 100644
index 0000000..7a13fde
--- /dev/null
+++ b/src/c.zig
@@ -0,0 +1,5 @@
+pub usingnamespace @cImport({
+ @cInclude("epoxy/gl.h");
+ @cInclude("GLFW/glfw3.h");
+ @cInclude("yaml.h");
+});
diff --git a/src/config.zig b/src/config.zig
new file mode 100644
index 0000000..8d296dc
--- /dev/null
+++ b/src/config.zig
@@ -0,0 +1,339 @@
+const c = @import("c.zig");
+const fs = @import("std").fs;
+const std = @import("std");
+const debug = @import("std").debug;
+
+pub const OutputType = enum {
+ window,
+};
+
+pub const OutputConfig = struct {
+ type: OutputType,
+ width: i32,
+ height: i32,
+ fullscreen: bool, // only for `Window`
+};
+
+pub const ParameterConfig = struct {
+ name: []const u8,
+ type: []const u8,
+};
+
+const defaultOutput = OutputConfig{
+ .type = OutputType.window,
+ .width = 800,
+ .height = 600,
+ .fullscreen = false,
+};
+
+pub const Config = struct {
+ width: i32,
+ height: i32,
+ outputs: []const OutputConfig,
+ fragment: []const u8,
+ parameters: []const ParameterConfig,
+ arena: std.heap.ArenaAllocator,
+
+ pub fn parse(filename: []const u8) !Config {
+ var parser: c.yaml_parser_t = undefined;
+ _ = c.yaml_parser_initialize(&parser);
+ defer c.yaml_parser_delete(&parser);
+
+ const file = try fs.cwd().openFile(filename, .{});
+ // c.yaml_parser_set_input_file(&parser, @as([*c]c.FILE, file.handle));
+ var buffer: [1024]u8 = undefined;
+ const len: usize = try file.read(buffer[0..]);
+ c.yaml_parser_set_input_string(&parser, &buffer[0], len);
+
+ var config: Config = .{
+ .width = 1920,
+ .height = 1080,
+ .outputs = ([_]OutputConfig{defaultOutput})[0..],
+ .fragment = "sphere.frag",
+ .parameters = ([0]ParameterConfig{})[0..],
+ .arena = std.heap.ArenaAllocator.init(std.heap.page_allocator),
+ };
+
+ try expectEvent(&parser, c.yaml_event_type_t.YAML_STREAM_START_EVENT);
+ try expectEvent(&parser, c.yaml_event_type_t.YAML_DOCUMENT_START_EVENT);
+ try expectEvent(&parser, c.yaml_event_type_t.YAML_MAPPING_START_EVENT);
+
+ while (true) {
+ var event: c.yaml_event_t = undefined;
+ if (c.yaml_parser_parse(&parser, &event) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&event);
+
+ switch (event.type) {
+ c.yaml_event_type_t.YAML_MAPPING_END_EVENT => break,
+ c.yaml_event_type_t.YAML_SCALAR_EVENT => {
+ const data = event.data.scalar;
+ const key: []const u8 = data.value[0..data.length];
+
+ if (std.mem.eql(u8, key, "width")) {
+ config.width = try parseInt(&parser, i32);
+ } else if (std.mem.eql(u8, key, "height")) {
+ config.height = try parseInt(&parser, i32);
+ } else if (std.mem.eql(u8, key, "fragment")) {
+ config.fragment = try parseString(&parser, &config.arena.allocator);
+ } else if (std.mem.eql(u8, key, "outputs")) {
+ config.outputs = try parseOutputs(&parser, &config.arena.allocator);
+ } else if (std.mem.eql(u8, key, "parameters")) {
+ config.parameters = try parseParameters(&parser, &config.arena.allocator);
+ } else {
+ debug.warn("unknown key: '{}'\n", .{key});
+ try skipAny(&parser);
+ }
+ },
+ else => {
+ debug.warn("unexpected event: {}\n", .{event.type});
+ return error.InvalidConfiguration;
+ },
+ }
+ }
+
+ try expectEvent(&parser, c.yaml_event_type_t.YAML_DOCUMENT_END_EVENT);
+ try expectEvent(&parser, c.yaml_event_type_t.YAML_STREAM_END_EVENT);
+
+ return config;
+ }
+};
+
+fn expectEvent(parser: *c.yaml_parser_t, expectedType: c.yaml_event_type_t) !void {
+ var event: c.yaml_event_t = undefined;
+
+ if (c.yaml_parser_parse(parser, &event) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&event);
+
+ if (event.type != expectedType) {
+ debug.warn("unexpected event: {}\n", .{event.type});
+ return error.InvalidConfiguration;
+ }
+}
+
+fn parseString(parser: *c.yaml_parser_t, allocator: *std.mem.Allocator) ![]const u8 {
+ var event: c.yaml_event_t = undefined;
+ if (c.yaml_parser_parse(parser, &event) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&event);
+
+ if (event.type != c.yaml_event_type_t.YAML_SCALAR_EVENT)
+ return error.InvalidType;
+
+ const data = event.data.scalar;
+ const value: []const u8 = data.value[0..data.length];
+ return try std.mem.dupe(allocator, u8, value);
+}
+
+fn parseInt(parser: *c.yaml_parser_t, comptime T: type) !T {
+ var event: c.yaml_event_t = undefined;
+ if (c.yaml_parser_parse(parser, &event) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&event);
+
+ if (event.type != c.yaml_event_type_t.YAML_SCALAR_EVENT)
+ return error.InvalidType;
+
+ const data = event.data.scalar;
+ const value: []const u8 = data.value[0..data.length];
+ return try std.fmt.parseInt(T, value, 10);
+}
+
+fn parseEnum(parser: *c.yaml_parser_t, comptime T: type) !T {
+ var event: c.yaml_event_t = undefined;
+ if (c.yaml_parser_parse(parser, &event) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&event);
+
+ if (event.type != c.yaml_event_type_t.YAML_SCALAR_EVENT)
+ return error.InvalidType;
+
+ const data = event.data.scalar;
+ const value: []const u8 = data.value[0..data.length];
+
+ inline for (@typeInfo(T).Enum.fields) |field| {
+ if (std.mem.eql(u8, value, field.name))
+ return @field(T, field.name);
+ }
+
+ return error.InvalidValue;
+}
+
+fn parseBool(parser: *c.yaml_parser_t) !bool {
+ var event: c.yaml_event_t = undefined;
+ if (c.yaml_parser_parse(parser, &event) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&event);
+
+ if (event.type != c.yaml_event_type_t.YAML_SCALAR_EVENT)
+ return error.InvalidType;
+
+ const data = event.data.scalar;
+ const value: []const u8 = data.value[0..data.length];
+
+ if (data.style != c.yaml_scalar_style_t.YAML_PLAIN_SCALAR_STYLE)
+ return false;
+ return std.mem.eql(u8, value, "true") or std.mem.eql(u8, value, "1");
+}
+
+fn parseOutputs(parser: *c.yaml_parser_t, allocator: *std.mem.Allocator) ![]OutputConfig {
+ try expectEvent(parser, c.yaml_event_type_t.YAML_SEQUENCE_START_EVENT);
+
+ var outputs = try allocator.alloc(OutputConfig, 1024);
+ var output_count: usize = 0;
+
+ while (true) {
+ var seqEvent: c.yaml_event_t = undefined;
+ if (c.yaml_parser_parse(parser, &seqEvent) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&seqEvent);
+
+ switch (seqEvent.type) {
+ c.yaml_event_type_t.YAML_SEQUENCE_END_EVENT => break,
+ c.yaml_event_type_t.YAML_MAPPING_START_EVENT => {
+ var output = &outputs[output_count];
+ output.* = defaultOutput;
+
+ while (true) {
+ var event: c.yaml_event_t = undefined;
+ if (c.yaml_parser_parse(parser, &event) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&event);
+
+ switch (event.type) {
+ c.yaml_event_type_t.YAML_MAPPING_END_EVENT => break,
+ c.yaml_event_type_t.YAML_SCALAR_EVENT => {
+ const data = event.data.scalar;
+ const key: []const u8 = data.value[0..data.length];
+
+ if (std.mem.eql(u8, key, "type")) {
+ output.*.type = try parseEnum(parser, OutputType);
+ } else if (std.mem.eql(u8, key, "width")) {
+ output.*.width = try parseInt(parser, i32);
+ } else if (std.mem.eql(u8, key, "height")) {
+ output.*.height = try parseInt(parser, i32);
+ } else if (std.mem.eql(u8, key, "fullscreen")) {
+ output.*.fullscreen = try parseBool(parser);
+ } else {
+ debug.warn("unknown key: '{}'\n", .{key});
+ try skipAny(parser);
+ }
+ },
+ else => {
+ debug.warn("unexpected event: {}\n", .{event.type});
+ return error.InvalidConfiguration;
+ },
+ }
+ }
+
+ output_count += 1;
+ },
+ else => {
+ debug.warn("unexpected event: {}\n", .{seqEvent.type});
+ return error.InvalidConfiguration;
+ },
+ }
+ }
+
+ _ = allocator.realloc(outputs, output_count) catch 0;
+ return outputs[0..output_count];
+}
+
+fn parseParameters(parser: *c.yaml_parser_t, allocator: *std.mem.Allocator) ![]ParameterConfig {
+ try expectEvent(parser, c.yaml_event_type_t.YAML_SEQUENCE_START_EVENT);
+
+ var parameters = try allocator.alloc(ParameterConfig, 1024);
+ var param_count: usize = 0;
+
+ while (true) {
+ var seqEvent: c.yaml_event_t = undefined;
+ if (c.yaml_parser_parse(parser, &seqEvent) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&seqEvent);
+
+ switch (seqEvent.type) {
+ c.yaml_event_type_t.YAML_SEQUENCE_END_EVENT => break,
+ c.yaml_event_type_t.YAML_MAPPING_START_EVENT => {
+ var param = &parameters[param_count];
+ var have_name = false;
+ var have_type = false;
+
+ while (true) {
+ var event: c.yaml_event_t = undefined;
+ if (c.yaml_parser_parse(parser, &event) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&event);
+
+ switch (event.type) {
+ c.yaml_event_type_t.YAML_MAPPING_END_EVENT => break,
+ c.yaml_event_type_t.YAML_SCALAR_EVENT => {
+ const data = event.data.scalar;
+ const key: []const u8 = data.value[0..data.length];
+
+ if (std.mem.eql(u8, key, "name")) {
+ param.*.name = try parseString(parser, allocator);
+ have_name = true;
+ } else if (std.mem.eql(u8, key, "type")) {
+ param.*.type = try parseString(parser, allocator);
+ have_type = true;
+ } else {
+ debug.warn("unknown key: '{}'\n", .{key});
+ try skipAny(parser);
+ }
+ },
+ else => {
+ debug.warn("unexpected event: {}\n", .{event.type});
+ return error.InvalidConfiguration;
+ },
+ }
+ }
+
+ if (!(have_name and have_type)) {
+ debug.warn("name and type are mandatory for parameters.\n", .{});
+ return error.InvalidConfiguration;
+ }
+
+ param_count += 1;
+ },
+ else => {
+ debug.warn("unexpected event: {}\n", .{seqEvent.type});
+ return error.InvalidConfiguration;
+ },
+ }
+ }
+
+ _ = allocator.realloc(parameters, param_count) catch 0;
+ return parameters[0..param_count];
+}
+
+fn skipAny(parser: *c.yaml_parser_t) !void {
+ var event: c.yaml_event_t = undefined;
+ var depth: u32 = 0;
+ while (true) {
+ if (c.yaml_parser_parse(parser, &event) != 1)
+ return error.YAMLParserError;
+ defer c.yaml_event_delete(&event);
+
+ switch (event.type) {
+ c.yaml_event_type_t.YAML_SCALAR_EVENT => {
+ if (depth == 0)
+ break;
+ },
+
+ c.yaml_event_type_t.YAML_SEQUENCE_START_EVENT, c.yaml_event_type_t.YAML_MAPPING_START_EVENT => depth += 1,
+
+ c.yaml_event_type_t.YAML_SEQUENCE_END_EVENT, c.yaml_event_type_t.YAML_MAPPING_END_EVENT => {
+ depth -= 1;
+
+ if (depth == 0)
+ break;
+ },
+
+ else => {
+ debug.warn("unexpected event: {}\n", .{event.type});
+ return error.InvalidConfiguration;
+ },
+ }
+ }
+}
diff --git a/src/debug_gl.zig b/src/debug_gl.zig
new file mode 100644
index 0000000..2fdcda0
--- /dev/null
+++ b/src/debug_gl.zig
@@ -0,0 +1,16 @@
+const c = @import("c.zig");
+const std = @import("std");
+const os = std.os;
+const panic = std.debug.panic;
+const builtin = @import("builtin");
+
+pub const is_on = if (builtin.mode == builtin.Mode.ReleaseFast) c.GL_FALSE else c.GL_TRUE;
+
+pub fn assertNoError() void {
+ if (builtin.mode != builtin.Mode.ReleaseFast) {
+ const err = c.glGetError();
+ if (err != c.GL_NO_ERROR) {
+ panic("GL error: {}\n", .{err});
+ }
+ }
+}
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..ce31e75
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,83 @@
+const std = @import("std");
+const panic = std.debug.panic;
+const c = @import("c.zig");
+const debug_gl = @import("debug_gl.zig");
+const cfg = @import("config.zig");
+const out = @import("output.zig");
+
+var window: *c.GLFWwindow = undefined;
+
+fn errorCallback(err: c_int, description: [*c]const u8) callconv(.C) void {
+ panic("Error: {}\n", .{description});
+}
+
+pub fn main() !void {
+ var config = try cfg.Config.parse("config.yaml");
+ defer config.arena.deinit();
+
+ _ = c.glfwSetErrorCallback(errorCallback);
+
+ if (c.glfwInit() == c.GL_FALSE) {
+ panic("GLFW init failure\n", .{});
+ }
+ defer c.glfwTerminate();
+
+ c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 3);
+ 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, debug_gl.is_on);
+ c.glfwWindowHint(c.GLFW_OPENGL_PROFILE, c.GLFW_OPENGL_CORE_PROFILE);
+ c.glfwWindowHint(c.GLFW_DEPTH_BITS, 0);
+ c.glfwWindowHint(c.GLFW_STENCIL_BITS, 8);
+ c.glfwWindowHint(c.GLFW_VISIBLE, c.GLFW_FALSE);
+
+ window = c.glfwCreateWindow(config.width, config.height, "glsl-view", null, null) orelse {
+ panic("unable to create window\n", .{});
+ };
+ defer c.glfwDestroyWindow(window);
+
+ c.glfwMakeContextCurrent(window);
+ c.glfwSwapInterval(1);
+
+ c.glClearColor(0.0, 0.0, 0.0, 1.0);
+
+ // c.glEnable(c.GL_BLEND);
+ // c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA);
+ // c.glPixelStorei(c.GL_UNPACK_ALIGNMENT, 1);
+
+ c.glViewport(0, 0, config.width, config.height);
+
+ debug_gl.assertNoError();
+
+ var outputs = try config.arena.allocator.alloc(*out.Output, config.outputs.len);
+ for (config.outputs) |output_config, i| {
+ outputs[i] = out.Output.create(&config.arena.allocator, output_config, window);
+ }
+ defer for (outputs) |output| {
+ output.destroy(output);
+ };
+
+ const start_time = c.glfwGetTime();
+ var prev_time = start_time;
+
+ while (c.glfwWindowShouldClose(window) == c.GL_FALSE) {
+ c.glClear(c.GL_COLOR_BUFFER_BIT | c.GL_DEPTH_BUFFER_BIT | c.GL_STENCIL_BUFFER_BIT);
+
+ const now_time = c.glfwGetTime();
+ const elapsed = now_time - prev_time;
+ prev_time = now_time;
+
+ for (outputs) |output| {
+ const close = output.update(output);
+ if (close) {
+ c.glfwSetWindowShouldClose(window, c.GL_TRUE);
+ }
+ }
+
+ c.glfwSwapBuffers(window);
+
+ c.glfwPollEvents();
+ }
+
+ debug_gl.assertNoError();
+}
diff --git a/src/output.zig b/src/output.zig
new file mode 100644
index 0000000..8303107
--- /dev/null
+++ b/src/output.zig
@@ -0,0 +1,76 @@
+const c = @import("c.zig");
+const std = @import("std");
+const cfg = @import("config.zig");
+
+pub const Output = struct {
+ update: fn (self: *Output) bool,
+ destroy: fn (self: *Output) void,
+
+ pub fn init(update: fn (*Output) bool, destroy: fn (*Output) void) Output {
+ return Output{
+ .update = update,
+ .destroy = destroy,
+ };
+ }
+
+ pub fn create(allocator: *std.mem.Allocator, config: cfg.OutputConfig, share_window: *c.GLFWwindow) *Output {
+ return switch (config.type) {
+ cfg.OutputType.window => WindowOutput.create(allocator, config, share_window),
+ else => unreachable,
+ };
+ }
+};
+
+pub const WindowOutput = struct {
+ output: Output,
+ window: *c.GLFWwindow,
+
+ pub fn create(allocator: *std.mem.Allocator, config: cfg.OutputConfig, share_window: *c.GLFWwindow) *Output {
+ const self = allocator.create(WindowOutput) catch unreachable;
+
+ c.glfwDefaultWindowHints();
+
+ self.* = WindowOutput{
+ .output = Output.init(update, destroy),
+ .window = c.glfwCreateWindow(config.width, config.height, "glsl-view output", null, share_window) orelse {
+ std.debug.panic("unable to create output window\n", .{});
+ },
+ };
+
+ c.glfwSetWindowUserPointer(self.*.window, @ptrCast(*c_void, self));
+ _ = c.glfwSetKeyCallback(self.*.window, keyCallback);
+
+ return &self.*.output;
+ }
+
+ fn update(output: *Output) bool {
+ const self = @fieldParentPtr(WindowOutput, "output", output);
+
+ if (c.glfwWindowShouldClose(self.*.window) == c.GL_TRUE)
+ return true;
+
+ c.glClear(c.GL_COLOR_BUFFER_BIT | c.GL_DEPTH_BUFFER_BIT | c.GL_STENCIL_BUFFER_BIT);
+
+ c.glfwSwapBuffers(self.*.window);
+ return false;
+ }
+
+ fn destroy(output: *Output) void {
+ const self = @fieldParentPtr(WindowOutput, "output", output);
+
+ c.glfwDestroyWindow(self.*.window);
+ }
+
+ extern fn keyCallback(win: ?*c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.C) void {
+ if (action != c.GLFW_PRESS) return;
+ const self = @ptrCast(*WindowOutput, @alignCast(@alignOf(WindowOutput), c.glfwGetWindowUserPointer(win).?));
+
+ std.debug.warn("key pressed: {}\n", .{ key });
+ switch (key) {
+ // c.GLFW_KEY_F => // toggle fullscreen
+ // c.GLFW_KEY_LEFT => // cycle through monitors
+ // c.GLFW_KEY_RIGHT => // cycle through monitors
+ else => {},
+ }
+ }
+};
diff --git a/src/shader.zig b/src/shader.zig
new file mode 100644
index 0000000..ed36035
--- /dev/null
+++ b/src/shader.zig
@@ -0,0 +1,118 @@
+const std = @import("std");
+const panic = std.debug.panic
+const c = @import("c.zig");
+
+
+pub const ShaderProgram = struct {
+ program_id: c.GLuint,
+ vertex_id: c.GLuint,
+ fragment_id: c.GLuint,
+ maybe_geometry_id: ?c.GLuint,
+
+ pub fn bind(sp: ShaderProgram) void {
+ c.glUseProgram(sp.program_id);
+ }
+
+ pub fn attribLocation(sp: ShaderProgram, name: [*]const u8) c.GLint {
+ const id = c.glGetAttribLocation(sp.program_id, name);
+ if (id == -1) {
+ panic("invalid attrib: {}\n", .{name});
+ }
+ return id;
+ }
+
+ pub fn uniformLocation(sp: ShaderProgram, name: [*]const u8) c.GLint {
+ const id = c.glGetUniformLocation(sp.program_id, name);
+ if (id == -1) {
+ panic("invalid uniform: {}\n", .{name});
+ }
+ return id;
+ }
+
+ pub fn setUniformInt(sp: ShaderProgram, uniform_id: c.GLint, value: c_int) void {
+ c.glUniform1i(uniform_id, value);
+ }
+
+ pub fn setUniformFloat(sp: ShaderProgram, uniform_id: c.GLint, value: f32) void {
+ c.glUniform1f(uniform_id, value);
+ }
+
+ pub fn setUniformVec3(sp: ShaderProgram, uniform_id: c.GLint, value: math3d.Vec3) void {
+ c.glUniform3fv(uniform_id, 1, value.data[0..].ptr);
+ }
+
+ pub fn setUniformVec4(sp: ShaderProgram, uniform_id: c.GLint, value: Vec4) void {
+ c.glUniform4fv(uniform_id, 1, value.data[0..].ptr);
+ }
+
+ pub fn setUniformMat4x4(sp: ShaderProgram, uniform_id: c.GLint, value: Mat4x4) void {
+ c.glUniformMatrix4fv(uniform_id, 1, c.GL_FALSE, value.data[0][0..].ptr);
+ }
+
+ pub fn create(
+ vertex_source: []const u8,
+ frag_source: []const u8,
+ maybe_geometry_source: ?[]u8,
+ ) !ShaderProgram {
+ var sp: ShaderProgram = undefined;
+ sp.vertex_id = try initGlShader(vertex_source, "vertex", c.GL_VERTEX_SHADER);
+ sp.fragment_id = try initGlShader(frag_source, "fragment", c.GL_FRAGMENT_SHADER);
+ sp.maybe_geometry_id = if (maybe_geometry_source) |geo_source|
+ try initGlShader(geo_source, "geometry", c.GL_GEOMETRY_SHADER)
+ else
+ null;
+
+ sp.program_id = c.glCreateProgram();
+ c.glAttachShader(sp.program_id, sp.vertex_id);
+ c.glAttachShader(sp.program_id, sp.fragment_id);
+ if (sp.maybe_geometry_id) |geo_id| {
+ c.glAttachShader(sp.program_id, geo_id);
+ }
+ c.glLinkProgram(sp.program_id);
+
+ var ok: c.GLint = undefined;
+ c.glGetProgramiv(sp.program_id, c.GL_LINK_STATUS, &ok);
+ if (ok != 0) return sp;
+
+ var error_size: c.GLint = undefined;
+ c.glGetProgramiv(sp.program_id, c.GL_INFO_LOG_LENGTH, &error_size);
+ const message = try c_allocator.alloc(u8, @intCast(usize, error_size));
+ c.glGetProgramInfoLog(sp.program_id, error_size, &error_size, message.ptr);
+ panic("Error linking shader program: {}\n", .{message.ptr});
+ }
+
+ pub fn destroy(sp: *ShaderProgram) void {
+ if (sp.maybe_geometry_id) |geo_id| {
+ c.glDetachShader(sp.program_id, geo_id);
+ }
+ c.glDetachShader(sp.program_id, sp.fragment_id);
+ c.glDetachShader(sp.program_id, sp.vertex_id);
+
+ if (sp.maybe_geometry_id) |geo_id| {
+ c.glDeleteShader(geo_id);
+ }
+ c.glDeleteShader(sp.fragment_id);
+ c.glDeleteShader(sp.vertex_id);
+
+ c.glDeleteProgram(sp.program_id);
+ }
+};
+
+fn initGlShader(source: []const u8, name: [*]const u8, kind: c.GLenum) !c.GLuint {
+ const shader_id = c.glCreateShader(kind);
+ const source_ptr: ?[*]const u8 = source.ptr;
+ const source_len = @intCast(c.GLint, 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 shader_id;
+
+ var error_size: c.GLint = undefined;
+ c.glGetShaderiv(shader_id, c.GL_INFO_LOG_LENGTH, &error_size);
+
+ const message = try c_allocator.alloc(u8, @intCast(usize, error_size));
+ c.glGetShaderInfoLog(shader_id, error_size, &error_size, message.ptr);
+ panic("Error compiling {} shader:\n{}\n", .{ name, message.ptr });
+}