Lessons Learned While Adding Geometry Streaming

This week I worked on adding Geometry Streaming to the engine and fixed a flickering issue that had been quietly annoying me for a while.

Both tasks ended up being more related than I initially expected.

BTW, here is version 0.10.0 of the engine with the Geometry Streaming Support.


Geometry Streaming Wasn’t the Hard Part — Integration Was

Getting Geometry Streaming working on its own wasn’t too bad. The goal was simple enough: render large scenes without having to load the entire scene into VRAM during initialization. Instead, meshes should be loaded and unloaded on demand, without stalling rendering.

The part that caused friction was not streaming itself, but getting it to behave correctly alongside two existing systems:

  • the LOD system
  • the static batching system

Each of these systems already worked well in isolation. The instability showed up once they had to coexist.

I initially overcomplicated the problem, mostly because I was treating these systems as if they were peers operating at the same level. They’re not.


The Assumption That Broke Everything

The thing that finally made it click was realizing that these systems don’t negotiate with each other — they react to upstream state.

Once I stopped thinking of them as equals and instead thought of them as layers in a pipeline, the engine immediately became more predictable.

A stable frame ended up looking like this:

  • Geometry streaming updates asset residency
  • LOD selection picks the best available representation
  • Static batching groups the selected meshes
  • The renderer submits batches to the GPU

Once I enforced this flow in the update loop, a surprising number of bugs simply disappeared.

The key insight here was that ordering matters more than clever logic.
These systems don’t need to know about each other — they just need to run in the right sequence and respond to state changes upstream.


The Kind of Bugs That Only Show Up Once Things Are “Mostly Working”

Getting the ordering right was half the battle. The other half was dealing with the kind of bugs that only appear once the architecture is almost correct.

For example:

  • I wasn’t clearing the octree properly, which caused the engine to look for entities that no longer existed.
  • One particularly frustrating bug refused to render a specific LOD whenever two or more entities were visible at the same time.

That second one took an entire day to track down.

It turned out the space uniform was getting overwritten during the unload/load phase of the streaming system. Nothing fancy — just a subtle overwrite happening at exactly the wrong time.

That kind of bug is annoying, but it’s also a signal that the system boundaries are finally being exercised in realistic ways.


The Flickering Issue That Didn’t Behave Like a Flicker

The flickering issue was a different kind of problem.

It only showed up in Edit mode, not reliably in Game mode. And it wasn’t the usual continuous flicker you expect when something is wrong. Instead, it would flicker once, stabilize, then flicker again a few seconds later — or sometimes not at all during a debug session.

That made it especially hard to reason about.

At first, I assumed it was a synchronization issue between render passes. I tried adding fences, forcing stricter ordering — none of that helped.

The clue ended up being that the flicker correlated with moments when nothing should have been changing visually.


The Real Cause: State Falling Out of Sync

Eventually, I traced the issue back to the culling system.

In some frames, the culling pass was returning zero visible entities — not because nothing was visible, but because the visibleEntityIds buffer was getting overwritten.

The fix wasn’t to add more synchronization, but to acknowledge reality: the culling system was already using triple buffering, and visibleEntityIds needed to follow the same pattern.

Once I made visibleEntityIds triple-buffered as well, the flickering disappeared completely.

The takeaway here wasn’t “use triple buffering,” but:

Any system that consumes frame-dependent data must respect the buffering strategy of the system producing it.


Final Thoughts

None of the issues this week were caused by exotic bugs or broken math. They all came from small assumptions about ordering, ownership, and state lifetime.

Once those assumptions were corrected, the engine became noticeably more stable — not just faster, but easier to reason about.

That’s usually a good sign that the architecture is moving in the right direction.

Thanks for reading.

Untold Engine Updates: LOD, Static Batching and More !!!

Hey guys,

It’s me again with a new update on the Untold Engine — this time focused on user experience and performance.

You might find this a bit odd coming from an engineer, but user experience matters a lot to me. Sometimes, I even see it as more important than performance itself. I know, that sounds backwards. But honestly, I don’t care how fast a tool is if the user experience is bad. If it’s frustrating to use, then to me, it’s not a good product.

So let’s start with the user-experience improvements I’ve been working on.

BTW, you can read more about version 0.9.0 of the engine here.

Quick USDZ Preview

I was never happy with the fact that every time I wanted to render a model with the Untold Engine, I had to create a full project first. That felt unnecessary and slow.

So I added a Quick Preview feature.

You can now preview a .usdz file directly without creating or importing it into a project. Just click the Quick Preview button, select your .usdz file, and you’re good to go.

Improved Importing Workflow

Next up: importing.

