Quentin's_Site|

ITERIS: Preburn


Intro & Live Demo

After processingBefore processing
Enough postprocessing can fix everything :)

Hey, this time it’s only been 4 months. I’m improving. Kind of.

Writing a game engine solo feels like willingly getting into a fistfight with your own computer every night for months, then questioning your sanity in between punches.

No matter how much time you pour into it, building your own engine is brutal, frustrating, and yet absurdly rewarding. When you finally see all the moving parts syncing like a goddamn symphony of duct tape and despair.

But enough poetic bullshit. Decide for yourself if I’m full of crap or actually onto something. Play the demo and judge me there.

(Be advised, if you see thrown exceptions you may need to reload the page, not everything is ironed out.)

Developing a Game Demo in ITERO

Building Iteris: Preburn over the past few months in my spare time drove home one brutal truth: your engine architecture needs to reflect reality—not some fantasyland of "best practices" peddled by corporate devs with teams and time.

Offloading Heavy Lifting: Main Thread + Worker Pool

Itero in action
Flame/Callgraph how Itero offloads processing onto multiple cores.

Single-threaded doesn’t mean single-core. ITERO keeps the core logic and render loop monolithic—because debugging race conditions at 4 AM sucks more than just writing the damn game. RmlUi, ImGui, Lua—they all think they own the place and want the main thread. So fine. Give it to them.

But that doesn’t mean you can’t make the rest of your CPU earn its rent.

ITERO runs a worker pool behind the scenes, doing the grunt work your render loop shouldn’t touch:

  • Lock-free task queue with two priority levels: HiPri and LoPri

  • Yielding to dodge deadlocks—like when MaterialLoader's waiting for TextureLoader to stop crying

  • What gets offloaded?

    • IO, asset loading, decoding, prep

    • Garbage collection (if you don’t abuse locks like a drunk monkey)

    • BVH builds and culling—it’s parallel by nature

Hell, if you’re ambitious, you can scale the whole thing until the only sync point between main and worker is raw data handoff. It’s not elegant. It’s not cutting-edge. But it sure as hell works, and you won't end up with a renderer tied in knots because your ECS wanted to be clever.

This isn't about flexing parallelism charts or reinventing fibers. It’s about getting something playable without lighting your codebase on fire.

Survivable > Smart. Remember that.

WebGl & WebGPU: Using your GPU in Chrome is still a nightmare.

Itero in action
80% time consumed by the primitive renderer despite many optimisations left.

WebGL (via Emscripten) is a nightmare duct-taped to a migraine. And don’t get me wrong, both are miracles considering the hand the devs were dealt. But miracles don’t make for clean living.

Until recently, moving the GL context off the main thread was basically black magic and tears. And sure, WebGPU is brewing in the background, all bright-eyed and promising—but I benched it. Why? Because browser-side implementations like Dawn and wgpu-native behave like they’re from alternate dimensions. Even simple triangle test cases would explode in different ways depending on which demon you summoned.

Itero in action
Taking a closer look at the logic part of the frame.

If you’re reading this in 2–3 years, chances are WebGPU has stabilized and might actually be worth your time. And you should probably switch. It’s the future: cleaner, leaner, saner. But right now? The payoff just isn’t there. Especially when I haven’t hit the kind of GL performance walls that would make me crawl over broken specs to adopt something this fragmented.

Memory Managment: Asset Streaming Without a Safety Net

The infamous 2GB RAM limit in Emscripten is a perfect reminder why no sane person ships a large game to the web. ITERO’s loading system is async, parallel, and fully streamed,no traditional loading screens,but the second you shove a few 4K textures into RAM, you're knocking on that hard limit's door and watching the whole thing go up in smoke.

The real kicker? Once the scene’s loaded and stabilized, memory usage drops to around 300MB. But by then, you’ve already faceplanted.

Pro tip: If you're targeting Emscripten, disable memory growth. Pre-allocate your worst-case memory up front. Emscripten's growth model involves copying the entire VM every time it expands. It's like upgrading your car’s gas tank by carrying fuel jugs and pouring them one at a time ... while driving.

LUA: A VM + GC in another VM -> GC HELL

Itero in action
Frametime Histogram. Mean at 14 ms in lila. Lua GC lag spikes in red at about 20ms.

Lua’s garbage collector becomes a goddamn dumpster fire when you let it run everything: UI, procedural audio, physics, animations. ITERO leans hard on Lua, and when the whole engine is dancing on a script VM inside another VM (thanks Emscripten), you’re basically nesting performance nightmares.

To stay sane, I shoved most of the GC workload onto worker threads, timing it to run during glSwapBuffers when the GPU’s off doing its thing. It helps. But not enough. You still get frame spikes—Lua has a nasty habit of pausing everything when it’s finally ready to sweep the corpses.

Asset Streaming & Hot‑Reload

ITERO streams everything asynchronously, not because it's cool, but because maintaining two separate loading systems sounds like a special kind of hell.

Here’s how streaming works:

