beyond-all-reason / beyond-all-reason/RecoilEngine

Multithreaded Game Loading

Open
#1,210 9 comments 0 reactions 0 assignees View on GitHub
Looking for a new maintainer refactor
Dominant language
C++
Stars
683
Forks
293
Avg merge
3d 2h
Merged PRs (30d)
40

Description

## Goals
Speed up load times.

## Requirements
No changes to the engine Lua API. Engine should be a drop in replacement for
both BAR and Zero-K.

## Major Concerns

- Lua is not multi-thread safe.
- Open GL calls must occur on the main thread. Conservatively, all of
`CGame::LoadLua` must be evaluated on the main thread. When loading assets
outside of lua, we must ensure that uploading textures and shaders occur on
the main thread.

## Approach

Unlike `LoadingMT`, which offloads the entirety of `CGame::Load` onto a separate
`CGameLoadThread`, we approach this processes incrementally.

1. At the beginning, we assume that all of `CGame::Load` must be executed on the
main thread. I.e. everything is on the 'critial loading path'.
1. Identify a portion of the loading routine which is safe to be offloaded onto
an auxiliary thread, call this the `thread-safe loading subroutine`.
1. If neccessary, subdivide `CGame::Load` into additional
[stages](#loading-stages) to facilitate parallelism.
1. Move the thread-safe loading subroutine onto an auxiliary thread. Go back to
step 2.

## Loading Stages

We subdivide `CGame::Load` into multiple loading stages. By grouping loading
subroutines in `CGame::Load` into stages, each subroutine only needs to consider
other subroutines in the same stage when evaluating thread safety. The end of
each stage forms a barrier for all ongoing subroutines to complete before
starting the next stage.

Below is an initial set of stages proposed by analyzing the flow of CGame::Load

![graphviz](https://github.com/beyond-all-reason/spring/assets/2730943/a98e0aaf-21df-4110-ac5e-988e8aa6e328)

[load_data_flow_viz.txt](https://github.com/beyond-all-reason/spring/files/13942731/load_data_flow_viz.txt)

## CGameLoadScreen::SetLoadMessage

`CGameLoadScreen::SetLoadMessage` triggers a`CGameLoadScreen::Update` and
`CGameLoadScreen::Draw` when called unless LoadingMT is set. We need to
be able to call `CGameLoadScreen::SetLoadMessage` without triggering `Update`
and `Draw` calls so that off-main-thread loading subroutines can update the
loading message without executing on the main thread. However, we don't want to
set LoadingMT since that also triggers a lot of other unwanted behavior. We will
need to extend `CGameLoadScreen::SetLoadMessage` with a new argument that skips
the `Update` and `Draw` calls.

### First CGameLoadScreen::SetLoadMessage call

The first SetLoadMessage call also triggers two lengthy load processes:

- Blurring fonts, as identified in
https://github.com/beyond-all-reason/spring/pull/1138.
- In BAR, the loadscreens are in 4k, which takes ~300ms to load.

In profiling, these two assets alone costs about ~600ms out of ~14s load time
(5%). We may want to offload these two loading processes.
Once the font and loading screen assets are available, `CGameLoadScreen::Draw`
will render the loadscreen and load text as normal. Deffered font rendering may
be particularly complex as CglFont is directly available from both the LuaMenu
and LuaIntro environments.

## Limit

If we apply this optimization technique repeatly without any changes to the Lua
API, we would run into the following limitations:
- All OpenGL calls must be performed on the main thread. For loading, the time
would mostly be consumed by uploading textures, compiling shaders, and
uploading shaders.
- Lua loading code, specifically loading and postprocessing defs as well as
CGame::LoadLua must be on the same thread. Note that this process does not
have to occur on the main thread, but cannot occure concurrently.

Assuming the lua loading process takes the most time, the theoretical fastest
load speed for BAR with no Lua API changes is ~8s (1s load defs, 1s post process
defs, 6s load lua), from ~13s currently.

Contributor guide

Open the contributing guide

Research direction

Start by reading CGame::Load, CGame::LoadLua, and CGameLoadScreen::SetLoadMessage, then review the proposed load_data_flow_viz.txt stages and the existing LoadingMT behavior. Done means loading work is safely staged across threads without changing the engine Lua API, while Lua and OpenGL-related work remain subject to the stated thread constraints.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, lua
Domain
game-dev, performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.