Java para Playstation 2: ¿es posible?

imagen

Introduccion


Con este proyecto quería responder una pregunta: ¿es posible escribir una API Java para Playstation 2 y crear una demostración gráfica en ella? No quiero revelar los spoilers, pero la respuesta es sí.

Hace unos años empecé el proyecto de Java Grinder , la recepción de los archivos compilados .class de Java y en realidad el trabajo como un desensamblador. Pero en lugar de desarmarse en el código ensamblador de Java, se desarma en el código fuente del ensamblador para procesadores reales. Si el archivo de clase necesita otros archivos de clase, también se leen y procesan. Todas las llamadas al método API se escriben en la salida, ya sea como código ensamblador incorporado o como llamadas a funciones preescritas que realizan su tarea prevista.

Dado que Java Grinder se escribió en C ++, palabras de recursos humanos preferidas, orientadas a objetos, abstraídas, polimórficas y muchas más de alto perfil, para expandirlo, se requería principalmente crear la clase Playstation2, expandir la nueva clase R5900, expandir la clase principal del generador.

Como resultado, el proyecto resultó ser más grande de lo que esperaba. El sistema en sí es bastante simple, pero todavía tengo mucho que aprender, y encontrar información de calidad no es tan simple. De hecho, por primera vez en este proyecto, comencé a programar en 3D. En otra publicación, ya hablé sobre lo que aprendí en mi página de programación de Playstation 2 .

A continuación hay un video y una explicación detallada del proceso de desarrollo.

Video



Grabé una demostración en una PS2 slim conectando cables de audio y video a una grabadora de DVD. Estaba un poco preocupado de que la PS2 tuviera algún tipo de protección de Macrovision que arruinara la señal de video, pero estaba apagada o la grabadora de DVD la ignoraba. El video comienza con una demostración de la Playstation 2 real, que ejecuta una demostración. La consola está conectada a un convertidor de señal compuesta a VGA, conectada a una pantalla LCD, lo que demuestra que la demostración se está ejecutando en una máquina real. Luego agregué un pegado con el video real grabado directamente desde la PS2 en la grabadora de DVD.

YouTube: https://youtu.be/AjM069oKUGs

Proyectos similares en mikekohn.net


Amoladora Java:Playstation 2 Java ,
Sega Genesis Java ,
Apple IIgs Java ,
TI99 / 4A Java ,
C64 Java ,
dsPIC Mandelbrots ,
Atari 2600 de Java ,
chipKIT Java ,
Amoladora Java ,
naken_asm

Demo


Recordando la demostración de Sega Genesis Java , lamento un poco no haberlo hecho más interesante. Entonces fue más interesante para mí demostrar las capacidades de la API de Java. Cuando comencé este proyecto, decidí hacer algo más serio. Desafortunadamente, nuevamente me quedé tan agotado en el proceso de estudiar el sistema y crear la API que no tuve suficiente fuerza para una gran demostración.

  • Logotipo de 3 mil millones de dispositivos: este es el logotipo de Java de 3 mil millones de dispositivos de baja resolución creado por Joe Davisson para su demostración Java Commodore 64 .
  • Logotipos: los dibujé con un marcador y los escaneé (con la excepción del logotipo de Java).
  • Estrellas: Copié el código de la demostración de Sega Genesis Java y lo modifiqué para que funcione con la API de Playstation 2. El texto aquí también se escribe con un marcador y se escanea.
  • Fractales de Mandelbrot: se demuestran usando la unidad de vector 0, que calcula fractales, y la unidad de vector 1 realiza cálculos 3D. MIPS controla lo que hacen ambos dispositivos vectoriales.
  • Cubos: Dibujé estos cubos en Wings3d y escribí el código C para convertir archivos STL en matrices que Java Grinder podría usar. Agregué colores manualmente.
  • Anillo de cuadrados: solo un intento de dibujar muchos objetos en movimiento en la pantalla. Probablemente valió la pena agregar más objetos antes de que el sistema comenzara a ralentizarse.

Musica


Para la demostración, compuse y grabé tres canciones, pero como resultado solo usé dos. La primera composición es en realidad una melodía que escribí para otro proyecto publicado en mi sitio web ( proyecto aceptador de monedas ) hace aproximadamente un año ... inicialmente solo había una parte. Después de que se completó el proyecto, pensé que sería interesante ponerle un solo de guitarra, y después de grabar, imaginé que esta música suena mientras las estrellas vuelan en la demo. Logré usarlo solo después de unos meses. La guitarra en la composición es Fender Strat I festoneada .