The old importing workflow was confusing at times and a bit error-prone. It was too easy to accidentally import a model into the wrong category, which is never a good experience.

Now, when you click Import, you’re explicitly asked what you want to import. This makes the process clearer and significantly reduces the chances of loading assets into the wrong place.

Scenegraph Parenting Support

At some point, I realized I really wanted to create parent–child relationships between entities directly from the editor — but the Scenegraph didn’t support that at all.

So I added it.

You can now parent entities directly in the Scenegraph by dragging one entity onto another.
To unparent an entity, just right-click it in the Scenegraph and select Unparent.

That said, I think I can make the hierarchy more visually obvious. That might be the next thing I tackle so parent–child relationships are easier to spot at a glance.

Viewport Child Selection

This one was a complete oversight on my end.

If an entity had multiple child meshes and you tried to select one of those meshes in the viewport using a right-click, the parent entity would get selected instead. That’s… not great.

This was a terrible user experience, so I made it a priority to fix.

You can now select child entities directly in the viewport using Shift + Right Mouse Click, which makes working with hierarchical scenes much more intuitive.


Now, let’s talk about performance improvements.

LOD System

The Untold Engine now supports an LOD system.

You can assign an LOD Component to an entity, provide multiple versions of a mesh, and the engine will automatically select the appropriate LOD based on distance. This is especially useful when you want to maintain a steady 60 FPS without rendering fully detailed meshes when they’re not needed.

Static Batching System

The engine now also supports Static Batching.

This is extremely useful for scenes with a large number of static objects. By batching these meshes together, the engine can significantly reduce the number of draw calls it needs to execute.

In one test scene, draw calls dropped from over 2,000 to just 34. That’s a massive improvement and makes a huge difference in frame stability.


That’s all for now.

If you want to follow the development of the engine and stay up to date with future updates, make sure to check out the project on GitHub.

Thanks for reading.

Untold Engine Updates: Multi-Platform support and Camera Behaviors

Hi guys,

Me again with some updates on the status of the Untold Engine. As always, I’ve been working diligently on the engine. Over the past two weeks, I focused on implementing multi-platform support, fixing several async issues with scene loading, and starting work on a couple of camera behaviors that I needed for benchmarking and game testing. So let me walk you through what’s new Untold Engine Studio

Multi-Platform Xcode Project Support

With the Untold Engine, you’re no longer limited to building a game for a single platform. You can now create an Xcode project with multi-platform support, either through the CLI or directly from the Untold Engine Studio.

This makes game development a lot smoother: you code once, and your game runs on macOS, iOS, iOS + AR, and Vision Pro. The only platform still missing is tvOS, but that will be added soon.

Fixed Async Issues

There was an issue when loading large scenes in async mode that could cause a runtime crash. This was tricky to debug, but I eventually tracked it down, and the crash is no longer present.

If you’ve been following the progress of the engine, you’ll know that one of my core rules is that the engine should never crash. If you do encounter any crashes while loading a scene, please let me know by opening a GitHub issue.

Camera Behaviors

Finally, I started implementing the following camera behaviors:

  • Camera path follow using waypoints
  • Camera follow with a dead zone

It’s interesting that I still haven’t implemented first-person or third-person camera behaviors, yet I decided to work on these first. The reason is simple: I’ve been benchmarking the engine, and I needed a camera that could follow a predefined path using waypoints. The goal is to measure frame rate as the camera traverses a heavy scene. Since this behavior didn’t exist, I implemented it and made it part of the engine, as I do see it being useful for game development in general.

The second behavior is one you often see in soccer (fútbol) games. The camera only follows the entity if it moves beyond the boundaries of a box; otherwise, the camera remains stationary. I implemented this because I’m currently developing a soccer video game using the new Untold Engine and Editor.

There’s a clear purpose behind developing this game, and the effort has already paid dividends. I’ve run into several bugs and API confusions that I would not have discovered otherwise. Even though I’m not in the business of making games, I do see game development as a core part of building a game engine. I mean, how else would I really test my own tools?

That’s it for now. I’m going to get back to coding. Stay tuned for upcoming features.

Thanks for reading. And don't forget to download the Untold Engine Studio

Untold Engine Updates: Faster Scene Loading, SSAO improvements, CLI

Hey guys, I’ve been working hard on improving the engine lately—both performance-wise and editor (user-experience) wise.

Faster Scene Loading with Async Asset Loading

One of the main issues I fixed in v0.7.0 was the long wait times when loading heavy scenes. I don’t have hard numbers, but it was definitely taking longer than what’s acceptable for a game engine. The main issue was that we didn’t have an async loading system in place. All models were being loaded synchronously, which caused long stalls.

With v0.7.0, scenes are now loaded through an async loading system, which makes the overall experience much better and more responsive.

