/wge

A multithreaded, high performance, fully functional game engine written in pure C, similar in speed to a Wildebeest™.

Primary LanguageCGNU General Public License v3.0GPL-3.0

WGE: Wildebeest Game Engine™

assets/logo.png

Buy Me A Coffee

GNU GPL v3.0 Code QA LoC Files

WGE CI: Build & Test WGE CI: Build & Push image that builds WGE WGE CI: Build & Push image that builds WGE

(…)

This work and all its documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

Table of Contents

Preliminary aspects

Platform support

Linux and Windows.

Tech stack

  • Language: C (gnu17 - ISO C 2017 [ ISO/IEC 9899:2018 ] with GNU extensions)
  • Compiler: GCC / Clang / MinGW-w64-GCC
  • Graphics API: Vulkan

Project structure

mindmap
  root(("Engine (shared)"))
    ("Hot-reloadable code (shared)")
      ("Game (exec)")
    ("Test (exec)")
    ("Editor (exec)")
Loading

Features

High-level product features

  • Lightweight build system (GNU Make)
  • Low-level utilities (dynamic arrays, string handling, etc.)
  • Platform layer (OS-based abstractions for windowing, input, console interaction, etc.)
  • Logger (debugging purposes)
  • File I/O capabilities
  • Application layer
  • Renderer and API abstraction layer
  • Memory management (custom allocators, etc.)
  • Entity Component System (ECS)
  • Profiling and debugging utilities
  • Scripts support via hot-realoadable modules
  • Physics system

Feature roadmap

General

FeatureStatusFeatureStatusFeatureStatus
Platform layerKeyboard supportTexture format conversion tool
Desktop GNU/Linux supportMouse supportResource hot-reloading
Desktop Windows supportGamepad supportEntity Component System (ECS)
Desktop macOS supportTouchscreen/mobile supportScenes
Mobile Android support (runtime)String library (basic)Scene format
Mobile iOS support (runtime)String library (struct based)Scene load/save procedures
Dynamic arrayMath libraryPrefabs
Free listSIMD support for math libraryRaycasting
Hash tableLinear allocatorObject picking
StackDynamic allocatorGizmos
QueuePool allocatorEditor (world)
RingSystem manager & interfaceAudio
PoolMultithreadingPhysics
Binary Search Tree (BST)Job systemNetworking
Logger (basic)Resource systemProfiling
Multithreaded loggingBinary resource loaderGame/editor logic hot-reloading
Logger channel groupingText resource loaderKeymaps/keybindings
Clock (basic)Image resource loaderConfigurable global settings
Clock (advanced)Material resource loaderConfigurable engine settings
Events (basic)Bitmap font resource loaderTimeline system
Event broadcastSystem font resource loaderSkeletal animation system
Event pollingScene resource loaderTerrain
Multithreaded eventsTexture format (binary)Skybox & skysphere

Renderer

FeatureStatusFeatureStatus
Renderer front/backend architecture2D/3D geometry generation
Vulkan API backend supportMultiple renderpass support
OpenGL API backend supportConfigurable renderpasses
Direct3D API backend supportPhong reflection/lighting model
Metal API backend supportSpecular maps
TexturesNormal maps
GeometryPhysically Based Rendering (PBR)
Materials (basic)Multithreading support for Vulkan renderer
Materials (advanced)Multithreading support for D3D12 renderer
Render targets/textures support2D/3D batch rendering

UI

FeatureStatusFeatureStatus
UI systemText (basic) control
LayeringText (rich) control
UI file formatButton control
Load/save proceduresCheckbox control
Editor (UI)Radio button control
Control focus (TAB-ing)Tab control
DockingWindow/modal control
Drag-and-Drop supportResizable multi-panel control
Base control (show/hide, position)Scrollbar control
Panel controlScroll container control
Image box controlTextbox/textarea control
Viewport controlIn-game debug console control

Miscellany

FeatureStatus
README-type documentation
White paper
Reference Manual (Info, HTML, PostScript, PDF)
API auto-generated code documentation (Man, HTML, PostScript, PDF)

Architecture

