Compare commits

...

3 Commits

5 changed files with 59 additions and 64 deletions

@ -1 +1 @@
Subproject commit 72349459ec29ee2d910a3dd3274be00254a7acd4 Subproject commit 77d11c9903d7f4a3a31ef59fc954421d7dfa0eb1

View File

@ -5,7 +5,9 @@ const config = @import("../config.zig");
const Scheduler = @import("scheduler.zig").Scheduler; const Scheduler = @import("scheduler.zig").Scheduler;
const Arm7tdmi = @import("cpu.zig").Arm7tdmi; const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
const Tracker = @import("../util.zig").FpsTracker; const Tracker = @import("../util.zig").FpsTracker;
const TwoWayChannel = @import("zba-util").TwoWayChannel; const Channel = @import("zba-util").Channel(Message, 0x100);
pub const Message = enum { Pause, Resume, Quit };
const Timer = std.time.Timer; const Timer = std.time.Timer;
@ -35,18 +37,18 @@ const RunKind = enum {
LimitedFPS, LimitedFPS,
}; };
pub fn run(cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: *Tracker, channel: *TwoWayChannel) void { pub fn run(cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: *Tracker, rx: Channel.Receiver) void {
const audio_sync = config.config().guest.audio_sync and !config.config().host.mute; const audio_sync = config.config().guest.audio_sync and !config.config().host.mute;
if (audio_sync) log.info("Audio sync enabled", .{}); if (audio_sync) log.info("Audio sync enabled", .{});
if (config.config().guest.video_sync) { if (config.config().guest.video_sync) {
inner(.LimitedFPS, audio_sync, cpu, scheduler, tracker, channel); inner(.LimitedFPS, audio_sync, cpu, scheduler, tracker, rx);
} else { } else {
inner(.UnlimitedFPS, audio_sync, cpu, scheduler, tracker, channel); inner(.UnlimitedFPS, audio_sync, cpu, scheduler, tracker, rx);
} }
} }
fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: ?*Tracker, channel: *TwoWayChannel) void { fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: ?*Tracker, rx: Channel.Receiver) void {
if (kind == .UnlimitedFPS or kind == .LimitedFPS) { if (kind == .UnlimitedFPS or kind == .LimitedFPS) {
std.debug.assert(tracker != null); std.debug.assert(tracker != null);
log.info("FPS tracking enabled", .{}); log.info("FPS tracking enabled", .{});
@ -59,13 +61,9 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
log.info("Emulation w/out video sync", .{}); log.info("Emulation w/out video sync", .{});
while (true) { while (true) {
if (channel.emu.pop()) |e| switch (e) { if (rx.recv()) |m| switch (m) {
.Quit => break, .Quit => break,
.Resume => paused = false, .Resume, .Pause => paused = m == .Pause,
.Pause => {
paused = true;
channel.gui.push(.Paused);
},
}; };
if (paused) continue; if (paused) continue;
@ -82,13 +80,9 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
var wake_time: u64 = frame_period; var wake_time: u64 = frame_period;
while (true) { while (true) {
if (channel.emu.pop()) |e| switch (e) { if (rx.recv()) |m| switch (m) {
.Quit => break, .Quit => break,
.Resume => paused = false, .Resume, .Pause => paused = m == .Pause,
.Pause => {
paused = true;
channel.gui.push(.Paused);
},
}; };
if (paused) continue; if (paused) continue;

View File

@ -56,7 +56,7 @@ pub const State = struct {
return .{ return .{
.title = handleTitle(title_opt), .title = handleTitle(title_opt),
.emulation = if (title_opt == null) .Inactive else .Active, .emulation = if (title_opt == null) .Inactive else .{ .Transition = .Active },
.fps_hist = RingBuffer(u32).init(history), .fps_hist = RingBuffer(u32).init(history),
}; };
} }

View File

@ -6,7 +6,7 @@ const clap = @import("clap");
const config = @import("config.zig"); const config = @import("config.zig");
const emu = @import("core/emu.zig"); const emu = @import("core/emu.zig");
const TwoWayChannel = @import("zba-util").TwoWayChannel; const Channel = @import("zba-util").Channel(emu.Message, 0x100);
const Gui = @import("platform.zig").Gui; const Gui = @import("platform.zig").Gui;
const Bus = @import("core/Bus.zig"); const Bus = @import("core/Bus.zig");
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi; const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
@ -98,8 +98,8 @@ pub fn main() void {
var quit = std.atomic.Atomic(bool).init(false); var quit = std.atomic.Atomic(bool).init(false);
var items: [0x100]u8 = undefined; var ch = Channel.init(allocator) catch |e| exitln("failed to initialize ui -> emu thread message channel: {}", .{e});
var channel = TwoWayChannel.init(&items); defer ch.deinit(allocator);
if (result.args.gdb != 0) { if (result.args.gdb != 0) {
const Server = @import("gdbstub").Server; const Server = @import("gdbstub").Server;
@ -122,22 +122,22 @@ pub fn main() void {
gui.run(.Debug, .{ gui.run(.Debug, .{
.cpu = &cpu, .cpu = &cpu,
.scheduler = &scheduler, .scheduler = &scheduler,
.channel = &channel, .ch = ch.tx,
}) catch |e| exitln("main thread panicked: {}", .{e}); }) catch |e| exitln("main thread panicked: {}", .{e});
} else { } else {
var tracker = FpsTracker.init(); var tracker = FpsTracker.init();
// emu should start paused if there's no ROM to run // emu should start paused if there's no ROM to run
if (paths.rom == null) if (paths.rom == null)
channel.emu.push(.Pause); ch.tx.send(.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, ch.rx }) catch |e| exitln("emu thread panicked: {}", .{e});
defer thread.join(); defer thread.join();
gui.run(.Standard, .{ gui.run(.Standard, .{
.cpu = &cpu, .cpu = &cpu,
.scheduler = &scheduler, .scheduler = &scheduler,
.channel = &channel, .ch = ch.tx,
.tracker = &tracker, .tracker = &tracker,
}) catch |e| exitln("main thread panicked: {}", .{e}); }) catch |e| exitln("main thread panicked: {}", .{e});
} }

View File

@ -11,7 +11,7 @@ const Apu = @import("core/apu.zig").Apu;
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi; const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
const Scheduler = @import("core/scheduler.zig").Scheduler; const Scheduler = @import("core/scheduler.zig").Scheduler;
const FpsTracker = @import("util.zig").FpsTracker; const FpsTracker = @import("util.zig").FpsTracker;
const TwoWayChannel = @import("zba-util").TwoWayChannel; const Channel = @import("zba-util").Channel(emu.Message, 0x100);
const KeyInput = @import("core/bus/io.zig").KeyInput; const KeyInput = @import("core/bus/io.zig").KeyInput;
const gba_width = @import("core/ppu.zig").width; const gba_width = @import("core/ppu.zig").width;
@ -94,7 +94,7 @@ pub const Gui = struct {
} }
const RunOptions = struct { const RunOptions = struct {
channel: *TwoWayChannel, ch: Channel.Sender,
tracker: ?*FpsTracker = null, tracker: ?*FpsTracker = null,
cpu: *Arm7tdmi, cpu: *Arm7tdmi,
scheduler: *Scheduler, scheduler: *Scheduler,
@ -105,7 +105,7 @@ pub const Gui = struct {
pub fn run(self: *Self, comptime mode: RunMode, opt: RunOptions) !void { 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 ch = opt.ch;
const objects = opengl_impl.createObjects(); const objects = opengl_impl.createObjects();
defer gl.deleteBuffers(3, @as(*const [3]GLuint, &.{ objects.vao, objects.vbo, objects.ebo })); defer gl.deleteBuffers(3, @as(*const [3]GLuint, &.{ objects.vao, objects.vbo, objects.ebo }));
@ -193,50 +193,50 @@ pub const Gui = struct {
switch (self.state.emulation) { switch (self.state.emulation) {
.Transition => |inner| switch (inner) { .Transition => |inner| switch (inner) {
.Active => { .Active => {
_ = channel.gui.pop(); ch.send(.Resume);
if (!config.config().host.mute) SDL.SDL_PauseAudioDevice(self.audio.device, 0);
channel.emu.push(.Resume);
self.state.emulation = .Active; self.state.emulation = .Active;
}, },
.Inactive => { .Inactive => {
// Assert that double pausing is impossible // Assert that double pausing is impossible
if (channel.gui.peek()) |value|
std.debug.assert(value != .Paused);
channel.emu.push(.Pause); SDL.SDL_PauseAudioDevice(self.audio.device, 1);
ch.send(.Pause);
self.state.emulation = .Inactive; self.state.emulation = .Inactive;
}, },
}, },
.Active => skip_draw: { .Active => {
const is_std = mode == .Standard; const is_std = mode == .Standard;
if (is_std) channel.emu.push(.Pause); if (is_std) ch.send(.Pause);
defer if (is_std) channel.emu.push(.Resume); defer if (is_std) ch.send(.Resume);
switch (mode) { // switch (mode) {
.Standard => blk: { // .Standard => blk: {
const limit = 15; // TODO: What should this be? // const limit = 15; // TODO: What should this be?
// TODO: learn more about std.atomic.spinLoopHint(); // // TODO: learn more about std.atomic.spinLoopHint();
for (0..limit) |_| { // for (0..limit) |_| {
const message = channel.gui.pop() orelse continue; // const message = channel.gui.pop() orelse continue;
switch (message) { // switch (message) {
.Paused => break :blk, // .Paused => break :blk,
.Quit => unreachable, // .Quit => unreachable,
} // }
} // }
log.info("timed out waiting for emu thread to pause (limit: {})", .{limit}); // log.info("timed out waiting for emu thread to pause (limit: {})", .{limit});
break :skip_draw; // break :skip_draw;
}, // },
.Debug => blk: { // .Debug => blk: {
switch (channel.gui.pop() orelse break :blk) { // switch (channel.gui.pop() orelse break :blk) {
.Paused => unreachable, // only in standard mode // .Paused => unreachable, // only in standard mode
.Quit => break :emu_loop, // FIXME: gdb side of emu is seriously out-of-date... // .Quit => break :emu_loop, // FIXME: gdb side of emu is seriously out-of-date...
} // }
}, // },
} // }
// Add FPS count to the histogram // Add FPS count to the histogram
if (tracker) |t| self.state.fps_hist.push(t.value()) catch {}; if (tracker) |t| self.state.fps_hist.push(t.value()) catch {};
@ -251,6 +251,12 @@ pub const Gui = struct {
opengl_impl.drawScreenTexture(emu_tex, prog_id, objects, buf); opengl_impl.drawScreenTexture(emu_tex, prog_id, objects, buf);
} }
// FIXME: We only really care about locking the audio device (and therefore writing silence)
// since if nfd-zig is used the emu may be paused for way too long. Perhaps we should try and limit
// spurious calls to SDL_LockAudioDevice?
SDL.SDL_LockAudioDevice(self.audio.device);
defer SDL.SDL_UnlockAudioDevice(self.audio.device);
zgui_redraw = imgui.draw(&self.state, win_dim, out_tex, cpu); zgui_redraw = imgui.draw(&self.state, win_dim, out_tex, cpu);
}, },
.Inactive => zgui_redraw = imgui.draw(&self.state, win_dim, out_tex, cpu), .Inactive => zgui_redraw = imgui.draw(&self.state, win_dim, out_tex, cpu),
@ -269,7 +275,7 @@ pub const Gui = struct {
SDL.SDL_GL_SwapWindow(self.window); SDL.SDL_GL_SwapWindow(self.window);
} }
channel.emu.push(.Quit); ch.send(.Quit);
} }
fn glGetProcAddress(ctx: SDL.SDL_GLContext, proc: [:0]const u8) ?*anyopaque { fn glGetProcAddress(ctx: SDL.SDL_GLContext, proc: [:0]const u8) ?*anyopaque {
@ -300,11 +306,6 @@ const Audio = struct {
const device = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0); const device = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0);
if (device == 0) panic(); if (device == 0) panic();
if (!config.config().host.mute) {
SDL.SDL_PauseAudioDevice(device, 0); // Unpause Audio
log.info("Unpaused Device", .{});
}
return .{ .device = device }; return .{ .device = device };
} }