const std = @import("std"); const fs = @import("std").fs; 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"); const gl = @import("gl.zig"); const ctrl = @import("control.zig"); const c_allocator = @import("std").heap.c_allocator; 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 arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); var config = try cfg.Config.parse(&arena.allocator, "config.yaml"); _ = c.glfwSetErrorCallback(errorCallback); if (c.glfwInit() == c.GL_FALSE) { panic("GLFW init failure\n", .{}); } defer c.glfwTerminate(); var monitor_count: c_int = 0; const monitors = c.glfwGetMonitors(&monitor_count); for (monitors[0..@intCast(usize, monitor_count)]) |monitor, i| { std.debug.warn("monitor {}: '{s}'\n", .{ i, @ptrCast([*:0]const u8, c.glfwGetMonitorName(monitor)) }); } 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, 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, 0); 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); var constants = try gl.Constants.create(window, &config); defer constants.destroy(); var outputs = try std.ArrayList(*out.Output).initCapacity(&arena.allocator, config.outputs.len); defer outputs.deinit(); for (config.outputs) |output_config, i| { try outputs.append(out.Output.create(&arena.allocator, output_config, &constants)); } defer for (outputs.toSlice()) |output| { output.destroy(output); }; c.glfwMakeContextCurrent(window); c.glClearColor(0.0, 0.0, 0.0, 1.0); debug_gl.assertNoError(); debug_gl.init(); const start_time = c.glfwGetTime(); var prev_time = start_time; constants.normalized_quad.bind(0); var main_program = try gl.ShaderProgram.create( \\#version 330 core \\ \\layout(location = 0) in vec2 position; \\out vec2 uv; \\ \\void main() { \\ uv = position / 2.0 + 0.5; \\ gl_Position = vec4(position, 0, 1); \\} , \\#version 330 core \\ \\in vec2 uv; \\out vec4 color; \\ \\void main() { \\ color = vec4(0,0,0,1); \\} ); defer main_program.destroy(); const shader_file = try fs.cwd().openFile(config.fragment, .{}); var last_stat = try shader_file.stat(); try reloadShader(&main_program, shader_file, last_stat.size); var cache = gl.UniformCache.init(std.heap.c_allocator, &main_program); defer cache.deinit(); var fbo = try gl.FramebufferObject.create(config.width, config.height); defer fbo.destroy(); const control = try ctrl.ControlServer.init(&arena.allocator, config.osc); defer control.destroy(); _ = try cache.get("radius\x00"); while (c.glfwWindowShouldClose(window) == c.GL_FALSE) { c.glfwMakeContextCurrent(window); c.glClear(c.GL_COLOR_BUFFER_BIT); const now_time = c.glfwGetTime(); const elapsed = now_time - prev_time; prev_time = now_time; const stat = try shader_file.stat(); if (stat.mtime > last_stat.mtime) { try reloadShader(&main_program, shader_file, stat.size); try cache.refresh(); last_stat = stat; } if (try cache.get("offset\x00")) |offset| { offset.value.FLOAT += 0.01; offset.setShaderValue(main_program); } control.update(main_program); fbo.bind(); c.glClear(c.GL_COLOR_BUFFER_BIT); main_program.bind(); constants.normalized_quad.draw(); fbo.unbind(); for (outputs.toSlice()) |output, i| { const close = output.update(output, fbo.texture_id); if (close) { const removed = outputs.swapRemove(i); removed.destroy(removed); break; } } if (outputs.len == 0) break; c.glfwPollEvents(); } } fn reloadShader(current: *gl.ShaderProgram, frag_file: fs.File, size: u64) !void { var frag_source = try c_allocator.alloc(u8, @intCast(usize, size)); defer c_allocator.free(frag_source); try frag_file.seekTo(0); const length = try frag_file.read(frag_source); const vert_source = \\#version 330 core \\ \\layout(location = 0) in vec2 position; \\out vec2 uv; \\ \\void main() { \\ uv = position / 2.0 + 0.5; \\ gl_Position = vec4(position, 0, 1); \\} ; if (gl.ShaderProgram.create(vert_source, frag_source[0..length])) |new_program| { current.destroy(); current.* = new_program; } else |err| { std.debug.warn("Error while reloading shader:{}\n", .{err}); } }