const c = @import("c.zig"); const std = @import("std"); const cfg = @import("config.zig"); const gl = @import("gl.zig"); pub const Output = struct { update_fn: *const fn (output: *Output, texture_id: c.GLuint) bool, destroy_fn: *const fn (output: *Output, allocator: std.mem.Allocator) void, pub fn create( allocator: std.mem.Allocator, config: *const cfg.OutputConfig, constants: *gl.Constants, ) *Output { return switch (config.*) { .window => |*con| WindowOutput.create(allocator, con, constants), .texture_share_vk => |*con| TSVOutput.create(allocator, con, constants), }; } pub fn update(self: *Output, texture_id: c.GLuint) bool { return self.update_fn(self, texture_id); } pub fn destroy(self: *Output, allocator: std.mem.Allocator) void { return self.destroy_fn(self, allocator); } }; pub const TSVOutput = struct { pub const Config = struct { name: [:0]const u8, pub const default: Config = .{ .name = "glsl-view", }; }; output: Output, config: *const Config, client: *c.GlClient, image_size: c.GlImageExtent, pub fn create( allocator: std.mem.Allocator, config: *const Config, constants: *gl.Constants, ) *Output { const self = allocator.create(TSVOutput) catch unreachable; _ = c.gl_client_initialize_external_gl() or unreachable; self.* = .{ .output = .{ .update_fn = update, .destroy_fn = destroy, }, .config = config, .client = c.gl_client_new(c.VK_SERVER_DEFAULT_SOCKET_PATH, 1000) orelse unreachable, .image_size = .{ .top_left = .{ 0, 0 }, .bottom_right = .{ constants.config.width, constants.config.height }, }, }; _ = c.gl_client_init_image( self.client, config.name, @intCast(constants.config.width), @intCast(constants.config.height), c.R8G8B8A8, true, ); return &self.output; } fn update(output: *Output, texture_id: c.GLuint) bool { const self: *TSVOutput = @fieldParentPtr("output", output); var fbo: c.GLint = undefined; c.glGetIntegerv(c.GL_DRAW_FRAMEBUFFER_BINDING, &fbo); _ = c.gl_client_send_image( self.client, self.config.name, texture_id, c.GL_TEXTURE_2D, false, @intCast(fbo), &self.image_size, ); return false; } fn destroy(output: *Output, allocator: std.mem.Allocator) void { const self: *TSVOutput = @fieldParentPtr("output", output); allocator.destroy(self); } }; pub const WindowOutput = struct { pub const Config = struct { const FilterMode = enum(c.GLuint) { nearest = c.GL_NEAREST, linear = c.GL_LINEAR, }; width: i32, height: i32, filter: FilterMode, monitor: []const u8, fullscreen: bool, pub const default: Config = .{ .width = 800, .height = 600, .filter = .linear, .monitor = "", .fullscreen = false, }; }; output: Output, window: *c.GLFWwindow, constants: *gl.Constants, resized: bool = false, pub fn create( allocator: std.mem.Allocator, config: *const Config, constants: *gl.Constants, ) *Output { const self = allocator.create(WindowOutput) catch unreachable; c.glfwDefaultWindowHints(); self.* = .{ .constants = constants, .output = .{ .update_fn = update, .destroy_fn = 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, allocator: std.mem.Allocator) void { const self: *WindowOutput = @fieldParentPtr("output", output); c.glfwDestroyWindow(self.window); allocator.destroy(self); } 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; } };