Compare commits

..

No commits in common. "5adbc354d649d69cda2c803e65261378e1485ed6" and "72b702cb21c8cc22a7bc56faa64cb6df69086f81" have entirely different histories.

25 changed files with 169 additions and 157 deletions

3
.gitmodules vendored
View File

@ -22,6 +22,3 @@
[submodule "lib/nfd-zig"] [submodule "lib/nfd-zig"]
path = lib/nfd-zig path = lib/nfd-zig
url = https://github.com/fabioarnold/nfd-zig url = https://github.com/fabioarnold/nfd-zig
[submodule "lib/zba-util"]
path = lib/zba-util
url = https://git.musuka.dev/paoda/zba-util.git

View File

@ -33,7 +33,7 @@ pub fn build(b: *std.build.Builder) void {
exe.addAnonymousModule("datetime", .{ .source_file = .{ .path = "lib/zig-datetime/src/main.zig" } }); exe.addAnonymousModule("datetime", .{ .source_file = .{ .path = "lib/zig-datetime/src/main.zig" } });
// Bitfield type from FlorenceOS: https://github.com/FlorenceOS/ // Bitfield type from FlorenceOS: https://github.com/FlorenceOS/
exe.addAnonymousModule("bitfield", .{ .source_file = .{ .path = "lib/bitfield.zig" } }); exe.addAnonymousModule("bitfield", .{ .source_file = .{ .path = "lib/util/bitfield.zig" } });
// Argument Parsing Library // Argument Parsing Library
exe.addAnonymousModule("clap", .{ .source_file = .{ .path = "lib/zig-clap/clap.zig" } }); exe.addAnonymousModule("clap", .{ .source_file = .{ .path = "lib/zig-clap/clap.zig" } });
@ -44,9 +44,6 @@ pub fn build(b: *std.build.Builder) void {
// OpenGL 3.3 Bindings // OpenGL 3.3 Bindings
exe.addAnonymousModule("gl", .{ .source_file = .{ .path = "lib/gl.zig" } }); exe.addAnonymousModule("gl", .{ .source_file = .{ .path = "lib/gl.zig" } });
// ZBA utility code
exe.addAnonymousModule("zba-util", .{ .source_file = .{ .path = "lib/zba-util/src/lib.zig" } });
// gdbstub // gdbstub
gdbstub.link(exe); gdbstub.link(exe);
// NativeFileDialog(ue) Bindings // NativeFileDialog(ue) Bindings

@ -1 +0,0 @@
Subproject commit d5e66caf2180324d83ad9be30e887849f5ed74da

View File

@ -19,7 +19,7 @@ const log = std.log.scoped(.Bus);
const createDmaTuple = @import("bus/dma.zig").create; const createDmaTuple = @import("bus/dma.zig").create;
const createTimerTuple = @import("bus/timer.zig").create; const createTimerTuple = @import("bus/timer.zig").create;
const rotr = @import("zba-util").rotr; const rotr = @import("../util.zig").rotr;
const timings: [2][0x10]u8 = [_][0x10]u8{ const timings: [2][0x10]u8 = [_][0x10]u8{
// BIOS, Unused, EWRAM, IWRAM, I/0, PALRAM, VRAM, OAM, ROM0, ROM0, ROM1, ROM1, ROM2, ROM2, SRAM, Unused // BIOS, Unused, EWRAM, IWRAM, I/0, PALRAM, VRAM, OAM, ROM0, ROM0, ROM1, ROM1, ROM2, ROM2, SRAM, Unused
@ -104,7 +104,7 @@ pub fn deinit(self: *Self) void {
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.bios.reset(); self.bios.reset();
self.ppu.reset(); // TODO: deinit ppu
self.apu.reset(); self.apu.reset();
self.iwram.reset(); self.iwram.reset();
self.ewram.reset(); self.ewram.reset();
@ -128,21 +128,6 @@ pub fn reset(self: *Self) void {
self.io.reset(); self.io.reset();
} }
pub fn replaceGamepak(self: *Self, file_path: []const u8) !void {
// Note: `save_path` isn't owned by `Backup`
const save_path = self.pak.backup.save_path;
self.pak.deinit();
self.pak = try GamePak.init(self.allocator, self.cpu, file_path, save_path);
const read_ptr: *[table_len]?*const anyopaque = @constCast(self.read_table);
const write_ptrs: [2]*[table_len]?*anyopaque = .{ @constCast(self.write_tables[0]), @constCast(self.write_tables[1]) };
self.fillReadTable(read_ptr);
self.fillWriteTable(u32, write_ptrs[0]);
self.fillWriteTable(u8, write_ptrs[1]);
}
fn fillReadTable(self: *Self, table: *[table_len]?*const anyopaque) void { fn fillReadTable(self: *Self, table: *[table_len]?*const anyopaque) void {
const vramMirror = @import("ppu/Vram.zig").mirror; const vramMirror = @import("ppu/Vram.zig").mirror;

View File

@ -14,6 +14,7 @@ const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
const getHalf = util.getHalf; const getHalf = util.getHalf;
const setHalf = util.setHalf; const setHalf = util.setHalf;
const intToBytes = util.intToBytes;
const log = std.log.scoped(.APU); const log = std.log.scoped(.APU);
@ -278,20 +279,16 @@ pub const Apu = struct {
.is_buffer_full = false, .is_buffer_full = false,
}; };
Self.initEvents(apu.sched, apu.interval()); sched.push(.SampleAudio, apu.interval());
sched.push(.{ .ApuChannel = 0 }, @import("apu/signal/Square.zig").interval);
sched.push(.{ .ApuChannel = 1 }, @import("apu/signal/Square.zig").interval);
sched.push(.{ .ApuChannel = 2 }, @import("apu/signal/Wave.zig").interval);
sched.push(.{ .ApuChannel = 3 }, @import("apu/signal/Lfsr.zig").interval);
sched.push(.FrameSequencer, FrameSequencer.interval);
return apu; return apu;
} }
fn initEvents(scheduler: *Scheduler, apu_interval: u64) void {
scheduler.push(.SampleAudio, apu_interval);
scheduler.push(.{ .ApuChannel = 0 }, @import("apu/signal/Square.zig").interval);
scheduler.push(.{ .ApuChannel = 1 }, @import("apu/signal/Square.zig").interval);
scheduler.push(.{ .ApuChannel = 2 }, @import("apu/signal/Wave.zig").interval);
scheduler.push(.{ .ApuChannel = 3 }, @import("apu/signal/Lfsr.zig").interval);
scheduler.push(.FrameSequencer, FrameSequencer.interval);
}
/// Used when resetting the emulator /// Used when resetting the emulator
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
// FIXME: These reset functions are meant to emulate obscure APU behaviour. Write proper emu reset fns // FIXME: These reset functions are meant to emulate obscure APU behaviour. Write proper emu reset fns
@ -310,8 +307,6 @@ pub const Apu = struct {
self.sampling_cycle = 0; self.sampling_cycle = 0;
self.fs.reset(); self.fs.reset();
Self.initEvents(self.sched, self.interval());
} }
/// Emulates the reset behaviour of the APU /// Emulates the reset behaviour of the APU
@ -562,7 +557,7 @@ pub fn DmaSound(comptime kind: DmaSoundKind) type {
pub fn push(self: *Self, value: u32) void { pub fn push(self: *Self, value: u32) void {
if (!self.enabled) self.enable(); if (!self.enabled) self.enable();
self.fifo.write(std.mem.asBytes(&value)) catch |e| log.err("{} Error: {}", .{ kind, e }); self.fifo.write(&intToBytes(u32, value)) catch |e| log.err("{} Error: {}", .{ kind, e });
} }
fn enable(self: *Self) void { fn enable(self: *Self) void {

View File

@ -3,7 +3,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const log = std.log.scoped(.Bios); const log = std.log.scoped(.Bios);
const rotr = @import("zba-util").rotr; const rotr = @import("../../util.zig").rotr;
const forceAlign = @import("../Bus.zig").forceAlign; const forceAlign = @import("../Bus.zig").forceAlign;
/// Size of the BIOS in bytes /// Size of the BIOS in bytes

View File

@ -12,7 +12,7 @@ const getHalf = util.getHalf;
const setHalf = util.setHalf; const setHalf = util.setHalf;
const setQuart = util.setQuart; const setQuart = util.setQuart;
const rotr = @import("zba-util").rotr; const rotr = @import("../../util.zig").rotr;
pub fn create() DmaTuple { pub fn create() DmaTuple {
return .{ DmaController(0).init(), DmaController(1).init(), DmaController(2).init(), DmaController(3).init() }; return .{ DmaController(0).init(), DmaController(1).init(), DmaController(2).init(), DmaController(3).init() };

View File

@ -2,7 +2,7 @@ const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").arm.InstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const sext = @import("zba-util").sext; const sext = @import("../../../util.zig").sext;
pub fn branch(comptime L: bool) InstrFn { pub fn branch(comptime L: bool) InstrFn {
return struct { return struct {

View File

@ -2,8 +2,8 @@ const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").arm.InstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const sext = @import("zba-util").sext; const sext = @import("../../../util.zig").sext;
const rotr = @import("zba-util").rotr; const rotr = @import("../../../util.zig").rotr;
pub fn halfAndSignedDataTransfer(comptime P: bool, comptime U: bool, comptime I: bool, comptime W: bool, comptime L: bool) InstrFn { pub fn halfAndSignedDataTransfer(comptime P: bool, comptime U: bool, comptime I: bool, comptime W: bool, comptime L: bool) InstrFn {
return struct { return struct {

View File

@ -7,7 +7,7 @@ const PSR = @import("../../cpu.zig").PSR;
const log = std.log.scoped(.PsrTransfer); const log = std.log.scoped(.PsrTransfer);
const rotr = @import("zba-util").rotr; const rotr = @import("../../../util.zig").rotr;
pub fn psrTransfer(comptime I: bool, comptime R: bool, comptime kind: u2) InstrFn { pub fn psrTransfer(comptime I: bool, comptime R: bool, comptime kind: u2) InstrFn {
return struct { return struct {

View File

@ -2,7 +2,7 @@ const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").arm.InstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const rotr = @import("zba-util").rotr; const rotr = @import("../../../util.zig").rotr;
pub fn singleDataSwap(comptime B: bool) InstrFn { pub fn singleDataSwap(comptime B: bool) InstrFn {
return struct { return struct {

View File

@ -3,7 +3,7 @@ const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").arm.InstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const rotr = @import("zba-util").rotr; const rotr = @import("../../../util.zig").rotr;
pub fn singleDataTransfer(comptime I: bool, comptime P: bool, comptime U: bool, comptime B: bool, comptime W: bool, comptime L: bool) InstrFn { pub fn singleDataTransfer(comptime I: bool, comptime P: bool, comptime U: bool, comptime B: bool, comptime W: bool, comptime L: bool) InstrFn {
return struct { return struct {

View File

@ -1,7 +1,7 @@
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
const CPSR = @import("../cpu.zig").PSR; const CPSR = @import("../cpu.zig").PSR;
const rotr = @import("zba-util").rotr; const rotr = @import("../../util.zig").rotr;
pub fn exec(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { pub fn exec(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 {
var result: u32 = undefined; var result: u32 = undefined;

View File

@ -3,7 +3,7 @@ const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").thumb.InstrFn; const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
const checkCond = @import("../../cpu.zig").checkCond; const checkCond = @import("../../cpu.zig").checkCond;
const sext = @import("zba-util").sext; const sext = @import("../../../util.zig").sext;
pub fn fmt16(comptime cond: u4) InstrFn { pub fn fmt16(comptime cond: u4) InstrFn {
return struct { return struct {

View File

@ -2,8 +2,8 @@ const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").thumb.InstrFn; const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
const rotr = @import("zba-util").rotr; const rotr = @import("../../../util.zig").rotr;
const sext = @import("zba-util").sext; const sext = @import("../../../util.zig").sext;
pub fn fmt6(comptime rd: u3) InstrFn { pub fn fmt6(comptime rd: u3) InstrFn {
return struct { return struct {

View File

@ -5,9 +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 Timer = std.time.Timer; const Timer = std.time.Timer;
const Atomic = std.atomic.Atomic;
/// 4 Cycles in 1 dot /// 4 Cycles in 1 dot
const cycles_per_dot = 4; const cycles_per_dot = 4;
@ -35,40 +35,29 @@ const RunKind = enum {
LimitedFPS, LimitedFPS,
}; };
pub fn run(cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: *Tracker, channel: *TwoWayChannel) void { pub fn run(quit: *Atomic(bool), pause: *Atomic(bool), cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: *Tracker) 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, quit, pause, cpu, scheduler, tracker);
} else { } else {
inner(.UnlimitedFPS, audio_sync, cpu, scheduler, tracker, channel); inner(.UnlimitedFPS, audio_sync, quit, pause, cpu, scheduler, tracker);
} }
} }
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, quit: *Atomic(bool), pause: *Atomic(bool), cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: ?*Tracker) 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", .{});
} }
var paused: bool = false;
switch (kind) { switch (kind) {
.Unlimited, .UnlimitedFPS => { .Unlimited, .UnlimitedFPS => {
log.info("Emulation w/out video sync", .{}); log.info("Emulation w/out video sync", .{});
while (true) { while (!quit.load(.Monotonic)) {
if (channel.emu.pop()) |e| switch (e) { if (pause.load(.Monotonic)) continue;
.Quit => break,
.Resume => paused = false,
.Pause => {
paused = true;
channel.gui.push(.Paused);
},
};
if (paused) continue;
runFrame(scheduler, cpu); runFrame(scheduler, cpu);
audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
@ -81,17 +70,8 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
var timer = Timer.start() catch @panic("failed to initalize std.timer.Timer"); var timer = Timer.start() catch @panic("failed to initalize std.timer.Timer");
var wake_time: u64 = frame_period; var wake_time: u64 = frame_period;
while (true) { while (!quit.load(.Monotonic)) {
if (channel.emu.pop()) |e| switch (e) { if (pause.load(.Monotonic)) continue;
.Quit => break,
.Resume => paused = false,
.Pause => {
paused = true;
channel.gui.push(.Paused);
},
};
if (paused) continue;
runFrame(scheduler, cpu); runFrame(scheduler, cpu);
const new_wake_time = videoSync(&timer, wake_time); const new_wake_time = videoSync(&timer, wake_time);
@ -249,8 +229,3 @@ pub fn reset(cpu: *Arm7tdmi) void {
cpu.bus.reset(); cpu.bus.reset();
cpu.reset(); cpu.reset();
} }
pub fn replaceGamepak(cpu: *Arm7tdmi, file_path: []const u8) !void {
try cpu.bus.replaceGamepak(file_path);
reset(cpu);
}

View File

@ -290,26 +290,6 @@ pub const Ppu = struct {
}; };
} }
pub fn reset(self: *Self) void {
self.sched.push(.Draw, 240 * 4);
self.vram.reset();
self.palette.reset();
self.oam.reset();
self.framebuf.reset();
self.win = Window.init();
self.bg = [_]Background{Background.init()} ** 4;
self.aff_bg = [_]AffineBackground{AffineBackground.init()} ** 2;
self.bld = Blend.create();
self.dispcnt = .{ .raw = 0x0000 };
self.dispstat = .{ .raw = 0x0000 };
self.vcount = .{ .raw = 0x0000 };
self.scanline.reset();
std.mem.set(?Sprite, self.scanline_sprites, null);
}
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.allocator.destroy(self.scanline_sprites); self.allocator.destroy(self.scanline_sprites);
self.framebuf.deinit(); self.framebuf.deinit();

View File

@ -34,10 +34,6 @@ pub fn init(allocator: Allocator) !Self {
return Self{ .buf = buf, .allocator = allocator }; return Self{ .buf = buf, .allocator = allocator };
} }
pub fn reset(self: *Self) void {
std.mem.set(u8, self.buf, 0);
}
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.allocator.free(self.buf); self.allocator.free(self.buf);
self.* = undefined; self.* = undefined;

View File

@ -37,10 +37,6 @@ pub fn init(allocator: Allocator) !Self {
return Self{ .buf = buf, .allocator = allocator }; return Self{ .buf = buf, .allocator = allocator };
} }
pub fn reset(self: *Self) void {
std.mem.set(u8, self.buf, 0);
}
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.allocator.free(self.buf); self.allocator.free(self.buf);
self.* = undefined; self.* = undefined;

View File

@ -45,10 +45,6 @@ pub fn init(allocator: Allocator) !Self {
return Self{ .buf = buf, .allocator = allocator }; return Self{ .buf = buf, .allocator = allocator };
} }
pub fn reset(self: *Self) void {
std.mem.set(u8, self.buf, 0);
}
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.allocator.free(self.buf); self.allocator.free(self.buf);
self.* = undefined; self.* = undefined;

View File

@ -11,7 +11,7 @@ const emu = @import("core/emu.zig");
const Gui = @import("platform.zig").Gui; const Gui = @import("platform.zig").Gui;
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi; const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
const RingBuffer = @import("zba-util").RingBuffer; const RingBuffer = @import("util.zig").RingBuffer;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const GLuint = gl.GLuint; const GLuint = gl.GLuint;
@ -43,7 +43,7 @@ pub const State = struct {
pub fn deinit(self: *@This(), allocator: Allocator) void { pub fn deinit(self: *@This(), allocator: Allocator) void {
allocator.free(self.title); allocator.free(self.title);
allocator.free(self.fps_hist.buf); self.fps_hist.deinit(allocator);
self.* = undefined; self.* = undefined;
} }
@ -70,10 +70,7 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
defer nfd.freePath(file_path); defer nfd.freePath(file_path);
log.info("user chose: \"{s}\"", .{file_path}); log.info("user chose: \"{s}\"", .{file_path});
emu.replaceGamepak(cpu, file_path) catch |e| { // emu.loadRom(cpu, file_path);
log.err("failed to replace GamePak: {}", .{e});
break :blk;
};
} }
} }
} }

View File

@ -6,7 +6,6 @@ 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 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;
@ -14,6 +13,7 @@ const Scheduler = @import("core/scheduler.zig").Scheduler;
const FilePaths = @import("util.zig").FilePaths; const FilePaths = @import("util.zig").FilePaths;
const FpsTracker = @import("util.zig").FpsTracker; const FpsTracker = @import("util.zig").FpsTracker;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Atomic = std.atomic.Atomic;
const log = std.log.scoped(.Cli); const log = std.log.scoped(.Cli);
pub const log_level = if (builtin.mode != .Debug) .info else std.log.default_level; pub const log_level = if (builtin.mode != .Debug) .info else std.log.default_level;
@ -70,10 +70,9 @@ pub fn main() void {
const paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e}); const paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e});
defer if (paths.save) |path| allocator.free(path); defer if (paths.save) |path| allocator.free(path);
const log_file = switch (config.config().debug.cpu_trace) { const log_file = if (config.config().debug.cpu_trace) blk: {
true => std.fs.cwd().createFile("zba.log", .{}) catch |e| exitln("failed to create trace log file: {}", .{e}), break :blk std.fs.cwd().createFile("zba.log", .{}) catch |e| exitln("failed to create trace log file: {}", .{e});
false => null, } else null;
};
defer if (log_file) |file| file.close(); defer if (log_file) |file| file.close();
// TODO: Take Emulator Init Code out of main.zig // TODO: Take Emulator Init Code out of main.zig
@ -94,10 +93,7 @@ pub fn main() void {
var gui = Gui.init(allocator, bus.pak.title, &bus.apu) catch |e| exitln("failed to init gui: {}", .{e}); var gui = Gui.init(allocator, bus.pak.title, &bus.apu) catch |e| exitln("failed to init gui: {}", .{e});
defer gui.deinit(); defer gui.deinit();
var quit = std.atomic.Atomic(bool).init(false); var quit = Atomic(bool).init(false);
var items: [0x100]u8 = undefined;
var channel = TwoWayChannel.init(&items);
if (result.args.gdb) { if (result.args.gdb) {
const Server = @import("gdbstub").Server; const Server = @import("gdbstub").Server;
@ -120,19 +116,21 @@ pub fn main() void {
gui.run(.{ gui.run(.{
.cpu = &cpu, .cpu = &cpu,
.scheduler = &scheduler, .scheduler = &scheduler,
.channel = &channel, .quit = &quit,
}) catch |e| exitln("main thread panicked: {}", .{e}); }) catch |e| exitln("main thread panicked: {}", .{e});
} else { } else {
var tracker = FpsTracker.init(); var tracker = FpsTracker.init();
var pause = Atomic(bool).init(false);
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, .{ &quit, &pause, &cpu, &scheduler, &tracker }) catch |e| exitln("emu thread panicked: {}", .{e});
defer thread.join(); defer thread.join();
gui.run(.{ gui.run(.{
.cpu = &cpu, .cpu = &cpu,
.scheduler = &scheduler, .scheduler = &scheduler,
.channel = &channel,
.tracker = &tracker, .tracker = &tracker,
.quit = &quit,
.pause = &pause,
}) 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 RingBuffer = @import("util.zig").RingBuffer;
const gba_width = @import("core/ppu.zig").width; const gba_width = @import("core/ppu.zig").width;
const gba_height = @import("core/ppu.zig").height; const gba_height = @import("core/ppu.zig").height;
@ -243,7 +243,8 @@ pub const Gui = struct {
} }
const RunOptions = struct { const RunOptions = struct {
channel: *TwoWayChannel, quit: *std.atomic.Atomic(bool),
pause: ?*std.atomic.Atomic(bool) = null,
tracker: ?*FpsTracker = null, tracker: ?*FpsTracker = null,
cpu: *Arm7tdmi, cpu: *Arm7tdmi,
scheduler: *Scheduler, scheduler: *Scheduler,
@ -252,7 +253,8 @@ pub const Gui = struct {
pub fn run(self: *Self, opt: RunOptions) !void { pub fn run(self: *Self, opt: RunOptions) !void {
const cpu = opt.cpu; const cpu = opt.cpu;
const tracker = opt.tracker; const tracker = opt.tracker;
const channel = opt.channel; const quit = opt.quit;
const pause = opt.pause;
const obj_ids = Self.genBufferObjects(); const obj_ids = Self.genBufferObjects();
defer gl.deleteBuffers(3, @as(*const [3]c_uint, &obj_ids)); defer gl.deleteBuffers(3, @as(*const [3]c_uint, &obj_ids));
@ -267,10 +269,7 @@ pub const Gui = struct {
emu_loop: while (true) { emu_loop: while (true) {
// `quit` from RunOptions may be modified by the GDBSTUB thread, // `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 // so we want to recognize that it may change to `true` and exit the GUI thread
if (channel.gui.pop()) |event| switch (event) { if (quit.load(.Monotonic)) break :emu_loop;
.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
@ -326,18 +325,17 @@ pub const Gui = struct {
} }
} }
// If `Gui.run` has been passed with a `pause` atomic, we should
// pause the emulation thread while we access the data there
{ {
channel.emu.push(.Pause); // TODO: Is there a nicer way to express this?
defer channel.emu.push(.Resume); if (pause) |val| val.store(true, .Monotonic);
defer if (pause) |val| val.store(false, .Monotonic);
// 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}),
};
// 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 {};
}
// Draw GBA Screen to Texture // Draw GBA Screen to Texture
{ {
@ -363,7 +361,7 @@ pub const Gui = struct {
SDL.SDL_GL_SwapWindow(self.window); SDL.SDL_GL_SwapWindow(self.window);
} }
channel.emu.push(.Quit); quit.store(true, .Monotonic); // Signals to emu thread to exit
} }
fn glGetProcAddress(ctx: SDL.SDL_GLContext, proc: [:0]const u8) ?*anyopaque { fn glGetProcAddress(ctx: SDL.SDL_GLContext, proc: [:0]const u8) ?*anyopaque {

View File

@ -7,6 +7,27 @@ const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
// Sign-Extend value of type `T` to type `U`
pub fn sext(comptime T: type, comptime U: type, value: T) T {
// U must have less bits than T
comptime std.debug.assert(@typeInfo(U).Int.bits <= @typeInfo(T).Int.bits);
const iT = std.meta.Int(.signed, @typeInfo(T).Int.bits);
const ExtU = if (@typeInfo(U).Int.signedness == .unsigned) T else iT;
const shift_amt = @intCast(Log2Int(T), @typeInfo(T).Int.bits - @typeInfo(U).Int.bits);
return @bitCast(T, @bitCast(iT, @as(ExtU, @truncate(U, value)) << shift_amt) >> shift_amt);
}
/// See https://godbolt.org/z/W3en9Eche
pub inline fn rotr(comptime T: type, x: T, r: anytype) T {
if (@typeInfo(T).Int.signedness == .signed)
@compileError("cannot rotate signed integer");
const ar = @intCast(Log2Int(T), @mod(r, @typeInfo(T).Int.bits));
return x >> ar | x << (1 +% ~ar);
}
pub const FpsTracker = struct { pub const FpsTracker = struct {
const Self = @This(); const Self = @This();
@ -36,6 +57,17 @@ pub const FpsTracker = struct {
} }
}; };
pub fn intToBytes(comptime T: type, value: anytype) [@sizeOf(T)]u8 {
comptime std.debug.assert(@typeInfo(T) == .Int);
var result: [@sizeOf(T)]u8 = undefined;
var i: Log2Int(T) = 0;
while (i < result.len) : (i += 1) result[i] = @truncate(u8, value >> i * @bitSizeOf(u8));
return result;
}
/// Creates a copy of a title with all Filesystem-invalid characters replaced /// Creates a copy of a title with all Filesystem-invalid characters replaced
/// ///
/// e.g. POKEPIN R/S to POKEPIN R_S /// e.g. POKEPIN R/S to POKEPIN R_S
@ -251,7 +283,7 @@ pub const FrameBuffer = struct {
layers: [2][]u8, layers: [2][]u8,
buf: []u8, buf: []u8,
current: u1 = 0, current: u1,
allocator: Allocator, allocator: Allocator,
@ -266,16 +298,12 @@ pub const FrameBuffer = struct {
// Front and Back Framebuffers // Front and Back Framebuffers
.layers = [_][]u8{ buf[0..][0..len], buf[len..][0..len] }, .layers = [_][]u8{ buf[0..][0..len], buf[len..][0..len] },
.buf = buf, .buf = buf,
.current = 0,
.allocator = allocator, .allocator = allocator,
}; };
} }
pub fn reset(self: *Self) void {
std.mem.set(u8, self.buf, 0);
self.current = 0;
}
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.allocator.free(self.buf); self.allocator.free(self.buf);
self.* = undefined; self.* = undefined;
@ -289,3 +317,78 @@ pub const FrameBuffer = struct {
return self.layers[if (dev == .Emulator) self.current else ~self.current]; return self.layers[if (dev == .Emulator) self.current else ~self.current];
} }
}; };
pub fn RingBuffer(comptime T: type) type {
return struct {
const Self = @This();
const Index = usize;
const max_capacity = (@as(Index, 1) << @typeInfo(Index).Int.bits - 1) - 1; // half the range of index type
const log = std.log.scoped(.RingBuffer);
read: Index,
write: Index,
buf: []T,
const Error = error{buffer_full};
pub fn init(buf: []T) Self {
std.debug.assert(std.math.isPowerOfTwo(buf.len)); // capacity must be a power of two
std.debug.assert(buf.len <= max_capacity);
std.mem.set(T, buf, 0);
return .{ .read = 0, .write = 0, .buf = buf };
}
pub fn deinit(self: *Self, allocator: Allocator) void {
allocator.free(self.buf);
self.* = undefined;
}
pub fn push(self: *Self, value: T) Error!void {
if (self.isFull()) return error.buffer_full;
defer self.write += 1;
self.buf[self.mask(self.write)] = value;
}
pub fn pop(self: *Self) ?T {
if (self.isEmpty()) return null;
defer self.read += 1;
return self.buf[self.mask(self.read)];
}
/// Returns the number of entries read
pub fn copy(self: *const Self, cpy: []T) Index {
const count = std.math.min(self.len(), cpy.len);
var start: Index = self.read;
for (cpy, 0..) |*v, i| {
if (i >= count) break;
v.* = self.buf[self.mask(start)];
start += 1;
}
return count;
}
fn len(self: *const Self) Index {
return self.write - self.read;
}
fn isFull(self: *const Self) bool {
return self.len() == self.buf.len;
}
fn isEmpty(self: *const Self) bool {
return self.read == self.write;
}
fn mask(self: *const Self, idx: Index) Index {
return idx & (self.buf.len - 1);
}
};
}