Skip to content

Commit 2dfab11

Browse files
committed
Fix inaccuracies in PPU rendering/CPU DAA instruction
Special shoutout to dmg-acid2 by Matt Currie for being a very well-rounded PPU test! - Adjust calculation of adjustment in DAA opcode - Added internal window counter for proper window tile calculation - Check OBJ-to-BG priority (OAM bit 7) and adjust sprite rendering accordingly - Handle 8x16 sprite rendering accurately - For two sprites with the same X coordinate, render the one that is first in OAM - Only render 10 max sprites per line
1 parent b0ba490 commit 2dfab11

4 files changed

Lines changed: 77 additions & 42 deletions

File tree

src/core/cpu.cpp

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,38 +2377,22 @@ uint8_t CPU::RLCA() {
23772377

23782378
uint8_t CPU::DAA() {
23792379
uint8_t adjustment = 0;
2380-
bool new_carry = false;
2380+
bool carry = false;
23812381

2382-
if (!get_flag_n()) {
2383-
// After an ADD operation
2384-
if (get_flag_c() || a > 0x99) {
2385-
adjustment |= 0x60;
2386-
new_carry = true;
2387-
}
2388-
if (get_flag_h() || (a & 0x0F) > 0x09) {
2389-
adjustment |= 0x06;
2390-
}
2391-
} else {
2392-
// After a SUB operation
2393-
if (get_flag_c()) {
2394-
adjustment |= 0x60;
2395-
new_carry = true;
2396-
}
2397-
if (get_flag_h()) {
2398-
adjustment |= 0x06;
2399-
}
2400-
2401-
// Subtract the adjustment instead of adding it
2402-
a -= adjustment;
2382+
if (get_flag_h() || (!get_flag_n() && (a & 0x0F) > 0x09)) {
2383+
adjustment |= 0x06;
24032384
}
2404-
2405-
if (!get_flag_n()) {
2406-
a += adjustment;
2385+
2386+
if (get_flag_c() || (!get_flag_n() && a > 0x99)) {
2387+
adjustment |= 0x60;
2388+
carry = true;
24072389
}
24082390

2391+
a += (get_flag_n() ? -adjustment : adjustment);
2392+
24092393
set_flag_z(a == 0);
24102394
set_flag_h(false);
2411-
set_flag_c(new_carry);
2395+
set_flag_c(carry);
24122396

24132397
return 4;
24142398
}

src/core/ppu.cpp

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ void PPU::tick(uint8_t cycles) {
9696
current_ly++;
9797

9898
if (current_ly > 153) {
99-
current_ly = 0; // Reset to start of next frame
99+
// Reset to start of next frame
100+
current_ly = 0;
101+
window_line_counter = 0;
100102
mode = 2;
101103
}
102104
}
@@ -142,6 +144,9 @@ void PPU::draw_scanline() {
142144
uint8_t bgp = mmu->read_byte(0xFF47);
143145
uint32_t shades[] = { 0xFFFFFFFF, 0xFFAAAAAA, 0xFF555555, 0xFF000000 };
144146

147+
// Sprite priority check
148+
uint8_t bg_color_ids[160] = {0};
149+
145150
// Check master bg/window enable bit (LCDC bit 0)
146151
if (!(lcdc & 0x01)) {
147152
// Fill scanline with white (color 0)
@@ -154,6 +159,7 @@ void PPU::draw_scanline() {
154159
uint8_t wy = mmu->read_byte(0xFF4A);
155160
uint8_t wx = mmu->read_byte(0xFF4B) - 7;
156161
bool window_enabled = (lcdc & 0x20) && (ly >= wy);
162+
bool window_drawn = false;
157163

158164
// Get scroll (x and y) pos
159165
uint8_t scy = mmu->read_byte(0xFF42);
@@ -167,7 +173,8 @@ void PPU::draw_scanline() {
167173
if (window_enabled && px >= wx) {
168174
map_base = (lcdc & 0x40) ? 0x9C00 : 0x9800; // LCDC bit 6
169175
t_x = px - wx;
170-
t_y = ly - wy;
176+
t_y = window_line_counter;
177+
window_drawn = true;
171178
} else {
172179
map_base = (lcdc & 0x08) ? 0x9C00 : 0x9800; // LCDC bit 3
173180
t_x = px + scx;
@@ -181,6 +188,7 @@ void PPU::draw_scanline() {
181188
// Tile data addressing
182189
bool is_unsigned = (lcdc & 0x10);
183190
uint16_t tile_data_addr;
191+
184192
if (is_unsigned) {
185193
tile_data_addr = 0x8000 + (tile_index * 16);
186194
} else {
@@ -196,35 +204,69 @@ void PPU::draw_scanline() {
196204
int bit = 7 - (t_x % 8);
197205
uint8_t color_id = ((b2 >> bit) & 0x01) << 1 | ((b1 >> bit) & 0x01);
198206

207+
bg_color_ids[px] = color_id;
208+
199209
// Apply palette and write to framebuffer
200210
uint8_t palette_color = (bgp >> (color_id * 2)) & 0x03;
201211
framebuffer[ly * 160 + px] = shades[palette_color];
202212
}
213+
214+
if (window_drawn) {
215+
window_line_counter++;
216+
}
203217
}
204218

205-
// Draw sprite(s) on top of background
219+
// Draw sprite(s) (10 max per line) on top of background
206220
if (lcdc & 0x02) {
221+
int sprites_on_line = 0;
222+
uint8_t used_sprite_x_coords[160] = {};
207223
for (int i = 0; i < 40; i++) {
208224
uint16_t oam_addr = 0xFE00 + (i * 4);
225+
if (sprites_on_line >= 10) break;
209226

210227
// Get proper sprite attributes
211228
uint8_t sprite_y = mmu->read_byte(oam_addr) - 16;
212229
uint8_t sprite_x = mmu->read_byte(oam_addr + 1) - 8;
213230
uint8_t tile_index = mmu->read_byte(oam_addr + 2);
214231
uint8_t attributes = mmu->read_byte(oam_addr + 3);
232+
uint8_t sprite_height = (lcdc & 0x04) ? 16 : 8;
215233

216234
// Check if sprite is visible on this scanline (ly)
217-
if (ly >= sprite_y && ly < sprite_y + 8) {
235+
if (ly >= sprite_y && ly < sprite_y + sprite_height) {
236+
sprites_on_line++;
237+
238+
// Check if sprite at this X coordinate has already been used (priority)
239+
if (sprite_x >= 0 && sprite_x < 160) {
240+
if (used_sprite_x_coords[sprite_x]) {
241+
continue;
242+
} else {
243+
used_sprite_x_coords[sprite_x] = 1;
244+
}
245+
}
246+
218247
// Determine which palette to use (Bit 4: 0=OBP0, 1=OBP1)
219248
uint8_t obp = mmu->read_byte((attributes & 0x10) ? 0xFF49 : 0xFF48);
220249

221250
// Fetch tile data (Sprites always use 0x8000-0x8FFF unsigned mode)
222-
uint16_t tile_addr = 0x8000 + (tile_index * 16);
251+
uint16_t tile_addr;
223252
uint8_t line = (ly - sprite_y);
224-
253+
225254
// Handle vertical flip (Bit 6)
226-
if (attributes & 0x40) line = 7 - line;
227-
255+
if (attributes & 0x40) line = (sprite_height - 1) - line;
256+
257+
if (sprite_height == 16) {
258+
// For 8x16 sprites, bit 0 of the tile index is ignored for the base
259+
uint8_t base_tile = tile_index & 0xFE;
260+
uint8_t actual_tile = (line < 8) ? base_tile : (base_tile | 0x01);
261+
262+
tile_addr = 0x8000 + (actual_tile * 16);
263+
// Reset line to 0-7 for the specific 8x8 tile selected
264+
line %= 8;
265+
} else {
266+
// Standard 8x8 mode
267+
tile_addr = 0x8000 + (tile_index * 16);
268+
}
269+
228270
uint8_t b1 = mmu->read_byte(tile_addr + (line * 2));
229271
uint8_t b2 = mmu->read_byte(tile_addr + (line * 2) + 1);
230272

@@ -238,8 +280,14 @@ void PPU::draw_scanline() {
238280

239281
// Don't draw transparent pixels (color 0)
240282
if (color_id != 0) {
241-
uint8_t palette_color = (obp >> (color_id * 2)) & 0x03;
242-
framebuffer[ly * 160 + pixel_x] = shades[palette_color];
283+
// OBJ-to-BG priority (OAM bit 7)
284+
uint8_t bg_id = bg_color_ids[pixel_x];
285+
bool bg_over_obj = (attributes & 0x80) != 0;
286+
287+
if (!bg_over_obj || (bg_over_obj && bg_id == 0)) {
288+
uint8_t palette_color = (obp >> (color_id * 2)) & 0x03;
289+
framebuffer[ly * 160 + pixel_x] = shades[palette_color];
290+
}
243291
}
244292
}
245293
}

src/core/ppu.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ class PPU {
7171
// Check last mode for STAT interrupt triggering
7272
uint8_t last_mode = 255;
7373

74+
// Window internal line counter
75+
uint8_t window_line_counter = 0;
76+
7477
// Read VRAM and fill frame buffer
7578
void draw_scanline();
7679

src/main.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
#include <SDL3/SDL.h>
33
#include <string>
44

5+
#include "core/cpu.h"
6+
#include "core/mmu.h"
7+
#include "core/rom.h"
8+
#include "core/ppu.h"
9+
#include "core/joypad.h"
10+
511
// Structure to hold file dialog state
612
struct DialogState {
713
bool complete = false;
@@ -18,12 +24,6 @@ void SDLCALL file_dialog_callback(void* userdata, const char* const* filelist, i
1824
state->complete = true;
1925
}
2026

21-
#include "core/cpu.h"
22-
#include "core/mmu.h"
23-
#include "core/rom.h"
24-
#include "core/ppu.h"
25-
#include "core/joypad.h"
26-
2727
// Constants for timing
2828
const int CYCLES_PER_FRAME = 70224;
2929
// 4194304 Hz / 70224 cycles/frame = 59.7275 Hz

0 commit comments

Comments
 (0)