Platforms
Category | Platforms |
---|
Overview
The low-level of a game runtime relies on many platform-specific functionalities.
Some of these features are implemented in the Standard C++ Library (STL).
On some platforms, some functionality is provided by the POSIX library or the GNU C Library.
Sometimes, none of these implementations fit the requirements, and it is necessary to access the platform's API directly.
Standard C++ Library (STL)
The standard C++ library was initially created before the standardization of the C++ language.
✔️
- Contains many fundamental functionality such as containers and algorithms.
- Provided on every platforms.
❌
- The implementation of the library is platform-specific. There can be variations in the implementation that can affect the user code (for example, with memory allocation).
- The library is difficult to debug with complex template types.
- Some features are not friendly to implementing real-time systems like a game engine.
Containers
The STL containers are implemented for generic-purposes, and most game engines implement custom containers instead.
Strings
The std::string
is useful to perform string operations, but in practice they are very limited at runtime.
The std::string_view
class is more useful as it provide a read-only reference to a string.
It can be when a string is used as an input parameter.
Allocator
The std::allocator
class is intended to be used as a template argument for containers.
As it is not possible to store state data, the only practical use of a custom allocator based on std::allocator
, is to override the allocation to another global allocator.
Algorithms
The algorithm
header file provides many functions that are useful for doing iterations, sorting, searches, ...
If you implement custom containers, be sure to provide the begin
and end
methods to return iterators that can be used with the STL algorithms (and the range-based for loop).
Exceptions
Exception handling is not recommended in the runtime.
However, it can still be useful in a limited scope, such as when running scripts, or in the editor build of the runtime.
A custom type of exception can be defined by deriving from std::exception
.
Windows
For debugging a new type of exception:
- Enable the checkbox next to
std::exception
in the Visual Studio "Exception Settings" dialog (Debug → Windows → Exception Settings).
- Select "C++ Exceptions", click the + button, and type the type name (including its namespace) to add the new exception.
CPU Features Detection
It's useful to determine the CPU on the target system to run the most optimized code path, and provide specific functionality (such as SIMD).
On x86, the CPUID
instruction can be used to get information about the supported features and the CPU type.
Microsoft
The __cpuid
intrinsic can be used to generate the instruction.
This intrinsic stores the supported features and CPU information in the cpuInfo
array.
void __cpuid(
int cpuInfo[4],
int function_id
);
cpuInfo
– An array of 4 integers that contains the information about the CPU. The array is filled with the values of the EAX, EBX, ECX, and EDX registers.
function_id
– Specifies the information to retrieve.
Usage
You first call cpuid
with function_id
set to 0, to get the number of the IDs.
int cpuInfo[4];
__cpuid(cpuInfo, 0);
// Number if IDs
int idCount = cpuInfo[0];
if (idCount >= 1)
{
// Detect the features
...
}
To obtain the vendor's name, you obtain three separate strings.
The vendor string is stored in EBX, EDX, and ECX in that order.
char vendorData[32] = {};
*reinterpret_cast<int*>(vendorData) = cpuInfo[1];
*reinterpret_cast<int*>(vendorData + 4) = cpuInfo[3];
*reinterpret_cast<int*>(vendorData + 8) = cpuInfo[2];
std::string vendor = vendorData;
You can then compare the vendor with predefined strings.
if (vendor == "GenuineIntel")
{
IsIntel = true;
}
else if (vendor == "AuthenticAMD")
{
IsAMD = true;
}
To detect the SIMD support, you use bit flags with the registers that contain the feature sets.
Here are the most important features to detect:
__cpuid(cpuInfo, 1);
int cpuInfo2 = cpuInfo[2];
int cpuInfo3 = cpuInfo[3];
// SSE
IsSSE2 = (cpuInfo3 & 25) != 0;
// SSE2
IsSSE2 = (cpuInfo3 & 26) != 0;
// SSE 3.x
IsSSE3 = ((cpuInfo2 & (1 << 0)) != 0);
IsSSE3x = ((cpuInfo2 & (1 << 9)) != 0);
// SSE 4.x
IsSSE41 = ((cpuInfo2 & (1 << 19)) != 0);
IsSSE42 = ((cpuInfo2 & (1 << 20)) != 0);
// AVX
IsAVX = ((cpuInfo2 & (1 << 28)) != 0);
// F16C (half-precision)
IsFP16C = ((cpuInfo2 & (1 << 29)) != 0);
// FMA3
IsFMA = ((cpuInfo2 & (1 << 12)) != 0);
If the number of IDs is higher than 7, you can check for extended features.
if (idCount >= 7)
{
__cpuid(cpuInfo, 7);
int cpuInfo1 = cpuInfo[1];
// AVX2
IsAVX2 = ((cpuInfo1 & (1 << 5)) != 0);
// AVX-512
IsAVX512 = ((cpuInfo1 & (1 << 16)) != 0);
}
Apple
The cpuid_info
function returns a pointer to a i386_cpu_info_t
structure.
i386_cpu_info_t * cpuid_info(void);
Byte Swapping
Most platform have either intrinsic functions or macro functions to perform byte swaps with integers.
They can be more efficient than custom functions.
Microsoft (CRT)
#include <stdlib.h>
unsigned short _byteswap_ushort(unsigned short val);
unsigned long _byteswap_ulong(unsigned long val);
unsigned __int64 _byteswap_uint64(unsigned __int64 val);
Microsoft (RTL)
#include <rtl.h>
NTSYSAPI USHORT RtlUshortByteSwap(USHORT Source);
NTSYSAPI ULONG RtlUlongByteSwap(ULONG Source);
NTSYSAPI ULONGLONG RtlUlonglongByteSwap(ULONGLONG Source);
Apple
#include <libkern/OSByteOrder.h>
OSSwapInt16(x);
OSSwapInt32(x);
OSSwapInt64(x);
Linux
#include <byteswap.h>
bswap_16(x);
bswap_32(x);
bswap_64(x);
Android
uint16_t __builtin_bswap16(uint16_t x);
uint32_t __builtin_bswap32(uint32_t x);
uint64_t __builtin_bswap64(uint64_t x);
Miscellaneous
Windows
Alignment
On 64-bit Windows, memory allocations are 16-byte aligned by default.
On 32 bit Windows, memory allocations are 8-byte aligned by default.
Calling Convention
The standard is __fastcall
, which passes values on the stack.
With SIMD types, the __vectorcall
calling convention can pass up to six __m128
values as arguments to a function in SIMD registers.
min/max
The Windows header define macros for min
and max
that can conflict with the standard functions in the std
namespace.
error C2589: '::' : illegal token on right side of '::'
To prevent windows from defining these macros, declare the NOMINMAX
symbol prior to including windows.h
.
#define NOMINMAX
#include <windows.h>
UWP
Reduce the WinRT code to a minimum.
For example, the references to Windows::UI::CoreWindow
and Platform::String
.
macOS
Reduce the Objective-C and Coco-dependent code to a minimum.
For example, convert NSString
to std::wstring
.
References
CPUID
- Getting Hardware Information, Microsoft Docs, https://docs.microsoft.com/en-us/windows/desktop/SysInfo/getting-hardware-information
- CPUSets for game development, Microsoft Docs, https://docs.microsoft.com/en-us/windows/uwp/xbox-apps/cpusets-games