const c = @import("c.zig"); const gl = @import("gl.zig"); const video = @import("video.zig"); const std = @import("std"); const cfg = @import("config.zig"); fn verify_args(expected: u8, got: []const u8) !void { for (got) |typ| { if (typ != expected) { std.debug.print("expected '{c}' but got '{c}' element (expected {s})\n", .{ expected, typ, got }); return error.typeMismatch; } } } fn set_one( comptime T: type, dest: *T, argv: [][*c]c.lo_arg, types: []const u8, ) !void { if (types.len != 1) return error.sizeMismatch; switch (T) { f32 => { try verify_args('f', types); dest.* = argv[0].*.f; }, f64 => { try verify_args('d', types); dest.* = argv[0].*.d; }, i32 => { try verify_args('d', types); dest.* = argv[0].*.i; }, u32 => { try verify_args('d', types); const val = argv[0].*.i; if (val < 0) return error.signDisallowed; dest.* = @as(u32, @intCast(val)); }, else => return error.invalidType, } } fn set_array( comptime T: type, dest: []T, argv: [][*c]c.lo_arg, types: []const u8, ) !void { if (types.len != dest.len) return error.sizeMismatch; switch (T) { f32 => { try verify_args('f', types); for (dest, 0..) |*v, i| v.* = argv[i].*.f; }, f64 => { try verify_args('d', types); for (dest, 0..) |*v, i| v.* = argv[i].*.d; }, i32 => { try verify_args('i', types); for (dest, 0..) |*v, i| v.* = argv[i].*.i; }, u32 => { try verify_args('i', types); for (dest, 0..) |*v, i| { const val = argv[i].*.i; if (val < 0) return error.signDisallowed; v.* = @as(u32, @intCast(argv[i].*.i)); } }, else => return error.invalidType, } } fn set_texture( progress: std.Progress.Node, dest: *?gl.Texture, texture_type: gl.Texture.Type, argv: [][*c]c.lo_arg, types: []const u8, ) !void { if (types.len != 1 or types[0] != 's') return error.invalidType; if (dest.*) |old| { old.destroy(); dest.* = null; } var buffer: [1024]u8 = undefined; const filepath = try std.fs.cwd().realpathZ(@ptrCast(&argv[0].*.s), buffer[0..]); buffer[filepath.len] = 0; dest.* = try video.loadVideo(progress, @ptrCast(filepath), texture_type); } pub const ControlServer = struct { server: c.lo_server, cache: *gl.UniformCache, config: *cfg.Config, progress: std.Progress.Node, allocator: std.mem.Allocator, pub fn init( allocator: std.mem.Allocator, progress: std.Progress.Node, config: *cfg.Config, cache: *gl.UniformCache, ) !*ControlServer { var self: *ControlServer = try allocator.create(ControlServer); self.* = .{ .server = null, .cache = cache, .config = config, .progress = progress, .allocator = allocator, }; std.debug.print("listening for OSC messages at {s}\n", .{config.osc}); self.server = c.lo_server_new_from_url(config.osc.ptr, handle_error); if (self.server == null) return error.serverInitializationError; _ = c.lo_server_add_method(self.server, "/-/shader", "s", handle_shader, @as(*anyopaque, @ptrCast(self))); // _ = c.lo_server_add_method(self.server, "/-/reload", "", handle_reload, @as(*anyopaque, @ptrCast(self))); _ = c.lo_server_add_method(self.server, "/*", null, handle_uniform, @as(*anyopaque, @ptrCast(self))); return self; } pub fn update(self: *ControlServer) void { while (c.lo_server_recv_noblock(self.server, 0) > 0) {} } pub fn destroy(self: ControlServer) void { c.lo_server_free(self.server); } fn handle_error(num: c_int, msg: [*c]const u8, where: [*c]const u8) callconv(.C) void { std.debug.print( "OSC error {} @ {s}: {s}\n", .{ num, @as([*:0]const u8, @ptrCast(where)), @as([*:0]const u8, @ptrCast(msg)) }, ); } fn set_uniform( self: *ControlServer, path: []const u8, argv: [][*c]c.lo_arg, types: []const u8, ) !void { var parts = std.mem.tokenizeScalar(u8, path, '/'); var uniform: *gl.CachedUniform = undefined; if (parts.next()) |uniform_name| { const nameZ = try self.allocator.dupeZ(u8, uniform_name); defer self.allocator.free(nameZ); const res = try self.cache.get(nameZ); uniform = res orelse return error.notFound; } else { return error.invalidMessage; } var value = (uniform.value orelse unreachable).getPointer(); while (parts.next()) |component| { const i = switch (component[0]) { 'x', 'r', 's' => 0, 'y', 'g', 't' => 1, 'z', 'b', 'p' => 2, 'w', 'a', 'q' => 3, else => std.fmt.parseUnsigned(u8, component, 10) catch null, }; if (i) |ii| { value = try value.index(ii); } else { return error.invalidIndex; } } try switch (value) { .FLOAT => |val| set_one(f32, val, argv, types), .DOUBLE => |val| set_one(f64, val, argv, types), .INT => |val| set_one(i32, val, argv, types), .UNSIGNED_INT => |val| set_one(u32, val, argv, types), .BOOL => |val| set_one(u32, val, argv, types), .FLOAT_VEC2 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_VEC3 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_VEC4 => |val| set_array(f32, val.*[0..], argv, types), .DOUBLE_VEC2 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_VEC3 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_VEC4 => |val| set_array(f64, val.*[0..], argv, types), .INT_VEC2 => |val| set_array(i32, val.*[0..], argv, types), .INT_VEC3 => |val| set_array(i32, val.*[0..], argv, types), .INT_VEC4 => |val| set_array(i32, val.*[0..], argv, types), .UNSIGNED_INT_VEC2 => |val| set_array(u32, val.*[0..], argv, types), .UNSIGNED_INT_VEC3 => |val| set_array(u32, val.*[0..], argv, types), .UNSIGNED_INT_VEC4 => |val| set_array(u32, val.*[0..], argv, types), .BOOL_VEC2 => |val| set_array(u32, val.*[0..], argv, types), .BOOL_VEC3 => |val| set_array(u32, val.*[0..], argv, types), .BOOL_VEC4 => |val| set_array(u32, val.*[0..], argv, types), .FLOAT_MAT2 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_MAT3 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_MAT4 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_MAT2x3 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_MAT2x4 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_MAT3x2 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_MAT3x4 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_MAT4x2 => |val| set_array(f32, val.*[0..], argv, types), .FLOAT_MAT4x3 => |val| set_array(f32, val.*[0..], argv, types), .DOUBLE_MAT2 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_MAT3 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_MAT4 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_MAT2x3 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_MAT2x4 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_MAT3x2 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_MAT3x4 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_MAT4x2 => |val| set_array(f64, val.*[0..], argv, types), .DOUBLE_MAT4x3 => |val| set_array(f64, val.*[0..], argv, types), .SAMPLER_2D, .SAMPLER_2D_SHADOW, .INT_SAMPLER_2D, .UNSIGNED_INT_SAMPLER_2D, => |val| set_texture(self.progress, val, .TEXTURE_2D, argv, types), .SAMPLER_2D_ARRAY, .SAMPLER_2D_ARRAY_SHADOW, .INT_SAMPLER_2D_ARRAY, .UNSIGNED_INT_SAMPLER_2D_ARRAY, => |val| set_texture(self.progress, val, .TEXTURE_2D_ARRAY, argv, types), .SAMPLER_3D, .INT_SAMPLER_3D, .UNSIGNED_INT_SAMPLER_3D, => |val| set_texture(self.progress, val, .TEXTURE_3D, argv, types), else => error.uniformNotSupported, }; uniform.setShaderValue(self.cache.shader.*); } fn handle_shader( path: [*c]const u8, types: [*c]const u8, argv: [*c][*c]c.lo_arg, argc: c_int, msg: c.lo_message, userdata: ?*anyopaque, ) callconv(.C) c_int { const self: *ControlServer = @ptrCast(@alignCast(userdata.?)); const code: [*:0]const u8 = @ptrCast(&argv[0].*.s); _ = path; _ = types; _ = argc; _ = msg; _ = e: { self.cache.shader.loadString(std.mem.span(code)) catch |err| break :e err; self.cache.refresh() catch |err| break :e err; } catch |err| { std.debug.print("{} while loading shader from OSC\n", .{err}); }; return 0; } fn handle_reload( path: [*c]const u8, types: [*c]const u8, argv: [*c][*c]c.lo_arg, argc: c_int, msg: c.lo_message, userdata: ?*anyopaque, ) callconv(.C) c_int { const self: *ControlServer = @ptrCast(@alignCast(userdata.?)); _ = path; _ = types; _ = argc; _ = argv; _ = msg; _ = e: { self.cache.shader.loadFile(self.config.fragment, 2048) catch |err| break :e err; self.cache.refresh() catch |err| break :e err; } catch |err| { std.debug.print("{} while reloading shader from file\n", .{err}); }; return 0; } fn handle_uniform( _path: [*c]const u8, _types: [*c]const u8, _argv: [*c][*c]c.lo_arg, argc: c_int, msg: c.lo_message, userdata: ?*anyopaque, ) callconv(.C) c_int { _ = msg; const self: *ControlServer = @ptrCast(@alignCast(userdata.?)); const path = _path[0..c.strlen(_path)]; const argv: [][*c]c.lo_arg = _argv[0..@intCast(argc)]; const types = _types[0..@intCast(argc)]; self.set_uniform(path, argv, types) catch |err| { if (err != error.notFound) { std.debug.print("{} while processing {s}\n", .{ err, path }); } return 1; }; return 0; } fn handle_bundle_start(time: c.lo_timetag, userdata: ?*anyopaque) callconv(.C) c_int { _ = time; _ = userdata; return 1; } fn handle_bundle_end(userdata: ?*anyopaque) callconv(.C) c_int { _ = userdata; return 1; } };