📱

Cross-Platform

CategoryPlatforms

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.

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:

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.

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;
}