245. Ppuscroll 第二次写入开关

实现 PPUSCROLL ($2005) 的两次写入滚动行为。

245 / 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_245_ppuscroll_second_write_toggle.py

实现 PPUSCROLL ($2005) 的两次写入滚动行为。!! 请慢慢实现这一步

参考资料

https://www.nesdev.org/wiki/PPU_registers#PPUSCROLL
https://www.nesdev.org/wiki/PPU_scrolling#$2005_(PPUSCROLL)_first_write_(w_is_0)
https://www.nesdev.org/wiki/PPU_scrolling#$2005_(PPUSCROLL)_second_write_(w_is_1)

要更新的文件

emulator/ppu/ppu.py

要更新的方法

PPU.write_register(addr, value)

为什么需要这一步

PPUSCROLL 是位于 $2005 的 CPU 可见寄存器。与 PPUADDR ($2006) 一样,它使用共享的第一次写入/第二次写入开关:

second_write_toggle == False -> first write
second_write_toggle == True  -> second write

第一次写入配置水平滚动

value bits 7-3 -> coarse X, stored in temp_vram_addr bits 0-4
value bits 2-0 -> fine X, stored in fine_x

第二次写入配置垂直滚动

value bits 7-3 -> coarse Y, stored in temp_vram_addr bits 5-9
value bits 2-0 -> fine Y, stored in temp_vram_addr bits 12-14

重要的内部寄存器模型

vram_addr           -> v, current VRAM address
temp_vram_addr      -> t, temporary VRAM address
fine_x              -> x, fine horizontal scroll
second_write_toggle -> w, first/second write toggle

建议的实现伪代码,与显式移动位的风格保持一致:

case 0x2005:
    # Keep for old compatibility.
    self.scroll = value

    if not self.second_write_toggle:
        # Reference:
        # https://www.nesdev.org/wiki/PPU_scrolling#$2005_(PPUSCROLL)_first_write_(w_is_0)
        #
        # PPU Scroll first write.
        # Incoming value bits are named ABCDEFGH.
        #
        # fine_x register:
        #     x: FGH <- value: .....FGH
        #
        # In other words, save the last 3 bits as fine horizontal scroll.
        self.fine_x = value & 0b0000_0111

        # temp_vram_addr register:
        #     t: ....... ...ABCDE <- value: ABCDE...
        #
        # In other words, save the upper 5 bits as coarse X in bits 0-4.
        # The mask keeps every temp_vram_addr bit except coarse X.
        self.temp_vram_addr = (
            (self.temp_vram_addr & 0b1111_1111_1110_0000)
            | (value >> 3)
        )

        # w <- 1
        # Next $2005/$2006 write will be treated as the second write.
        self.second_write_toggle = True
    else:
        # Reference:
        # https://www.nesdev.org/wiki/PPU_scrolling#$2005_(PPUSCROLL)_second_write_(w_is_1)
        #
        # PPU Scroll second write.
        # Incoming value bits are named ABCDEFGH.
        #
        # temp_vram_addr register:
        #     t: FGH..AB CDE..... <- value: ABCDEFGH
        #
        # Step 1: keep only untouched bits:
        #     - bit 15
        #     - nametable bits 10-11
        #     - coarse X bits 0-4
        #
        # This clears the vertical scroll destination bits:
        #     - coarse Y bits 5-9
        #     - fine Y bits 12-14
        self.temp_vram_addr = (
            self.temp_vram_addr & 0b1000_1100_0001_1111
        )

        # Step 2: put the last 3 bits into fine Y.
        #     value: .....FGH
        #     FGH << 12 -> bits 12-14
        self.temp_vram_addr = (
            self.temp_vram_addr | ((value & 0b0000_0111) << 12)
        )

        # Step 3: put the upper 5 bits into coarse Y.
        #     value: ABCDE...
        #     (value & 11111000) << 2 -> bits 5-9
        self.temp_vram_addr = (
            self.temp_vram_addr | ((value & 0b1111_1000) << 2)
        )

        # w <- 0
        # The two-write PPUSCROLL sequence is complete.
        self.second_write_toggle = False

超出范围

  • 在渲染期间把滚动位从 temp_vram_addr 复制到 vram_addr
  • 扫描线/周期时序
  • 实际的背景渲染

运行本课

uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_245_ppuscroll_second_write_toggle.py -v