A recent Shot of Jaq episode described the many problems that indie game developers face when porting their games to Linux, let's break down the problems into categories:
1. Which libraries do I use?
2. How do I package the game?
So here we go.
Which libraries do I use?
This is the one that people make the most fuss about. When developing a game on Windows, you know full well that if you want to create a 3D enabled window, you have Win32 + Direct3D + DirectSound (or whatever it is now) right there. And it works. It works because the whole Windows game world develops using those APIs and they are well tested. When coming over to develop for Linux, many devs don't know where to look for the equivalents, and when they ask around they get different answers from everyone, because people recommend what they like, not what is the most sensible option. Let me answer the question right now:
1. For graphics, use OpenGL 2.1 (or 1.5 if you don't need GLSL)
2. For sound, use OpenAL
3. For windowing and input, use SDL
Now, before I get ransacked by people with opinions, I'll explain why :)
Graphics
This is the easiest one to decide. OpenGL is the only choice for hardware accelerated graphics on Linux. If you can do without shaders and target 1.5 you can get the lower end hardware, if you need shaders then go for 2.1 rather than 3.x or 4.x because Mesa doesn't support them (yet) and the open source ATI and Intel drivers both support it. You can of course check the version at runtime if you want 3.x or 4.x features, but don't rely on it.
Sound
This is the one that causes the most noise (no pun intended), ALSA? Pulse? OSS? etc. No you want OpenAL. OpenAL gives you a consistent 3D sound API that wraps the underlying complexity of various sound systems. It's not perfect, but it's definitely your best choice. Hemisphere games came to the same conclusion when porting Osmos. Just go with it and test as much as possible. openal-soft has a number of backends too, so if you don't have Pulseaudio, or whatever, it will still work. The openal-soft developers are really responsive. I had a load of help trying to diagnose a problem with openal + pulse (which was a bug fixed some time ago now).
Windowing and Input
I can tell this will stir up a hornets nest of people saying that straight X is better, and indeed straight X is what the Osmos developers went with. But SDL has been used for a load of games (World of Goo, Penumbra, Quake 4 etc.) it's pretty damn good. Again, it wraps the complexity of the underlying X and handles any quirks for you. Also the SDL developers are responsive to both feature requests and bug reports, and have a really busy mailing list. If you have any trouble, you have somewhere to go and get it fixed. If you don't think SDL is flexible enough, then go with X, but let me assure you - you'll be writing a lot more code, and that code is your problem.
There, done. By offloading the work to these libraries you get a number of advantages:
1. Bugs you find and report (and perhaps even fix) help everyone
2. They deal with the hard work for you, reducing the amount you need to code
3. They are widely available
4. All three of them have nice, consistent APIs
Packaging the Game
Packaging is a pain in the arse if you want to target as much of the Linux-based ecosystem as possible. Generally though, packaging in .deb, .rpm and .tar.gz in both 64 bit and 32 bit versions seems the way to go. Games in The Humble Indie Bundle were packaged in various different ways, but World of Goo had a .deb package that worked the best compared to the fiddling I had to do to get the others to run.
It's probably best to go with the distributions package manager to handle dependencies, especially for the big 3 libraries (GL, AL, SDL) and also FreeType and libvorbis. That's the approach Hemisphere games took and they seem pretty happy with the result.
Other Libraries
Obviously it's not just graphics, sound, input and windowing you need to worry about, here are some suggestions for other libraries:
1. Fonts - Freetype2
2. Image loading - Take a look at SOIL
3. OpenGL extension handling - GLee is popular, but not included in the Ubuntu repos, GLew does the same job and is in the repos.
4. Physics - Box2D for 2D, ODE for 3D
Showing posts with label openal. Show all posts
Showing posts with label openal. Show all posts
Friday, 18 June 2010
Friday, 19 February 2010
OpenALSoft/Pulseaudio: it's not me, it's you.
I learnt a valuable lesson this week. If you have rewritten your code 3 times, and your tests all pass and you understand every part of it and yet it still doesn't work ... it's probably something not to do with you.
Last weekend I decided it was time my game gained some sound. I'd just finished the game menu screen and figured that some music would make it seem a little more complete. In an attempt to follow the KISS principle I decided to keep it simple, I'd write a class that just plays an OGG file in 2D. No multiple sources, no bells and whistles, nothing fancy, just load an OGG and play it. A quick Google search turned up this gem of a tutorial, so I figured it was a 10 minute job. When the time came to do more, I'd look for a decent high-level library, or just steadily build on my simple base code but for now, I figured, following this tutorial would teach me a little OpenAL then I could move on.
And for a little while I was right. I wrote my class, loaded an OGG file and played it with OpenAL by loading it into a single buffer and binding the buffer to the source before calling alSourcePlay(). It worked!
...
For about 2 seconds. Then it crackled and went silent.
I assumed that the problem was the track I was playing was too long to be bound in a single buffer, and instead I figured I should break it into smaller buffers and feed it into the source to play. Possibly a bad assumption, I guess I should've tried playing a smaller sound to see if that was actually the case - but I was having fun, and queueing the buffers looked simple enough.
So I got to work, I decoded the OGG stream into a vector of vector<char>'s the idea being that as room became available in the queue I could grab the next chunk of data and queue it up ready for playing. Again, it only sort of worked. The track would buzz for 10 seconds, then play a second or 2, then go back to buzzing, then play some more. Sometimes it would play a whole 30-40 seconds perfectly, then the buzzing would start again. I fiddled with buffer sizes, buffer counts, filled my code with logging statements (the buffer queuing just seemed to stall while the buzzing took place). I tried to simplify it, removing lines of code, reorganizing until the code was slim and streamlined, I switched from vectors, to filling out static char* buffers. It made no difference.
I downloaded the source code of various apps, SuperTuxKart uses OpenAL and apparently the followed the same tutorial as me! In fact all the examples I could find had very similar code to the same tutorial, and my code looked pretty much the same.
This is the point I should have realized something else was at work here, in fact I did try upgrading ALSA but that changed nothing, so what did I do? I came up with a new design, one that had many benefits and might, just might have the side effect of solving the problem.
The problem with the previous attempts was I was loading and decoding the entire OGG file in one go. A decompressed, high quality OGG takes up a lot of memory, it's REALLY wasteful to decode the whole thing and keep it in memory. The other thing I had to be careful of was that multiple sources could play the same stream without an issue. I decided instead to load and store the compressed file data. Then create an OggDecoder class which each source gets an instance of that decompresses the data from memory on the fly. The benefits of this approach are huge, you don't hit the disk while playing, you keep memory usage down, and multiple sources can play from the same compressed data, and the code is pretty small too. So I finished, stood back and admired my work and pressed "compile and run". It worked! I was relieved. But just in case, I ran it a second time (it had *occasionally* worked before, it just wasn't consistent). You can guess what happened, the buzzing was back. Repeated runs left me with the same problem.
I was about to give up, about half way through this process I discovered cAudio which was basically where unintentional feature creep was pushing my code anyway (while trying to load an play a sound, I'd come up with a whole multiple song streaming solution with pluggable decoders!), so I figured I'd dump my code into the "deprecated" folder that I keep, and just use cAudio. Just as I was about to give up I found a forum post where someone had similar problems and they solved it by upgrading the OpenALSoft libraries. I gave it a try and lo and behold, it works!
I don't know why it works and didn't before, perhaps an OpenALSoft conflict with PA? Perhaps a bug in OpenALSoft? I dunno, and I don't care, if the problem occurs on another PC at least I know how to fix it.
The only regret is that I didn't realize it wasn't me earlier. I'd literally rewritten the code several times in different ways while comparing to code in other projects. Alarm bells should have rung earlier. Oh well.
Last weekend I decided it was time my game gained some sound. I'd just finished the game menu screen and figured that some music would make it seem a little more complete. In an attempt to follow the KISS principle I decided to keep it simple, I'd write a class that just plays an OGG file in 2D. No multiple sources, no bells and whistles, nothing fancy, just load an OGG and play it. A quick Google search turned up this gem of a tutorial, so I figured it was a 10 minute job. When the time came to do more, I'd look for a decent high-level library, or just steadily build on my simple base code but for now, I figured, following this tutorial would teach me a little OpenAL then I could move on.
And for a little while I was right. I wrote my class, loaded an OGG file and played it with OpenAL by loading it into a single buffer and binding the buffer to the source before calling alSourcePlay(). It worked!
...
For about 2 seconds. Then it crackled and went silent.
I assumed that the problem was the track I was playing was too long to be bound in a single buffer, and instead I figured I should break it into smaller buffers and feed it into the source to play. Possibly a bad assumption, I guess I should've tried playing a smaller sound to see if that was actually the case - but I was having fun, and queueing the buffers looked simple enough.
So I got to work, I decoded the OGG stream into a vector of vector<char>'s the idea being that as room became available in the queue I could grab the next chunk of data and queue it up ready for playing. Again, it only sort of worked. The track would buzz for 10 seconds, then play a second or 2, then go back to buzzing, then play some more. Sometimes it would play a whole 30-40 seconds perfectly, then the buzzing would start again. I fiddled with buffer sizes, buffer counts, filled my code with logging statements (the buffer queuing just seemed to stall while the buzzing took place). I tried to simplify it, removing lines of code, reorganizing until the code was slim and streamlined, I switched from vectors, to filling out static char* buffers. It made no difference.
I downloaded the source code of various apps, SuperTuxKart uses OpenAL and apparently the followed the same tutorial as me! In fact all the examples I could find had very similar code to the same tutorial, and my code looked pretty much the same.
This is the point I should have realized something else was at work here, in fact I did try upgrading ALSA but that changed nothing, so what did I do? I came up with a new design, one that had many benefits and might, just might have the side effect of solving the problem.
The problem with the previous attempts was I was loading and decoding the entire OGG file in one go. A decompressed, high quality OGG takes up a lot of memory, it's REALLY wasteful to decode the whole thing and keep it in memory. The other thing I had to be careful of was that multiple sources could play the same stream without an issue. I decided instead to load and store the compressed file data. Then create an OggDecoder class which each source gets an instance of that decompresses the data from memory on the fly. The benefits of this approach are huge, you don't hit the disk while playing, you keep memory usage down, and multiple sources can play from the same compressed data, and the code is pretty small too. So I finished, stood back and admired my work and pressed "compile and run". It worked! I was relieved. But just in case, I ran it a second time (it had *occasionally* worked before, it just wasn't consistent). You can guess what happened, the buzzing was back. Repeated runs left me with the same problem.
I was about to give up, about half way through this process I discovered cAudio which was basically where unintentional feature creep was pushing my code anyway (while trying to load an play a sound, I'd come up with a whole multiple song streaming solution with pluggable decoders!), so I figured I'd dump my code into the "deprecated" folder that I keep, and just use cAudio. Just as I was about to give up I found a forum post where someone had similar problems and they solved it by upgrading the OpenALSoft libraries. I gave it a try and lo and behold, it works!
I don't know why it works and didn't before, perhaps an OpenALSoft conflict with PA? Perhaps a bug in OpenALSoft? I dunno, and I don't care, if the problem occurs on another PC at least I know how to fix it.
The only regret is that I didn't realize it wasn't me earlier. I'd literally rewritten the code several times in different ways while comparing to code in other projects. Alarm bells should have rung earlier. Oh well.
Subscribe to:
Posts (Atom)