Grabé la segunda composición solo un día antes de la publicación del artículo. El solo de guitarra suena un poco ... borracho porque se toca en una guitarra que convertí sin trastes . No soy muy bueno para tocarlo, y las notas altas se desvanecen muy rápidamente, pero las diapositivas suenan bastante bien. La parte rítmica se jugó en mi kit de aspirantes a Yngwie (Squier Strat festoneado barato, sobremarcha DOD YJM308 y Mini-Marshall con baterías de 9 voltios).

Programé la batería para ambas composiciones usando el programa Drums ++ de larga duración. Recibe archivos de texto de entrada grabados en mi propio idioma, y ​​los convierte en archivos .mid, que importé a Apple Garage Band, después de lo cual puede grabar pistas base y de guitarra. Los archivos fuente fretless.dpp y shoebox.dpp están en la carpeta de activos de mi repositorio de demostración.

Playstation 2 SPU2 reproduce la música y, gracias al R5900, puede hacer otro trabajo. Debido a la falta de buena documentación, casi terminé la demostración sin música alguna. Más sobre esto a continuación.

Aquí hay dos pistas en formato MP3:


Desarrollo


El proyecto se ha desarrollado durante mucho tiempo. Comencé a agregar instrucciones de R5900 Emotion Engine al ensamblador MIPS en naken_asm , luego busqué instrucciones de punto flotante e instrucciones de unidad de macro / micro vector. Haciendo un gran respiro para trabajar en otros proyectos, estudié todos los demás aspectos necesarios para esta demostración, y procedí a agregar su apoyo a Java Grinder . Si alguien está interesado en los detalles de bajo nivel, creé una página en la que intenté documentar toda la información recopilada: la programación de Playstation 2 .

Principalmente programé usando el emulador PCXS2 . Es bastante conveniente, porque en él puedo examinar registros y similares en la pantalla. Pero definitivamente no es tan flexible y simple como MAME cuando se desarrolla Sega Genesis . Por ejemplo, en MAME es más fácil examinar la memoria y la RAM, y los registros de video / audio para asegurarse de que Java Grinder funciona correctamente.

Cuando trabajé con el código para Sega, cometí un error: no lo probé en la máquina hasta que se escribió la demostración. Hubo al menos tres rarezas en el código Sega que el emulador ignoró, pero no les gustó la máquina real. Esta vez, después de escribir las partes individuales del código, las probé en una máquina real, de modo que una vez completada la demostración, funciona tanto en el equipo real como en el emulador. Nuevamente me encontré con cosas que funcionaban en el emulador, pero que no comenzaron en una PS2 real. También descubrí que funcionaba en una Playstation 2 real, pero funcionaba incorrectamente en el emulador.

Características de la API


  • Vector Unit 0 tiene métodos para cargar / ejecutar código y cargar / descargar datos.
  • Vector Unit 1 realiza rotaciones y proyecciones 3D.
  • Texturas que utilizan un formato de 16 o 24 bits (la transparencia se indica en negro).
  • Las texturas en formato de 16 bits se pueden codificar con RLE.
  • Código para dibujar puntos, líneas, triángulos, con y sin texturas.
  • Niebla y sombreado por Guro.
  • Métodos para acceder a un generador de números aleatorios.
  • Usando dos contextos (reemplazando páginas)
  • Inserte datos binarios grandes en el código ensamblador compilado.
  • Tocando musica.

API


La parte principal de la API se establece en la clase Playstation2 . Inicialmente, iba a darle un alto grado de libertad: la capacidad de configurar modos de video y cosas similares, pero luego pensé que sería mejor ocultar todas estas dificultades. Esencialmente, solo configura una pantalla entrelazada de 640x448. Al igual que con otros proyectos de Java Grinder , básicamente agregué métodos / características según sea necesario.

Hay otro conjunto de clases, la cual di a aburrido nombre Draw3D . En esencia, definen todos los tipos de primitivas que Graphics Synthesizer puede procesar con soporte para texturas de 16, 24 y 32 bits. Pensé en agregar texturas de 8 bits, pero decidí no hacerlo todavía. Draw3D proporciona rotaciones 3D, proyección, transferencia de hardware DMA, texturas, etc. Probablemente se preguntará por qué no lo creé basado en OpenGL, pero nunca antes había trabajado con OpenGL. Érase una vez una simple programación de ps2dev, pero no había nada serio allí y apenas recuerdo ese proyecto, así que repito: podemos suponer que esta es la primera vez que hago algo serio en 3D.

