Compare commits

...

6 Commits

18 changed files with 229 additions and 121 deletions

View File

@ -77,7 +77,7 @@ arm7wrestler GBA Fixed | [destoer](https://github.com/destoer)
## 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

View File

@ -1,7 +1,15 @@
const std = @import("std");
const builtin = @import("builtin");
const Sdk = @import("lib/SDL.zig/Sdk.zig");
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
// 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

View File

@ -108,8 +108,14 @@ pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
const byte_addr = @truncate(u8, addr);
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
switch (T) {
u32 => switch (byte_addr) {
u32 => {
// 0x80 and 0x81 handled in setSoundCnt
if (byte_addr < 0x80 and !apu.cnt.apu_enable.read()) return;
switch (byte_addr) {
0x60 => apu.ch1.setSound1Cnt(value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)),
@ -131,8 +137,12 @@ pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
0xA0 => apu.chA.push(value), // FIFO_A
0xA4 => apu.chB.push(value), // FIFO_B
else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
}
},
u16 => switch (byte_addr) {
u16 => {
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
switch (byte_addr) {
0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L
0x62 => apu.ch1.setSound1CntH(value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, value),
@ -164,8 +174,12 @@ pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
0xA0, 0xA2 => log.err("Tried to write 0x{X:0>4}{} to FIFO_A", .{ value, T }),
0xA4, 0xA6 => log.err("Tried to write 0x{X:0>4}{} to FIFO_B", .{ value, T }),
else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
}
},
u8 => switch (byte_addr) {
u8 => {
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
switch (byte_addr) {
0x60 => apu.ch1.setSound1CntL(value),
0x61 => {},
0x62 => apu.ch1.setNr11(value),
@ -208,6 +222,7 @@ pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
0xA0...0xA3 => log.err("Tried to write 0x{X:0>2}{} to FIFO_A", .{ value, T }),
0xA4...0xA7 => log.err("Tried to write 0x{X:0>2}{} to FIFO_B", .{ value, T }),
else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
}
},
else => @compileError("APU: Unsupported write width"),
}
@ -275,15 +290,20 @@ pub const Apu = struct {
}
fn reset(self: *Self) void {
// All PSG Registers between 0x0400_0060..0x0400_0081 are zeroed
// 0x0400_0082 and 0x0400_0088 retain their values
self.ch1.reset();
self.ch2.reset();
self.ch3.reset();
self.ch4.reset();
// GBATEK says 4000060h..4000081h I take this to mean inclusive
self.psg_cnt.raw = 0x0000;
}
/// SOUNDCNT
fn setSoundCnt(self: *Self, value: u32) void {
self.setSoundCntL(@truncate(u16, value));
if (self.cnt.apu_enable.read()) self.setSoundCntL(@truncate(u16, value));
self.setSoundCntH(@truncate(u16, value >> 16));
}
@ -322,11 +342,14 @@ pub const Apu = struct {
self.fs.step = 0; // Reset Frame Sequencer
// Reset Square Wave Offsets
self.ch1.square.pos = 0;
self.ch2.square.pos = 0;
self.ch1.square.reset();
self.ch2.square.reset();
// Reset Wave Device Offsets
self.ch3.wave_dev.offset = 0;
// Reset Wave
self.ch3.wave_dev.reset();
// Rest Noise
self.ch4.lfsr.reset();
} else {
self.reset();
}
@ -395,8 +418,8 @@ pub const Apu = struct {
right += if (self.dma_cnt.chB_right.read()) chB_sample else 0;
// Add SOUNDBIAS
// FIXME: Is SOUNDBIAS 9-bit or 10-bit?
const bias = @as(i16, self.bias.level.read()) << 1;
// FIXME: SOUNDBIAS is 10-bit but The waveform is centered around 0 if I treat it as 11-bit
const bias = @as(i16, self.bias.level.read()) << 2;
left += bias;
right += bias;

View File

@ -49,10 +49,13 @@ pub fn init(sched: *Scheduler) Self {
}
pub fn reset(self: *Self) void {
self.len = 0;
self.envelope.raw = 0;
self.poly.raw = 0;
self.cnt.raw = 0;
self.len = 0; // NR41
self.envelope.raw = 0; // NR42
self.poly.raw = 0; // NR43
self.cnt.raw = 0; // NR44
self.len_dev.reset();
self.env_dev.reset();
self.sample = 0;
self.enabled = false;

View File

@ -43,9 +43,12 @@ pub fn init(sched: *Scheduler) Self {
}
pub fn reset(self: *Self) void {
self.duty.raw = 0;
self.envelope.raw = 0;
self.freq.raw = 0;
self.duty.raw = 0; // NR21
self.envelope.raw = 0; // NR22
self.freq.raw = 0; // NR32, NR24
self.len_dev.reset();
self.env_dev.reset();
self.sample = 0;
self.enabled = false;

View File

@ -50,12 +50,14 @@ pub fn init(sched: *Scheduler) Self {
}
pub fn reset(self: *Self) void {
self.sweep.raw = 0;
self.sweep_dev.calc_performed = false;
self.sweep.raw = 0; // NR10
self.duty.raw = 0; // NR11
self.envelope.raw = 0; // NR12
self.freq.raw = 0; // NR13, NR14
self.duty.raw = 0;
self.envelope.raw = 0;
self.freq.raw = 0;
self.len_dev.reset();
self.sweep_dev.reset();
self.env_dev.reset();
self.sample = 0;
self.enabled = false;

View File

@ -42,10 +42,13 @@ pub fn init(sched: *Scheduler) Self {
}
pub fn reset(self: *Self) void {
self.select.raw = 0;
self.length = 0;
self.vol.raw = 0;
self.freq.raw = 0;
self.select.raw = 0; // NR30
self.length = 0; // NR31
self.vol.raw = 0; // NR32
self.freq.raw = 0; // NR33, NR34
self.len_dev.reset();
self.wave_dev.reset();
self.sample = 0;
self.enabled = false;

View File

@ -11,6 +11,11 @@ pub fn create() Self {
return .{ .timer = 0, .vol = 0 };
}
pub fn reset(self: *Self) void {
self.timer = 0;
self.vol = 0;
}
pub fn tick(self: *Self, nrx2: io.Envelope) void {
if (nrx2.period.read() != 0) {
if (self.timer != 0) self.timer -= 1;

View File

@ -6,6 +6,10 @@ pub fn create() Self {
return .{ .timer = 0 };
}
pub fn reset(self: *Self) void {
self.timer = 0;
}
pub fn tick(self: *Self, enabled: bool, ch_enable: *bool) void {
if (enabled) {
if (self.timer == 0) return;

View File

@ -18,6 +18,13 @@ pub fn create() Self {
};
}
pub fn reset(self: *Self) void {
self.timer = 0;
self.enabled = false;
self.shadow = 0;
self.calc_performed = false;
}
pub fn tick(self: *Self, ch1: *ToneSweep) void {
if (self.timer != 0) self.timer -= 1;

View File

@ -19,6 +19,11 @@ pub fn create(sched: *Scheduler) Self {
};
}
pub fn reset(self: *Self) void {
self.shift = 0;
self.timer = 0;
}
pub fn sample(self: *const Self) i8 {
return if ((~self.shift & 1) == 1) 1 else -1;
}

View File

@ -20,6 +20,11 @@ pub fn init(sched: *Scheduler) Self {
};
}
pub fn reset(self: *Self) void {
self.timer = 0;
self.pos = 0;
}
/// Scheduler Event Handler for Square Synth Timer Expire
pub fn onSquareTimerExpire(self: *Self, comptime T: type, nrx34: io.Frequency, late: u64) void {
comptime std.debug.assert(T == ToneSweep or T == Tone);

View File

@ -38,6 +38,13 @@ pub fn init(sched: *Scheduler) Self {
};
}
pub fn reset(self: *Self) void {
self.timer = 0;
self.offset = 0;
// sample buffer isn't reset because it's outside of the range of what NR52{7}'s effects
}
/// Reload internal Wave Timer
pub fn reload(self: *Self, value: u11) void {
self.sched.removeScheduledEvent(.{ .ApuChannel = 2 });

View File

@ -5,7 +5,7 @@ const DmaControl = @import("io.zig").DmaControl;
const Bus = @import("../Bus.zig");
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 getHalf = util.getHalf;

View File

@ -5,7 +5,7 @@ const TimerControl = @import("io.zig").TimerControl;
const Scheduler = @import("../scheduler.zig").Scheduler;
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 getHalf = util.getHalf;

View File

@ -87,7 +87,7 @@ pub fn main() void {
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();
gui.run(&cpu, &scheduler) catch |e| exitln("failed to run gui thread: {}", .{e});

View File

@ -15,7 +15,7 @@ const gba_height = @import("core/ppu.zig").height;
pub const sample_rate = 1 << 15;
pub const sample_format = SDL.AUDIO_U16;
const default_title: []const u8 = "ZBA";
const default_title = "ZBA";
pub const Gui = struct {
const Self = @This();
@ -44,7 +44,7 @@ pub const Gui = struct {
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_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();
@ -53,7 +53,7 @@ pub const Gui = struct {
const win_scale = @intCast(c_int, config.config().host.win_scale);
const window = SDL.SDL_CreateWindow(
default_title.ptr,
default_title,
SDL.SDL_WINDOWPOS_CENTERED,
SDL.SDL_WINDOWPOS_CENTERED,
@as(c_int, width * win_scale),
@ -67,7 +67,7 @@ pub const Gui = struct {
gl.load(ctx, Self.glGetProcAddress) catch @panic("gl.load failed");
if (SDL.SDL_GL_SetSwapInterval(@boolToInt(config.config().host.vsync)) < 0) panic();
const program_id = compileShaders();
const program_id = try compileShaders();
return Self{
.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
const vert_shader = @embedFile("shader/pixelbuf.vert");
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.compileShader(vs);
if (!shader.didCompile(vs)) return error.VertexCompileError;
const fs = gl.createShader(gl.FRAGMENT_SHADER);
defer gl.deleteShader(fs);
gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0);
gl.compileShader(fs);
if (!shader.didCompile(fs)) return error.FragmentCompileError;
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
@ -104,7 +108,7 @@ pub const Gui = struct {
}
// 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 vbo_id: c_uint = undefined;
var ebo_id: c_uint = undefined;
@ -154,13 +158,20 @@ pub const Gui = struct {
var quit = std.atomic.Atomic(bool).init(false);
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];
_ = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer));
const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, scheduler, cpu, &tracker });
defer thread.join();
var title_buf: [0x100]u8 = [_]u8{0} ** 0x100;
const vao_id = Self.generateBuffers()[0];
_ = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer));
var title_buf: [0x100]u8 = undefined;
emu_loop: while (true) {
var event: SDL.SDL_Event = undefined;
@ -230,7 +241,7 @@ pub const Gui = struct {
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null);
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);
}
@ -296,6 +307,28 @@ const Audio = struct {
}
};
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)});
}
};
fn panic() noreturn {
const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error";
@panic(std.mem.sliceTo(str, 0));

View File

@ -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 {
const _io = @import("core/bus/io.zig");