chore: implement a non-working mode 0
This commit is contained in:
parent
64b1bdbe19
commit
16233f3cd8
|
@ -110,7 +110,7 @@ fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, v
|
||||||
0b00 => writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value),
|
0b00 => writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value),
|
||||||
else => self.shr_wram.write(T, .nds7, aligned_addr, value),
|
else => self.shr_wram.write(T, .nds7, aligned_addr, value),
|
||||||
},
|
},
|
||||||
0x0380_0000...0x0380_FFFF => writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value),
|
0x0380_0000...0x03FF_FFFF => writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value),
|
||||||
0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value),
|
0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value),
|
||||||
0x0600_0000...0x06FF_FFFF => self.vram.write(T, .nds7, aligned_addr, value),
|
0x0600_0000...0x06FF_FFFF => self.vram.write(T, .nds7, aligned_addr, value),
|
||||||
else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }),
|
else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }),
|
||||||
|
|
|
@ -63,7 +63,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
||||||
0x0400_0214 => bus.io.irq.raw,
|
0x0400_0214 => bus.io.irq.raw,
|
||||||
|
|
||||||
0x0410_0000 => bus.io.shr.ipc.recv(.nds7),
|
0x0410_0000 => bus.io.shr.ipc.recv(.nds7),
|
||||||
else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
else => 0, // warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
||||||
},
|
},
|
||||||
u16 => switch (address) {
|
u16 => switch (address) {
|
||||||
0x0400_0004 => bus.io.ppu.?.nds7.dispstat.raw,
|
0x0400_0004 => bus.io.ppu.?.nds7.dispstat.raw,
|
||||||
|
@ -76,7 +76,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
||||||
0x0400_0130 => bus.io.shr.keyinput.load(.Monotonic),
|
0x0400_0130 => bus.io.shr.keyinput.load(.Monotonic),
|
||||||
0x0400_0180 => @truncate(bus.io.shr.ipc._nds7.sync.raw),
|
0x0400_0180 => @truncate(bus.io.shr.ipc._nds7.sync.raw),
|
||||||
0x0400_0184 => @truncate(bus.io.shr.ipc._nds7.cnt.raw),
|
0x0400_0184 => @truncate(bus.io.shr.ipc._nds7.cnt.raw),
|
||||||
else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
else => 0, // warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
||||||
},
|
},
|
||||||
u8 => switch (address) {
|
u8 => switch (address) {
|
||||||
// DMA Transfers
|
// DMA Transfers
|
||||||
|
@ -89,7 +89,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
||||||
0x0400_0241 => bus.io.shr.wramcnt.raw,
|
0x0400_0241 => bus.io.shr.wramcnt.raw,
|
||||||
|
|
||||||
0x0400_0300 => @intFromEnum(bus.io.postflg),
|
0x0400_0300 => @intFromEnum(bus.io.postflg),
|
||||||
else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
else => 0, // warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
||||||
},
|
},
|
||||||
else => @compileError(T ++ " is an unsupported bus read type"),
|
else => @compileError(T ++ " is an unsupported bus read type"),
|
||||||
};
|
};
|
||||||
|
@ -110,7 +110,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_0214 => bus.io.irq.raw &= ~value,
|
0x0400_0214 => bus.io.irq.raw &= ~value,
|
||||||
|
|
||||||
0x0400_0188 => bus.io.shr.ipc.send(.nds7, value),
|
0x0400_0188 => bus.io.shr.ipc.send(.nds7, value),
|
||||||
else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }),
|
else => {}, // log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }),
|
||||||
},
|
},
|
||||||
u16 => switch (address) {
|
u16 => switch (address) {
|
||||||
// DMA Transfers
|
// DMA Transfers
|
||||||
|
@ -123,7 +123,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds7, value),
|
0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds7, value),
|
||||||
|
|
||||||
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
||||||
else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>4})", .{ T, address, value }),
|
else => {}, // log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>4})", .{ T, address, value }),
|
||||||
},
|
},
|
||||||
u8 => switch (address) {
|
u8 => switch (address) {
|
||||||
// DMA Transfers
|
// DMA Transfers
|
||||||
|
@ -133,7 +133,16 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_0100...0x0400_010F => log.warn("TODO: impl timer", .{}),
|
0x0400_0100...0x0400_010F => log.warn("TODO: impl timer", .{}),
|
||||||
|
|
||||||
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
||||||
else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>2})", .{ T, address, value }),
|
|
||||||
|
0x0400_0301 => switch ((value >> 6) & 0b11) {
|
||||||
|
0b00 => bus.io.haltcnt = .execute,
|
||||||
|
0b10 => bus.io.haltcnt = .halt,
|
||||||
|
else => |val| {
|
||||||
|
const tag: Haltcnt = @enumFromInt(val);
|
||||||
|
log.err("TODO: Implement {}", .{tag});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
else => {}, // log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>2})", .{ T, address, value }),
|
||||||
},
|
},
|
||||||
else => @compileError(T ++ " is an unsupported bus write type"),
|
else => @compileError(T ++ " is an unsupported bus write type"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ const log = std.log.scoped(.nds9_bus);
|
||||||
|
|
||||||
main: *[4 * MiB]u8,
|
main: *[4 * MiB]u8,
|
||||||
wram: *Wram,
|
wram: *Wram,
|
||||||
|
makeshift_palram: *[2 * KiB]u8,
|
||||||
scheduler: *Scheduler,
|
scheduler: *Scheduler,
|
||||||
|
|
||||||
io: io.Io,
|
io: io.Io,
|
||||||
|
@ -31,6 +32,7 @@ pub fn init(allocator: Allocator, scheduler: *Scheduler, ctx: SharedCtx) !@This(
|
||||||
return .{
|
return .{
|
||||||
.main = ctx.main,
|
.main = ctx.main,
|
||||||
.wram = ctx.wram,
|
.wram = ctx.wram,
|
||||||
|
.makeshift_palram = try allocator.create([2 * KiB]u8),
|
||||||
.ppu = try Ppu.init(allocator, ctx.vram),
|
.ppu = try Ppu.init(allocator, ctx.vram),
|
||||||
.scheduler = scheduler,
|
.scheduler = scheduler,
|
||||||
.io = io.Io.init(ctx.io),
|
.io = io.Io.init(ctx.io),
|
||||||
|
@ -42,6 +44,8 @@ pub fn init(allocator: Allocator, scheduler: *Scheduler, ctx: SharedCtx) !@This(
|
||||||
pub fn deinit(self: *@This(), allocator: Allocator) void {
|
pub fn deinit(self: *@This(), allocator: Allocator) void {
|
||||||
self.ppu.deinit(allocator);
|
self.ppu.deinit(allocator);
|
||||||
self.bios.deinit(allocator);
|
self.bios.deinit(allocator);
|
||||||
|
|
||||||
|
allocator.destroy(self.makeshift_palram);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(_: *@This()) void {
|
pub fn reset(_: *@This()) void {
|
||||||
|
@ -72,6 +76,7 @@ fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T
|
||||||
0x0200_0000...0x02FF_FFFF => readInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count]),
|
0x0200_0000...0x02FF_FFFF => readInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count]),
|
||||||
0x0300_0000...0x03FF_FFFF => self.wram.read(T, .nds9, aligned_addr),
|
0x0300_0000...0x03FF_FFFF => self.wram.read(T, .nds9, aligned_addr),
|
||||||
0x0400_0000...0x04FF_FFFF => io.read(self, T, aligned_addr),
|
0x0400_0000...0x04FF_FFFF => io.read(self, T, aligned_addr),
|
||||||
|
0x0500_0000...0x05FF_FFFF => readInt(T, self.makeshift_palram[aligned_addr & (2 * KiB - 1) ..][0..@sizeOf(T)]),
|
||||||
0x0600_0000...0x06FF_FFFF => self.ppu.vram.read(T, .nds9, aligned_addr),
|
0x0600_0000...0x06FF_FFFF => self.ppu.vram.read(T, .nds9, aligned_addr),
|
||||||
0xFFFF_0000...0xFFFF_FFFF => self.bios.read(T, address),
|
0xFFFF_0000...0xFFFF_FFFF => self.bios.read(T, address),
|
||||||
else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
||||||
|
@ -102,6 +107,7 @@ fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, v
|
||||||
0x0200_0000...0x02FF_FFFF => writeInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], value),
|
0x0200_0000...0x02FF_FFFF => writeInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], value),
|
||||||
0x0300_0000...0x03FF_FFFF => self.wram.write(T, .nds9, aligned_addr, value),
|
0x0300_0000...0x03FF_FFFF => self.wram.write(T, .nds9, aligned_addr, value),
|
||||||
0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value),
|
0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value),
|
||||||
|
0x0500_0000...0x05FF_FFFF => writeInt(T, self.makeshift_palram[aligned_addr & (2 * KiB - 1) ..][0..@sizeOf(T)], value),
|
||||||
0x0600_0000...0x06FF_FFFF => self.ppu.vram.write(T, .nds9, aligned_addr, value),
|
0x0600_0000...0x06FF_FFFF => self.ppu.vram.write(T, .nds9, aligned_addr, value),
|
||||||
0xFFFF_0000...0xFFFF_FFFF => self.bios.write(T, address, value),
|
0xFFFF_0000...0xFFFF_FFFF => self.bios.write(T, address, value),
|
||||||
else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }),
|
else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }),
|
||||||
|
|
|
@ -62,6 +62,8 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
||||||
0x0400_02A8, 0x0400_02AC => @truncate(bus.io.div.remainder >> shift(u64, address)),
|
0x0400_02A8, 0x0400_02AC => @truncate(bus.io.div.remainder >> shift(u64, address)),
|
||||||
0x0400_02B4 => @truncate(bus.io.sqrt.result),
|
0x0400_02B4 => @truncate(bus.io.sqrt.result),
|
||||||
|
|
||||||
|
0x0400_1000 => bus.ppu.engines[1].dispcnt.raw,
|
||||||
|
|
||||||
0x0410_0000 => bus.io.shr.ipc.recv(.nds9),
|
0x0410_0000 => bus.io.shr.ipc.recv(.nds9),
|
||||||
|
|
||||||
0x0400_4000, 0x0400_4008 => 0x0000_0000, // Lets software know this is NOT a DSi
|
0x0400_4000, 0x0400_4008 => 0x0000_0000, // Lets software know this is NOT a DSi
|
||||||
|
@ -84,6 +86,20 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
||||||
0x0400_0280 => @truncate(bus.io.div.cnt.raw),
|
0x0400_0280 => @truncate(bus.io.div.cnt.raw),
|
||||||
0x0400_02B0 => @truncate(bus.io.sqrt.cnt.raw),
|
0x0400_02B0 => @truncate(bus.io.sqrt.cnt.raw),
|
||||||
|
|
||||||
|
0x0400_1008 => bus.ppu.engines[1].bg[0].cnt.raw,
|
||||||
|
0x0400_100A => bus.ppu.engines[1].bg[1].cnt.raw,
|
||||||
|
0x0400_100C => bus.ppu.engines[1].bg[2].cnt.raw,
|
||||||
|
0x0400_100E => bus.ppu.engines[1].bg[3].cnt.raw,
|
||||||
|
|
||||||
|
0x0400_1010 => bus.ppu.engines[1].bg[0].hofs.raw,
|
||||||
|
0x0400_1012 => bus.ppu.engines[1].bg[0].vofs.raw,
|
||||||
|
0x0400_1014 => bus.ppu.engines[1].bg[1].hofs.raw,
|
||||||
|
0x0400_1016 => bus.ppu.engines[1].bg[1].vofs.raw,
|
||||||
|
0x0400_1018 => bus.ppu.engines[1].bg[2].hofs.raw,
|
||||||
|
0x0400_101A => bus.ppu.engines[1].bg[2].vofs.raw,
|
||||||
|
0x0400_101C => bus.ppu.engines[1].bg[3].hofs.raw,
|
||||||
|
0x0400_101E => bus.ppu.engines[1].bg[3].vofs.raw,
|
||||||
|
|
||||||
else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }),
|
||||||
},
|
},
|
||||||
u8 => switch (address) {
|
u8 => switch (address) {
|
||||||
|
@ -108,6 +124,7 @@ const subset = @import("../../util.zig").subset;
|
||||||
pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
switch (T) {
|
switch (T) {
|
||||||
u32 => switch (address) {
|
u32 => switch (address) {
|
||||||
|
|
||||||
// DMA Transfers
|
// DMA Transfers
|
||||||
0x0400_00B0...0x0400_00DC => log.warn("TODO: impl DMA", .{}),
|
0x0400_00B0...0x0400_00DC => log.warn("TODO: impl DMA", .{}),
|
||||||
0x0400_00E0...0x0400_00EC => log.warn("TODO: impl DMA fill", .{}),
|
0x0400_00E0...0x0400_00EC => log.warn("TODO: impl DMA fill", .{}),
|
||||||
|
@ -179,6 +196,20 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
|
|
||||||
0x0400_0304 => bus.ppu.io.powcnt.raw = value,
|
0x0400_0304 => bus.ppu.io.powcnt.raw = value,
|
||||||
|
|
||||||
|
0x0400_1008 => bus.ppu.engines[1].bg[0].cnt.raw = value,
|
||||||
|
0x0400_100A => bus.ppu.engines[1].bg[1].cnt.raw = value,
|
||||||
|
0x0400_100C => bus.ppu.engines[1].bg[2].cnt.raw = value,
|
||||||
|
0x0400_100E => bus.ppu.engines[1].bg[3].cnt.raw = value,
|
||||||
|
|
||||||
|
0x0400_1010 => bus.ppu.engines[1].bg[0].hofs.raw = value,
|
||||||
|
0x0400_1012 => bus.ppu.engines[1].bg[0].vofs.raw = value,
|
||||||
|
0x0400_1014 => bus.ppu.engines[1].bg[1].hofs.raw = value,
|
||||||
|
0x0400_1016 => bus.ppu.engines[1].bg[1].vofs.raw = value,
|
||||||
|
0x0400_1018 => bus.ppu.engines[1].bg[2].hofs.raw = value,
|
||||||
|
0x0400_101A => bus.ppu.engines[1].bg[2].vofs.raw = value,
|
||||||
|
0x0400_101C => bus.ppu.engines[1].bg[3].hofs.raw = value,
|
||||||
|
0x0400_101E => bus.ppu.engines[1].bg[3].vofs.raw = value,
|
||||||
|
|
||||||
else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>4})", .{ T, address, value }),
|
else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>4})", .{ T, address, value }),
|
||||||
},
|
},
|
||||||
u8 => switch (address) {
|
u8 => switch (address) {
|
||||||
|
@ -400,7 +431,7 @@ pub const DispcntA = extern union {
|
||||||
tile_obj_1d_boundary: Bitfield(u32, 20, 2),
|
tile_obj_1d_boundary: Bitfield(u32, 20, 2),
|
||||||
bitmap_obj_1d_boundary: Bit(u32, 22),
|
bitmap_obj_1d_boundary: Bit(u32, 22),
|
||||||
obj_during_hblank: Bit(u32, 23),
|
obj_during_hblank: Bit(u32, 23),
|
||||||
character_base: Bitfield(u32, 24, 3),
|
char_base: Bitfield(u32, 24, 3),
|
||||||
screen_base: Bitfield(u32, 27, 3),
|
screen_base: Bitfield(u32, 27, 3),
|
||||||
bg_ext_pal_enable: Bit(u32, 30),
|
bg_ext_pal_enable: Bit(u32, 30),
|
||||||
obj_ext_pal_enable: Bit(u32, 31),
|
obj_ext_pal_enable: Bit(u32, 31),
|
||||||
|
@ -476,3 +507,23 @@ pub const Dispstat = extern union {
|
||||||
lyc: Bitfield(u16, 7, 9),
|
lyc: Bitfield(u16, 7, 9),
|
||||||
raw: u16,
|
raw: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Bgcnt = extern union {
|
||||||
|
priority: Bitfield(u16, 0, 2),
|
||||||
|
char_base: Bitfield(u16, 2, 4),
|
||||||
|
mosaic_enable: Bit(u16, 6),
|
||||||
|
colour_mode: Bit(u16, 7),
|
||||||
|
screen_base: Bitfield(u16, 8, 5),
|
||||||
|
display_overflow: Bit(u16, 13),
|
||||||
|
size: Bitfield(u16, 14, 2),
|
||||||
|
raw: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Write Only
|
||||||
|
const BackgroundOffset = extern union {
|
||||||
|
offset: Bitfield(u16, 0, 9),
|
||||||
|
raw: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Hofs = BackgroundOffset;
|
||||||
|
pub const Vofs = BackgroundOffset;
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub const Ppu = struct {
|
||||||
|
|
||||||
vram: *Vram,
|
vram: *Vram,
|
||||||
|
|
||||||
engines: struct { EngineA, EngineB } = .{ .{}, .{} },
|
engines: struct { EngineA, EngineB },
|
||||||
|
|
||||||
io: Io = .{},
|
io: Io = .{},
|
||||||
|
|
||||||
|
@ -44,20 +44,22 @@ pub const Ppu = struct {
|
||||||
pub fn init(allocator: Allocator, vram: *Vram) !@This() {
|
pub fn init(allocator: Allocator, vram: *Vram) !@This() {
|
||||||
return .{
|
return .{
|
||||||
.fb = try FrameBuffer.init(allocator),
|
.fb = try FrameBuffer.init(allocator),
|
||||||
|
.engines = .{ try EngineA.init(allocator), try EngineB.init(allocator) },
|
||||||
.vram = vram,
|
.vram = vram,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: @This(), allocator: Allocator) void {
|
pub fn deinit(self: @This(), allocator: Allocator) void {
|
||||||
self.fb.deinit(allocator);
|
self.fb.deinit(allocator);
|
||||||
|
inline for (self.engines) |eng| eng.deinit(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawScanline(self: *@This(), bus: *System.Bus9) void {
|
pub fn drawScanline(self: *@This(), bus: *System.Bus9) void {
|
||||||
if (self.io.powcnt.engine2d_a.read())
|
if (self.io.powcnt.engine2d_a.read())
|
||||||
self.engines[0].drawScanline(bus, &self.fb, &self.io);
|
self.engines[0].drawScanline(bus, &self.fb);
|
||||||
|
|
||||||
if (self.io.powcnt.engine2d_b.read())
|
if (self.io.powcnt.engine2d_b.read())
|
||||||
self.engines[1].drawScanline(bus, &self.fb, &self.io);
|
self.engines[1].drawScanline(bus, &self.fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HDraw -> HBlank
|
/// HDraw -> HBlank
|
||||||
|
|
|
@ -11,14 +11,15 @@ const Ppu = @import("../ppu.zig").Ppu;
|
||||||
const width = @import("../ppu.zig").screen_width;
|
const width = @import("../ppu.zig").screen_width;
|
||||||
const height = @import("../ppu.zig").screen_height;
|
const height = @import("../ppu.zig").screen_height;
|
||||||
|
|
||||||
|
const KiB = 0x400;
|
||||||
|
|
||||||
const EngineKind = enum { a, b };
|
const EngineKind = enum { a, b };
|
||||||
|
|
||||||
pub const EngineA = Engine(.a);
|
pub const EngineA = Engine(.a);
|
||||||
pub const EngineB = Engine(.b);
|
pub const EngineB = Engine(.b);
|
||||||
|
|
||||||
fn Engine(comptime kind: EngineKind) type {
|
fn Engine(comptime kind: EngineKind) type {
|
||||||
const log = std.log.scoped(.engine2d);
|
const log = std.log.scoped(.engine2d); // TODO: specify between 2D-A and 2D-B
|
||||||
_ = log; // TODO: specify between 2D-A and 2D-B
|
|
||||||
|
|
||||||
// FIXME: don't commit zig crimes
|
// FIXME: don't commit zig crimes
|
||||||
const Type = struct {
|
const Type = struct {
|
||||||
|
@ -30,25 +31,79 @@ fn Engine(comptime kind: EngineKind) type {
|
||||||
return struct {
|
return struct {
|
||||||
dispcnt: Type(DispcntA, DispcntB) = .{ .raw = 0x0000_0000 },
|
dispcnt: Type(DispcntA, DispcntB) = .{ .raw = 0x0000_0000 },
|
||||||
|
|
||||||
pub fn drawScanline(self: *@This(), bus: *Bus, fb: *FrameBuffer, io: *Ppu.Io) void {
|
bg: [4]bg.Text = .{ .{}, .{}, .{}, .{} },
|
||||||
|
|
||||||
|
// TODO: Rename
|
||||||
|
scanline: Scanline,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !@This() {
|
||||||
|
return .{
|
||||||
|
.scanline = try Scanline.init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: @This(), allocator: std.mem.Allocator) void {
|
||||||
|
self.scanline.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawScanline(self: *@This(), bus: *Bus, fb: *FrameBuffer) void {
|
||||||
const disp_mode = self.dispcnt.display_mode.read();
|
const disp_mode = self.dispcnt.display_mode.read();
|
||||||
|
|
||||||
switch (disp_mode) {
|
switch (disp_mode) {
|
||||||
0 => { // Display Off
|
0 => { // Display Off
|
||||||
const buf = switch (kind) {
|
const buf = switch (kind) {
|
||||||
.a => if (io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back),
|
.a => if (bus.ppu.io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back),
|
||||||
.b => if (io.powcnt.display_swap.read()) fb.btm(.back) else fb.top(.back),
|
.b => if (bus.ppu.io.powcnt.display_swap.read()) fb.btm(.back) else fb.top(.back),
|
||||||
};
|
};
|
||||||
|
|
||||||
@memset(buf, 0xFF); // set everything to white
|
@memset(buf, 0xFF); // set everything to white
|
||||||
},
|
},
|
||||||
1 => @panic("TODO: standard graphics display (text mode, etc)"),
|
1 => {
|
||||||
|
const bg_mode = self.dispcnt.bg_mode.read();
|
||||||
|
const bg_enable = self.dispcnt.bg_enable.read();
|
||||||
|
const scanline: u32 = bus.ppu.io.nds9.vcount.scanline.read();
|
||||||
|
|
||||||
|
switch (bg_mode) {
|
||||||
|
// BG0 BG1 BG2 BG3
|
||||||
|
// Text/3D Text Text Text
|
||||||
|
0 => {
|
||||||
|
// TODO: Fetch Sprites
|
||||||
|
|
||||||
|
for (0..4) |layer| {
|
||||||
|
// TODO: Draw Sprites
|
||||||
|
|
||||||
|
inline for (0..4) |i| {
|
||||||
|
if (layer == self.bg[i].cnt.priority.read() and (bg_enable >> i) & 1 == 1)
|
||||||
|
self.drawBackground(i, bus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: IN ZBA we calculate the base address of the emu framebuffer to pass in
|
||||||
|
|
||||||
|
const buf = switch (kind) {
|
||||||
|
.a => if (bus.ppu.io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back),
|
||||||
|
.b => if (bus.ppu.io.powcnt.display_swap.read()) fb.btm(.back) else fb.top(.back),
|
||||||
|
};
|
||||||
|
|
||||||
|
const scanline_buf = blk: {
|
||||||
|
const rgba_ptr: *[width * height]u32 = @ptrCast(@alignCast(buf));
|
||||||
|
break :blk rgba_ptr[width * scanline ..][0..width];
|
||||||
|
};
|
||||||
|
|
||||||
|
self.renderTextMode(scanline_buf);
|
||||||
|
},
|
||||||
|
else => |mode| {
|
||||||
|
log.err("TODO: Implement Mode {}", .{mode});
|
||||||
|
@panic("fatal error");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
2 => { // VRAM display
|
2 => { // VRAM display
|
||||||
if (kind == .b) return;
|
if (kind == .b) return;
|
||||||
// TODO: Master Brightness can still affect this mode
|
// TODO: Master Brightness can still affect this mode
|
||||||
|
|
||||||
const scanline: u32 = io.nds9.vcount.scanline.read();
|
const scanline: u32 = bus.ppu.io.nds9.vcount.scanline.read();
|
||||||
const buf = if (io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back);
|
const buf = if (bus.ppu.io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back);
|
||||||
|
|
||||||
const scanline_buf = blk: {
|
const scanline_buf = blk: {
|
||||||
const rgba_ptr: *[width * height]u32 = @ptrCast(@alignCast(buf));
|
const rgba_ptr: *[width * height]u32 = @ptrCast(@alignCast(buf));
|
||||||
|
@ -69,9 +124,210 @@ fn Engine(comptime kind: EngineKind) type {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drawBackground(self: *@This(), comptime layer: u2, bus: *Bus) void {
|
||||||
|
const screen_base = blk: {
|
||||||
|
const bgcnt_off: u32 = self.bg[layer].cnt.screen_base.read();
|
||||||
|
const dispcnt_off: u32 = if (kind == .a) self.dispcnt.screen_base.read() else 0;
|
||||||
|
|
||||||
|
break :blk (2 * KiB) * bgcnt_off + (64 * KiB) * dispcnt_off;
|
||||||
|
};
|
||||||
|
|
||||||
|
const char_base = blk: {
|
||||||
|
const bgcnt_off: u32 = self.bg[layer].cnt.char_base.read();
|
||||||
|
const dispcnt_off: u32 = if (kind == .a) self.dispcnt.char_base.read() else 0;
|
||||||
|
|
||||||
|
break :blk (16 * KiB) * bgcnt_off + (64 * KiB) * dispcnt_off;
|
||||||
|
};
|
||||||
|
|
||||||
|
const is_8bpp = self.bg[layer].cnt.colour_mode.read();
|
||||||
|
const size = self.bg[layer].cnt.size.read();
|
||||||
|
|
||||||
|
// In 4bpp: 1 byte represents two pixels so the length is (8 x 8) / 2
|
||||||
|
// In 8bpp: 1 byte represents one pixel so the length is 8 x 8
|
||||||
|
const tile_len: u32 = if (is_8bpp) 0x40 else 0x20;
|
||||||
|
const tile_row_offset: u32 = if (is_8bpp) 0x8 else 0x4;
|
||||||
|
|
||||||
|
const vofs: u32 = self.bg[layer].vofs.offset.read();
|
||||||
|
const hofs: u32 = self.bg[layer].hofs.offset.read();
|
||||||
|
|
||||||
|
const y = vofs + bus.ppu.io.nds9.vcount.scanline.read();
|
||||||
|
|
||||||
|
for (0..width) |_i| {
|
||||||
|
const i: u32 = @intCast(_i);
|
||||||
|
const x = hofs + i;
|
||||||
|
|
||||||
|
// TODO: Windowing
|
||||||
|
|
||||||
|
// Grab the Screen Entry from VRAM
|
||||||
|
const entry_addr = 0x0600_0000 + screen_base + tilemapOffset(size, x, y);
|
||||||
|
const entry: bg.Screen.Entry = @bitCast(bus.read(u16, entry_addr));
|
||||||
|
|
||||||
|
// Calculate the Address of the Tile in the designated Charblock
|
||||||
|
// We also take this opportunity to flip tiles if necessary
|
||||||
|
const tile_id: u32 = entry.tile_id.read();
|
||||||
|
|
||||||
|
// Calculate row and column offsets. Understand that
|
||||||
|
// `tile_len`, `tile_row_offset` and `col` are subject to different
|
||||||
|
// values depending on whether we are in 4bpp or 8bpp mode.
|
||||||
|
const row = @as(u3, @truncate(y)) ^ if (entry.v_flip.read()) 7 else @as(u3, 0);
|
||||||
|
const col = @as(u3, @truncate(x)) ^ if (entry.h_flip.read()) 7 else @as(u3, 0);
|
||||||
|
const tile_addr = char_base + (tile_id * tile_len) + (row * tile_row_offset) + if (is_8bpp) col else col >> 1;
|
||||||
|
|
||||||
|
const tile = bus.read(u8, 0x0600_0000 + tile_addr);
|
||||||
|
// If we're in 8bpp, then the tile value is an index into the palette,
|
||||||
|
// If we're in 4bpp, we have to account for a pal bank value in the Screen entry
|
||||||
|
// and then we can index the palette
|
||||||
|
const pal_id: u16 = if (!is_8bpp) get4bppTilePalette(entry.pal_bank.read(), col, tile) else tile;
|
||||||
|
|
||||||
|
if (pal_id != 0) self.drawBackgroundPixel(layer, i, bus.read(u16, @as(u32, 0x0500_0000) + pal_id * 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn get4bppTilePalette(pal_bank: u4, col: u3, tile: u8) u8 {
|
||||||
|
const nybble_tile = tile >> ((col & 1) << 2) & 0xF;
|
||||||
|
if (nybble_tile == 0) return 0;
|
||||||
|
|
||||||
|
return (@as(u8, pal_bank) << 4) | nybble_tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderTextMode(self: *@This(), frame_buf: []u32) void {
|
||||||
|
for (self.scanline.top(), 0..) |maybe_top, i| {
|
||||||
|
const maybe_btm = self.scanline.btm()[i];
|
||||||
|
_ = maybe_btm;
|
||||||
|
|
||||||
|
const bgr555 = switch (maybe_top) {
|
||||||
|
.set => |px| px,
|
||||||
|
else => 0xAAAA,
|
||||||
|
};
|
||||||
|
|
||||||
|
frame_buf[i] = rgba888(bgr555);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scanline.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Comment this + get a better understanding
|
||||||
|
fn tilemapOffset(size: u2, x: u32, y: u32) u32 {
|
||||||
|
// Current Row: (y % PIXEL_COUNT) / 8
|
||||||
|
// Current COlumn: (x % PIXEL_COUNT) / 8
|
||||||
|
// Length of 1 row of Screen Entries: 0x40
|
||||||
|
// Length of 1 Screen Entry: 0x2 is the size of a screen entry
|
||||||
|
@setRuntimeSafety(false);
|
||||||
|
|
||||||
|
return switch (size) {
|
||||||
|
0 => (x % 256 / 8) * 2 + (y % 256 / 8) * 0x40, // 256 x 256
|
||||||
|
1 => blk: {
|
||||||
|
// 512 x 256
|
||||||
|
const offset: u32 = if (x & 0x1FF > 0xFF) 0x800 else 0;
|
||||||
|
break :blk offset + (x % 256 / 8) * 2 + (y % 256 / 8) * 0x40;
|
||||||
|
},
|
||||||
|
2 => blk: {
|
||||||
|
// 256 x 512
|
||||||
|
const offset: u32 = if (y & 0x1FF > 0xFF) 0x800 else 0;
|
||||||
|
break :blk offset + (x % 256 / 8) * 2 + (y % 256 / 8) * 0x40;
|
||||||
|
},
|
||||||
|
3 => blk: {
|
||||||
|
// 512 x 512
|
||||||
|
const offset: u32 = if (x & 0x1FF > 0xFF) 0x800 else 0;
|
||||||
|
const offset_2: u32 = if (y & 0x1FF > 0xFF) 0x800 else 0;
|
||||||
|
break :blk offset + offset_2 + (x % 256 / 8) * 2 + (y % 512 / 8) * 0x40;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drawBackgroundPixel(self: *@This(), comptime layer: u2, i: u32, bgr555: u16) void {
|
||||||
|
_ = layer;
|
||||||
|
|
||||||
|
self.scanline.top()[i] = Scanline.Pixel.from(.Background, bgr555);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Scanline = struct {
|
||||||
|
const Pixel = union(enum) {
|
||||||
|
// TODO: Rename
|
||||||
|
const Layer = enum { Background, Sprite };
|
||||||
|
|
||||||
|
set: u16,
|
||||||
|
obj_set: u16,
|
||||||
|
unset: void,
|
||||||
|
hidden: void,
|
||||||
|
|
||||||
|
fn from(comptime layer: Layer, bgr555: u16) Pixel {
|
||||||
|
return switch (layer) {
|
||||||
|
.Background => .{ .set = bgr555 },
|
||||||
|
.Sprite => .{ .obj_set = bgr555 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isSet(self: @This()) bool {
|
||||||
|
return switch (self) {
|
||||||
|
.set, .obj_set => true,
|
||||||
|
.unset, .hidden => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
layers: [2][]Pixel,
|
||||||
|
buf: []Pixel,
|
||||||
|
|
||||||
|
fn init(allocator: std.mem.Allocator) !@This() {
|
||||||
|
const buf = try allocator.alloc(Pixel, width * 2); // Top & Bottom Scanline
|
||||||
|
@memset(buf, .unset);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
// Top & Bototm Layers
|
||||||
|
.layers = [_][]Pixel{ buf[0..][0..width], buf[width..][0..width] },
|
||||||
|
.buf = buf,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(self: *@This()) void {
|
||||||
|
@memset(self.buf, .unset);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: @This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn top(self: *@This()) []Pixel {
|
||||||
|
return self.layers[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn btm(self: *@This()) []Pixel {
|
||||||
|
return self.layers[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const bg = struct {
|
||||||
|
const Text = struct {
|
||||||
|
const io = @import("../nds9/io.zig");
|
||||||
|
|
||||||
|
/// Read / Write
|
||||||
|
cnt: io.Bgcnt = .{ .raw = 0x0000 },
|
||||||
|
/// Write Only
|
||||||
|
hofs: io.Hofs = .{ .raw = 0x0000 },
|
||||||
|
/// Write Only
|
||||||
|
vofs: io.Vofs = .{ .raw = 0x0000 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const Screen = struct {
|
||||||
|
const Entry = extern union {
|
||||||
|
const Bitfield = @import("bitfield").Bitfield;
|
||||||
|
const Bit = @import("bitfield").Bit;
|
||||||
|
|
||||||
|
tile_id: Bitfield(u16, 0, 10),
|
||||||
|
h_flip: Bit(u16, 10),
|
||||||
|
v_flip: Bit(u16, 11),
|
||||||
|
pal_bank: Bitfield(u16, 12, 4),
|
||||||
|
raw: u16,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Affine = @compileError("TODO: Implement Affine Backgrounds");
|
||||||
|
};
|
||||||
|
|
||||||
inline fn rgba888(bgr555: u16) u32 {
|
inline fn rgba888(bgr555: u16) u32 {
|
||||||
const b: u32 = bgr555 >> 10 & 0x1F;
|
const b: u32 = bgr555 >> 10 & 0x1F;
|
||||||
const g: u32 = bgr555 >> 5 & 0x1F;
|
const g: u32 = bgr555 >> 5 & 0x1F;
|
||||||
|
|
Loading…
Reference in New Issue