Java for Playstation 2 - is it possible?

Original author: Michael Kohn
  • Transfer
image

Introduction


With this project, I wanted to answer one question: is it possible to write a Java API for the Playstation 2 and create a graphic demo on it. I do not want to disclose spoilers, but the answer is "yes."

A few years ago, I started the Java Grinder project , which gets compiled Java class files and actually works as a disassembler. But instead of disassembling Java into assembler code, it performs disassembling into assembler source code for real processors. If a class file needs other class files, they are also read and processed. All API method calls are written to output, either as inline assembler code, or as calls to previously written functions that perform their intended task.

Since Java Grinder was written in C ++, object-oriented, abstracted, polymorphic and many more loud favorite HR words in a way, its expansion basically required the creation of a Playstation2 class that expanded the new R5900 class, which extended the core Generator class.

As a result, the project turned out to be more than I expected. The system itself is quite simple, but I still have a lot to learn, and it’s not so easy to search for quality information. In fact, for the first time in this project I took up real 3D programming. In another post, I already talked about what I learned on my Playstation 2 Programming page .

Below are videos and a detailed explanation of the development process.

Video



I recorded a demo on the PS2 slim by connecting audio and video cables to a DVD recorder. I was a little worried that the PS2 has any Macrovision protection that would ruin the video signal, but it was either turned off or the DVD recorder ignored it. The video begins with a demonstration of this Playstation 2, which runs a demo. The console is connected to a composite signal to VGA converter, connected to an LCD display, proving that the demo is running on a real machine. Then I added a splice to this video, recorded directly from the PS2 in the DVD recorder.

YouTube: https://youtu.be/AjM069oKUGs

Related projects at mikekohn.net


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

Demo


Recalling the Sega Genesis Java demo , I’m a little sorry I didn’t make it more interesting. Then it was more interesting for me to demonstrate the capabilities of the Java API. When I started this project, I decided to do something more serious. Unfortunately, I again burned out in the process of studying the system and creating an API, that I did not have enough strength for a large demo.

  • 3 Billion Devices Logo: This is a low-resolution 3 billion devices run Java logo that Joe Davisson created for his Commodore 64 Java demo .
  • Logos: I drew them with a marker and scanned (with the exception of the Java logo).
  • Stars: I actually copied the code from the Sega Genesis Java demo and modified it to work with the Playstation 2 API. The text here is also written with a marker and scanned.
  • Mandelbrot fractals: they are shown using vector unit 0, which calculates fractals, and vector unit 1 performs 3D calculations. MIPS controls what both vector devices do.
  • Cubes: I drew these cubes in Wings3d and wrote C code to convert STL files into arrays that Java Grinder could use . I added the colors manually.
  • Ring of squares: just an attempt to draw on the screen a lot of moving objects. It was probably worth adding more objects until the system began to slow down.

Music


For the demo, I composed and recorded three compositions, but as a result I used only two. The first composition is actually a melody that I wrote for another project published on my website ( the coin acceptor project ) about a year ago ... initially there was only a part. After the project was completed, I thought it would be interesting to put a guitar solo on it, and after recording, I imagined that this music was playing while the stars were flying in the demo. I managed to use it only after a few months. The guitar in the composition is the Fender Strat I scalloped .

I recorded the second song just a day before the publication of the article. The guitar solo sounds a bit ... drunk, because it is played on a guitar, which I turned into a fretless. I'm not very good at playing it, and the high notes fade out very quickly, but the slides sound pretty cool. The rhythm part was played on my Yngwie wanna-be kit (cheap scalloped Squier Strat, DOD YJM308 overdrive and Mini-Marshall powered by 9 volt batteries).

I programmed the drums for both songs with the help of the Drums ++ program that was written a long time ago . It receives text files written in the language invented by me and transforms them into .mid files that I imported into the Apple Garage Band, after which you can record basic and guitar tracks. The source files fretless.dpp and shoebox.dpp are located in the assets folder of my demo repository.

Music is played on the Playstation 2's SPU2 device, and thanks to the R5900 chip, it can do other work. Due to the lack of good documentation, I almost finished the demo without any music at all. More on this below.

