Game Design, Programming and running a one-man games business…

Optimising my ‘dumb’ textures

I have this class in my code called ‘smart texture’. I’ve had it for several games now. It basically lets me tell a sprite to use “gun.dds”, and it transparently converts that to the LPDIRECT3DTEXTURE9  and doesn’t mention it. It keeps ‘gun.dds’ in memory, and if I ever lose the screen (alt+tab), and need to rebuild stuff, it will replace the invalid surface pointer the next time it gets drawn with a fresh copy.

MAGIC.

Trouble is, that means keeping a list of every smart texture, so that I don’t miss any when recovering from alt+tab. That’s easy, but I ended up using texturedsprites (with a built-in smart texture) everywhere, and thus the list, for a full battle could be 20,000 textures long.

F**k.

So here I am, effectively keeping a record of me trying to fix this…

It’s 5.35PM Sunday. The wonders of aqtime show me that when I ditch the current level and load in a new one, I am killing off about 8,000 smart textures. Every one is going through it’s destructor and removing itself from the smart textured list. This sucks. I need this to be faster. First instinct is to speed up the destructor, but obviously that’s treating the symptom, not the cause. The real problem is 8,000 smart textures. That isn’t so smart, when 2,000 of them are probably pointing to the exact SAME surface in directx… I need to rethink this system, and NOT break any other code…

I *do* have a system called GUI_TextureCache which does some ‘fixed’ storing of textures I use all the time, like UI stuff, buttons etc. This is obviously a similar task to what I want to do here, in that I need a dynamic dumping ground for commonly used textures pointers. I’m concluding that the cleanest way to handle a fix would be within my SmartTexture itself. it can do some clever caching, and then nobody will ever know any code changed!

Realisation that this means I still keep all these smart textures knocking about, with their ‘gun.dds’ strings. That offends me, as a programmer, but tbh, even ‘longcomplextexturefilename.dds’ is only 30 characters, so with 2,000 units I’m wasting 60k here. Big deal.

Right, so the plan is when I call SmartTexture::SetTexture(“gun.dds”) it may, or may not add itself to a list of textures that would need rebuilding, based on if its already in the list.

Problem: I can’t do that, because it means checking the whole list every time I call SetTexture(). That might even be *slower*. However, my current ‘SetTexture’ goes through a list to grab the pointer, how slow is that? It looks like currently its about half as much time as all those smarttexture destructors, so it might be a win. Plus my SetTexture() stuff uses a MRU caching system which could make ti super fast when loading in 100 identical units…

5.50PM Actually realising I’ll need a totally new class to handle this. it needs to be done a different way, not with existing smart textures. Balls. Surely it can be done? Hold on.. Surely its just a matter of indirection. The purpose of a smart texture is to hold a surface pointer it can rebuild if needed, but it doesn’t have to hold a direct link. It could actually hold an indirect link.  One more piece of pointer indirection is trivial. I just add a new CLoadedTexture class that handles rebuilds, and the smarttextures can point at those, meaning no need to keep a list of them at all, as their pointers never go invalid or need rebuilding.

6.25PM everything coded except the actual rebuild() calls for alt+tab. I’ll comment the errors out for a quick test… GAH, it crashes immediately. fixed easily enoguh, and the code RUNS! hurrah, but it doesn’t seem AMAZINGLY faster,. Quick! to aqtime! what the hell… it’s slower?…

6.35OM. Hah! looking at it backwards, it’s about 75% faster. how much time does that knock off loading in a new level now? 5.28 seconds down to 2.1 seconds. That’s pretty good. I’ll code in the rebuilding stuff and give it a quick test…

6.50PM Alt+tab doesnt work, I get just a black window, but that may be something else. I’ve checked in my current code, and am investigating alt+tab issues now. It looks like the restore code hasn’t worked on this new game at all yet. I consider my (dumb) smart texture code fixed :D

 


9 thoughts on Optimising my ‘dumb’ textures


  1. Right, so the plan is when I call SmartTexture::SetTexture(“gun.dds”) it may, or may not add itself to a list of textures that would need rebuilding, based on if its already in the list.

    Problem: I can’t do that, because it means checking the whole list every time I call SetTexture().

    This is probably moot since your new approach breaks off at an earlier branch, but there are ways of making that “check the whole list” operation a ton faster if all you’re looking for is a matching string. How many distinct texture filenames are we talking?

  2. You still could hold a list of all smart textures and still quickly access it. Use a hashmap and hash the filename. This would also quite effectively solve the multiple entries problem for any double texture entries.

  3. true, but I don’t like the idea of forcing that operation to be faster, if I know deep-down that I shouldnt be searching such a list in the first place. just feels pretty untidy to me :D fastest code is code never run, etc.

  4. Yep, the best optimizations are ones that make the program do less work :)

  5. I also believe that using a Hashmap would solve your issues. Sure, it’s a tiny bit slower than your complex approach. But it also takes you (exactly!) seven seconds to write, instead of a full day, and it will work first try, because it’s so simple, it’s hard to mess up.

    You’ve now spent a day to optimize 2.5 seconds. You could have spent a day on something else and only optimized it by 2.49 seconds. I quote Knuth so often (*) I will soon start mumbling it in my sleep: Premature optimization is the root of all evil.

    * When someone wants to improve a simple function call “foo(Cstring s)” by templating it to hell and back (something like “foo<TemplateClassToInt, CString, enum_Const>(datatype s)”, and I’m not making this up) to save three CPU-cycles once per program load, when the program in question has to query the DB anyway. Seven nanoseconds off a full second is still a full second.

  6. it certainly didnt take a whole day, just a few hours, and its an optimisation at the heart o my engine code, meaning whatever game I do next will benefit as well, so I think it is worth doing it right.
    I also like having stuff ov er optimised to allow for later cslaing up. I may end up using a lot more textures than I currently do, for example, or might scale from 1,000 objects to 50,000.

    But I take the point, and if I used hash tables all over the place and could just drop in some code from elsewhere that I knew really well, it would have been more tempting to do so.

  7. Remember, that’s 2.5 seconds off of *USER RESPONSE TIME*. It’s not like it only loads once per play session, loading a level could happen frequently, especially if using quicksave and quickload, or if the game calls for being able to restart a level easily. It’ll make a definite impact on user experience.

  8. I guess that Cliff knows very well which code gets called how often to justify the time he puts into it.

    But hashtables (=dictionaries) rule. Totally!

  9. Reading this post inspired me to finally sort out my own resource manager. I blogged about it here:
    http://mattiasgustavsson.com/Blog/simple-and-efficient-management-of-shared-resources/
    and I’ve made the source code public domain. It is using the hash table approach mentioned by others here, and it is pretty well optimised (I can create and destroy one million assets in less than half a second on my computer – this being the overhead of the resource manager, excluding the time to actually load data etc).

    Hope it will be of use to someone :-)

Comments are currently closed.