Compare commits

..

8 Commits

Author SHA1 Message Date
Rekai Nyangadzayi Musuka d645827a19 chore: dont allocate not-small ?Sprite array on stack
use memset like most other allocations in this emu
2022-09-18 09:20:47 -03:00
Rekai Nyangadzayi Musuka 8f9dc9d33c chore: move FrameBuffer struct to util.zig 2022-09-18 09:20:47 -03:00
Rekai Nyangadzayi Musuka cd985eec94 chore: move OAM, PALRAM and VRAM structs to separate files 2022-09-18 09:20:47 -03:00
Rekai Nyangadzayi Musuka 19498f157a 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-09-18 09:20:47 -03:00
Rekai Nyangadzayi Musuka 9a48d1aa94 chore: refactor window 2022-09-18 09:20:47 -03:00
Rekai Nyangadzayi Musuka cc6c8f1552 chore: crude background window impl (no affine) 2022-09-18 09:20:47 -03:00
Rekai Nyangadzayi Musuka 5a1f81093b chore: rename function (misspelt until now somehow) 2022-09-18 09:20:47 -03:00
Rekai Nyangadzayi Musuka e192c6712f chore: disable audio sync by default
forgot SDL2 AudioStream doesn't work well for my use-case
2022-09-18 09:20:01 -03:00
7 changed files with 211 additions and 216 deletions

View File

