diff --git a/src/ppu.zig b/src/ppu.zig index 05d2518..bc59a80 100644 --- a/src/ppu.zig +++ b/src/ppu.zig @@ -4,6 +4,9 @@ const io = @import("bus/io.zig"); const EventKind = @import("scheduler.zig").EventKind; const Scheduler = @import("scheduler.zig").Scheduler; +const Bit = @import("bitfield").Bit; +const Bitfield = @import("bitfield").Bitfield; + const Allocator = std.mem.Allocator; pub const width = 240; @@ -68,8 +71,50 @@ pub const Ppu = struct { switch (bg_mode) { 0x0 => { - // Mode 0 + // A Tile is always 8x8 pixels + // Mode 0 Implementation Assuming: + // - Scrolling isn't a thing + // - Bill Gates said we'll never need more than BG0 + + // Write to this Scanline once we're done + const start = framebuf_pitch * @as(usize, scanline); + var scanline_buf = std.mem.zeroes([framebuf_pitch]u8); + + // These we can probably move to top level? + const charblock_len: u32 = 0x4000; + const screenblock_len: u32 = 0x800; + + const cbb: u2 = self.bg0.cnt.char_base.read(); // Char Block Base + const sbb: u5 = self.bg0.cnt.screen_base.read(); // Screen Block Base + const is_8bpp: bool = self.bg0.cnt.palette_type.read(); // Colour Mode + const size: u2 = self.bg0.cnt.screen_size.read(); // Background Size + + // 0x0600_000 is implied because we can access VRAM without the Bus + const char_base: u32 = charblock_len * @as(u32, cbb); + const screen_base: u32 = screenblock_len * @as(u32, sbb); + + const y = @as(u32, scanline); + var x: u32 = 0; + while (x < width) : (x += 1) { + const entry_addr = screen_base + tilemapIndex(size, x, y); + const entry = @bitCast(ScreenEntry, @as(u16, self.vram.buf[entry_addr + 1]) << 8 | @as(u16, self.vram.buf[entry_addr])); + + const tile_id: u32 = entry.tile_id.read(); + const px_y = if (entry.h_flip.read()) 7 - (y % 8) else y % 8; + const px_x = if (entry.v_flip.read()) 7 - (x % 8) else x % 8; + const tile_addr = char_base + if (is_8bpp) 0x40 * tile_id + 0x8 * px_y else 0x20 * tile_id + 0x4 * px_y; + + var tile = self.vram.buf[tile_addr + if (is_8bpp) px_x else px_x >> 1]; + tile = if (px_x & 1 == 1) tile >> 4 else tile & 0xF; + + const pal_bank: u8 = @as(u8, entry.palette_bank.read()) << 4; + const colour = pal_bank | tile; + + std.mem.copy(u8, scanline_buf[x * 2 ..][0..2], self.palette.buf[colour * 2 ..][0..2]); + } + + std.mem.copy(u8, self.framebuf[start..][0..framebuf_pitch], &scanline_buf); }, 0x3 => { const start = framebuf_pitch * @as(usize, scanline); @@ -94,6 +139,14 @@ pub const Ppu = struct { else => std.debug.panic("[PPU] TODO: Implement BG Mode {}", .{bg_mode}), } } + + fn tilemapIndex(size: u2, x: u32, y: u32) u32 { + return switch (size) { + 0 => (((y % 256) / 8) * 64) + (((x % 256) / 8) * 2), + 1 => (((y % 256) / 8) * 64) + (((x % 256) / 8) * 2), + else => std.debug.panic("tile size {}", .{size}), + }; + } }; const Palette = struct { @@ -241,3 +294,11 @@ const Background = struct { }; } }; + +const ScreenEntry = extern union { + tile_id: Bitfield(u16, 0, 10), + h_flip: Bit(u16, 10), + v_flip: Bit(u16, 11), + palette_bank: Bitfield(u16, 12, 4), + raw: u16, +};