Hay ejemplos de uso de todas estas cosas, no solo en la demostración, sino también en la carpeta de muestras .
Casi todas las dificultades están ocultas en la API. El desarrollador no necesita preocuparse por borrar el caché, por la informática en 3D, etc. Sin embargo, la recompensa por esto fue una disminución en la versatilidad. Por lo tanto, si, por ejemplo, el procesador cambió la textura, pero solo han cambiado los primeros 64 píxeles, entonces solo necesita borrar una línea de caché de 64 bytes, pero Java Grinder borra toda la imagen. Marca objetos, por lo tanto, se borran solo si es necesario, pero borra todo el fragmento de memoria. Con una alta probabilidad al cambiar 64 bytes, la imagen completa también cambia.

Unidad vectorial 0 (VU0)


El usuario de Java Grinder es libre de usar VU0. Usé la parte de demostración llamada "Dos unidades de vectores, un MIPS" para representar los fractales de Mandelbrot. El código se puede optimizar mejor, por ejemplo, la mayoría de las instrucciones de coma flotante de la unidad vectorial tienen un tiempo de ejecución de 1 y una latencia de 4. Hasta donde yo entiendo, esto significa que si el registro es el objetivo de la instrucción, entonces se puede ejecutar en 1 ciclo, pero para para que el resultado esté disponible, se requieren 4 ciclos. Si se utiliza este registro, la unidad de vector permanecerá inactiva hasta que esté lista. Por lo tanto, la idea es que necesita organizar las instrucciones para que pueda ejecutar cada instrucción en 1 ciclo sin tiempo de inactividad. Cuando creé los fractales Mandelbrot en Playstation 3 el año pasado, optimicé este código mientras calculaba simultáneamente 8 píxeles (2 registros SIMD), al tiempo que noté un gran aumento de velocidad. En el caso actual, traté de hacer que el código fuera más fácil de leer, por lo que no me molesté con su optimización.

VU0 contiene solo 4 KB de memoria de datos, y no escribirá allí toda la imagen fractal, por lo que MIPS envía las coordenadas de solo 1/8 de la imagen a la vez.

Lo extraño que encontré al trabajar con VU0: inicialmente ejecuté el código VU0 usando instrucciones y usé VIF0_STAT para verificar la finalización de su ejecución. Parece que VIF0_STAT no funciona si no inicia VU0 con el paquete VIF. Esto se soluciona en el emulador, pero el error todavía está en el hardware real. Como resultado, descubrí que vcallms y el uso de cfc2 en el registro 29 funciona en ambos casos.

Me parece que los conjuntos de instrucciones de la unidad vectorial carecen de las instrucciones de comparación paralelas que se encuentran en el Intel X86_64, Playstation 3 e incluso en el conjunto de instrucciones MIPS R5900 Emotion Engine. Los fractales de Mandelbrot deben calcular iterativamente la fórmula hasta que el resultado sea superior, por lo que con un conjunto diferente de instrucciones, simplemente realizaría una comparación paralela que crearía una máscara para todos los binarios 1 o 0. La máscara se puede usar para detener el incremento de los contadores de color. Para Playstation 2, tuve que derivar una fórmula muy incómoda, que está bastante cerca de la fórmula fractal. Documenté esto en el código fuente de mandelbrot_vu0.asm en líneas con Python comentado.

Me parece genial en los dispositivos de unidad de vector de la consola Playstation 2 que es genial que no haya visto ningún otro conjunto de instrucciones SIMD en el que todas las instrucciones de FPU puedan tener el atributo .xyzw que indique a la instrucción cuál de los cuatro números de punto flotante tiene afectos Es decir, si necesitaba que el resultado de la instrucción afectara solo el componente x del vector, entonces simplemente agregaría .x al final de la instrucción. Otra cosa interesante es que es un conjunto de instrucciones VLIW, es decir, en cada ciclo, se ejecutan dos instrucciones simultáneamente. De hecho, el módulo se parece más a un DSP que a un procesador de uso general.

Unidad vectorial 1 (VU1)


