Thursday, June 16, 2016

Video Game Development: Efficiency Part 1

What kind of computers to game developers program on?  I cannot tell you the answer to this question, and I am sure it varies widely, but there are some important things we know about programming and especially writing large programs that can give us some insight.

Computer programs take time to compile.  Compiling very small programs typically takes mere seconds.  Compiling medium sized programs can take significantly longer.  For example, the Linux kernel can take from 1 to 8 hours to compile, depending on your machine.  It may be possible to get it down to 30 minutes, but this is still quite a long time for someone sitting in an office, repeatedly compiling a game to test the most recently changed or added code.  Many games are smaller than the Linux kernel, and thus should take significantly less time to compile.  Most mainstream games, however, are much bigger than the Linux kernel, and thus should take significantly longer to compile.  There are two ways to improve the speed at which your program compiles.  Adding more memory to your machine can reduce how much disk reads and writes are necessary, and large programs can even start swapping memory to disk if you don't have enough.  The other way to improve compile speeds is to get a better processor, and if your compiler can multi-thread compilation, having more cores can make a huge difference.  The point here is, if every compile took many hours, video game companies would have a hard time being profitable.  If, however, the developers all have the fastest machines, with as many cores as possible, and with plenty of memory, then compilation time can be very dramatically reduced, which will allow the company to be far more profitable.

What kind of computer does the average person have?  The average person does not have a computer with a very fast 4, 6, or 8 core processor.  The average person does not have enormous amounts of memory.  In short, the average person has a cheap Walmart computer that is great for surfing the web (without too many tabs open at a time), editing Word documents, and watching Netflix.  The average person does not need a computer that can compile a large video game in less than an hour.

What kind of computer does the typical member of your target audience have?  This is the important question.  There are some gamers who will buy a new computer, if their current one cannot run a game they want.  Don't expect anyone to do this for a game developer that is not very well known though.  It is essential to realize that if your game does not run well on the computers owned by your target audience, your target audience will not be part of the market for your game.  In other words, you will likely find that very few people will buy it.

Platforms

There are many possible game platforms, and some of them are extremely convenient to develop games for.  Knowing your platform well is very important, if you want your target audience to be able to run your game.

Aside from politics, expensive development packs, and certifications, perhaps the easiest platform to develop for is a console.  Consoles provide a static set of requirements.  If you are developing a game for the Wii, the XBox One, or the latest Playstation, you don't have to worry about what processor your customers will have or how much memory your game will need to work with.  You can buy the console you are developing for, and then when you test your game on it, it will tell you exactly how your game will perform for every other person that has the same console.  The biggest downside is that new console versions come out fairly regularly, which often makes console games become obsolete much faster than PC games.

PCs (including Linux, Mac, and Windows PCs) are a very open platform.  You don't need to buy a dev kit from anyone to program for PC, and it is legal to sell your games without expensive certifications.  You also don't have to deal with some of the politic surrounding console game development, that tends to push smaller game developers out of the market.  Unfortunately, the ease ends there.  You will need to make a choice up front: Which OS do you want to create your game for, or do you want to try to make it cross platform?  If you have to pick a single OS, Windows is almost your only option for a really profitable game.  If you decide to go cross platform, you are either stuck using a less efficient interpreted or bytecode compiled language, or you are stuck writing OS specific code for each OS you want to support, and then dealing with the compile time stuff for making sure the right code gets compiled.  In any case, a cross platform game means that you will need to do testing on all platforms you expect it to run on.  In general, PC is the most accessible platform, but it does come at a cost.

Mobile devices are currently an enormous part of the video game market, and this market is only growing.  Mobile games also seem to have a might higher chance of going viral than PC or console games.  As a market that still has a lot of room for innovation and improvement, this might be the market with the highest potential profit.  Unfortunately, it suffers from many of the problems with PC, but it does not have most of the benefits.  Officially, mobile is a very open platform, however, it can be quite difficult to get into, because it has a steep learning curve.  In addition though, you will find yourself not only dealing with multiple operating system, an enormous ecosystem of devices with difference processors, memory, screen size, screen orientation, and even input interfaces (buttons, touchscreens...).  If you want to write games for iOS, you will have to deal with all of the politics, dev kit, and certification that comes with developing for consoles, but you don't get the dedicated gamer base that successful consoles always have.  Writing games for Android does not come with as much drama, but you still need a free dev kit, and you are largely constrained in your choice of language, unless you are really in for some pain and suffering.  One of the worst parts with mobile game development (which is generally also a problem with consoles) is that you cannot generally develop on the device.  You have to develop and compile on another computer, and then load into a virtual machine or your device for testing, which increases the development time.

