diff --git a/src/state/actor.zig b/src/state/actor.zig index 9b18de2..060bbaf 100644 --- a/src/state/actor.zig +++ b/src/state/actor.zig @@ -137,7 +137,13 @@ pub const Actor = struct { const thread_2 = try std.Thread.spawn(.{}, struct { fn run(_self: *Self) void { - if (_self.state.user_settings.settings.restore_capture_source_on_startup) { + const restore_capture_source_on_startup = blk: { + const settings_locked = _self.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + break :blk settings.restore_capture_source_on_startup; + }; + if (restore_capture_source_on_startup) { _self.handle_action(.restore_capture_session) catch |err| { log.err("[capture_startup] restore_capture_session error: {}\n", .{err}); }; @@ -151,9 +157,13 @@ pub const Actor = struct { fn run(_self: *Self, t1: std.Thread, t2: std.Thread) void { t1.join(); t2.join(); - if (_self.state.user_settings.settings.restore_capture_source_on_startup and - _self.state.user_settings.settings.start_replay_buffer_on_startup) - { + const should_handle_action: bool = blk: { + const settings_locked = _self.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + break :blk settings.restore_capture_source_on_startup and settings.start_replay_buffer_on_startup; + }; + if (should_handle_action) { _self.handle_action(.start_record) catch |err| { log.err("[capture_startup] start_record error: {}", .{err}); }; @@ -226,8 +236,14 @@ pub const Actor = struct { log.debug("[handle_action] save_replay - not recording, skipping capture", .{}); return; } - fps = self.state.user_settings.settings.capture_fps; - replay_seconds = self.state.user_settings.settings.replay_seconds; + } + + { + const settings_locked = self.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + fps = settings.capture_fps; + replay_seconds = settings.replay_seconds; } // We should always have a size if the state is recording. @@ -320,9 +336,9 @@ pub const Actor = struct { try self.stop_capture(); const fps = blk: { - self.ui_mutex.lock(); - defer self.ui_mutex.unlock(); - break :blk self.state.user_settings.settings.capture_fps; + const settings_locked = self.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + break :blk settings_locked.unwrap_ptr().capture_fps; }; self.video_capture.select_source(selection, fps) catch |err| { @@ -355,9 +371,9 @@ pub const Actor = struct { while (true) { const fps = blk: { - self.ui_mutex.lock(); - defer self.ui_mutex.unlock(); - break :blk self.state.user_settings.settings.capture_fps; + const settings_locked = self.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + break :blk settings_locked.unwrap_ptr().capture_fps; }; // Here we wait until the next projected frame time. This will happen if we are @@ -515,10 +531,18 @@ pub const Actor = struct { { self.ui_mutex.lock(); defer self.ui_mutex.unlock(); - if (!self.state.is_capturing_video or self.state.is_recording_video) return; - fps = self.state.user_settings.settings.capture_fps; - capture_bit_rate = self.state.user_settings.settings.capture_bit_rate; - replay_seconds = self.state.user_settings.settings.replay_seconds; + if (!self.state.is_capturing_video or self.state.is_recording_video) { + return; + } + } + + { + const settings_locked = self.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + fps = settings.capture_fps; + capture_bit_rate = settings.capture_bit_rate; + replay_seconds = settings.replay_seconds; } const size = self.video_capture.size() orelse { diff --git a/src/state/audio_state.zig b/src/state/audio_state.zig index 4178d69..1f2302e 100644 --- a/src/state/audio_state.zig +++ b/src/state/audio_state.zig @@ -91,9 +91,10 @@ pub const AudioState = struct { defer available_devices.deinit(); var user_settings = blk: { - actor.ui_mutex.lock(); - defer actor.ui_mutex.unlock(); - break :blk try actor.state.user_settings.settings.clone(self.allocator); + const settings_locked = actor.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + break :blk try settings.clone(self.allocator); }; defer user_settings.deinit(self.allocator); @@ -185,9 +186,10 @@ pub const AudioState = struct { }, .start_record => { const replay_seconds = blk: { - actor.ui_mutex.lock(); - defer actor.ui_mutex.unlock(); - break :blk actor.state.user_settings.settings.replay_seconds; + const settings_locked = actor.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + break :blk settings.replay_seconds; }; var replay_buffer_locked = self.audio_replay_buffer.lock(); defer replay_buffer_locked.unlock(); diff --git a/src/state/user_settings.zig b/src/state/user_settings.zig index 98f9be8..00c4ff9 100644 --- a/src/state/user_settings.zig +++ b/src/state/user_settings.zig @@ -9,9 +9,6 @@ pub const UserSettings = struct { gain: f32 = 1.0, }; - // NOTE: Default values here are default user settings. - gui_foreground_fps: u32 = 120, - gui_background_fps: u32 = 30, capture_fps: u32 = 60, /// In bits per second (bps). capture_bit_rate: u64 = 10_000_000, diff --git a/src/state/user_settings_state.zig b/src/state/user_settings_state.zig index 5d0ab0c..b18421d 100644 --- a/src/state/user_settings_state.zig +++ b/src/state/user_settings_state.zig @@ -5,6 +5,7 @@ const ActionPayload = @import("./action_payload.zig").ActionPayload; const util = @import("../util.zig"); const Actions = @import("./actor.zig").Actions; const UserSettings = @import("./user_settings.zig").UserSettings; +const Mutex = @import("../mutex.zig").Mutex; const log = std.log.scoped(.user_settings_state); @@ -12,8 +13,6 @@ pub const UserSettingsActions = union(enum) { set_capture_fps: u32, set_capture_bit_rate: u64, set_replay_seconds: u32, - set_gui_foreground_fps: u32, - set_gui_background_fps: u32, set_restore_capture_source_on_startup: bool, set_start_replay_buffer_on_startup: bool, set_audio_device_settings: *ActionPayload(struct { @@ -38,8 +37,7 @@ pub const UserSettingsState = struct { const Self = @This(); allocator: Allocator, - // TODO: Use mutex here instead of ui_mutex. - settings: UserSettings = .{}, + settings: Mutex(UserSettings) = .init(.{}), pub fn init(allocator: Allocator) !Self { var self: Self = .{ @@ -52,7 +50,10 @@ pub const UserSettingsState = struct { } pub fn deinit(self: *Self) void { - self.settings.deinit(self.allocator); + var settings_locked = self.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + settings.deinit(self.allocator); } pub fn handle_action(self: *Self, actor: *Actor, action: Actions) !void { @@ -69,12 +70,6 @@ pub const UserSettingsState = struct { .set_replay_seconds => |replay_seconds| { try self.set_state(actor, "replay_seconds", replay_seconds); }, - .set_gui_foreground_fps => |gui_foreground_fps| { - try self.set_state(actor, "gui_foreground_fps", gui_foreground_fps); - }, - .set_gui_background_fps => |gui_background_fps| { - try self.set_state(actor, "gui_background_fps", gui_background_fps); - }, .set_start_replay_buffer_on_startup => |start_replay_buffer_on_startup| { try self.set_state(actor, "start_replay_buffer_on_startup", start_replay_buffer_on_startup); }, @@ -86,15 +81,16 @@ pub const UserSettingsState = struct { const payload = _action.payload; var settings_snapshot: UserSettings = undefined; { - actor.ui_mutex.lock(); - defer actor.ui_mutex.unlock(); - try self.settings.update_audio_device_settings( + const settings_locked = actor.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + try settings.update_audio_device_settings( self.allocator, payload.device_id, payload.selected, payload.gain, ); - settings_snapshot = try self.settings.clone(self.allocator); + settings_snapshot = try settings.clone(self.allocator); } defer settings_snapshot.deinit(self.allocator); try self.save(&settings_snapshot); @@ -120,10 +116,11 @@ pub const UserSettingsState = struct { value: anytype, ) !void { var settings_snapshot: UserSettings = blk: { - actor.ui_mutex.lock(); - defer actor.ui_mutex.unlock(); - @field(self.settings, field_name) = value; - break :blk try self.settings.clone(self.allocator); + const settings_locked = actor.state.user_settings.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + @field(settings, field_name) = value; + break :blk try settings.clone(self.allocator); }; defer settings_snapshot.deinit(self.allocator); try self.save(&settings_snapshot); @@ -171,8 +168,11 @@ pub const UserSettingsState = struct { ); } - self.settings.deinit(self.allocator); - self.settings = loaded; + var settings_locked = self.settings.lock(); + defer settings_locked.unlock(); + const settings = settings_locked.unwrap_ptr(); + settings.deinit(self.allocator); + settings_locked.set(loaded); } /// Save a copy of settings to disk. diff --git a/src/ui/draw_left_column.zig b/src/ui/draw_left_column.zig index 0d9a310..6f9c088 100644 --- a/src/ui/draw_left_column.zig +++ b/src/ui/draw_left_column.zig @@ -188,8 +188,6 @@ pub fn draw_left_column(allocator: std.mem.Allocator, actor: *Actor) !void { c.ImGuiWindowFlags_NoCollapse); defer c.ImGui_End(); - // c.ImGui_SeparatorText("Spacecap"); - if (c.ImGui_BeginTabBar("MainTabBar", 0)) { defer c.ImGui_EndTabBar(); @@ -291,64 +289,6 @@ pub fn draw_left_column(allocator: std.mem.Allocator, actor: *Actor) !void { try draw_capture_settings(allocator, actor); - c.ImGui_SeparatorText("GUI Settings"); - - c.ImGui_Text("FG FPS"); - c.ImGui_SameLine(); - imgui_util.help_marker("Forground FPS. This is the max frame rate Spacecap will render while focused. Drag or double click to change."); - c.ImGui_SetNextItemWidth(-std.math.floatMin(f32)); - const current_fg_fps: i32 = @intCast(actor.state.user_settings.settings.gui_foreground_fps); - var fg_fps = fg_fps_local orelse current_fg_fps; - if (c.ImGui_InputIntEx( - "##fg_fps", - &fg_fps, - 5, - 10, - c.ImGuiInputTextFlags_None, - )) { - fg_fps = std.math.clamp(fg_fps, GUI_FPS_MIN, GUI_FPS_MAX); - fg_fps_local = fg_fps; - } - if (c.ImGui_IsItemDeactivatedAfterEdit() and fg_fps != current_fg_fps) { - try actor.dispatch(.{ .user_settings = .{ - .set_gui_foreground_fps = @intCast(fg_fps), - } }); - fg_fps_local = null; - } else if (!c.ImGui_IsItemActive()) { - // Keep the UI synced with state when not actively editing. - fg_fps_local = null; - } - - c.ImGui_Text("BG FPS"); - c.ImGui_SameLine(); - imgui_util.help_marker("Background FPS. This is the max frame rate Spacecap will render while NOT focused. Drag or double click to change."); - c.ImGui_SetNextItemWidth(-std.math.floatMin(f32)); - const current_bg_fps: i32 = @intCast(actor.state.user_settings.settings.gui_background_fps); - var bg_fps = bg_fps_local orelse current_bg_fps; - if (c.ImGui_InputIntEx( - "##bg_fps", - &bg_fps, - 5, - 10, - c.ImGuiInputTextFlags_None, - )) { - bg_fps = std.math.clamp(bg_fps, GUI_FPS_MIN, GUI_FPS_MAX); - bg_fps_local = bg_fps; - } - if (c.ImGui_IsItemDeactivatedAfterEdit() and bg_fps != current_bg_fps) { - try actor.dispatch(.{ .user_settings = .{ - .set_gui_background_fps = @intCast(bg_fps), - } }); - bg_fps_local = null; - } else if (!c.ImGui_IsItemActive()) { - // Keep the UI synced with state when not actively editing. - bg_fps_local = null; - } - - const io = c.ImGui_GetIO(); - c.ImGui_TextDisabled("%.3f ms/frame", 1000.0 / io.*.Framerate); - c.ImGui_TextDisabled("%.1f fps", io.*.Framerate); - // NOTE: Hiding this for now. Linux shortcuts can be configured at the // desktop environment level. See comments regarding `Method.configure_shortcuts` // in `xdg_desktop_portal_global_shortcuts.zig` for more info. @@ -369,6 +309,11 @@ pub fn draw_left_column(allocator: std.mem.Allocator, actor: *Actor) !void { if (c.ImGui_ButtonEx("Show Demo", .{ .x = c.ImGui_GetContentRegionAvail().x, .y = 0 })) { try actor.dispatch(.show_demo); } + + c.ImGui_Spacing(); + const io = c.ImGui_GetIO(); + c.ImGui_TextDisabled("%.3f ms/frame", 1000.0 / io.*.Framerate); + c.ImGui_TextDisabled("%.1f fps", io.*.Framerate); } } } @@ -377,9 +322,19 @@ pub fn draw_left_column(allocator: std.mem.Allocator, actor: *Actor) !void { fn draw_capture_settings(allocator: std.mem.Allocator, actor: *Actor) !void { c.ImGui_SeparatorText("Capture Settings"); + const settings_locked = actor.state.user_settings.settings.lock(); + const settings = settings_locked.unwrap_ptr(); + + const current_capture_fps: i32 = @intCast(settings.capture_fps); + const current_capture_bit_rate: i32 = @intCast(settings.capture_bit_rate / CAPTURE_BIT_RATE_BPS_PER_KBPS); + const current_replay_seconds: i32 = @intCast(settings.replay_seconds); + var restore_capture_source_on_startup = settings.restore_capture_source_on_startup; + var start_replay_buffer_on_startup = settings.start_replay_buffer_on_startup; + + settings_locked.unlock(); + // FPS { - const current_capture_fps: i32 = @intCast(actor.state.user_settings.settings.capture_fps); var fps = capture_fps_local orelse current_capture_fps; c.ImGui_Text("Max FPS"); c.ImGui_SameLine(); @@ -412,7 +367,6 @@ fn draw_capture_settings(allocator: std.mem.Allocator, actor: *Actor) !void { c.ImGui_SameLine(); imgui_util.help_marker("Capture bitrate in Kbps"); c.ImGui_SetNextItemWidth(-std.math.floatMin(f32)); - const current_capture_bit_rate: i32 = @intCast(actor.state.user_settings.settings.capture_bit_rate / CAPTURE_BIT_RATE_BPS_PER_KBPS); var capture_bit_rate = capture_bit_rate_local orelse current_capture_bit_rate; if (c.ImGui_InputIntEx( "##bitrate", @@ -447,7 +401,6 @@ fn draw_capture_settings(allocator: std.mem.Allocator, actor: *Actor) !void { c.ImGui_SameLine(); imgui_util.help_marker("Length of video and audio stored in memory (seconds)"); c.ImGui_SetNextItemWidth(-std.math.floatMin(f32)); - const current_replay_seconds: i32 = @intCast(actor.state.user_settings.settings.replay_seconds); var replay_seconds = replay_seconds_local orelse current_replay_seconds; if (c.ImGui_InputIntEx( "##replay_buffer_length", @@ -478,7 +431,6 @@ fn draw_capture_settings(allocator: std.mem.Allocator, actor: *Actor) !void { c.ImGui_Text("Restore capture source on startup"); c.ImGui_SameLine(); imgui_util.help_marker("Try to restore the last capture source when Spacecap starts."); - var restore_capture_source_on_startup = actor.state.user_settings.settings.restore_capture_source_on_startup; if (c.ImGui_Checkbox("##restore_capture_source_on_startup", &restore_capture_source_on_startup)) { try actor.dispatch(.{ .user_settings = .{ .set_restore_capture_source_on_startup = restore_capture_source_on_startup, @@ -489,9 +441,8 @@ fn draw_capture_settings(allocator: std.mem.Allocator, actor: *Actor) !void { c.ImGui_Text("Start replay buffer on startup"); c.ImGui_SameLine(); imgui_util.help_marker("Start the replay buffer when Spacecap starts. Requires 'Restore capture source on startup'."); - c.ImGui_BeginDisabled(!actor.state.user_settings.settings.restore_capture_source_on_startup); + c.ImGui_BeginDisabled(!restore_capture_source_on_startup); defer c.ImGui_EndDisabled(); - var start_replay_buffer_on_startup = actor.state.user_settings.settings.start_replay_buffer_on_startup; if (c.ImGui_Checkbox("##start_replay_buffer_on_startup", &start_replay_buffer_on_startup)) { try actor.dispatch(.{ .user_settings = .{ .set_start_replay_buffer_on_startup = start_replay_buffer_on_startup, diff --git a/src/ui/ui.zig b/src/ui/ui.zig index 6f445e4..0a5300b 100644 --- a/src/ui/ui.zig +++ b/src/ui/ui.zig @@ -387,21 +387,6 @@ pub const UI = struct { try self.frame_present(); } } - - // Delay until the next frame to maintain desired FPS. - // We use mailbox present mode so unlimited FPS can get - // pretty high e.g. 2k+ - self.actor.ui_mutex.lock(); - const fg_fps = self.actor.state.user_settings.settings.gui_foreground_fps; - const bg_fps = self.actor.state.user_settings.settings.gui_background_fps; - self.actor.ui_mutex.unlock(); - - const frame_duration_ns = if (window_has_focus) (1_000_000_000 / fg_fps) else (1_000_000_000 / bg_fps); - const elapsed_ns = timer.read(); - if (elapsed_ns < frame_duration_ns) { - const sleep_duration_ns = frame_duration_ns - elapsed_ns; - c.SDL_DelayNS(sleep_duration_ns); - } } try self.actor.dispatch(.exit); @@ -535,11 +520,11 @@ pub const UI = struct { // Select Present Mode const present_modes = [_]c.VkPresentModeKHR{ - // NOTE: Had some deadlocks with fifo, use mailbox instead - // and limit fps manually. - // c.VK_PRESENT_MODE_FIFO_KHR, // c.VK_PRESENT_MODE_IMMEDIATE_KHR, - c.VK_PRESENT_MODE_MAILBOX_KHR, + // c.VK_PRESENT_MODE_MAILBOX_KHR, + // NOTE: FIFO seems to be the best for a desktop application. Had some deadlock issues + // in the past with this, but it should be resolved now. + c.VK_PRESENT_MODE_FIFO_KHR, }; self.vulkan.window.?.PresentMode = c.cImGui_ImplVulkanH_SelectPresentMode( self.vk_physical_device(),