Compare commits

..

15 Commits

Author SHA1 Message Date
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
Rekai Nyangadzayi Musuka 9fd405a896 chore(ci): update CI dependency 2022-11-11 13:25:56 -04:00
Rekai Nyangadzayi Musuka 5d7cf3a8a2 chore: remove util fn for stdlib equivalent 2022-11-11 13:02:51 -04:00
6 changed files with 58 additions and 124 deletions

View File

@ -17,7 +17,7 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
steps: steps:
- uses: goto-bus-stop/setup-zig@v1 - uses: goto-bus-stop/setup-zig@v2
with: with:
version: master version: master
- name: prepare-linux - name: prepare-linux
@ -51,7 +51,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: goto-bus-stop/setup-zig@v1 - uses: goto-bus-stop/setup-zig@v2
with: with:
version: master version: master
- run: zig fmt src/**/*.zig - run: zig fmt src/**/*.zig

View File

@ -6,7 +6,6 @@ const Eeprom = @import("backup/eeprom.zig").Eeprom;
const Flash = @import("backup/Flash.zig"); const Flash = @import("backup/Flash.zig");
const escape = @import("../../util.zig").escape; const escape = @import("../../util.zig").escape;
const span = @import("../../util.zig").span;
const Needle = struct { str: []const u8, kind: Backup.Kind }; const Needle = struct { str: []const u8, kind: Backup.Kind };
const backup_kinds = [6]Needle{ const backup_kinds = [6]Needle{
@ -195,7 +194,7 @@ pub const Backup = struct {
} }
fn saveName(self: *const Self, allocator: Allocator) ![]const u8 { fn saveName(self: *const Self, allocator: Allocator) ![]const u8 {
const title_str = span(&escape(self.title)); const title_str = std.mem.sliceTo(&escape(self.title), 0);
const name = if (title_str.len != 0) title_str else "untitled"; const name = if (title_str.len != 0) title_str else "untitled";
return try std.mem.concat(allocator, u8, &[_][]const u8{ name, ".sav" }); return try std.mem.concat(allocator, u8, &[_][]const u8{ name, ".sav" });

View File

@ -573,7 +573,7 @@ pub const Ppu = struct {
switch (bg_mode) { switch (bg_mode) {
0x0 => { 0x0 => {
const fb_base = framebuf_pitch * @as(usize, scanline); const framebuf_base = width * @as(usize, scanline);
if (obj_enable) self.fetchSprites(); if (obj_enable) self.fetchSprites();
var layer: usize = 0; var layer: usize = 0;
@ -585,23 +585,10 @@ pub const Ppu = struct {
if (layer == self.bg[3].cnt.priority.read() and bg_enable >> 3 & 1 == 1) self.drawBackground(3); if (layer == self.bg[3].cnt.priority.read() and bg_enable >> 3 & 1 == 1) self.drawBackground(3);
} }
// Copy Drawn Scanline to Frame Buffer self.drawTextMode(framebuf_base);
// If there are any nulls present in self.scanline it means that no background drew a pixel there, so draw backdrop
for (self.scanline.top()) |maybe_px, i| {
const maybe_top = maybe_px;
const maybe_btm = self.scanline.btm()[i];
const bgr555 = self.getBgr555(maybe_top, maybe_btm);
std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555));
}
// Reset Current Scanline Pixel Buffer and list of fetched sprites
// in prep for next scanline
self.scanline.reset();
std.mem.set(?Sprite, self.scanline_sprites, null);
}, },
0x1 => { 0x1 => {
const fb_base = framebuf_pitch * @as(usize, scanline); const framebuf_base = width * @as(usize, scanline);
if (obj_enable) self.fetchSprites(); if (obj_enable) self.fetchSprites();
var layer: usize = 0; var layer: usize = 0;
@ -612,23 +599,10 @@ pub const Ppu = struct {
if (layer == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawAffineBackground(2); if (layer == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawAffineBackground(2);
} }
// Copy Drawn Scanline to Frame Buffer self.drawTextMode(framebuf_base);
// If there are any nulls present in self.scanline.top() it means that no background drew a pixel there, so draw backdrop
for (self.scanline.top()) |maybe_px, i| {
const maybe_top = maybe_px;
const maybe_btm = self.scanline.btm()[i];
const bgr555 = self.getBgr555(maybe_top, maybe_btm);
std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555));
}
// Reset Current Scanline Pixel Buffer and list of fetched sprites
// in prep for next scanline
self.scanline.reset();
std.mem.set(?Sprite, self.scanline_sprites, null);
}, },
0x2 => { 0x2 => {
const fb_base = framebuf_pitch * @as(usize, scanline); const framebuf_base = width * @as(usize, scanline);
if (obj_enable) self.fetchSprites(); if (obj_enable) self.fetchSprites();
var layer: usize = 0; var layer: usize = 0;
@ -638,40 +612,32 @@ pub const Ppu = struct {
if (layer == self.bg[3].cnt.priority.read() and bg_enable >> 3 & 1 == 1) self.drawAffineBackground(3); if (layer == self.bg[3].cnt.priority.read() and bg_enable >> 3 & 1 == 1) self.drawAffineBackground(3);
} }
// Copy Drawn Scanline to Frame Buffer self.drawTextMode(framebuf_base);
// If there are any nulls present in self.scanline.top() it means that no background drew a pixel there, so draw backdrop
for (self.scanline.top()) |maybe_px, i| {
const maybe_top = maybe_px;
const maybe_btm = self.scanline.btm()[i];
const bgr555 = self.getBgr555(maybe_top, maybe_btm);
std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555));
}
// Reset Current Scanline Pixel Buffer and list of fetched sprites
// in prep for next scanline
self.scanline.reset();
std.mem.set(?Sprite, self.scanline_sprites, null);
}, },
0x3 => { 0x3 => {
const vram_base = width * @sizeOf(u16) * @as(usize, scanline); const vram_base = width * @as(usize, scanline);
const fb_base = framebuf_pitch * @as(usize, scanline); const framebuf_base = width * @as(usize, scanline);
var i: usize = 0; // FIXME: @ptrCast between slices changing the length isn't implemented yet
while (i < width) : (i += 1) { const vram_buf = @ptrCast([*]const u16, @alignCast(@alignOf(u16), self.vram.buf));
const bgr555 = self.vram.read(u16, vram_base + i * @sizeOf(u16)); const framebuf = @ptrCast([*]u32, @alignCast(@alignOf(u32), self.framebuf.get(.Emulator)));
std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555));
for (vram_buf[vram_base .. vram_base + width]) |bgr555, i| {
framebuf[framebuf_base + i] = rgba888(bgr555);
} }
}, },
0x4 => { 0x4 => {
const sel = self.dispcnt.frame_select.read(); const sel = self.dispcnt.frame_select.read();
const vram_base = width * @as(usize, scanline) + if (sel) 0xA000 else @as(usize, 0);
const fb_base = framebuf_pitch * @as(usize, scanline);
// Render Current Scanline const vram_base = width * @as(usize, scanline) + if (sel) 0xA000 else @as(usize, 0);
for (self.vram.buf[vram_base .. vram_base + width]) |byte, i| { const framebuf_base = width * @as(usize, scanline);
const bgr555 = self.palette.read(u16, @as(u16, byte) * @sizeOf(u16));
std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555)); // FIXME: @ptrCast between slices changing the length isn't implemented yet
const pal_buf = @ptrCast([*]const u16, @alignCast(@alignOf(u16), self.palette.buf));
const framebuf = @ptrCast([*]u32, @alignCast(@alignOf(u32), self.framebuf.get(.Emulator)));
for (self.vram.buf[vram_base .. vram_base + width]) |pal_id, i| {
framebuf[framebuf_base + i] = rgba888(pal_buf[pal_id]);
} }
}, },
0x5 => { 0x5 => {
@ -679,22 +645,44 @@ pub const Ppu = struct {
const m5_height = 128; const m5_height = 128;
const sel = self.dispcnt.frame_select.read(); const sel = self.dispcnt.frame_select.read();
const vram_base = m5_width * @sizeOf(u16) * @as(usize, scanline) + if (sel) 0xA000 else @as(usize, 0); const vram_base = m5_width * @as(usize, scanline) + if (sel) 0xA000 else @as(usize, 0);
const fb_base = framebuf_pitch * @as(usize, scanline); const framebuf_base = width * @as(usize, scanline);
// FIXME: @ptrCast between slices changing the length isn't implemented yet
const vram_buf = @ptrCast([*]const u16, @alignCast(@alignOf(u16), self.vram.buf));
const framebuf = @ptrCast([*]u32, @alignCast(@alignOf(u32), self.framebuf.get(.Emulator)));
var i: usize = 0; var i: usize = 0;
while (i < width) : (i += 1) { while (i < width) : (i += 1) {
// If we're outside of the bounds of mode 5, draw the background colour const bgr555 = if (scanline < m5_height and i < m5_width) vram_buf[vram_base + i] else self.palette.backdrop();
const bgr555 = framebuf[framebuf_base + i] = rgba888(bgr555);
if (scanline < m5_height and i < m5_width) self.vram.read(u16, vram_base + i * @sizeOf(u16)) else self.palette.backdrop();
std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555));
} }
}, },
else => std.debug.panic("[PPU] TODO: Implement BG Mode {}", .{bg_mode}), else => std.debug.panic("[PPU] TODO: Implement BG Mode {}", .{bg_mode}),
} }
} }
fn drawTextMode(self: *Self, framebuf_base: usize) void {
// Copy Drawn Scanline to Frame Buffer
// If there are any nulls present in self.scanline.top() it means that no background drew a pixel there, so draw backdrop
// FIXME: @ptrCast between slices changing the length isn't implemented yet
const framebuf = @ptrCast([*]u32, @alignCast(@alignOf(u32), self.framebuf.get(.Emulator)));
for (self.scanline.top()) |maybe_px, i| {
const maybe_top = maybe_px;
const maybe_btm = self.scanline.btm()[i];
const bgr555 = self.getBgr555(maybe_top, maybe_btm);
framebuf[framebuf_base + i] = rgba888(bgr555);
}
// Reset Current Scanline Pixel Buffer and list of fetched sprites
// in prep for next scanline
self.scanline.reset();
std.mem.set(?Sprite, self.scanline_sprites, null);
}
fn getBgr555(self: *Self, maybe_top: ?u16, maybe_btm: ?u16) u16 { fn getBgr555(self: *Self, maybe_top: ?u16, maybe_btm: ?u16) u16 {
if (maybe_btm) |btm| { if (maybe_btm) |btm| {
return switch (self.bld.cnt.mode.read()) { return switch (self.bld.cnt.mode.read()) {

View File

@ -42,6 +42,6 @@ pub fn deinit(self: *Self) void {
self.* = undefined; self.* = undefined;
} }
pub fn backdrop(self: *const Self) u16 { pub inline fn backdrop(self: *const Self) u16 {
return self.read(u16, 0); return std.mem.readIntNative(u16, self.buf[0..2]);
} }

View File

@ -9,8 +9,6 @@ const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
const Scheduler = @import("core/scheduler.zig").Scheduler; const Scheduler = @import("core/scheduler.zig").Scheduler;
const FpsTracker = @import("util.zig").FpsTracker; const FpsTracker = @import("util.zig").FpsTracker;
const span = @import("util.zig").span;
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;
@ -73,7 +71,7 @@ pub const Gui = struct {
return Self{ return Self{
.window = window, .window = window,
.title = span(title), .title = std.mem.sliceTo(title, 0),
.ctx = ctx, .ctx = ctx,
.program_id = program_id, .program_id = program_id,
.audio = Audio.init(apu), .audio = Audio.init(apu),

View File

@ -68,57 +68,6 @@ pub fn intToBytes(comptime T: type, value: anytype) [@sizeOf(T)]u8 {
return result; return result;
} }
/// The Title from the GBA Cartridge is an Uppercase ASCII string which is
/// null-padded to 12 bytes
///
/// This function returns a slice of the ASCII string without the null terminator(s)
/// (essentially, a proper Zig/Rust/Any modern language String)
pub fn span(title: *const [12]u8) []const u8 {
const end = std.mem.indexOfScalar(u8, title, '\x00');
return title[0 .. end orelse title.len];
}
test "span" {
var example: *const [12]u8 = "POKEMON_EMER";
try std.testing.expectEqualSlices(u8, "POKEMON_EMER", span(example));
example = "POKEMON_EME\x00";
try std.testing.expectEqualSlices(u8, "POKEMON_EME", span(example));
example = "POKEMON_EM\x00\x00";
try std.testing.expectEqualSlices(u8, "POKEMON_EM", span(example));
example = "POKEMON_E\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "POKEMON_E", span(example));
example = "POKEMON_\x00\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "POKEMON_", span(example));
example = "POKEMON\x00\x00\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "POKEMON", span(example));
example = "POKEMO\x00\x00\x00\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "POKEMO", span(example));
example = "POKEM\x00\x00\x00\x00\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "POKEM", span(example));
example = "POKE\x00\x00\x00\x00\x00\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "POKE", span(example));
example = "POK\x00\x00\x00\x00\x00\x00\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "POK", span(example));
example = "PO\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "PO", span(example));
example = "P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "P", span(example));
example = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
try std.testing.expectEqualSlices(u8, "", span(example));
}
/// Creates a copy of a title with all Filesystem-invalid characters replaced /// Creates a copy of a title with all Filesystem-invalid characters replaced
/// ///
/// e.g. POKEPIN R/S to POKEPIN R_S /// e.g. POKEPIN R/S to POKEPIN R_S