Porting Half Life with Xash3D: Not Strictly Straightforward

As announced a few days ago, Half Life can now run on Pandora with the help of the Xash3D engine. It’s fantastic that allows the user to use Half Life data and run the game on a target that was not possible until now. And it runs great (at least on my Pandora 1Ghz, you mileage may vary a little on a CC or Rebirth). Here’s a quick look at this great game and how to get it running with PtitSeb’s port – not all was as easy as pie this time around.

Xash3D is an engine that can run the world of Half Life 1. It uses the Half Life SDK in the first place, in order to replicate how the original Half life works. PtitSeb started his work by compiling that piece of the software.

PtitSeb: I created specific Makefiles for the Pandora target, for Xash3D, mixing the Android.mk and the Linux makefile. It’s Cmake so it was not too hard to compile, with OpenGL support as for the desktop Linux version. But Xash3D itself is useless, though. You need the server library and the game client to make everything run together.

If you are wondering why there is a server and a client library… well, it’s because most FPS after Quake were designed from the beginning to be multi-player, online games. So the server library takes care of the AI, the physics engine, and running scripts.

The client library is there for displaying the world to the gamer, and letting you interact with it using the controls. And even if you start a game locally, offline, it still works this way – but the server is just running locally instead of being on a remote machine.

PtitSeb: Usually, the server and client libraries are also included in the Half life SDK. But the SDK uses one closed source library for everything GUI related: vgui.so. The server dll, hl.so, in itself is no issue, and can be recompiled for the ARM architecture. The client library, however, uses vgui and cannot be recompiled. That’s where XashXT enters the stage. It’s a client library that is not using any closed source code, to replace the closed source implementation. In order to be compiled, I had to prepare a customized makefile as well, while still keeping the Linux/OpenGL structure.

He ended up with all libraries as follows:

xash3d, which does not do much

libxash3d.so, the heart of the engine

libxashmenu.so, used for the main menu

hl.so, as mentioned before, the Half life server library

client.so, coming from XashXT, replacing the original one from Half life.

Glshim (already discussed many times previously on PandoraLive) is a central piece as well since it takes care of converting the OpenGL code to OpenGLES on the fly for the Pandora hardware. We won’t come back on it here.

So, at this stage you might expect the work of PtitSeb to be done ? just like these scientists thought nothing could go wrong, hey ?

Well, like in HL, nothing went exactly as expected.

PtitSeb: The first time I launched it, the Half life Menu appeared, I was very happy… but the New Game menu item could not be selected! It could find the data files, but it was not able to find the hl.so and client.so files. The trick was to modify Xash3d a little bit to look for hl.so and client.so in the right folder instead of its root directory.

After fixing that, I was able to select “New Game”, and then select the “Easy” difficulty mode. But nothing happened. It was very frustrating. After multiple debugging sessions and traces, I did finally understand that my previous attempts with the missing client.so and hl.so files resulted in the creation of an improper file, used for the subsequent launches. After erasing it, the game was finally able to start! The next step was to add default mapping for the controls, to make it easy to play Half Life on the Pandora.

While the game ran very well at that stage, a few issues were still remaining. First, some small textures on screen were not displayed properly and had this fuzzy look to them, especially at close distance. See the effect below on the ground floor tiles ?

While the first aid kit looks fine at this distance, when you come closer the same artifacts occur:

This is actually an issue probably related to the GPu (the SGX hardware, or its driver):

PtitSeb: In OpenGL, just like in DirectX, you always need define a “world” in the first place, with coordinates in all three axes. If the world becomes too big, the SGX driver experiences issues. You get this staircase effect on smaller textures, because the texture coordinates are between 0.0f and 1.0f. There are probably limits on the shaders precision in GLES1.1… and this is not unique to Half Life, I have noticed this issue as well in my other TORCS port… and I think it occurs in Alien Vs Predator as well.

Unfortunately this issue is going to be hard to resolve, unless someone comes with a proper solution. It’s not game breaking anyway, but it does not completely clean either. The second bug was much, much more critical to the gameplay itself.

It affected the weapons.

During the beta test of the port (where PtitSeb provided a binary for 5-6 people to test), someone realized that picking the crowbar did not work as in the original game: the crowbar disappeared from the ground where it was lying, but did not show up in Gordon’s inventory thereafter!

How can you fight those face-huggers then?! Do you spit on them when they come ?

PtitSeb: This was coming from the communication between the client and the server libraries, as mentioned earlier. They both communicate using a message encoded bit by bit. Since the server is trying to minimize the size of the messages to be communicated (remember, at the time, 56k modems where the state of the art), the server tries to compress the data whenever possible to minimize the total message size.

Weapons and the suit are coded on 32 bits. Freeman’s suit is using the last bit of the 32bit DWORD, while the crowbar is using the #0 bit. So if you have the suit and the crowbar, the server computes it as 0x80000001. It is stored as an integer, which becomes (if unsigned) equal to 2147483649. The server communicates with the client by multiplying the integer by a float to transmit an integer – while the multiplier can be 1.0f, it can also take a different value (for example 0.1f if it knows the number can be divided by 10) in order to reduce the amount of data to transfer to the client.

