From 21295b8d03f3cef4cd7ed659c12f29a88a3f7d2d Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Sun, 21 May 2023 15:01:42 -0500 Subject: [PATCH] feat(ui): implement pausing --- lib/zba-util | 2 +- src/imgui.zig | 17 +++++++++ src/main.zig | 8 +++- src/platform.zig | 96 +++++++++++++++++++++++++++++++----------------- 4 files changed, 86 insertions(+), 37 deletions(-) diff --git a/lib/zba-util b/lib/zba-util index c1388c7..7234945 160000 --- a/lib/zba-util +++ b/lib/zba-util @@ -1 +1 @@ -Subproject commit c1388c731820d9847c9d54f698c55e07752bc5ea +Subproject commit 72349459ec29ee2d910a3dd3274be00254a7acd4 diff --git a/src/imgui.zig b/src/imgui.zig index a7910b7..337d7b3 100644 --- a/src/imgui.zig +++ b/src/imgui.zig @@ -30,6 +30,7 @@ pub const State = struct { fps_hist: RingBuffer(u32), should_quit: bool = false, + emulation: Emulation, win_stat: WindowStatus = .{}, @@ -41,6 +42,12 @@ pub const State = struct { show_palette: bool = false, }; + const Emulation = union(enum) { + Active, + Inactive, + Transition: enum { Active, Inactive }, + }; + /// if zba is initialized with a ROM already provided, this initializer should be called /// with `title_opt` being non-null pub fn init(allocator: Allocator, title_opt: ?*const [12]u8) !@This() { @@ -48,6 +55,7 @@ pub const State = struct { return .{ .title = handleTitle(title_opt), + .emulation = if (title_opt == null) .Inactive else .Active, .fps_hist = RingBuffer(u32).init(history), }; } @@ -90,6 +98,7 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void { }; state.title = handleTitle(&cpu.bus.pak.title); + state.emulation = .{ .Transition = .Active }; } } @@ -105,6 +114,14 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void { if (zgui.menuItem("Schedule", .{ .selected = state.win_stat.show_schedule })) state.win_stat.show_schedule = true; + if (zgui.menuItem("Paused", .{ .selected = state.emulation == .Inactive })) { + state.emulation = switch (state.emulation) { + .Active => .{ .Transition = .Inactive }, + .Inactive => .{ .Transition = .Active }, + else => state.emulation, + }; + } + if (zgui.menuItem("Restart", .{})) emu.reset(cpu); } diff --git a/src/main.zig b/src/main.zig index 13a2289..5706257 100644 --- a/src/main.zig +++ b/src/main.zig @@ -119,7 +119,7 @@ pub fn main() void { const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &quit }) catch |e| exitln("gdb server thread crashed: {}", .{e}); defer thread.join(); - gui.run(.{ + gui.run(.Debug, .{ .cpu = &cpu, .scheduler = &scheduler, .channel = &channel, @@ -127,10 +127,14 @@ pub fn main() void { } else { var tracker = FpsTracker.init(); + // emu should start paused if there's no ROM to run + if (paths.rom == null) + channel.emu.push(.Pause); + const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &channel }) catch |e| exitln("emu thread panicked: {}", .{e}); defer thread.join(); - gui.run(.{ + gui.run(.Standard, .{ .cpu = &cpu, .scheduler = &scheduler, .channel = &channel, diff --git a/src/platform.zig b/src/platform.zig index c5b7986..cf804df 100644 --- a/src/platform.zig +++ b/src/platform.zig @@ -99,7 +99,9 @@ pub const Gui = struct { scheduler: *Scheduler, }; - pub fn run(self: *Self, opt: RunOptions) !void { + const RunMode = enum { Standard, Debug }; + + pub fn run(self: *Self, comptime mode: RunMode, opt: RunOptions) !void { const cpu = opt.cpu; const tracker = opt.tracker; const channel = opt.channel; @@ -121,13 +123,6 @@ pub const Gui = struct { var win_dim: Dimensions = default_dim; emu_loop: while (true) { - // `quit` from RunOptions may be modified by the GDBSTUB thread, - // so we want to recognize that it may change to `true` and exit the GUI thread - if (channel.gui.pop()) |event| switch (event) { - .Quit => break :emu_loop, - .Paused => @panic("TODO: We want to peek (and then pop if it's .Quit), not always pop"), - }; - // Outside of `SDL.SDL_QUIT` below, the DearImgui UI might signal that the program // should exit, in which case we should also handle this if (self.state.should_quit) break :emu_loop; @@ -190,40 +185,73 @@ pub const Gui = struct { } } - { - channel.emu.push(.Pause); - defer channel.emu.push(.Resume); + zgui.backend.newFrame(@intToFloat(f32, win_dim.width), @intToFloat(f32, win_dim.height)); - // Spin Loop until we know that the emu is paused - wait: while (true) switch (channel.gui.pop() orelse continue) { - .Paused => break :wait, - else => |any| std.debug.panic("[Gui/Channel]: Unhandled Event: {}", .{any}), - }; + switch (self.state.emulation) { + .Transition => |inner| switch (inner) { + .Active => { + _ = channel.gui.pop(); - // Add FPS count to the histogram - if (tracker) |t| self.state.fps_hist.push(t.value()) catch {}; + channel.emu.push(.Resume); + self.state.emulation = .Active; + }, + .Inactive => { + // Assert that double pausing is impossible + if (channel.gui.peek()) |value| + std.debug.assert(value != .Paused); - // Draw GBA Screen to Texture - { - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id); - defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0); + channel.emu.push(.Pause); + self.state.emulation = .Inactive; + }, + }, + .Active => { + const is_std = mode == .Standard; - const buf = cpu.bus.ppu.framebuf.get(.Renderer); - gl.viewport(0, 0, gba_width, gba_height); - opengl_impl.drawScreenTexture(emu_tex, prog_id, objects, buf); - } + if (is_std) channel.emu.push(.Pause); + defer if (is_std) channel.emu.push(.Resume); - // Background Colour - const size = zgui.io.getDisplaySize(); - gl.viewport(0, 0, @floatToInt(GLsizei, size[0]), @floatToInt(GLsizei, size[1])); - gl.clearColor(0, 0, 0, 1.0); - gl.clear(gl.COLOR_BUFFER_BIT); + switch (mode) { + .Standard => { + // TODO: add timeout + while (true) switch (channel.gui.pop() orelse continue) { + .Paused => break, + .Quit => unreachable, // only signaled in debug mode + }; + }, + .Debug => blk: { + switch (channel.gui.pop() orelse break :blk) { + .Paused => unreachable, // only in standard mode + .Quit => break :emu_loop, // FIXME: gdb side of emu is seriously out-of-date... + } + }, + } - zgui.backend.newFrame(@intToFloat(f32, win_dim.width), @intToFloat(f32, win_dim.height)); - imgui.draw(&self.state, out_tex, cpu); - zgui.backend.draw(); + // Add FPS count to the histogram + if (tracker) |t| self.state.fps_hist.push(t.value()) catch {}; + + // Draw GBA Screen to Texture + { + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id); + defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0); + + const buf = cpu.bus.ppu.framebuf.get(.Renderer); + gl.viewport(0, 0, gba_width, gba_height); + opengl_impl.drawScreenTexture(emu_tex, prog_id, objects, buf); + } + + imgui.draw(&self.state, out_tex, cpu); + }, + .Inactive => imgui.draw(&self.state, out_tex, cpu), } + // Background Colour + const size = zgui.io.getDisplaySize(); + gl.viewport(0, 0, @floatToInt(GLsizei, size[0]), @floatToInt(GLsizei, size[1])); + gl.clearColor(0, 0, 0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + zgui.backend.draw(); + SDL.SDL_GL_SwapWindow(self.window); }