feat(ppu): introduce concept of 2 graphics engines + refactor
This commit is contained in:
		| @@ -66,7 +66,7 @@ pub fn handle(self: *@This(), bus_ptr: ?*anyopaque, event: Event, late: u64) voi | ||||
|                     bus.ppu.onHdrawEnd(self, late); | ||||
|                 }, | ||||
|                 .hblank => bus.ppu.onHblankEnd(self, late), | ||||
|                 .vblank => bus.ppu.onHblankEnd(self, late), | ||||
|                 .vblank => bus.ppu.onVblankEnd(self, late), | ||||
|                 .sqrt => bus.io.sqrt.onSqrtCalc(), | ||||
|                 .div => bus.io.div.onDivCalc(), | ||||
|             } | ||||
|   | ||||
| @@ -132,7 +132,7 @@ pub const SharedCtx = struct { | ||||
|     const KiB = 0x400; | ||||
|  | ||||
|     pub const Io = @import("io.zig").Io; | ||||
|     const Vram = @import("ppu.zig").Vram; | ||||
|     const Vram = @import("ppu/Vram.zig"); | ||||
|  | ||||
|     io: *Io, | ||||
|     main: *[4 * MiB]u8, | ||||
|   | ||||
| @@ -4,7 +4,7 @@ const io = @import("io.zig"); | ||||
| const Scheduler = @import("../Scheduler.zig"); | ||||
| const SharedCtx = @import("../emu.zig").SharedCtx; | ||||
| const Wram = @import("../emu.zig").Wram; | ||||
| const Vram = @import("../ppu.zig").Vram; | ||||
| const Vram = @import("../ppu/Vram.zig"); | ||||
| const Bios = @import("Bios.zig"); | ||||
| const forceAlign = @import("../emu.zig").forceAlign; | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ const std = @import("std"); | ||||
|  | ||||
| const log = std.log.scoped(.cp15); | ||||
|  | ||||
| const panic_on_unimplemented: bool = true; | ||||
| const panic_on_unimplemented: bool = false; | ||||
|  | ||||
| control: u32 = 0x0005_2078, | ||||
| dtcm_size_base: u32 = 0x0300_000A, | ||||
|   | ||||
| @@ -34,10 +34,6 @@ pub const Io = struct { | ||||
|     /// Caller must cast the `u32` to either `nds7.IntRequest` or `nds9.IntRequest` | ||||
|     irq: IntRequest = .{ .raw = 0x0000_0000 }, | ||||
|  | ||||
|     /// POWCNT1 - Graphics Power Control | ||||
|     /// Read / Write | ||||
|     powcnt: PowCnt = .{ .raw = 0x0000_0000 }, | ||||
|  | ||||
|     // Read Only | ||||
|     keyinput: AtomicKeyInput = .{}, | ||||
|  | ||||
| @@ -82,7 +78,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T { | ||||
|             // Timers | ||||
|             0x0400_0100...0x0400_010E => warn("TODO: impl timer", .{}), | ||||
|  | ||||
|             0x0400_0004 => bus.ppu.io.dispstat.raw, | ||||
|             0x0400_0004 => bus.ppu.engines[0].dispstat.raw, | ||||
|             0x0400_0130 => bus.io.keyinput.load(.Monotonic), | ||||
|  | ||||
|             0x0400_0180 => @truncate(bus.io.shr.ipc._nds9.sync.raw), | ||||
| @@ -120,7 +116,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | ||||
|             // Timers | ||||
|             0x0400_0100...0x0400_010C => log.warn("TODO: impl timer", .{}), | ||||
|  | ||||
|             0x0400_0000 => bus.ppu.io.dispcnt_a.raw = value, | ||||
|             0x0400_0000 => bus.ppu.engines[0].dispcnt.raw = value, | ||||
|             0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds9, value), | ||||
|             0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds9, value), | ||||
|             0x0400_0188 => bus.io.shr.ipc.send(.nds9, value), | ||||
| @@ -151,7 +147,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | ||||
|                 bus.io.sqrt.schedule(bus.scheduler); | ||||
|             }, | ||||
|  | ||||
|             0x0400_0304 => bus.io.powcnt.raw = value, | ||||
|             0x0400_0304 => bus.ppu.io.powcnt.raw = value, | ||||
|  | ||||
|             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||
|         }, | ||||
| @@ -177,7 +173,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | ||||
|                 bus.io.sqrt.schedule(bus.scheduler); | ||||
|             }, | ||||
|  | ||||
|             0x0400_0304 => bus.io.powcnt.raw = value, | ||||
|             0x0400_0304 => bus.ppu.io.powcnt.raw = value, | ||||
|  | ||||
|             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>4})", .{ T, address, value }), | ||||
|         }, | ||||
| @@ -241,7 +237,7 @@ fn warn(comptime format: []const u8, args: anytype) u0 { | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| const PowCnt = extern union { | ||||
| pub const PowCnt = extern union { | ||||
|     // Enable flag for both LCDs | ||||
|     lcd: Bit(u32, 0), | ||||
|     engine2d_a: Bit(u32, 1), | ||||
| @@ -382,8 +378,7 @@ const SquareRootUnit = struct { | ||||
| }; | ||||
|  | ||||
| pub const DispcntA = extern union { | ||||
|     bg_mode: Bitfield(u32, 0, 2), | ||||
|  | ||||
|     bg_mode: Bitfield(u32, 0, 3), | ||||
|     /// toggle between 2D and 3D for BG0 | ||||
|     bg0_dimension: Bit(u32, 3), | ||||
|     tile_obj_mapping: Bit(u32, 4), | ||||
| @@ -400,7 +395,25 @@ pub const DispcntA = extern union { | ||||
|     bitmap_obj_1d_boundary: Bit(u32, 22), | ||||
|     obj_during_hblank: Bit(u32, 23), | ||||
|     character_base: Bitfield(u32, 24, 3), | ||||
|     screen_base: Bitfield(u32, 27, 2), | ||||
|     screen_base: Bitfield(u32, 27, 3), | ||||
|     bg_ext_pal_enable: Bit(u32, 30), | ||||
|     obj_ext_pal_enable: Bit(u32, 31), | ||||
|     raw: u32, | ||||
| }; | ||||
|  | ||||
| pub const DispcntB = extern union { | ||||
|     bg_mode: Bitfield(u32, 0, 3), | ||||
|     tile_obj_mapping: Bit(u32, 4), | ||||
|     bitmap_obj_2d_dimension: Bit(u32, 5), | ||||
|     bitmap_obj_mapping: Bit(u32, 6), | ||||
|     forced_blank: Bit(u32, 7), | ||||
|     bg_enable: Bitfield(u32, 8, 4), | ||||
|     obj_enable: Bit(u32, 12), | ||||
|     win_enable: Bitfield(u32, 13, 2), | ||||
|     obj_win_enable: Bit(u32, 15), | ||||
|     display_mode: Bitfield(u32, 16, 2), | ||||
|     tile_obj_1d_boundary: Bitfield(u32, 20, 2), | ||||
|     obj_during_hblank: Bit(u32, 23), | ||||
|     bg_ext_pal_enable: Bit(u32, 30), | ||||
|     obj_ext_pal_enable: Bit(u32, 31), | ||||
|     raw: u32, | ||||
|   | ||||
							
								
								
									
										421
									
								
								src/core/ppu.zig
									
									
									
									
									
								
							
							
						
						
									
										421
									
								
								src/core/ppu.zig
									
									
									
									
									
								
							| @@ -4,6 +4,10 @@ const Allocator = std.mem.Allocator; | ||||
| const Scheduler = @import("Scheduler.zig"); | ||||
| const System = @import("emu.zig").System; | ||||
|  | ||||
| const Vram = @import("ppu/Vram.zig"); | ||||
| const EngineA = @import("ppu/engine.zig").EngineA; | ||||
| const EngineB = @import("ppu/engine.zig").EngineB; | ||||
|  | ||||
| pub const screen_width = 256; | ||||
| pub const screen_height = 192; | ||||
| const KiB = 0x400; | ||||
| @@ -15,18 +19,14 @@ pub const Ppu = struct { | ||||
|  | ||||
|     vram: *Vram, | ||||
|  | ||||
|     engines: struct { EngineA, EngineB } = .{ .{}, .{} }, | ||||
|  | ||||
|     io: Io = .{}, | ||||
|  | ||||
|     const Io = struct { | ||||
|         const nds9 = @import("nds9/io.zig"); | ||||
|         const types = @import("nds9/io.zig"); | ||||
|  | ||||
|         /// Read / Write | ||||
|         dispcnt_a: nds9.DispcntA = .{ .raw = 0x0000_0000 }, | ||||
|         /// Read / Write | ||||
|         dispstat: nds9.Dispstat = .{ .raw = 0x0000 }, | ||||
|  | ||||
|         /// Read-Only | ||||
|         vcount: nds9.Vcount = .{ .raw = 0x0000 }, | ||||
|         powcnt: types.PowCnt = .{ .raw = 0x0000_0000 }, | ||||
|     }; | ||||
|  | ||||
|     pub fn init(allocator: Allocator, vram: *Vram) !@This() { | ||||
| @@ -41,64 +41,69 @@ pub const Ppu = struct { | ||||
|     } | ||||
|  | ||||
|     pub fn drawScanline(self: *@This(), bus: *System.Bus9) void { | ||||
|         const bg_mode = self.io.dispcnt_a.display_mode.read(); | ||||
|         const scanline = self.io.vcount.scanline.read(); | ||||
|         if (self.io.powcnt.engine2d_a.read()) | ||||
|             self.engines[0].drawScanline(bus, &self.fb, &self.io.powcnt); | ||||
|  | ||||
|         switch (bg_mode) { | ||||
|             0x0 => {}, | ||||
|             0x1 => {}, | ||||
|             0x2 => { | ||||
|                 // Draw Top Screen | ||||
|                 { | ||||
|                     const buf = self.fb.top(.back); | ||||
|  | ||||
|                     const ptr: *[screen_width * screen_height]u32 = @ptrCast(@alignCast(buf.ptr)); | ||||
|                     const scanline_ptr = ptr[screen_width * @as(u32, scanline) ..][0..screen_width]; | ||||
|  | ||||
|                     const base_addr: u32 = 0x0680_0000 + (screen_width * @sizeOf(u16)) * @as(u32, scanline); | ||||
|  | ||||
|                     // FIXME: I don't think it's okay to be accessing the ARM9 Bus instead of just working with | ||||
|                     // memory directly. However, I do understand that VRAM-A, VRAM-B might change things | ||||
|  | ||||
|                     for (scanline_ptr, 0..) |*rgba, i| { | ||||
|                         const addr = base_addr + @as(u32, @intCast(i)) * @sizeOf(u16); | ||||
|                         rgba.* = rgba888(bus.dbgRead(u16, addr)); | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             0x3 => {}, | ||||
|         } | ||||
|         if (self.io.powcnt.engine2d_b.read()) | ||||
|             self.engines[1].drawScanline(bus, &self.fb, &self.io.powcnt); | ||||
|     } | ||||
|  | ||||
|     /// HDraw -> HBlank | ||||
|     pub fn onHdrawEnd(self: *@This(), scheduler: *Scheduler, late: u64) void { | ||||
|         const dots_in_hblank = 99; | ||||
|         std.debug.assert(self.io.dispstat.hblank.read() == false); | ||||
|         std.debug.assert(self.io.dispstat.vblank.read() == false); | ||||
|         inline for (&self.engines) |*engine| { | ||||
|             std.debug.assert(engine.dispstat.hblank.read() == false); | ||||
|             std.debug.assert(engine.dispstat.vblank.read() == false); | ||||
|  | ||||
|             // TODO: Signal HBlank IRQ | ||||
|  | ||||
|         self.io.dispstat.hblank.set(); | ||||
|             engine.dispstat.hblank.set(); | ||||
|         } | ||||
|  | ||||
|         const dots_in_hblank = 99; | ||||
|         scheduler.push(.{ .nds9 = .hblank }, dots_in_hblank * cycles_per_dot -| late); | ||||
|     } | ||||
|  | ||||
|     // VBlank -> HBlank (Still VBlank) | ||||
|     pub fn onVblankEnd(self: *@This(), scheduler: *Scheduler, late: u64) void { | ||||
|         inline for (&self.engines) |*engine| { | ||||
|             std.debug.assert(!engine.dispstat.hblank.read()); | ||||
|             std.debug.assert(engine.vcount.scanline.read() == 262 or engine.dispstat.vblank.read()); | ||||
|  | ||||
|             // TODO: Signal HBlank IRQ | ||||
|  | ||||
|             engine.dispstat.hblank.set(); | ||||
|         } | ||||
|  | ||||
|         const dots_in_hblank = 99; | ||||
|         scheduler.push(.{ .nds9 = .hblank }, dots_in_hblank * cycles_per_dot -| late); | ||||
|     } | ||||
|  | ||||
|     /// HBlank -> HDraw / VBlank | ||||
|     pub fn onHblankEnd(self: *@This(), scheduler: *Scheduler, late: u64) void { | ||||
|         const scanline_count = 192 + 71; | ||||
|         const scanline_total = 263; // 192 visible, 71 blanking | ||||
|  | ||||
|         const prev_scanline = self.io.vcount.scanline.read(); | ||||
|         const scanline = (prev_scanline + 1) % scanline_count; | ||||
|         std.debug.assert(self.engines[0].vcount.scanline.read() == self.engines[1].vcount.scanline.read()); | ||||
|  | ||||
|         self.io.vcount.scanline.write(scanline); | ||||
|         self.io.dispstat.hblank.unset(); | ||||
|         inline for (&self.engines) |*engine| { | ||||
|             const prev_scanline = engine.vcount.scanline.read(); | ||||
|             const scanline = (prev_scanline + 1) % scanline_total; | ||||
|  | ||||
|         const coincidence = scanline == self.io.dispstat.lyc.read(); | ||||
|         self.io.dispstat.coincidence.write(coincidence); | ||||
|             engine.vcount.scanline.write(scanline); | ||||
|             engine.dispstat.hblank.unset(); | ||||
|  | ||||
|             const coincidence = scanline == engine.dispstat.lyc.read(); | ||||
|             engine.dispstat.coincidence.write(coincidence); | ||||
|         } | ||||
|  | ||||
|         const scanline = self.engines[0].vcount.scanline.read(); | ||||
|  | ||||
|         // TODO: LYC == LY IRQ | ||||
|  | ||||
|         if (scanline < 192) { | ||||
|             std.debug.assert(self.io.dispstat.vblank.read() == false); | ||||
|             std.debug.assert(self.io.dispstat.hblank.read() == false); | ||||
|             inline for (&self.engines) |*engine| { | ||||
|                 std.debug.assert(engine.dispstat.vblank.read() == false); | ||||
|                 std.debug.assert(engine.dispstat.hblank.read() == false); | ||||
|             } | ||||
|  | ||||
|             // Draw Another Scanline | ||||
|             const dots_in_hdraw = 256; | ||||
| @@ -108,16 +113,22 @@ pub const Ppu = struct { | ||||
|         if (scanline == 192) { | ||||
|             // Transition from Hblank to Vblank | ||||
|             self.fb.swap(); | ||||
|             self.io.dispstat.vblank.set(); | ||||
|  | ||||
|             inline for (&self.engines) |*engine| | ||||
|                 engine.dispstat.vblank.set(); | ||||
|  | ||||
|             // TODO: Signal VBlank IRQ | ||||
|         } | ||||
|  | ||||
|         if (scanline == 262) self.io.dispstat.vblank.unset(); | ||||
|         std.debug.assert(self.io.dispstat.vblank.read() == (scanline != 262)); | ||||
|         if (scanline == 262) { | ||||
|             inline for (&self.engines) |*engine| { | ||||
|                 engine.dispstat.vblank.unset(); | ||||
|                 std.debug.assert(engine.dispstat.vblank.read() == (scanline != 262)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const dots_in_scanline = 256 + 99; | ||||
|         scheduler.push(.{ .nds9 = .hblank }, dots_in_scanline * cycles_per_dot -| late); | ||||
|         const dots_in_vblank = 256; | ||||
|         scheduler.push(.{ .nds9 = .vblank }, dots_in_vblank * cycles_per_dot -| late); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| @@ -126,15 +137,15 @@ pub const FrameBuffer = struct { | ||||
|  | ||||
|     current: u1 = 0, | ||||
|  | ||||
|     ptr: *[len * 4]u8, | ||||
|     ptr: *align(@sizeOf(u32)) [len * 4]u8, | ||||
|  | ||||
|     const Position = enum { top, bottom }; | ||||
|     const Layer = enum { front, back }; | ||||
|  | ||||
|     pub fn init(allocator: Allocator) !@This() { | ||||
|         const ptr = try allocator.create([len * 4]u8); | ||||
|         const buf = try allocator.alignedAlloc(u8, @sizeOf(u32), len * 4); | ||||
|  | ||||
|         return .{ .ptr = ptr }; | ||||
|         return .{ .ptr = buf[0 .. len * 4] }; | ||||
|     } | ||||
|  | ||||
|     pub fn deinit(self: @This(), allocator: Allocator) void { | ||||
| @@ -162,303 +173,3 @@ pub const FrameBuffer = struct { | ||||
|         return self.get(.bottom, layer); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| inline fn rgba888(bgr555: u16) u32 { | ||||
|     const b: u32 = bgr555 >> 10 & 0x1F; | ||||
|     const g: u32 = bgr555 >> 5 & 0x1F; | ||||
|     const r: u32 = bgr555 & 0x1F; | ||||
|  | ||||
|     // zig fmt: off | ||||
|     return (r << 3 | r >> 2) << 24 | ||||
|         |  (g << 3 | g >> 2) << 16 | ||||
|         |  (b << 3 | b >> 2) << 8 | ||||
|         |  0xFF; | ||||
|     // zig fmt: on | ||||
| } | ||||
|  | ||||
| pub const Vram = struct { | ||||
|     const page_size = 16 * KiB; // smallest allocation is 16 KiB | ||||
|     const addr_space_size = 0x0100_0000; // 0x0600_0000 -> 0x06FF_FFFF (inclusive) | ||||
|     const table_len = addr_space_size / page_size; | ||||
|     const buf_len = 656 * KiB; | ||||
|  | ||||
|     const IntFittingRange = std.math.IntFittingRange; | ||||
|     const log = std.log.scoped(.vram); | ||||
|  | ||||
|     io: Io = .{}, | ||||
|  | ||||
|     _buf: *[buf_len]u8, | ||||
|     nds9_table: *const [table_len]?[*]u8, | ||||
|     nds7_table: *const [table_len]?[*]u8, | ||||
|  | ||||
|     const Io = struct { | ||||
|         const nds9 = @import("nds9/io.zig"); | ||||
|         const nds7 = @import("nds7/io.zig"); | ||||
|         pub const Vramstat = @import("nds7/io.zig").Vramstat; | ||||
|  | ||||
|         stat: nds7.Vramstat = .{ .raw = 0x00 }, | ||||
|  | ||||
|         /// Write-Only (according to melonDS these are readable lol) | ||||
|         cnt_a: nds9.Vramcnt.A = .{ .raw = 0x00 }, | ||||
|         cnt_b: nds9.Vramcnt.A = .{ .raw = 0x00 }, | ||||
|         cnt_c: nds9.Vramcnt.C = .{ .raw = 0x00 }, | ||||
|         cnt_d: nds9.Vramcnt.C = .{ .raw = 0x00 }, | ||||
|         cnt_e: nds9.Vramcnt.E = .{ .raw = 0x00 }, | ||||
|         cnt_f: nds9.Vramcnt.C = .{ .raw = 0x00 }, | ||||
|         cnt_g: nds9.Vramcnt.C = .{ .raw = 0x00 }, | ||||
|         cnt_h: nds9.Vramcnt.H = .{ .raw = 0x00 }, | ||||
|         cnt_i: nds9.Vramcnt.H = .{ .raw = 0x00 }, | ||||
|     }; | ||||
|  | ||||
|     pub fn init(self: *@This(), allocator: Allocator) !void { | ||||
|         const buf = try allocator.create([buf_len]u8); | ||||
|         errdefer allocator.destroy(buf); | ||||
|         @memset(buf, 0); | ||||
|  | ||||
|         const tables = try allocator.alloc(?[*]u8, 2 * table_len); | ||||
|         @memset(tables, null); | ||||
|  | ||||
|         self.* = .{ | ||||
|             .nds9_table = tables[0..table_len], | ||||
|             .nds7_table = tables[table_len .. 2 * table_len], | ||||
|             ._buf = buf, | ||||
|         }; | ||||
|  | ||||
|         // ROMS like redpanda.nds won't write to VRAMCNT before trying to write to VRAM | ||||
|         // therefore we assume some default allocation (in this casee VRAMCNT_A -> VRAMCNT_I are 0x00) | ||||
|         self.update(); | ||||
|     } | ||||
|  | ||||
|     pub fn deinit(self: @This(), allocator: Allocator) void { | ||||
|         allocator.destroy(self._buf); | ||||
|  | ||||
|         const ptr: [*]?[*]const u8 = @ptrCast(@constCast(self.nds9_table)); | ||||
|         allocator.free(ptr[0 .. 2 * table_len]); | ||||
|     } | ||||
|  | ||||
|     pub fn stat(self: *const @This()) Io.Vramstat { | ||||
|         const vram_c: u8 = @intFromBool(self.io.cnt_c.enable.read() and self.io.cnt_c.mst.read() == 2); | ||||
|         const vram_d: u8 = @intFromBool(self.io.cnt_d.enable.read() and self.io.cnt_d.mst.read() == 2); | ||||
|  | ||||
|         return .{ .raw = (vram_d << 1) | vram_c }; | ||||
|     } | ||||
|  | ||||
|     const Kind = enum { | ||||
|         a, | ||||
|         b, | ||||
|         c, | ||||
|         d, | ||||
|         e, | ||||
|         f, | ||||
|         g, | ||||
|         h, | ||||
|         i, | ||||
|  | ||||
|         /// In Bytes | ||||
|         inline fn size(self: @This()) u32 { | ||||
|             return switch (self) { | ||||
|                 .a => 128 * KiB, | ||||
|                 .b => 128 * KiB, | ||||
|                 .c => 128 * KiB, | ||||
|                 .d => 128 * KiB, | ||||
|                 .e => 64 * KiB, | ||||
|                 .f => 16 * KiB, | ||||
|                 .g => 16 * KiB, | ||||
|                 .h => 32 * KiB, | ||||
|                 .i => 16 * KiB, | ||||
|             }; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     // TODO: Rename | ||||
|     fn range(comptime kind: Kind, mst: u3, offset: u2) u32 { | ||||
|         const ofs: u32 = offset; | ||||
|         // panic messages are from GBATEK | ||||
|  | ||||
|         return switch (kind) { | ||||
|             .a => switch (mst) { | ||||
|                 0 => 0x0680_0000, | ||||
|                 1 => 0x0600_0000 + (0x0002_0000 * ofs), | ||||
|                 2 => 0x0640_0000 + (0x0002_0000 * (ofs & 0b01)), | ||||
|                 3 => @panic("VRAMCNT_A: Slot OFS(0-3)"), | ||||
|                 else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|             }, | ||||
|             .b => switch (mst) { | ||||
|                 0 => 0x0682_0000, | ||||
|                 1 => 0x0600_0000 + (0x0002_0000 * ofs), | ||||
|                 2 => 0x0640_0000 + (0x0002_0000 * (ofs & 0b01)), | ||||
|                 3 => @panic("VRAMCNT_B: Slot OFS(0-3)"), | ||||
|                 else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|             }, | ||||
|             .c => switch (mst) { | ||||
|                 0 => 0x0684_0000, | ||||
|                 1 => 0x0600_0000 + (0x0002_0000 * ofs), | ||||
|                 2 => 0x0600_0000 + (0x0002_0000 * (ofs & 0b01)), | ||||
|                 3 => @panic("VRAMCNT_C: Slot OFS(0-3)"), | ||||
|                 4 => 0x0620_0000, | ||||
|                 else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|             }, | ||||
|             .d => switch (mst) { | ||||
|                 0 => 0x0686_0000, | ||||
|                 1 => 0x0600_0000 + (0x0002_0000 * ofs), | ||||
|                 2 => 0x0600_0000 + (0x0002_0000 * (ofs & 0b01)), | ||||
|                 3 => @panic("VRAMCNT_D: Slot OFS(0-3)"), | ||||
|                 4 => 0x0660_0000, | ||||
|                 else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|             }, | ||||
|             .e => switch (mst) { | ||||
|                 0 => 0x0688_0000, | ||||
|                 1 => 0x0600_0000, | ||||
|                 2 => 0x0640_0000, | ||||
|                 3 => @panic("VRAMCNT_E: Slots 0-3"), | ||||
|                 else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|             }, | ||||
|             .f => switch (mst) { | ||||
|                 0 => 0x0689_0000, | ||||
|                 1 => 0x0600_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)), | ||||
|                 2 => 0x0640_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)), | ||||
|                 3 => @panic("VRAMCNT_F: Slot (OFS.0*1)+(OFS.1*4)"), | ||||
|                 4 => @panic("VRAMCNT_F: Slot 0-1 (OFS=0), Slot 2-3 (OFS=1)"), | ||||
|                 5 => @panic("VRAMCNT_F: Slot 0"), | ||||
|                 else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|             }, | ||||
|             .g => switch (mst) { | ||||
|                 0 => 0x0689_4000, | ||||
|                 1 => 0x0600_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)), | ||||
|                 2 => 0x0640_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)), | ||||
|                 3 => @panic("VRAMCNT_G: Slot (OFS.0*1)+(OFS.1*4)"), | ||||
|                 4 => @panic("VRAMCNT_G: Slot 0-1 (OFS=0), Slot 2-3 (OFS=1)"), | ||||
|                 5 => @panic("VRAMCNT_G: Slot 0"), | ||||
|                 else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|             }, | ||||
|             .h => switch (mst) { | ||||
|                 0 => 0x0689_8000, | ||||
|                 1 => 0x0620_0000, | ||||
|                 2 => @panic("VRAMCNT_H: Slot 0-3"), | ||||
|                 else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|             }, | ||||
|             .i => switch (mst) { | ||||
|                 0 => 0x068A_0000, | ||||
|                 1 => 0x0620_8000, | ||||
|                 2 => 0x0660_0000, | ||||
|                 3 => @panic("Slot 0"), | ||||
|                 else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     fn buf_offset(comptime kind: Kind) usize { | ||||
|         // zig fmt: off | ||||
|         return switch (kind) { | ||||
|             .a => 0,                                                            // 0x00000 | ||||
|             .b => (128 * KiB) * 1,                                              // 0x20000 (+ 0x20000) | ||||
|             .c => (128 * KiB) * 2,                                              // 0x40000 (+ 0x20000) | ||||
|             .d => (128 * KiB) * 3,                                              // 0x60000 (+ 0x20000) | ||||
|             .e => (128 * KiB) * 4,                                              // 0x80000 (+ 0x20000) | ||||
|             .f => (128 * KiB) * 4 + (64 * KiB),                                 // 0x90000 (+ 0x10000) | ||||
|             .g => (128 * KiB) * 4 + (64 * KiB) + (16 * KiB) * 1,                // 0x94000 (+ 0x04000) | ||||
|             .h => (128 * KiB) * 4 + (64 * KiB) + (16 * KiB) * 2,                // 0x98000 (+ 0x04000) | ||||
|             .i => (128 * KiB) * 4 + (64 * KiB) + (16 * KiB) * 2 + (32 * KiB)    // 0xA0000 (+ 0x08000) | ||||
|         }; | ||||
|         // zig fmt: on | ||||
|     } | ||||
|  | ||||
|     fn CntType(comptime kind: Kind) type { | ||||
|         const io = @import("nds9/io.zig"); | ||||
|  | ||||
|         return switch (kind) { | ||||
|             .a => io.Vramcnt.A, | ||||
|             .b => io.Vramcnt.A, | ||||
|             .c => io.Vramcnt.C, | ||||
|             .d => io.Vramcnt.C, | ||||
|             .e => io.Vramcnt.E, | ||||
|             .f => io.Vramcnt.C, | ||||
|             .g => io.Vramcnt.C, | ||||
|             .h => io.Vramcnt.H, | ||||
|             .i => io.Vramcnt.H, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     fn cntValue(self: *const @This(), comptime kind: Kind) CntType(kind) { | ||||
|         return switch (kind) { | ||||
|             .a => self.io.cnt_a, | ||||
|             .b => self.io.cnt_b, | ||||
|             .c => self.io.cnt_c, | ||||
|             .d => self.io.cnt_d, | ||||
|             .e => self.io.cnt_e, | ||||
|             .f => self.io.cnt_f, | ||||
|             .g => self.io.cnt_g, | ||||
|             .h => self.io.cnt_h, | ||||
|             .i => self.io.cnt_i, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     // TODO: We always update the entirety of VRAM when that argubably isn't necessary | ||||
|     pub fn update(self: *@This()) void { | ||||
|         const nds9_tbl = @constCast(self.nds9_table); | ||||
|         const nds7_tbl = @constCast(self.nds7_table); | ||||
|  | ||||
|         for (nds9_tbl, nds7_tbl, 0..) |*nds9_ptr, *nds7_ptr, i| { | ||||
|             const addr = 0x0600_0000 + (i * page_size); | ||||
|  | ||||
|             inline for (std.meta.fields(Kind)) |f| { | ||||
|                 const kind = @field(Kind, f.name); | ||||
|                 const cnt = cntValue(self, kind); | ||||
|                 const ofs = switch (kind) { | ||||
|                     .e, .h, .i => 0, | ||||
|                     else => cnt.offset.read(), | ||||
|                 }; | ||||
|  | ||||
|                 const min = range(kind, cnt.mst.read(), ofs); | ||||
|                 const max = min + kind.size(); | ||||
|                 const offset = addr & (kind.size() - 1); | ||||
|  | ||||
|                 if (min <= addr and addr < max) { | ||||
|                     if ((kind == .c or kind == .d) and cnt.mst.read() == 2) { | ||||
|                         // Allocate to ARM7 | ||||
|                         nds7_ptr.* = self._buf[buf_offset(kind) + offset ..].ptr; | ||||
|                     } else { | ||||
|                         nds9_ptr.* = self._buf[buf_offset(kind) + offset ..].ptr; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // TODO: Rename | ||||
|     const Device = enum { nds9, nds7 }; | ||||
|  | ||||
|     pub fn read(self: @This(), comptime T: type, comptime dev: Device, address: u32) T { | ||||
|         const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; | ||||
|         const masked_addr = address & (addr_space_size - 1); | ||||
|         const page = masked_addr >> bits; | ||||
|         const offset = masked_addr & (page_size - 1); | ||||
|         const table = if (dev == .nds9) self.nds9_table else self.nds7_table; | ||||
|  | ||||
|         if (table[page]) |some_ptr| { | ||||
|             const ptr: [*]const T = @ptrCast(@alignCast(some_ptr)); | ||||
|  | ||||
|             return ptr[offset / @sizeOf(T)]; | ||||
|         } | ||||
|  | ||||
|         log.err("{s}: read(T: {}, addr: 0x{X:0>8}) was in un-mapped VRAM space", .{ @tagName(dev), T, address }); | ||||
|         return 0x00; | ||||
|     } | ||||
|  | ||||
|     pub fn write(self: *@This(), comptime T: type, comptime dev: Device, address: u32, value: T) void { | ||||
|         const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; | ||||
|         const masked_addr = address & (addr_space_size - 1); | ||||
|         const page = masked_addr >> bits; | ||||
|         const offset = masked_addr & (page_size - 1); | ||||
|         const table = if (dev == .nds9) self.nds9_table else self.nds7_table; | ||||
|  | ||||
|         if (table[page]) |some_ptr| { | ||||
|             const ptr: [*]T = @ptrCast(@alignCast(some_ptr)); | ||||
|             ptr[offset / @sizeOf(T)] = value; | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         log.err("{s}: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8}) was in un-mapped VRA< space", .{ @tagName(dev), T, address, value }); | ||||
|     } | ||||
| }; | ||||
|   | ||||
							
								
								
									
										291
									
								
								src/core/ppu/Vram.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								src/core/ppu/Vram.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,291 @@ | ||||
| const std = @import("std"); | ||||
| const KiB = 0x400; | ||||
|  | ||||
| const Allocator = std.mem.Allocator; | ||||
| const IntFittingRange = std.math.IntFittingRange; | ||||
|  | ||||
| const page_size = 16 * KiB; // smallest allocation is 16 KiB | ||||
| const addr_space_size = 0x0100_0000; // 0x0600_0000 -> 0x06FF_FFFF (inclusive) | ||||
| const table_len = addr_space_size / page_size; | ||||
| const buf_len = 656 * KiB; | ||||
|  | ||||
| const log = std.log.scoped(.vram); | ||||
|  | ||||
| io: Io = .{}, | ||||
|  | ||||
| _buf: *[buf_len]u8, | ||||
| nds9_table: *const [table_len]?[*]u8, | ||||
| nds7_table: *const [table_len]?[*]u8, | ||||
|  | ||||
| const Io = struct { | ||||
|     pub const Vramstat = nds7.Vramstat; | ||||
|  | ||||
|     const nds9 = @import("../nds9/io.zig"); | ||||
|     const nds7 = @import("../nds7/io.zig"); | ||||
|  | ||||
|     stat: nds7.Vramstat = .{ .raw = 0x00 }, | ||||
|  | ||||
|     /// Write-Only (according to melonDS these are readable lol) | ||||
|     cnt_a: nds9.Vramcnt.A = .{ .raw = 0x00 }, | ||||
|     cnt_b: nds9.Vramcnt.A = .{ .raw = 0x00 }, | ||||
|     cnt_c: nds9.Vramcnt.C = .{ .raw = 0x00 }, | ||||
|     cnt_d: nds9.Vramcnt.C = .{ .raw = 0x00 }, | ||||
|     cnt_e: nds9.Vramcnt.E = .{ .raw = 0x00 }, | ||||
|     cnt_f: nds9.Vramcnt.C = .{ .raw = 0x00 }, | ||||
|     cnt_g: nds9.Vramcnt.C = .{ .raw = 0x00 }, | ||||
|     cnt_h: nds9.Vramcnt.H = .{ .raw = 0x00 }, | ||||
|     cnt_i: nds9.Vramcnt.H = .{ .raw = 0x00 }, | ||||
| }; | ||||
|  | ||||
| pub fn init(self: *@This(), allocator: Allocator) !void { | ||||
|     const buf = try allocator.create([buf_len]u8); | ||||
|     errdefer allocator.destroy(buf); | ||||
|     @memset(buf, 0); | ||||
|  | ||||
|     const tables = try allocator.alloc(?[*]u8, 2 * table_len); | ||||
|     @memset(tables, null); | ||||
|  | ||||
|     self.* = .{ | ||||
|         .nds9_table = tables[0..table_len], | ||||
|         .nds7_table = tables[table_len .. 2 * table_len], | ||||
|         ._buf = buf, | ||||
|     }; | ||||
|  | ||||
|     // ROMS like redpanda.nds won't write to VRAMCNT before trying to write to VRAM | ||||
|     // therefore we assume some default allocation (in this casee VRAMCNT_A -> VRAMCNT_I are 0x00) | ||||
|     self.update(); | ||||
| } | ||||
|  | ||||
| pub fn deinit(self: @This(), allocator: Allocator) void { | ||||
|     allocator.destroy(self._buf); | ||||
|  | ||||
|     const ptr: [*]?[*]const u8 = @ptrCast(@constCast(self.nds9_table)); | ||||
|     allocator.free(ptr[0 .. 2 * table_len]); | ||||
| } | ||||
|  | ||||
| /// NDS7 VRAMSTAT | ||||
| pub fn stat(self: *const @This()) Io.Vramstat { | ||||
|     const vram_c: u8 = @intFromBool(self.io.cnt_c.enable.read() and self.io.cnt_c.mst.read() == 2); | ||||
|     const vram_d: u8 = @intFromBool(self.io.cnt_d.enable.read() and self.io.cnt_d.mst.read() == 2); | ||||
|  | ||||
|     return .{ .raw = (vram_d << 1) | vram_c }; | ||||
| } | ||||
|  | ||||
| const Kind = enum { | ||||
|     a, | ||||
|     b, | ||||
|     c, | ||||
|     d, | ||||
|     e, | ||||
|     f, | ||||
|     g, | ||||
|     h, | ||||
|     i, | ||||
|  | ||||
|     /// In Bytes | ||||
|     inline fn size(self: @This()) u32 { | ||||
|         return switch (self) { | ||||
|             .a => 128 * KiB, | ||||
|             .b => 128 * KiB, | ||||
|             .c => 128 * KiB, | ||||
|             .d => 128 * KiB, | ||||
|             .e => 64 * KiB, | ||||
|             .f => 16 * KiB, | ||||
|             .g => 16 * KiB, | ||||
|             .h => 32 * KiB, | ||||
|             .i => 16 * KiB, | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| // TODO: Rename | ||||
| fn range(comptime kind: Kind, mst: u3, offset: u2) u32 { | ||||
|     const ofs: u32 = offset; | ||||
|     // panic messages are from GBATEK | ||||
|  | ||||
|     return switch (kind) { | ||||
|         .a => switch (mst) { | ||||
|             0 => 0x0680_0000, | ||||
|             1 => 0x0600_0000 + (0x0002_0000 * ofs), | ||||
|             2 => 0x0640_0000 + (0x0002_0000 * (ofs & 0b01)), | ||||
|             3 => @panic("VRAMCNT_A: Slot OFS(0-3)"), | ||||
|             else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|         }, | ||||
|         .b => switch (mst) { | ||||
|             0 => 0x0682_0000, | ||||
|             1 => 0x0600_0000 + (0x0002_0000 * ofs), | ||||
|             2 => 0x0640_0000 + (0x0002_0000 * (ofs & 0b01)), | ||||
|             3 => @panic("VRAMCNT_B: Slot OFS(0-3)"), | ||||
|             else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|         }, | ||||
|         .c => switch (mst) { | ||||
|             0 => 0x0684_0000, | ||||
|             1 => 0x0600_0000 + (0x0002_0000 * ofs), | ||||
|             2 => 0x0600_0000 + (0x0002_0000 * (ofs & 0b01)), | ||||
|             3 => @panic("VRAMCNT_C: Slot OFS(0-3)"), | ||||
|             4 => 0x0620_0000, | ||||
|             else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|         }, | ||||
|         .d => switch (mst) { | ||||
|             0 => 0x0686_0000, | ||||
|             1 => 0x0600_0000 + (0x0002_0000 * ofs), | ||||
|             2 => 0x0600_0000 + (0x0002_0000 * (ofs & 0b01)), | ||||
|             3 => @panic("VRAMCNT_D: Slot OFS(0-3)"), | ||||
|             4 => 0x0660_0000, | ||||
|             else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|         }, | ||||
|         .e => switch (mst) { | ||||
|             0 => 0x0688_0000, | ||||
|             1 => 0x0600_0000, | ||||
|             2 => 0x0640_0000, | ||||
|             3 => @panic("VRAMCNT_E: Slots 0-3"), | ||||
|             else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|         }, | ||||
|         .f => switch (mst) { | ||||
|             0 => 0x0689_0000, | ||||
|             1 => 0x0600_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)), | ||||
|             2 => 0x0640_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)), | ||||
|             3 => @panic("VRAMCNT_F: Slot (OFS.0*1)+(OFS.1*4)"), | ||||
|             4 => @panic("VRAMCNT_F: Slot 0-1 (OFS=0), Slot 2-3 (OFS=1)"), | ||||
|             5 => @panic("VRAMCNT_F: Slot 0"), | ||||
|             else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|         }, | ||||
|         .g => switch (mst) { | ||||
|             0 => 0x0689_4000, | ||||
|             1 => 0x0600_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)), | ||||
|             2 => 0x0640_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)), | ||||
|             3 => @panic("VRAMCNT_G: Slot (OFS.0*1)+(OFS.1*4)"), | ||||
|             4 => @panic("VRAMCNT_G: Slot 0-1 (OFS=0), Slot 2-3 (OFS=1)"), | ||||
|             5 => @panic("VRAMCNT_G: Slot 0"), | ||||
|             else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|         }, | ||||
|         .h => switch (mst) { | ||||
|             0 => 0x0689_8000, | ||||
|             1 => 0x0620_0000, | ||||
|             2 => @panic("VRAMCNT_H: Slot 0-3"), | ||||
|             else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|         }, | ||||
|         .i => switch (mst) { | ||||
|             0 => 0x068A_0000, | ||||
|             1 => 0x0620_8000, | ||||
|             2 => 0x0660_0000, | ||||
|             3 => @panic("Slot 0"), | ||||
|             else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), | ||||
|         }, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| fn buf_offset(comptime kind: Kind) usize { | ||||
|     // zig fmt: off | ||||
|         return switch (kind) { | ||||
|             .a => 0,                                                            // 0x00000 | ||||
|             .b => (128 * KiB) * 1,                                              // 0x20000 (+ 0x20000) | ||||
|             .c => (128 * KiB) * 2,                                              // 0x40000 (+ 0x20000) | ||||
|             .d => (128 * KiB) * 3,                                              // 0x60000 (+ 0x20000) | ||||
|             .e => (128 * KiB) * 4,                                              // 0x80000 (+ 0x20000) | ||||
|             .f => (128 * KiB) * 4 + (64 * KiB),                                 // 0x90000 (+ 0x10000) | ||||
|             .g => (128 * KiB) * 4 + (64 * KiB) + (16 * KiB) * 1,                // 0x94000 (+ 0x04000) | ||||
|             .h => (128 * KiB) * 4 + (64 * KiB) + (16 * KiB) * 2,                // 0x98000 (+ 0x04000) | ||||
|             .i => (128 * KiB) * 4 + (64 * KiB) + (16 * KiB) * 2 + (32 * KiB)    // 0xA0000 (+ 0x08000) | ||||
|         }; | ||||
|         // zig fmt: on | ||||
| } | ||||
|  | ||||
| fn CntType(comptime kind: Kind) type { | ||||
|     const Vramcnt = @import("../nds9/io.zig").Vramcnt; | ||||
|  | ||||
|     return switch (kind) { | ||||
|         .a => Vramcnt.A, | ||||
|         .b => Vramcnt.A, | ||||
|         .c => Vramcnt.C, | ||||
|         .d => Vramcnt.C, | ||||
|         .e => Vramcnt.E, | ||||
|         .f => Vramcnt.C, | ||||
|         .g => Vramcnt.C, | ||||
|         .h => Vramcnt.H, | ||||
|         .i => Vramcnt.H, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| fn cntValue(self: *const @This(), comptime kind: Kind) CntType(kind) { | ||||
|     return switch (kind) { | ||||
|         .a => self.io.cnt_a, | ||||
|         .b => self.io.cnt_b, | ||||
|         .c => self.io.cnt_c, | ||||
|         .d => self.io.cnt_d, | ||||
|         .e => self.io.cnt_e, | ||||
|         .f => self.io.cnt_f, | ||||
|         .g => self.io.cnt_g, | ||||
|         .h => self.io.cnt_h, | ||||
|         .i => self.io.cnt_i, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| // TODO: We always update the entirety of VRAM when that argubably isn't necessary | ||||
| pub fn update(self: *@This()) void { | ||||
|     const nds9_tbl = @constCast(self.nds9_table); | ||||
|     const nds7_tbl = @constCast(self.nds7_table); | ||||
|  | ||||
|     for (nds9_tbl, nds7_tbl, 0..) |*nds9_ptr, *nds7_ptr, i| { | ||||
|         const addr = 0x0600_0000 + (i * page_size); | ||||
|  | ||||
|         inline for (std.meta.fields(Kind)) |f| { | ||||
|             const kind = @field(Kind, f.name); | ||||
|             const cnt = cntValue(self, kind); | ||||
|             const ofs = switch (kind) { | ||||
|                 .e, .h, .i => 0, | ||||
|                 else => cnt.offset.read(), | ||||
|             }; | ||||
|  | ||||
|             const min = range(kind, cnt.mst.read(), ofs); | ||||
|             const max = min + kind.size(); | ||||
|             const offset = addr & (kind.size() - 1); | ||||
|  | ||||
|             if (min <= addr and addr < max) { | ||||
|                 if ((kind == .c or kind == .d) and cnt.mst.read() == 2) { | ||||
|                     // Allocate to ARM7 | ||||
|                     nds7_ptr.* = self._buf[buf_offset(kind) + offset ..].ptr; | ||||
|                 } else { | ||||
|                     nds9_ptr.* = self._buf[buf_offset(kind) + offset ..].ptr; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // TODO: Rename | ||||
| const Device = enum { nds9, nds7 }; | ||||
|  | ||||
| pub fn read(self: @This(), comptime T: type, comptime dev: Device, address: u32) T { | ||||
|     const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; | ||||
|     const masked_addr = address & (addr_space_size - 1); | ||||
|     const page = masked_addr >> bits; | ||||
|     const offset = masked_addr & (page_size - 1); | ||||
|     const table = if (dev == .nds9) self.nds9_table else self.nds7_table; | ||||
|  | ||||
|     if (table[page]) |some_ptr| { | ||||
|         const ptr: [*]const T = @ptrCast(@alignCast(some_ptr)); | ||||
|  | ||||
|         return ptr[offset / @sizeOf(T)]; | ||||
|     } | ||||
|  | ||||
|     log.err("{s}: read(T: {}, addr: 0x{X:0>8}) was in un-mapped VRAM space", .{ @tagName(dev), T, address }); | ||||
|     return 0x00; | ||||
| } | ||||
|  | ||||
| pub fn write(self: *@This(), comptime T: type, comptime dev: Device, address: u32, value: T) void { | ||||
|     const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; | ||||
|     const masked_addr = address & (addr_space_size - 1); | ||||
|     const page = masked_addr >> bits; | ||||
|     const offset = masked_addr & (page_size - 1); | ||||
|     const table = if (dev == .nds9) self.nds9_table else self.nds7_table; | ||||
|  | ||||
|     if (table[page]) |some_ptr| { | ||||
|         const ptr: [*]T = @ptrCast(@alignCast(some_ptr)); | ||||
|         ptr[offset / @sizeOf(T)] = value; | ||||
|  | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     log.err("{s}: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8}) was in un-mapped VRA< space", .{ @tagName(dev), T, address, value }); | ||||
| } | ||||
							
								
								
									
										91
									
								
								src/core/ppu/engine.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/core/ppu/engine.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| const std = @import("std"); | ||||
| const Bus = @import("../nds9/Bus.zig"); | ||||
|  | ||||
| const FrameBuffer = @import("../ppu.zig").FrameBuffer; | ||||
|  | ||||
| const DispcntA = @import("../nds9/io.zig").DispcntA; | ||||
| const DispcntB = @import("../nds9/io.zig").DispcntB; | ||||
|  | ||||
| const Dispstat = @import("../nds9/io.zig").Dispstat; | ||||
| const Vcount = @import("../nds9/io.zig").Vcount; | ||||
|  | ||||
| const PowCnt = @import("../nds9/io.zig").PowCnt; | ||||
|  | ||||
| const width = @import("../ppu.zig").screen_width; | ||||
| const height = @import("../ppu.zig").screen_height; | ||||
|  | ||||
| const EngineKind = enum { a, b }; | ||||
|  | ||||
| pub const EngineA = Engine(.a); | ||||
| pub const EngineB = Engine(.b); | ||||
|  | ||||
| fn Engine(comptime kind: EngineKind) type { | ||||
|     const log = std.log.scoped(.engine2d); | ||||
|     _ = log; // TODO: specify between 2D-A and 2D-B | ||||
|  | ||||
|     // FIXME: don't commit zig crimes | ||||
|     const Type = struct { | ||||
|         fn inner(comptime TypeA: type, comptime TypeB: type) type { | ||||
|             return if (kind == .a) TypeA else TypeB; | ||||
|         } | ||||
|     }.inner; | ||||
|  | ||||
|     return struct { | ||||
|         dispcnt: Type(DispcntA, DispcntB) = .{ .raw = 0x0000_0000 }, | ||||
|         dispstat: Dispstat = .{ .raw = 0x0000_0000 }, | ||||
|         vcount: Vcount = .{ .raw = 0x0000_0000 }, | ||||
|  | ||||
|         pub fn drawScanline(self: *@This(), bus: *Bus, fb: *FrameBuffer, powcnt: *PowCnt) void { | ||||
|             const disp_mode = self.dispcnt.display_mode.read(); | ||||
|  | ||||
|             switch (disp_mode) { | ||||
|                 0 => { // Display Off | ||||
|                     const buf = switch (kind) { | ||||
|                         .a => if (powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back), | ||||
|                         .b => if (powcnt.display_swap.read()) fb.btm(.back) else fb.top(.back), | ||||
|                     }; | ||||
|  | ||||
|                     @memset(buf, 0xFF); // set everything to white | ||||
|                 }, | ||||
|                 1 => @panic("TODO: standard graphics display (text mode, etc)"), | ||||
|                 2 => { // VRAM display | ||||
|                     if (kind == .b) return; | ||||
|                     // TODO: Master Brightness can still affect this mode | ||||
|  | ||||
|                     const scanline: u32 = self.vcount.scanline.read(); | ||||
|                     const buf = if (powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back); | ||||
|  | ||||
|                     const scanline_buf = blk: { | ||||
|                         const rgba_ptr: *[width * height]u32 = @ptrCast(@alignCast(buf)); | ||||
|                         break :blk rgba_ptr[width * scanline ..][0..width]; | ||||
|                     }; | ||||
|  | ||||
|                     const base_addr: u32 = 0x0680_0000 + (width * @sizeOf(u16)) * @as(u32, scanline); | ||||
|  | ||||
|                     for (scanline_buf, 0..) |*rgba, i| { | ||||
|                         const addr = base_addr + @as(u32, @intCast(i)) * @sizeOf(u16); | ||||
|                         rgba.* = rgba888(bus.dbgRead(u16, addr)); | ||||
|                     } | ||||
|                 }, | ||||
|                 3 => { | ||||
|                     if (kind == .b) return; | ||||
|  | ||||
|                     @panic("TODO: main memory display"); | ||||
|                 }, | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| inline fn rgba888(bgr555: u16) u32 { | ||||
|     const b: u32 = bgr555 >> 10 & 0x1F; | ||||
|     const g: u32 = bgr555 >> 5 & 0x1F; | ||||
|     const r: u32 = bgr555 & 0x1F; | ||||
|  | ||||
|     // zig fmt: off | ||||
|     return (r << 3 | r >> 2) << 24 | ||||
|         |  (g << 3 | g >> 2) << 16 | ||||
|         |  (b << 3 | b >> 2) << 8 | ||||
|         |  0xFF; | ||||
|     // zig fmt: on | ||||
| } | ||||
		Reference in New Issue
	
	Block a user