Os gráficos são gerados por um chip proprietário chamado Picture Processing Unit (PPU). Esse é um dos chips que dão ao NES uma identidade. Em outras palavras, qualquer um pode pegar uma CPU 6502 na loja de hardware, então por que o NES é diferente de, digamos, de um Apple 2 ou de um Commodore 64? Bem, o que distingue o NES de outras máquinas são os chips em volta da CPU: A PPU e a APU. Esses dois constituem, respectivamente, as capacidades gráficas e de áudio exclusivas do NES.
Dito isso, a PPU renderiza gráficos 2D chamados de sprites e backgrounds (planos de fundo), enviando o resultado para o sinal de vídeo.
Organizando o conteúdo
Para renderizar algo na tela, a PPU precisa saber quais gráficos deve desenhar, onde na tela colocá-los; e como desenhá-los (ex: qual paleta usar).
Para responder a estas perguntas, a PPU foi pré-programada com um mapa de memória diferente que procura os seguintes tipos de dados:
- Dados gráficos são lidos do cartucho do jogo, que contém um chip dedicado chamado Character Memory armazenando os desenhos em 2D (chamados tiles, ou ladrilhos), organizados em uma estrutura de dados chamada Pattern Table (Tabela de Padrões). A Character Memory pode vir na forma de "Read Only Memory" (ROM) ou "Random Access Memory" (RAM), dependendo se o jogo é composto por um conjunto imutável de gráficos ou se a CPU deve intervir, respectivamente.
- A PPU endereça até 8 KB de Character Memory, organizada em dois grupos de 4 KB cada.
- Metadados dizendo à PPU "onde" e "como" desenhar gráficos são encontrados em outras áreas:
- Uma SRAM separada de 2 KB fica localizada na placa-mãe, dedicada aos dados relacionados aos gráficos. A Nintendo chama este espaço Video RAM (VRAM) e armazena duas estruturas de dados chamadas Nametables (Tabela de Nomes).
- A PPU incorpora 256 B de DRAM para armazenar a Object Attribute Memory (Memória de Atributo de Objeto, ou OAM).
- Por último, a PPU também possui 4 B de memória para definir as paletas de cores.
Não se preocupe com a nova terminologia, o significado dessas estruturas de dados é discutido passo a passo nos parágrafos a seguir.
Construindo o quadro
Tal como com seus contemporâneos, este chip é projetado para o comportamento de uma tela CRT, popularmente conhecida como "TV de tubo". Não existe um frame buffer: a PPU irá renderizar ao mesmo tempo que o feixe de elétrons da CRT varre a tela, construindo a imagem em tempo real.
A PPU desenha quadros com uma dimensão fixa de 256x240 píxeis . Infelizmente, devido às discrepâncias entre os padrões de vídeo analógico ao redor do mundo, a imagem terá uma aparência diferente dependendo da região do aparelho (NTSC ou PAL) pelo qual é exibido. Resumindo, as televisões NTSC vão cortar as bordas superiores e inferiores para acomodar a faixa de overscan (apenas ~224 linhas de varredura são visíveis), essas bordas são consideradas "zonas de perigo" pelos desenvolvedores na hora de decidir onde colocar elementos no jogo. Por outro lado, as telas PAL não cortarão as bordas, mas mostrarão barras pretas extras para preencher o sinal mais alto (PAL usa 288 linhas de varredura).
Por trás dos panos, o quadro que a saí da PPU é composto por duas camadas diferentes. Para fins de demonstração, vamos usar Super Mario Bros. para mostrar como isso funciona:
Tiles

Duas tabelas de padrões com múltiplos tiles juntos.

Um único tile.
Para começar, a PPU usa tiles como um ingrediente básico para produzir sprites e backgrounds.
O NES define tiles como mapas básicos de 8x8 píxeis, estes são armazenados na Character memory (localizada no cartucho do jogo) e organizados em uma grande estrutura de dados chamada Tabela de Padrões . Cada tile ocupa 16 B e a Tabela de Padrões contém 256 tiles . Como a PPU acessa até 8 KB de Character memory, ela pode acessar até duas Tabelas de Padrões.
Dentro de um tile, cada um dos seus pixeis é codificado usando um valor de 2 bits, que faz referência a uma das quatro cores de uma paleta. Os programadores podem definir até oito paletas (quatro para o background e as outras quatro para sprites). As cores referenciadas em cada paleta apontam para uma "paleta mestra" composta de 64 cores , representando todas as cores que o console consegue produzir. As paletas são feitas de quatro cores, embora uma seja reservada para transparência.
Para começar a desenhar algo na tela, os jogos preenchem um conjunto de tabelas com referências a tiles na Character memory. Cada tabela é responsável por uma camada (sprite ou background) do quadro. Então, a PPU lê destas tabelas e constrói as linhas de varredura que serão projetadas pelo canhão CRT.
Agora vou explicar como cada camada/tabela funciona e como elas diferem em termos de funcionalidade.
Camada Background

Mapa de plano de fundo alocado.

Mapa de background alocado com a área selecionada marcada.
A camada de background é um mapa de 512x480 píxeis contendo tiles estáticos . Você pode se lembrar que o quadro visível é muito menor, então o jogo decide qual parte da camada é selecionada para exibição. Jogos também podem mover a área visível durante o jogo; é assim que se chega ao efeito de rolagem.
Para economizar memória, grupos de quatro tiles são combinados em mapas de 16x16 pixeis chamados de blocos, nos quais todos os tiles compartilham uma paleta de cores.
As tabelas de nomes (armazenadas na VRAM) especificam quais tiles exibir na camada de background. A PPU procura por quatro tabelas de nomes de tamanho 1024 bytes, onde cada uma correspondendo a um quadrante da camada. No entanto, só 2 KB de VRAM está disponível! Como consequência, apenas duas tabelas de nomes podem ser armazenadas sem o uso de hardware adicional no cartucho. Apesar de ainda ser necessário endereçar as duas tabelas de nomes restantes em algum lugar, a maioria dos jogos apenas as aponta para onde as primeiras duas estão localizadas (o que é conhecido como espelhamento).
Embora essa arquitetura possa parecer falha à primeira vista, ela foi projetada para manter os custos baixos enquanto oferecer uma expansibilidade simples: se os jogos precisassem de um background mais amplo, mais VRAM poderia ser incluída no cartucho.
Os últimos bytes de cada tabela de nomes armazenam uma tabela de atributos de 64 bytes que especifica qual paleta de cores é atribuída a cada bloco .
Camada de sprites
Sprites são tiles que podem se mover pela tela. Eles também podem se sobrepor uns aos outros ou aparecer atrás do background. A imagem visível será decidida com base em seu valor de prioridade (é o mesmo conceito de "camadas" utilizados em software tradicionais de design gráfico).
A tabela de Object Attribute Memory (OAM) especifica quais tiles serão usados como sprites . Além do índice do tile, cada entrada contém uma posição (x,y) e vários atributos (paleta de cores, prioridade e flags de espelhamento). Esta tabela é armazenada em uma DRAM de 256 bytes encontrada no chip PPU.
A tabela OAM pode ser preenchida pela CPU. Contudo, isso pode ser bastante lento na prática (e corre o risco de corromper o quadro se não for feito no momento certo). Como consequência, a PPU contém um pequeno componente chamado Direct Memory Access ou DMA, que pode ser programado (alterando os registrados da PPU) para buscar a tabela na WRAM. Com o DMA, é garantido que a tabela será carregada a tempo para o próximo quadro ser desenhado, mas é importante lembrar que a CPU será interrompida durante a transferência!
A PPU é limitada a oito sprites por linha de varredura e até 64 por quadro. O limite de linha de varredura pode ser excedido graças às habilidades de multiplexação da PPU. Em outras palavras, a PPU irá alternar automaticamente os sprites entre as varreduras; contudo, eles aparecerão piscando na tela.
Divisão de background

Camada de background renderizada, destacando as duas partes com valores de rolagem distintos definidos. Apenas a segunda parte rola conforme o Mario se move.
Antes de prosseguirmos, há algo que eu ainda não disse. Se você jogar Super Mario Bros, irá perceber que quando o Mario se move, a cena rola sem problemas. No entanto, você também observará que a área superior (onde fica as estatísticas) permanece estática, mesmo que ambas as partes façam parte da mesma camada de background! Então, o que acontece aqui? Bem, o jogo está alternando os valores de rolagem no meio do quadro para mostrar o mundo e as estatísticas (que residem numa parte fixa do background) simultaneamente. O NES não oferece esse recurso nativamente, mas o jogo deduz os tempos observando o estado da PPU (manifestado através de seu registro de status ).
Para conseguir isso, os jogos utilizam uma técnica chamada Sprite 0 Hit. Em Super Mario Bros, a PPU é instruída a renderizar um sprite fictício atrás da moeda, que é o primeiro sprite desenhado no quadro. Após a PPU desenhar este sprite, ela atualiza seu registro de status com uma flag que denota que o primeiro sprite (conhecido como sprite 0) foi desenhado. Enquanto isso, o jogo está constantemente verificando no meio do quadro se o status do sprite 0 foi marcado (ou seja, hit), se isso acontecer, o jogo passa a atualizar a propriedade de rolagem da tabela de background para movê-la para onde o Mario está.
Em geral, o Sprite 0 Hit é um procedimento muito delicado, pois é fácil errar nos tempos (a flag sprite 0 não é apagada depois de verificada, o que leva a positivos "duplicados" ). Além disso, como essa rotina se repete indefinidamente, pode ser bastante dispendiosa (em ciclos da CPU) para executar. Por outro lado, posteriormente, os mappers assumiram essa função com o uso de interrupções automáticas que são acionadas sempre que uma linha de varredura arbitrária é atingida (uma técnica muito mais eficiente), o que melhorou significativamente as capacidades visuais de Super Mario Bros 3, por exemplo.
Resultado
Depois que o quadro é finalizado, é hora de passar para o próximo!
No entanto, a CPU não pode modificar nenhuma tabela que esteja sendo usada atualmente pela PPU, caso contrário, podem aparecer artefatos na tela. Então, quando todas as linhas de varredura são concluídas, a PPU aciona a interrupção Vertical Blank (V-Blank) na CPU. Isso notifica o jogo de que ele pode começar a atualizar as tabelas sem rasgar a imagem exibida atualmente. Nesse momento, o feixe do CRT está apontando abaixo da área visível da tela, na área de borda inferior (overscan).
Apenas alguns registros da PPU podem ser atualizados foram da janela V-Blank , o que explica a capacidade de rolar a camada de background no meio do quadro.
Segredos e limitações
Se você está pensando que o sistema de buffer de quadros com memória alocada para armazenar o quadro completo teria sido preferível: os cursos de RAM eram muito altos e o objetivo do console era ser acessível. Deixe-me agora mostrar por que esse projeto se mostrou muito eficiente e flexível.
Multi-rolagem

Super Mario Bros. 2. Configuração da tabela de nome para rolagem vertical (espelhamento horizontal).

Super Mario Bros. 3. Mario pode correr e voar, então a PPU precisa rolar diagonalmente. Observe a borda direita mostrando a paleta de cores errada! A borda esquerda tem uma máscara aplicada.
Alguns jogos exigem que o personagem principal se mova verticalmente - assim, a tabela de nomes será configurada com espelhamento horizontal. Outros jogos precisam que o personagem se mova para a esquerda e para a direita, então eles usam o espelhamento vertical.
Qualquer um dos modos de espelhamentos permite que a PPU atualize os tiles de background sem que o usuário perceba: há bastante espaço para rolar enquanto novos tiles estão sendo renderizados a uma certa distância.
E se o personagem quiser se mover diagonalmente? A PPU pode rolar em qualquer direção, mas sem VRAM extra, as bordas são forçadas a compartilhar a mesma paleta de cores (lembre-se de que os tiles são agrupados em blocos).
É por isso que alguns jogos, como Super Mario Bros. 3, mostram gráficos estranhos na borda direita da tela enquanto Mario se move (o jogo é configurado para rolagem vertical) . É possível que os desenvolvedores precisassem minimizar o custo de hardware por cartucho (já que este jogo já tem um poderoso mapper instalado).
Como uma correção interessante, a PPU permite que os desenvolvedores apliquem uma máscara vertical sobre os tiles, ocultando efetivamente parte da área com defeito.
Troca de Tile

Hipoteticamente, se fosse renderizado usando os tiles disponíveis durante as primeiras linhas de varredura.

Hipoteticamente, se fosse renderizado usando os tiles disponíveis durante as últimas linhas de varredura.

O quadro real exibido ao usuário.
Outra especialidade de Super Mario Bros. 3 é a quantidade de gráficos que ele pode exibir.
Este jogo exibe mais tiles de background do que é estritamente permitido. Então, como ele faz isso? Se tirarmos duas capturas de tela em momentos diferentes enquanto a exibição é gerada, é possível observar que o quadro final é realmente composto de dois quadros diferentes.
Esta é outra magia do mapper MMC3. Ele não só foi usado para acessar espaço extra na ROM de Programa, também foi usado para estende o espaço do Character ROM conectando dois chips de Character ROM diferentes. Ao verificar qual parte da tela a PPU está solicitando, o mapper redireciona para um chip ou outro — permitindo assim mais tiles únicos na tela do que era originalmente suportado .
Comportamento curioso
Ao longo da minha pesquisa, encontrei muitos artigos interessantes que explicam comportamentos incomuns da PPU, então pensei em mencionar alguns aqui:
- Ao contrário do VDP do Master System, que gera cores RGB que são posteriormente codificadas em sinais NTSC/PAL para transmissão, a PPU do NES faz tudo de uma vez . Portanto, não há uma conexão um-a-um entre as cores da paleta mestra da PPU e o espaço de cores RGB padrão (amplamente adotado pela tecnologia atual). Isso deixa algum espaço para interpretação e, como consequência, vários emuladores exibirão uma paleta diferente.
- As discrepâncias entre paletas RGB são mais evidentes com o kit DIY de Tim Worthington, que adiciona saída de sinal RGB ao NES, pois ele também implementa uma interruptor que escolhe entre três paletas pré-definidas .
- A paleta mestra contém uma cor "amaldiçoada" (
$0D) que pode bagunçar o sinal de TV NTSC . Bem, o que acontece é que a TV confunde o sinal ao exibir essa cor com o sinal de supressão, então pode ocorrer cintilação. - A PPU depende da DRAM para armazenar sua Object Attribute Memory (OAM). A DRAM exige atualizações constantes para evitar a perda de dados (ao contrário do SRAM), entretanto a PPU não atualiza o DRAM quando não está renderizando o quadro . Isso se manifesta durante a sincronização vertical. Por esse motivo, é recomendado não atualizar a OAM fora da sincronização vertical, pois o período de não-atualização que ocorre durante o V-blank pode corromper parte da tabela.
- A variante da PPU para sistemas PAL não são afetadas por isso, pois atualiza durante a V-Blank (que dura mais nos sistemas PAL).

