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,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										425
									
								
								src/core/ppu.zig
									
									
									
									
									
								
							
							
						
						
									
										425
									
								
								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 {
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
            engine.dispstat.hblank.set();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const dots_in_hblank = 99;
 | 
			
		||||
        std.debug.assert(self.io.dispstat.hblank.read() == false);
 | 
			
		||||
        std.debug.assert(self.io.dispstat.vblank.read() == false);
 | 
			
		||||
 | 
			
		||||
        // TODO: Signal HBlank IRQ
 | 
			
		||||
 | 
			
		||||
        self.io.dispstat.hblank.set();
 | 
			
		||||
        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