Compare commits
12 Commits
5841ecea46
...
paoda/upgr
| Author | SHA1 | Date | |
|---|---|---|---|
| de711a37c2 | |||
| 38fabc92c5 | |||
| f4c8bd9099 | |||
| 242060e35d | |||
| 497a62c16d | |||
| 08f56ef4e8 | |||
| 53bb41e151 | |||
| 5b2bd0b6b6 | |||
| 10308e2dd0 | |||
| af47c00749 | |||
| 32fb30e954 | |||
| c3d687fe6a |
0
.gitmodules
vendored
0
.gitmodules
vendored
30
README.md
30
README.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
// Later on we'll use this module as the root module of a test executable
|
// Later on we'll use this module as the root module of a test executable
|
||||||
// which requires us to specify a target.
|
// which requires us to specify a target.
|
||||||
.target = target,
|
.target = target,
|
||||||
.link_libc = true,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Here we define an executable. An executable needs to have a root module
|
// Here we define an executable. An executable needs to have a root module
|
||||||
@@ -80,6 +80,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") });
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
36
dl_sdl2.ps1
36
dl_sdl2.ps1
@@ -1,36 +0,0 @@
|
|||||||
$SDL2Version = "2.30.0"
|
|
||||||
$ArchiveFile = ".\SDL2-devel-mingw.zip"
|
|
||||||
$Json = @"
|
|
||||||
{
|
|
||||||
"x86_64-windows-gnu": {
|
|
||||||
"include": ".build_config\\SDL2\\include",
|
|
||||||
"libs": ".build_config\\SDL2\\lib",
|
|
||||||
"bin": ".build_config\\SDL2\\bin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"@
|
|
||||||
|
|
||||||
New-Item -Force -ItemType Directory -Path .\.build_config
|
|
||||||
Set-Location -Path .build_config -PassThru
|
|
||||||
|
|
||||||
if (!(Test-Path -PathType Leaf $ArchiveFile)) {
|
|
||||||
Invoke-WebRequest "https://github.com/libsdl-org/SDL/releases/download/release-$SDL2Version/SDL2-devel-$SDL2Version-mingw.zip" -OutFile $ArchiveFile
|
|
||||||
}
|
|
||||||
|
|
||||||
Expand-Archive $ArchiveFile
|
|
||||||
|
|
||||||
if (Test-Path -PathType Container .\SDL2) {
|
|
||||||
Remove-Item -Recurse .\SDL2
|
|
||||||
}
|
|
||||||
|
|
||||||
New-Item -Force -ItemType Directory -Path .\SDL2
|
|
||||||
Get-ChildItem -Path ".\SDL2-devel-mingw\SDL2-$SDL2Version\x86_64-w64-mingw32" | Move-Item -Destination .\SDL2
|
|
||||||
|
|
||||||
# #include <SDL.h>
|
|
||||||
Move-Item -Force -Path .\SDL2\include\SDL2\* -Destination .\SDL2\include
|
|
||||||
Remove-Item -Force .\SDL2\include\SDL2
|
|
||||||
|
|
||||||
New-Item -Force .\sdl.json -Value $Json
|
|
||||||
|
|
||||||
Remove-Item -Recurse .\SDL2-devel-mingw
|
|
||||||
Set-Location -Path .. -PassThru
|
|
||||||
@@ -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 = .{},
|
||||||
@@ -50,16 +49,30 @@ pub fn config() *const Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a config file and then loads it into the global state
|
/// Reads a config file and then loads it into the global state
|
||||||
pub fn load(allocator: Allocator, file_path: []const u8) !void {
|
pub fn load(allocator: Allocator, config_path: []const u8) !void {
|
||||||
var config_file = try std.fs.cwd().openFile(file_path, .{});
|
var dir = try std.fs.openDirAbsolute(config_path, .{});
|
||||||
defer config_file.close();
|
defer dir.close();
|
||||||
|
|
||||||
log.info("loaded from {s}", .{file_path});
|
const sub_path = "zba" ++ std.fs.path.sep_str ++ "config.toml";
|
||||||
|
|
||||||
const contents = try config_file.readToEndAlloc(allocator, try config_file.getEndPos());
|
var file = try dir.openFile(sub_path, .{});
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
{
|
||||||
|
const path = try std.fs.path.join(allocator, &.{ config_path, sub_path });
|
||||||
|
defer allocator.free(path);
|
||||||
|
|
||||||
|
log.info("loaded from {s}", .{path});
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,14 +247,14 @@ pub const Apu = struct {
|
|||||||
|
|
||||||
sampling_cycle: u2,
|
sampling_cycle: u2,
|
||||||
|
|
||||||
stream: *c.SDL_AudioStream,
|
// NB: This AudioStream is owned by platform.zig
|
||||||
|
stream: ?*c.SDL_AudioStream = null,
|
||||||
|
|
||||||
sched: *Scheduler,
|
sched: *Scheduler,
|
||||||
|
|
||||||
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 {
|
||||||
@@ -273,11 +273,9 @@ pub const Apu = struct {
|
|||||||
|
|
||||||
.sampling_cycle = 0b00,
|
.sampling_cycle = 0b00,
|
||||||
.sched = sched,
|
.sched = sched,
|
||||||
.stream = undefined, // FIXME: bad practice
|
|
||||||
|
|
||||||
.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());
|
||||||
@@ -398,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;
|
||||||
|
|
||||||
@@ -461,32 +454,23 @@ pub const Apu = struct {
|
|||||||
if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
|
if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
|
||||||
|
|
||||||
const ret = c.SDL_PutAudioStreamData(self.stream, &[2]i16{ @bitCast(ext_left ^ 0x8000), @bitCast(ext_right ^ 0x8000) }, 2 * @sizeOf(i16));
|
const ret = c.SDL_PutAudioStreamData(self.stream, &[2]i16{ @bitCast(ext_left ^ 0x8000), @bitCast(ext_right ^ 0x8000) }, 2 * @sizeOf(i16));
|
||||||
if (!ret) @panic("TODO: Failed to put i16s into SDL Audio Queue");
|
if (!ret) std.debug.panic("failed to append to sample queue. SDL Error: {s}", .{c.SDL_GetError()});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replaceSDLResampler(self: *Self) void {
|
fn replaceSDLResampler(self: *Self) void {
|
||||||
@branchHint(.cold);
|
const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
|
||||||
_ = self;
|
log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
|
||||||
|
|
||||||
@panic("TODO: Implement Multiple Sample Rates...");
|
self.sampling_cycle = self.bias.sampling_cycle.read();
|
||||||
|
|
||||||
// const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
|
const desired: c.SDL_AudioSpec = .{
|
||||||
// log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
|
.channels = 2,
|
||||||
|
.format = c.SDL_AUDIO_S16LE,
|
||||||
|
.freq = @intCast(Self.sampleRate(self.sampling_cycle)),
|
||||||
|
};
|
||||||
|
|
||||||
// // Sampling Cycle (Sample Rate) changed, Craete a new SDL Audio Resampler
|
const ret = c.SDL_SetAudioStreamFormat(self.stream, &desired, null);
|
||||||
// // FIXME: Replace SDL's Audio Resampler with either a custom or more reliable one
|
if (!ret) std.debug.panic("failed to change sample rate. SDL Error: {s}", .{c.SDL_GetError()});
|
||||||
// const old_stream = self.stream;
|
|
||||||
// defer c.SDL_DestroyAudioStream(old_stream);
|
|
||||||
|
|
||||||
// self.sampling_cycle = self.bias.sampling_cycle.read();
|
|
||||||
|
|
||||||
// var desired: c.SDL_AudioSpec = std.mem.zeroes(c.SDL_AudioSpec);
|
|
||||||
// desired.format = c.SDL_AUDIO_S16;
|
|
||||||
// desired.channels = 2;
|
|
||||||
// desired.freq = @intCast(sample_rate);
|
|
||||||
|
|
||||||
// const new_stream = c.SDL_OpenAudioDeviceStream(c.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired, null, null) orelse @panic("TODO: Failed to replace SDL Audio Stream");
|
|
||||||
// self.stream = new_stream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interval(self: *const Self) u64 {
|
fn interval(self: *const Self) u64 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
// comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16);
|
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) {
|
||||||
_ = stream;
|
const bytes = c.SDL_GetAudioStreamQueued(stream);
|
||||||
_ = is_buffer_full;
|
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 {
|
||||||
|
|||||||
147
src/imgui.zig
147
src/imgui.zig
@@ -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", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,63 +313,44 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
|
|||||||
_ = zgui.begin("Performance", .{ .popen = &state.win_stat.show_perf });
|
_ = zgui.begin("Performance", .{ .popen = &state.win_stat.show_perf });
|
||||||
defer zgui.end();
|
defer zgui.end();
|
||||||
|
|
||||||
// const tmp = blk: {
|
var values: [histogram_len]u32 = undefined;
|
||||||
// var buf: [histogram_len]u32 = undefined;
|
const len = state.fps_hist.copy(&values);
|
||||||
// const len = state.fps_hist.copy(&buf);
|
|
||||||
|
|
||||||
// break :blk .{ buf, len };
|
// FIXME(paoda): okay but why
|
||||||
// };
|
if (len == values.len) _ = state.fps_hist.pop();
|
||||||
// const values = tmp[0];
|
|
||||||
// const len = tmp[1];
|
|
||||||
|
|
||||||
// if (len == values.len) _ = state.fps_hist.pop();
|
const y_max: f64 = 2 * if (len != 0) @as(f64, @floatFromInt(std.mem.max(u32, &values))) else emu.frame_rate;
|
||||||
|
const x_max: f64 = @floatFromInt(values.len);
|
||||||
|
|
||||||
// const sorted = blk: {
|
if (zgui.plot.beginPlot("Emulation FPS", .{ .w = 0.0, .flags = .{ .no_title = true, .no_frame = true } })) {
|
||||||
// var buf: @TypeOf(values) = undefined;
|
defer zgui.plot.endPlot();
|
||||||
|
|
||||||
// @memcpy(buf[0..len], values[0..len]);
|
zgui.plot.setupLegend(.{ .north = true, .east = true }, .{});
|
||||||
// std.mem.sort(u32, buf[0..len], {}, std.sort.asc(u32));
|
zgui.plot.setupAxis(.x1, .{ .flags = .{ .no_grid_lines = true, .no_tick_labels = true, .no_tick_marks = true } });
|
||||||
|
zgui.plot.setupAxis(.y1, .{ .label = "FPS", .flags = .{ .no_grid_lines = true } });
|
||||||
|
zgui.plot.setupAxisLimits(.y1, .{ .min = 0.0, .max = y_max, .cond = .always });
|
||||||
|
zgui.plot.setupAxisLimits(.x1, .{ .min = 0.0, .max = x_max, .cond = .always });
|
||||||
|
zgui.plot.setupFinish();
|
||||||
|
|
||||||
// break :blk buf;
|
zgui.plot.plotLineValues("FPS", u32, .{ .v = values[0..len] });
|
||||||
// };
|
}
|
||||||
|
|
||||||
// const y_max: f64 = 2 * if (len != 0) @as(f64, @floatFromInt(sorted[len - 1])) else emu.frame_rate;
|
// the following metrics require a sorted array
|
||||||
// const x_max: f64 = @floatFromInt(values.len);
|
std.mem.sort(u32, values[0..len], {}, std.sort.asc(u32));
|
||||||
|
|
||||||
// const y_args = .{ .flags = .{ .no_grid_lines = true } };
|
const avg: u32 = avg: {
|
||||||
// const x_args = .{ .flags = .{ .no_grid_lines = true, .no_tick_labels = true, .no_tick_marks = true } };
|
var sum: u32 = 0;
|
||||||
|
for (values[0..len]) |value| sum += value;
|
||||||
|
|
||||||
// if (zgui.plot.beginPlot("Emulation FPS", .{ .w = 0.0, .flags = .{ .no_title = true, .no_frame = true } })) {
|
break :avg @intCast(sum / len);
|
||||||
// defer zgui.plot.endPlot();
|
};
|
||||||
|
|
||||||
// zgui.plot.setupLegend(.{ .north = true, .east = true }, .{});
|
const median = if (len == 0) 0 else values[len / 2];
|
||||||
// zgui.plot.setupAxis(.x1, x_args);
|
const low = if (len == 0) 0 else values[len / 100]; // 1% Low
|
||||||
// zgui.plot.setupAxis(.y1, y_args);
|
|
||||||
// zgui.plot.setupAxisLimits(.y1, .{ .min = 0.0, .max = y_max, .cond = .always });
|
|
||||||
// zgui.plot.setupAxisLimits(.x1, .{ .min = 0.0, .max = x_max, .cond = .always });
|
|
||||||
// zgui.plot.setupFinish();
|
|
||||||
|
|
||||||
// zgui.plot.plotLineValues("FPS", u32, .{ .v = values[0..len] });
|
zgui.text("Average: {:0>3} fps", .{avg});
|
||||||
// }
|
zgui.text(" Median: {:0>3} fps", .{median});
|
||||||
|
zgui.text(" 1% Low: {:0>3} fps", .{low});
|
||||||
// const stats: struct { u32, u32, u32 } = blk: {
|
|
||||||
// if (len == 0) break :blk .{ 0, 0, 0 };
|
|
||||||
|
|
||||||
// const average: u32 = average: {
|
|
||||||
// var sum: u32 = 0;
|
|
||||||
// for (sorted[0..len]) |value| sum += value;
|
|
||||||
|
|
||||||
// break :average @intCast(sum / len);
|
|
||||||
// };
|
|
||||||
// const median = sorted[len / 2];
|
|
||||||
// const low = sorted[len / 100]; // 1% Low
|
|
||||||
|
|
||||||
// break :blk .{ average, median, low };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// zgui.text("Average: {:0>3} fps", .{stats[0]});
|
|
||||||
// zgui.text(" Median: {:0>3} fps", .{stats[1]});
|
|
||||||
// zgui.text(" 1% Low: {:0>3} fps", .{stats[2]});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.win_stat.show_schedule) {
|
if (state.win_stat.show_schedule) {
|
||||||
|
|||||||
109
src/main.zig
109
src/main.zig
@@ -31,7 +31,7 @@ const params = clap.parseParamsComptime(
|
|||||||
\\
|
\\
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() !void {
|
||||||
// Main Allocator for ZBA
|
// Main Allocator for ZBA
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
defer std.debug.assert(gpa.deinit() == .ok);
|
defer std.debug.assert(gpa.deinit() == .ok);
|
||||||
@@ -40,10 +40,8 @@ pub fn main() void {
|
|||||||
|
|
||||||
// Determine the Data Directory (stores saves)
|
// Determine the Data Directory (stores saves)
|
||||||
const data_path = blk: {
|
const data_path = blk: {
|
||||||
const result = known_folders.getPath(allocator, .data);
|
const path = (try known_folders.getPath(allocator, .data)) orelse return error.unknown_data_folder;
|
||||||
const option = result catch |e| exitln("interrupted while determining the data folder: {}", .{e});
|
try makePath(path, "zba" ++ std.fs.path.sep_str ++ "save");
|
||||||
const path = option orelse exitln("no valid data folder found", .{});
|
|
||||||
ensureDataDirsExist(path) catch |e| exitln("failed to create folders under \"{s}\": {}", .{ path, e });
|
|
||||||
|
|
||||||
break :blk path;
|
break :blk path;
|
||||||
};
|
};
|
||||||
@@ -51,47 +49,41 @@ pub fn main() void {
|
|||||||
|
|
||||||
// Determine the Config Directory
|
// Determine the Config Directory
|
||||||
const config_path = blk: {
|
const config_path = blk: {
|
||||||
const result = known_folders.getPath(allocator, .roaming_configuration);
|
const path = (try known_folders.getPath(allocator, .roaming_configuration)) orelse return error.unknown_config_folder;
|
||||||
const option = result catch |e| exitln("interreupted while determining the config folder: {}", .{e});
|
try makePath(path, "zba");
|
||||||
const path = option orelse exitln("no valid config folder found", .{});
|
|
||||||
ensureConfigDirExists(path) catch |e| exitln("failed to create required folder \"{s}\": {}", .{ path, e });
|
|
||||||
|
|
||||||
break :blk path;
|
break :blk path;
|
||||||
};
|
};
|
||||||
defer allocator.free(config_path);
|
defer allocator.free(config_path);
|
||||||
|
|
||||||
// Parse CLI
|
// Parse CLI
|
||||||
|
const result = try clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .allocator = allocator });
|
||||||
const result = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .allocator = allocator }) catch |e| exitln("failed to parse cli: {}", .{e});
|
|
||||||
defer result.deinit();
|
defer result.deinit();
|
||||||
|
|
||||||
// TODO: Move config file to XDG Config directory?
|
// TODO: Move config file to XDG Config directory?
|
||||||
const cfg_file_path = configFilePath(allocator, config_path) catch |e| exitln("failed to ready config file for access: {}", .{e});
|
try makeConfigFilePath(config_path);
|
||||||
defer allocator.free(cfg_file_path);
|
try config.load(allocator, config_path);
|
||||||
|
|
||||||
config.load(allocator, cfg_file_path) catch |e| exitln("failed to load config file: {}", .{e});
|
var paths = try handleArguments(allocator, data_path, &result);
|
||||||
|
|
||||||
var paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e});
|
|
||||||
defer paths.deinit(allocator);
|
defer paths.deinit(allocator);
|
||||||
|
|
||||||
// if paths.bios is null, then we want to see if it's in the data directory
|
// if paths.bios is null, then we want to see if it's in the data directory
|
||||||
if (paths.bios == null) blk: {
|
if (paths.bios == null) blk: {
|
||||||
const bios_path = std.mem.join(allocator, "/", &.{ data_path, "zba", "gba_bios.bin" }) catch |e| exitln("failed to allocate backup bios dir path: {}", .{e});
|
const bios_path = try std.mem.join(allocator, "/", &.{ data_path, "zba", "gba_bios.bin" }); // FIXME: std.fs.path_sep or something
|
||||||
defer allocator.free(bios_path);
|
defer allocator.free(bios_path);
|
||||||
|
|
||||||
_ = std.fs.cwd().statFile(bios_path) catch |e| switch (e) {
|
_ = std.fs.cwd().statFile(bios_path) catch |e| {
|
||||||
error.FileNotFound => { // ZBA will crash on attempt to read BIOS but that's fine
|
if (e != std.fs.Dir.StatFileError.FileNotFound) return e;
|
||||||
|
|
||||||
log.err("file located at {s} was not found", .{bios_path});
|
log.err("file located at {s} was not found", .{bios_path});
|
||||||
break :blk;
|
break :blk;
|
||||||
},
|
|
||||||
else => exitln("error when checking \"{s}\": {}", .{ bios_path, e }),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
paths.bios = allocator.dupe(u8, bios_path) catch |e| exitln("failed to duplicate path to bios: {}", .{e});
|
paths.bios = try allocator.dupe(u8, bios_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const log_file = switch (config.config().debug.cpu_trace) {
|
const log_file = switch (config.config().debug.cpu_trace) {
|
||||||
true => std.fs.cwd().createFile("zba.log", .{}) catch |e| exitln("failed to create trace log file: {}", .{e}),
|
true => try std.fs.cwd().createFile("zba.log", .{}),
|
||||||
false => null,
|
false => null,
|
||||||
};
|
};
|
||||||
defer if (log_file) |file| file.close();
|
defer if (log_file) |file| file.close();
|
||||||
@@ -107,7 +99,7 @@ pub fn main() void {
|
|||||||
|
|
||||||
var cpu = Arm7tdmi.init(ischeduler, ibus);
|
var cpu = Arm7tdmi.init(ischeduler, ibus);
|
||||||
|
|
||||||
bus.init(allocator, &scheduler, &cpu, paths) catch |e| exitln("failed to init zba bus: {}", .{e});
|
try bus.init(allocator, &scheduler, &cpu, paths);
|
||||||
defer bus.deinit();
|
defer bus.deinit();
|
||||||
|
|
||||||
if (config.config().guest.skip_bios or result.args.skip != 0 or paths.bios == null) {
|
if (config.config().guest.skip_bios or result.args.skip != 0 or paths.bios == null) {
|
||||||
@@ -117,10 +109,10 @@ pub fn main() void {
|
|||||||
const title_ptr = if (paths.rom != null) &bus.pak.title else null;
|
const title_ptr = if (paths.rom != null) &bus.pak.title else null;
|
||||||
|
|
||||||
// TODO: Just copy the title instead of grabbing a pointer to it
|
// TODO: Just copy the title instead of grabbing a pointer to it
|
||||||
var gui = Gui.init(allocator, &bus.apu, title_ptr) catch |e| exitln("failed to init gui: {}", .{e});
|
var gui = try Gui.init(allocator, &bus.apu, title_ptr);
|
||||||
defer gui.deinit();
|
defer gui.deinit();
|
||||||
|
|
||||||
var sync = Synchro.init(allocator) catch |e| exitln("failed to allocate sync types: {}", .{e});
|
var sync = try Synchro.init(allocator);
|
||||||
defer sync.deinit(allocator);
|
defer sync.deinit(allocator);
|
||||||
|
|
||||||
if (result.args.gdb != 0) {
|
if (result.args.gdb != 0) {
|
||||||
@@ -133,34 +125,22 @@ pub fn main() void {
|
|||||||
|
|
||||||
log.info("Ready to connect", .{});
|
log.info("Ready to connect", .{});
|
||||||
|
|
||||||
var server = Server.init(
|
var server = try Server.init(emulator, .{ .memory_map = EmuThing.map, .target = EmuThing.target });
|
||||||
emulator,
|
|
||||||
.{ .memory_map = EmuThing.map, .target = EmuThing.target },
|
|
||||||
) catch |e| exitln("failed to init gdb server: {}", .{e});
|
|
||||||
defer server.deinit(allocator);
|
defer server.deinit(allocator);
|
||||||
|
|
||||||
log.info("Starting GDB Server Thread", .{});
|
log.info("Starting GDB Server Thread", .{});
|
||||||
|
|
||||||
const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &sync.should_quit }) catch |e| exitln("gdb server thread crashed: {}", .{e});
|
const thread = try std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &sync.should_quit });
|
||||||
defer thread.join();
|
defer thread.join();
|
||||||
|
|
||||||
gui.run(.{
|
try gui.run(.{ .cpu = &cpu, .scheduler = &scheduler, .sync = &sync });
|
||||||
.cpu = &cpu,
|
|
||||||
.scheduler = &scheduler,
|
|
||||||
.sync = &sync,
|
|
||||||
}) catch |e| exitln("main thread panicked: {}", .{e});
|
|
||||||
} else {
|
} else {
|
||||||
var tracker = FpsTracker.init();
|
var tracker = FpsTracker.init();
|
||||||
|
|
||||||
const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &sync }) catch |e| exitln("emu thread panicked: {}", .{e});
|
const thread = try std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &sync });
|
||||||
defer thread.join();
|
defer thread.join();
|
||||||
|
|
||||||
gui.run(.{
|
try gui.run(.{ .cpu = &cpu, .scheduler = &scheduler, .tracker = &tracker, .sync = &sync });
|
||||||
.cpu = &cpu,
|
|
||||||
.scheduler = &scheduler,
|
|
||||||
.tracker = &tracker,
|
|
||||||
.sync = &sync,
|
|
||||||
}) catch |e| exitln("main thread panicked: {}", .{e});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,53 +164,36 @@ fn handleArguments(allocator: Allocator, data_path: []const u8, result: *const c
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configFilePath(allocator: Allocator, config_path: []const u8) ![]const u8 {
|
fn makeConfigFilePath(config_path: []const u8) !void {
|
||||||
const path = try std.fs.path.join(allocator, &[_][]const u8{ config_path, "zba", "config.toml" });
|
var dir = try std.fs.openDirAbsolute(config_path, .{});
|
||||||
errdefer allocator.free(path);
|
defer dir.close();
|
||||||
|
|
||||||
|
const sub_path = "zba" ++ std.fs.path.sep_str ++ "config.toml";
|
||||||
|
|
||||||
// We try to create the file exclusively, meaning that we err out if the file already exists.
|
// We try to create the file exclusively, meaning that we err out if the file already exists.
|
||||||
// All we care about is a file being there so we can just ignore that error in particular and
|
// All we care about is a file being there so we can just ignore that error in particular and
|
||||||
// continue down the happy pathj
|
// continue down the happy pathj
|
||||||
std.fs.accessAbsolute(path, .{}) catch |e| {
|
dir.access(sub_path, .{}) catch |e| {
|
||||||
if (e != error.FileNotFound) return e;
|
if (e != std.fs.Dir.AccessError.FileNotFound) return e;
|
||||||
|
|
||||||
const config_file = std.fs.createFileAbsolute(path, .{}) catch |err| exitln("failed to create \"{s}\": {}", .{ path, err });
|
const config_file = try dir.createFile(sub_path, .{});
|
||||||
defer config_file.close();
|
defer config_file.close();
|
||||||
|
|
||||||
try config_file.writeAll(@embedFile("example.toml"));
|
try config_file.writeAll(@embedFile("example.toml"));
|
||||||
};
|
};
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensureDataDirsExist(data_path: []const u8) !void {
|
|
||||||
var dir = try std.fs.openDirAbsolute(data_path, .{});
|
|
||||||
defer dir.close();
|
|
||||||
|
|
||||||
// Will recursively create directories
|
|
||||||
try dir.makePath("zba" ++ std.fs.path.sep_str ++ "save");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensureConfigDirExists(config_path: []const u8) !void {
|
|
||||||
var dir = try std.fs.openDirAbsolute(config_path, .{});
|
|
||||||
defer dir.close();
|
|
||||||
|
|
||||||
try dir.makePath("zba");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn romPath(allocator: Allocator, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !?[]const u8 {
|
fn romPath(allocator: Allocator, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !?[]const u8 {
|
||||||
return switch (result.positionals.len) {
|
return switch (result.positionals.len) {
|
||||||
0 => null,
|
0 => null,
|
||||||
1 => if (result.positionals[0]) |path| try allocator.dupe(u8, path) else null,
|
1 => if (result.positionals[0]) |path| try allocator.dupe(u8, path) else null,
|
||||||
else => exitln("ZBA received too many positional arguments.", .{}),
|
else => error.too_many_positional_arguments,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exitln(comptime format: []const u8, args: anytype) noreturn {
|
fn makePath(path: []const u8, sub_path: []const u8) !void {
|
||||||
var buf: [1024]u8 = undefined;
|
var dir = try std.fs.openDirAbsolute(path, .{});
|
||||||
var stderr = std.fs.File.stderr().writer(&buf).interface;
|
defer dir.close();
|
||||||
|
|
||||||
stderr.print(format, args) catch {}; // Just exit already...
|
try dir.makePath(sub_path);
|
||||||
stderr.writeByte('\n') catch {};
|
|
||||||
std.process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ pub const Gui = struct {
|
|||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, apu: *Apu, title_opt: ?*const [12]u8) !Self {
|
pub fn init(allocator: Allocator, apu: *Apu, title_opt: ?*const [12]u8) !Self {
|
||||||
|
errdefer |err| if (err == error.sdl_error) log.err("SDL Error: {s}", .{c.SDL_GetError()});
|
||||||
|
|
||||||
c.SDL_SetMainReady();
|
c.SDL_SetMainReady();
|
||||||
|
|
||||||
try errify(c.SDL_Init(c.SDL_INIT_VIDEO | c.SDL_INIT_AUDIO | c.SDL_INIT_EVENTS));
|
try errify(c.SDL_Init(c.SDL_INIT_VIDEO | c.SDL_INIT_AUDIO | c.SDL_INIT_EVENTS));
|
||||||
@@ -200,16 +202,16 @@ pub const Gui = struct {
|
|||||||
var zgui_redraw: bool = false;
|
var zgui_redraw: bool = false;
|
||||||
|
|
||||||
switch (self.state.emulation) {
|
switch (self.state.emulation) {
|
||||||
.Transition => |inner| switch (inner) {
|
.Transition => |target| switch (target) {
|
||||||
.Active => {
|
.Active => {
|
||||||
sync.paused.store(false, .monotonic);
|
sync.paused.store(false, .monotonic);
|
||||||
if (!config.config().host.mute) try errify(c.SDL_PauseAudioDevice(c.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK));
|
if (!config.config().host.mute) try errify(c.SDL_ResumeAudioStreamDevice(self.audio.stream));
|
||||||
|
|
||||||
self.state.emulation = .Active;
|
self.state.emulation = .Active;
|
||||||
},
|
},
|
||||||
.Inactive => {
|
.Inactive => {
|
||||||
// Assert that double pausing is impossible
|
// Assert that double pausing is impossible
|
||||||
try errify(c.SDL_ResumeAudioDevice(c.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK));
|
if (!config.config().host.mute) try errify(c.SDL_PauseAudioStreamDevice(self.audio.stream));
|
||||||
sync.paused.store(true, .monotonic);
|
sync.paused.store(true, .monotonic);
|
||||||
|
|
||||||
self.state.emulation = .Inactive;
|
self.state.emulation = .Inactive;
|
||||||
@@ -233,7 +235,7 @@ pub const Gui = struct {
|
|||||||
// spurious calls to SDL_LockAudioDevice?
|
// spurious calls to SDL_LockAudioDevice?
|
||||||
|
|
||||||
try errify(c.SDL_LockAudioStream(self.audio.stream));
|
try errify(c.SDL_LockAudioStream(self.audio.stream));
|
||||||
defer errify(c.SDL_UnlockAudioStream(self.audio.stream)) catch @panic("TODO: FIXME");
|
defer errify(c.SDL_UnlockAudioStream(self.audio.stream)) catch std.debug.panic("SDL Error: {s}", .{c.SDL_GetError()});
|
||||||
|
|
||||||
zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex[0]);
|
zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex[0]);
|
||||||
},
|
},
|
||||||
@@ -257,17 +259,17 @@ pub const Gui = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Audio = struct {
|
pub const Audio = struct {
|
||||||
const Self = @This();
|
const log = std.log.scoped(.platform_audio);
|
||||||
const log = std.log.scoped(.PlatformAudio);
|
|
||||||
|
|
||||||
stream: *c.SDL_AudioStream,
|
stream: *c.SDL_AudioStream,
|
||||||
|
|
||||||
fn init(apu: *Apu) !Self {
|
fn init(apu: *Apu) !@This() {
|
||||||
var desired: c.SDL_AudioSpec = std.mem.zeroes(c.SDL_AudioSpec);
|
const desired: c.SDL_AudioSpec = .{
|
||||||
desired.freq = sample_rate;
|
.freq = sample_rate,
|
||||||
desired.format = c.SDL_AUDIO_S16LE;
|
.format = c.SDL_AUDIO_S16LE,
|
||||||
desired.channels = 2;
|
.channels = 2,
|
||||||
|
};
|
||||||
|
|
||||||
log.info("Host Sample Rate: {}Hz, Host Format: SDL_AUDIO_S16LE", .{sample_rate});
|
log.info("Host Sample Rate: {}Hz, Host Format: SDL_AUDIO_S16LE", .{sample_rate});
|
||||||
|
|
||||||
@@ -278,7 +280,7 @@ const Audio = struct {
|
|||||||
return .{ .stream = stream };
|
return .{ .stream = stream };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Self) void {
|
fn deinit(self: *@This()) void {
|
||||||
c.SDL_DestroyAudioStream(self.stream);
|
c.SDL_DestroyAudioStream(self.stream);
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user