fix: resolve issues with sprite mirroring

This commit is contained in:
Rekai Nyangadzayi Musuka 2022-03-11 22:40:47 -04:00
parent 05e67da181
commit 0c50ef1e6d
1 changed files with 61 additions and 93 deletions

View File

@ -95,10 +95,10 @@ pub const Ppu = struct {
// Determine sprite bounds // Determine sprite bounds
// We only care about the Y axis since that value remains constant // We only care about the Y axis since that value remains constant
const sy = sprite.y(); const start = sprite.y();
const sy_end = sy + sprite.height; const end = start + sprite.height;
if ((sy <= y and sy_end > y) or (sy_end < sy and y < sy_end)) { if (start <= y and y < end) {
for (self.scanline_sprites) |*maybe_sprite| { for (self.scanline_sprites) |*maybe_sprite| {
if (maybe_sprite.* == null) { if (maybe_sprite.* == null) {
maybe_sprite.* = sprite; maybe_sprite.* = sprite;
@ -107,12 +107,12 @@ pub const Ppu = struct {
} }
log.err("Found more than 128 sprites in OAM Search", .{}); log.err("Found more than 128 sprites in OAM Search", .{});
unreachable; unreachable; // TODO: Is this truly unreachable?
} }
} }
} }
fn drawSprite(self: *Self, prio: u2) void { fn drawSprites(self: *Self, prio: u2) void {
// Object VRAM 3rd and 4th (0-indexed) charblocks // Object VRAM 3rd and 4th (0-indexed) charblocks
const char_base = 0x4000 * 4; const char_base = 0x4000 * 4;
const scanline = self.vcount.scanline.read(); const scanline = self.vcount.scanline.read();
@ -125,89 +125,57 @@ pub const Ppu = struct {
const x = i; const x = i;
const y = scanline; const y = scanline;
// Find Relevant Tile for (self.scanline_sprites) |maybe_sprite| {
var maybe_sprite: ?Sprite = null; if (maybe_sprite) |sprite| {
for (self.scanline_sprites) |sprite_opt| {
if (sprite_opt) |sprite| {
if (sprite.priority() != prio) continue; if (sprite.priority() != prio) continue;
const sx = sprite.x(); const start = sprite.x();
const sx_end = sx + sprite.width; const end = start + sprite.width;
if (sx <= x and sx_end > x) { if (start <= x and x < end) {
maybe_sprite = sprite;
break;
}
} else break;
}
// // TODO: Scanning OAM for every single pixel is insanely expensive // FIXME: We branch on this condition quite a lot
// // This should be done once per scanline (and then check for X bounds every pixel)
// var j: u32 = 0;
// while (j < self.oam.buf.len) : (j += 8) {
// // Attributes in OAM are 6 bytes long, with 2 bytes of padding
// // Grab Attributes from OAM
// const attr0 = @bitCast(Attr0, self.oam.get16(j));
// const attr1 = @bitCast(Attr1, self.oam.get16(j + 2));
// const attr2 = @bitCast(Attr2, self.oam.get16(j + 4));
// // Only consider enabled sprites on the current priority
// if (attr0.disabled.read() or attr2.rel_prio.read() != prio) continue;
// // Determine sprite bounds
// const d = spriteDimensions(attr0.shape.read(), attr1.size.read());
// const sy = attr0.y.read();
// const sx = attr1.x.read();
// const sx_end = sx + d[0];
// const sy_end = sy + d[1];
// // If sprite is in range
// if (sy < y and sy_end > y and sx < x and sx_end > x) {
// maybe_sprite = Sprite.init(attr0, attr1, attr2);
// break;
// }
// }
// If we didn't find a sprite, progress to the next pixel
const sprite: Sprite = if (maybe_sprite) |s| s else continue;
const is_8bpp = sprite.is_8bpp(); const is_8bpp = sprite.is_8bpp();
// Y and X coordinates within the context of a singular 8x8 tile // Y and X coordinates within the context of a singular 8x8 tile
const tile_y = y - sprite.y(); const tile_y: u16 = (y - sprite.y()) ^ if (sprite.v_flip()) (sprite.height - 1) else 0;
const tile_x = x - sprite.x(); const tile_x = (x - sprite.x()) ^ if (sprite.h_flip()) (sprite.width - 1) else 0;
const tile_id: u32 = sprite.tile_id(); const tile_id: u32 = sprite.tile_id();
const tile_row_offset: u32 = if (is_8bpp) 8 else 4; const tile_row_offset: u32 = if (is_8bpp) 8 else 4;
const tile_len: u32 = if (is_8bpp) 0x40 else 0x20; const tile_len: u32 = if (is_8bpp) 0x40 else 0x20;
const row = if (sprite.v_flip()) 7 - (tile_y % 8) else tile_y % 8; const row = tile_y % 8;
const col = if (sprite.h_flip()) 7 - (tile_x % 8) else tile_x % 8; const col = tile_x % 8;
const tile_base: u32 = char_base + (0x20 * tile_id) + (tile_row_offset * row) + if (is_8bpp) col else col / 2; const tile_base: u32 = char_base + (0x20 * tile_id) + (tile_row_offset * row) + if (is_8bpp) col else col / 2;
var tile_offset = (tile_x >> 3) * tile_len; var tile_offset = (tile_x / 8) * tile_len;
if (self.dispcnt.obj_mapping.read()) { if (self.dispcnt.obj_mapping.read()) {
// One Dimensional // One Dimensional
tile_offset += (tile_y / 8) * tile_len * (sprite.width >> 3); tile_offset += (tile_y / 8) * tile_len * (sprite.width / 8);
} else { } else {
// Two Dimensional // Two Dimensional
tile_offset += (@as(u32, tile_y) >> 3) * tile_len * 0x20; // TODO: This doesn't work
tile_offset += (tile_y / 8) * tile_len * 0x20;
} }
const tile = self.vram.buf[tile_base + tile_offset]; const tile = self.vram.buf[tile_base + tile_offset];
const pal_id = if (!is_8bpp) blk: { const pal_id: u16 = if (!is_8bpp) blk: {
const nybble_tile = if (col & 1 == 1) tile >> 4 else tile & 0xF; const nybble_tile = if (col & 1 == 1) tile >> 4 else tile & 0xF;
if (nybble_tile == 0) break :blk 0; if (nybble_tile == 0) break :blk 0;
const pal_bank: u16 = @as(u8, sprite.pal_bank()) << 4; const pal_bank = @as(u8, sprite.pal_bank()) << 4;
break :blk pal_bank | nybble_tile; break :blk pal_bank | nybble_tile;
} else tile; } else tile;
// Sprite Palette starts at 0x0500_0200 // Sprite Palette starts at 0x0500_0200
if (pal_id != 0) self.scanline_buf[i] = self.palette.get16(0x200 + pal_id * 2); if (pal_id != 0) self.scanline_buf[i] = self.palette.get16(0x200 + pal_id * 2);
} }
} else break;
}
}
} }
fn drawBackround(self: *Self, comptime n: u3) void { fn drawBackround(self: *Self, comptime n: u3) void {
@ -287,7 +255,7 @@ pub const Ppu = struct {
var i: usize = 0; var i: usize = 0;
while (i < 4) : (i += 1) { while (i < 4) : (i += 1) {
// Draw Sprites Here // Draw Sprites Here
if (obj_enable) self.drawSprite(@truncate(u2, i)); if (obj_enable) self.drawSprites(@truncate(u2, i));
if (i == self.bg[0].cnt.priority.read() and bg_enable & 1 == 1) self.drawBackround(0); if (i == self.bg[0].cnt.priority.read() and bg_enable & 1 == 1) self.drawBackround(0);
if (i == self.bg[1].cnt.priority.read() and bg_enable >> 1 & 1 == 1) self.drawBackround(1); if (i == self.bg[1].cnt.priority.read() and bg_enable >> 1 & 1 == 1) self.drawBackround(1);
if (i == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawBackround(2); if (i == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawBackround(2);
@ -541,47 +509,47 @@ const Sprite = struct {
}; };
} }
fn x(self: *const Self) u16 { inline fn x(self: *const Self) u16 {
return self.attr1.x.read(); return self.attr1.x.read();
} }
fn y(self: *const Self) u8 { inline fn y(self: *const Self) u8 {
return self.attr0.y.read(); return self.attr0.y.read();
} }
fn is_8bpp(self: *const Self) bool { inline fn is_8bpp(self: *const Self) bool {
return self.attr0.is_8bpp.read(); return self.attr0.is_8bpp.read();
} }
fn shape(self: *const Self) u2 { inline fn shape(self: *const Self) u2 {
return self.attr0.shape.read(); return self.attr0.shape.read();
} }
fn size(self: *const Self) u2 { inline fn size(self: *const Self) u2 {
return self.attr1.size.read(); return self.attr1.size.read();
} }
fn tile_id(self: *const Self) u10 { inline fn tile_id(self: *const Self) u10 {
return self.attr2.tile_id.read(); return self.attr2.tile_id.read();
} }
fn pal_bank(self: *const Self) u4 { inline fn pal_bank(self: *const Self) u4 {
return self.attr2.pal_bank.read(); return self.attr2.pal_bank.read();
} }
fn h_flip(self: *const Self) bool { inline fn h_flip(self: *const Self) bool {
return self.attr1.h_flip.read(); return self.attr1.h_flip.read();
} }
fn v_flip(self: *const Self) bool { inline fn v_flip(self: *const Self) bool {
return self.attr1.v_flip.read(); return self.attr1.v_flip.read();
} }
fn priority(self: *const Self) u2 { inline fn priority(self: *const Self) u2 {
return self.attr2.rel_prio.read(); return self.attr2.rel_prio.read();
} }
fn isDisabled(self: *const Self) bool { inline fn isDisabled(self: *const Self) bool {
return self.attr0.disabled.read(); return self.attr0.disabled.read();
} }
}; };