6 Commits

7 changed files with 132 additions and 106 deletions

View File

@@ -27,35 +27,9 @@ Finally it's worth noting that ZBA uses a TOML config file it'll store in your O
## Compiling ## Compiling
Most recently built on Zig [v0.11.0](https://github.com/ziglang/zig/tree/0.11.0) Most recently built on Zig [v0.15.1](https://github.com/ziglang/zig/tree/0.15.1)
### Dependencies Run `zig build -Doptimize=ReleaseSafe`. The executable will be under `zig-out/bin` and the shared libraries (if enabled) under `zig-out/lib`. If working with shared libraries on windows, be sure to add all artifacts to the same directory. On Unix, you'll want to make use of `LD_PRELOAD`.
Dependency | Source
--- | ---
known-folders | <https://github.com/ziglibs/known-folders>
nfd-zig | <https://github.com/fabioarnold/nfd-zig>
SDL.zig | <https://github.com/MasterQ32/SDL.zig>
tomlz | <https://github.com/mattyhall/tomlz>
zba-gdbstub | <https://github.com/paoda/zba-gdbstub>
zba-util | <https://git.musuka.dev/paoda/zba-util>
zgui | <https://github.com/michal-z/zig-gamedev/tree/main/libs/zgui>
zig-clap | <https://github.com/Hejsil/zig-clap>
zig-datetime | <https://github.com/frmdstryr/zig-datetime>
`bitfield.zig` | [https://github.com/FlorenceOS/Florence](https://github.com/FlorenceOS/Florence/blob/aaa5a9e568/lib/util/bitfields.zig)
`gl.zig` | <https://github.com/MasterQ32/zig-opengl>
Use `git submodule update --init` from the project root to pull the git relevant git submodules
Be sure to provide SDL2 using:
- Linux: Your distro's package manager
- macOS: ¯\\\_(ツ)_/¯ (try [this formula](https://formulae.brew.sh/formula/sdl2)?)
- Windows: [`vcpkg`](https://github.com/Microsoft/vcpkg) (install `sdl2:x64-windows`)
`SDL.zig` will provide a helpful compile error if the zig compiler is unable to find SDL2.
Once you've got all the dependencies, execute `zig build -Doptimize=ReleaseSafe`. The executable will be under `zig-out/bin` and the shared libraries (if enabled) under `zig-out/lib`. If working with shared libraries on windows, be sure to add all artifacts to the same directory. On Unix, you'll want to make use of `LD_PRELOAD`.
## Controls ## Controls

View File

@@ -58,11 +58,7 @@ pub fn build(b: *std.Build) void {
// //
// If neither case applies to you, feel free to delete the declaration you // If neither case applies to you, feel free to delete the declaration you
// don't need and to put everything under a single module. // don't need and to put everything under a single module.
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{ .name = "zba", .root_module = exe_mod });
.name = "zba",
.root_module = exe_mod,
.use_llvm = true,
});
const zgui = b.dependency("zgui", .{ .shared = false, .with_implot = true, .backend = .sdl3_opengl3 }); const zgui = b.dependency("zgui", .{ .shared = false, .with_implot = true, .backend = .sdl3_opengl3 });
const sdl = b.dependency("sdl", .{ .target = target, .optimize = optimize, .preferred_linkage = .static }); const sdl = b.dependency("sdl", .{ .target = target, .optimize = optimize, .preferred_linkage = .static });
@@ -71,6 +67,8 @@ pub fn build(b: *std.Build) void {
const sdl_lib = sdl.artifact("SDL3"); const sdl_lib = sdl.artifact("SDL3");
const zgui_lib = zgui.artifact("imgui"); const zgui_lib = zgui.artifact("imgui");
zgui_lib.linkLibrary(sdl_lib);
exe_mod.linkLibrary(sdl_lib); exe_mod.linkLibrary(sdl_lib);
exe_mod.linkLibrary(zgui_lib); exe_mod.linkLibrary(zgui_lib);
@@ -84,6 +82,7 @@ pub fn build(b: *std.Build) void {
exe_mod.addImport("nfd", b.dependency("nfdzig", .{}).module("nfd")); exe_mod.addImport("nfd", b.dependency("nfdzig", .{}).module("nfd"));
exe_mod.addImport("zgui", zgui.module("root")); exe_mod.addImport("zgui", zgui.module("root"));
exe_mod.addImport("bitjuggle", b.dependency("bitjuggle", .{}).module("bitjuggle")); exe_mod.addImport("bitjuggle", b.dependency("bitjuggle", .{}).module("bitjuggle"));
exe_mod.addImport("toml", b.dependency("toml", .{}).module("toml"));
exe_mod.addAnonymousImport("example.toml", .{ .root_source_file = b.path("example.toml") }); exe_mod.addAnonymousImport("example.toml", .{ .root_source_file = b.path("example.toml") });

View File

@@ -11,14 +11,6 @@
.minimum_zig_version = "0.15.1", .minimum_zig_version = "0.15.1",
.fingerprint = 0xcb596c7fbdb20efc, .fingerprint = 0xcb596c7fbdb20efc,
.dependencies = .{ .dependencies = .{
// .tomlz = .{
// .url = "git+https://github.com/paoda/tomlz#9a16dd53927ef2012478b6494bafb4475e44f4c9",
// .hash = "12204f922cab84980e36b5c058d354ec0ee169bda401c8e0e80a463580349b476569",
// },
.zba_util = .{ .path = "../zba-util" },
.arm32 = .{ .path = "../arm32" },
.zba_gdbstub = .{ .path = "../zba-gdbstub" },
.known_folders = .{ .known_folders = .{
.url = "git+https://github.com/ziglibs/known-folders.git#ab5cf5feb936fa3b72c95d3ad0c0c67791937ba1", .url = "git+https://github.com/ziglibs/known-folders.git#ab5cf5feb936fa3b72c95d3ad0c0c67791937ba1",
.hash = "known_folders-0.0.0-Fy-PJtTTAADUOhGKM0sxzG4eMkNQxRvx9e5dfHVyaeA3", .hash = "known_folders-0.0.0-Fy-PJtTTAADUOhGKM0sxzG4eMkNQxRvx9e5dfHVyaeA3",
@@ -51,5 +43,21 @@
.url = "git+https://github.com/zig-gamedev/zgui#7fa8081c208885b85e3fdfc043cd9d9cb9559123", .url = "git+https://github.com/zig-gamedev/zgui#7fa8081c208885b85e3fdfc043cd9d9cb9559123",
.hash = "zgui-0.6.0-dev--L6sZL7tbQAPRLYrcQAVx0V49tPHAXNxclZ-v8IP4wLr", .hash = "zgui-0.6.0-dev--L6sZL7tbQAPRLYrcQAVx0V49tPHAXNxclZ-v8IP4wLr",
}, },
.toml = .{
.url = "git+https://github.com/sam701/zig-toml?ref=zig-0.15#475b03c630c802f8b6bd3e239d8fc2279b4fadb8",
.hash = "toml-0.3.0-bV14BfV7AQD8DkuQI7skP8ekQTaBYKTO0MY_35Cw_EXo",
},
.zba_util = .{
.url = "git+https://git.musuka.dev/paoda/zba-util#d75962ac9bdf9b6f0d37e37d5097ea9bb9c16779",
.hash = "zba_util-0.0.0-SK3QiakqAABPCct0WbLsuHFfmxwzH99FmEVznCBKzdmR",
},
.arm32 = .{
.url = "git+https://git.musuka.dev/paoda/arm32#c9360e1e239300d8c80bcbc3a7494f015a82c4e1",
.hash = "arm32-0.0.0--_LW1VwTAgAbqwOzVfjnkClkwBrB8IZ1LSz8N4EoA3U3",
},
.zba_gdbstub = .{
.url = "git+https://git.musuka.dev/paoda/zba-gdbstub#7b77964e145ac874424a190ecd390596601a40d1",
.hash = "zba_gdbstub-0.0.0-KDf2GNiPAAAR5QuteF1MsN3XGElNvHLlRJ7O4PNFvTmK",
},
}, },
} }

View File

@@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
// const tomlz = @import("tomlz"); const toml = @import("toml");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@@ -7,7 +7,6 @@ const log = std.log.scoped(.Config);
var state: Config = .{}; var state: Config = .{};
const Config = struct { const Config = struct {
// FIXME: tomlz expects these to be case sensitive
host: Host = .{}, host: Host = .{},
guest: Guest = .{}, guest: Guest = .{},
debug: Debug = .{}, debug: Debug = .{},
@@ -69,7 +68,11 @@ pub fn load(allocator: Allocator, config_path: []const u8) !void {
const contents = try file.readToEndAlloc(allocator, try file.getEndPos()); const contents = try file.readToEndAlloc(allocator, try file.getEndPos());
defer allocator.free(contents); defer allocator.free(contents);
// FIXME(2025-09-22): re-enable var parser = toml.Parser(Config).init(allocator);
// state = try tomlz.parser.decode(Config, allocator, contents); defer parser.deinit();
state = .{};
const parsed = try parser.parseString(contents);
defer parsed.deinit();
state = parsed.value; // FIXME: should copy the struct to state
} }

View File

@@ -255,8 +255,6 @@ pub const Apu = struct {
fs: FrameSequencer, fs: FrameSequencer,
capacitor: f32, capacitor: f32,
is_buffer_full: bool,
pub const Tick = enum { Length, Envelope, Sweep }; pub const Tick = enum { Length, Envelope, Sweep };
pub fn init(sched: *Scheduler) Self { pub fn init(sched: *Scheduler) Self {
@@ -278,7 +276,6 @@ pub const Apu = struct {
.capacitor = 0, .capacitor = 0,
.fs = FrameSequencer.init(), .fs = FrameSequencer.init(),
.is_buffer_full = false,
}; };
Self.initEvents(apu.sched, apu.interval()); Self.initEvents(apu.sched, apu.interval());
@@ -399,11 +396,6 @@ pub const Apu = struct {
pub fn sampleAudio(self: *Self, late: u64) void { pub fn sampleAudio(self: *Self, late: u64) void {
self.sched.push(.SampleAudio, self.interval() -| late); self.sched.push(.SampleAudio, self.interval() -| late);
// Whether the APU is busy or not is determined by the main loop in emu.zig
// This should only ever be true (because this side of the emu is single threaded)
// When audio sync is disaabled
if (self.is_buffer_full) return;
var left: i16 = 0; var left: i16 = 0;
var right: i16 = 0; var right: i16 = 0;

View File

@@ -95,7 +95,7 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
if (sync.paused.load(.monotonic)) continue; if (sync.paused.load(.monotonic)) continue;
runFrame(scheduler, cpu); runFrame(scheduler, cpu);
audioSync(audio_sync, bus_ptr.apu.stream, &bus_ptr.apu.is_buffer_full); if (audio_sync) audioSync(bus_ptr.apu.stream);
if (kind == .UnlimitedFPS) tracker.?.tick(); if (kind == .UnlimitedFPS) tracker.?.tick();
} }
@@ -117,10 +117,13 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
// the amount of time needed for audio to catch up rather than // the amount of time needed for audio to catch up rather than
// our expected wake-up time // our expected wake-up time
audioSync(audio_sync, bus_ptr.apu.stream, &bus_ptr.apu.is_buffer_full); if (audio_sync) {
if (!audio_sync) spinLoop(&timer, wake_time); audioSync(bus_ptr.apu.stream);
wake_time = new_wake_time; } else {
spinLoop(&timer, wake_time);
}
wake_time = new_wake_time;
if (kind == .LimitedFPS) tracker.?.tick(); if (kind == .LimitedFPS) tracker.?.tick();
} }
}, },
@@ -160,35 +163,25 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
} }
} }
fn audioSync(audio_sync: bool, stream: ?*c.SDL_AudioStream, is_buffer_full: *bool) void { fn audioSync(stream: ?*c.SDL_AudioStream) void {
if (stream == null) return; if (stream == null) {
@branchHint(.cold);
return log.err("audio sync failed. no SDL_AudioStream to sync to", .{});
}
const sample_size = 2 * @sizeOf(u16); const sample_size = 2 * @sizeOf(u16);
const max_buf_size: c_int = 0x400; const max_sample_delay: c_int = 0x800;
var is_behind = true;
_ = audio_sync; while (is_behind) {
_ = is_buffer_full; const bytes = c.SDL_GetAudioStreamQueued(stream);
if (bytes == -1) std.debug.panic("failed to query amount of queued bytes in audio stream: SDL Error {s}", .{c.SDL_GetError()});
_ = sample_size; is_behind = bytes > max_sample_delay * sample_size;
_ = max_buf_size; std.atomic.spinLoopHint();
}
// TODO(paoda): re-enable // FIXME(paoda, 2025-11-12): There was an is_buffer_full thing going on here that I'm sure was really important for some niche reason
// // Determine whether the APU is busy right at this moment
// var still_full: bool = SDL.SDL_AudioStreamAvailable(stream) > sample_size * if (is_buffer_full.*) max_buf_size >> 1 else max_buf_size;
// defer is_buffer_full.* = still_full; // Update APU Busy status right before exiting scope
// // If Busy is false, there's no need to sync here
// if (!still_full) return;
// // TODO: Refactor!!!!
// // while (SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1)
// // std.atomic.spinLoopHint();
// while (true) {
// still_full = SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1;
// if (!audio_sync or !still_full) break;
// }
} }
fn videoSync(timer: *Timer, wake_time: u64) u64 { fn videoSync(timer: *Timer, wake_time: u64) u64 {

View File

@@ -204,44 +204,84 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
_ = zgui.begin("Dependencies", .{ .popen = &state.win_stat.show_deps }); _ = zgui.begin("Dependencies", .{ .popen = &state.win_stat.show_deps });
defer zgui.end(); defer zgui.end();
zgui.bulletText("known-folders by ziglibs", .{}); zgui.textLinkOpenURL("known-folders", "https://github.com/ziglibs/known-folders");
zgui.bulletText("nfd-zig ported by Fabio Arnold", .{}); zgui.sameLine(.{});
zgui.text("by ziglibs", .{});
zgui.textLinkOpenURL("nfd-zig", "https://github.com/fabioarnold/nfd-zig");
zgui.sameLine(.{});
zgui.text("ported by Fabio Arnold", .{});
{ {
zgui.indent(.{}); zgui.indent(.{});
defer zgui.unindent(.{}); defer zgui.unindent(.{});
zgui.bulletText("nativefiledialog by Michael Labbe", .{}); zgui.textLinkOpenURL("nativefiledialog", "https://github.com/mlabbe/nativefiledialog");
zgui.sameLine(.{});
zgui.text("by Michael Labbe", .{});
} }
zgui.bulletText("SDL ported by Carl Åstholm", .{}); zgui.textLinkOpenURL("SDL", "https://github.com/castholm/SDL");
zgui.sameLine(.{});
zgui.text("ported by Carl Åstholm", .{});
{ {
zgui.indent(.{}); zgui.indent(.{});
defer zgui.unindent(.{}); defer zgui.unindent(.{});
zgui.bulletText("SDL by Sam Lantinga", .{}); zgui.textLinkOpenURL("SDL", "by Sam Lantinga");
zgui.sameLine(.{});
zgui.text("by Sam Lantinga", .{});
} }
// zgui.bulletText("tomlz by Matthew Hall", .{}); zgui.textLinkOpenURL("zig-toml", "https://github.com/sam701/zig-toml");
zgui.bulletText("zba-gdbstub by Rekai Musuka", .{}); zgui.sameLine(.{});
zgui.bulletText("zba-util by Rekai Musuka", .{}); zgui.text("by Alexei Samokvalov", .{});
zgui.bulletText("zgui ported by Michal Ziulek et al.", .{});
zgui.textLinkOpenURL("zba-gdbstub", "https://git.musuka.dev/paoda/zba-gdbstub");
zgui.sameLine(.{});
zgui.text("by Rekai Musuka", .{});
zgui.textLinkOpenURL("zba-util", "https://git.musuka.dev/paoda/zba-util");
zgui.sameLine(.{});
zgui.text("by Rekai Musuka", .{});
zgui.textLinkOpenURL("arm32", "https://git.musuka.dev/paoda/arm32");
zgui.sameLine(.{});
zgui.text("by Rekai Musuka", .{});
zgui.textLinkOpenURL("zgui", "https://github.com/zig-gamedev/zgui");
zgui.sameLine(.{});
zgui.text("ported by Michal Ziulek et al.", .{});
{ {
zgui.indent(.{}); zgui.indent(.{});
defer zgui.unindent(.{}); defer zgui.unindent(.{});
zgui.bulletText("DearImGui by Omar Cornut", .{}); zgui.textLinkOpenURL("Dear ImGui", "https://github.com/ocornut/imgui");
zgui.sameLine(.{});
zgui.text("by Omar Cornut", .{});
} }
zgui.bulletText("zig-clap by Jimmi Holst Christensen", .{});
zgui.bulletText("zig-datetime by Jairus Martin", .{});
zgui.newLine(); zgui.textLinkOpenURL("zig-clap", "https://github.com/Hejsil/zig-clap");
zgui.bulletText("bitfield.zig by Hannes Bredberg et al.", .{}); zgui.sameLine(.{});
zgui.bulletText("zigglgen ported by Carl Åstholm", .{}); zgui.text("by Jimmi Holst Christensen", .{});
zgui.textLinkOpenURL("zig-datetime", "https://github.com/frmdstryr/zig-datetime");
zgui.sameLine(.{});
zgui.text("by Jairus Martin", .{});
zgui.textLinkOpenURL("zig-bitjuggle", "https://github.com/leecannon/zig-bitjuggle");
zgui.sameLine(.{});
zgui.text("by Lee Cannon + Hannes Bredberg et al.", .{});
zgui.textLinkOpenURL("zigglgen", "https://github.com/castholm/zigglgen");
zgui.sameLine(.{});
zgui.text("ported by Carl Åstholm", .{});
{ {
zgui.indent(.{}); zgui.indent(.{});
defer zgui.unindent(.{}); defer zgui.unindent(.{});
zgui.bulletText("OpenGL-Registry by The Khronos Group", .{}); zgui.textLinkOpenURL("OpenGL-Registry", "https://github.com/KhronosGroup/OpenGL-Registry");
zgui.sameLine(.{});
zgui.text("by The Khronos Group", .{});
} }
} }
@@ -318,14 +358,19 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
defer zgui.end(); defer zgui.end();
const scheduler = cpu.sched; const scheduler = cpu.sched;
_ = scheduler;
// zgui.text("tick: {X:0>16}", .{scheduler.now()}); const tick = scheduler.now();
zgui.text("tick: (TODO: FIX THIS)", .{});
// >> 24 becuase the ARM7TDMI runs at 2^24 Hz
const elapsed_str = std.mem.sliceTo(&printTime(tick >> 24), 0);
zgui.text("tick: 0x{X:0>16}", .{tick});
zgui.sameLine(.{});
zgui.text(" {s}s", .{elapsed_str});
zgui.separator(); zgui.separator();
const sched_ptr: *Scheduler = @ptrCast(@alignCast(cpu.sched.ptr)); const sched_ptr: *Scheduler = @ptrCast(@alignCast(scheduler.ptr));
const Event = std.meta.Child(@TypeOf(sched_ptr.queue.items)); const Event = @typeInfo(@TypeOf(sched_ptr.queue.items)).pointer.child;
var items: [20]Event = undefined; var items: [20]Event = undefined;
const len = @min(sched_ptr.queue.items.len, items.len); const len = @min(sched_ptr.queue.items.len, items.len);
@@ -334,9 +379,7 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
std.mem.sort(Event, items[0..len], {}, widgets.eventDesc(Event)); std.mem.sort(Event, items[0..len], {}, widgets.eventDesc(Event));
for (items[0..len]) |event| { for (items[0..len]) |event| {
// zgui.text("{X:0>16} | {?}", .{ event.tick, event.kind }); zgui.text("0x{X:0>16} | {t}", .{ event.tick, event.kind });
_ = event;
zgui.text("TODO: Fix This", .{});
} }
} }
@@ -495,3 +538,17 @@ fn handleTitle(title_opt: ?*const [12]u8) [12:0]u8 {
return title.* ++ [_:0]u8{}; return title.* ++ [_:0]u8{};
} }
fn printTime(seconds: u64) [0x20]u8 {
var str = [_]u8{0x0} ** 0x20;
var writer = std.Io.Writer.fixed(&str);
const hr = seconds / 3600;
const min = (seconds % 3600) / 60;
const sec = seconds % 60;
// longest string is "5124095576030431:00:15" which is 22 bytes
writer.print("{:0>2}:{:0>2}:{:0>2}", .{ hr, min, sec }) catch unreachable;
return str;
}