e-tipsmemo.hatenablog.com
以前にBackgroundが実装されていたがその時にSpriteも実装していた。(前後関係がおかしいままであるが)
Sprite描写の実装は参考サイトとは少し違う
(参考サイトの実装方法ではSprite zero hitのタイミングが数pixelずれる気がしたので)
描写タイミング
Ppuの1Cycleに対応する関数内で、PPUが走査しているy座標に含まれるSpriteを探す処理を行う(sprite_evaluation)
pub fn run<'a>(nes: &'a Nes) -> impl Generator<Yield = PpuStep, Return = !> + 'a { let mut sprite_evaluation = Ppu::sprite_evaluation(nes); let mut run_renderer = Ppu::renderer(nes); move || loop { loop { match Pin::new(&mut sprite_evaluation).resume(()) { GeneratorState::Yielded(PpuStep::Cycle) => { break; } GeneratorState::Yielded(step) => { yield step; } } } loop { match Pin::new(&mut run_renderer).resume(()) { GeneratorState::Yielded(PpuStep::Cycle) => { break; } GeneratorState::Yielded(step) => { yield step; } } } yield PpuStep::Cycle; } }
あるy座標に描写される予定のスプライト情報(座標、スプライト番号、反転や優先度)は、Secondary OAMにストアされる。(最大8個)
PPU Frame Timingの下に、「Sprite evaluation for next scanline」というのがあり、すべてのVisible scanlineでこの処理が行われている。
Sprite Zero Hit
このエミュレータではSprite evaluationのときに、同時にスプライトがPrimary OAMの0番目であるフラグを持っておかないと、実際の描写時に、Sprite Zero Hitさせることができない。(と思う)
実装
fn sprite_evaluation<'a>(nes: &'a Nes) -> impl Generator<Yield = PpuStep, Return = !> + 'a { move || loop { for frame in 0.. { let frame_is_odd = frame % 2 != 0; for y in 0u16..cparams::FRAME_H as u16 { let should_skip_first_cycle = frame_is_odd && y == 0; if !should_skip_first_cycle { // The first cycle of each scanline is idle (except // for the first cycle of the pre-render scanline // for odd frames, which is skipped) // yield PpuStep::Cycle; } //x = 0; let oam = nes.ppu.oam(); nes.ppu.status.update(|s| s & !PpuStatus::OVERFLOW); if y < 240 || y == 261 { let mut oam_buf = [0xFF; 32]; let mut sprite_zero_flag = [false; 8]; let mut oam_buf_index = 0; //0 ~ 7 ......//Cycles //evaluate sprite for (i, _) in (64..=255).step_by(2).enumerate() { //Cycle 65-256 //i = 0 ~ 63? yield PpuStep::Cycle; yield PpuStep::Cycle; if i < 64 { let sprite_y = oam[i * 4].get() as u16; if sprite_y <= y && y < sprite_y + 8 { if oam_buf_index < 8 { let y = oam[i * 4 + 0].get(); let x = oam[i * 4 + 3].get(); oam_buf[oam_buf_index * 4 + 0] = oam[i * 4 + 0].get(); oam_buf[oam_buf_index * 4 + 1] = oam[i * 4 + 1].get(); oam_buf[oam_buf_index * 4 + 2] = oam[i * 4 + 2].get(); oam_buf[oam_buf_index * 4 + 3] = oam[i * 4 + 3].get(); sprite_zero_flag[oam_buf_index] = i == 0; oam_buf_index = oam_buf_index + 1; } else { nes.ppu.status.update(|s| s | PpuStatus::OVERFLOW); } } } } ......//Cycles for _ in 320..cparams::FRAME_W - 1 { yield PpuStep::Cycle; } nes.ppu.secondary_oam.set(oam_buf); nes.ppu.sprite_zero_flag.set(sprite_zero_flag); } else { for _ in 0..cparams::FRAME_W - 1 { nes.ppu.secondary_oam.set([0xFF; 32]); nes.ppu.sprite_zero_flag.set([false; 8]); yield PpuStep::Cycle; } }
対象のy座標にスプライトが入っていたら、oamから コピーして、最後にsecondary_OAMに入れる。
run_renderer ではそれを使用してBackgroundと描写する。
動作
まだrenderer側にspriteを表示する記事を書いていないが、実際は実装してそれなりに動作している。
giko008.nesやgiko009.nesが実行して、キャラを動かせる。
ドンキーコングはかなりよさそう。
しかしピーチが欠けている。
(Sprite Zero hitも実装していとマリオが動く)
マリオのキノコが透明なところがあったり、雲が赤色だったりするがゲームとしてプレイはできるようになった。
前後関係がおかしい
正しい表示
感想
それっぽくマリオが動いてしまったので正しい色を表示するPPUのデバッグをやる気がなくなってきた。。。