@ -227,7 +227,7 @@ fn DmaController(comptime id: u2) type {
} }
} }
pub fn pollBlankingDma(self: *Self, comptime kind: DmaKind) void { pub fn pollBlanking(self: *Self, comptime kind: DmaKind) void {
if (self.in_progress) return; // If there's an ongoing DMA Transfer, exit early if (self.in_progress) return; // If there's an ongoing DMA Transfer, exit early
// No ongoing DMA Transfer, We want to check if we should repeat an existing one // No ongoing DMA Transfer, We want to check if we should repeat an existing one
@ -271,11 +271,11 @@ fn DmaController(comptime id: u2) type {
}; };
} }
pub fn pollBlankingDma(bus: *Bus, comptime kind: DmaKind) void { pub fn onBlanking(bus: *Bus, comptime kind: DmaKind) void {
bus.dma[0].pollBlankingDma(kind); bus.dma[0].pollBlanking(kind);
bus.dma[1].pollBlankingDma(kind); bus.dma[1].pollBlanking(kind);
bus.dma[2].pollBlankingDma(kind); bus.dma[2].pollBlanking(kind);
bus.dma[3].pollBlankingDma(kind); bus.dma[3].pollBlanking(kind);
} }
const Adjustment = enum(u2) { const Adjustment = enum(u2) {

View File

@ -13,7 +13,7 @@ const Atomic = std.atomic.Atomic;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
// TODO: Move these to a TOML File // TODO: Move these to a TOML File
const sync_audio = true; // Enable Audio Sync const sync_audio = false; // Enable Audio Sync
const sync_video: RunKind = .LimitedFPS; // Configure Video Sync const sync_video: RunKind = .LimitedFPS; // Configure Video Sync
pub const win_scale = 4; // 1x, 2x, 3x, etc. Window Scaling pub const win_scale = 4; // 1x, 2x, 3x, etc. Window Scaling
pub const cpu_logging = false; // Enable detailed CPU logging pub const cpu_logging = false; // Enable detailed CPU logging

View File

@ -1,16 +1,19 @@
const std = @import("std"); const std = @import("std");
const io = @import("bus/io.zig"); const io = @import("bus/io.zig");
const Bit = @import("bitfield").Bit;
const Bitfield = @import("bitfield").Bitfield;
const dma = @import("bus/dma.zig");
const Oam = @import("ppu/Oam.zig");
const Palette = @import("ppu/Palette.zig");
const Vram = @import("ppu/Vram.zig");
const EventKind = @import("scheduler.zig").EventKind; const EventKind = @import("scheduler.zig").EventKind;
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 FrameBuffer = @import("util.zig").FrameBuffer;
const Bit = @import("bitfield").Bit;
const Bitfield = @import("bitfield").Bitfield;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const log = std.log.scoped(.PPU); const log = std.log.scoped(.Ppu);
const pollBlankingDma = @import("bus/dma.zig").pollBlankingDma;
/// This is used to generate byuu / Talurabi's Color Correction algorithm /// This is used to generate byuu / Talurabi's Color Correction algorithm
const COLOUR_LUT = genColourLut(); const COLOUR_LUT = genColourLut();
@ -51,14 +54,14 @@ pub const Ppu = struct {
sched.push(.Draw, 240 * 4); sched.push(.Draw, 240 * 4);
const sprites = try allocator.create([128]?Sprite); const sprites = try allocator.create([128]?Sprite);
sprites.* = [_]?Sprite{null} ** 128; std.mem.set(?Sprite, sprites, null);
return Self{ return Self{
.vram = try Vram.init(allocator), .vram = try Vram.init(allocator),
.palette = try Palette.init(allocator), .palette = try Palette.init(allocator),
.oam = try Oam.init(allocator), .oam = try Oam.init(allocator),
.sched = sched, .sched = sched,
.framebuf = try FrameBuffer.init(allocator), .framebuf = try FrameBuffer.init(allocator, framebuf_pitch * height),
.allocator = allocator, .allocator = allocator,
// Registers // Registers
@ -488,7 +491,7 @@ pub const Ppu = struct {
while (i < width) : (i += 1) { while (i < width) : (i += 1) {
// If we're outside of the bounds of mode 5, draw the background colour // If we're outside of the bounds of mode 5, draw the background colour
const bgr555 = const bgr555 =
if (scanline < m5_height and i < m5_width) self.vram.read(u16, vram_base + i * @sizeOf(u16)) else self.palette.getBackdrop(); 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)], COLOUR_LUT[bgr555 & 0x7FFF]); std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], COLOUR_LUT[bgr555 & 0x7FFF]);
} }
@ -532,7 +535,7 @@ pub const Ppu = struct {
} }
if (maybe_top) |top| return top; if (maybe_top) |top| return top;
return self.palette.getBackdrop(); return self.palette.backdrop();
} }
fn copyToBackgroundBuffer(self: *Self, comptime n: u2, bounds: ?WindowBounds, i: usize, bgr555: u16) void { fn copyToBackgroundBuffer(self: *Self, comptime n: u2, bounds: ?WindowBounds, i: usize, bgr555: u16) void {
@ -661,7 +664,7 @@ pub const Ppu = struct {
// See if HBlank DMA is present and not enabled // See if HBlank DMA is present and not enabled
if (!self.dispstat.vblank.read()) if (!self.dispstat.vblank.read())
pollBlankingDma(cpu.bus, .HBlank); dma.onBlanking(cpu.bus, .HBlank);
self.dispstat.hblank.set(); self.dispstat.hblank.set();
self.sched.push(.HBlank, 68 * 4 -| late); self.sched.push(.HBlank, 68 * 4 -| late);
@ -703,7 +706,7 @@ pub const Ppu = struct {
self.aff_bg[1].latchRefPoints(); self.aff_bg[1].latchRefPoints();
// See if Vblank DMA is present and not enabled // See if Vblank DMA is present and not enabled
pollBlankingDma(cpu.bus, .VBlank); dma.onBlanking(cpu.bus, .VBlank);
} }
if (scanline == 227) self.dispstat.vblank.unset(); if (scanline == 227) self.dispstat.vblank.unset();
@ -712,158 +715,6 @@ pub const Ppu = struct {
} }
}; };
const Palette = struct {
const palram_size = 0x400;
const Self = @This();
buf: []u8,
allocator: Allocator,
fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, palram_size);
std.mem.set(u8, buf, 0);
return Self{
.buf = buf,
.allocator = allocator,
};
}
fn deinit(self: *Self) void {
self.allocator.free(self.buf);
self.* = undefined;
}
pub fn read(self: *const Self, comptime T: type, address: usize) T {
const addr = address & 0x3FF;
return switch (T) {
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
else => @compileError("PALRAM: Unsupported read width"),
};
}
pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
const addr = address & 0x3FF;
switch (T) {
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
u8 => {
const align_addr = addr & ~@as(u32, 1); // Aligned to Halfword boundary
std.mem.writeIntSliceLittle(u16, self.buf[align_addr..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
},
else => @compileError("PALRAM: Unsupported write width"),
}
}
fn getBackdrop(self: *const Self) u16 {
return self.read(u16, 0);
}
};
const Vram = struct {
const vram_size = 0x18000;
const Self = @This();
buf: []u8,
allocator: Allocator,
fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, vram_size);
std.mem.set(u8, buf, 0);
return Self{
.buf = buf,
.allocator = allocator,
};
}
fn deinit(self: *Self) void {
self.allocator.free(self.buf);
self.* = undefined;
}
pub fn read(self: *const Self, comptime T: type, address: usize) T {
const addr = Self.mirror(address);
return switch (T) {
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
else => @compileError("VRAM: Unsupported read width"),
};
}
pub fn write(self: *Self, comptime T: type, dispcnt: io.DisplayControl, address: usize, value: T) void {
const mode: u3 = dispcnt.bg_mode.read();
const idx = Self.mirror(address);
switch (T) {
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[idx..][0..@sizeOf(T)], value),
u8 => {
// Ignore write if it falls within the boundaries of OBJ VRAM
switch (mode) {
0, 1, 2 => if (0x0001_0000 <= idx) return,
else => if (0x0001_4000 <= idx) return,
}
const align_idx = idx & ~@as(u32, 1); // Aligned to a halfword boundary
std.mem.writeIntSliceLittle(u16, self.buf[align_idx..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
},
else => @compileError("VRAM: Unsupported write width"),
}
}
fn mirror(address: usize) usize {
// Mirrored in steps of 128K (64K + 32K + 32K) (abcc)
const addr = address & 0x1FFFF;
// If the address is within 96K we don't do anything,
// otherwise we want to mirror the last 32K (addresses between 64K and 96K)
return if (addr < vram_size) addr else 0x10000 + (addr & 0x7FFF);
}
};
const Oam = struct {
const oam_size = 0x400;
const Self = @This();
buf: []u8,
allocator: Allocator,
fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, oam_size);
std.mem.set(u8, buf, 0);
return Self{
.buf = buf,
.allocator = allocator,
};
}
fn deinit(self: *Self) void {
self.allocator.free(self.buf);
self.* = undefined;
}
pub fn read(self: *const Self, comptime T: type, address: usize) T {
const addr = address & 0x3FF;
return switch (T) {
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
else => @compileError("OAM: Unsupported read width"),
};
}
pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
const addr = address & 0x3FF;
switch (T) {
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
u8 => return, // 8-bit writes are explicitly ignored
else => @compileError("OAM: Unsupported write width"),
}
}
};
const Window = struct { const Window = struct {
const Self = @This(); const Self = @This();
@ -1309,48 +1160,3 @@ const Scanline = struct {
return self.layers[1]; return self.layers[1];
} }
}; };
// Double Buffering Implementation
const FrameBuffer = struct {
const Self = @This();
layers: [2][]u8,
buf: []u8,
current: u1,
allocator: Allocator,
// TODO: Rename
const Device = enum {
Emulator,
Renderer,
};
pub fn init(allocator: Allocator) !Self {
const framebuf_len = framebuf_pitch * height;
const buf = try allocator.alloc(u8, framebuf_len * 2);
std.mem.set(u8, buf, 0);
return .{
// Front and Back Framebuffers
.layers = [_][]u8{ buf[0..][0..framebuf_len], buf[framebuf_len..][0..framebuf_len] },
.buf = buf,
.current = 0,
.allocator = allocator,
};
}
fn deinit(self: *Self) void {
self.allocator.free(self.buf);
self.* = undefined;
}
pub fn swap(self: *Self) void {
self.current = ~self.current;
}
pub fn get(self: *Self, comptime dev: Device) []u8 {
return self.layers[if (dev == .Emulator) self.current else ~self.current];
}
};