Here is an example of how you can use the new async loading:

// Create entity
let entityId = createEntity()

// Load mesh asynchronously (fire and forget)
setEntityMeshAsync( entityId: entityId, filename: "large_model", withExtension: "usdz")

For more info, read the UsingAsyncLoading.md under the docs folder.

SSAO Performance Improvements (Especially on Vision Pro)

Another issue I worked on was improving SSAO performance, especially on mobile and Vision Pro. I added three quality modes for SSAO computation:

  • Fast
  • Balanced
  • High

Fast mode is the most performant but has the lowest quality, while High mode provides the best quality at the cost of performance. Fast mode did improve performance on Vision Pro, but unfortunately, not enough—the FPS is still not acceptable. Until I find a better solution, I recommend disabling SSAO entirely when using Vision Pro.

To use it in code, simply set the quality as shown below:

SSAOParams.shared.quality = .balanced //.fast or .high

Command-Line Project Creation (Xcode Integration)

The third feature I added, which I think will be really helpful, is the ability to create an Xcode game project with Untold Engine as a dependency directly from the command line. This is especially useful for users who want to bypass the editor and work directly in Xcode. That said, this doesn’t mean you can’t use the editor later—projects created this way can still be opened in Untold Engine Studio.

Here is an example on how to install and use the cli tool:

# clone the repo
git clone https://github.com/untoldengine/UntoldEngine.git
cd UntoldEngine

# Install the CLI globally:
./scripts/install-create.sh

# Verify installation:
untoldengine-create --version
untoldengine-create --help

# After installing the CLI, create a project from anywhere:

#  Create project directory
cd ~/anywhere
mkdir MyGame && cd MyGame

#  Create the project
untoldengine-create create MyGame

#  Open in Xcode
open MyGame/MyGame.xcodeproj

For more information, see: Tools/UntoldEngineCLI/README.md

Editor Workflow Improvements

I also spent time improving the user experience in the Untold Editor. The workflow is starting to take shape:

  1. User opens Untold Engine Studio
  2. Creates a new project or opens an existing one
  3. Populates the scene
  4. Writes game logic using Swift or scripting
  5. Builds & plays
  6. Repeat steps 3–5

This is still a work in progress, but I’m liking how everything is coming together.

What’s Next

For this week, I’m planning to focus on:

  1. Benchmarking + metrics harness
  2. Improving performance on Vision Pro and iOS

That’s the plan.
Feel free to checkout the the Untold Engine Studio v0.7.0.

Thanks for reading.

Untold Engine is Growing Up

I’ve been working on the Untold Engine for nearly 12 years.

I started in 2013, back when I didn’t even know what version control was. The early versions of the engine were crude, fragile, and limited—but seeing it improve, fix by fix, was deeply motivating.

There were many points along the way where I wanted to quit. Days where I was tired of touching the engine at all. At one point, I stopped entirely for about six months before coming back.

What I learned over time is that building a game engine isn’t technically hard in the way people expect. The real difficulty isn’t math, rendering, or architecture—it’s consistency. Showing up every day after the excitement wears off. Continuing when motivation is gone.

That’s the part most people underestimate.

I eventually realized something about myself: I’m not a good engineer because I write good code. I’m a good engineer because I don’t leave problems unfinished. I stay with them. I’ve done that since I was a kid—I just didn’t recognize it until much later.

That persistence is the reason Untold Engine still exists today.

After rebuilding the engine twice—from C++ to Swift—the project is finally reaching a point where it feels grown up. Today, I’m releasing Untold Engine Studio, the first bundled desktop release of the Untold Engine ecosystem.

This release exists for one reason: to remove friction.

With Untold Engine Studio, developers can:

  • Download a single DMG and start immediately
  • Skip repository cloning and local build setup
  • Create and manage projects from a standalone app
  • Work visually with scenes, assets, and entities
  • Iterate quickly using Play Mode and Scripting language

From the beginning, my focus has been user experience. I’m not trying to compete with Unreal on performance or Unity on market share. My goal is simpler—and harder: to build a tool that feels intuitive, stable, and dependable. Something that doesn’t fight you. Something that just works.

That’s not easy. It requires constant iteration and restraint. But this release is a meaningful step in that direction.

If you’re curious, I encourage you to download Untold Engine Studio and try it for yourself. Your feedback—good or bad—is genuinely valuable, and you can share it through the Untold Engine GitHub issues.

I’ve also invested a significant amount of time in documentation to make getting started easier, and I hope it helps.

As 2025 comes to a close, I’m proud of where Untold Engine is today. I have ambitious plans for 2026, and I’m excited to see how much further this project can go.

Thank you for reading.