Optimizing my gratuitous GUI

January 11, 2015 | Filed under: gsb2 | programming

If you’ve watched high-def videos of Gratuitous Space Battles 2, or been lucky enough to try it at EGX, then you may have noticed all that gratuitous GUI fluff that animates and tweaks and flickers all over the place, because… frankly, I like that kinda nonsense, and it’s called GRATUITOUS, so I can get away with it. This sort of stuff…

widgets

Anyway…I love it,and it’s cool, but until today it was coded really badly. I had a bunch of helper functions to create these widgets, and I sprinkled them all over the GUI for fun. For example there was a function to add an animated horizontal bar that goes up and down. Wahey. Also there are functions to add random text boxes of stuff. The big problem is that each one was an isolated little widget that did it’s own thing. In the case of a simple progress bar widget, it would have a rectangle, and a flat-textured shaded box inside it that would animate. That meant 2 draw calls, one for the outline of the box (using a linelist) and the other was a 2-triangle trainglestrip which was the box inside it. That was 2 draw calls for a single animated progress bar thing, and a single GUI window might have 6 or even 20 widgets like that… so suddenly just adding a dialog box means an additional 40 draw calls.

Normally that doesn’t matter because a) 40 draw calls isn’t a lot, and b) graphics cards can handle it. However, it’s come to my attention that on some Intel integrated cards, which are actually surprisingly good at fill rate and general poly-drawing, too many draw calls really pisses them off, performance wise. Plus… 40 draw calls isn’t a lot, if thats your ‘thing’, but if there are 40 on the minimap, 40 on each score indicator, 40 on the comms readout, 40 on each of 3 ship inspector windows, then suddenly you have several hundred draw calls of GUI fluff, before you do the actual real GUI, let alone the big super-complex silly space battle, and yup…I’ve seen 4,000 draw calls in a frame. Ooops. To illustrate this, here is that top bunch of widgets in wireframe.

wire

That’s a lot of stuff being drawn just for fluff, so to ease the burden on lesser cards, I should be batching it all, and now I am. That used to be about ten trillion draw calls and now its about five. I have a new class which acts as a collection of all the widgets on a certain Z-level, and it goes through drawing each ‘type’ of them as it’s own list. Nothing actually draws itself any more, it just copies it’s verts to the global vertex buffer, and then when I need to, I actually do a DrawIndexedPrimitiveVB() call with all of them in one go.

Ironically, this all involves MORE verts than before, because whereas drawing a 12 pixel rectangle with a line list involves 4 verts, drawing it as a trianglelist uses loads more, but I’m betting (and it’s a very educated bet) that adding the odd dozen verts is totally and utterly offset by doing far, far fewer draw calls.

This is how I spend Sunday Afternoons when it’s too cold for archery…

 

I publish games by other indies (redshirt and big pharma so far, with an investment in duskers), and am always open to looking at proposals. I turn down more than I fund. Some pitches are awesome, some are dreadful. Here are some tips, whether you are pitching to me, or someone else.

Reality check:

Before you write a single word of a pitch, step back and remember what you are asking. Your whole pitch is basically ‘Will you take a $50-200,000 bet on me making a profitable indie game?’ This is a tough ask, a VERY tough ask. Unless you have, in your direct personal experience taken a bet of that magnitude where there is a chance you will lose every single penny of it, then you don’t really appreciate what you are asking. No investor *has* to invest their money. They can leave it in a bank earning nothing, but staying safe. They can stick it on the stock market and hope to make a relatively safe return. They can spread it across business crowd sourcing and probably make a safe 5%. Or they can gamble the whole lot on your game design… The reason they will choose the game is that sometimes (rarely), it’s a big big win.

What To Do:

  • Include a brief bio, so we know who you are and what you did. Even if you are just 18 years old and always been in school, still say it, tell us what you studied in this case, and how you did.
  • Get straight to the point with a very short elevator pitch description of your game. Don’t expect us to get to page eight to find out its a mobile platformer.
  • Include figures. They will be wrong, but we need to know you have thought about them. Be able to back them up to the best of your ability.
  • Include timescales, broken down into milestones. We want to know this is not an open ended thing.
  • Explain why you need the VC. Is it the money? or the expertise? If it’s just money, why haven’t you borrowed it from family, or a bank? or re-mortgaged your house? There are justifications for all of these, but we want to know yours.
  • Include flavor text. Games that are simply a list of bullet point features with no soul generally tank. We want to know the passion, the feelings and emotions you will put into this game. Concept art also helps. Even art from other games, movies, inspirations etc is fine, if it helps to conveys your vision.
  • Include an allowance for marketing the game in the budget.
  • Include any evidence that you can finish a project this big. Even if its not a game, if you wrote some software that manages a tractor factory, thats really worth knowing. Seriously.
  • Include allowances for stuff like software licenses and subscription stuff.

What Not To Do:

  • Don’t include a lot of crap about the size of the games industry, with huge numbers in, as some sort of ‘intro’. If I’m an investor in the games industry, I know this stuff anyway so you are boring me, and telling me what activisions profits were last year has fuck-all relevance to your indie game.
  • Make financial errors in any calculation, anywhere in your pitch
  • Compare yourself in any way to minecraft. It’s an outlier.
  • Pitch a three year project based on you sleeping in a tent eating noodles. This is not sustainable, and we can smell the desperation.
  • Pitch a three year game where you earn $80,000 a year salary because that’s ‘the going rate’. Thats the going rate for a job, not a partnership where you own a huge royalty stream at the end of it.
  • Wrap up the whole pitch in a single page. If you are serious about your project, then editing your enthusiasm down to a few pages will be hard.

What the investor/publisher will do:

We will tell you if your game is doomed from the start (our best guess, we can be wrong) due to business / financial choices. We will tell you how we think you can fix that. Don’t think that all of your pitch has to be the absolute right choices, there just has to be some spark there, combined with reliability and technical ability. If you pitch a mobile game aimed at kids, and we think the game design and art is fantastic but it would work better as a pc game aimed at the 30+ market, thats still something that can be funded. Investors are generally looking for people, more than ideas. We assume you are flexible.

So how to pitch?

You do *not* need friends in the industry (although tbh it does help). What you need is just email. You don’t need a glossy printed brochure about your game, unless your plan is to physically hand it to someone like me at a conference. (Thats totally acceptable btw). Nobody cares if your pitch is a slide ‘deck’ or a word document, or a PDF or whatever. Contrary to what some people think, it is totally fine to just email investors out of the blue to pitch your project. If I’m honest, you probably do slightly better to introduce yourself in person, because then an immediate two-way discussion takes place, but if you email me a decent pitch I will definitely read it. My email address is cliff AT positech dot co dot uk. I only invest in PC games. Strategy preferred, but not essential. I don’t do Free to play games. I’ll be at rezzed in London in March 2015 and on a panel at GDC 2015 too if you want to elevator pitch to me.

So Gratuitous Space Battles 2 is running really well (50-60FPS even at dual-monitor 5120 res mode) on my dev PC. dev PC specs:

win 7 64bit 8 GB RAM, i7-3770k CPU @ 3.50 GHZ. GeForce GTX 670.

However it can drip pretty low on my HD4000 intel laptop (also an i7, but lower spec). I’ve seen things go to 25 FPS at 1600×900, although that is without all the fancy options off, so maybe it will go higher with them deselected. Ideally I’d get that much better. So what is the problem?

I think it’s too many small draw calls, and sadly, thats kinda the way my engine works.

The basic algorithm of my engine is this:

Update_Everything() (game simulation, partly multithreaded)
Check_what_is_onscreen()
SetRenderTarget()
DrawBigListOfObjectsToRenderTarget()
SetRenderTarget()
DrawBigListOfObjectsToRenderTarget()
...
CompositeAllTheTargetsIntoFinalImage()
GUI()

The problem is all of those lists of objects being drawn. The solution to this in a conventional 3D game (before you all suggest it), is to use a z-buffer, sort all those objects by render state or texture or both, and blap them in a few draw calls. Thats fab, but it doesn’t work with alpha blending. People who do 3D games think alpha blending means particles, but nope, it also means nice fuzzy edges of complex sprite objects. To do the order-independent Z-buffer rendering method, you have to disable proper alpha-blending, and then everything starts to look sharp, boxy and ugly as hell. 3D games sprinkle antialiasing everywhere to try and cover it. With complex sprites layered on top of each other, this just looks dreadful.

The solution is the good old fashioned painters algorithm, meaning drawing in Z order from back to front. This works well and everything looks lovely.

screen1

The problem is that you end up with 4,000 draw calls in a frame, and thenĀ  the HD4000 explodes. Why 4,000? well to get some of my more l33t effects I need to draw a lot of objects four times, so thats only really 1,000 objects. to do proper lighting on rotated objects I can’t group objects of a different rotation, so each angle of an identical object means a separate draw call. Some of my render targets let me draw regardless of that angle, but the problem then becomes textures. If you draw painters algorithm and draw this…

ShipA
ShipB
ShipA
ShipB

Then there is no way to group the ships by texture without screwing it up, if those ships overlap. This is “a pain”. There are some simple things I can do…and I have a system that does them. For example, if I have this big list of sprites to draw and it turns out that occasionally I *do* get ShipA,ShipA Then I identify that, and optimize away the second call by making a single VertexBuffer call for both sprites. (or both particle systems, in those cases) I even have a GUI that shows me when this happens….

engine1

The trouble is, the majority of the time this is NOT happening. There are to my mind two potential solutions, both of them horribly messy:

1) Go through the listĀ  and calculate where I have a ‘ShipA….ShipA’ pair where there is nothing in between them that overlaps either of them, and then re-arrange them so that they are next to each other, thus allowing for a lot more grouping. (This involves some hellish sorting and overlap detection hell).

2) Pre-process everything, building up a database at the start of the rendering of which textures seem to naturally follow on from each other, then render those textures to a temporary ‘scratch’ render target atlas, which I can then index into. This would be fun to code, also amusing to watch the render target itself in the debugger :D Adds a lot of ‘changing texture pointers and UVs after the event’ complexity though.

Be aware that I’m using Direct9, mostly for compatibility reasons, which means that rendering to multiple render targets at once, or doing multithreaded rendering really isn’t an option.

Edit: just spotted a bug with method 2. If I draw 10 instances of ShipA, they may be at different Zooms, so I will only be caching (in my temp atlas) a single image, not the full mip-map chain, meaning the rendering of atlased sprites would lose effective neat mip-mapping and potentially look bad :(