Compare commits

...

10 Commits

11 changed files with 223 additions and 110 deletions

View File

@ -77,7 +77,7 @@ arm7wrestler GBA Fixed | [destoer](https://github.com/destoer)
## Compiling ## Compiling
Most recently built on Zig [v0.11.0-dev.144+892fb0fc8](https://github.com/ziglang/zig/tree/892fb0fc8) Most recently built on Zig [0.11.0-dev.368+1829b6eab](https://github.com/ziglang/zig/tree/1829b6eab)
### Dependencies ### Dependencies

View File

@ -1,7 +1,15 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const Sdk = @import("lib/SDL.zig/Sdk.zig"); const Sdk = @import("lib/SDL.zig/Sdk.zig");
pub fn build(b: *std.build.Builder) void { pub fn build(b: *std.build.Builder) void {
// Minimum Zig Version
const min_ver = std.SemanticVersion.parse("0.11.0-dev.323+30eb2a175") catch return; // https://github.com/ziglang/zig/commit/30eb2a175
if (builtin.zig_version.order(min_ver).compare(.lt)) {
std.log.err("{s}", .{b.fmt("Zig v{} does not meet the minimum version requirement. (Zig v{})", .{ builtin.zig_version, min_ver })});
std.os.exit(1);
}
// Standard target options allows the person running `zig build` to choose // Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which // what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options // means any target is allowed, and the default is native. Other options

View File

@ -15,12 +15,10 @@ 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 intToBytes = util.intToBytes;
const RingBuffer = util.RingBuffer;
const log = std.log.scoped(.APU); const log = std.log.scoped(.APU);
pub const host_rate = @import("../platform.zig").sample_rate;
pub const host_format = @import("../platform.zig").sample_format;
pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T { pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
const byte_addr = @truncate(u8, addr); const byte_addr = @truncate(u8, addr);
@ -246,17 +244,20 @@ pub const Apu = struct {
sampling_cycle: u2, sampling_cycle: u2,
stream: *SDL.SDL_AudioStream, sample_queue: RingBuffer(u16),
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 {
const NUM_CHANNELS: usize = 2;
const allocator = std.heap.c_allocator;
const sample_buf = allocator.alloc(u16, 0x800 * NUM_CHANNELS) catch @panic("failed to allocate sample buffer");
const apu: Self = .{ const apu: Self = .{
.ch1 = ToneSweep.init(sched), .ch1 = ToneSweep.init(sched),
.ch2 = Tone.init(sched), .ch2 = Tone.init(sched),
@ -271,12 +272,11 @@ pub const Apu = struct {
.bias = .{ .raw = 0x0200 }, .bias = .{ .raw = 0x0200 },
.sampling_cycle = 0b00, .sampling_cycle = 0b00,
.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, 1 << 15, host_format, 2, host_rate).?, .sample_queue = RingBuffer(u16).init(sample_buf),
.sched = sched, .sched = sched,
.capacitor = 0, .capacitor = 0,
.fs = FrameSequencer.init(), .fs = FrameSequencer.init(),
.is_buffer_full = false,
}; };
sched.push(.SampleAudio, apu.interval()); sched.push(.SampleAudio, apu.interval());
@ -370,11 +370,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;
@ -430,23 +425,7 @@ pub const Apu = struct {
const ext_left = (clamped_left << 5) | (clamped_left >> 6); const ext_left = (clamped_left << 5) | (clamped_left >> 6);
const ext_right = (clamped_right << 5) | (clamped_right >> 6); const ext_right = (clamped_right << 5) | (clamped_right >> 6);
if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler(); self.sample_queue.push(ext_left, ext_right) catch {};
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ ext_left, ext_right }, 2 * @sizeOf(u16));
}
fn replaceSDLResampler(self: *Self) void {
@setCold(true);
const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
// Sampling Cycle (Sample Rate) changed, Craete a new SDL Audio Resampler
// FIXME: Replace SDL's Audio Resampler with either a custom or more reliable one
const old_stream = self.stream;
defer SDL.SDL_FreeAudioStream(old_stream);
self.sampling_cycle = self.bias.sampling_cycle.read();
self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(c_int, sample_rate), host_format, 2, host_rate).?;
} }
fn interval(self: *const Self) u64 { fn interval(self: *const Self) u64 {

View File

@ -5,7 +5,7 @@ const DmaControl = @import("io.zig").DmaControl;
const Bus = @import("../Bus.zig"); const Bus = @import("../Bus.zig");
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
pub const DmaTuple = std.meta.Tuple(&[_]type{ DmaController(0), DmaController(1), DmaController(2), DmaController(3) }); pub const DmaTuple = struct { DmaController(0), DmaController(1), DmaController(2), DmaController(3) };
const log = std.log.scoped(.DmaTransfer); const log = std.log.scoped(.DmaTransfer);
const getHalf = util.getHalf; const getHalf = util.getHalf;

View File

@ -24,14 +24,14 @@ pub const Io = struct {
postflg: PostFlag, postflg: PostFlag,
waitcnt: WaitControl, waitcnt: WaitControl,
haltcnt: HaltControl, haltcnt: HaltControl,
keyinput: KeyInput, keyinput: AtomicKeyInput,
pub fn init() Self { pub fn init() Self {
return .{ return .{
.ime = false, .ime = false,
.ie = .{ .raw = 0x0000 }, .ie = .{ .raw = 0x0000 },
.irq = .{ .raw = 0x0000 }, .irq = .{ .raw = 0x0000 },
.keyinput = .{ .raw = 0x03FF }, .keyinput = AtomicKeyInput.init(.{ .raw = 0x03FF }),
.waitcnt = .{ .raw = 0x0000_0000 }, // Bit 15 == 0 for GBA .waitcnt = .{ .raw = 0x0000_0000 }, // Bit 15 == 0 for GBA
.postflg = .FirstBoot, .postflg = .FirstBoot,
.haltcnt = .Execute, .haltcnt = .Execute,
@ -92,7 +92,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) ?T {
0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT", .{T}), 0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT", .{T}),
// Keypad Input // Keypad Input
0x0400_0130 => bus.io.keyinput.raw, 0x0400_0130 => bus.io.keyinput.load(.Monotonic).raw,
// Serial Communication 2 // Serial Communication 2
0x0400_0134 => util.io.read.todo(log, "Read {} from RCNT", .{T}), 0x0400_0134 => util.io.read.todo(log, "Read {} from RCNT", .{T}),
@ -376,6 +376,31 @@ const KeyInput = extern union {
raw: u16, raw: u16,
}; };
const AtomicKeyInput = struct {
const Self = @This();
const Ordering = std.atomic.Ordering;
inner: KeyInput,
pub fn init(value: KeyInput) Self {
return .{ .inner = value };
}
pub inline fn load(self: *const Self, comptime ordering: Ordering) KeyInput {
return .{ .raw = switch (ordering) {
.AcqRel, .Release => @compileError("not supported for atomic loads"),
else => @atomicLoad(u16, &self.inner.raw, ordering),
} };
}
pub inline fn store(self: *Self, value: u16, comptime ordering: Ordering) void {
switch (ordering) {
.AcqRel, .Acquire => @compileError("not supported for atomic stores"),
else => @atomicStore(u16, &self.inner.raw, value, ordering),
}
}
};
// Read / Write // Read / Write
pub const BackgroundControl = extern union { pub const BackgroundControl = extern union {
priority: Bitfield(u16, 0, 2), priority: Bitfield(u16, 0, 2),

View File

@ -5,7 +5,7 @@ const TimerControl = @import("io.zig").TimerControl;
const Scheduler = @import("../scheduler.zig").Scheduler; const Scheduler = @import("../scheduler.zig").Scheduler;
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
pub const TimerTuple = std.meta.Tuple(&[_]type{ Timer(0), Timer(1), Timer(2), Timer(3) }); pub const TimerTuple = struct { Timer(0), Timer(1), Timer(2), Timer(3) };
const log = std.log.scoped(.Timer); const log = std.log.scoped(.Timer);
const getHalf = util.getHalf; const getHalf = util.getHalf;

View File

@ -5,6 +5,7 @@ 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 FpsTracker = @import("../util.zig").FpsTracker; const FpsTracker = @import("../util.zig").FpsTracker;
const RingBuffer = @import("../util.zig").RingBuffer;
const Timer = std.time.Timer; const Timer = std.time.Timer;
const Atomic = std.atomic.Atomic; const Atomic = std.atomic.Atomic;
@ -56,9 +57,9 @@ fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), schedule
.Unlimited, .UnlimitedFPS => { .Unlimited, .UnlimitedFPS => {
log.info("Emulation w/out video sync", .{}); log.info("Emulation w/out video sync", .{});
while (!quit.load(.SeqCst)) { while (!quit.load(.Monotonic)) {
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.sample_queue);
if (kind == .UnlimitedFPS) tracker.?.tick(); if (kind == .UnlimitedFPS) tracker.?.tick();
} }
@ -68,7 +69,7 @@ fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), schedule
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 (!quit.load(.SeqCst)) { while (!quit.load(.Monotonic)) {
runFrame(scheduler, cpu); runFrame(scheduler, cpu);
const new_wake_time = videoSync(&timer, wake_time); const new_wake_time = videoSync(&timer, wake_time);
@ -77,7 +78,7 @@ fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), schedule
// 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, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); audioSync(audio_sync, &cpu.bus.apu.sample_queue);
if (!audio_sync) spinLoop(&timer, wake_time); if (!audio_sync) spinLoop(&timer, wake_time);
wake_time = new_wake_time; wake_time = new_wake_time;
@ -104,22 +105,13 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
} }
} }
fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void { fn audioSync(audio_sync: bool, sample_queue: *RingBuffer(u16)) void {
comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16); comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16);
const sample_size = 2 * @sizeOf(u16); // const sample_size = 2 * @sizeOf(u16);
const max_buf_size: c_int = 0x400; // const max_buf_size: c_int = 0x400;
// Determine whether the APU is busy right at this moment _ = audio_sync;
var still_full: bool = SDL.SDL_AudioStreamAvailable(stream) > sample_size * if (is_buffer_full.*) max_buf_size >> 1 else max_buf_size; _ = sample_queue;
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;
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

@ -1464,7 +1464,7 @@ const FrameBuffer = struct {
layers: [2][]u8, layers: [2][]u8,
buf: []u8, buf: []u8,
current: u1, current: std.atomic.Atomic(u8),
allocator: Allocator, allocator: Allocator,
@ -1483,7 +1483,7 @@ const FrameBuffer = struct {
// Front and Back Framebuffers // Front and Back Framebuffers
.layers = [_][]u8{ buf[0..][0..framebuf_len], buf[framebuf_len..][0..framebuf_len] }, .layers = [_][]u8{ buf[0..][0..framebuf_len], buf[framebuf_len..][0..framebuf_len] },
.buf = buf, .buf = buf,
.current = 0, .current = std.atomic.Atomic(u8).init(0),
.allocator = allocator, .allocator = allocator,
}; };
@ -1495,10 +1495,12 @@ const FrameBuffer = struct {
} }
pub fn swap(self: *Self) void { pub fn swap(self: *Self) void {
self.current = ~self.current; _ = self.current.fetchXor(1, .Release); // fetchNot(.Release)
} }
pub fn get(self: *Self, comptime dev: Device) []u8 { pub fn get(self: *Self, comptime dev: Device) []u8 {
return self.layers[if (dev == .Emulator) self.current else ~self.current]; const current = @intCast(u1, self.current.load(.Acquire));
return self.layers[if (dev == .Emulator) current else ~current];
} }
}; };

View File

@ -87,7 +87,7 @@ pub fn main() void {
cpu.fastBoot(); cpu.fastBoot();
} }
var gui = Gui.init(&bus.pak.title, &bus.apu, width, height); var gui = Gui.init(&bus.pak.title, &bus.apu, width, height) catch |e| exitln("failed to init gui: {}", .{e});
defer gui.deinit(); defer gui.deinit();
gui.run(&cpu, &scheduler) catch |e| exitln("failed to run gui thread: {}", .{e}); gui.run(&cpu, &scheduler) catch |e| exitln("failed to run gui thread: {}", .{e});

View File

@ -12,10 +12,10 @@ const FpsTracker = @import("util.zig").FpsTracker;
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;
pub const sample_rate = 1 << 15; pub const sample_rate = 1 << 16;
pub const sample_format = SDL.AUDIO_U16; pub const sample_format = SDL.AUDIO_U16;
const default_title: []const u8 = "ZBA"; const default_title = "ZBA";
pub const Gui = struct { pub const Gui = struct {
const Self = @This(); const Self = @This();
@ -44,7 +44,7 @@ pub const Gui = struct {
program_id: gl.GLuint, program_id: gl.GLuint,
pub fn init(title: *const [12]u8, apu: *Apu, width: i32, height: i32) Self { pub fn init(title: *const [12]u8, apu: *Apu, width: i32, height: i32) !Self {
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic(); if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic();
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic(); if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic();
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic(); if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic();
@ -53,7 +53,7 @@ pub const Gui = struct {
const win_scale = @intCast(c_int, config.config().host.win_scale); const win_scale = @intCast(c_int, config.config().host.win_scale);
const window = SDL.SDL_CreateWindow( const window = SDL.SDL_CreateWindow(
default_title.ptr, default_title,
SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED,
SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED,
@as(c_int, width * win_scale), @as(c_int, width * win_scale),
@ -64,10 +64,10 @@ pub const Gui = struct {
const ctx = SDL.SDL_GL_CreateContext(window) orelse panic(); const ctx = SDL.SDL_GL_CreateContext(window) orelse panic();
if (SDL.SDL_GL_MakeCurrent(window, ctx) < 0) panic(); if (SDL.SDL_GL_MakeCurrent(window, ctx) < 0) panic();
gl.load(ctx, Self.glGetProcAddress) catch @panic("gl.load failed"); try gl.load(ctx, Self.glGetProcAddress);
if (SDL.SDL_GL_SetSwapInterval(@boolToInt(config.config().host.vsync)) < 0) panic(); if (SDL.SDL_GL_SetSwapInterval(@boolToInt(config.config().host.vsync)) < 0) panic();
const program_id = compileShaders(); const program_id = try compileShaders();
return Self{ return Self{
.window = window, .window = window,
@ -78,7 +78,7 @@ pub const Gui = struct {
}; };
} }
fn compileShaders() gl.GLuint { fn compileShaders() !gl.GLuint {
// TODO: Panic on Shader Compiler Failure + Error Message // TODO: Panic on Shader Compiler Failure + Error Message
const vert_shader = @embedFile("shader/pixelbuf.vert"); const vert_shader = @embedFile("shader/pixelbuf.vert");
const frag_shader = @embedFile("shader/pixelbuf.frag"); const frag_shader = @embedFile("shader/pixelbuf.frag");
@ -89,12 +89,16 @@ pub const Gui = struct {
gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0); gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0);
gl.compileShader(vs); gl.compileShader(vs);
if (!shader.didCompile(vs)) return error.VertexCompileError;
const fs = gl.createShader(gl.FRAGMENT_SHADER); const fs = gl.createShader(gl.FRAGMENT_SHADER);
defer gl.deleteShader(fs); defer gl.deleteShader(fs);
gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0); gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0);
gl.compileShader(fs); gl.compileShader(fs);
if (!shader.didCompile(fs)) return error.FragmentCompileError;
const program = gl.createProgram(); const program = gl.createProgram();
gl.attachShader(program, vs); gl.attachShader(program, vs);
gl.attachShader(program, fs); gl.attachShader(program, fs);
@ -104,7 +108,7 @@ pub const Gui = struct {
} }
// Returns the VAO ID since it's used in run() // Returns the VAO ID since it's used in run()
fn generateBuffers() [3]c_uint { fn generateBuffers() struct { c_uint, c_uint, c_uint } {
var vao_id: c_uint = undefined; var vao_id: c_uint = undefined;
var vbo_id: c_uint = undefined; var vbo_id: c_uint = undefined;
var ebo_id: c_uint = undefined; var ebo_id: c_uint = undefined;
@ -154,13 +158,21 @@ pub const Gui = struct {
var quit = std.atomic.Atomic(bool).init(false); var quit = std.atomic.Atomic(bool).init(false);
var tracker = FpsTracker.init(); var tracker = FpsTracker.init();
var buffer_ids = Self.generateBuffers();
defer {
gl.deleteBuffers(1, &buffer_ids[2]); // EBO
gl.deleteBuffers(1, &buffer_ids[1]); // VBO
gl.deleteVertexArrays(1, &buffer_ids[0]); // VAO
}
const vao_id = buffer_ids[0];
const tex_id = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer));
defer gl.deleteTextures(1, &tex_id);
const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, scheduler, cpu, &tracker }); const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, scheduler, cpu, &tracker });
defer thread.join(); defer thread.join();
var title_buf: [0x100]u8 = [_]u8{0} ** 0x100; var title_buf: [0x100]u8 = undefined;
const vao_id = Self.generateBuffers()[0];
_ = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer));
emu_loop: while (true) { emu_loop: while (true) {
var event: SDL.SDL_Event = undefined; var event: SDL.SDL_Event = undefined;
@ -168,54 +180,50 @@ pub const Gui = struct {
switch (event.type) { switch (event.type) {
SDL.SDL_QUIT => break :emu_loop, SDL.SDL_QUIT => break :emu_loop,
SDL.SDL_KEYDOWN => { SDL.SDL_KEYDOWN => {
const io = &cpu.bus.io;
const key_code = event.key.keysym.sym; const key_code = event.key.keysym.sym;
var keyinput = cpu.bus.io.keyinput.load(.Monotonic);
switch (key_code) { switch (key_code) {
SDL.SDLK_UP => io.keyinput.up.unset(), SDL.SDLK_UP => keyinput.up.unset(),
SDL.SDLK_DOWN => io.keyinput.down.unset(), SDL.SDLK_DOWN => keyinput.down.unset(),
SDL.SDLK_LEFT => io.keyinput.left.unset(), SDL.SDLK_LEFT => keyinput.left.unset(),
SDL.SDLK_RIGHT => io.keyinput.right.unset(), SDL.SDLK_RIGHT => keyinput.right.unset(),
SDL.SDLK_x => io.keyinput.a.unset(), SDL.SDLK_x => keyinput.a.unset(),
SDL.SDLK_z => io.keyinput.b.unset(), SDL.SDLK_z => keyinput.b.unset(),
SDL.SDLK_a => io.keyinput.shoulder_l.unset(), SDL.SDLK_a => keyinput.shoulder_l.unset(),
SDL.SDLK_s => io.keyinput.shoulder_r.unset(), SDL.SDLK_s => keyinput.shoulder_r.unset(),
SDL.SDLK_RETURN => io.keyinput.start.unset(), SDL.SDLK_RETURN => keyinput.start.unset(),
SDL.SDLK_RSHIFT => io.keyinput.select.unset(), SDL.SDLK_RSHIFT => keyinput.select.unset(),
else => {}, else => {},
} }
cpu.bus.io.keyinput.store(keyinput.raw, .Monotonic);
}, },
SDL.SDL_KEYUP => { SDL.SDL_KEYUP => {
const io = &cpu.bus.io;
const key_code = event.key.keysym.sym; const key_code = event.key.keysym.sym;
var keyinput = cpu.bus.io.keyinput.load(.Monotonic);
switch (key_code) { switch (key_code) {
SDL.SDLK_UP => io.keyinput.up.set(), SDL.SDLK_UP => keyinput.up.set(),
SDL.SDLK_DOWN => io.keyinput.down.set(), SDL.SDLK_DOWN => keyinput.down.set(),
SDL.SDLK_LEFT => io.keyinput.left.set(), SDL.SDLK_LEFT => keyinput.left.set(),
SDL.SDLK_RIGHT => io.keyinput.right.set(), SDL.SDLK_RIGHT => keyinput.right.set(),
SDL.SDLK_x => io.keyinput.a.set(), SDL.SDLK_x => keyinput.a.set(),
SDL.SDLK_z => io.keyinput.b.set(), SDL.SDLK_z => keyinput.b.set(),
SDL.SDLK_a => io.keyinput.shoulder_l.set(), SDL.SDLK_a => keyinput.shoulder_l.set(),
SDL.SDLK_s => io.keyinput.shoulder_r.set(), SDL.SDLK_s => keyinput.shoulder_r.set(),
SDL.SDLK_RETURN => io.keyinput.start.set(), SDL.SDLK_RETURN => keyinput.start.set(),
SDL.SDLK_RSHIFT => io.keyinput.select.set(), SDL.SDLK_RSHIFT => keyinput.select.set(),
SDL.SDLK_i => { SDL.SDLK_i => {
comptime std.debug.assert(sample_format == SDL.AUDIO_U16); comptime std.debug.assert(sample_format == SDL.AUDIO_U16);
log.err("Sample Count: {}", .{@intCast(u32, SDL.SDL_AudioStreamAvailable(cpu.bus.apu.stream)) / (2 * @sizeOf(u16))}); log.err("Sample Count: {}", .{cpu.bus.apu.sample_queue.len() / 2});
},
SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }),
SDL.SDLK_k => {
// Dump IWRAM to file
log.info("PC: 0x{X:0>8}", .{cpu.r[15]});
log.info("LR: 0x{X:0>8}", .{cpu.r[14]});
// const iwram_file = try std.fs.cwd().createFile("iwram.bin", .{});
// defer iwram_file.close();
// try iwram_file.writeAll(cpu.bus.iwram.buf);
}, },
// SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }),
SDL.SDLK_k => {},
else => {}, else => {},
} }
cpu.bus.io.keyinput.store(keyinput.raw, .Monotonic);
}, },
else => {}, else => {},
} }
@ -230,16 +238,15 @@ pub const Gui = struct {
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null); gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null);
SDL.SDL_GL_SwapWindow(self.window); SDL.SDL_GL_SwapWindow(self.window);
const dyn_title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable; const dyn_title = std.fmt.bufPrintZ(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable;
SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr); SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr);
} }
quit.store(true, .SeqCst); // Terminate Emulator Thread quit.store(true, .Monotonic); // Terminate Emulator Thread
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.audio.deinit(); self.audio.deinit();
// TODO: Buffer deletions
gl.deleteProgram(self.program_id); gl.deleteProgram(self.program_id);
SDL.SDL_GL_DeleteContext(self.ctx); SDL.SDL_GL_DeleteContext(self.ctx);
SDL.SDL_DestroyWindow(self.window); SDL.SDL_DestroyWindow(self.window);
@ -292,7 +299,37 @@ const Audio = struct {
const T = *Apu; const T = *Apu;
const apu = @ptrCast(T, @alignCast(@alignOf(T), userdata)); const apu = @ptrCast(T, @alignCast(@alignOf(T), userdata));
_ = SDL.SDL_AudioStreamGet(apu.stream, stream, len); comptime std.debug.assert(sample_format == SDL.AUDIO_U16);
const sample_buf = @ptrCast([*]u16, @alignCast(@alignOf(u16), stream))[0 .. @intCast(u32, len) / @sizeOf(u16)];
var previous: u16 = 0x8000;
for (sample_buf) |*sample| {
if (apu.sample_queue.pop()) |value| previous = value;
sample.* = previous;
}
}
};
const shader = struct {
const Kind = enum { vertex, fragment };
const log = std.log.scoped(.Shader);
fn didCompile(id: gl.GLuint) bool {
var success: gl.GLint = undefined;
gl.getShaderiv(id, gl.COMPILE_STATUS, &success);
if (success == 0) err(id);
return success == 1;
}
fn err(id: gl.GLuint) void {
const buf_len = 512;
var error_msg: [buf_len]u8 = undefined;
gl.getShaderInfoLog(id, buf_len, 0, &error_msg);
log.err("{s}", .{std.mem.sliceTo(&error_msg, 0)});
} }
}; };

