Compare commits
2 Commits
9183e6850d
...
05b7a9014d
Author | SHA1 | Date |
---|---|---|
Rekai Nyangadzayi Musuka | 05b7a9014d | |
Rekai Nyangadzayi Musuka | 493d7aeede |
|
@ -19,8 +19,8 @@
|
|||
.hash = "1220f48518ce22882e102255ed3bcdb7aeeb4891f50b2cdd3bd74b5b2e24d3149ba2",
|
||||
},
|
||||
.@"zba-util" = .{
|
||||
.url = "https://git.musuka.dev/paoda/zba-util/archive/322c798e384a0d24cc84ffcfa2e4a3ca807798a0.tar.gz",
|
||||
.hash = "12209ce0e729460b997706e47a53a32f1842672cd120189e612f4871731780a30ed0",
|
||||
.url = "https://git.musuka.dev/paoda/zba-util/archive/14ea006f4ffae77a333de4993c89690ce94d4abc.tar.gz",
|
||||
.hash = "1220f0d7c5802ae0a297844f96b2226ccc3d4d895277278e4345c3660161debbe85d",
|
||||
},
|
||||
.tomlz = .{
|
||||
.url = "https://github.com/mattyhall/tomlz/archive/47067cd7c902485f7d6e928331fd171ed47f72da.tar.gz",
|
||||
|
|
|
@ -6,8 +6,7 @@ const Scheduler = @import("scheduler.zig").Scheduler;
|
|||
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||
const Bus = @import("Bus.zig");
|
||||
const Tracker = @import("../util.zig").FpsTracker;
|
||||
|
||||
pub const Message = enum { Pause, Resume, Quit };
|
||||
const Channel = @import("../util.zig").Queue;
|
||||
|
||||
const stepDmaTransfer = @import("cpu_util.zig").stepDmaTransfer;
|
||||
const isHalted = @import("cpu_util.zig").isHalted;
|
||||
|
@ -17,10 +16,27 @@ const Timer = std.time.Timer;
|
|||
pub const Synchro = struct {
|
||||
const AtomicBool = std.atomic.Atomic(bool);
|
||||
|
||||
// FIXME: This Enum ends up being really LARGE!!!
|
||||
pub const Message = union(enum) {
|
||||
rom_path: [std.fs.MAX_PATH_BYTES]u8,
|
||||
bios_path: [std.fs.MAX_PATH_BYTES]u8,
|
||||
restart: void,
|
||||
};
|
||||
|
||||
paused: AtomicBool = AtomicBool.init(true), // FIXME: can ui_busy and paused be the same?
|
||||
should_quit: AtomicBool = AtomicBool.init(false),
|
||||
|
||||
emu_access: std.Thread.Mutex = .{},
|
||||
ch: Channel(Message),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !@This() {
|
||||
const msg_buf = try allocator.alloc(Message, 1);
|
||||
return .{ .ch = Channel(Message).init(msg_buf) };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||
allocator.free(self.ch.inner.buf);
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/// 4 Cycles in 1 dot
|
||||
|
@ -75,9 +91,10 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
|
|||
log.info("Emulation w/out video sync", .{});
|
||||
|
||||
while (!sync.should_quit.load(.Monotonic)) {
|
||||
handleChannel(cpu, &sync.ch);
|
||||
if (sync.paused.load(.Monotonic)) continue;
|
||||
|
||||
runFrame(sync, scheduler, cpu);
|
||||
runFrame(scheduler, cpu);
|
||||
audioSync(audio_sync, bus_ptr.apu.stream, &bus_ptr.apu.is_buffer_full);
|
||||
|
||||
if (kind == .UnlimitedFPS) tracker.?.tick();
|
||||
|
@ -89,9 +106,10 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
|
|||
var wake_time: u64 = frame_period;
|
||||
|
||||
while (!sync.should_quit.load(.Monotonic)) {
|
||||
handleChannel(cpu, &sync.ch);
|
||||
if (sync.paused.load(.Monotonic)) continue;
|
||||
|
||||
runFrame(sync, scheduler, cpu);
|
||||
runFrame(scheduler, cpu);
|
||||
const new_wake_time = videoSync(&timer, wake_time);
|
||||
|
||||
// Spin to make up the difference of OS scheduler innacuracies
|
||||
|
@ -109,10 +127,23 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
|
|||
}
|
||||
}
|
||||
|
||||
pub fn runFrame(sync: *Synchro, sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
sync.emu_access.lock();
|
||||
defer sync.emu_access.unlock();
|
||||
inline fn handleChannel(cpu: *Arm7tdmi, channel: *Channel(Synchro.Message)) void {
|
||||
const message = channel.pop() orelse return;
|
||||
|
||||
switch (message) {
|
||||
.rom_path => |path_buf| {
|
||||
const path = std.mem.sliceTo(&path_buf, 0);
|
||||
replaceGamepak(cpu, path) catch |e| log.err("failed to replace GamePak: {}", .{e});
|
||||
},
|
||||
.bios_path => |path_buf| {
|
||||
const path = std.mem.sliceTo(&path_buf, 0);
|
||||
replaceBios(cpu, path) catch |e| log.err("failed to replace BIOS: {}", .{e});
|
||||
},
|
||||
.restart => reset(cpu),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
const frame_end = sched.tick + cycles_per_frame;
|
||||
|
||||
while (sched.tick < frame_end) {
|
||||
|
@ -249,21 +280,21 @@ pub const EmuThing = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub fn reset(cpu: *Arm7tdmi) void {
|
||||
fn reset(cpu: *Arm7tdmi) void {
|
||||
// @breakpoint();
|
||||
cpu.sched.reset(); // Yes this is order sensitive, see the PPU reset for why
|
||||
cpu.bus.reset();
|
||||
cpu.reset();
|
||||
}
|
||||
|
||||
pub fn replaceGamepak(cpu: *Arm7tdmi, file_path: []const u8) !void {
|
||||
fn replaceGamepak(cpu: *Arm7tdmi, file_path: []const u8) !void {
|
||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||
|
||||
try bus_ptr.replaceGamepak(file_path);
|
||||
reset(cpu);
|
||||
}
|
||||
|
||||
pub fn replaceBios(cpu: *Arm7tdmi, file_path: []const u8) !void {
|
||||
fn replaceBios(cpu: *Arm7tdmi, file_path: []const u8) !void {
|
||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||
|
||||
const allocator = bus_ptr.bios.allocator;
|
||||
|
|
|
@ -13,6 +13,7 @@ const Gui = @import("platform.zig").Gui;
|
|||
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||
const Scheduler = @import("core/scheduler.zig").Scheduler;
|
||||
const Bus = @import("core/Bus.zig");
|
||||
const Synchro = @import("core/emu.zig").Synchro;
|
||||
|
||||
const RingBuffer = @import("zba-util").RingBuffer;
|
||||
const Dimensions = @import("platform.zig").Dimensions;
|
||||
|
@ -70,11 +71,13 @@ pub const State = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi) bool {
|
||||
pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi, tex_id: GLuint) bool {
|
||||
const scn_scale = config.config().host.win_scale;
|
||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||
|
||||
zgui.backend.newFrame(@floatFromInt(win_dim.width), @floatFromInt(win_dim.height));
|
||||
zgui.backend.newFrame(@floatFromInt(dim.width), @floatFromInt(dim.height));
|
||||
|
||||
state.title = handleTitle(&bus_ptr.pak.title);
|
||||
|
||||
{
|
||||
_ = zgui.beginMainMenuBar();
|
||||
|
@ -87,42 +90,59 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
|
|||
state.should_quit = true;
|
||||
|
||||
if (zgui.menuItem("Insert ROM", .{})) blk: {
|
||||
const maybe_path = nfd.openFileDialog("gba", null) catch |e| {
|
||||
log.err("failed to open file dialog: {}", .{e});
|
||||
const file_path = tmp: {
|
||||
const path_opt = nfd.openFileDialog("gba", null) catch |e| {
|
||||
log.err("file dialog failed to open: {}", .{e});
|
||||
break :blk;
|
||||
};
|
||||
|
||||
const file_path = maybe_path orelse {
|
||||
break :tmp path_opt orelse {
|
||||
log.warn("did not receive a file path", .{});
|
||||
break :blk;
|
||||
};
|
||||
};
|
||||
defer nfd.freePath(file_path);
|
||||
|
||||
log.info("user chose: \"{s}\"", .{file_path});
|
||||
emu.replaceGamepak(cpu, file_path) catch |e| {
|
||||
log.err("failed to replace GamePak: {}", .{e});
|
||||
|
||||
const message = tmp: {
|
||||
var msg: Synchro.Message = .{ .rom_path = undefined };
|
||||
@memcpy(msg.rom_path[0..file_path.len], file_path);
|
||||
break :tmp msg;
|
||||
};
|
||||
|
||||
sync.ch.push(message) catch |e| {
|
||||
log.err("failed to send file path to emu thread: {}", .{e});
|
||||
break :blk;
|
||||
};
|
||||
|
||||
state.title = handleTitle(&bus_ptr.pak.title);
|
||||
state.emulation = .{ .Transition = .Active };
|
||||
}
|
||||
|
||||
if (zgui.menuItem("Load BIOS", .{})) blk: {
|
||||
const maybe_path = nfd.openFileDialog("bin", null) catch |e| {
|
||||
log.err("failed to open file dialog: {}", .{e});
|
||||
const file_path = tmp: {
|
||||
const path_opt = nfd.openFileDialog("bin", null) catch |e| {
|
||||
log.err("file dialog failed to open: {}", .{e});
|
||||
break :blk;
|
||||
};
|
||||
|
||||
const file_path = maybe_path orelse {
|
||||
break :tmp path_opt orelse {
|
||||
log.warn("did not receive a file path", .{});
|
||||
break :blk;
|
||||
};
|
||||
};
|
||||
defer nfd.freePath(file_path);
|
||||
|
||||
log.info("user chose: \"{s}\"", .{file_path});
|
||||
emu.replaceBios(cpu, file_path) catch |e| {
|
||||
log.err("failed to replace BIOS: {}", .{e});
|
||||
|
||||
const message = tmp: {
|
||||
var msg: Synchro.Message = .{ .bios_path = undefined };
|
||||
@memcpy(msg.bios_path[0..file_path.len], file_path);
|
||||
break :tmp msg;
|
||||
};
|
||||
|
||||
sync.ch.push(message) catch |e| {
|
||||
log.err("failed to send file path to emu thread: {}", .{e});
|
||||
break :blk;
|
||||
};
|
||||
}
|
||||
|
@ -149,7 +169,7 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
|
|||
}
|
||||
|
||||
if (zgui.menuItem("Restart", .{}))
|
||||
emu.reset(cpu);
|
||||
sync.ch.push(.restart) catch |e| log.err("failed to send restart req to emu thread: {}", .{e});
|
||||
}
|
||||
|
||||
if (zgui.beginMenu("Stats", true)) {
|
||||
|
@ -324,9 +344,9 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
|
|||
const Event = std.meta.Child(@TypeOf(sched_ptr.queue.items));
|
||||
|
||||
var items: [20]Event = undefined;
|
||||
const len = sched_ptr.queue.len;
|
||||
const len = @min(sched_ptr.queue.len, items.len);
|
||||
|
||||
@memcpy(&items, sched_ptr.queue.items);
|
||||
@memcpy(items[0..len], sched_ptr.queue.items[0..len]);
|
||||
std.mem.sort(Event, items[0..len], {}, widgets.eventDesc(Event));
|
||||
|
||||
for (items[0..len]) |event| {
|
||||
|
@ -345,9 +365,9 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
|
|||
widgets.paletteGrid(.Object, cpu);
|
||||
}
|
||||
|
||||
{
|
||||
zgui.showDemoWindow(null);
|
||||
}
|
||||
// {
|
||||
// zgui.showDemoWindow(null);
|
||||
// }
|
||||
|
||||
return true; // request redraw
|
||||
}
|
||||
|
|
|
@ -119,7 +119,8 @@ pub fn main() void {
|
|||
var gui = Gui.init(allocator, &bus.apu, title_ptr) catch |e| exitln("failed to init gui: {}", .{e});
|
||||
defer gui.deinit();
|
||||
|
||||
var sync: Synchro = .{};
|
||||
var sync = Synchro.init(allocator) catch |e| exitln("failed to allocate sync types: {}", .{e});
|
||||
defer sync.deinit(allocator);
|
||||
|
||||
if (result.args.gdb != 0) {
|
||||
const Server = @import("gdbstub").Server;
|
||||
|
|
|
@ -207,9 +207,6 @@ pub const Gui = struct {
|
|||
},
|
||||
},
|
||||
.Active => {
|
||||
sync.emu_access.lock();
|
||||
defer sync.emu_access.unlock();
|
||||
|
||||
// Add FPS count to the histogram
|
||||
if (tracker) |t| self.state.fps_hist.push(t.value()) catch {};
|
||||
|
||||
|
@ -228,9 +225,9 @@ pub const Gui = struct {
|
|||
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, sync, win_dim, cpu, out_tex);
|
||||
},
|
||||
.Inactive => zgui_redraw = imgui.draw(&self.state, win_dim, out_tex, cpu),
|
||||
.Inactive => zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex),
|
||||
}
|
||||
|
||||
if (zgui_redraw) {
|
||||
|
|
28
src/util.zig
28
src/util.zig
|
@ -295,3 +295,31 @@ pub const FrameBuffer = struct {
|
|||
return self.layers[if (dev == .Emulator) self.current else ~self.current];
|
||||
}
|
||||
};
|
||||
|
||||
const RingBuffer = @import("zba-util").RingBuffer;
|
||||
|
||||
// TODO: Lock Free Queue?
|
||||
pub fn Queue(comptime T: type) type {
|
||||
return struct {
|
||||
inner: RingBuffer(T),
|
||||
mtx: std.Thread.Mutex = .{},
|
||||
|
||||
pub fn init(buf: []T) @This() {
|
||||
return .{ .inner = RingBuffer(T).init(buf) };
|
||||
}
|
||||
|
||||
pub fn push(self: *@This(), value: T) !void {
|
||||
self.mtx.lock();
|
||||
defer self.mtx.unlock();
|
||||
|
||||
try self.inner.push(value);
|
||||
}
|
||||
|
||||
pub fn pop(self: *@This()) ?T {
|
||||
self.mtx.lock();
|
||||
defer self.mtx.unlock();
|
||||
|
||||
return self.inner.pop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue