const std = @import("std"); const c = @import("c.zig").c; 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 @panic("failed to init tsv gl_client"); } 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), .update_fn = update_source, .deinit_fn = deinit, }, .stream = .{}, .name = try allocator.dupeZ(u8, std.mem.span(name)), .thread = undefined, }; errdefer allocator.free(self.name); self.thread = try gl.Thread.init(allocator, constants, update_loop, .{self}); return &self.source; } fn update_source(source: *src.Source) !bool { const self: *TSVSource = @fieldParentPtr("source", source); if (self.thread.has_quit) return true; return false; } 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 { _ = c.gl_client_initialize_external_gl(); const 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 return error.tsvErrorCreateClient; const guard = c.gl_client_find_image_data(client, self.name, false) orelse return error.tsvDataNotFound; 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, ); while (!self.thread.quit) { try self.update(client); c.glFlush(); try std.Thread.yield(); } } fn update(self: *TSVSource, client: *c.GlClient) !void { if (!self.stream.shouldStep()) return; switch (c.gl_client_recv_image( client, self.name.ptr, self.source.texture.id, @intFromEnum(self.source.texture.type), false, 0, null, )) { c.RequiresUpdate, c.Found => {}, c.NotFound => return error.tsvRecvNotFound, c.Error => return error.tsvRecvError, else => unreachable, } } }; 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, }; switch (c.gl_client_init_image( get_or_init_client(), config.name, @intCast(constants.config.width), @intCast(constants.config.height), c.R8G8B8A8, true, )) { c.RequiresUpdate, c.Found => {}, c.NotFound => return error.tsvCreateNotFound, c.Error => return error.tsvCreateError, else => unreachable, } return &self.output; } }; output: Output, config: *const Config, fn update(output: *Output, fbo: gl.FramebufferObject, fresh: bool) !?bool { const self: *TSVOutput = @fieldParentPtr("output", output); if (fresh) { var prev_fbo: c.GLint = undefined; c.glGetIntegerv(c.GL_DRAW_FRAMEBUFFER_BINDING, &prev_fbo); switch (c.gl_client_send_image( get_or_init_client(), self.config.name, fbo.texture_id, c.GL_TEXTURE_2D, false, @intCast(prev_fbo), null, )) { 1 => {}, 0 => std.debug.print("tsv send: no remote image\n", .{}), else => |e| std.debug.print("tsv send: error {}\n", .{e}), } } return null; } fn destroy(output: *Output, allocator: std.mem.Allocator) void { const self: *TSVOutput = @fieldParentPtr("output", output); allocator.destroy(self); } };