잔글편집 요약 없음
편집 요약 없음
385번째 줄: 385번째 줄:
==== 팔레트 RAM 읽기 ====
==== 팔레트 RAM 읽기 ====


이후 PPU는 $3F00-$3FFF에서 팔레트 데이터를 읽는 신뢰할 수 없는 기능을 추가했다. 이러한 읽기는 팔레트 RAM이 PPU 주소 공간에 오버레이된 PPU 내부의 별도 메모리 공간이므로 표준 VRAM 읽기와 다르게 작동한다. 참조된 6비트 팔레트 데이터는 내부 읽기 버퍼로 가지 않고 즉시 반환되므로 준비 읽기가 필요하지 않다. 동시에 PPU는 지정된 주소의 PPU 메모리에서 팔레트 데이터 "아래"에서 일반 읽기를 수행하고 이 읽기 결과는 평소와 같이 읽기 버퍼로 들어간다. 읽기 버퍼의 이전 내용은 팔레트를 읽을 때 버려지지만, 주소를 팔레트 RAM 외부로 변경하고 한 번 읽으면 이 그림자 메모리([[PPU memory map|일반적으로 미러링된 네임테이블]])의 내용에 접근할 수 있다. 팔레트 RAM 읽기를 지원하지 않는 PPU에서는 이 메모리 범위가 나머지 PPU 메모리와 동일하게 동작한다.
이후 PPU는 $3F00-$3FFF에서 팔레트 데이터를 읽는 신뢰할 수 없는 기능을 추가했다. 이러한 읽기는 팔레트 RAM이 PPU 주소 공간에 오버레이된 PPU 내부의 별도 메모리 공간이므로 표준 VRAM 읽기와 다르게 작동한다. 참조된 6비트 팔레트 데이터는 내부 읽기 버퍼로 가지 않고 즉시 반환되므로 준비 읽기가 필요하지 않다. 동시에 PPU는 지정된 주소의 PPU 메모리에서 팔레트 데이터 "아래"에서 일반 읽기를 수행하고 이 읽기 결과는 평소와 같이 읽기 버퍼로 들어간다. 읽기 버퍼의 이전 내용은 팔레트를 읽을 때 버려지지만, 주소를 팔레트 RAM 외부로 변경하고 한 번 읽으면 이 그림자 메모리([[PPU 메모리 맵|일반적으로 미러링된 네임테이블]])의 내용에 접근할 수 있다. 팔레트 RAM 읽기를 지원하지 않는 PPU에서는 이 메모리 범위가 나머지 PPU 메모리와 동일하게 동작한다.