40
src/core/ppu/Oam.zig Normal file
View File

@ -0,0 +1,40 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const buf_len = 0x400;
const Self = @This();
buf: []u8,
allocator: Allocator,
pub fn read(self: *const Self, comptime T: type, address: usize) T {
const addr = address & 0x3FF;
return switch (T) {
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
else => @compileError("OAM: Unsupported read width"),
};
}
pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
const addr = address & 0x3FF;
switch (T) {
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
u8 => return, // 8-bit writes are explicitly ignored
else => @compileError("OAM: Unsupported write width"),
}
}
pub fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, buf_len);
std.mem.set(u8, buf, 0);
return Self{ .buf = buf, .allocator = allocator };
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.buf);
self.* = undefined;
}

47
src/core/ppu/Palette.zig Normal file
View File

@ -0,0 +1,47 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const buf_len = 0x400;
const Self = @This();
buf: []u8,
allocator: Allocator,
pub fn read(self: *const Self, comptime T: type, address: usize) T {
const addr = address & 0x3FF;
return switch (T) {
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
else => @compileError("PALRAM: Unsupported read width"),
};
}
pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
const addr = address & 0x3FF;
switch (T) {
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
u8 => {
const align_addr = addr & ~@as(u32, 1); // Aligned to Halfword boundary
std.mem.writeIntSliceLittle(u16, self.buf[align_addr..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
},
else => @compileError("PALRAM: Unsupported write width"),
}
}
pub fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, buf_len);
std.mem.set(u8, buf, 0);
return Self{ .buf = buf, .allocator = allocator };
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.buf);
self.* = undefined;
}
pub fn backdrop(self: *const Self) u16 {
return self.read(u16, 0);
}

