const std = @import("std"); const c = @import("c.zig"); const gl = @import("gl.zig"); const src = @import("source.zig"); const ctrl = @import("control.zig"); const Output = @import("output.zig").Output; var global_client: ?*c.GlClient = null; fn get_or_init_client() *c.GlClient { if (global_client == null) { _ = c.gl_client_initialize_external_gl(); global_client = c.gl_client_new_with_server_launch( c.VK_SERVER_DEFAULT_SOCKET_PATH, 1000, c.VK_SERVER_EXECUTABLE, c.VK_SERVER_DEFAULT_LOCKFILE_PATH, c.VK_SERVER_DEFAULT_SOCKET_PATH, c.VK_SERVER_DEFAULT_SHMEM_PREFIX, 2000, 2000, 2000, 2000, 2000, ) orelse unreachable; } return global_client.?; } pub const TSVSource = struct { source: src.Source, stream: src.StreamFlags, name: [:0]const u8, thread: *gl.Thread, pub fn init( allocator: std.mem.Allocator, constants: *const gl.Constants, texture_type: gl.Texture.Type, name: [*:0]const u8, ) !*src.Source { const self = try allocator.create(TSVSource); errdefer allocator.destroy(self); self.* = .{ .source = .{ .texture = gl.Texture.create(texture_type), .deinit_fn = deinit, }, .stream = .{}, .name = try allocator.dupeZ(u8, std.mem.span(name)), .thread = undefined, }; errdefer allocator.free(self.name); const guard = c.gl_client_find_image_data(get_or_init_client(), name, false) orelse return error.notFound; defer c.gl_client_image_data_guard_destroy(guard); const metadata = c.gl_client_image_data_guard_read(guard) orelse return error.tsvError; self.source.texture.allocate( @intCast(metadata.*.width), @intCast(metadata.*.height), 0, c.GL_RGBA8, ); self.update(); self.thread = try gl.Thread.init(allocator, constants, update_loop, .{self}); return &self.source; } fn deinit(source: *const src.Source, allocator: std.mem.Allocator) void { const self: *const TSVSource = @fieldParentPtr("source", source); self.thread.deinit(allocator); allocator.free(self.name); allocator.destroy(self); } fn update_loop(self: *TSVSource) !void { while (!self.thread.quit) { self.update(); c.glFlush(); try std.Thread.yield(); } } fn update(self: *TSVSource) void { if (!self.stream.shouldStep()) return; _ = c.gl_client_recv_image( get_or_init_client(), self.name.ptr, self.source.texture.id, @intFromEnum(self.source.texture.type), false, 0, null, ); } }; pub const TSVOutput = struct { pub const Config = struct { name: [:0]const u8, pub const default: Config = .{ .name = "glsl-view", }; pub fn create( config: *const Config, allocator: std.mem.Allocator, constants: *gl.Constants, ) *Output { const self = allocator.create(TSVOutput) catch unreachable; self.* = .{ .output = .{ .update_fn = update, .destroy_fn = destroy, }, .config = config, }; _ = c.gl_client_init_image( get_or_init_client(), config.name, @intCast(constants.config.width), @intCast(constants.config.height), c.R8G8B8A8, true, ); return &self.output; } }; output: Output, config: *const Config, 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( get_or_init_client(), self.config.name, texture_id, c.GL_TEXTURE_2D, false, @intCast(fbo), null, ); return false; } fn destroy(output: *Output, allocator: std.mem.Allocator) void { const self: *TSVOutput = @fieldParentPtr("output", output); allocator.destroy(self); } };