const c = @import("c.zig"); const std = @import("std"); const cfg = @import("config.zig"); const gl = @import("gl.zig"); pub const Output = struct { update: *const fn (*Output, c.GLuint) bool, destroy: *const fn (*Output) void, fn init(update: *const fn (*Output, c.GLuint) bool, destroy: *const fn (*Output) void) Output { return Output{ .update = update, .destroy = destroy, }; } pub fn create( allocator: std.mem.Allocator, config: cfg.OutputConfig, constants: *gl.Constants, ) *Output { return switch (config.type) { .window => WindowOutput.create(allocator, config, constants), }; } }; pub const WindowOutput = struct { output: Output, window: *c.GLFWwindow, constants: *gl.Constants, resized: bool = false, pub fn create( allocator: std.mem.Allocator, config: cfg.OutputConfig, constants: *gl.Constants, ) *Output { const self = allocator.create(WindowOutput) catch unreachable; c.glfwDefaultWindowHints(); self.* = WindowOutput{ .constants = constants, .output = Output.init(update, destroy), .window = c.glfwCreateWindow( config.width, config.height, "glsl-view output", null, constants.main_window, ) orelse { std.debug.panic("unable to create output window\n", .{}); }, }; c.glfwSetWindowUserPointer(self.window, @as(*anyopaque, @ptrCast(self))); _ = c.glfwSetKeyCallback(self.window, keyCallback); _ = c.glfwSetFramebufferSizeCallback(self.window, sizeCallback); c.glfwMakeContextCurrent(self.window); constants.normalized_quad.bind(0); return &self.output; } fn update(output: *Output, texture_id: c.GLuint) bool { const self: *WindowOutput = @fieldParentPtr("output", output); if (c.glfwWindowShouldClose(self.window) == c.GL_TRUE) return true; c.glfwMakeContextCurrent(self.window); c.glClear(c.GL_COLOR_BUFFER_BIT); if (self.resized) { var width: c_int = undefined; var height: c_int = undefined; var scaled_width: c_int = undefined; var scaled_height: c_int = undefined; c.glfwGetFramebufferSize(self.window, &width, &height); const window_aspect = @as(f32, @floatFromInt(width)) / @as(f32, @floatFromInt(height)); if (window_aspect >= self.constants.aspect) { scaled_height = height; scaled_width = @as(c_int, @intFromFloat(@as(f32, @floatFromInt(height)) * self.constants.aspect)); } else { scaled_width = width; scaled_height = @as(c_int, @intFromFloat(@as(f32, @floatFromInt(width)) / self.constants.aspect)); } c.glViewport( @divFloor(width - scaled_width, 2), @divFloor(height - scaled_height, 2), scaled_width, scaled_height, ); } c.glBindTexture(c.GL_TEXTURE_2D, texture_id); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); self.constants.texture_shader.bind(); self.constants.normalized_quad.draw(); c.glfwSwapBuffers(self.window); return false; } fn destroy(output: *Output) void { const self: *WindowOutput = @fieldParentPtr("output", output); c.glfwDestroyWindow(self.window); } 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 userptr = c.glfwGetWindowUserPointer(win).?; const self = @as(*WindowOutput, @ptrCast(@alignCast(userptr))); _ = self; _ = scancode; _ = mods; switch (key) { // c.GLFW_KEY_F => // toggle fullscreen // c.GLFW_KEY_LEFT => // cycle through monitors // c.GLFW_KEY_RIGHT => // cycle through monitors else => {}, } } fn sizeCallback(win: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void { const userptr = c.glfwGetWindowUserPointer(win).?; const self = @as(*WindowOutput, @ptrCast(@alignCast(userptr))); self.resized = true; _ = width; _ = height; } };