There is one more reasonably common platform for games: Embedded systems.  They used to be much more popular, and technically, some handheld consoles qualify as more complex embedded systems, but I am talking about the handheld game devices that come with only one or a few games.  New games cannot be added to these, and the kind of games you find on them are generally fairly simple, but they can also be quite fun.  Programming for embedded systems can be very challenging.  For an embedded system game, the game itself takes the place of the OS, which means that you will also probably have to write some of your own device drivers and utility functions.  Learning a new embedded system is a lot of work, and if you don't use the knowledge constantly, it is easy to forget, but embedded systems can be extremely cheap.  You will find this kind of handheld game for between $5 and $20 in most cases.  They are cheap enough to be impulse purchases.  Generally more experienced gamers will save their money for something better, but younger children tend to really enjoy them, and the often pressure their parents to buy them.  They also make good stocking stuffers for Christmas, or small gifts for other holidays.  In general, embedded system games are not so good for small game companies, because LCD screens can be fairly expensive if you cannot buy in bulk, and an embedded system handheld game is very unlikely to sell well at $40 to $50 each.

Resource Management

One way to ensure that a game fails is to include so much special effects that your target audience cannot run the game on their computers.  Even if most of your target audience does have powerful machines, you will almost always lose market share if your game has very high system requirements.

Any time you add something to your game that will require more resources, it is a good idea to ask yourself if that thing is really necessary.  For some types of games, 3D graphics with realistic shadows may be necessary to appeal to your target audience.  In rare cases, these shadows may even add significantly to the game.  Most of the time though, you can reduce resource requirements by using something less realistic but also less resource hungry, and no one will notice.

Why is it that in the age of high end 3D video games, some of the most popular games use low resolution pixel graphics?  Terraria and Minecraft both use graphic styles that are way behind the times.  Terraria, admittedly, has absolutely beautiful pixel graphics, but so did Commander Keen, and almost no one is playing that anymore.  Minecraft does have 3D graphics, but not really in any style ever used historically.  Minecraft takes 3D graphics and puts low resolution pixel graphics textures on them, giving it a very unique sort of lazy grunge look, and it is one of the most popular games of all time.  These two games, and a host of others, make it clear that you don't need GPU intensive graphics to impress and be profitable.  A good theme (sandbox, for Minecraft and Terraria) or a good story (plenty of indie games found on Steam) are often far more valuable to gamers than epic graphics without either of those things.

The point here is, don't add expensive bells and whistles to your game when they are not needed.  Sometimes high end graphics are justified, but if they come at the expensive of the game actually being good, that is a problem.  Adding high end graphics to try to appeal to a wider audience may work quite well, but if the additional resource usage puts your game out of reach of most of your audience, you spent extra time and money for nothing.

Another factor in resource management is how much time and money a game costs to make.  The longer the game takes, the more it costs, and the most impressive things like graphics, the more it costs.  It is important to consider how an impressive feature will affect your schedule and your cost.  If the game comes too late, something similar but better might upstage it, and if the cost is to high, you may find yourself having to charge more than your target audience can afford, just to break even.

Optimization

Sometimes your game will end up requiring to much resources, but there is nothing you can reasonably remove to improve the situation.  You can try to take care of this problem early on, but beware of premature optimization.  In many cases, optimization can increase your market by making your game run on older, or less expensive machines.

There are several ways to optimize CPU performance.  The first is precalculation.  It is fairly common to use a calculation in a game in multiple places.  When doing movement math, you may end up with a factor that needs to be multiplied by both your x and y coordinate.  Instead of doing the math twice, in two long calculations, store the factor in a variable, so you only have to calculate it once.  So long as all of this happens within a function, the memory will be released as soon as it returns, making the performance gain free.

There are also some micro level CPU optimizations, however, these should not even be considered until the product is almost finished, and even then, they should generally only be attempted after everything else.  One valuable CPU level optimization is cache coherency.  Because memory access is very slow, the CPU will copy chunks of memory into cache, which is much faster.  A CPU only has a limited amount of cache though, and if you consistently access items in memory that are very far from each other, the CPU will have to constantly reload the cache, which will cost a lot of time for the memory access.  Arranging your data so that things like loops will stick to accessing only data that is close together will minimize these "cache misses," which can have a very dramatic effect on performance.  The catch with cache coherency optimization is that there is a heavy dependence on what CPU you are using.  While most modern CPUs use similar cache systems, failure to account for differences can make your game run really well on a system with one processor, but rather poorly on a system with a similar processor that is a different model or made by a different manufacturer.  If you are programming for a console, you don't have to worry about this, but if you are programming for PC or mobile, there are practical limitations to how much you can get out of this, if you still want your game to run well on any system that meets the system requirements.