VU1 está reservado por Java Grinder para realizar rotaciones, movimientos y proyecciones en 3D. Los objetos Draw3D se pasan a VU1 utilizando el método draw (), que utiliza el ensamblador de unidades vectoriales para convertir los puntos y transferirlos al Sintetizador de gráficos. El código del ensamblador en VU1 puede optimizarse mucho mejor para la velocidad, pero es adecuado para mis propósitos, así que decidí dejar el código fácil de leer (no optimizado).

Para estudiar las fórmulas de proyecciones y giros, utilicé Wikipedia: proyecciones y giros .

El código de transformación 3D también está en el repositorio naken_asm como un simple archivo .asm: rotacion_vu1.asm .

MIPS R5900


Realmente no me gustó el conjunto de instrucciones MIPS hasta que comencé a trabajar en este proyecto. De hecho, es bastante fácil trabajar con esta CPU. La versión Emotion Engine de esta CPU tiene características muy convenientes. Lo más notable es que los registros tienen una longitud de 128 bits, pero de hecho simplemente se usan para cargar / almacenar y SIMD. Es decir, en realidad, estos son registros de 128 bits, ALU de 64 bits y punteros de 32 bits.

También fue posible introducir optimizaciones en el código MIPS principal, pero no lo hice para mantener la legibilidad del código o por falta de tiempo. Por ejemplo, la CPU MIPS está inactiva durante un ciclo si el registro de instrucciones de destino se usó inmediatamente después de configurarlo. Este comportamiento podría mejorarse.

Hacks de Java


Java Grinder en sí también tiene sus propias ... rarezas, pero simplemente falta algo, principalmente porque inicialmente apunté al MSP430 y en su mayor parte fue un experimento. Uno de los elementos que faltaban era la incapacidad de asignar memoria para los objetos. Agregué esta característica en Playstation 2 para instanciar múltiples objetos, principalmente usando la API Draw3D. No escribí ningún asignador de memoria o recolector de basura, por lo que todas las llamadas nuevas se realizan en la pila. Estaba pensando en implementar algo como un asignador dinámico de memoria, pero al final decidí no complicarlo. También agregué soporte extendido para Playstation 2 para números de punto flotante (flotante) (parcialmente este soporte todavía estaba en el código Epiphany / Parallella). Algunas otras cosas, como los tipos largos y dobles, todavía no son compatibles.

Probablemente lo más molesto que hice estuvo relacionado con la terrible restricción de los archivos de clase Java. El método Java no puede ser mayor que 64K si no recuerdo mal. Quizás esto es normal, pero el problema surge cuando hay una matriz estática en el archivo de clase y no se descarga en el archivo de clase como datos binarios. Se coloca en el archivo de clase como una instrucción de ensamblador Java en un inicializador estático para crear una matriz. Intenté guardar imágenes en archivos de clase como arreglos de bytes estáticos [], pero algunos de ellos no encajaban, así que agregué un método al archivo de clase Memory Java Grinder :

byte[] Memory.preloadByteArray(String filename); 

No descarga este archivo en tiempo de ejecución, pero lo descarga en tiempo de compilación utilizando la directiva .binfile naken_asm . Las imágenes se copian al binario de salida durante el ensamblaje.

Con todo esto en mente, realmente espero que James Gosling nunca tropiece con mi proyecto.

Imágenes


Draw3D API puede usar texturas de 16, 24 y 32 bits. Los píxeles de textura se pueden configurar píxel por píxel o cargando utilizando conjuntos de bytes []. También agregué la capacidad de comprimir imágenes RLE en el formato {longitud, lo16, hi16}, donde lo16 y hi16 son el color de 16 bits en el formato little endian, que se copia en los tiempos de "longitud" de la textura.

Las herramientas


Cuando trabajé en Sega para crear herramientas para crear imágenes, música y similares, utilicé el idioma Google Go, solo para aprender un nuevo idioma. Esta vez probé Rust. La primera herramienta convierte binarios en código fuente Java, y la segunda convierte BMP en formato binario, que puede cargarse en texturas, incluso en formato RLE. Como resultado, los escribí en Python, en caso de que alguien quiera unirse a mí para crear una demostración.

Sonido


Habiendo descubierto cómo funcionan los dispositivos gráficos y de unidad de vector, el último paso fue el sonido. Pensé que sería la parte más fácil, especialmente después de estudiar el PDF con la descripción de Sony SPU2. Qué equivocado estaba. Esta parte del sistema está muy poco documentada.