A system requests a resource and registers a callback for when it finishes (or fails).

The request gets deduplicated on the fly, because the last thing you want is 1,200 asteroids each queuing up the same damn material.

Worker threads handle the grunt work: decompressing PNGs, parsing GLTFs, blocking IO. Whatever slow nonsense needs doing.

Assets are reference-counted. When the count drops to one (meaning: only the system cache still has it), they get purged during a periodic cleanup sweep.

Scripting & Lua GC Gotchas

Itero in action
Animation, Physics and AI handled in one big Script. Spacial lookup threaded.

Lua is fast to integrate... and a pain in the ass to clean up after.

Everything Is Lua

Because hot reloading beats waiting for the linker to pretend it's doing something.

In ITERO, Lua scripts run damn near everything: UI animation, physics logic (at least in this demo), procedural audio, even animation curves. Sounds powerful, right? Yeah, until you realize everything Lua touches turns into memory trash.

Sure, Lua has incremental and generational garbage collection, but when that generational sweep hits mid-frame? You’ll feel it. Spikes. Stutters. The kind of jank that makes Chrome flash black frames in shame.

So, if you’re thinking about letting your scripting language handle everything: don’t. Or at least be ready to micromanage your GC like it’s a spoiled trust fund kid.

Profiling - Know Exactly Which Garbage Not to Polish

Performance without metrics is just vibes. And if you’re running on vibes, you’re probably shipping stuttery trash.

ITERO packs two profiling modes: good ol’ Tracy, and a quick-and-dirty internal profiler. I briefly flirted with the idea of using browser-based profiling through the proper APIs, but then I remembered: Emscripten takes forever to compile, and I’ve got better things to do than wait for the sun to explode.

So, I threw some stuff together in ImGui and called it a day. It works. It tells me what’s on fire. That’s all I need.

Advice For First Time Engine Developers

Start simple - You are not competing with CryEngine

Don’t over-engineer your Frankenstein monster on day one. Nail down what you actually want. Are you chasing mythical 16K/1000fps/zero-G latency benchmarks for kicks, or are you just trying to ship a damn game because Unreal makes you nauseous and Unity makes you want to commit crimes?

You don’t need three scripting languages, a Vulkan renderer, and a multithreaded ECS running on a custom scheduler written in Haskell. You need something that works. Start there.

Itero in action
The BVH only existed after my laptop gpu hit draw call limits.

When you hit real limits, engine chokes, gameplay bottlenecks, or your GPU cries for mercy,then you start sweating the big stuff. Until then: build something, ship something, shut up.

Respect Your Dependencies

Every library you pull in comes with baggage: assumptions, threading models, main-thread worship, you name it. Know them before you try to slap them into your shimmering architecture of doom. Tried bolting a multithreaded physics lib into your hipster fiber scheduler? No? Me neither. Sometimes, shipping means killing your darlings. Ask yourself: what's more important—your theoretical ECS-hierarchy built for a fantasy MMO or finally rendering a goddamn UI that doesn’t look like a 3D cube in space?

Hot-Reload is a must for small teams

If you’re flying solo or with a skeleton crew, hot reloading isn’t optional. It’s life support. ITERO pushes it to the edge. Even the editor itself is written in Lua and hot reloads on keystroke. The filesystem watcher is wired so tightly into the runtime, it feels like the engine is breathing with you. That feedback loop being near zero? It’s the only reason this shit got finished.

Build something playable ASAP

Once upon a tech forum, some developer pontificated that beginners should "focus on architecture and systems" instead of, you know, making an actual game. That guy? I strongly resent him.

ITERO started as a humble triangle in a blank window. Now it’s a full demo, with UI, audio, hot reloadable everything. I’ve got code, a semi-playable build, and a semi-broken window next to my desk. That other guy? He’s probably still perfecting his abstract ECS layer while wondering why nobody’s clapping.

Sure, ITERO isn’t elegant. It’s not fast. It won’t win awards. But guess what? It runs. It’s playable.

Nobody gives a shit about your perfectly optimized render graph or how many triangles your SIMD wizardry can cull in a frame. That’s maintenance work—infrastructure, not a sales pitch. Your engine’s job isn’t to look smart in a benchmark. It’s to make something real show up on screen and actually do something when the player hits a key.

Stop chasing perfection. Build something someone can play. That’s the win condition.

Conclusion

Don’t obsess over the ideal architecture or flawless performance. Start small, measure brutally, and scale pragmatically. Your engine doesn’t have to solve every problem. It just needs to solve the ones that matter today. Everything else comes later, through iteration, profiling, and stubbornness.

And never underestimate the power of shipping something playable. A real demo forces you to face real issues. It humbles you, teaches you, and ultimately shapes your engine more effectively than any tutorial ever could.

So stop theorizing. Stop waiting for perfection.

Go build something. Break something. Ship it.

Share this Article

Comments aren’t working right now. I’ll fix them when I get a chance.
Breaking every styling convention since 2021!