Here are two MP3 songs:


Development


The project was developed for a long time. I started adding the R5900 Emotion Engine instructions to the MIPS assembler in naken_asm , then I took up floating-point instructions and macro / micro mode vector unit instructions. Taking a big breather to work on other projects, I explored all the other aspects needed for this demo, and proceeded to add their support in the Java Grinder . If someone is interested in low-level details, then I created a page on which I tried to document all the collected information: Playstation 2 programming .

I mainly programmed using the PCXS2 emulator. It is quite convenient, because in it I can explore registers and the like on the screen. But it is definitely not as flexible and simple as MAME when developing the Sega Genesis . For example, in MAME, it is easier to examine memory, and RAM, and video / audio registers to make sure that the Java Grinder is working properly .

When working with the code for Sega, I made one mistake - I did not test it on the machine until the demo was written. In the Sega code there were at least three oddities that the emulator ignored, but they didn’t like the real car. This time, after writing separate parts of the code, I tested them on a real machine, so that after the demo was completed, it worked on real hardware and in the emulator. I once again ran into things that worked in the emulator, but did not run on a real PS2. I also discovered what worked on a real Playstation 2, but it was incorrectly executed in the emulator.

API features


  • Vector Unit 0 has methods for loading / running code and loading / unloading data.
  • Vector Unit 1 performs 3D turns and projection.
  • Textures using 16-bit or 24-bit format (transparency is indicated in black).
  • Textures in 16-bit format can be encoded RLE.
  • The code for drawing points, lines, triangles, with and without textures.
  • Fog and shading by Guro.
  • Methods for accessing a random number generator.
  • Using two contexts (replacing pages)
  • Inserting large binary data in compiled assembler code.
  • Play music.

API


The main part of the API is defined in the Playstation2 class . Initially, I was going to give him a greater degree of freedom - the ability to set video modes and the like, but then I thought that it might be better to hide all these difficulties. In fact, it simply sets the display to 640x448 with interlaced scanning. As with other Java Grinder projects , I basically added methods / capabilities just as needed.

There is another set of classes, which I gave the boring name Draw3D. In fact, they define all types of primitives that Graphics Synthesizer can draw with support for 16, 24, and 32-bit textures. I thought about adding 8-bit textures, but decided not to do it yet. Draw3D provides 3D turns, projection, hardware DMA transfer, textures, etc. You will probably ask why I did not create it based on OpenGL, but I had never worked with OpenGL before. Once upon a time I was engaged in simple programming of ps2dev, but there was nothing serious there and I barely remember that project, so I repeat - we can assume that this is the first time I do something serious in 3D.

There are examples of using all these things, not only in the demo, but also in the samples folder .
Almost all the difficulties are hidden in the API. The developer does not need to worry about clearing the cache, about 3D computing, etc. However, the price paid for this was a decrease in versatility. Therefore, if, for example, the texture was changed by the processor, but only the first 64 pixels changed, then only one 64-byte cache line needs to be cleared, but the Java Grinder clears the entire image. It marks objects, so they are cleared only when necessary, but it clears the entire fragment of memory. With a high probability, if you change 64 bytes, the entire image changes as well.

Vector Unit 0 (VU0)


Java Grinder user is free to use VU0. I used the demo part called “Two Vector Units, One MIPS” to draw the Mandelbrot fractals. The code can be optimized better, for example, most floating-point vector unit instructions have a time of 1 and latency 4. As far as I understand, this means that if a register is target for an instruction, then it can be executed in 1 cycle, but in order the result is available, it takes 4 cycles. If this register is used, the vector unit will be idle until it is ready. Therefore, the idea is that you need to arrange the instructions so that you can execute each instruction in 1 cycle without downtime. When last year I created Mandelbrot fractals on a Playstation 3I optimized this code while simultaneously calculating 8 pixels (2 registers of SIMD) at the same time, noticing a large increase in speed. In the current case, I tried to make the code easier to read, so I did not bother with its optimization.

