Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Compiling Godot for the PlayStation 2 - Progress Report

A topic by Juxxec created Apr 10, 2024 Views: 1,065 Replies: 4
Viewing posts 1 to 4
(3 edits) (+2)

PlayStation 2 Logo

Hello everyone!

I was a bit bored at work as I do not have a lot of stuff to do at the moment. I was playing on my PlayStation 2 and thought: “How hard could it be to create a homebrew game for the PS2?”.

I went online and started looking for homebrew games. To my surprise, there weren’t that many games. I saw an active community surrounding the PS2 and a great set of open-source tools to create homebrew games.

So why weren’t there any games written, I asked myself. Turns out the PS2 is a very complicated piece of technology. Apart from simple games, you would have to write low-level instructions (yes, I am talking assembly) for the two coprocessors VU0 and VU1 that the PS2 uses, to get anything decent in terms of visual fidelity.

I searched a bit more and found a game engine that takes care of the complicated stuff - the Tyra game engine. But even if you were to use Tyra, you would have to write all of the logic in C++. There is no built-in script support. Getting Lua to run is pretty simple, but you would still lack an environment to manage your assets, code, scenes, etc.

I am a big fan of the Godot engine. The idea of compiling Godot for the PS2 captivated me. I had previously stumbled upon godot-switch and godot-3ds and godot-psp, so it should theoretically be possible.

Now, Godot uses a renderer based on OpenGL ES 2.0 and 3.0. This is not a thing of the PS2, you see. The PS2 uses a chip, called the Graphics Synthesizer (GS) in combination with the VU0 and VU1 coprocessors to get the job done. But I thought that with the help of Tyra, I would use what was already there and fill in the gaps where necessary.

So, I downloaded all the required repositories and the Godot 3.5.2 repository to start compiling.

I browsed the platforms section of the code and saw that the first step was to create the detect.py script. It configures the SCons build system (used by Godot) to compile for the target platform.

Initially, I was not aware that a guide for porting to other platforms was released. That is why, I was experimenting and trying to figure out how to do things on my own.

To build for the PS2, we need to have the correct toolchain. Luckily, the Tyra game engine came with a Dockerfile that downloads and compiles the toolchain.

I modified the Dockerfile to include the Godot source code as a volume so that I could compile it inside the Docker container. I set up tasks inside Visual Studio Code that would run SCons inside the container to compile Godot.

Inside the detect.py file, I had the following functions to implement:

# Checks if the platform is active. Always true.
def is_active():
    return True


# The name of the platform
def get_name():
    return "Playstation 2"


# Checks if the toolchain is present and working
def can_build():
    return True


# Build options. Here, different features of the Godot engine can be enabled/disabled.
def get_opts():
    return []


# Additional options that can be passed to SCons when building for the platform
def get_flags():
    return []


# Configures the compiler. Here we set the path to the compiler, the compiler flags, defines, include directories, etc.
def configure(env):
    pass

Initially, I decided to compile Godot 3.5.2, but I ran into multiple compilation issues. I bashed my head, trying different compile flags and I asked ChatGPT, but nothing helped. Because of this, I created a post on the Godot forums to ask people for help.

Somebody suggested I start with Godot version 2.1.x as it is much simpler, needs fewer system resources, and uses C++ 11, instead of C++ 17, which Godot 3.5.x uses. So, I downloaded the Godot 2.1.6 source and started working with that.

Of course, things did not go well at first. I was getting a weird error about the variant_parser.cpp file on line 1152:

} else if (id == "IntArray") {
    Vector<int32_t> args;
    Error err = _parse_construct<int32_t>(p_stream, args, line, r_err_str);
    if (err)
        return err;

    DVector<int32_t> arr;
    {
        int len = args.size();
        arr.resize(len);
        DVector<int32_t>::Write w = arr.write();
        for (int i = 0; i < len; i++) {
            w[i] = int(args[i]);
        }
    }

    // value = arr;
    // TODO: idortulov - The compiler gets confused on this part so do a c-style cast
    value = *(Variant*)&arr;

    return OK;

}