assets/engine-arch-diagram.png

Renderer

(…)

Architecture

assets/renderer-arch-diagram.png

(…)

Lifecycle

flowchart TB
  A[Initialization] --> B[Prepare frame]
  B --> C[Set state on GPU]
  C --> D[Present to screen]
  D --> E{Still running?}
  E --> |Yes| B
  E --> |No| F[Shutdown]
Loading

(…)

Phases

  • Phase 0:
    • Graphics API instantiation
    • Clear screen to solid color
  • Phase 1:
    • Static meshes
    • Textures
    • Materials
    • Phong reflection model (basic lighting)
  • Phase 2:
    • Render targets/textures
    • Terrain
    • Skybox
    • Water
  • Phase 3:
    • Post FX
    • Pipeline (configurable)
  • Phase 4:
    • Physically Based Rendering (PBR) (advanced lighting)

Graphics pipeline

flowchart TB
  A[Vertex & Index Buffers] --> |Input Assembler| B[Vertex Shader]
  B --> C[Tessellation]
  C --> D[Geometry Shader]
  D --> |Rasterization| E[Fragment Shader]
  E --> |Color Blending| F[Framebuffer]
Loading

(…)

Shader modules

flowchart TB
  A[SPIR-V]
  B[GLSL] --> A
  C[HLSL] --> A
Loading

(…)

Vertex shader

assets/renderer-vertex-shader-coordinates.png

(…)

Fragment shader

(…)

Data structures

(…)

Dynamic array (darray)

(…)

Free list

A free list is a data structure which mantains the locations and sizes of memory blocks as they are allocated and freed. Effectively, it only tracks the actual allocations themselves.

It uses a linked list internally, which typically means dynamic allocations have to occur for each node. However, in this implementation it only happens a single time and up front, to likely reduce the overhead.

assets/free-list-diagram-1.png

There are two possible ways to implement an allocation:

  1. First fit: Use the first block of memory that can hold the requested amount. It is fast to perform the allocation, and it stops searching at the first match. However, it can lead to memory fragmentation quite often.
  2. Best fit: Search all blocks of memory for the closest match in size. It is slow to perform the allocation, as it has to search through the entire list. However, it can be slightly better at preventing memory fragmentation depending on the specific setup.

After research and testing, the theoretical benefits of a best fit solution doesn’t outweight the costs, so the selected method has been the first fit (1).

Allocation

assets/free-list-diagram-2.png

  1. When a request comes in for memory allocation, the system checks the free list to see if there’s an available block that can fulfill the request. This is done by iterating over the free list until a suitable block is found.
  2. Once a suitable block is found, it is removed from the free list. This means updating the links between the other blocks in the list to exclude the newly allocated block.
  3. The block is then marked as being in use. This could involve changing a status flag or similar mechanism.
  4. Finally, the address of the allocated block, or in this case an offset, is returned to the requester.

Free

assets/free-list-diagram-3.png

  1. When a block is no longer in use and needs to be returned to the free list, it is first marked as available again. This might involve clearing a status flag or similar mechanism.
  2. The block is then added back to the free list. This involves updating the links between the other blocks in the list to include the newly freed block.
  3. Finally, the system may need to perform some form of compaction operation to ensure that the free list remains efficient. This could involve moving the freed block to a different location in memory if doing so would make the free list more compact or efficient.

Hash table

(…)

Stack

(…)

Queue

(…)

Ring

(…)

Pool

(…)

Binary Search Tree (BST)

(…)

Install a release

(…)

Build from source

(…)

Get the code

(…)

$  git clone --recurse-submodules https://github.com/iwas-coder/wge

(…)

$  git clone https://github.com/iwas-coder/wge && cd wge
$  git submodule init
$  git submodule update

Build

WGE ships with a ready-to-go Makefile, so GNU Make is needed in order to build the engine. It is as simple as doing:

$  make

By default, it targets the Linux platform (e.g. GNU/Linux). In order to build the project for Windows, it will be needed the MinGW-w64 compiler suite in order to cross-compile it. With all that setup, it can be specified by doing:

$  make TARGET=windows

Usage

(…)