View File

@ -47,7 +47,7 @@ pub const FpsTracker = struct {
pub fn value(self: *Self) u32 { pub fn value(self: *Self) u32 {
if (self.timer.read() >= std.time.ns_per_s) { if (self.timer.read() >= std.time.ns_per_s) {
self.fps = self.count.swap(0, .SeqCst); self.fps = self.count.swap(0, .Monotonic);
self.timer.reset(); self.timer.reset();
} }
@ -181,7 +181,7 @@ pub const Logger = struct {
} }
}; };
const FmtArgTuple = std.meta.Tuple(&.{ u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32 }); const FmtArgTuple = struct { u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32 };
pub const audio = struct { pub const audio = struct {
const _io = @import("core/bus/io.zig"); const _io = @import("core/bus/io.zig");
@ -275,3 +275,73 @@ fn HalfInt(comptime T: type) type {
return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1); return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1);
} }
const Mutex = std.Thread.Mutex;
pub fn RingBuffer(comptime T: type) type {
return struct {
const Self = @This();
const Index = usize;
const max_capacity = (std.math.powi(Index, 2, @typeInfo(Index).Int.bits - 1) catch @compileError("uhhhhh")) - 1;
read: Index,
write: Index,
buf: []T,
mutex: Mutex,
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); // Capacity must be half the range of the index data types
return .{ .read = 0, .write = 0, .buf = buf, .mutex = .{} };
}
pub fn push(self: *Self, left: T, right: T) Error!void {
self.mutex.lock();
defer self.mutex.unlock();
try self._push(left);
self._push(right) catch |e| {
self.write -= 1; // undo the previous write;
return e;
};
}
pub fn pop(self: *Self) ?T {
self.mutex.lock();
defer self.mutex.unlock();
if (self.isEmpty()) return null;
defer self.read += 1;
return self.buf[self.mask(self.read)];
}
pub fn len(self: *const Self) Index {
return self.write - self.read;
}
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;
}
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);
}
};
}