Reposted from Shamus’ Good Robot Devblog
Good Robot has a problem. It’s a strange, goofy, inexplicable problem and I’m pretty sure (60%-ish) that it’s not my fault. Here is what’s up:
Our game is capped at 60fps. That’s fine, except the cap isn’t self-imposed. Oh, I have a frame-limiter in the game, but it doesn’t do anything. If I disable it, the game is still limited to 60fps. Even if I render nothing more than a blank screen, I can’t get the framerate to go above 60. Under those conditions, the framerate should be in the thousands.
That’s not the problem. It’s certainly a curiosity, and it’s been on my long list of “mysterious stuff that bugs me” for a couple of years now, but it’s not really a threat to the project as a commercial product that will hopefully feed us someday. The more serious problem is that if you try to capture the game footage at all through Fraps, Bandicam, or streaming software, the framerate drops to 30fps.
Note that it drops to exactly 30fps. It’s not like the game gets bogged down drawing robots and moving laser bolts around. My code continutes to run nice and fast, but then some other system jumps in and puts on the brakes. As I said way back in part 4, the gameplay is tied directly to the framerate in Good Robot. If the framerate drops to half, then the game begins running at half speed.
This would be a major no-no in a AAA game (stuff like this is one of the reasons console ports go bad) but here I don’t think it’s a big deal. At any rate, it saves me a ton of complexity and headaches, and is one of the reasons I was able to accomplish so much on my own. And usually limits like this are a problem because they’re low. Someone ports a 30fps game to the PC and players want to run at 60fps, only to discover the collision engine / audio engine / game logic breaks at that speed. I seriously doubt there are lots of people who are going to want to under-clock the game because 30fps “feels better”. Which is to say: The game is locked at 60fps, and I doubt that’s going to cause a consumer revolt.
As I said earlier in the project, it’s pretty common to build projects using tools made by other people. Every single project does not need to re-invent the same wheels. Now, most projects use a third-party graphics / game engine. But I like to mess around with rendering on the polygon level, so I didn’t go that route. Instead, I used some smaller-scale stuff. I’m using OpenAL to handle audio. OpenGL to talk to the graphics hardware. SDL for talking to the windows gui.
That last one is kind of important. When you want to create a window (the thing you can move around, minimize, and resize on your desktop) you need to talk to Windows (the commercial operating system from Microsoft) and it usually takes a few hundred lines of super-boring boilerplate code to do that. Worse, that boilerplate code needs to be different for every target platform: Windows, Linux, Apple. This stuff is really annoying and ugly and every operating system has a slightly different way of doing things, so the logic for each platform will always be a little different from the others.
SDL fixes this by hiding all of that functionality inside of a black box. You just tell SDL, “Hey, I need a window that’s 1024 x 768, and I need that window configured so that I can render into it with OpenGL.” In about five lines of code you can accomplish what would require 100 lines if I was using the windows API directly. The reason for this simplicity is that a game doesn’t need 90% of the windows API. It doesn’t need to be able to drag-and-drop files, it doesn’t have a right-click menu, it doesn’t have a menu at the top, and it doesn’t have dozens of little sub-windows and dialog boxes like (say) a spreadsheet or Photoshop or whatever. All we want is a rectangle to put all our pretty graphics in.
How it works is this: After I create my window, I can begin rendering. So I draw some robots and lasers and whatever. When I’m done I say:
SDL_GL_SwapBuffers(); |
This tells SDL “Okay, I’m done drawing now. Show the result to the user.” Then SDL tells OpenGL to show the user the next frame of gameplay. I can then begin drawing the next frame. The problem is that SDL_GL_SwapBuffers() also seems to be “helping” me in the most unhelpful way.
If you’re trying to run your game at 60fps, then you’ve got just 16 milliseconds to finish everything. If you take 17, then you’ll miss the screen refresh. It’s a bit like missing the bus: You have to wait for the next one. Missing the bus by one minute won’t make you one minute late, it will make you N minutes late, where N is the interval between buses. In this case, you’ll be a whole frame late and your framerate will drop to (say) 30fps.
It’s actually more complicated than I’ve described it. (Isn’t it always?) But this is close enough for our purposes.
The problem is that SDL_GL_SwapBuffers()
has taken it upon itself to enforce my framerate for me. Or maybe not SDL_GL_SwapBuffers()
itself, but something inside of SDL_GL_SwapBuffers()
is doing this. If I finish my frame in 15ms, it waits 1ms before returning control to me. If I finish in 1ms, it waits 15. No matter what I do, it won’t let me go faster than 60fps.
Which would be fine, except for the fact that the framerate goes down to 30fps when anyone tries to record or stream.
This is important. The odds are that none of the big famous streamers are going to play our game. But what if one did, and when they tried the game was awful and sluggish because it was running at half speed? It would turn a stroke of good fortune into a disaster and people would laugh at us because our 2D game runs like a butt. For small teams like ours, streaming can result in a huge boost to sales.
- So we have this problem where framerate is cut in half. This is triggered solely by external programs, over which we have no control.
- I’ve made many other game-type things, and this problem only happens on my projects that use SDL. My old projects are fine[1].
- Whether it’s the fault of SDL or not, the problem happens inside of an SDL call where we can’t see what’s going on.
- This slowdown doesn’t make the game choppy. Instead, it makes the game feel slow, like constant bullet-time. That’s a fun-killer.
If worse comes to worse, I could use some kludge that will detect this case. If the game sees that the framerate is at 30fps and that SDL_GL_SwapBuffers()
is devouring a ton of time, then it can simply skip drawing every other frame. This would counter the game running at half speed by forcing it to run at double. That’s a gross and clumsy solution, but it’s better than shipping a game in this condition.
Still, a better solution would be to track down the cause of the problem.
Searching the SDL docs yields nothing.
Searching for the problem via Google yields nothing.
As an experiment, I try calling wglSwapBuffers ()
. This is the windows-specific[2] call. See, I stongly suspect that when I call SDL_GL_SwapBuffers()
it turns around and calls wglSwapBuffers ()
for me. If you’ve ever heard coders talk about a “wrapper function”, then now you know what it is. It’s a function that simply calls another function. The reason for this is that it can hide the OS-specific crap for me. On windows it will call wglSwapBuffers ()
but on linux it will call glXSwapBuffers ()
I don’t have to memorize how every OS works and write special code for each one.
So, if the problem is in SDL, then going around SDL and forcing the refresh myself ought to fix the problem. Which it doesn’t.
So while looking exceptionally guilty, SDL is not our culprit. I try searching for this problem again, but looking for OpenGL answers instead of SDL answers. At the end of much head-scratching I find:
wglSwapIntervalEXT (0); |
Now, I’ve never used this thing. Heck, I’ve never even heard of it. But apparently it asks OpenGL to handle the framerate cap for you. I’ve never had to use it in any of my projects, and I’m pretty sure the feature is not enabled by default. Which means you have to specifically request it. Which I haven’t.
But sure enough, if I call this function and explicitly turn OFF the OpenGL frame limiter, the game is uncapped. On my machine, I get about 150fps. I have to enable my own cap to keep it at 60fps, because otherwise the game runs at super speed. Which is amusing. Briefly.
So why do I need to turn off a feature I’ve never heard of, never encountered before, and which should be off by default? My hypothesis is that SDL turns this feature ON for some reason. (Probably when you create your window.) So you have to turn around and turn it off again if you don’t want it messing with your clock.
With this fix in place, the game is stable at 60fps, and recording / streaming no longer causes problems.
So why did capture software cut the framerate in half? I have no idea. There’s all kinds of sorcery going on down in the lower levels that I don’t like to think about or mess with. That’s where balrogs live.
My guess is that the frame-capture was hitting at just the wrong moment and preventing a clean refresh. It’s like someone taking your picture just as you’re about to get on the bus. You have to hold still for a second, and by the time they’re done you’ve missed the bus and have to wait for the next one.
Hopefully someone will stream this game so I won’t feel like this was a waste.
Posted In: Games, Good Robot