And this is where the problem comes from. On X86, the typical calculation of integer * float is going to be addressed the math coprocessor, using 80 bits precision for the final float. No problem. In the end, the client takes the float and converts it back to an integer, and you get 0x80000001 again. On the ARM architecture, however, the calculation of an integer by a float is handled by the NEON architecture, and its precision is 32 bits, not 80 bits. Because of the way the float is rounded, you lose the final 1 that was in 0x80000001, and it ends up as 0x80000000 instead. Which means, you lose the crowbar!

I added an if to fix this issue, so that the multiplier is ignored in case it is equal to 1.0f (iValue contains the weapons code in the following lines):

That way there is no loss of precision even on the ARM architecture, and the crowbar can be obtained!

So this sounds like the end of the tunnel, but you know PtitSeb if you are familiar with the Open Pandora community. He’s not satisfied until he tried to optimize things a little bit.

While the initial framerate was already pretty good, sometimes the FPS dropped a little under the 10fps count causing some slowdowns and disruption in more complex scenes. PtitSeb found another way to further optimize this behavior.

PtitSeb: I am used to the IdTech3 Engine, where everything is optimized for rendering to minimize the OpenGL calls. IdTech 1, however (used for Half life as well) was first made for software rendering and OpenGL was an afterthought. Therefore the OpenGL calls are not grouped together as much. There are a lot of individual glBegin / glEnd in the renderer source code. Therefore, I decided to activate the “batch” mode of glshim, that I coded in order to limit the GLES calls.

In this mode, all GL calls are stored into lists, and if two lists follow each other and are deemed “compatible”, they are merged in a single call. I determine if they are compatible by confirming there is no state change (for example textures or matrices). It’s still very experimental, but it yields good results in this particular game.

indeed, framerates have improved with the batch mode, but more significantly the overall framerate is more consistent and does not vary as much as in the previous version. And all in all, Half Life looks fantastic on 5 inches screen like the Pandora’s.

If are you interested to know a little more about this batch mode, here’s some further technical explanation from PtitSeb himself:

PtitSeb: Here is some actual code. In xash3d/client/gl_rsurf.c are found many of the Level Geometry drawing. If you take void DrawGLPoly( glpoly_t *p, float xScale, float yScale ) function for example, you can see it’s basically a simple loop, that draws … a polygon.

PtitSeb: In other words, it’s a very simple glBegin / glEnd with a loop to feed vertex / texture coord… Of courses, thoses kind of calls doesn’t exists on GLES, so glshim takes care of the transformation. A GL_POLYGON can be viewed as a simple GL_TRIANGLE_FAN. So what glshim does is:

1. At the glBegin: prepare a list, allocate some space for Vertex, TexCoord, Normal, everything… and store the mode wanted (here GL_POLYGON)

2. At each glTexCoord2f: change current TexCoord

3. at each glVertex3f, create a new complete vertex, with the vertex coord, but also (here) the Texture Coords. Everything is stored in some arrays.

4. At glEnd, the arrays are finished, counted, and a call to glDrawArrays (here, because GL_POLYGON is the same as GL_TRIANGLE_FAN) is drawn, and the arrays are freed. If the mode have no direct equivalent in GLES (like for a GL_QUADS), than an index array is created and a glDrawElements is performed.

With the BATCH mode, at the glEnd, the glDrawElements function is not called immediately, but the list is simply stored for later rendering. Moreover, that new list is compared to the previous one (if any). If the current list is just a glDraw, it is checked if it can be merged with previous.

So, if a texture remains the same, two (or more) subsequent calls to DrawGLPoly will produce only a single glDraw call (and in this case, probably a glDrawElements, because you need to transform the GL_TRIANGLE_FAN to a GL_TRIANGLES with an index, and then you can “add” the two indexed GL_TRIANGLES into one).

By saving on the number of drawing calls, you get more FPS, especially with GLES.

Now, I guess you want to know how to play and install Half Life on your Pandora ? Well, you can either dig an old Half life CD, update it to 1.1.1.0 on Windows and then follow the below steps for the valve folder… or if you prefer a more recent online source, you can do the following:

And no word about SDLash. In fact – that’s not port. It already runs on ARM linux and Android (with NanoGL wrapper). All credits goes to a1batross and SDLash3D project members, and, of course, to original Xash3D creator – Uncle Mike.

It is not really need to buy game from steam. Valve already got money on it in 2000-x, so you can just download half-life dedicated server (it is availiable on ulilities page for free). It has all resourses except of some hd models.

Vote Up0Vote Down Reply

2 years ago

Post navigation

What’s This ?

GiantPockets (Previously Known as Pandoralive.info) is a blog dedicated to the Pocket Linux Handheld computers (such as the Open Pandora and the upcoming DragonBox Pyra, but also non-gaming handhelds like the GPD Pocket). These handhelds can be used for gaming but for a bunch of serious uses as they can run full Linux distributions and are typically equipped with physical keyboards. You will find here news regarding their latest software offering, hardware updates, development stories and testimonies of users.