feat(ui): implement pausing
This commit is contained in:
parent
89671f767e
commit
21295b8d03
|
@ -1 +1 @@
|
||||||
Subproject commit c1388c731820d9847c9d54f698c55e07752bc5ea
|
Subproject commit 72349459ec29ee2d910a3dd3274be00254a7acd4
|
|
@ -30,6 +30,7 @@ pub const State = struct {
|
||||||
|
|
||||||
fps_hist: RingBuffer(u32),
|
fps_hist: RingBuffer(u32),
|
||||||
should_quit: bool = false,
|
should_quit: bool = false,
|
||||||
|
emulation: Emulation,
|
||||||
|
|
||||||
win_stat: WindowStatus = .{},
|
win_stat: WindowStatus = .{},
|
||||||
|
|
||||||
|
@ -41,6 +42,12 @@ pub const State = struct {
|
||||||
show_palette: bool = false,
|
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
|
/// if zba is initialized with a ROM already provided, this initializer should be called
|
||||||
/// with `title_opt` being non-null
|
/// with `title_opt` being non-null
|
||||||
pub fn init(allocator: Allocator, title_opt: ?*const [12]u8) !@This() {
|
pub fn init(allocator: Allocator, title_opt: ?*const [12]u8) !@This() {
|
||||||
|
@ -48,6 +55,7 @@ pub const State = struct {
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.title = handleTitle(title_opt),
|
.title = handleTitle(title_opt),
|
||||||
|
.emulation = if (title_opt == null) .Inactive else .Active,
|
||||||
.fps_hist = RingBuffer(u32).init(history),
|
.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.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 }))
|
if (zgui.menuItem("Schedule", .{ .selected = state.win_stat.show_schedule }))
|
||||||
state.win_stat.show_schedule = true;
|
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", .{}))
|
if (zgui.menuItem("Restart", .{}))
|
||||||
emu.reset(cpu);
|
emu.reset(cpu);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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});
|
const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &quit }) catch |e| exitln("gdb server thread crashed: {}", .{e});
|
||||||
defer thread.join();
|
defer thread.join();
|
||||||
|
|
||||||
gui.run(.{
|
gui.run(.Debug, .{
|
||||||
.cpu = &cpu,
|
.cpu = &cpu,
|
||||||
.scheduler = &scheduler,
|
.scheduler = &scheduler,
|
||||||
.channel = &channel,
|
.channel = &channel,
|
||||||
|
@ -127,10 +127,14 @@ pub fn main() void {
|
||||||
} else {
|
} else {
|
||||||
var tracker = FpsTracker.init();
|
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});
|
const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &channel }) catch |e| exitln("emu thread panicked: {}", .{e});
|
||||||
defer thread.join();
|
defer thread.join();
|
||||||
|
|
||||||
gui.run(.{
|
gui.run(.Standard, .{
|
||||||
.cpu = &cpu,
|
.cpu = &cpu,
|
||||||
.scheduler = &scheduler,
|
.scheduler = &scheduler,
|
||||||
.channel = &channel,
|
.channel = &channel,
|
||||||
|
|
|
@ -99,7 +99,9 @@ pub const Gui = struct {
|
||||||
scheduler: *Scheduler,
|
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 cpu = opt.cpu;
|
||||||
const tracker = opt.tracker;
|
const tracker = opt.tracker;
|
||||||
const channel = opt.channel;
|
const channel = opt.channel;
|
||||||
|
@ -121,13 +123,6 @@ pub const Gui = struct {
|
||||||
var win_dim: Dimensions = default_dim;
|
var win_dim: Dimensions = default_dim;
|
||||||
|
|
||||||
emu_loop: while (true) {
|
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
|
// Outside of `SDL.SDL_QUIT` below, the DearImgui UI might signal that the program
|
||||||
// should exit, in which case we should also handle this
|
// should exit, in which case we should also handle this
|
||||||
if (self.state.should_quit) break :emu_loop;
|
if (self.state.should_quit) break :emu_loop;
|
||||||
|
@ -190,40 +185,73 @@ pub const Gui = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
zgui.backend.newFrame(@intToFloat(f32, win_dim.width), @intToFloat(f32, win_dim.height));
|
||||||
channel.emu.push(.Pause);
|
|
||||||
defer channel.emu.push(.Resume);
|
|
||||||
|
|
||||||
// Spin Loop until we know that the emu is paused
|
switch (self.state.emulation) {
|
||||||
wait: while (true) switch (channel.gui.pop() orelse continue) {
|
.Transition => |inner| switch (inner) {
|
||||||
.Paused => break :wait,
|
.Active => {
|
||||||
else => |any| std.debug.panic("[Gui/Channel]: Unhandled Event: {}", .{any}),
|
_ = channel.gui.pop();
|
||||||
};
|
|
||||||
|
|
||||||
// Add FPS count to the histogram
|
channel.emu.push(.Resume);
|
||||||
if (tracker) |t| self.state.fps_hist.push(t.value()) catch {};
|
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
|
channel.emu.push(.Pause);
|
||||||
{
|
self.state.emulation = .Inactive;
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id);
|
},
|
||||||
defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0);
|
},
|
||||||
|
.Active => {
|
||||||
|
const is_std = mode == .Standard;
|
||||||
|
|
||||||
const buf = cpu.bus.ppu.framebuf.get(.Renderer);
|
if (is_std) channel.emu.push(.Pause);
|
||||||
gl.viewport(0, 0, gba_width, gba_height);
|
defer if (is_std) channel.emu.push(.Resume);
|
||||||
opengl_impl.drawScreenTexture(emu_tex, prog_id, objects, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background Colour
|
switch (mode) {
|
||||||
const size = zgui.io.getDisplaySize();
|
.Standard => {
|
||||||
gl.viewport(0, 0, @floatToInt(GLsizei, size[0]), @floatToInt(GLsizei, size[1]));
|
// TODO: add timeout
|
||||||
gl.clearColor(0, 0, 0, 1.0);
|
while (true) switch (channel.gui.pop() orelse continue) {
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
.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));
|
// Add FPS count to the histogram
|
||||||
imgui.draw(&self.state, out_tex, cpu);
|
if (tracker) |t| self.state.fps_hist.push(t.value()) catch {};
|
||||||
zgui.backend.draw();
|
|
||||||
|
// 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);
|
SDL.SDL_GL_SwapWindow(self.window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue