Coding tricks of game developers
dodgycoder.net
dodgycoder.net
To this day I still feel very very dirty about this hack, but it was needed to achieve the objectives and harmed no-one :)
Great story! Next time just make sure you wear gloves.
The dirty code is the stuff that was stomping yours in the first place.
We just forcefully reset the bit after any call to the Windows message pump, and that was the last of our network desyncs gone for good. Ship it!
(I recall DirectX later added some flag related to this, but memory is hazy)
// branchless octree traversal
i = x <= t->mx;
i <<= 1;
i |= y <= t->my;
i <<= 1;
i |= z <= t->mz;
t = t->child[i];
// unrolled branchless binary search of a 64-element array
if (p[32] <= x) p += 32;
if (p[16] <= x) p += 16;
if (p[ 8] <= x) p += 8;
if (p[ 4] <= x) p += 4;
if (p[ 2] <= x) p += 2;
if (p[ 1] <= x) p += 1;
return p;
(In the binary search, you'd implement the if statements with conditional moves or predicated adds, depending on what your platform offers.)Both of these examples replace branching with data-dependent array indexing, which is a recurring pattern.
Branchless algorithms are often a performance win on consoles and embedded systems. But they really shine when you're writing code for a wide-SIMD architecture with gather-scatter support, where divergent memory accesses are generally much less costly than divergent branches, GPUs being the most widespread example.
> (In the binary search, you'd implement the if statements with conditional moves or predicated adds, depending on what your platform offers.)
It's usually a bad idea to use arithmetical trickery in an effort to get the compiler to generate conditional moves or predicated instructions. If you want to make sure the compiler does the right thing, use macros that are conditionally expanded to the appropriate intrinsics on each compiler/platform.
Since I didn't want to get into that, I used if-statements with the above caveat. Makes sense?
Now you're just being intentionally obtuse. When I write if (x) y += z and say that it should be implemented with conditional moves or predicated adds per your platform, there really isn't much room for confusion.
> This is what GPUs do often (many architectures don't have branch instructions so all branches are always executed with predication).
Actually, all modern desktop-class GPU architectures have branch instructions. They only revert to predication (which in NVIDIA's case is implicit, i.e. not controlled by microcode-visible mask registers, though they of course also have CMOV-like instructions) when the different SIMD lanes take divergent branches. That's been the case since the G80/Tesla microarchitecture, which the GeForce 8800 from 2006 was the first product to use. Mostly-coherent dynamic branching is heavily used in modern shader code, to say nothing of GPGPU code, and is almost free. Incoherent branching is the big issue. Replacing that with incoherent memory accesses using branchless code can be a huge win, even though incoherent memory access is far from free.
May be I am obtuse though not intentionally. If you wanted to say that if's can be replaced with predicated instructions why such a convoluted example (which is a nice piece of code, by the way)?
>Actually, all modern desktop-class GPU architectures have branch instructions.
Indeed, and as you say, they are not always executed because a single SIMD core executes threads in a lock-step and it's only possible to branch when all the threads yield the same condition. Besides there are still GPUs without branches, e.g. PS3 fragment shaders.
That's not what I wanted to say; it was a side note to a piece of code. Anyway, sorry if it was confusing. It was a spur-of-the-moment comment on Hacker News, not a carefully wrought essay.
> Besides there are still GPUs without branches, e.g. PS3 fragment shaders.
The RSX is ancient at this point, a turd in a box. It was a turd even at the time when, in the eleventh hour of the PS3 project, Sony begged NVIDIA to give them a GPU after their fantasy of Cell-based rasterization had proved ludicrous. It's so bad that you have to offload a ton of work on the SPUs to get acceptable graphics performance.
On PSX every triangle you draw is with screen integer coordinates, this creates a lot of "shaking" which was okay for the regular PS1 user, but when such game is ported to the PC it looks even worse.
What I did was a global array "float numbers[65536]" that kept for every major GTE function (matrix rotation, scaling, vector multiply, etc.) a better precision number. For example if after projection of coordinate 123.434 to screen, it would write numbers[123] = 123.434 (can't remember whether I used float or fixed).
So later when triangles would draw, if I had to draw triangle from X or Y coordinate of 123 I would reuse the number 123.434
Now this is not accurate, but good enough - after all not many things end up being on screen at coordinate 123, and most likely they would've been the same calculated coordinate but for different tris... in fact it might've helped sticking together certain things...
I dunno - but the shaking effect was gone :)
There was a lot to learn from Hideo's team too - We had an artist who doubled all textures (eyes, clothes, etc.) - Hideo specially forbid the better looking eyes. He said that they specifically blurred all eyes, because they could not put eye animation in the cut scenes - this way, because they are blurred you can't really see where the eyes look at it - hence preventing the Uncanny Valley.
Another trick from Hideo's team was storing in one of the pointer bits game play related info. On PSX if one of the bits of a pointer was up, it was pointing to the same memory, but with uncached access. So they had pointers to C4 bombs, that if were planted to the ground had the bit off, and when planted to wall the bit on.
They also had a nice solving for T-junctions (they do happen a lot with geometry). Rather than drawing extra polygon to fill, they were stretching a little bit the polygons so they overlap by pixel or two.
And many other tricks, and memory failing me :)
I used to work for a company that had a horrific hardware requisition policy. If your team needed a server, it had to go through a lengthy and annoying approvals process - and even then, it took months before Infrastructure would actually provide said servers.
In other words, when a project gets handed down from above to launch in, say, 3 months, there's no way in hell you can get the servers requisitioned, approved, and installed in that time. It became standard practice for each team to slightly over-request server capacity with each project and throwing the excess hosts into a rainy day pool, immediately available and repurposeable as required.
New servers will still get requested for these projects, but since they took so long to approve, odds are they'd go right into the pool whenever they actually arrived, which sometimes took up to a year.
Of course, it was horrifyingly inefficient. Just on my team alone I think we had easily 50 boxes sitting around doing nothing (and powered on to boot) waiting to pick up the slack of a horrendously broken bureaucracy.
That word again was months. Is that common in game dev?
http://en.wikipedia.org/wiki/Electronic_Arts#Treatment_of_em...
It was basically standard in the industry for everyone to work weekends and nights for a month before release. It's calmed down perhaps and more people are hourly so at least they get paid for it, but it's still expected that you will put in significant weekend and evening hours before a milestone.
One issue is that a lot of the games don't seem to develop well under more Agile practices. Games are very large projects and it can be hard to build them incrementally, so they always end up with the standard software-management over-budget and deadline slips.
At least, this is what I'm told by my wife and friends in the industry. I still believe that their requirement of overtime is due to poor management, pressure by execs, poor specs, and inflated goals.
It's only a requirement because game developers have tended to put up with it. Especially in the earlier days, game development attracted people who were extremely passionate about making games. This meant they were willing to put up with a lot of shit. You don't have to ascribe much malice or incompetence to companies to explain crunch time under those conditions--it would be more surprising if it hadn't happened. Fortunately, things have changed for the better, although there's still a long way to go.
I'm fine with crunch time so long as (1) it is surgically applied, (2) employees know what they're getting into, and (3) employees are rewarded for going above and beyond the call of duty.
This is what's wrong with inheritance.
The possibility that a programmer could make a mistake in the implementation of a paradigm is not an argument against that paradigm. The first part of your comment is very real and very valid, but copy-pasting is a potential issue regardless of your programming language.
Inheritance views the world as fundamentally hierarchical; you can divide things up into branches of a tree. A node on the tree either has all of a set of functions or none of them. Any code you put into Reptile will never be needed by a Mammal, and will never be inappropriate for an object that needs other things in Reptile.
But of course, that's unrealistic. Where would you put swim()? Where would you put fly()? Code reuse can't always be decomposed into a tree. And in an inheritance paradigm, you get ugly workarounds in that case -- cut and pasted code, nasty multiple inheritance hacks, or util classes. All bad things.
What you want are things coupled, not by convenient proximity in current function, but by logical relationship. You want a class that has swimming-related code and nothing else, that any swimmer can use without preconceptions about whether it's a Reptile or a Mammal. You want, in short, Traits.
Yes it can make your code more complex, but you can also use it to make the game more clean.
I do not make any similar claim about the validity of object-oriented programming, but I find your argument as a reaction to my comment rather silly. People make mistakes in paradigms all the time. Read The Daily WTF is you want to observe how badly people can mangle code. I'm not suggesting that all your problems with OOP can be solved by using more OOP, just as I'm not saying that the chance hat a programmer will misunderstand for-loops in Lisp means that we should throw out functional programming.
From the review here: http://au.pc.ign.com/articles/161/161030p1.html
"You view the game mainly from the Tactical 3D window. The camera is, sadly, always situated just above the lead vehicle in the selected platoon. A free roaming camera would have been better as it would have given you more opportunities to view the action. Still, the camera is very useful and really succeeds at drawing you into the action. The camera is very, very easy to use--just bump the edges of your screen with the mouse...well, the mouse cursor, not the actual mouse itself. The camera then spins around the lead unit of the platoon. Vertical movement isn't as good. The angle of the camera doesn't allow you to look straight down on your units or get down on the ground beside them. Oh well, c'est la guerre. There's also a Strategic view that's better for getting an overall impression of the terrain elevation, but it's no good for seeing the action. For the overall impression of the battle, I preferred to use the tiny strategic window in the corner of the screen."
I'm amused to find that this has been written up as a pattern, the Memory Overdraft Pattern: http://www.charlesweir.com/papers/Mempres8AsHtml.html
I don't think he has a loop like that in our current code base. But it's possible.
It seemed in this first point that nobody really gave memory usage a lot of thought ('write the solution first, optimize second'), and thus they were way over the limit. Let's say the goal was 120MB and they were at 160. Then they started compressing and optimizing everything, and after a while they got very close; 121.5MB. So the experienced programmer removes the 2MB allocation and saves the day.
If the experienced programmer hadn't done this, I don't think (seeing how much they cared about the memory usage before) the memory usage would have been more. They might have been at 158.5MB before optimization as well, and gotten under the limit with the same optimizations as they already had to do now.
So as far as I can see, it seems the only value of doing this is the psychological value. Might still be worth something, though!
It's not as fatal to blow your memory budgets on PC as it is on a console but if you're trying to hit a certain memory footprint so that the game is playable on the minspec machines defined by your publisher then it could very well be an issue.
The doing it in secret and the cheering in this story are quite funny though (and yeah I have seen those as well).
Why mix the stories together like that?
The CRC one, #16, was cute.
I guess they didn't have time?
> I read that [..] I guess they didn't have time
It says in the original post that the reason they didn't change the hashing algorithm was that they didn't have time.Adding another variable was thrashing the cache, so instead the GC would tag the VFT pointer (making it unusable obviously) and then untag it before GC ended, fixing the object.
I wasn't sure if I should be horrified or applaud when I found out about this.
So I wrote some code that constructed a new object of the same type on the stack, then
memcpy(&realObject, &stackObject,
(char * )&stackObject.m_firstDataMember - (char * )&stackObject));
It worked. I'm not proud of it, but I am amused by it. I'm sure it wasn't guaranteed to work by any standard, but in practice worked fine for us.Your designers and artists know about the technique and grudgingly accept it BUT having multiple different programmers independently come up having "found" some memory AFTER I spent all day cutting everything from my level instead of fixing bugs...
This made me laugh, as the first PC game I worked on had system requirements of 570K of RAM and 2M of hard drive space. (That was in 1992, and he's talking about a "late-90's" title, so things had already changed a lot.)
(To be honest, I just found the system requirements by looking them up online now; I don't remember them myself, and my personal copy of the game is at work. I'll come back here and correct it if I was wrong.)
The only real problem you have is shipping working product on time. Anything that allows that to happen is a fix.
Oddly people started to complain that the application was _too fast_, after much arguments (many of these being management thinking that, because it was too fast it was not working properly) they came upon a solution.
The solution in all its glory was a loop at the end of the sort, with a sleep in it.
The best thing was, that when they were feeling lazy they would put out a release that was, for example 33% faster - by reducing the sleep time.