60
src/core/ppu/Vram.zig Normal file
View File

@ -0,0 +1,60 @@
const std = @import("std");
const io = @import("../bus/io.zig");
const Allocator = std.mem.Allocator;
const buf_len = 0x18000;
const Self = @This();
buf: []u8,
allocator: Allocator,
pub fn read(self: *const Self, comptime T: type, address: usize) T {
const addr = Self.mirror(address);
return switch (T) {
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
else => @compileError("VRAM: Unsupported read width"),
};
}
pub fn write(self: *Self, comptime T: type, dispcnt: io.DisplayControl, address: usize, value: T) void {
const mode: u3 = dispcnt.bg_mode.read();
const idx = Self.mirror(address);
switch (T) {
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[idx..][0..@sizeOf(T)], value),
u8 => {
// Ignore write if it falls within the boundaries of OBJ VRAM
switch (mode) {
0, 1, 2 => if (0x0001_0000 <= idx) return,
else => if (0x0001_4000 <= idx) return,
}
const align_idx = idx & ~@as(u32, 1); // Aligned to a halfword boundary
std.mem.writeIntSliceLittle(u16, self.buf[align_idx..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
},
else => @compileError("VRAM: Unsupported write width"),
}
}
pub fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, buf_len);
std.mem.set(u8, buf, 0);
return Self{ .buf = buf, .allocator = allocator };
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.buf);
self.* = undefined;
}
fn mirror(address: usize) usize {
// Mirrored in steps of 128K (64K + 32K + 32K) (abcc)
const addr = address & 0x1FFFF;
// If the address is within 96K we don't do anything,
// otherwise we want to mirror the last 32K (addresses between 64K and 96K)
return if (addr < buf_len) addr else 0x10000 + (addr & 0x7FFF);
}

View File

@ -3,6 +3,8 @@ const builtin = @import("builtin");
const Log2Int = std.math.Log2Int; const Log2Int = std.math.Log2Int;
const Arm7tdmi = @import("cpu.zig").Arm7tdmi; const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
const Allocator = std.mem.Allocator;
const allow_unhandled_io = @import("emu.zig").allow_unhandled_io; const allow_unhandled_io = @import("emu.zig").allow_unhandled_io;
// Sign-Extend value of type `T` to type `U` // Sign-Extend value of type `T` to type `U`
@ -172,6 +174,7 @@ pub fn writeUndefined(log: anytype, comptime format: []const u8, args: anytype)
pub const Logger = struct { pub const Logger = struct {
const Self = @This(); const Self = @This();
const FmtArgTuple = std.meta.Tuple(&.{ u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32 });
buf: std.io.BufferedWriter(4096 << 2, std.fs.File.Writer), buf: std.io.BufferedWriter(4096 << 2, std.fs.File.Writer),
@ -229,4 +232,43 @@ 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 }); // Double Buffering Implementation
pub const FrameBuffer = struct {
const Self = @This();
layers: [2][]u8,
buf: []u8,
current: u1,
allocator: Allocator,
// TODO: Rename
const Device = enum { Emulator, Renderer };
pub fn init(allocator: Allocator, comptime len: comptime_int) !Self {
const buf = try allocator.alloc(u8, len * 2);
std.mem.set(u8, buf, 0);
return .{
// Front and Back Framebuffers
.layers = [_][]u8{ buf[0..][0..len], buf[len..][0..len] },
.buf = buf,
.current = 0,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.buf);
self.* = undefined;
}
pub fn swap(self: *Self) void {
self.current = ~self.current;
}
pub fn get(self: *Self, comptime dev: Device) []u8 {
return self.layers[if (dev == .Emulator) self.current else ~self.current];
}
};