VU0 contains only 4KB of data memory, and there the whole image of the fractal is not recorded, so MIPS sends him only 1/8 of the coordinates at a time.

The strangeness that I encountered when working with VU0: I initially ran the VU0 code using instructions and used VIF0_STAT to check the completion of their execution. It seems that VIF0_STAT does not work unless you launch VU0 with a VIF package. This is fixed in the emulator, but the error still exists in real hardware. As a result, I found out that vcallms and the use of cfc2 in register 29 work in both cases.

It seems to me that the vector unit instruction sets lack the parallel comparison instruction, which is in Intel X86_64, Playstation 3, and even in the MIPS R5900 Emotion Engine instruction set. Mandelbrot fractals must iteratively calculate a formula until the result exceeds, therefore with a different set of instructions I would simply perform a parallel comparison that would create a mask for all binary 1 or 0. The mask can be used to stop the increment of color counters. For the Playstation 2, I had to derive a very clumsy formula that is fairly close to the fractal formula. I documented it in the source code mandelbrot_vu0.asm in lines with commented out Python.

I think it's great in the Playstation 2 console vector unit devices that I haven't seen any other sets of SIMD instructions, in which all FPU instructions can have a .xyzw attribute that tells which of the four floating-point numbers affects. That is, if I needed the result of the instruction to affect only the x-component of the vector, then I would simply add .x to the end of the instruction. Another interesting thing is that this is a set of VLIW instructions, that is, two instructions are simultaneously executed in each cycle. In fact, the module is more like a DSP than a general-purpose processor.

Vector Unit 1 (VU1)


VU1 is reserved by Java Grinder for performing 3D turns, movements and projections. Draw3D objects are passed to VU1 using the draw () method, which uses the vector unit assembler to convert points and transfer them to the Graphics Synthesizer. The assembler code in VU1 can be much better optimized for speed, but it is suitable for my purposes, so I decided to leave the code easy to read (non-optimized).

To study the formulas of projections and turns, I used Wikipedia: projections and turns .

The 3D transform code is also in the naken_asm repository as a simple .asm file: rotation_vu1.asm .

MIPS R5900


I didn’t really like the MIPS instruction set until I started working on this project. In fact, this CPU is quite easy to work with. The Emotion Engine version of this CPU has very convenient features. Most notably, registers have a length of 128 bits, but in fact they are simply used for loading / storing and SIMD. That is, in reality, these are 128-bit registers, 64-bit ALU and 32-bit pointers.

It was also possible to make optimization in the main MIPS code, but I didn’t do it to keep the code readable or due to lack of time. For example, the MIPS CPU is idle for one cycle if the target instruction register was used immediately after its setting. This behavior could be improved.

Java hacks


In the java grinder itselfthere are also ... oddities, but something is simply not enough, mainly because I initially aimed at MSP430 and for the most part it was an experiment. One of the missing elements was the inability to allocate memory for objects. I added this feature on Playstation 2 to create instances of several objects, mainly using the Draw3D API. I did not write any memory allocators or garbage collectors, so all calls to “new” are performed on the stack. I was thinking about implementing something like a dynamic memory allocator, but in the end I decided not to complicate things. I also added expanded support for floating-point numbers (float) for the Playstation 2 (some of this support was still in the code of Epiphany / Parallella). Some other things, such as the “long” and “double” types, are still not supported.

Probably the most annoying thing I did was the terrible limitation of the Java class files. The Java method cannot be more than 64 KB if I remember correctly. Perhaps this is normal, but the problem occurs when there is a static array in the class file and it is not dumped into the class file as binary data. It is placed in a class file as an assembly Java instruction in a static initializer for creating an array. I tried to save images to class files as static byte [] arrays, but some of them did not fit, so I added a method to the Memory Java Grinder class class file :

byte[] Memory.preloadByteArray(String filename);

It does not load this file at runtime, but loads it at build time using the .binfile naken_asm directive . Images are copied to the output binary file at build time.

With all this in mind, I very much hope that James Gosling will never stumble upon my project.

Images


