SAMSON: A Tyndalston Story

I worked across engine and game code on the performance, streaming, memory use, rendering, and platform readiness of SAMSON: A Tyndalston Story. Much of this work involved tracing problems across system boundaries.

My contributions ranged from building runtime systems to profiling and tuning existing ones, adapting engine fixes, and tracking down production issues. The goal was to make a large Unreal Engine world run reliably within the CPU, GPU, and memory budgets of its target platforms.

World streaming

I built and maintained a custom streaming solution for static world geometry, there were many iterations of this system, after the introduction of the world partition cell transformer stack this system morphed into a heavily modified version of FastGeo. A cook time tool gathers geometry into cell data. At runtime, the streaming system uses that data to create the corresponding render and physics state in batches. It avoids representing streamed static content as individual Actors, Components or UObjects, reducing object and garbage collection overhead as large numbers of entities move in and out of the world.

Render and physics state creation can run across worker threads, while work per frame is paced according to current streaming conditions. Creation and removal have separate budgets because their costs differ, and those budgets can be adjusted for the target platform. This spreads expensive work across frames, limits hitches, and lets the system respond to differences between hardware and frame rate modes.

As part of the cell streaming transformation pipeline, I also worked on grouping primitives for ray tracing. The transformer batches them for performance, using material frequency to limit the number of unique materials in each group. This helps reduce Lumen scene update cost while keeping capture hitches and peak frame work under control.

I also worked on the surrounding streaming pipeline: cell transformation and cooking, asset and collision behavior, and fixes for issues that appeared only in cooked builds or under particular timing conditions. This required keeping editor data, cooked content, runtime streaming, rendering, physics, and object lifetimes consistent.

Performance

I profiled CPU and GPU costs across rendering, world streaming, navigation, and gameplay systems, then tuned work to fit frame time budgets. This included engine side optimization. I added support for splitting work across frames for multiple game/engine side systems. For example, I added support for incrementally streaming navigation mesh tiles, limiting how many tiles could be registered in a frame. As an example, hitch frames in a stress test scene during heavy navigation tile streaming dropped from showing a peak navigation tile attach time of ~4ms to 0.8ms.

I consolidated various effects spawned throughout the world into a single or small number of partitioned emitters driven through Niagara Data Channels. This reduced per emitter instance and renderer overhead while keeping individual effect streams distinct where needed. As an example, a 1.5ms improvement on the GPU was measured in a stress test scene for some vehicle ribbon effects which have a very high per-emitter overhead.

Other performance work included removing unnecessary per frame work and pacing streaming tasks.

Fredrik Lönn, our Technical Director, wrote a series of articles about some of the performance work that went into the console push that I and others were part of: Part one covers Unreal Engine defaults and build configuration.

Memory optimization

I worked on memory use in both engine and game code, including render resource pools, streaming budgets, object layouts, and platform specific settings. For Xbox Series S, the tighter memory budget required some targeted trade offs across systems such as ray tracing, Lumen, virtual shadow maps, and texture streaming.

While there was indeed some targeted memory savings specifically for these platforms a lot of work resulted in savings across all platforms. Some examples are reducing the size of some heavily instanced objects e.g. ~100MiB was saved by changing an inline allocated TArray in the nanite scene proxy to be dynamic, paying the cost later, this was not a problem for us since the allocation went unused in more than 95% of all such proxies in our case since it was only used under certain conditions. A similar story for the entity representation in our Cell Streaming system which was packed as tightly as possible with the help of memory and packing visualizers.

I changed Nanite and VSM upload buffer sizing strategy so later frames could reuse buffers more effectively. This lowered peak memory use by avoiding repeated allocations of additional buffers that remained alive for several frames, with this peak VRAM usage was reduced by 400MiB during heavy movement.

Other engine side allocation changes avoided repeated GPU Scene buffer reallocations. On the game side, I made smaller per object savings where they applied at scale.

Console and handheld support

I contributed to getting the game ready for PlayStation 5 and Xbox Series X|S through platform profiles, rendering configuration, performance tuning, and memory work. The profiles account for different hardware budgets and frame rate modes, with settings for systems such as ray tracing, reflections, virtual textures, Nanite, and dynamic resolution.

For Xbox Series S, I worked on memory reductions and platform specific rendering settings. For PlayStation 5 and Xbox Series X, I tuned work budgets to prevent streaming hitches in quality modes. I also addressed platform specific issues, including an Xbox material shader fallback problem and rendering or material issues observed on consoles.

I also contributed to Steam Deck support, including platform detection, device profiles, graphics and memory settings, and adapting the settings interface to the device. This work connected build configuration, runtime detection, plugins, and user facing settings.

Console work meant checking that engine code, project configuration, shaders, and cooked content behaved together on target hardware, not just that the game built successfully.

Engine, rendering, and stability

I worked across Unreal Engine and game code to investigate rendering and stability problems, including GPU crashes, validation errors, shader and material issues, memory stomps, races, and other threading hazards. Some fixes involved project configuration. Others required tracing behavior into engine code or adapting an upstream fix to the project fork.

Examples include fixing a GPU crash caused by shader buffer alignment, resolving a data race between asynchronous loading and game thread work, and investigating Nanite GPU crashes associated with corrupted or prematurely freed data. I also worked on rendering issues involving GPU Scene, ray tracing data, hair, water, and console shader compilation.

I did some work on strand based hair rendering across engine and game code. Hair composition needed to coordinate with other rendering passes better, such as SingleLayerWater, I added different composition methods for strand hair that composited better with e.g. SingleLayerWater and reduced issues with translucency and solved some issues with hair and velocity during game pause and slow motion.

I also implemented an interactive ripple system for the ocean surface. It collects dynamic forces from registered skeletal meshes and custom providers, such as vehicles, along with direct impacts, then accumulates them into a force texture built around the view. A cheap simulation then runs at a fixed rate, with substeps if needed. It produces surface displacement and reconstructed normals that are written out for the ocean material to sample.

When an engine fix was applicable beyond the project, it was sent to Epic. Some of these fixes have since been included in later Unreal Engine versions.