The last CPU optimization we will discuss is assembly level optimization.  Even highly optimizing compilers are not perfect, and because the compiler does not always know your intent, sometimes it has to make assumptions that may not be true.  If you are familiar with assembly language, you can fix these optimization issues, which can cause very dramatic performance improvements.  Generally though, this should be avoided.  assembly languages are processor specific.  If you are trying to make a cross platform game, you will have to write the assembly optimization for every processor type you expect the game to run on.  In addition, there is only one case where assembly level optimization will make a noticeable difference.  This is where the thing you are optimizing is a bottleneck.  Improving the performance of something that is already blazing fast won't do much for the overall performance of your game.  If, however, you come across a performance problem caused by something that uses a significant amount of the execution time, assembly level optimization might be a good solution if nothing else works.

In many cases, often including mobile devices, memory usage is an important efficiency factor.  While most modern PCs have more than 4GB of memory, your typical cell phone or tablet will have half that or less.  It can be fairly easy to use all of that up quite quickly, if you are not paying attention.  A good example of a memory optimization problem is map tile data.  If we are making a game that has a tiled map, what data do we need to store for each tile?  Examples might include x and y coordinates, tile type, whether or not units can walk over this tile, and the sprite to display.  If the game is a strategy game, we might want to also store information like resources available on the tile and who controls it.  It turns out though, that for a large sized map, tracking all of this data can be extremely expensive.  It is not always obvious, but because a flat map is a 2 dimensional object, its memory usage increases much faster than linearly as it expands.  A better option might be to store the map in a 2D array, where the array index represents the location of the tile, associate the tile type with the sprite, so we only have to store type, and perhaps use bit packing to store things like whether a tile is traversable or not and who owns the tile, in a single 8 or 16 bit variable.  The takeaway here is, when storing state data, consider whether you really need each element of data.  Some data can be left out and be calculated from other data (though, on a slower system with a lot of memory, this might not be optimal).  Some data can be associated with other data, allowing it to just be looked up, instead of storing a copy with every object.  Some data is just not really important.  Cool features are nice, but a game that performs well is better.

Another facet to memory optimization is data types.  This is a problem I see, even from professionals.  It is trivial to consider what data type you actually need, and this does not qualify as premature optimization.  If there is no way your variable will ever exceed 255 or go below 0, use a char instead of an int!  If you end up creating many instances of an object or struct, those 3 unnecessary bytes can waste an enormous amount of memory.  In general, if you are programming in a language that allows you to choose your data types, spend a second or two thinking about what will work best, instead of just picking a common default.  Far too many programs, games and otherwise, waste tons of memory using ints for every integer value, even when a short or char would do perfectly fine!  Also, consider whether an unsigned type might be better.  There are plenty of cases where negative numbers are not needed, and the extended positive length of a smaller type would be sufficient.  It is almost always worth considering whether a smaller type would be sufficient.  That said, always consider carefully.  You might use an unsigned char to represent your tile types, because you never expect to use more than 256 different tiles on a map, but then one day, you find that this is limiting your creativity on a certain map you want to make.  Doubling the side of the type variable might not always be feasible, but it does not take much math to get a good estimate of how much memory a certain size of map will take up given the data and types the tiles contain, and you may find that for your game, the effect is not as dramatic as you expected.

The takeaway for optimization is, there are some good practices (choosing variable types wisely) that you should probably already be doing, which will avoid unnecessary performance issues.  Aside from those things though, optimization should occur only when necessary.  Premature optimization will cost a lot of time and money, and it rarely ends up targeting the biggest problems.  Don't try to solve problems that don't yet exist.

Graphics

There are some valuable things to know about graphics that can help with optimization.  The first is that graphics operations are slow I/O operations.  Communicating with the video card is generally even slower than communicating with memory.  Minimizing graphics operations can make a big difference for performance.  OpenGL and some other graphics card interfaces allow the program to store graphics data on memory that is on the actual graphics card.  This is an excellent way of improving performance.

The really important things to understand are related to human biology.  The human body, and especially the eyes, have some inherent limitation, and being aware of these limitations can help you to avoid over optimizing your game.  The first thing to know is that few humans can see any difference past about a 50Hz frame rate.  The second thing to know is that more than 60Hz is beyond the perception of almost all humans, and if you are using a 60Hz monitor, it won't make any difference anyhow, because the monitor cannot display faster than that.  Since most monitors have a refresh rate of 60Hz or 75Hz, the hardware limitations make it wasteful and pointless to try to push your frame rate past 75Hz, and biological limitations make it pointless to go past 50Hz or 60Hz.  For some perspective, subliminal messages, which are generally one frame ads put into movies, are generally not noticeable running at a mere 24Hz.

One other way of optimizing graphics is to put your rendering in a separate thread from the main game loop.  This will allow your game loop to keep running while the renderer is waiting for I/O to finish.  For the most part though, the best way to improve graphics performance is to simplify the graphics, so the GPU has less work to do.


This  is an extensive topic, so the rest will be covered in a separate post.

No comments:

Post a Comment