Compare commits

..

14 Commits

Author SHA1 Message Date
Rekai Nyangadzayi Musuka 429b482c7c chore: reimplement alpha blending 2022-11-17 15:57:36 -04:00
Rekai Nyangadzayi Musuka 2c6a6a2ebb chore(ppu): use @ptrCast in drawTextMode 2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka c94913c1d0 chore(ppu): reimplement modes 3, 4, and 5 2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka c3f79b4aa9 style(ppu): move text mode drawing to unique fn 2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka 2128a2c14f fix(window): proper inRange impl for window
window wrap now works (it's pretty slow though?)
2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka 88c7104eb3 chore: improve readability of sprite drawing code a bit 2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka faaa7d8a92 style: remove unused imports 2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka a51ab6015d chore: dont allocate not-small ?Sprite array on stack
use memset like most other allocations in this emu
2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka 46a8d68347 chore: move FrameBuffer struct to util.zig 2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka 42cbe06953 chore: move OAM, PALRAM and VRAM structs to separate files 2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka 255ff37d20 fix: 8-bit writes to WIN PPU registers
Advance Wars depends on these registers similar to Mario Kart's 8-bit
writes to Affine Background registers:
2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka d74f3cee4f chore: refactor window 2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka 76c161953f chore: crude background window impl (no affine) 2022-11-13 08:23:33 -04:00
Rekai Nyangadzayi Musuka df56cf150a chore: rename function (misspelt until now somehow) 2022-11-13 08:23:33 -04:00
26 changed files with 358 additions and 493 deletions

View File

@ -28,45 +28,31 @@ Finally it's worth noting that ZBA uses a TOML config file it'll store in your O
## Tests
GBA Tests | [jsmolka](https://github.com/jsmolka/)
--- | ---
`arm.gba`, `thumb.gba` | PASS
`memory.gba`, `bios.gba` | PASS
`flash64.gba`, `flash128.gba` | PASS
`sram.gba` | PASS
`none.gba` | PASS
`hello.gba`, `shades.gba`, `stripes.gba` | PASS
`nes.gba` | PASS
GBARoms | [DenSinH](https://github.com/DenSinH/)
--- | ---
`eeprom-test`, `flash-test` | PASS
`midikey2freq` | PASS
`swi-tests-random` | FAIL
gba_tests | [destoer](https://github.com/destoer/)
--- | ---
`cond_invalid.gba` | PASS
`dma_priority.gba` | PASS
`hello_world.gba` | PASS
`if_ack.gba` | PASS
`line_timing.gba` | FAIL
`lyc_midline.gba` | FAIL
`window_midframe.gba` | FAIL
GBA Test Collection | [ladystarbreeze](https://github.com/ladystarbreeze)
--- | ---
`retAddr.gba` | PASS
`helloWorld.gba` | PASS
`helloAudio.gba` | PASS
FuzzARM | [DenSinH](https://github.com/DenSinH/)
--- | ---
`main.gba` | PASS
arm7wrestler GBA Fixed | [destoer](https://github.com/destoer)
--- | ---
`armwrestler-gba-fixed.gba` | PASS
- [x] [jsmolka's GBA Test Collection](https://github.com/jsmolka/gba-tests)
- [x] `arm.gba` and `thumb.gba`
- [x] `flash64.gba`, `flash128.gba`, `none.gba`, and `sram.gba`
- [x] `hello.gba`, `shades.gba`, and `stripes.gba`
- [x] `memory.gba`
- [x] `bios.gba`
- [x] `nes.gba`
- [ ] [DenSinH's GBA ROMs](https://github.com/DenSinH/GBARoms)
- [x] `eeprom-test` and `flash-test`
- [x] `midikey2freq`
- [ ] `swi-tests-random`
- [ ] [destoer's GBA Tests](https://github.com/destoer/gba_tests)
- [x] `cond_invalid.gba`
- [x] `dma_priority.gba`
- [x] `hello_world.gba`
- [x] `if_ack.gba`
- [ ] `line_timing.gba`
- [ ] `lyc_midline.gba`
- [ ] `window_midframe.gba`
- [x] [ladystarbreeze's GBA Test Collection](https://github.com/ladystarbreeze/GBA-Test-Collection)
- [x] `retAddr.gba`
- [x] `helloWorld.gba`
- [x] `helloAudio.gba`
- [x] [`armwrestler-gba-fixed.gba`](https://github.com/destoer/armwrestler-gba-fixed)
- [x] [FuzzARM](https://github.com/DenSinH/FuzzARM)
## Resources
@ -77,19 +63,19 @@ arm7wrestler GBA Fixed | [destoer](https://github.com/destoer)
## Compiling
Most recently built on Zig [0.11.0-dev.368+1829b6eab](https://github.com/ziglang/zig/tree/1829b6eab)
Most recently built on Zig [v0.11.0-dev.144+892fb0fc8](https://github.com/ziglang/zig/tree/892fb0fc8)
### Dependencies
Dependency | Source
--- | ---
SDL.zig | <https://github.com/MasterQ32/SDL.zig>
zig-clap | <https://github.com/Hejsil/zig-clap>
known-folders | <https://github.com/ziglibs/known-folders>
zig-toml | <https://github.com/aeronavery/zig-toml>
zig-datetime | <https://github.com/frmdstryr/zig-datetime>
`bitfields.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>
- [SDL.zig](https://github.com/MasterQ32/SDL.zig)
- [SDL2](https://www.libsdl.org/download-2.0.php)
- [zig-clap](https://github.com/Hejsil/zig-clap)
- [known-folders](https://github.com/ziglibs/known-folders)
- [zig-toml](https://github.com/aeronavery/zig-toml)
- [zig-datetime](https://github.com/frmdstryr/zig-datetime)
- [`bitfields.zig`](https://github.com/FlorenceOS/Florence/blob/aaa5a9e568/lib/util/bitfields.zig)
`bitfields.zig` from [FlorenceOS](https://github.com/FlorenceOS) is included under `lib/util/bitfield.zig`.
Use `git submodule update --init` from the project root to pull the git submodules `SDL.zig`, `zig-clap`, `known-folders`, `zig-toml` and `zig-datetime`

View File

@ -1,15 +1,7 @@
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

@ -1 +1 @@
Subproject commit 00b43568854f14e3bab340a4e062776ecb44a727
Subproject commit 6a9e37687a4b9ae3c14c9ea148ec51d14e01c7db

@ -1 +1 @@
Subproject commit a1b01ffeab452790790034b8a0e97aa30bbeb800
Subproject commit 749c43f1f846adc950a5920ed61b40cbc3ec2c54

@ -1 +1 @@
Subproject commit 016b8bcf98e50ae9408f6a9606bbec5a9bc6f677
Subproject commit be26d500917b5b3af4708f62ce22b990032a0ad3

View File

@ -58,10 +58,7 @@ pub fn load(allocator: Allocator, file_path: []const u8) !void {
const contents = try config_file.readToEndAlloc(allocator, try config_file.getEndPos());
defer allocator.free(contents);
var parser = try toml.parseFile(allocator, file_path);
defer parser.deinit();
const table = try parser.parse();
const table = try toml.parseContents(allocator, contents, null);
defer table.deinit();
// TODO: Report unknown config options

View File

@ -311,7 +311,7 @@ pub fn read(self: *Self, comptime T: type, unaligned_address: u32) T {
self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, unaligned_address >> 24)];
// We're doing some serious out-of-bounds open-bus reads
if (page >= table_len) return self.openBus(T, unaligned_address);
if (page > table_len) return self.openBus(T, unaligned_address);
if (self.read_table[page]) |some_ptr| {
// We have a pointer to a page, cast the pointer to it's underlying type
@ -377,7 +377,7 @@ pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo
self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, unaligned_address >> 24)];
// We're doing some serious out-of-bounds open-bus writes, they do nothing though
if (page >= table_len) return;
if (page > table_len) return;
if (self.write_tables[@boolToInt(T == u8)][page]) |some_ptr| {
// We have a pointer to a page, cast the pointer to it's underlying type

View File

@ -108,14 +108,8 @@ 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 => {
// 0x80 and 0x81 handled in setSoundCnt
if (byte_addr < 0x80 and !apu.cnt.apu_enable.read()) return;
switch (byte_addr) {
u32 => switch (byte_addr) {
0x60 => apu.ch1.setSound1Cnt(value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)),
@ -137,12 +131,8 @@ 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 => {
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
switch (byte_addr) {
u16 => switch (byte_addr) {
0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L
0x62 => apu.ch1.setSound1CntH(value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, value),
@ -174,12 +164,8 @@ 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 => {
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
switch (byte_addr) {
u8 => switch (byte_addr) {
0x60 => apu.ch1.setSound1CntL(value),
0x61 => {},
0x62 => apu.ch1.setNr11(value),
@ -222,7 +208,6 @@ 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"),
}
@ -290,20 +275,15 @@ 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 {
if (self.cnt.apu_enable.read()) self.setSoundCntL(@truncate(u16, value));
self.setSoundCntL(@truncate(u16, value));
self.setSoundCntH(@truncate(u16, value >> 16));
}
@ -342,14 +322,11 @@ pub const Apu = struct {
self.fs.step = 0; // Reset Frame Sequencer
// Reset Square Wave Offsets
self.ch1.square.reset();
self.ch2.square.reset();
self.ch1.square.pos = 0;
self.ch2.square.pos = 0;
// Reset Wave
self.ch3.wave_dev.reset();
// Rest Noise
self.ch4.lfsr.reset();
// Reset Wave Device Offsets
self.ch3.wave_dev.offset = 0;
} else {
self.reset();
}
@ -418,8 +395,8 @@ pub const Apu = struct {
right += if (self.dma_cnt.chB_right.read()) chB_sample else 0;
// Add SOUNDBIAS
// 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;
// FIXME: Is SOUNDBIAS 9-bit or 10-bit?
const bias = @as(i16, self.bias.level.read()) << 1;
left += bias;
right += bias;

View File

@ -49,13 +49,10 @@ pub fn init(sched: *Scheduler) Self {
}
pub fn reset(self: *Self) void {
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.len = 0;
self.envelope.raw = 0;
self.poly.raw = 0;
self.cnt.raw = 0;
self.sample = 0;
self.enabled = false;

View File

@ -43,12 +43,9 @@ pub fn init(sched: *Scheduler) Self {
}
pub fn reset(self: *Self) void {
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.duty.raw = 0;
self.envelope.raw = 0;
self.freq.raw = 0;
self.sample = 0;
self.enabled = false;

View File

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

View File

@ -42,13 +42,10 @@ pub fn init(sched: *Scheduler) Self {
}
pub fn reset(self: *Self) void {
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.select.raw = 0;
self.length = 0;
self.vol.raw = 0;
self.freq.raw = 0;
self.sample = 0;
self.enabled = false;

View File

@ -11,11 +11,6 @@ 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,10 +6,6 @@ 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,13 +18,6 @@ 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,11 +19,6 @@ 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,11 +20,6 @@ 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,13 +38,6 @@ 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 = struct { DmaController(0), DmaController(1), DmaController(2), DmaController(3) };
pub const DmaTuple = std.meta.Tuple(&[_]type{ DmaController(0), DmaController(1), DmaController(2), DmaController(3) });
const log = std.log.scoped(.DmaTransfer);
const getHalf = util.getHalf;
@ -256,16 +256,11 @@ fn DmaController(comptime id: u2) type {
cpu.bus.write(u16, dad_addr, @truncate(u16, rotr(u32, self.data_latch, 8 * (dad_addr & 3))));
}
switch (@truncate(u8, sad_addr >> 24)) {
// according to fleroviux, DMAs with a source address in ROM misbehave
// the resultant behaviour is that the source address will increment despite what DMAXCNT says
0x08...0x0D => self.sad_latch +%= offset, // obscure behaviour
else => switch (sad_adj) {
switch (sad_adj) {
.Increment => self.sad_latch +%= offset,
.Decrement => self.sad_latch -%= offset,
.IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}),
.Fixed => {},
},
}
switch (dad_adj) {

View File

@ -24,14 +24,14 @@ pub const Io = struct {
postflg: PostFlag,
waitcnt: WaitControl,
haltcnt: HaltControl,
keyinput: AtomicKeyInput,
keyinput: KeyInput,
pub fn init() Self {
return .{
.ime = false,
.ie = .{ .raw = 0x0000 },
.irq = .{ .raw = 0x0000 },
.keyinput = AtomicKeyInput.init(.{ .raw = 0x03FF }),
.keyinput = .{ .raw = 0x03FF },
.waitcnt = .{ .raw = 0x0000_0000 }, // Bit 15 == 0 for GBA
.postflg = .FirstBoot,
.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}),
// Keypad Input
0x0400_0130 => bus.io.keyinput.load(.Monotonic).raw,
0x0400_0130 => bus.io.keyinput.raw,
// Serial Communication 2
0x0400_0134 => util.io.read.todo(log, "Read {} from RCNT", .{T}),
@ -376,31 +376,6 @@ const KeyInput = extern union {
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
pub const BackgroundControl = extern union {
priority: Bitfield(u16, 0, 2),

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 = struct { Timer(0), Timer(1), Timer(2), Timer(3) };
pub const TimerTuple = std.meta.Tuple(&[_]type{ Timer(0), Timer(1), Timer(2), Timer(3) });
const log = std.log.scoped(.Timer);
const getHalf = util.getHalf;
@ -150,35 +150,20 @@ fn Timer(comptime id: u2) type {
pub fn setTimcntH(self: *Self, halfword: u16) void {
const new = TimerControl{ .raw = halfword };
if (self.cnt.enabled.read()) {
// timer was already enabled
// If enabled falling edge or cascade falling edge, timer is paused
if (!new.enabled.read() or (!self.cnt.cascade.read() and new.cascade.read())) {
// If Timer happens to be enabled, It will either be resheduled or disabled
self.sched.removeScheduledEvent(.{ .TimerOverflow = id });
// Counter should hold the value it stopped at meaning we have to calculate it now
if (self.cnt.enabled.read() and (new.cascade.read() or !new.enabled.read())) {
// Either through the cascade bit or the enable bit, the timer has effectively been disabled
// The Counter should hold whatever value it should have been at when it was disabled
self._counter +%= @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency());
}
// the timer has always been enabled, but the cascade bit which was blocking the timer has been unset
if (new.enabled.read() and (self.cnt.cascade.read() and !new.cascade.read())) {
// we want to reschedule the timer event, however we won't reload the counter.
// the invariant here is that self._counter holds the already calculated paused value
// The counter is only reloaded on the rising edge of the enable bit
if (!self.cnt.enabled.read() and new.enabled.read()) self._counter = self._reload;
self.rescheduleTimerExpire(0);
}
} else {
// the timer was previously disabeld
if (new.enabled.read()) {
// timer should start counting (with a reloaded counter value)
self._counter = self._reload;
// if cascade happens to be set, the timer doesn't actually do anything though
if (!new.cascade.read()) self.rescheduleTimerExpire(0);
}
}
// If Timer is enabled and we're not cascading, we need to schedule an overflow event
if (new.enabled.read() and !new.cascade.read()) self.rescheduleTimerExpire(0);
self.cnt.raw = halfword;
}
@ -217,8 +202,7 @@ fn Timer(comptime id: u2) type {
}
// Reschedule Timer if we're not cascading
// TIM0 cascade value is N/A
if (id == 0 or !self.cnt.cascade.read()) {
if (!self.cnt.cascade.read()) {
self._counter = self._reload;
self.rescheduleTimerExpire(late);
}

View File

@ -7,7 +7,6 @@ const Scheduler = @import("scheduler.zig").Scheduler;
const Logger = @import("../util.zig").Logger;
const File = std.fs.File;
const log = std.log.scoped(.Arm7Tdmi);
// ARM Instructions
pub const arm = struct {
@ -235,6 +234,8 @@ pub const thumb = struct {
}
};
const log = std.log.scoped(.Arm7Tdmi);
pub const Arm7tdmi = struct {
const Self = @This();
@ -245,36 +246,34 @@ pub const Arm7tdmi = struct {
cpsr: PSR,
spsr: PSR,
bank: Bank,
/// Storage for R8_fiq -> R12_fiq and their normal counterparts
/// e.g [r[0 + 8], fiq_r[0 + 8], r[1 + 8], fiq_r[1 + 8]...]
banked_fiq: [2 * 5]u32,
/// Storage for r13_<mode>, r14_<mode>
/// e.g. [r13, r14, r13_svc, r14_svc]
banked_r: [2 * 6]u32,
banked_spsr: [5]PSR,
logger: ?Logger,
/// Bank of Registers from other CPU Modes
const Bank = struct {
/// Storage for r13_<mode>, r14_<mode>
/// e.g. [r13, r14, r13_svc, r14_svc]
r: [2 * 6]u32,
/// Storage for R8_fiq -> R12_fiq and their normal counterparts
/// e.g [r[0 + 8], fiq_r[0 + 8], r[1 + 8], fiq_r[1 + 8]...]
fiq: [2 * 5]u32,
spsr: [5]PSR,
const Kind = enum(u1) {
R13 = 0,
R14,
};
pub fn create() Bank {
return .{
.r = [_]u32{0x00} ** 12,
.fiq = [_]u32{0x00} ** 10,
.spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5,
pub fn init(sched: *Scheduler, bus: *Bus, log_file: ?std.fs.File) Self {
return Self{
.r = [_]u32{0x00} ** 16,
.pipe = Pipeline.init(),
.sched = sched,
.bus = bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.banked_fiq = [_]u32{0x00} ** 10,
.banked_r = [_]u32{0x00} ** 12,
.banked_spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5,
.logger = if (log_file) |file| Logger.init(file) else null,
};
}
inline fn regIdx(mode: Mode, kind: Kind) usize {
inline fn bankedIdx(mode: Mode, kind: BankedKind) usize {
const idx: usize = switch (mode) {
.User, .System => 0,
.Supervisor => 1,
@ -287,7 +286,7 @@ pub const Arm7tdmi = struct {
return (idx * 2) + if (kind == .R14) @as(usize, 1) else 0;
}
inline fn spsrIdx(mode: Mode) usize {
inline fn bankedSpsrIndex(mode: Mode) usize {
return switch (mode) {
.Supervisor => 0,
.Abort => 1,
@ -298,23 +297,9 @@ pub const Arm7tdmi = struct {
};
}
inline fn fiqIdx(i: usize, mode: Mode) usize {
inline fn bankedFiqIdx(i: usize, mode: Mode) usize {
return (i * 2) + if (mode == .Fiq) @as(usize, 1) else 0;
}
};
pub fn init(sched: *Scheduler, bus: *Bus, log_file: ?std.fs.File) Self {
return Self{
.r = [_]u32{0x00} ** 16,
.pipe = Pipeline.init(),
.sched = sched,
.bus = bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.bank = Bank.create(),
.logger = if (log_file) |file| Logger.init(file) else null,
};
}
pub inline fn hasSPSR(self: *const Self) bool {
const mode = getModeChecked(self, self.cpsr.mode.read());
@ -351,14 +336,14 @@ pub const Arm7tdmi = struct {
switch (idx) {
8...12 => {
if (current == .Fiq) {
self.bank.fiq[Bank.fiqIdx(idx - 8, .User)] = value;
self.banked_fiq[bankedFiqIdx(idx - 8, .User)] = value;
} else self.r[idx] = value;
},
13, 14 => switch (current) {
.User, .System => self.r[idx] = value,
else => {
const kind = std.meta.intToEnum(Bank.Kind, idx - 13) catch unreachable;
self.bank.r[Bank.regIdx(.User, kind)] = value;
const kind = std.meta.intToEnum(BankedKind, idx - 13) catch unreachable;
self.banked_r[bankedIdx(.User, kind)] = value;
},
},
else => self.r[idx] = value, // R0 -> R7 and R15
@ -369,12 +354,12 @@ pub const Arm7tdmi = struct {
const current = getModeChecked(self, self.cpsr.mode.read());
return switch (idx) {
8...12 => if (current == .Fiq) self.bank.fiq[Bank.fiqIdx(idx - 8, .User)] else self.r[idx],
8...12 => if (current == .Fiq) self.banked_fiq[bankedFiqIdx(idx - 8, .User)] else self.r[idx],
13, 14 => switch (current) {
.User, .System => self.r[idx],
else => blk: {
const kind = std.meta.intToEnum(Bank.Kind, idx - 13) catch unreachable;
break :blk self.bank.r[Bank.regIdx(.User, kind)];
const kind = std.meta.intToEnum(BankedKind, idx - 13) catch unreachable;
break :blk self.banked_r[bankedIdx(.User, kind)];
},
},
else => self.r[idx], // R0 -> R7 and R15
@ -387,38 +372,38 @@ pub const Arm7tdmi = struct {
// Bank R8 -> r12
var i: usize = 0;
while (i < 5) : (i += 1) {
self.bank.fiq[Bank.fiqIdx(i, now)] = self.r[8 + i];
self.banked_fiq[bankedFiqIdx(i, now)] = self.r[8 + i];
}
// Bank r13, r14, SPSR
switch (now) {
.User, .System => {
self.bank.r[Bank.regIdx(now, .R13)] = self.r[13];
self.bank.r[Bank.regIdx(now, .R14)] = self.r[14];
self.banked_r[bankedIdx(now, .R13)] = self.r[13];
self.banked_r[bankedIdx(now, .R14)] = self.r[14];
},
else => {
self.bank.r[Bank.regIdx(now, .R13)] = self.r[13];
self.bank.r[Bank.regIdx(now, .R14)] = self.r[14];
self.bank.spsr[Bank.spsrIdx(now)] = self.spsr;
self.banked_r[bankedIdx(now, .R13)] = self.r[13];
self.banked_r[bankedIdx(now, .R14)] = self.r[14];
self.banked_spsr[bankedSpsrIndex(now)] = self.spsr;
},
}
// Grab R8 -> R12
i = 0;
while (i < 5) : (i += 1) {
self.r[8 + i] = self.bank.fiq[Bank.fiqIdx(i, next)];
self.r[8 + i] = self.banked_fiq[bankedFiqIdx(i, next)];
}
// Grab r13, r14, SPSR
switch (next) {
.User, .System => {
self.r[13] = self.bank.r[Bank.regIdx(next, .R13)];
self.r[14] = self.bank.r[Bank.regIdx(next, .R14)];
self.r[13] = self.banked_r[bankedIdx(next, .R13)];
self.r[14] = self.banked_r[bankedIdx(next, .R14)];
},
else => {
self.r[13] = self.bank.r[Bank.regIdx(next, .R13)];
self.r[14] = self.bank.r[Bank.regIdx(next, .R14)];
self.spsr = self.bank.spsr[Bank.spsrIdx(next)];
self.r[13] = self.banked_r[bankedIdx(next, .R13)];
self.r[14] = self.banked_r[bankedIdx(next, .R14)];
self.spsr = self.banked_spsr[bankedSpsrIndex(next)];
},
}
@ -439,8 +424,8 @@ pub const Arm7tdmi = struct {
self.r[13] = 0x0300_7F00;
self.r[15] = 0x0800_0000;
self.bank.r[Bank.regIdx(.Irq, .R13)] = 0x0300_7FA0;
self.bank.r[Bank.regIdx(.Supervisor, .R13)] = 0x0300_7FE0;
self.banked_r[bankedIdx(.Irq, .R13)] = 0x0300_7FA0;
self.banked_r[bankedIdx(.Supervisor, .R13)] = 0x0300_7FE0;
// self.cpsr.raw = 0x6000001F;
self.cpsr.raw = 0x0000_001F;
@ -530,10 +515,10 @@ pub const Arm7tdmi = struct {
std.debug.print("R{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\n", .{ i, self.r[i], i_1, self.r[i_1], i_2, self.r[i_2], i_3, self.r[i_3] });
}
std.debug.print("cpsr: 0x{X:0>8} ", .{self.cpsr.raw});
self.cpsr.toString();
prettyPrintPsr(&self.cpsr);
std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw});
self.spsr.toString();
prettyPrintPsr(&self.spsr);
std.debug.print("pipeline: {??X:0>8}\n", .{self.pipe.stage});
@ -551,6 +536,76 @@ pub const Arm7tdmi = struct {
std.debug.panic(format, args);
}
fn prettyPrintPsr(psr: *const PSR) void {
std.debug.print("[", .{});
if (psr.n.read()) std.debug.print("N", .{}) else std.debug.print("-", .{});
if (psr.z.read()) std.debug.print("Z", .{}) else std.debug.print("-", .{});
if (psr.c.read()) std.debug.print("C", .{}) else std.debug.print("-", .{});
if (psr.v.read()) std.debug.print("V", .{}) else std.debug.print("-", .{});
if (psr.i.read()) std.debug.print("I", .{}) else std.debug.print("-", .{});
if (psr.f.read()) std.debug.print("F", .{}) else std.debug.print("-", .{});
if (psr.t.read()) std.debug.print("T", .{}) else std.debug.print("-", .{});
std.debug.print("|", .{});
if (getMode(psr.mode.read())) |mode| std.debug.print("{s}", .{modeString(mode)}) else std.debug.print("---", .{});
std.debug.print("]\n", .{});
}
fn modeString(mode: Mode) []const u8 {
return switch (mode) {
.User => "usr",
.Fiq => "fiq",
.Irq => "irq",
.Supervisor => "svc",
.Abort => "abt",
.Undefined => "und",
.System => "sys",
};
}
fn mgbaLog(self: *const Self, file: *const File, opcode: u32) !void {
const thumb_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>4}:\n";
const arm_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>8}:\n";
var buf: [0x100]u8 = [_]u8{0x00} ** 0x100; // this is larger than it needs to be
const r0 = self.r[0];
const r1 = self.r[1];
const r2 = self.r[2];
const r3 = self.r[3];
const r4 = self.r[4];
const r5 = self.r[5];
const r6 = self.r[6];
const r7 = self.r[7];
const r8 = self.r[8];
const r9 = self.r[9];
const r10 = self.r[10];
const r11 = self.r[11];
const r12 = self.r[12];
const r13 = self.r[13];
const r14 = self.r[14];
const r15 = self.r[15] -| if (self.cpsr.t.read()) 2 else @as(u32, 4);
const c_psr = self.cpsr.raw;
var log_str: []u8 = undefined;
if (self.cpsr.t.read()) {
if (opcode >> 11 == 0x1E) {
// Instruction 1 of a BL Opcode, print in ARM mode
const other_half = self.bus.debugRead(u16, self.r[15] - 2);
const bl_opcode = @as(u32, opcode) << 16 | other_half;
log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, bl_opcode });
} else {
log_str = try std.fmt.bufPrint(&buf, thumb_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, opcode });
}
} else {
log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, opcode });
}
_ = try file.writeAll(log_str);
}
};
const condition_lut = [_]u16{
@ -629,22 +684,6 @@ pub const PSR = extern union {
z: Bit(u32, 30),
n: Bit(u32, 31),
raw: u32,
fn toString(self: PSR) void {
std.debug.print("[", .{});
if (self.n.read()) std.debug.print("N", .{}) else std.debug.print("-", .{});
if (self.z.read()) std.debug.print("Z", .{}) else std.debug.print("-", .{});
if (self.c.read()) std.debug.print("C", .{}) else std.debug.print("-", .{});
if (self.v.read()) std.debug.print("V", .{}) else std.debug.print("-", .{});
if (self.i.read()) std.debug.print("I", .{}) else std.debug.print("-", .{});
if (self.f.read()) std.debug.print("F", .{}) else std.debug.print("-", .{});
if (self.t.read()) std.debug.print("T", .{}) else std.debug.print("-", .{});
std.debug.print("|", .{});
if (getMode(self.mode.read())) |m| std.debug.print("{s}", .{m.toString()}) else std.debug.print("---", .{});
std.debug.print("]\n", .{});
}
};
const Mode = enum(u5) {
@ -655,18 +694,11 @@ const Mode = enum(u5) {
Abort = 0b10111,
Undefined = 0b11011,
System = 0b11111,
};
fn toString(self: Mode) []const u8 {
return switch (self) {
.User => "usr",
.Fiq => "fiq",
.Irq => "irq",
.Supervisor => "svc",
.Abort => "abt",
.Undefined => "und",
.System => "sys",
};
}
const BankedKind = enum(u1) {
R13 = 0,
R14,
};
fn getMode(bits: u5) ?Mode {

View File

@ -56,7 +56,7 @@ fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), schedule
.Unlimited, .UnlimitedFPS => {
log.info("Emulation w/out video sync", .{});
while (!quit.load(.Monotonic)) {
while (!quit.load(.SeqCst)) {
runFrame(scheduler, cpu);
audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
@ -68,7 +68,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 wake_time: u64 = frame_period;
while (!quit.load(.Monotonic)) {
while (!quit.load(.SeqCst)) {
runFrame(scheduler, cpu);
const new_wake_time = videoSync(&timer, wake_time);

View File

@ -87,7 +87,7 @@ pub fn main() void {
cpu.fastBoot();
}
var gui = Gui.init(&bus.pak.title, &bus.apu, width, height) catch |e| exitln("failed to init gui: {}", .{e});
var gui = Gui.init(&bus.pak.title, &bus.apu, width, height);
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 = "ZBA";
const default_title: []const u8 = "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,
default_title.ptr,
SDL.SDL_WINDOWPOS_CENTERED,
SDL.SDL_WINDOWPOS_CENTERED,
@as(c_int, width * win_scale),
@ -64,10 +64,10 @@ pub const Gui = struct {
const ctx = SDL.SDL_GL_CreateContext(window) orelse panic();
if (SDL.SDL_GL_MakeCurrent(window, ctx) < 0) panic();
try gl.load(ctx, Self.glGetProcAddress);
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 = try compileShaders();
const program_id = 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,16 +89,12 @@ 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);
@ -108,7 +104,7 @@ pub const Gui = struct {
}
// Returns the VAO ID since it's used in run()
fn generateBuffers() struct { c_uint, c_uint, c_uint } {
fn generateBuffers() [3]c_uint {
var vao_id: c_uint = undefined;
var vbo_id: c_uint = undefined;
var ebo_id: c_uint = undefined;
@ -158,21 +154,13 @@ 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];
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 });
defer thread.join();
var title_buf: [0x100]u8 = undefined;
var title_buf: [0x100]u8 = [_]u8{0} ** 0x100;
const vao_id = Self.generateBuffers()[0];
_ = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer));
emu_loop: while (true) {
var event: SDL.SDL_Event = undefined;
@ -180,50 +168,54 @@ pub const Gui = struct {
switch (event.type) {
SDL.SDL_QUIT => break :emu_loop,
SDL.SDL_KEYDOWN => {
const io = &cpu.bus.io;
const key_code = event.key.keysym.sym;
var keyinput = cpu.bus.io.keyinput.load(.Monotonic);
switch (key_code) {
SDL.SDLK_UP => keyinput.up.unset(),
SDL.SDLK_DOWN => keyinput.down.unset(),
SDL.SDLK_LEFT => keyinput.left.unset(),
SDL.SDLK_RIGHT => keyinput.right.unset(),
SDL.SDLK_x => keyinput.a.unset(),
SDL.SDLK_z => keyinput.b.unset(),
SDL.SDLK_a => keyinput.shoulder_l.unset(),
SDL.SDLK_s => keyinput.shoulder_r.unset(),
SDL.SDLK_RETURN => keyinput.start.unset(),
SDL.SDLK_RSHIFT => keyinput.select.unset(),
SDL.SDLK_UP => io.keyinput.up.unset(),
SDL.SDLK_DOWN => io.keyinput.down.unset(),
SDL.SDLK_LEFT => io.keyinput.left.unset(),
SDL.SDLK_RIGHT => io.keyinput.right.unset(),
SDL.SDLK_x => io.keyinput.a.unset(),
SDL.SDLK_z => io.keyinput.b.unset(),
SDL.SDLK_a => io.keyinput.shoulder_l.unset(),
SDL.SDLK_s => io.keyinput.shoulder_r.unset(),
SDL.SDLK_RETURN => io.keyinput.start.unset(),
SDL.SDLK_RSHIFT => io.keyinput.select.unset(),
else => {},
}
cpu.bus.io.keyinput.store(keyinput.raw, .Monotonic);
},
SDL.SDL_KEYUP => {
const io = &cpu.bus.io;
const key_code = event.key.keysym.sym;
var keyinput = cpu.bus.io.keyinput.load(.Monotonic);
switch (key_code) {
SDL.SDLK_UP => keyinput.up.set(),
SDL.SDLK_DOWN => keyinput.down.set(),
SDL.SDLK_LEFT => keyinput.left.set(),
SDL.SDLK_RIGHT => keyinput.right.set(),
SDL.SDLK_x => keyinput.a.set(),
SDL.SDLK_z => keyinput.b.set(),
SDL.SDLK_a => keyinput.shoulder_l.set(),
SDL.SDLK_s => keyinput.shoulder_r.set(),
SDL.SDLK_RETURN => keyinput.start.set(),
SDL.SDLK_RSHIFT => keyinput.select.set(),
SDL.SDLK_UP => io.keyinput.up.set(),
SDL.SDLK_DOWN => io.keyinput.down.set(),
SDL.SDLK_LEFT => io.keyinput.left.set(),
SDL.SDLK_RIGHT => io.keyinput.right.set(),
SDL.SDLK_x => io.keyinput.a.set(),
SDL.SDLK_z => io.keyinput.b.set(),
SDL.SDLK_a => io.keyinput.shoulder_l.set(),
SDL.SDLK_s => io.keyinput.shoulder_r.set(),
SDL.SDLK_RETURN => io.keyinput.start.set(),
SDL.SDLK_RSHIFT => io.keyinput.select.set(),
SDL.SDLK_i => {
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))});
},
// SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }),
SDL.SDLK_k => {},
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);
},
else => {},
}
cpu.bus.io.keyinput.store(keyinput.raw, .Monotonic);
},
else => {},
}
@ -238,15 +230,16 @@ pub const Gui = struct {
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null);
SDL.SDL_GL_SwapWindow(self.window);
const dyn_title = std.fmt.bufPrintZ(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable;
const dyn_title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable;
SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr);
}
quit.store(true, .Monotonic); // Terminate Emulator Thread
quit.store(true, .SeqCst); // Terminate Emulator Thread
}
pub fn deinit(self: *Self) void {
self.audio.deinit();
// TODO: Buffer deletions
gl.deleteProgram(self.program_id);
SDL.SDL_GL_DeleteContext(self.ctx);
SDL.SDL_DestroyWindow(self.window);
@ -303,28 +296,6 @@ 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

@ -49,7 +49,7 @@ pub const FpsTracker = struct {
pub fn value(self: *Self) u32 {
if (self.timer.read() >= std.time.ns_per_s) {
self.fps = self.count.swap(0, .Monotonic);
self.fps = self.count.swap(0, .SeqCst);
self.timer.reset();
}
@ -148,7 +148,7 @@ pub const Logger = struct {
if (cpu.cpsr.t.read()) {
if (opcode >> 11 == 0x1E) {
// Instruction 1 of a BL Opcode, print in ARM mode
const low = cpu.bus.dbgRead(u16, cpu.r[15] - 2);
const low = cpu.bus.dbgRead(u16, cpu.r[15]);
const bl_opcode = @as(u32, opcode) << 16 | low;
self.print(arm_fmt, Self.fmtArgs(cpu, bl_opcode)) catch @panic("failed to write to log file");