Silver Engine

What is Silver Engine?

Silver Engine is a 3D game engine built from the ground up using C++ and Vulkan.
It's my first game engine and includes a fully functional editor, allowing users to develop games and write gameplay code directly in C++.

Originally created for fun and learning, it served as a sandbox where I could experiment with rendering techniques and various systems.
Over time, it evolved into the core codebase for my personal projects, including Glimmer Chain, which was successfully shipped to the public.

The codebase includes various core systems that replaces the standard C/C++ libraries.

About Silver Engine

Development Details

Memory Management

When we think about memory management in C/C++, the usual suspects are malloc/free and smart pointers. But that's just scratching the surface. Modern hardware and operating systems offer virtual memory, which does far more than just isolate processes for safety. It also allows for continuous virtual memory ranges, letting the OS map memory pages across RAM (and even the hard drive) transparently.

The problem with malloc, free, and the abstractions built on top of them is that they don't really align with how programs actually use memory. While they're flexible for individual allocations, if you take a closer look at your allocation patterns, you'll notice that most objects can be grouped together and share the same lifetime.

That's where arena allocators come in. An arena (or linear allocator) lets you allocate a group of objects that share a lifetime, and then free them all at once with a single call when they're no longer needed.

Benefits of Arena Allocators:

Much of my understanding of arena allocators comes from Ryan Fleury, who wrote an excellent post on the topic: Untangling Lifetimes: The Arena Allocator

How Silver Engine Uses Arenas:

Silver Engine provides these arena allocators by default:

Replacing Standard Libraries

The C++ standard library has become increasingly complex with each new version of the language. It's built on top of a stack of features like RAII, operator overloading, templates, manual memory management (new/delete), and smart pointers.

At some point in development, it becomes hard to understand what's really happening under the hood. You might end up with subtle memory leaks, redundant allocations, or even serious safety issues without clear visibility.

Some languages like Rust attempt to solve these issues by introducing strict lifetime tracking, but this often comes at the cost of added complexity and mental overhead for the programmer.

Instead of layering more abstractions, I prefer addressing the problem at its root. That's why I built my own standard library functions, based on Arena allocators, avoiding unnecessary language features and keeping things simple, explicit, and predictable.

Strings

Traditional C strings use null-terminated character arrays, which come with several drawbacks. To determine the length of a string, you need to iterate through the entire buffer until a null character is found. Taking slices is awkward, and if the null terminator is missing, due to a bug or corrupted data, it can lead to undefined behavior or even fatal memory access errors.

C++ strings, on the other hand, manage memory internally using RAII, which introduces hidden allocations and potential performance issues. They also make it easy to introduce subtle bugs, and taking a slice requires using an entirely different data structure.

In Silver Engine, strings are simplified. They are not null-terminated, and they don't manage memory themselves. Instead, when needed, string functions can allocate memory through an arena allocator. A string is simply a slice: a pointer and a size. This makes strings easy to work with, efficient, and flexible.

Collections

In Silver Engine, collections are also custom-built. It includes hash tables, dynamic arrays, string builders, static arrays, and even a space partitioning table. Most of these structures are arena-based, allocating memory in blocks as needed for performance and simplicity.

This is also the only place where I use C++ templates, strictly for convenience.

Math

Games require a solid foundation in linear algebra, including vectors, matrices, and quaternions. These are essential for handling transformations, physics simulations, and camera logic. Additionally, many physics systems rely on geometric intersection tests, which are also part of this module and can be reused across different systems.

Random Number Generation

Pseudorandom number generators have a property that many programmers don't usually consider: they are deterministic.
This means that, given the same seed, they will always produce the same sequence of “random” numbers.

A good example of this in action can be found in Glimmer Chain, where it's used for particle effects, and in Voxel Game, where it's used for terrain generation.

My random functions are based on the SplitMix algorithm. They use a combination of a fixed seed and a seed mask, which is updated on every call to a random function. Both values are stored in a thread-local context, allowing deterministic behavior while supporting multithreaded use.

u64 u64_random() {
    u64 seed = context.seed ^ context.seed_mask;
    u64 result = random_splitmix64(&seed);
    context.seed_mask = seed;
    return result;
}

u32 u32_random() {
    return (u32)u64_random();
}
f32 f32_random() {
    u32 v = u32_random() & 0xFFFFFF;
    return (f32)v / (f32)0xFFFFFF;
}
f32 f32_random_max(f32 max) {
    return f32_random() * max;
}
f32 f32_random_range(f32 min, f32 max) {
    return min + f32_random() * (max - min);
}

Timing Functions

When using the C++ standard library for timing, you're often faced with a maze of classes, templates, and opaque functions.
Why should something as simple as measuring time feel so unnecessarily complex?

Silver Engine provides a clear and minimal set of functions and structs that have been more than enough for every use case I've encountered so far:

struct Date {
    u32 year;
    u32 month;
    u32 day;
    u32 hour;
    u32 minute;
    u32 second;
    u32 millisecond;
};

struct {
    u64 start_clock_counter;
    u64 clock_frequency;
    
    u64 frame_count;
    f64 start_frame;
    f32 delta_time;
    u32 FPS;
    f32 smooth_FPS;
    u32 smooth_FPS_counter;
} timer;

u64 os_get_clock_counter(); // QueryPerformanceCounter on Windows
Date os_get_date();

f64 timer_now(); // Seconds since start of the program
u64 timer_seed(); // Used for non-deterministics behaviours on random functions

// Notice that timer_now() = (os_get_clock_counter() - timer.start_clock_counter) / timer.clock_frequency

u64 timer_calculate_cpu_frequency();
void timer_limit_fps(u32 frame_rate, f64 start_of_frame); // Sleeps the thread to match the given frame rate

Rendering Modules

The engine features a custom GraphicsAPI that wraps the internals of OpenGL, DirectX11, and Vulkan, enabling multiplatform support.
It also includes some rendering modules:

2D Immediate Mode Renderer

Uses a technique called batch rendering to efficiently draw sprites to the screen.
It supports both simple sprites and more advanced ones.

The advanced sprites have a variety of visual effects, such as rounded corners, borders, and color fades.
These are typically used in UI elements.

Below is a screenshot of a unfinished application built with Silver Engine:

Text

Text is one of the most underrated aspects of graphical applications.
We all expect clean text rendering across different resolutions, sizes, fonts, and even languages.
But building a robust text renderer that handles all of this dynamically is a real challenge.

Here's an overview of the steps involved in displaying text in Silver Engine:

That's the basic workflow behind dynamically rendered, high-quality text.

Below is an example SDF texture extracted from Glimmer Chain:

Shader Compilation

All shaders must be compiled before execution, and this process is handled manually through a shader compilation module in Silver Engine.

This module processes every .shader file and generates binaries for each supported Graphics API, using dxcompiler under the hood.
A .shader file is a custom format designed for Silver Engine. It contains multiple shader stages, such as vertex, pixel, and compute. All within the same file.

Deprecated Features

Over time, I implemented a wide range of features for the engine. However, many of them were later removed as the engine evolved into more of a modular framework, allowing me to reuse components across different projects.
Below is a list of features that were implemented at various stages: