Cross-Platform
Category | Platforms |
---|
Overview
A game engine needs to be designed as a cross-platform application.
Even when a game is only targeting a single platform, a platform-abstraction from the game-specific code can help isolate platform-specific issues.
Platforms
There are many platforms that a game engine should be supporting in the current ecosystem.
- Microsoft Windows (Desktop and UWP)
- Apple macOS
- Linux (Ubuntu is the most popular distribution)
- Apple iOS (includes iPhone, iPad, tvOS)
- Android
- Microsoft Xbox One and Xbox One X
- Sony PlayStation 4
- Nintendo Switch
- Nintendo 3DS
Operating System
On Desktop platforms, the operating system shares the hardware resources with multiple applications running at the same time.
On Mobile platforms, the operating system can suspend and resume applications at any time depending on the available resources.
On Console platforms, the operating system is provided as a thin layer that provides additional features for the user such as downloading or sharing content.
Platform Abstraction
A game should run on multiple platforms, and therefore requires platform abstraction.
There are many goals and benefits of platform abstraction:
- Code reuse for all target platforms.
- Separate platform-independent code from platform-dependent code.
- Parallelize the effort of implementing common features on target platforms.
- Distinguish issues as being platform-dependent or platform-independent.
A platform abstraction architecture consists of three layers.
Platform layer
Lowest layer in the architecture.
Platform-dependent.
Interacts directly with the target system (operating system, hardware, drivers).
Abstraction layer
Middle layer in the architecture.
Never makes system calls.
Interfaces directly with the platform layer.
Application layer
Top layer in the architecture.
Interfaces directly with the abstraction layer.
Abstraction Architecture
The abstraction layer provides interfaces without any platform-dependent types.
There are two strategies to implement the abstraction layer.
- Provide different implementations in different source files, selected by the build system or using preprocessor directives.
- Implement the interface as a pure abstract class and provide different implementations using a factory.
The first strategy works well with low-level functionality like memory, timing, threading, concurrency, ...
The second strategy works well with high-level systems such as graphics, input, audio, ...
For example, with all the supported graphic device implementations available at run-time, it's possible to provide, a command-line or setting in the application to switch between different graphic devices.
Compile-time Implementation
For example, the MemorySystem
is implemented in the abstraction layer and declares an Allocate
method.
// MemorySystem.h
class MemorySystem
{
void* Allocate(size_t size);
...
};
The method is implemented is distinct .cpp files for each platform, and the method is resolved at linking.
// MemoryWindows.cpp
void* MemorySystem::Allocate(size_t size)
{
return VirtualAlloc(...);
}
// MemorySystemMacOS.cpp
void* MemorySystem::Allocate(size_t size)
{
return mmap(...);
}
Compile-time Factory
// GraphicSystem.h
GraphicDevice* CreateGraphicDevice();
// GraphicSystem.cpp
GraphicDevice* GraphicSystem::CreateGraphicDevice()
{
#if defined(DIRECT3D11)
return new Direct3D11Device();
#elif defined(DIRECT3D12)
return new Direct3D12Device();
#elif defined(OPENGL)
return new OpenGLDevice();
#else
assert("Unsupported platform!")
return nullptr;
#endif
}
Run-time Factory
// GraphicDevice.h
class GraphicDevice
{
GraphicDevice() = delete;
virtual HardwareBuffer* CreateTexture() = 0;
}
// GraphicSystem.h
GraphicDevice* CreateGraphicDevice();
// Direct3D12Device.cpp
class Direct3D12Device : GraphicDevice
{
Direct3D12Device() {}
virtual HardwareBuffer* CreateTexture() override
{
return new Direct3D12HardwareBuffer();
}
}
// OpenGLDevice.cpp
class OpenGLDevice : GraphicDevice
{
OpenGLDevice() {}
virtual HardwareBuffer* CreateTexture() override
{
return new OpenGLDeviceHardwareBuffer();
}
}
// GraphicSystem.cpp
GraphicDevice* GraphicSystem::CreateGraphicDevice()
{
#if defined(WINDOWS) || defined(XBOX)
if (_deviceType == DIRECT3D11)
return new Direct3D11Device();
if (_deviceType == DIRECT3D12)
return new Direct3D12Device();
#elif defined(WINDOWS) || defined(MACOS)
if (_deviceType == OPENGL)
return new OpenGLDevice();
#endif
assert("Unsupported platform!")
return nullptr;
}