The compiler was telling me that no suitable conversion existed between a DVector<int32_t> and Variant. The fix was to do a C-style cast and force the conversion.

That solved this issue, but the code was still failing to compile. It was giving me errors that did not make sense. I scoured the internet, trying to figure out what was wrong. Until I stumbled on a comment somewhere that the PS2 toolchain would fail to compile when doing threaded compilation. For whatever reason, I used a -j1 parameter and to my surprise, the compilation succeeded! As the compilation with only one thread is slow, I tested different values until I found the highest value was -j8. This was good enough for me.

Godot was now compiling, even though I hadn’t started writing the platform-specific code.

This is what I managed to accomplish in Week 1. Stay tuned for the next update and how week 2 went.

The code can be found on GitHub.

(1 edit) (+2)

It is the second week. I continued development on the port by implementing one of the core classes, used by Godot - the OS class. It, as the name suggests, handles a lot of the OS-specific operations, as well as initializes and configures a lot of the servers used by Godot such as the Visual Server, used for rendering.

The most important functions of the OS are:

void OS_PS2::initialize(const VideoMode& p_desired, int p_video_driver, int p_audio_driver)
{
	args = OS::get_singleton()->get_cmdline_args();
	current_videomode = p_desired;
	main_loop = NULL;

	ticks_start = clock();

	rasterizer = memnew(RasterizerDummy);

	visual_server = memnew(VisualServerRaster(rasterizer));

	AudioDriverManagerSW::get_driver(p_audio_driver)->set_singleton();

	if (AudioDriverManagerSW::get_driver(p_audio_driver)->init() != OK)
	{
		ERR_PRINT("Initializing audio failed.");
	}

	sample_manager = memnew(SampleManagerMallocSW);
	audio_server = memnew(AudioServerSW(sample_manager));
	audio_server->init();
	spatial_sound_server = memnew(SpatialSoundServerSW);
	spatial_sound_server->init();
	spatial_sound_2d_server = memnew(SpatialSound2DServerSW);
	spatial_sound_2d_server->init();

	ERR_FAIL_COND(!visual_server);

	visual_server->init();

	physics_server = memnew(PhysicsServerSW);
	physics_server->init();
	physics_2d_server = memnew(Physics2DServerSW);
	physics_2d_server->init();

	input = memnew(InputDefault);

	_ensure_data_dir();
}

void OS_PS2::initialize_core()
{
	ThreadDummy::make_default();
	SemaphoreDummy::make_default();
	MutexDummy::make_default();

	FileAccess::make_default<FileAccessPS2>(FileAccess::ACCESS_RESOURCES);
	FileAccess::make_default<FileAccessPS2>(FileAccess::ACCESS_USERDATA);
	FileAccess::make_default<FileAccessPS2>(FileAccess::ACCESS_FILESYSTEM);

	DirAccess::make_default<DirAccessPS2>(DirAccess::ACCESS_RESOURCES);
	DirAccess::make_default<DirAccessPS2>(DirAccess::ACCESS_USERDATA);
	DirAccess::make_default<DirAccessPS2>(DirAccess::ACCESS_FILESYSTEM);

	mempool_static = new MemoryPoolStaticMalloc;
	mempool_dynamic = memnew(MemoryPoolDynamicStatic);
}

A couple of very important operations take place here. Let us start with the initialize function. Here we set up the different servers used by Godot such as the Physics Server, the Visual Server the Audio Server, etc. This is where we register our Rasterizer. The Rasterizer is responsible for all of the rendering.

At this point, I just wanted to have the thing run without rendering anything so I used a Dummy Rasterizer that does nothing. I would worry about that later down the line.

Now, the second most important function is the initialize_core function. Here we configure OS stuff such as Multithreading, Synchronization, File Access, and Memory.

The PlayStation 2 does support multithreading but to limit the points of failure, I opted to not implement this right now. A bare-bones port to start with that I could extend later down the line.