Draw3D API can use 16-, 24-, and 32-bit textures. Texture pixels can be specified pixel by pixel or by loading with byte [] arrays. I also added the ability to RLE-compress images in the format {length, lo16, hi16}, where lo16 and hi16 are 16-bit little endian format, which is copied into the texture “length” times.

Instruments


When working on Sega to create tools for creating images, music and the like, I used the Google Go language, just to learn a new language. This time I tried Rust. The first tool converts binary files to Java source code, and the second one converts BMP to binary format, which can be loaded into textures, including RLE. As a result, I wrote them in Python, in case someone wants to join me in creating a demo.

Sound


Having dealt with how the graphics and devices of the vector unit work, the last stage was the sound. I figured it would be the easiest part, especially after studying a PDF with a description of Sony SPU2. How wrong I was. This part of the system is very poorly documented.

The first thing I found out is that SPU2 (sound processing unit) is connected to an IOP (I / O processor, aka Playstation 1 processor). The Playstation 2 CPU is connected to this IOP through something called SIF. In the Sony PDF, only SIF DMA is mentioned, but nothing is said about its use.

As a result, I refused to use SIF, but decided to add a linker to naken_asm so that I could use kernel.a from the PS2DEV SDK. Linker earned, but nothing happened.

At this stage, I already decided that I could not make the sound work, and just wanted to finish the demo without it. But it tormented me, so I decided to take a look at the source code of various Playstation 2 emulators to figure out how SIF works. Finally, I figured out how to perform direct memory access from the MIPS R3000 code in the IOP and run it (there is an example in the samples folder of the naken_asm repository). I managed to make the sound work in the emulator.

In the end, I found out that the IOP memory (including SPU2) was placed in the Emotion Engine space, so it put a lot of effort (there is very little documentation and it is not fully implemented in any of the emulators, but for their work it doesn’t matter ), I learned to work with sound.

Comparison of emulator and iron


I found some differences between running on a real machine and in an emulator.

  • If the GIF packet sets the PRIM register to both IIP values ​​(shading method), and the FIX bits are all 1, then the emulator takes the IIP bit into account and performs Goura shading, while the actual equipment performs flat shading.
  • If the GIF packet is transmitted via PATH3 (EE direct to GS), and the EOP (end of packet) flag is not set, then if VU1 tries to send the GIF packet through PATH 1 (VU1 to GS), then it will hang in real hardware, but will work in the emulator.
  • Skipping clearing the CPU cache before the DMA transfer is not necessary, but on a real machine leads to strange behavior.
  • When placing the SPU2 in the EE space, the emulator can simply record the audio data in the FIFO SPU2. On a real Playstation 2, after writing 32 half words, you need to write to the register in order to give the command to clear the FIFO. Also on real hardware, when setting the transmit / start address of the SPU2, the transfer mode must be set to 0. The emulators do not care if the mode has the value 0.
  • Writing to the registers of the allocated IOP memory from EE causes the real machine to fail, despite the fact that it is in kernel mode. The emulator allows such operations to work regardless of the current CPU mode.
  • Using channels SIF DMA works in the emulator, but I could not get them to work on real equipment. I received a memory access error for SIF DMA registers even in kernel mode.
  • The emulator is too slow to demo when calculating fractals using VU0, so the sound is out of sync.

Summarize


I wanted to write some program for the Playstation 2 almost from the very moment of its purchase. In fact, I already had a Linux kit for PS2 (I think it was for this reason that I acquired Playstation 2), and I even tried working with PS2DEV in C, but this is a completely different experience compared to programming in assembly language for iron.

I have to thank Lukash for keeping the old assembly source code and PS2 documents. Not sure if I could even get started without the 3 Star Duke developer demo that helped me initialize the hardware. I am also grateful to the developers of the PCSX2 emulator., greatly accelerated the debugging process. In addition, I could not deal with the sound if I had not looked at the source code of the emulator and did not understand what was wrong.

And thanks to Sony for this lovely little computer. If someone from Sony reads this article, then here's a tip for you: why not squeeze it to the size of Rapsberry Pi and sell as a fee for hobby projects? :)

Build demo


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

Also popular now: