1. Introdução
Com este projeto, eu queria responder uma pergunta: é possível escrever uma API Java para Playstation 2 e criar uma demonstração gráfica nela. Eu não quero revelar os spoilers, mas a resposta é sim.
Alguns anos atrás, iniciei um projeto
Java Grinder que recebe arquivos .class Java compilados e realmente funciona como um desmontador. Mas, em vez de desmontar no código do assembler Java, ele desmonta no código-fonte do assembler para processadores reais. Se o arquivo de classe precisar de outros arquivos de classe, eles também serão lidos e processados. Todas as chamadas de método da API são gravadas na saída, como código do assembler interno ou como chamadas para funções pré-gravadas que executam a tarefa pretendida.
Como o
Java Grinder foi escrito em C ++, orientado a objetos, abstraído, polimórfico e muitas outras palavras favoritas de RH de alto perfil, para expandi-lo, foi necessário principalmente criar a classe Playstation2, expandindo a nova classe R5900, expandindo a classe principal Generator.
Como resultado, o projeto acabou sendo maior do que eu esperava. O sistema em si é bastante simples, mas ainda tenho muito a aprender, e encontrar informações de qualidade não é tão simples. De fato, pela primeira vez neste projeto, iniciei programação 3D real. Em outro post, eu já falei sobre o que aprendi na minha página de
programação do Playstation 2 .
Abaixo está um vídeo e uma explicação detalhada do processo de desenvolvimento.
Vídeo
Gravei uma demo em um PS2 slim conectando cabos de áudio e vídeo a um gravador de DVD. Fiquei um pouco preocupado que o PS2 tivesse algum tipo de proteção Macrovision que estragaria o sinal de vídeo, mas foi desligado ou o gravador de DVD o ignorou. O vídeo começa com uma demonstração do Playstation 2 real, que executa uma demonstração. O console está conectado a um sinal composto ao conversor VGA, conectado a um monitor LCD, provando que a demonstração está sendo executada em uma máquina real. Então eu adicionei uma colagem com o vídeo real gravado diretamente do PS2 no gravador de DVD.
YouTube:
https://youtu.be/AjM069oKUGsProjetos similares em mikekohn.net
Demo
Lembrando da demonstração
Java da
Sega Genesis , lamento um pouco que não tenha tornado mais interessante. Então, foi mais interessante para mim demonstrar os recursos da API Java. Quando iniciei este projeto, decidi fazer algo mais sério. Infelizmente, novamente fiquei tão esgotado no processo de estudar o sistema e criar a API que não tinha força suficiente para uma grande demonstração.
- Logotipo de 3 bilhões de dispositivos: são os 3 bilhões de dispositivos de baixa resolução que executam o logotipo Java que Joe Davisson criou para sua demo Java do Commodore 64 .
- Logotipos: desenhei-os com um marcador e os digitalizei (com exceção do logotipo Java).
- Estrelas: Na verdade, copiei o código da demonstração Java Sega Genesis e modifiquei-o para funcionar com a API do Playstation 2. O texto aqui também é escrito com um marcador e digitalizado.
- Fractais de Mandelbrot: são demonstrados usando a unidade vetorial 0, que calcula fractais, e a unidade vetorial 1 executa cálculos 3D. O MIPS controla o que os dois dispositivos vetoriais fazem.
- Cubos: desenhei esses cubos no Wings3d e escrevi o código C para converter arquivos STL em matrizes que o Java Grinder poderia usar. Eu adicionei cores manualmente.
- Anel de quadrados: apenas uma tentativa de desenhar muitos objetos em movimento na tela. Provavelmente valia a pena adicionar mais objetos antes que o sistema começasse a desacelerar.
Música
Para a demo, compus e gravei três músicas, mas como resultado usei apenas duas. A primeira composição é na verdade uma melodia que eu escrevi para outro projeto publicado no meu site (
projeto aceitador de moedas ) cerca de um ano atrás ... inicialmente havia apenas uma parte. Depois que o projeto foi concluído, pensei que seria interessante colocar um solo de guitarra e, após a gravação, imaginei que essa música tocasse enquanto as estrelas voassem na demo. Só consegui usá-lo depois de alguns meses. O violão na composição é
Fender Strat I scalloped .
Gravei a segunda composição apenas um dia antes da publicação do artigo. O solo da guitarra soa um pouco ... bêbado, porque é tocado em uma guitarra que eu me
tornei irritada . Eu não sou muito bom em tocá-lo, e as notas agudas desaparecem muito rapidamente, mas os slides soam bem legais. A parte rítmica foi tocada no meu
kit Yngwie Wanna-Be (Squier Strat barato e de vieiras, DOD YJM308 overdrive e Mini-Marshall alimentado por baterias de 9 volts).
Programei bateria para ambas as composições usando o programa
Drums ++, escrito há muito. Ele recebe arquivos de texto de entrada gravados no meu próprio idioma e os transforma em arquivos .mid, importados para a Apple Garage Band, após o qual você pode gravar faixas de base e de guitarra. Os arquivos de origem
fretless.dpp e
shoebox.dpp estão na pasta de ativos do meu repositório de demonstração.
A música é reproduzida pelo Playstation 2 SPU2 e, graças ao R5900, ele pode fazer outros trabalhos. Devido à falta de boa documentação, quase terminei a demo sem nenhuma música. Mais sobre isso abaixo.
Aqui estão duas faixas no formato MP3:
Desenvolvimento
O projeto foi desenvolvido por um longo tempo. Comecei a adicionar as instruções do R5900 Emotion Engine ao assembler do MIPS em
naken_asm e , em seguida,
procurei instruções de ponto flutuante e instruções de unidade de macro / micro vetores. Fazendo uma grande pausa para trabalhar em outros projetos, estudei todos os outros aspectos necessários para esta demonstração e continuei adicionando seu suporte ao
Java Grinder . Se alguém estiver interessado em detalhes de baixo nível, criei uma página na qual tentei documentar todas as informações coletadas:
programação do Playstation 2 .
Programei principalmente usando
o emulador PCXS2 . É bastante conveniente, porque nele posso examinar registros e afins na tela. Mas definitivamente não é tão flexível e simples quanto o MAME ao desenvolver o
Sega Genesis . Por exemplo, em MAME, é mais fácil examinar a memória, a RAM e os registros de vídeo / áudio para garantir que o
Java Grinder esteja funcionando corretamente.
Ao trabalhar com o código da Sega, cometi um erro - não o testei na máquina até a demo ser escrita. Havia pelo menos três esquisitices no código da Sega que o emulador ignorou, mas elas não gostaram da máquina real. Dessa vez, depois de escrever as partes individuais do código, eu as testei em uma máquina real, para que, após a conclusão da demonstração, ela funcione tanto no equipamento real quanto no emulador. Mais uma vez me deparei com coisas que funcionavam no emulador, mas não iniciavam em um PS2 real. Também descobri que funcionava em um Playstation 2 real, mas funcionava incorretamente no emulador.
Recursos da API
- A unidade vetorial 0 possui métodos para carregar / executar código e carregar / descarregar dados.
- A Unidade 1 do vetor executa rotações e projeção em 3D.
- Texturas usando um formato de 16 ou 24 bits (a transparência é indicada em preto).
- Texturas em formato de 16 bits podem ser codificadas em RLE.
- Código para desenhar pontos, linhas, triângulos, com e sem texturas.
- Nevoeiro e sombreamento de Guro.
- Métodos para acessar um gerador de números aleatórios.
- Usando dois contextos (substituindo páginas)
- Inserir dados binários grandes no código do assembler compilado.
- Tocando música.
API
A parte principal da API é definida na classe
Playstation2 . Inicialmente, eu daria a ele um alto grau de liberdade - a capacidade de definir modos de vídeo e coisas do gênero, mas depois pensei que seria melhor esconder todas essas dificuldades. Essencialmente, ele apenas configura uma tela entrelaçada de 640x448. Como em outros projetos do
Java Grinder , basicamente adicionei métodos / recursos conforme necessário.
Há outro conjunto de classes ao qual dei o nome chato
Draw3D . Em essência, eles definem todos os tipos de primitivos que o Graphics Synthesizer pode renderizar com suporte para texturas de 16, 24 e 32 bits. Pensei em adicionar texturas de 8 bits, mas decidi não fazê-lo ainda. Draw3D fornece rotações 3D, projeção, transferência de hardware DMA, texturas, etc. Você provavelmente perguntará por que não o criei com base no OpenGL, mas nunca havia trabalhado com o OpenGL antes. Era uma vez, eu estava envolvido em uma programação simples do ps2dev, mas não havia nada sério por lá e mal me lembro desse projeto, então repito - podemos assumir que esta é a primeira vez que faço algo sério em 3D.
Existem exemplos de como usar todas essas coisas, não apenas na demonstração, mas também na pasta de
amostras .
Quase todas as dificuldades estão ocultas na API. O desenvolvedor não precisa se preocupar em limpar o cache, em computação 3D etc. No entanto, o retorno disso foi uma diminuição na universalidade. Portanto, se, por exemplo, a textura foi alterada pelo processador, mas apenas os primeiros 64 pixels foram alterados, é necessário limpar apenas uma linha de cache de 64 bytes, mas o Java Grinder limpa a imagem inteira. Ele marca objetos, portanto, eles são limpos apenas se necessário, mas limpam todo o fragmento de memória. Com uma alta probabilidade de alterar 64 bytes, a imagem inteira também muda.
Unidade vetorial 0 (VU0)
O usuário do Java Grinder é livre para usar o VU0. Eu usei a parte demo chamada “Two Vector Units, One MIPS” para renderizar os fractais de Mandelbrot. O código pode ser otimizado melhor, por exemplo, a maioria das instruções de ponto flutuante da unidade vetorial tem um tempo de execução de 1 e uma latência de 4. Até onde eu entendo, isso significa que se o registro é o destino da instrução, ele pode ser executado em 1 ciclo, mas para para que o resultado esteja disponível, são necessários 4 ciclos. Se esse registro for usado, a unidade vetorial permanecerá ociosa até que esteja pronta. Portanto, a idéia é que você precise organizar as instruções para poder executar cada instrução em 1 ciclo sem tempo de inatividade. Quando criei os
fractais de Mandelbrot no Playstation 3 no ano passado, otimizei esse código enquanto calculava simultaneamente 8 pixels (2 registros SIMD), observando um grande aumento de velocidade. No caso atual, tentei facilitar a leitura do código, para não me incomodar com sua otimização.
O VU0 contém apenas 4 KB de memória de dados e você não grava a imagem fractal inteira lá, então o MIPS envia as coordenadas de apenas 1/8 da imagem por vez.
A estranheza que encontrei ao trabalhar com VU0: Inicialmente, executei o código VU0 usando instruções e usei VIF0_STAT para verificar a conclusão de sua execução. Parece que VIF0_STAT não funciona se você não iniciar o VU0 com o pacote VIF. Isso foi corrigido no emulador, mas o erro ainda está no hardware real. Como resultado, descobri que vcallms e usando cfc2 no registro 29 funcionam em ambos os casos.
Parece-me que os conjuntos de instruções da unidade vetorial não possuem as instruções de comparação paralela encontradas no
Intel X86_64, Playstation 3 e até no conjunto de instruções MIPS R5900 Emotion Engine. Os fractais de Mandelbrot devem calcular iterativamente a fórmula até que o resultado seja superior; portanto, com um conjunto diferente de instruções, eu faria uma comparação paralela que criaria uma máscara para todos os binários 1 ou 0. A máscara pode ser usada para interromper o incremento dos contadores de cores. Para o Playstation 2, eu tive que derivar uma fórmula muito estranha, que é bem próxima da fórmula fractal. Eu documentei isso no código fonte do
mandelbrot_vu0.asm em linhas com o Python comentado.
Acho legal nos dispositivos de unidade vetorial do console Playstation 2 que é ótimo não ter visto outros conjuntos de instruções SIMD nas quais todas as instruções da FPU podem ter o atributo .xyzw informando à instrução qual dos quatro números de ponto flutuante possui afeta. Ou seja, se eu precisasse que o resultado da instrução afetasse apenas o componente x do vetor, simplesmente adicionaria .x no final da instrução. Outra coisa interessante é que é um conjunto de instruções VLIW, ou seja, em cada ciclo, duas instruções são executadas simultaneamente. De fato, o módulo é mais um DSP do que um processador de uso geral.
Unidade vetorial 1 (VU1)
O VU1 é reservado pelo Java Grinder para executar rotações, movimentos e projeções 3D. Os objetos Draw3D são transmitidos para a VU1 usando o método draw (), que usa o montador de unidade vetorial para converter os pontos e transferi-los para o sintetizador gráfico. O código do assembler na VU1 pode ser muito melhor otimizado para velocidade, mas é adequado para meus propósitos, por isso decidi deixar o código fácil de ler (não otimizado).
Para estudar as fórmulas de projeções e curvas, usei a Wikipedia:
projeções e
curvas .
O código de transformação 3D também está no repositório
naken_asm como um arquivo .asm:
rotation_vu1.asm simples.
MIPS R5900
Eu realmente não gostei do conjunto de instruções do MIPS até começar a trabalhar neste projeto. De fato, é muito fácil trabalhar com esta CPU. A versão do Emotion Engine desta CPU possui recursos muito convenientes. Mais notavelmente, os registradores têm 128 bits, mas na verdade são simplesmente usados para carregamento / armazenamento e SIMD. Ou seja, na realidade, são registradores de 128 bits, ALU de 64 bits e ponteiros de 32 bits.
Também foi possível introduzir otimizações no código MIPS principal, mas não o fiz para manter a legibilidade do código ou devido à falta de tempo. Por exemplo, a CPU do MIPS fica ociosa por um ciclo se o registro de instruções de destino foi usado imediatamente após a configuração. Esse comportamento pode ser melhorado.
Hacks Java
O próprio Java Grinder também tem suas próprias ... esquisitices, mas algo está simplesmente faltando, principalmente porque eu visava inicialmente o MSP430 e, na maioria das vezes, era um experimento. Um dos elementos ausentes era a incapacidade de alocar memória para objetos. Eu adicionei esse recurso no Playstation 2 para instanciar vários objetos, principalmente usando a API Draw3D. Como não escrevi nenhum alocador de memória ou coletor de lixo, todas as novas chamadas são feitas na pilha. Eu estava pensando em implementar algo como um alocador de memória dinâmico, mas no final decidi não complicá-lo. Também adicionei ao suporte estendido do Playstation 2 para números de ponto flutuante (float) (parcialmente esse suporte ainda estava no código Epiphany / Parallella). Algumas outras coisas, como os tipos longo e duplo, ainda não são suportadas.
Provavelmente, a coisa mais irritante que fiz foi relacionada à terrível restrição de arquivos de classe Java. O método Java não pode ser maior que 64 K se bem me lembro. Talvez isso seja normal, mas o problema surge quando há uma matriz estática no arquivo de classe e ele não é despejado no arquivo de classe como dados binários. Ele é colocado no arquivo de classe como uma instrução Java assembler em um inicializador estático para criar uma matriz. Tentei salvar imagens em arquivos de classe como matrizes de bytes estáticos [], mas algumas delas não se encaixavam, então adicionei um método ao arquivo de classe Memory
Java Grinder :
byte[] Memory.preloadByteArray(String filename);
Ele não baixa esse arquivo em tempo de execução, mas o baixa em tempo de compilação usando a diretiva .binfile
naken_asm . As imagens são copiadas para o binário de saída durante a montagem.
Com tudo isso em mente, eu realmente espero que
James Gosling nunca tropece no meu projeto.
Imagens
A API Draw3D pode usar texturas de 16, 24 e 32 bits. Os pixels de textura podem ser definidos pixel por pixel ou carregando usando matrizes de bytes []. Também adicionei a capacidade de compactar imagens com RLE no formato {length, lo16, hi16}, em que lo16 e hi16 são as cores de 16 bits no formato little endian, que são copiadas para os tempos de “length” da textura.
As ferramentas
Ao trabalhar na Sega para criar ferramentas para criar imagens, músicas e coisas do gênero, usei o idioma do Google Go, apenas para aprender um novo idioma. Desta vez eu tentei Rust. A primeira ferramenta converte binários em código-fonte Java e a segunda converte BMP em formato binário, que pode ser carregado em texturas, inclusive no formato RLE. Como resultado, eu os escrevi em Python, caso alguém queira se juntar a mim na criação de uma demonstração.
Som
Tendo descoberto como os dispositivos gráficos e de unidade vetorial funcionam, o último passo foi o som. Eu pensei que seria a parte mais fácil, especialmente depois de estudar o PDF com a descrição do Sony SPU2. Quão errado eu estava. Esta parte do sistema está muito mal documentada.
A primeira coisa que descobri é que o SPU2 (unidade de processamento de som) está conectado ao IOP (processador de E / S, também conhecido como processador Playstation 1). A CPU do Playstation 2 está conectada a este IOP através de algo chamado SIF. O PDF da Sony menciona apenas o SIF DMA, mas não diz nada sobre seu uso.
Como resultado, eu me recusei a usar o SIF, mas decidi adicionar um vinculador ao
naken_asm para poder usar o kernel.a no PS2DEV SDK. O vinculador ganhou, mas falhou.
Nesta fase, eu já decidi que não poderia fazer o som funcionar, e eu só queria terminar a demo sem ele. Mas isso me atormentou, então decidi dar uma olhada no código fonte de vários emuladores do Playstation 2 para entender como o SIF funciona. Finalmente, eu descobri como acessar diretamente a memória do código MIPS R3000 no IOP e executá-la (há um exemplo na pasta samples do repositório naken_asm). Consegui fazer o som funcionar no emulador.
No final, descobri que a memória da IOP (incluindo SPU2) estava localizada no espaço do Emotion Engine, por isso fiz um grande esforço (a documentação é extremamente pequena e em nenhum dos emuladores é implementada completamente corretamente, mas não importa que eles funcionem) ), Aprendi a trabalhar com som.
Comparação de emulador e ferro
Eu encontrei algumas diferenças entre a execução em uma máquina real e em um emulador.
- Se o pacote GIF definir o PRIM registra os dois valores IIP (método de sombreamento) e os bits FIX são todos 1, o emulador leva em conta o bit IIP e executa o sombreamento Gouro, enquanto o equipamento real executa sombreamento simples.
- Se o pacote GIF for transmitido através do PATH3 (EE direto para GS) e o sinalizador EOP (fim do pacote) não estiver definido, se o VU1 tentar enviar o pacote GIF pelo PATH 1 (VU1 para GS), isso causará um travamento no hardware real, mas funcionará no emulador.
- Ignorar a limpeza do cache da CPU antes da transferência do DMA não é necessário, mas em uma máquina real leva a um comportamento estranho.
- Ao colocar o SPU2 no espaço EE, o emulador pode simplesmente gravar dados de áudio no FIFO SPU2. Em um Playstation 2 real, depois de gravar 32 palavras, é necessário escrever no registro para dar um comando para limpar o FIFO. Além disso, no hardware real, ao definir o endereço de transmissão / início do SPU2, o modo de transmissão deve ser definido como 0. Os emuladores não se importam se o modo tiver um valor 0.
- A gravação de IOPs do EE na memória alocada registra falhas em uma máquina real, mesmo que esteja no modo kernel. O emulador permite que essas operações funcionem independentemente do modo atual da CPU.
- O uso de canais SIF DMA funciona no emulador, mas ainda não consegui fazê-los funcionar em equipamentos reais. Eu estava recebendo um erro de acesso à memória para registros SIF DMA, mesmo no modo kernel.
- O emulador é muito lento para executar uma demonstração ao calcular fractais usando VU0, para que o som esteja fora de sincronia.
Resumir
Eu queria escrever um programa para o Playstation 2 quase desde o momento da sua compra. Na verdade, eu já tinha um kit Linux para PS2 há muito tempo (acho que foi por isso que comprei o Playstation 2) e até tentei trabalhar com a biblioteca PS2DEV em C, mas essa é uma experiência completamente diferente em comparação à programação diretamente na linguagem assembly. para ferro.
Devo agradecer ao
Lukash por salvar o antigo código-fonte do assembler e os documentos PS2. Não tenho certeza se eu poderia começar sem a demonstração Duke 3 Star, o que me ajudou a inicializar o equipamento. Também sou grato aos desenvolvedores do emulador
PCSX2 , que aceleraram bastante o processo de depuração. Além disso, eu não conseguia descobrir o som se não tivesse olhado o código fonte do emulador e não entendesse o que estava errado.
E obrigado à
Sony por este lindo e pequeno computador. Se alguém da Sony ler este artigo, aqui vai uma dica: por que não reduzi-lo ao tamanho de um Rapsberry Pi e vendê-lo como um quadro para projetos de hobby? :).
Demonstração de compilação
git clone https://github.com/mikeakohn/playstation2_demo.git
git clone https://github.com/mikeakohn/java_grinder.git
git clone https://github.com/mikeakohn/naken_asm.git
cd naken_asm
./configure
make
cd ..
cd java_grinder
make java
make
cd ..
cd playstation2_demo
make