Lo primero que descubrí es que SPU2 (unidad de procesamiento de sonido) está conectado a IOP (procesador de E / S, también conocido como procesador Playstation 1). La CPU Playstation 2 está conectada a este IOP a través de algo llamado SIF. El PDF de Sony solo menciona SIF DMA, pero no dice nada sobre su uso.

Como resultado, me negué a usar SIF, pero decidí agregar un enlazador a naken_asm para poder usar kernel.a desde el SDK de PS2DEV. Linker ganó, pero falló.

En esta etapa, ya decidí que no podía hacer que el sonido funcionara, y solo quería terminar la demostración sin él. Pero me atormentó, así que decidí echar un vistazo al código fuente de varios emuladores de Playstation 2 para comprender cómo funciona SIF. Finalmente, descubrí cómo acceder directamente a la memoria desde el código MIPS R3000 en IOP y ejecutarlo (hay un ejemplo en la carpeta de muestras del repositorio naken_asm). Logré que el sonido funcionara en el emulador.

Al final, descubrí que la memoria IOP (incluida la SPU2) estaba ubicada en el espacio de Emotion Engine, así que puse mucho esfuerzo (la documentación es extremadamente pequeña y en ninguno de los emuladores está implementada completamente correctamente, pero no les importa que funcionen) ), Aprendí a trabajar con sonido.

Comparación de emulador y hierro


Encontré algunas diferencias entre la ejecución en una máquina real y en un emulador.

  • Si el paquete GIF establece que el PRIM registra ambos valores IIP (método de sombreado), y los bits FIX son todos 1, entonces el emulador tiene en cuenta el bit IIP y realiza el sombreado Gouro, mientras que el equipo real realiza el sombreado plano.
  • Si el paquete GIF se transmite a través de PATH3 (EE directo a GS), y el indicador EOP (final del paquete) no está configurado, entonces si VU1 intenta enviar el paquete GIF a través de PATH 1 (VU1 a GS), esto provocará un bloqueo en el hardware real, pero funcionará en el emulador.
  • Omitir borrar el caché de la CPU antes de la transferencia DMA no es necesario, pero en una máquina real conduce a un comportamiento extraño.
  • Al colocar SPU2 en el espacio EE, el emulador puede simplemente grabar datos de audio en FIFO SPU2. En una Playstation 2 real, después de grabar 32 palabras, es necesario escribir en el registro para dar un comando para borrar el FIFO. Además, en hardware real, al configurar la dirección de transmisión / inicio de SPU2, el modo de transmisión debe establecerse en 0. A los emuladores no les importa si el modo tiene un valor de 0.
  • Escribir IOP desde EE en los registros de memoria asignados se bloquea en una máquina real, aunque esté en modo kernel. El emulador permite que tales operaciones funcionen independientemente del modo de CPU actual.
  • El uso de canales SIF DMA funciona en el emulador, pero aún no he podido hacer que funcionen en equipos reales. Estaba recibiendo un error de acceso a la memoria para registros SIF DMA incluso en modo kernel.
  • El emulador es demasiado lento para ejecutar una demostración al calcular fractales usando VU0, por lo que el sonido no está sincronizado.

Para resumir


Quería escribir algún programa para Playstation 2 casi desde el momento de su compra. De hecho, ya tuve un kit de Linux para PS2 durante mucho tiempo (creo que es por eso que compré Playstation 2), e incluso intenté trabajar con la biblioteca PS2DEV en C, pero esta es una experiencia completamente diferente en comparación con la programación en lenguaje ensamblador directamente para hierro

Dar las gracias a Lukas para el mantenimiento de viejos documentos de código fuente en ensamblador y PS2. No estoy seguro de si podría comenzar sin la demostración de Duke 3 Star, que me ayudó a inicializar el equipo. También estoy agradecido a los desarrolladores del emulador PCSX2 , que aceleró enormemente el proceso de depuración. Además, no podría descifrar el sonido si no hubiera mirado el código fuente del emulador y no hubiera entendido lo que estaba mal.

Y gracias a Sony por esta pequeña y hermosa computadora. Si alguien de Sony lee este artículo, aquí hay un consejo: ¿por qué no reducirlo al tamaño de un Rapsberry Pi y venderlo como un tablero para proyectos de pasatiempo? :).

Construir demostración


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

Source: https://habr.com/ru/post/es423751/


All Articles