이 기능은 2C02G, 2C02H 및 PAL PPU에서 지원된다. 팔레트를 읽을 때 반환되는 바이트는 상위 2비트에 [[Open_bus_behavior#PPU_open_bus|PPU 열린 버스]]를 포함하며, 값은 그레이스케일 모드에 의해 수정된 후 반환된다. 그레이스케일 모드가 활성화되면 하위 4비트가 지워진다. 불행히도 일부 콘솔에서는 마스터 클럭에 대한 4개의 CPU/PPU 정렬 중 하나에서 팔레트 읽기가 손상될 수 있다. 이 손상은 레지스터 접근을 나타내는 [[PPU pinout|PPU /CS]] 신호가 언제 비활성화되는지에 따라 다르며, 이는 콘솔마다 다르다. 이 기능이 모든 PPU에 있는 것은 아니라는 점과 결합하여 개발자는 팔레트 RAM에서 읽는 것에 의존해서는 안 된다.
이 기능은 2C02G, 2C02H 및 PAL PPU에서 지원된다. 팔레트를 읽을 때 반환되는 바이트는 상위 2비트에 [[Open_bus_behavior#PPU_open_bus|PPU 열린 버스]]를 포함하며, 값은 그레이스케일 모드에 의해 수정된 후 반환된다. 그레이스케일 모드가 활성화되면 하위 4비트가 지워진다. 불행히도 일부 콘솔에서는 마스터 클럭에 대한 4개의 CPU/PPU 정렬 중 하나에서 팔레트 읽기가 손상될 수 있다. 이 손상은 레지스터 접근을 나타내는 [[PPU pinout|PPU /CS]] 신호가 언제 비활성화되는지에 따라 다르며, 이는 콘솔마다 다르다. 이 기능이 모든 PPU에 있는 것은 아니라는 점과 결합하여 개발자는 팔레트 RAM에서 읽는 것에 의존해서는 안 된다.

2025년 10월 3일 (금) 22:09 판

해당 내용은 NES에 국한된 내용입니다.
해당 문서의 내용의 일부는 NES(패미컴) 외에서는 해당 내용이 적용되지 않을 수 있습니다.
해당 문서는 NesDev의 문서를 번역했습니다.
해당 문서의 전체 또는 일부는 NesDev의 문서를 번역했습니다. 원문서는 CC0 1.0 Universal이므로 가져와도 문제가 없습니다.
원글링크: [1]

PPU는 CPU에 8개의 메모리 매핑된 레지스터를 노출한다. 이것들은 명목상 CPU의 주소 공간에서 $2000부터 $2007까지 위치하지만, 주소가 불완전하게 디코딩되기 때문에 $2008부터 $3FFF까지 8바이트마다 미러링된다. 예를 들어, $3456에 쓰는 것은 $2006에 쓰는 것과 같다.

PPU는 전원 인가 또는 리셋 직후 렌더링을 시작하지만, 다음 프레임의 프리 렌더 스캔라인에 도달할 때까지 대부분의 레지스터($2000, $2001, $2005, $2006)에 대한 쓰기를 무시한다. 더 구체적으로, CPU와 PPU가 동시에 리셋된다고 가정할 때 약 29658 NTSC CPU 사이클 또는 33132 PAL CPU 사이클 동안이다. 자세한 내용은 PPU power up stateInit code를 참조하라.

요약

일반 이름 주소 비트 종류 비고
PPUCTRL $2000 VPHB SINN W NMI 활성화 (V), PPU 마스터/슬레이브 (P), 스프라이트 높이 (H), 배경 타일 선택 (B), 스프라이트 타일 선택 (S), 증가 모드 (I), 네임테이블 선택 / X 및 Y 스크롤 비트 8 (NN)
PPUMASK $2001 BGRs bMmG W 색상 강조 (BGR), 스프라이트 활성화 (s), 배경 활성화 (b), 스프라이트 왼쪽 열 활성화 (M), 배경 왼쪽 열 활성화 (m), 그레이스케일 (G)
PPUSTATUS $2002 VSO- ---- R vblank (V), 스프라이트 0 히트 (S), 스프라이트 오버플로 (O); 읽기는 $2005/$2006의 쓰기 쌍을 리셋한다
OAMADDR $2003 AAAA AAAA W OAM 읽기/쓰기 주소
OAMDATA $2004 DDDD DDDD RW OAM 데이터 읽기/쓰기
PPUSCROLL $2005 XXXX XXXX YYYY YYYY Wx2 X 및 Y 스크롤 비트 7-0 (두 번 쓰기: X 스크롤, 그 다음 Y 스크롤)
PPUADDR $2006 ..AA AAAA AAAA AAAA Wx2 VRAM 주소 (두 번 쓰기: 최상위 바이트, 그 다음 최하위 바이트)
PPUDATA $2007 DDDD DDDD RW VRAM 데이터 읽기/쓰기
OAMDMA $4014 AAAA AAAA W OAM DMA 상위 주소

레지스터 종류:

  • R - 읽기 가능
  • W - 쓰기 가능
  • x2 - 내부 2바이트 상태를 1바이트 접근 두 번으로 접근

MMIO 레지스터

PPU는 CPU와의 통신에 사용하는 내부 데이터 버스를 가지고 있다. 이 버스는 Visual 2C02에서 `_io_db`로, FCEUX에서는 `PPUGenLatch`로 불리며[1], PPU의 여러 부분으로 이어지는 매우 긴 트레이스의 커패시턴스로 인해 8비트 동적 래치처럼 동작한다. 명목상 읽기 전용인 PPUSTATUS를 포함한 모든 PPU 포트에 값을 쓰면 이 래치가 채워진다. 읽기 가능한 포트(PPUSTATUS, OAMDATA, 또는 PPUDATA)를 읽는 것도 읽은 비트로 래치를 채운다. 명목상 "쓰기 전용" 레지스터를 읽으면 래치의 현재 값이 반환되며, PPUSTATUS의 사용되지 않는 비트도 마찬가지다. 이 값의 적어도 하나의 비트는 3ms에서 30ms 후에 붕괴되며, PPU가 따뜻할 때 더 빠르다. [2]

PPUCTRL - 기타 설정 ($2000 쓰기)


7  bit  0
---- ----
VPHB SINN
|||| ||||
|||| ||++- 기본 네임테이블 주소
|||| ||    (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
|||| |+--- PPUDATA의 CPU 읽기/쓰기 당 VRAM 주소 증가
|||| |     (0: 1씩 증가, 가로로 진행; 1: 32씩 증가, 세로로 진행)
|||| +---- 8x8 스프라이트의 스프라이트 패턴 테이블 주소
||||       (0: $0000; 1: $1000; 8x16 모드에서는 무시됨)
|||+------ 배경 패턴 테이블 주소 (0: $0000; 1: $1000)
||+------- Sprite size (0: 8x8 픽셀; 1: 8x16 픽셀 – PPU OAM#Byte 1 참조)
|+-------- PPU 마스터/슬레이브 선택
|          (0: EXT 핀에서 배경 읽기; 1: EXT 핀에 색상 출력)
+--------- Vblank NMI 활성화 (0: 꺼짐, 1: 켜짐)

PPUCTRL ("컨트롤" 또는 "컨트롤러" 레지스터)은 렌더링, 스크롤 위치, vblank NMI, 그리고 듀얼 PPU 구성과 관련된 설정의 조합을 포함한다. 전원/리셋 후, 이 레지스터에 대한 쓰기는 첫 번째 프리 렌더 스캔라인까지 무시된다.

Vblank NMI

PPUCTRL에서 NMI를 활성화하면 vblank 시작 시(스캔라인 241, 도트 1) NMI 핸들러가 호출된다. 이것은 소프트웨어가 디스플레이의 프레임 속도로 실행될 수 있도록 신뢰할 수 있는 시간 소스를 제공하고, 소프트웨어에 vblank를 알린다. Vblank는 렌더링이 활성화된 상태에서 소프트웨어가 VRAM 및 OAM에 데이터를 보낼 수 있는 유일한 시간이며, 이 NMI는 vblank를 감지하는 유일한 신뢰할 수 있는 방법이다. PPUSTATUS에서 vblank 플래그를 폴링하는 것은 vblank를 완전히 놓칠 수 있다.

PPUSTATUS의 vblank 플래그가 1인 동안 NMI 활성화를 0에서 1로 변경하면 즉시 NMI가 트리거된다. 이것은 PPUSTATUS 레지스터가 아직 읽히지 않은 경우 vblank 중에 발생한다. NMI 루틴이 vblank에서 너무 늦게 실행되어 제시간에 끝나지 못하게 하여 그래픽 결함을 일으키거나, 게임이 실제로 발생한 것보다 더 많은 프레임을 처리하게 할 수 있다. 이 문제를 피하려면, PPUCTRL에서 NMI를 활성화하기 전에 먼저 PPUSTATUS를 읽어 vblank 플래그를 지우는 것이 신중하다.

스크롤링

PPUCTRL 비트 0과 1의 현재 네임테이블 비트는 스크롤 좌표의 최상위 비트로 간주될 수 있으며, 이는 9비트 너비이다(네임테이블PPUSCROLL 참조):

7  bit  0
---- ----
.... ..YX
       ||
       |+- X 스크롤 위치 비트 8 (즉, X에 256을 더함)
       +-- Y 스크롤 위치 비트 8 (즉, Y에 240을 더함)

이 두 비트는 PPUSCROLL에 기록된 값과 동일한 내부 t 레지스터로 이동하며, 스크롤 위치를 완전히 지정하려면 PPUSCROLL과 함께 기록되어야 한다.

마스터/슬레이브 모드와 EXT 핀

PPUCTRL의 비트 6은 PPU를 손상시킬 수 있으므로 순정 콘솔에서는 절대 설정해서는 안 된다.

이 비트가 클리어되면(일반적인 경우), PPU는 EXT 핀에서 배경색에 대한 팔레트 인덱스를 가져온다. 순정 NES는 이 핀들을 접지하여 예상대로 팔레트 인덱스 0을 배경색으로 만든다. EXT 핀에 연결된 보조 그림 생성기는 배경 팔레트의 색상을 사용하여 배경을 다른 이미지로 교체할 수 있으며, 이는 시차 스크롤링과 같은 기능에 사용될 수 있다.

비트 6을 설정하면 PPU는 각 픽셀에 대해 EXT 핀에 팔레트 메모리 인덱스의 하위 4비트를 출력한다. 4비트만 출력되므로 일반적으로 배경과 스프라이트 픽셀을 이 방법으로 구별할 수 없다. 이 비트를 설정해도 PPU의 컴포지트 비디오 출력의 이미지는 영향을 받지 않는다. 수정되지 않은 NES에서는 EXT 핀이 접지되어 있으므로, 비트 6을 설정하는 것은 0이 아닌 픽셀 값을 출력할 때마다 칩을 손상시킬 수 있으므로 권장되지 않는다(실질적으로 Vcc와 GND를 함께 단락시키기 때문). 투명 픽셀에 대한 EXT 출력은 일반적인 배경색이 아니라 해당 배경 슬라이버 팔레트의 항목 0이라는 점에 유의하라. 렌더링이 비활성화되면 배경 재정의에 관계없이 EXT 출력은 항상 인덱스 0이다.

비트 0 경쟁 조건

수평 네임테이블 배열(일명 수직 미러링) 또는 4화면 VRAM을 사용하는 경우 vblank 외부에서 이 레지스터에 쓸 때 주의하라. 특정 CPU-PPU 정렬의 경우, 도트 257에서 시작하는 쓰기는 다음 스캔라인만 왼쪽 네임테이블에서 잘못 그려지게 한다. 이것은 눈에 보이는 결함을 유발할 수 있으며, 해당 스캔라인에 대한 스프라이트 0 히트를 방해할 수도 있다(잘못된 배경으로 그려지기 때문). 이 결함은 수평 또는 단일 화면 미러링에서는 왼쪽과 오른쪽 네임테이블이 동일하기 때문에 효과가 없다. 도트 257에서 시작하여 도트 258까지 계속되는 쓰기만 이 결함을 유발할 수 있다. 다른 모든 수평 타이밍은 안전하다. 이 결함은 구체적으로 열린 버스의 값을 레지스터에 쓰는데, 이는 거의 항상 주소의 상위 바이트가 될 것이다. 원하는 네임테이블에 따라 이 레지스터 또는 $2100에 있는 이 레지스터의 미러에 쓰는 것이 기능적인 해결 방법으로 보인다.

이것은 슈퍼 마리오 브라더스에서 프로그램이 게임 로직 끝에 PPUCTRL에 쓸 때 가끔 보이는 결함을 생성한다. 게임 로직 중에 NMI를 껐다가 게임 로직이 끝나면 NMI를 다시 켜서 게임 로직이 끝나기 전에 NMI 핸들러가 다시 호출되는 것을 방지하는 것으로 보인다. 또 다른 해결 방법은 PPU의 NMI 활성화를 사용하는 대신 소프트웨어 플래그를 사용하여 NMI 재진입을 방지하는 것이다.

PPUMASK - 렌더링 설정 ($2001 쓰기)


7  bit  0
---- ----
BGRs bMmG
|||| ||||
|||| |||+- 그레이스케일 (0: 일반 색상, 1: 그레이스케일)
|||| ||+-- 1: 화면 왼쪽 8픽셀에 배경 표시, 0: 숨기기
|||| |+--- 1: 화면 왼쪽 8픽셀에 스프라이트 표시, 0: 숨기기
|||| +---- 1: 배경 렌더링 활성화
|||+------ 1: 스프라이트 렌더링 활성화
||+------- 빨간색 강조 (PAL/Dendy에서는 녹색)
|+-------- 녹색 강조 (PAL/Dendy에서는 빨간색)
+--------- 파란색 강조

PPUMASK ("마스크" 레지스터)는 스프라이트와 배경의 렌더링 및 색상 효과를 제어한다. 전원/리셋 후, 이 레지스터에 대한 쓰기는 첫 번째 프리 렌더 스캔라인까지 무시된다.

가장 일반적으로, PPUMASK는 게임 플레이 외부에서 VRAM으로 대량의 데이터를 전송할 수 있도록 $00으로 설정되고, 게임 플레이 중에는 색상 효과 없이 모든 렌더링을 활성화하기 위해 $1E로 설정된다.

렌더링 제어

렌더링은 PPU가 메모리를 적극적으로 가져와 화면에 이미지를 그리는 과정이다. PPUMASK에서 스프라이트 및 배경 렌더링 중 하나 또는 둘 다 활성화되어 있는 한 렌더링 전체가 활성화된다. 한 구성 요소가 활성화되고 다른 구성 요소가 활성화되지 않은 경우, 비활성화된 구성 요소는 단순히 투명하게 처리된다. 렌더링 프로세스는 그 외에는 영향을 받지 않는다. 비트 3과 4를 통해 두 구성 요소가 모두 비활성화되면 렌더링 프로세스가 중지되고 PPU는 배경색을 표시한다.

렌더링 중에는 PPU가 VRAM과 OAM을 적극적으로 사용한다. 이로 인해 CPU는 PPUDATA를 통해 VRAM에 접근하거나 OAMDATA를 통해 OAM에 접근할 수 없으므로, 이러한 접근은 렌더링 외부에서 수행되어야 한다. 즉, vblank 중(게임 플레이 중 데이터 전송) 또는 렌더링이 꺼진 상태(레벨 로드와 같은 대규모 데이터 전송)에서 수행해야 한다. 수많은 하드웨어 버그와 제한을 피하기 위해, 일반적으로 렌더링은 vblank 중에만 켜거나 끄는 것이 좋다. 이는 원하는 PPUMASK 값을 레지스터 자체가 아닌 변수에 쓰고 NMI 핸들러의 vblank 중에만 해당 변수를 PPUMASK에 복사하여 수행할 수 있다.

PPU는 화면의 가장 왼쪽 8픽셀에서만 스프라이트와 배경을 선택적으로 숨길 수 있으며, 이를 투명하게 만들어 배경색을 그린다. 스프라이트의 경우, 이는 스프라이트가 오른쪽 가장자리에서처럼 화면 왼쪽 가장자리에서 부분적으로 걸칠 수 없는 제한인 스프라이트 팝인을 피하는 데 유용할 수 있다. 배경의 경우, 이는 수직 또는 단일 화면 네임테이블 배열로 수평으로 스크롤할 때 타일 아티팩트를 제거하고 속성 아티팩트를 줄일 수 있다. 이러한 배열은 스크롤 이음새를 화면 밖으로 숨길 수 없기 때문이다. 배경색은 배경 아트에 사용된 색상과 일치하지 않을 수 있으므로 왼쪽 열을 비활성화하는 것이 사소한 아티팩트보다 더 산만할 수 있다.

참고:

  • 렌더링 중에 PPUDATA에 쓰면 VRAM이 손상될 수 있으므로, 쓰기는 vblank 중이거나 PPUMASK 비트 3과 4에서 렌더링이 비활성화된 상태에서 수행해야 한다.
  • 배경이나 스프라이트가 비활성화된 영역에서는 스프라이트 0 히트가 트리거되지 않는다.
  • 렌더링 토글은 쓰기 후 약 3-4 도트 후에 적용된다. 이 지연은 Battletoads가 충돌을 피하기 위해 필요하다.
  • 화면 중간에 렌더링을 토글하면 종종 OAM의 1개 행이 손상되고 현재 및 다음 스캔라인에 대해 잘못된 스프라이트가 그려진다. (참조: 정오표)
  • 화면 중간에 렌더링을 끄면 내부 v 레지스터의 하위 14비트 값이 $3C00-$3FFF 사이인 경우 팔레트 RAM이 손상될 수 있다.
  • 렌더링을 늦게 켜면 프리 렌더 끝의 도트가 절대 건너뛰지 않게 되어 정지된 화면에서 도트 크롤이 발생할 수 있다.
  • 렌더링을 늦게 켜면 복잡한 쓰기 시리즈로 수동으로 설정하지 않는 한 PPU의 스크롤 값이 잘못된다.

색상 제어

그레이스케일 모드는 모든 색상을 회색 또는 흰색 음영으로 강제한다. 이것은 색상을 $30과 비트 AND 연산하여 수행되며, 모든 색상이 회색 열($00, $10, $20, $30)에서 나오게 한다. 이 열에는 검은색이 없다는 점에 유의하라. 이 AND 동작은 색상이 뒤섞인 RGB PPU(2C04 시리즈)가 실제로 회색 음영을 얻는 것이 아니라 $x0 열에 있는 색상을 얻는다는 것을 의미한다. 팔레트 RAM에서 읽을 때 반환된 값은 이 AND 동작을 반영하지만 기본 데이터는 보존된다. 팔레트 쓰기는 그레이스케일 모드에 관계없이 정상적으로 작동한다.

Color emphasis는 다른 두 색상 구성 요소를 어둡게 하여 선택한 구성 요소를 비교적 밝게 만들어 강조하는 색조 효과를 유발한다. 3가지 구성 요소를 모두 강조하면 모든 색상이 단순히 어두워진다. 이것은 그레이스케일과 독립적으로 작동하여 회색을 착색할 수 있다. PAL 및 Dendy PPU는 강조 비트 순서가 다르므로 포트 및 이중 지역 게임은 비트를 재정렬해야 한다. 또한 RGB PPU의 강조는 완전히 다르며, 대신 강조된 구성 요소의 밝기를 최대화하고 모든 구성 요소가 강조될 때 완전히 흰색 화면을 생성한다. RGB 강조는 훨씬 덜 유용하며 일반적으로 피하는 것이 가장 좋다.

PPUSTATUS - 렌더링 이벤트 ($2002 읽기)


7  bit  0
---- ----
VSOx xxxx
|||| ||||
|||+-++++- (PPU 열린 버스 또는 2C05 PPU 식별자)
||+------- 스프라이트 오버플로 플래그
|+-------- 스프라이트 0 히트 플래그
+--------- Vblank 플래그, 읽을 때 지워짐. 신뢰할 수 없음; 아래 참조.

PPUSTATUS ("상태" 레지스터)는 렌더링 관련 이벤트의 상태를 반영하며 주로 타이밍에 사용된다. 이 레지스터의 세 플래그는 프리렌더 스캔라인의 도트 1에서 자동으로 지워진다. 설정 및 해제 타이밍에 대한 자세한 내용은 PPU 렌더링을 참조하라.

이 레지스터를 읽으면 PPU의 내부 w 레지스터가 지워지는 부작용이 있다. 쓰기가 올바른 순서로 이루어지도록 PPUSCROLLPPUADDR에 쓰기 전에 일반적으로 읽는다.

Vblank 플래그

vblank 플래그는 vblank 시작 시(스캔라인 241, 도트 1) 설정된다. PPUSTATUS를 읽으면 이 플래그의 현재 상태가 반환된 다음 지워진다. 읽어서 vblank 플래그가 지워지지 않으면 프리렌더 스캔라인의 도트 1에서 자동으로 지워진다.

vblank 플래그를 읽는 것은 vblank를 감지하는 신뢰할 수 있는 방법이 아니다. 대신 NMI를 사용해야 한다. 플래그가 설정되기 전 도트(스캔라인 241, 도트 0)에서 플래그를 읽으면 0으로 읽히고 지워지므로, vblank 플래그에 대해 PPUSTATUS를 폴링하면 vblank를 놓치고 게임이 버벅거릴 수 있다. 이 경우 NMI도 억제되며, 다음 도트 또는 두 도트에 걸친 읽기에 의해서도 억제될 수 있다. NTSC 및 PAL에서는 플래그가 두 프레임 연속으로 떨어지지 않는 것이 보장되지만, Dendy에서는 매 프레임마다 발생하여 게임이 충돌할 수 있다. NMI를 사용하면 소프트웨어가 매 프레임마다 vblank를 올바르게 감지할 수 있다. 또한 PlayChoice-10에서도 필요하며, NMI가 너무 오랫동안 비활성화되면 게임을 거부한다. 콘솔을 부팅하는 동안에는 vblank 플래그를 폴링해야 하지만, 이 시점의 타이밍은 중요하지 않다(안전한 부팅에 대한 자세한 내용은 Init code 참조).

vblank 플래그는 NMI 생성에 사용되며, 이 플래그가 1인 동안 NMI를 활성화하면 즉시 NMI가 발생한다(PPUCTRL 참조).

스프라이트 0 히트 플래그

스프라이트 0 히트는 OAM의 첫 번째 스프라이트(스프라이트 0)와 배경 간의 픽셀 단위 충돌을 감지하는 하드웨어 충돌 감지 기능이다. 스프라이트 0 히트 플래그는 스프라이트 0의 불투명한 픽셀이 배경의 불투명한 픽셀과 겹칠 때 즉시 설정되며, 스프라이트 우선 순위와는 무관하다. '불투명'은 픽셀이 '투명'하지 않다는 것을 의미한다. 즉, 두 패턴 비트가 %00이 아니다. 플래그는 프리렌더 스캔라인의 도트 1까지 설정된 상태를 유지하므로 프레임당 하나의 충돌만 감지할 수 있다.

이 플래그는 충돌을 감지하지만 주로 타이밍에 사용된다. 많은 게임에서 스프라이트 0을 화면의 고정된 위치에 배치하고 이 플래그가 설정될 때까지 폴링한다. 이를 통해 CPU는 화면에서 대략적인 위치를 파악하여 하드웨어 레지스터에 대한 화면 중간 쓰기 시간을 맞출 수 있다. 일반적으로 이것은 슈퍼 마리오 브라더스와 같이 배경 기반 HUD를 허용하기 위해 화면 중간에 스크롤 위치를 변경하는 데 사용된다. 그러나 일부 최신 홈브루 게임은 Lunar LimitIrritating Ship과 같이 실제 충돌에 이를 사용한다.

스프라이트 0 히트는 X=255에서 또는 PPUMASK를 통해 스프라이트나 배경이 비활성화된 곳에서는 충돌을 감지할 수 없다. 여기에는 가장 왼쪽 8픽셀이 숨겨져 있을 때 X=0..7이 포함된다. 그러나 PAL의 왼쪽 및 오른쪽 가장자리 자르기에는 영향을 받지 않는다.

타이밍에 이 플래그를 사용할 때 몇 가지 중요한 고려 사항이 있다.

  • 스프라이트 0 히트는 프리렌더 스캔라인까지 지워지지 않으므로 소프트웨어는 이전 프레임의 히트를 현재 프레임의 히트로 착각할 수 있다. 따라서 플래그가 다시 설정되기를 기다리기 전에 플래그가 지워질 때까지 폴링해야 할 수 있다.
  • 게임이 스프라이트 0 히트가 발생할 것으로 예상하고 발생하지 않으면 종종 충돌이 발생한다. 히트가 발생하지 않을 위험이 있는 경우(아마도 스크롤할 때 겹침이 발생하지 않거나 전원 주기, 콘솔 또는 에뮬레이터에 따라 달라질 수 있는 정확한 화면 중간 타이밍에 의존하기 때문에) 폴링 루프를 빠져나갈 다른 방법이 있는 것이 중요하다. 예를 들어, vblank 플래그를 폴링하거나 NMI 핸들러가 게임이 여전히 스프라이트 0 히트를 폴링하고 있는지 확인하여 이를 수행할 수 있다.
  • 게임은 종종 랙 프레임에서 스프라이트 0 히트를 처리하지 않아 화면 중간 이벤트가 발생하지 않는다. 이로 인한 일반적인 결과는 랙 중에 HUD가 깜박이는 것이다. 적어도 랙 프레임에서 NMI 핸들러에서 스프라이트 0 히트를 처리하면 이 문제를 해결할 수 있다.

스프라이트 오버플로 플래그

스프라이트 오버플로 플래그는 스캔라인에 8개 이상의 스프라이트가 있을 때마다 설정되도록 의도되었다. 불행히도 이를 감지하는 논리가 제대로 작동하지 않아 PPU가 9번째 스프라이트를 검색할 때 OAM에서 잘못된 인덱스를 확인하게 된다. 이로 인해 거짓 양성과 거짓 음성이 모두 발생한다. 잘못된 동작에 대한 자세한 내용은 PPU 스프라이트 평가를 참조하라. 실제로 스프라이트 오버플로는 스프라이트 0 히트와 같이 타이밍에 사용되지만, 버그가 있는 동작과 9개의 스프라이트 타일 비용 때문에 일반적으로 두 개 이상의 타이밍 소스가 필요할 때만 사용된다. 스프라이트 0 히트와 마찬가지로 이 플래그는 프리렌더 스캔라인 시작 시 지워지며 프레임당 한 번만 설정할 수 있다.

스프라이트 오버플로를 사용하는 것은 종종 최후의 수단이다. 매퍼 IRQ를 사용할 수 없는 경우 DMC IRQ는 타이밍에 효과적인 대안이 될 수 있지만 사용하기 복잡하다.

2C05 식별자

2C05 시리즈 아케이드 PPU는 PPU 열린 버스 대신 비트 4-0에 식별자를 반환한다. 이 값은 일종의 복사 방지 형태로 게임에서 확인한다. 이는 소비자용 2C05-99에는 적용되지 않으며, 평소와 같이 열린 버스를 반환한다. PPU에서 직접 데이터를 수집하지는 않았지만 2C05 게임은 다음 값을 예상한다.

PPU 마스크
2C05-02 $3F $3D
2C05-03 $1F $1C
2C05-04 $1F $1B

OAMADDR - 스프라이트 RAM 주소 ($2003 쓰기)


7  bit  0
---- ----
AAAA AAAA
|||| ||||
++++-++++- OAM 주소

여기에 접근하려는 OAM의 주소를 쓴다. 대부분의 게임은 여기에 $00을 쓰고 OAMDMA를 사용한다. (DMA는 2A03/7 칩에 구현되어 있으며 OAMDATA에 반복적으로 쓰는 방식으로 작동한다)

렌더링 중 값

OAMADDR은 프리렌더 및 가시 스캔라인의 257-320 틱(스프라이트 타일 로딩 간격) 각각 동안 0으로 설정된다. 이는 또한 정상적으로 완료된 렌더링 프레임의 끝에서 OAMADDR이 항상 0으로 돌아온다는 것을 의미한다.

렌더링이 스캔라인 중간에 활성화되면[3], 가시 스캔라인의 틱 65에서 OAM 스프라이트 평가가 시작되기 전에 OAMADDR이 0으로 설정되지 않은 경우 추가적인 결과가 발생한다. 이 틱에서의 OAMADDR 값은 이 스캔라인에 대한 스프라이트 평가의 시작 주소를 결정하며, 이로 인해 OAMADDR의 스프라이트가 sprite-0 hit 및 우선 순위 모두에 대해 스프라이트 0인 것처럼 처리될 수 있다. OAMADDR이 정렬되지 않고 OAM 항목의 Y 위치(첫 번째 바이트)를 가리키지 않으면, 가리키는 모든 것(타일 인덱스, 속성 또는 X 좌표)이 Y 위치로 재해석되고 다음 바이트도 유사하게 재해석된다. OAM의 끝에 도달하면 더 이상 스프라이트가 발견되지 않으며, 시작 OAMADDR 이전의 모든 스프라이트를 효과적으로 숨긴다.

OAMADDR 주의사항

2C02G에서는 OAMADDR에 쓰면 OAM이 손상된다. 정확한 손상은 완전히 설명되지 않았지만, 이는 일반적으로 대상 주소의 8바이트 행 위에 스프라이트 8과 9(주소 $20)를 복사하는 것으로 보인다. 이 복사의 소스 주소는 CPU 버스의 이전 값(가장 자주 $2003 피연산자에서 $20)에서 오는 것으로 보인다.[3][4] 다른 가능한 동작도 있을 수 있다. 그런 다음 OAM의 256바이트를 모두 써서 이 문제를 해결할 수 있지만, OAM 붕괴가 시작되기 전의 제한된 시간으로 인해 일반적으로 OAMDMA를 통해 수행해야 한다.

또한 렌더링이 시작될 때 OAMADDR이 8보다 작지 않으면 OAMADDR & 0xF8에서 시작하는 8바이트가 OAM의 첫 8바이트에 복사되는 경우도 있다. 이것이 관련이 있을 가능성이 높다. Dendy에서는 후자의 버그가 2C02 호환성을 위해 필요하다.

2C03, 2C04, 2C05[5] 및 2C07에서는 OAMADDR이 의도한 대로 작동하는 것으로 알려져 있다. 이 버그가 2C02의 모든 리비전에 존재하는지는 알려져 있지 않다.

OAMDATA - 스프라이트 RAM 데이터 ($2004 읽기/쓰기)


7  bit  0
---- ----
DDDD DDDD
|||| ||||
++++-++++- OAM 데이터

여기에 OAM 데이터를 쓴다. 쓰기는 쓰기 후 OAMADDR을 증가시킨다. 읽기는 그렇지 않다. 수직 또는 강제 블랭킹 중 읽기는 해당 주소의 OAM에서 값을 반환한다.

대부분의 경우 이 레지스터에 직접 쓰지 마라. OAM에 대한 변경은 일반적으로 vblank 중에만 이루어져야 하므로, OAMDATA를 통한 쓰기는 부분 업데이트에만 효과적이다. 한 vblank 간격 내에 모든 OAM을 업데이트하기에는 너무 느리고, 위에서 설명한 것처럼 부분 쓰기는 손상을 유발하기 때문이다. 대부분의 게임은 대신 OAMDMA를 통해 DMA 기능을 사용한다.

  • PPU가 렌더링하는 동안 OAMDATA를 읽으면 스프라이트 평가 및 로딩 중 내부 OAM 접근이 노출된다. Micro Machines가 이를 수행한다.
  • 렌더링 중(프리 렌더 라인 및 가시 라인 0-239에서, 스프라이트 또는 배경 렌더링이 활성화된 경우) OAMDATA에 대한 쓰기는 OAM의 값을 수정하지 않지만, OAMADDR의 결함 있는 증가를 수행하여 상위 6비트만 증가시킨다(즉, PPU sprite evaluation에서 [n] 값을 증가시킨다. 스프라이트 평가의 현재 상태에 따라 하위 비트를 대신 증가시킬 수도 있다). 이는 $2004에 대한 쓰기를 사용하므로 OAMDMA를 통한 DMA 전송에도 적용된다. 에뮬레이션 목적으로는 렌더링 중 쓰기를 완전히 무시하는 것이 가장 좋다.
  • 이전에는 이 레지스터에서 읽는 것이 신뢰할 수 없다고 생각되었다[6]. 그러나 최근 증거에 따르면 이는 전적으로 OAMADDR 쓰기에 의한 손상 때문인 것으로 보인다.
  • 초기 Famicom 및 NES에서 발견된 가장 오래된 PPU 구현에서는 이 레지스터를 읽을 수 없다[7]. 읽기 기능은 대부분의 NES 및 이후 Famicom에서 발견된 RP2C02G에 추가되었다.[8]
  • 2C07에서는 스프라이트 평가를 절대 완전히 비활성화할 수 없으며, vblank 시작 후 24 스캔라인 후에 항상 시작된다[9](2C02에서 프리렌더 스캔라인이 있었을 때와 동일). 따라서 OAM에 대한 모든 업데이트는 2C07이 수직 블랭킹을 신호한 후 처음 24 스캔라인 내에 수행되어야 한다.

PPUSCROLL - X 및 Y 스크롤 ($2005 쓰기)


1번째 쓰기
7  bit  0
---- ----
XXXX XXXX
|||| ||||
++++-++++- X 스크롤 비트 7-0 (PPUCTRL 비트 0의 비트 8)

2번째 쓰기
7  bit  0
---- ----
YYYY YYYY
|||| ||||
++++-++++- Y 스크롤 비트 7-0 (PPUCTRL 비트 1의 비트 8)

이 레지스터는 스크롤 위치를 변경하는 데 사용되며, PPUCTRL을 통해 선택된 네임테이블의 어떤 픽셀이 렌더링된 화면의 왼쪽 상단 모서리에 있어야 하는지 PPU에 알려준다. PPUSCROLL은 두 번의 쓰기를 필요로 한다. 첫 번째는 X 스크롤이고 두 번째는 Y 스크롤이다. 이것이 첫 번째 쓰기인지 두 번째 쓰기인지는 PPUADDR과 공유되는 w 레지스터에 의해 내부적으로 추적된다. 일반적으로 이 레지스터는 다음 프레임이 원하는 위치에서 렌더링을 시작하도록 수직 블랭킹 중에 쓰여지지만, 화면을 분할하기 위해 렌더링 중에 수정될 수도 있다. 렌더링 중에 수직 스크롤을 변경하면 다음 프레임에만 적용된다. PPUCTRL의 네임테이블 비트와 함께 스크롤은 구성 요소당 9비트로 생각할 수 있으며, 스크롤 위치를 완전히 지정하려면 PPUSCROLL과 함께 PPUCTRL을 업데이트해야 한다.


PPU 스크롤 레지스터는 PPU 주소 레지스터와 내부 상태를 공유한다. 이 때문에 PPUSCROLL 및 PPUCTRL의 네임테이블 비트는 PPUADDR에 대한 모든 쓰기 후에 쓰여져야 한다.

w (쓰기 래치)를 지우기 위해 PPUSTATUS를 읽은 후, 화면을 켜기 직전에 PPUSCROLL에 수평 및 수직 스크롤 오프셋을 쓴다.

 ; X 및 Y 스크롤의 상위 비트를 설정한다.
 lda ppuctrl_value
 ora current_nametable
 sta PPUCTRL

 ; X 및 Y 스크롤의 하위 8비트를 설정한다.
 bit PPUSTATUS
 lda cam_position_x
 sta PPUSCROLL
 lda cam_position_y
 sta PPUSCROLL

수평 오프셋은 0에서 255까지의 범위를 갖는다. "정상적인" 수직 오프셋은 0에서 239까지의 범위를 가지며, 240에서 255까지의 값은 현재 네임테이블 끝의 속성 데이터가 타일 데이터로 잘못 사용되게 한다. PPU는 일반적으로 239에서 다음 네임테이블의 0으로 자동으로 건너뛰므로 이러한 "잘못된" 스크롤 위치는 명시적으로 기록된 경우에만 발생한다.

여기서 여러 프레임에 걸쳐 스크롤 값을 변경하고 새로 드러난 네임테이블 영역에 타일을 쓰면 큰 배경 위로 카메라가 패닝하는 효과를 얻을 수 있다.

PPUADDR - VRAM 주소 ($2006 쓰기)


1번째 쓰기  2번째 쓰기
15 bit  8  7  bit  0
---- ----  ---- ----
..AA AAAA  AAAA AAAA
  || ||||  |||| ||||
  ++-++++--++++-++++- VRAM 주소

CPU와 PPU는 별도의 버스에 있기 때문에 어느 쪽도 다른 쪽의 메모리에 직접 접근할 수 없다. CPU는 PPU의 한 쌍의 레지스터를 통해 VRAM에 쓴다. 먼저 PPUADDR에 주소를 로드한 다음 PPUDATA에 데이터를 반복적으로 쓴다. 각 PPUDATA 접근은 PPUCTRL에 구성된 대로 주소를 자동으로 1 또는 32만큼 증가시키므로 VRAM 주소는 모든 데이터 쓰기 시리즈에 대해 한 번만 설정하면 된다.

16비트 주소는 PPUADDR에 한 번에 한 바이트씩, 상위 바이트부터 쓴다. 이것이 첫 번째 쓰기인지 두 번째 쓰기인지는 PPU의 내부 w 레지스터에 의해 추적되며, 이는 PPUSCROLL과 공유된다. w가 0이 아니거나 그 상태를 알 수 없는 경우, 주소를 쓰기 전에 PPUSTATUS를 읽어 지워야 한다. 예를 들어, w가 0으로 알려진 후 VRAM 주소를 $2108로 설정하려면:

  lda #$21
  sta PPUADDR
  lda #$08
  sta PPUADDR

PPU 주소 공간은 14비트로, $0000-$3FFF에 걸쳐 있다. 이 레지스터에 기록된 값의 비트 14와 15는 무시된다. 그러나 PPUADDR에 기록된 데이터를 보유하는 내부 t 레지스터의 비트 14는 PPUADDR 상위 바이트를 쓸 때 0으로 강제된다. 이 세부 사항은 VRAM 주소를 설정하기 위해 PPUADDR을 사용할 때는 중요하지 않지만, 화면 중간 스크롤을 제어하는 데 사용할 때는 중요한 제한 사항이다(자세한 내용은 PPU scrolling 참조).

참고

화면 새로 고침 중 PPUSCROLLPPUADDR에 접근하면 흥미로운 래스터 효과가 발생한다. 각 스캔라인의 시작 위치는 네임테이블 메모리의 모든 픽셀 위치로 설정할 수 있다. 자세한 내용은 PPU scrolling을 참조하라.

팔레트 손상

특정 상황에서 PPU 팔레트의 항목이 손상될 수 있다. 이것이 정확히 어떻게 또는 왜 발생하는지는 불분명하지만, NTSC PPU의 모든 리비전은 적어도 어느 정도 취약한 것으로 보인다.[10]

팔레트 메모리에 쓰기를 마쳤을 때의 해결 방법은 항상 다음과 같다.

  1. 필요한 경우 주소를 업데이트하여 $3F00, $3F10, $3F20 또는 다른 미러를 가리키도록 한다.
  2. 그런 다음에만 주소를 팔레트 메모리 외부를 가리키도록 변경한다.

이 해결 방법을 구현하는 코드 조각은 수많은 게임에 존재한다.[11]

  lda #$3F
  sta PPUADDR
  lda #0
  sta PPUADDR
  sta PPUADDR
  sta PPUADDR

버스 충돌

래스터 효과 중, PPUADDR에 대한 두 번째 쓰기가 특정 시간에 발생하면 최대 한 축의 스크롤링이 기록된 값과 현재 값의 비트 AND로 설정된다. 두 번째 쓰기를 완료하는 유일한 안전한 시간은 블랭킹 중이다. 더 구체적인 타이밍은 PPU scrolling을 참조하라. [2]

PPUDATA - VRAM 데이터 ($2007 읽기/쓰기)


7  bit  0
---- ----
DDDD DDDD
|||| ||||
++++-++++- VRAM 데이터

VRAM 읽기/쓰기 데이터 레지스터. 접근 후, 비디오 메모리 주소는 $2000의 비트 2에 의해 결정된 양만큼 증가한다.

PPUMASK로 배경/스프라이트 렌더링 플래그를 비활성화하여 화면이 꺼지거나 수직 블랭킹 중에 이 포트를 통해 VRAM에서 데이터를 읽거나 쓸 수 있다. 이 레지스터에 접근하면 VRAM 주소가 증가하므로, 그래픽 결함을 유발하고 쓰기 시 예측할 수 없는 VRAM 주소에 쓰게 되므로 수직 또는 강제 블랭킹 외부에서는 접근해서는 안 된다. 그러나 소수의 게임은 렌더링 중에 PPUDATA에서 읽는 것으로 알려져 있으며, 이로 인해 스크롤 위치가 변경된다. PPU 스크롤링Tricky-to-emulate games를 참조하라.

VRAM 읽기 및 쓰기는 렌더링이 사용하는 것과 동일한 내부 주소 레지스터를 공유한다. 따라서 비디오 메모리에 데이터를 로드한 후, 프로그램은 잘못된 스크롤링을 피하기 위해 PPUSCROLLPPUCTRL (비트 1-0) 쓰기로 스크롤 위치를 다시 로드해야 한다.

PPUDATA 읽기 버퍼

PPUDATA에서 읽는 것은 현재 VRAM 주소의 값을 직접 반환하는 것이 아니라 내부 읽기 버퍼의 내용을 반환한다. 이 읽기 버퍼는 모든 PPUDATA 읽기에서 업데이트되지만, 이전 내용이 CPU로 반환된 후에만 업데이트되므로 PPUDATA 읽기를 효과적으로 하나 지연시킨다. 이는 PPU 버스 읽기가 너무 느려서 CPU 읽기를 서비스할 시간에 완료할 수 없기 때문이다. 이 읽기 버퍼 때문에, PPUADDR을 통해 VRAM 주소를 설정한 후에는 먼저 PPUDATA를 읽어 읽기 버퍼를 준비(결과 무시)한 다음 원하는 데이터를 읽어야 한다.

읽기 버퍼는 PPUDATA 읽기에서만 업데이트된다는 점에 유의하라. 쓰기나 렌더링과 같은 다른 PPU 프로세스에는 영향을 받지 않으며, 다음 읽기까지 값을 무기한 유지한다.

팔레트 RAM 읽기

이후 PPU는 $3F00-$3FFF에서 팔레트 데이터를 읽는 신뢰할 수 없는 기능을 추가했다. 이러한 읽기는 팔레트 RAM이 PPU 주소 공간에 오버레이된 PPU 내부의 별도 메모리 공간이므로 표준 VRAM 읽기와 다르게 작동한다. 참조된 6비트 팔레트 데이터는 내부 읽기 버퍼로 가지 않고 즉시 반환되므로 준비 읽기가 필요하지 않다. 동시에 PPU는 지정된 주소의 PPU 메모리에서 팔레트 데이터 "아래"에서 일반 읽기를 수행하고 이 읽기 결과는 평소와 같이 읽기 버퍼로 들어간다. 읽기 버퍼의 이전 내용은 팔레트를 읽을 때 버려지지만, 주소를 팔레트 RAM 외부로 변경하고 한 번 읽으면 이 그림자 메모리(일반적으로 미러링된 네임테이블)의 내용에 접근할 수 있다. 팔레트 RAM 읽기를 지원하지 않는 PPU에서는 이 메모리 범위가 나머지 PPU 메모리와 동일하게 동작한다.

이 기능은 2C02G, 2C02H 및 PAL PPU에서 지원된다. 팔레트를 읽을 때 반환되는 바이트는 상위 2비트에 PPU 열린 버스를 포함하며, 값은 그레이스케일 모드에 의해 수정된 후 반환된다. 그레이스케일 모드가 활성화되면 하위 4비트가 지워진다. 불행히도 일부 콘솔에서는 마스터 클럭에 대한 4개의 CPU/PPU 정렬 중 하나에서 팔레트 읽기가 손상될 수 있다. 이 손상은 레지스터 접근을 나타내는 PPU /CS 신호가 언제 비활성화되는지에 따라 다르며, 이는 콘솔마다 다르다. 이 기능이 모든 PPU에 있는 것은 아니라는 점과 결합하여 개발자는 팔레트 RAM에서 읽는 것에 의존해서는 안 된다.

DPCM 샘플과의 읽기 충돌

현재 DPCM 샘플을 재생 중인 경우, APU의 샘플 가져오기 중단이 $2007을 읽는 명령어와 동시에 발생하면 추가 읽기 사이클이 발생할 가능성이 있다. 이로 인해 추가 증가가 발생하고 바이트가 건너뛰어져 잘못된 데이터가 읽힌다. 참조: APU DMC

OAMDMA - 스프라이트 DMA ($4014 쓰기)


7  bit  0
---- ----
AAAA AAAA
|||| ||||
++++-++++- 소스 페이지 (소스 주소의 상위 바이트)

OAMDMA는 CPU를 일시 중단하여 DMA를 사용하여 CPU 메모리 페이지를 PPU OAM으로 빠르게 복사할 수 있는 CPU 레지스터이다. 항상 256바이트를 복사하고 소스 주소는 항상 페이지 정렬($00으로 끝남)로 시작한다. 이 레지스터에 기록된 값은 소스 주소의 상위 바이트이며, 복사는 쓰기 직후 사이클에서 시작된다. 복사는 513 또는 514 사이클이 걸리며, CPU 메모리에서 읽기와 OAMDATA에 쓰기의 256 쌍으로 구현된다. vblank가 매우 짧고 OAMADDR을 변경하면 종종 OAM이 손상되기 때문에, OAM DMA는 일반적으로 매 프레임마다 스프라이트를 업데이트하는 유일한 현실적인 옵션이다. 데이터가 올바르게 정렬되고 손상을 피하기 위해 DMA를 시작하기 전에 OAMADDR에 0을 써야 한다.[4] OAM DMA는 렌더링이 비활성화된 상태에서 프레임 중간에 수행할 수 있지만, 일반적으로 vblank에서만 수행된다.

OAM은 동적 RAM(DRAM)으로 구성되어 있으며, 자주 새로 고치지 않으면 붕괴된다. 이는 NTSC와 PAL에서 다른 고려 사항이 필요하다. 새로 고침은 DRAM 행을 읽거나 쓸 때마다 자동으로 발생하므로, 렌더링 중 스프라이트 평가 프로세스에 의해 모든 스캔라인에서 새로 고쳐진다. NTSC에서는 vblank가 충분히 짧아서 렌더링이 다시 시작되기 전에 OAM이 붕괴되지 않으므로, OAM DMA는 vblank 중 언제든지 수행할 수 있다. PAL에서는 vblank가 훨씬 길기 때문에 그 시간 동안 붕괴를 피하기 위해 PPU는 NMI 후 24 스캔라인 후에 강제 새로 고침을 자동으로 수행하며, 이 동안에는 OAM에 쓸 수 없다. 이는 OAM DMA가 PAL의 vblank 시작으로 제한된다는 것을 의미한다. NTSC vblank는 24 PAL 스캔라인보다 짧으므로, NTSC 호환 NMI 핸들러는 강제 새로 고침 전에 완료되므로 OAM DMA 타이밍에 관계없이 PAL에서 작동해야 한다. 어느 경우든, vblank 중에 업데이트되지 않으면 OAM은 붕괴되지 않으며, 실제로 랙 프레임(CPU가 vblank 전에 작업을 완료하지 못한 프레임)에서는 불완전한 스프라이트 데이터를 PPU에 복사하는 것을 피하기 위해 일반적으로 업데이트해서는 안 된다.

내부 레지스터

PPU에는 PPU 스크롤링에 자세히 설명된 4개의 내부 레지스터도 있다.

  • v: 렌더링 중에는 스크롤 위치에 사용된다. 렌더링 외부에서는 현재 VRAM 주소로 사용된다.
  • t: 렌더링 중에는 다음 스캔라인의 시작 거친 x 스크롤과 화면의 시작 y 스크롤을 지정한다. 렌더링 외부에서는 스크롤 또는 VRAM 주소를 v로 전송하기 전에 보유한다.
  • x: 현재 스크롤의 미세 x 위치로, 렌더링 중에 v와 함께 사용된다.
  • w: PPUSCROLL 또는 PPUADDR에 대한 각 쓰기에서 토글되며, 이것이 첫 번째 쓰기인지 두 번째 쓰기인지를 나타낸다. PPUSTATUS를 읽으면 지워진다. '쓰기 래치' 또는 '쓰기 토글'이라고도 한다.

참조