DMG-acid2测试ROM失败

编程语言 2026-07-10

我在用C 语言开发一个Game Boy仿真器,并使用DMG-acid2测试ROM来测试PPU。其他的blargg测试ROM都通过,但PPU的渲染存在问题。具体表现为眩动和笑脸的一部分缺失。我附上了一段视频以作进一步展示。

我尝试查看PanDocs,看看是否有我遗漏的地方,但找不到导致我的PPU出现这种情况的原因。

void tick_ppu(Gameboy *gameboy, uint8_t cycles) {
    if (!(gameboy->ppu.lcdc & 0x80)) {
        gameboy->ppu.ly = 0;
        gameboy->ppu.mode = 0;
        gameboy->ppu.dots = 0;
        return;
    }

    gameboy->ppu.dots += cycles;

    switch (gameboy->ppu.mode) {
        case 2: {
            if (gameboy->ppu.dots >= 80) {
                gameboy->ppu.sprite_count = 0;
                uint8_t sprite_height = (gameboy->ppu.lcdc & 0x04) ? 16 : 8;

                for (int i = 0; i < 40 && gameboy->ppu.sprite_count < 10; i++) {
                    uint8_t y = gameboy->oam[i * 4];
                    int16_t sprite_y = y - 16;

                    if (gameboy->ppu.ly >= sprite_y && gameboy->ppu.ly < sprite_y + sprite_height) {
                        for (int j = 0; j < 4; j++) {
                            gameboy->ppu.sprite_buffer[gameboy->ppu.sprite_count * 4 + j] = gameboy->oam[i * 4 + j];
                        }
                        gameboy->ppu.sprite_count++;
                    }
                }

                gameboy->ppu.mode3_length = 172 + (gameboy->ppu.sprite_count * 8);

                gameboy->ppu.dots = 0;
                gameboy->ppu.mode = 3;
            }
            break;
        }
        case 3: {
            if (gameboy->ppu.dots >= gameboy->ppu.mode3_length) {
                gameboy->ppu.dots = 0;
                gameboy->ppu.mode = 0;
                render_scanline(gameboy);
            }
            break;
        }
        case 0: {
            if (gameboy->ppu.dots >= 204) {
                gameboy->ppu.dots = 0;
                gameboy->ppu.ly++;

                if (gameboy->ppu.ly >= 144) {
                    gameboy->ppu.mode = 1;
                    gameboy->if_reg |= 0x01;
                } else {
                    gameboy->ppu.mode = 2;
                }
            }
            break;
        }
        case 1: {
            if (gameboy->ppu.dots >= 456) {
                gameboy->ppu.dots = 0;
                gameboy->ppu.ly++;

                if (gameboy->ppu.ly >= 154) {
                    gameboy->ppu.ly = 0;
                    gameboy->ppu.mode = 2;
                    gameboy->ppu.window_line = 0;
                }
            }
            break;
        }
    }

    gameboy->ppu.stat = (gameboy->ppu.stat & 0xFC) | (gameboy->ppu.mode & 0x03);

    if (gameboy->ppu.ly == gameboy->ppu.lyc) {
        gameboy->ppu.stat |= 0x04;
        if (gameboy->ppu.stat & 0x40) {
            gameboy->if_reg |= 0x02;
        }
    } else {
        gameboy->ppu.stat &= ~0x04;
    }
}

If you want a further view, 以下链接可查看源代码提交哈希对应的内容:

render_scanline - https://git.critchlow.net/brodycritchlow/CGameboy/src/commit/b122b11203eb2fe0c62afb4eaac81f80f740b618/cpu.c#L2652

tick_ppu - https://git.critchlow.net/brodycritchlow/CGameboy/src/commit/b122b11203eb2fe0c62afb4eaac81f80f740b618/cpu.c#L2838

init_display - https://git.critchlow.net/brodycritchlow/CGameboy/src/commit/b122b11203eb2fe0c62afb4eaac81f80f740b618/gameboy.c#L42

在此输入图片描述

解决方案

最终有效的修复方法如下:

在渲染扫描线时,我选择创建一个新的“精灵缓存”(sprite buffer),本质上是一个用于渲染所需对象的缓存。这使我能够摆脱实时OAM扫描,这是代码中的一个压力点。

for (int i = 0; i < count; i++) {
    sprites[i].y = gameboy->ppu.sprite_buffer[i * 4];
    sprites[i].x = gameboy->ppu.sprite_buffer[i * 4 + 1];
    sprites[i].tile = gameboy->ppu.sprite_buffer[i * 4 + 2];
    sprites[i].flags = gameboy->ppu.sprite_buffer[i * 4 + 3];
    sprites[i].oam_index = i;
}

tick_ppu 中,总扫描线数量因为模式定时0 而不正确:

case 2: {
    if (gameboy->ppu.dots >= 80) {
        gameboy->ppu.dots = 0;
        gameboy->ppu.sprite_count = 0;
        uint8_t sprite_height = (gameboy->ppu.lcdc & 0x04) ? 16 : 8;

        for (int i = 0; i < 40 && gameboy->ppu.sprite_count < 10; i++) {
            uint8_t y = gameboy->oam[i * 4];
            int16_t sprite_y = y - 16;

            if (gameboy->ppu.ly >= sprite_y && gameboy->ppu.ly < sprite_y + sprite_height) {
                for (int j = 0; j < 4; j++) {
                    gameboy->ppu.sprite_buffer[gameboy->ppu.sprite_count * 4 + j] = gameboy->oam[i * 4 + j];
                }
                gameboy->ppu.sprite_count++;
            }
        }

        gameboy->ppu.mode3_length = 172 + (gameboy->ppu.sprite_count * 8);
        gameboy->ppu.mode0_length = 456 - 80 - gameboy->ppu.mode3_length;

        gameboy->ppu.dots -= 80;

将所有多余的点保留,而不是设为0,下面给出前后对比的示例:

if (gameboy->ppu.dots >= 80) {
    gameboy->ppu.dots = 0;
    ...
}

// AFTER
if (gameboy->ppu.dots >= 80) {
    gameboy->ppu.dots -= 80;
    ...
}

现在它可以在没有任何缺失的画面碎片或视觉闪烁的情况下通过DMG-acid2测试。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章