Introduction
Generally, when you write your game, very little thought will initially be given to system specifications. The usual train of thought might be "well it runs on my system, so i'll publish this as the minimum or recommended specs in a readme file, or on my website".
However, what will your game do if the player's PC fails to meet your expectations? This article will outline what sort of things you should be checking for, and when.
There is a decidedly windows and DirectX slant to this article as these are my platforms of choice. The concepts are transferable to other platforms and frameworks, however, the source code i give here is not.
Why should i even attempt to detect system specifications?
It gives a good impression
Checking for the user's system specification will ensure that all users who can run your game will run it without issue, and those who cannot run it will be presented with something useful. A game which crashes or even worse, takes down the player's system when they try to start it, with no reason or rhyme as to why will discourage them from trying again, and what's worse they might even go onto Twitter and disparage your game's name. One player spreading bad news about your game is enough to deter many other potential players.
It cuts down on support issues
If the player receives a well thought out and instructive error message in the event of a failure, they will know who to contact, and how. The error message could even advise them on what they need to do next before they call you, e.g. to purchase a better graphics card, or delete some programs to free up space, or even to change their display resolution. If they aren't told this beforehand, they will have to contact someone. That someone might be you, and this is your time they will take up which is better spend producing more games.
It helps with system stability
Checking for the correct capaibilities beforehand will cut down on the amount of crashes that a player might encounter if their machine isn't quite up to par. As outlined above, a game which crashes is a bad thing, but worse than that, a complete system crash (e.g. by switching to full screen mode with no easy way out) might risk other data on the user's machine, causing damage as well as annoyance.
How and when should i detect system specifications?
You should attempt to detect system specifications whenever your game starts. This should preferably be done before any systems are properly initialised, so that windows is still in a state where the user can properly click any error messages away and get back to what they were doing before trying to run your game.
In my own game, I have split system specifications detection into several classes, each of which is responsibile for detecting the state of one subsystem. Each is called in turn, with the simplest checks done first as some depend on others for success.
It is best to leave the code which checks for system specifications till last in your game, as you won't know what specifications your game needs until this point and are likely to go back and change it repeatedly, otherwise.
Important subsystems to check are:
- System RAM - is there enough to run the game?
- CPU speed - is the CPU fast enough? Is it multi-core?
- Hard disk space - is there enough for save game files and other data you might put there?
- Hard disk speed - will your game fall over when streaming assets from disk?
- GPU speed and video RAM - Is the graphical performance of the machine sufficient?
- Network connectivity - Is there a network connection? Is the internet reachable, e.g. to look for updates?
- Windows version - Is the version of windows up to date enough to do what you need to do?
I will cover a subset of these checks here, and recommend where you can find code for the others, as to cover every possible thing you might want to check is beyond the scope of this article as many things are system specific.
Checking system RAM size
You can check the system RAM size on windows using the GlobalMemoryStatusEx() function, which will tell you amongst other things the amount of free and total RAM, and the amount of free and total pagefile:
const ONE_GIG = 1073741824; MEMORYSTATUSEX status; ZeroMemory(&status); status.dwLength = sizeof(status); GlobalMemoryStatusEx(&status); if (status.ullTotalPhys < ONE_GIG) { MessageBox(0, "You don't have enough RAM, 1GB is needed", "Epic Fail", MB_OK); exit(0); }
Checking video RAM size
You can check the video RAM size using DXGI, and then based upon this you could load lower resolution textures to cut down on memory usage, or you could outright tell the player to get a new graphics card. I prefer the first of these two options wherever possible, as it gives a more friendly experience. Only once you have exhausted all possibilities should you give up. The code to detect video RAM is relatively simple:
#include <iostream> #include <string> #include <d3d11.h> int main() { HRESULT hr; D3D_FEATURE_LEVEL FeatureLevel; // Create DXGI factory to enumerate adapters CComPtr<IDXGIFactory1> DXGIFactory; hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&DXGIFactory); if(SUCCEEDED(hr)) { CComPtr<IDXGIAdapter1> Adapter; hr = DXGIFactory->EnumAdapters1(0, &Adapter); if(SUCCEEDED(hr)) { CComPtr<ID3D11Device> Device; CComPtr<ID3D11DeviceContext> Context; hr = D3D11CreateDevice(Adapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, &Device, &FeatureLevel, &Context); if(SUCCEEDED(hr)) { DXGI_ADAPTER_DESC adapterDesc; Adapter->GetDesc(&adapterDesc); std::wstring Description = adapterDesc.Description; INT64 VideoRam = adapterDesc.DedicatedVideoMemory; INT64 SystemRam = adapterDesc.DedicatedSystemMemory; INT64 SharedRam = adapterDesc.SharedSystemMemory; std::wcout << L"***************** GRAPHICS ADAPTER DETAILS ***********************"; std::wcout << L"Adapter Description: " << Description; std::wcout << L"Dedicated Video RAM: " << VideoRam; std::wcout << L"Dedicated System RAM: " << SystemRam; std::wcout << L"Shared System RAM: " << SharedRam; std::wcout << L"PCI ID: " << Description; std::wcout << L"Feature Level: " << FeatureLevel; } } } }
The FeatureLevel variable is also useful here, as it will show you which of the graphics card features the PC actually supports.
Detecting the windows version
Detecting the windows version may be important if you only wish to support certain types of installation. For example, you might not want the user to run your game on a server, or you might want to ensure, before your game even tries to access DirectX, that they are not running windows XP or earlier if this will have an impact on your game.
Detecting the version information of windows is very simple and should be done using the GetVersionEx win32 function:
WindowsVersion::WindowsVersion() : Checkable("windows/version") { OSVERSIONINFOEX vi; ZeroMemory(&vi, sizeof(OSVERSIONINFOEX)); vi.dwOSVersionInfoSize = sizeof(vi); GetVersionEx((LPOSVERSIONINFO)&vi); vMajor = vi.dwMajorVersion; vMinor = vi.dwMinorVersion; spMajor = vi.wServicePackMajor; spMinor = vi.wServicePackMinor; Build = vi.dwBuildNumber; Platform = vi.dwPlatformId; ProductType = vi.wProductType; } bool WindowsVersion::IsServer() { return (ProductType == VER_NT_DOMAIN_CONTROLLER || ProductType == VER_NT_SERVER); } bool WindowsVersion::IsGreaterThanXP() { return (Platform == VER_PLATFORM_WIN32_NT && vMajor >= 6); }
Please note, however, that there is an important gotcha to this function call. You cannot use it to detect if the user is running windows 8.1, only version 8.0. This is because the call will only return the newer version number if your executable embeds the correct manifest. If you want to detect this, you should use the newer Version helper API from the Windows 8.1 SDK instead. If all you want to do is detect XP, or anything older than windows 8.0, then GetVersionEx will do fine.
Detecting hard disk space
Detecting hard disk space is relatively simple, and can be done via the GetDiskFreeSpaceEx function. You should always avoid the simpler GetDiskFreeSpace function, which operates in number of clusters rather than number of free bytes, taking more work to get a simple answer rather than just returning a simple 64 bit value you can check.
Using this function is very simple:
INT64 userbytes; INT64 totalbytes; INT64 systembytes; BOOL res = GetDiskFreeSpaceEx(L".", (PULARGE_INTEGER)&userbytes, (PULARGE_INTEGER)&totalbytes, (PULARGE_INTEGER)&systembytes); std::cout << "Your disk has " << userbytes << " bytes available for use.";
Note the difference between userbytes and systembytes in the example above. The userbytes value is the amount of disk space available to the current user, as the disk might be limited by a quota. The systembytes is the total space ignoring quotas, available to all users. Therefore, you should usually check the first result field.
Detecting CPU speed
There are many ways to detect the CPU speed. Some of the more common ones are:
- Using WMI to read the Win32_Processor information - my personally preferred method
- Using the machine code CPUID instruction via inline assembly - less portable, but accurate
- Using a busy loop to calculate CPU - mostly deprecated as this is extremely hard to get right on multi-tasking operating systems, and not recommended outside of kernel level code
On most modern systems, you are more likely to run into problems with lack of RAM, or lack of a good graphics card before you encounter problems with CPU performance. Your mileage may vary but in my own experience, less time needs to be spent on detecting the CPU speed and more time on other factors as CPU speed is greatly affected by what else is running at the time and how much swapping the system might need to do.
Conclusion
The advice above should help you detect your player's specifications effectively. This is of course just the tip of a very big iceberg, and once you start detecting various metrics from a player's system, you will keep thinking of other things you really should check. Don't get carried away though, as it is easy to get distracted trying to detect potential edge cases, rather than just carrying on with the game.
Article Update Log
17 Mar 2014: Initial release