Technology

Kiln, our engine

Kiln is the C++ engine under Shardbound: our own renderer on Metal and Vulkan, and the layers a game stands on. Here's what it does today, what it doesn't yet, and every number with the hardware it was measured on.

Why build an engine

Shardbound began on raylib, which gave it a window, input, audio and an OpenGL 3.3 renderer. OpenGL is deprecated on macOS, frozen at 4.1 and translated to Metal by the system, and it shuts the door on modern upscalers, which need native Metal or Vulkan resources.

So we replaced raylib with Kiln, layer by layer. There is no OpenGL backend: Metal on macOS, Vulkan on Windows and Linux (and on macOS through MoltenVK, for testing). If the chosen API can't start, the other is tried.

The game kept its code. Kiln offers the roughly 145 raylib-shaped functions Shardbound used, built on the new layers, so the move didn't need the interface and screens rewritten. New code uses Kiln's own interfaces.

The stack

Seven layers, one game-facing API

Each layer stands on a small, well-known library or on the graphics API itself.

  1. platformWindow, events, keyboard, mouse and text input, time, files, DPI, fullscreenSDL3
  2. audioSounds, aliases, streamed musicminiaudio
  3. gfxBuffers, textures, render targets, shaders, pipelines, drawsMetal · Vulkan 1.2
  4. shadersGLSL to SPIR-V and Metal Shading Language at load time, with reflection and a disk cacheglslang · SPIRV-Cross
  5. drawA 2D canvas: shapes, textures, gradients, scissor, blend modes, render-to-texture, textgfx · stb_truetype
  6. modelglTF meshes, materials and skins; generated meshescgltf
  7. imageDecode, encode, resizestb_image

Metal

MSL from SPIRV-Cross, compiled at load. Three frames in flight; uploads and readbacks are blits ordered with the draws around them.

Vulkan 1.2

Dynamic rendering, VMA and volk. Two frames in flight; textures rest in a read-only layout between uses.

One shader source

GLSL 330, as before the move. Loose uniforms are gathered into one block per stage and still set by name.

Rendering

What it draws

Everything here is in Shardbound today.

Metal

Ray-traced sun shadows

On Apple silicon (macOS 13+), the sun's shadows are traced with GLSL ray queries in a pass of their own: one ray per pixel at half size from the depth buffer, read by the next frame through reprojection.

Metal · Vulkan

Temporal upscaling, ours

In the manner of FSR 2, on every backend: each output pixel is rebuilt from the render-size samples around it, weighted by where they really fell, and blended into history at the motion vectors.

Options

MetalFX and spatial upscaling

Apple's temporal upscaler where it exists. A spatial path in the manner of FSR 1: a Lanczos upscale clamped against ringing, then contrast-adaptive sharpening.

Default

Temporal anti-aliasing

An 8-step Halton jitter, per-pixel motion for the camera, characters, animation and wind, a Catmull-Rom history clipped by variance in YCoCg so moving things don't trail.

Shared

One sky for everyone

The hour comes from the server clock: a game day is two real hours. Weather drifts from clear to storm on the same clock, with cloud shadows, rain, wet ground and puddles that mirror the sky.

Scene

Water

Waves from drifting noise, the bottom seen through them and fading with depth, screen-space reflections of the shore, glitter from the sun or moon, foam where it's shallow.

Characters

Skin and hair

Pre-integrated subsurface scattering for skin. Hair lit along its strands with Kajiya-Kay and Marschner's three highlights, alpha-hashed cards a temporal resolve turns into soft strands.

Surfaces

Surface relief

Parallax occlusion with self-shadowing on the ground and on walls; on walls the relief cuts the silhouette, so edges follow the stones instead of a flat line.

Post

Light shafts

When the sun or moon is near the view, a quarter-resolution march through the scene's depth lets open sky through, so trees, towers and hills cut the light into beams.

Effects

Particles from a fixed pool

Effects are data. Every particle lives in one pool of 12,000 reserved at start, so playing an effect never allocates, and each blend mode is one instanced draw.

Memory

Compressed, lazy textures

Mip chains made on the CPU and block-compressed on every core (BC1, BC5 for normals), cached on disk. Character parts load as placeholders and become real the first time they're drawn.

Performance

Level of detail

Props are culled to what the camera and the light can see, with two simpler levels made at load by meshoptimizer.

Measured, not promised

The numbers

An Apple M4 at 2646×1602, from Kiln's own benchmark and overlay.

~2.7 ms
ray-traced sun shadows, one ray per pixel at half size
2.8 → 1.05 GB
texture memory in a town, after block compression
102–111 FPS
our temporal upscaler at 67% render scale
0.14 ms
to update ~11,800 live particles
70 → 92 FPS
the forest, uncapped, after culling and levels of detail (town 73 → 90, lake 65 → 84)
10 ms
to load a full outfit's 24 textures from the cache

Tools

Built to be looked inside

A performance overlay shows frame time and the 1% low as frames actually reached the display, GPU time, CPU time by part, draw calls, memory and a graph of the last 240 frames. On request, the GPU's time splits by section of the frame, from the shadow map to the interface.

A benchmark mode settles in the world, averages a run and quits, so changes are compared on numbers. The client and server were leak-free at last check.

Editors

An editor framework with no game in it

Dear ImGui drawn by Kiln, viewports and cameras, transform gizmos with snapping, one undo history, preferences with key rebinding, overlays on the ground, and file writes that can't leave a half file behind.

Shardbound's world editor is built on it. So is a small demo, the starting point for the next game's.

Planned, not started

Next: GPU-driven rendering and cluster LOD

The groundwork first: compute dispatch, storage buffers and indirect draws on both backends. Then meshes cut offline into clusters in a tree of simpler levels, each part of an object drawn at its own detail, and a compute pass that culls clusters against the view and last frame's depth.

It isn't the bottleneck yet, so it waits for the denser art that will need it.