The PS2 SDK uses functions from the standard C library for working with files. I copied the already existing implementation for Unix and just removed some of the system functions that were not available on the PS2. The SDK is not fully Unix-compatible.

I had to disable rendering at first as I just wanted to see the engine boot. I managed to get my code to compile but I had to turn off many of the features of Godot as I didn’t really need them for my first attempts of porting:

    env["module_cscript_enabled"] = "no"
    env["module_dds_enabled"] = "no"
    env["module_mpc_enabled"] = "no"
    env["module_openssl_enabled"] = "no"
    env["module_opus_enabled"] = "no"
    env["module_pvr_enabled"] = "no"
    env["module_speex_enabled"] = "no"
    env["module_theora_enabled"] = "no"
    env["module_webp_enabled"] = "no"
    env["module_squish_enabled"] = "no"

The engine compiled and I had an ELF file that I could run on the PS2. I fired up the PCSX2 emulator and ran the ELF file. To nobody’s surprise, the thing crashes. I didn’t have a debugger at hand and I saw that it would not be easy to setup one so I opted to use console logs. However, at first, I could just not get PCSX2 to show me any console output. No matter what options I used, there was no output.

I saw that the PS2SDK had debugging utilities that could be used to print text to the screen. So I modified my code to use those functions and to my surprise, I did get the output!

Godot running on PCSX2

Awesome! It was complaining that it could not find files with game data. Godot stores the game resources (textures, sounds, scripts, scenes, etc.) in files with the PCK extension. I quickly exported a data.pck file from a “Hello, world!” project I created with Godot 2.1.6 and placed it beside the ELF file.

I saw that the PS2 supports File IO via the standard libraries so I just copied over the Unix implementation for the FileAccess and DirAccess classes. As their names suggest, they are used to access files and directories on the OS. I had to do a bit of modification as not all Posix functions were implemented on the PS2.

So I ran the game again but to my surprise, the data.pck was still not being picked up. I needed to debug but I had no debugging. “Not a problem. I will use logging!” - I said to myself. But then I realized. Other than the onscreen console, which was running out of space because of what Godot was printing, I had no access to the console. Nor did I know how to output anything.

well, I did some research and initially, it seemed that I could only get the logs on a real PS2 through serial. Yikes!.

But, I saw an option in the PCSX2 debugging window: PCSX Debugging Window Sources Menu

I unticked all options and left just the System out one. I thought that that is what would get my printfs to appear. Boy was I wrong. This took me 3 days to figure out!

It turns out that what I was using for debugging was actually printing to the IOP Console. Who would have figured … Anyway, I started placing logs everywhere to try and figure out what was going on.

As is with these things, it turned out that I just had to read the console carefully. Even if I hadn’t gotten the logs from the console, I could have seen what was wrong:

Unknown device `host`

Well, that is a pickle. Turns out that PCSX2 was using my host machine as a device, however, it was using it only to load the ELF file. After the file is loaded, the host machine data is no longer accessible.

After a log of research, I discovered that I needed to enable the host device feature on PCSX2, which would give me access to the files on my hard drive and more importantly, access to the data.pck file that was right next to the ELF file.

To my surprise, this option was removed in the version of PCSX2 I was using originally - 1.6.0. So, I tried downloading a more recent version of PCSX2. Turns out, there weren’t any versions. Or, at least, I could not find any.

I did some more research and I found out that there was a plugging that allows me to mount an ISO in the USB drive that can be seen by Godot. I set up the plugin, but again, the data.pck file could not be loaded. Turns out, that the plugin worked all along, but the issue lay elsewhere. But that is a topic for the next development log. This concludes the second week of development.

Deleted post

Hello, interesting undertaking you got there, I've been thinking of gluing Tyra to Godot 3.5 myself for a few years but never knew where to start. Mind chatting about this on discord if you have it?

Sure, hit me up! Juxxec is my username on Discord.