<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://eliasfarhan.ch/feed.xml" rel="self" type="application/atom+xml" /><link href="https://eliasfarhan.ch/" rel="alternate" type="text/html" /><updated>2026-10-10T16:48:19+00:00</updated><id>https://eliasfarhan.ch/feed.xml</id><title type="html">Elias Farhan</title><subtitle>Elias Farhan is a indie game developer, programmer, teacher and speaker. He has fun with the gameboy, computer graphics, game engine programming, C++, prototyping with Unity and online multiplayer game programming.</subtitle><entry><title type="html">CppCon 2026</title><link href="https://eliasfarhan.ch/jekyll/update/2026/09/20/cppcon26.html" rel="alternate" type="text/html" title="CppCon 2026" /><published>2026-09-20T00:00:00+00:00</published><updated>2026-09-20T00:00:00+00:00</updated><id>https://eliasfarhan.ch/jekyll/update/2026/09/20/cppcon26</id><content type="html" xml:base="https://eliasfarhan.ch/jekyll/update/2026/09/20/cppcon26.html"><![CDATA[<p><img src="/images/2026/cppcon/20260918_131140.jpg" alt="Intro cppcon photo" /></p>

<p>After two years, I went back to CppCon this week to give a talk about game engines and my little summer experiment with Soup Raiders (blog series <a href="/gamedev/cpp/2026/08/05/srnative-00.html">here</a>). But CppCon is more than just talks!</p>

<!--more-->

<p>I landed in Denver on Saturday evening after a stopover at Washington Dulles. I woke up at 4am on Sunday and had some time to work on my slides before heading out for the field trip to Red Rocks:</p>

<p><img src="/images/2026/cppcon/20260913_124336.jpg" alt="red rocks" /></p>

<p>It was really hot, but everybody was still smiling. This field trip is a nice opportunity to meet new people in a small group. I tend to feel uncomfortable when I am suddenly in the presence of a lot of people that I don’t know at all, but we speak the same (programming) language. We then went to see some dinosaur footprints nearby:</p>

<p><img src="/images/2026/cppcon/20260913_144233.jpg" alt="dino1" /></p>

<p><img src="/images/2026/cppcon/20260913_144726.jpg" alt="dino2" /></p>

<p>We had to hike up a hill to see them, and with the heat, I used my Korean Daiso umbrella to keep the sun off. Conversation topics ranged from the upcoming talks to AI, programming, skiing, and our current jobs. We then went back to the hotel to join the registration reception:</p>

<p><img src="/images/2026/cppcon/20260913_200739.jpg" alt="registration" /></p>

<p>After getting all my swag, I did not stay too long, because the jet lag was hitting hard. Anyway, time for the talks.</p>

<p>On Monday, I went to:</p>
<ul>
  <li><em>Are you smarter than a branch predictor</em> by Michelle D’Souza: I will be teaching my students about branch prediction in the coming weeks, so getting a refresher and learning new things was nice.</li>
  <li><em>The journey to “/W4 /WX”: How hard could it be?</em> by Keith Stockdale: A very interesting talk with lots of short code reviews to showcase all the little bugs and instances of undefined behavior that can creep into a codebase without treating warnings as errors.</li>
</ul>

<p><img src="/images/2026/cppcon/20260914_150719.jpg" alt="" /></p>

<ul>
  <li><em>Zero Cost Scripting Languages for Game Engines With C++26 Static Reflection</em> by Koen Samyn: I did not know much about AngelScript, but compiling its bytecode by combining <code class="language-plaintext highlighter-rouge">#embed</code> and static reflection was fascinating and gave me ideas.</li>
</ul>

<p>On Tuesday, with my talk in the afternoon, I missed the Language Designers Panel in the morning. I was still adjusting the slides before the talk. Finally, it was time. This was the room I gave the talk in:</p>

<p><img src="/images/2026/cppcon/20260915_133808.jpg" alt="" /></p>

<p>My slides included a few interactive web demos. Fortunately, there was a screen below the stage showing what the attendees were seeing, which let me interact with the demos.</p>

<p>After my talk, I went to Jody Haggins’ talk: <em>C++ in an AI World</em>, but I was still feeling the adrenaline from my own talk, so I will need to rewatch it.</p>

<p>Then I needed to relax and missed a few talks that I really wanted to see (especially <em>Ensuring Code Quality in the Age of AI : More Code, Less Engineering!</em> by Peter Muldoon).</p>

<p>On Wednesday, I went to these talks:</p>
<ul>
  <li><em>Awaiters and Awaitables</em> by Mateusz Pusz: Since the networking proposal involves coroutines and my colleague had been experimenting with it, I took the opportunity to learn more about coroutines in Mateusz’s talk. He managed to make a seemingly inaccessible topic accessible.</li>
  <li><em>How C++26 changes the way we write code</em> by Timur Doumler: The big four: Reflection, Contracts, Execution and SIMD. As a teacher, I will need to play a bit more with <code class="language-plaintext highlighter-rouge">std::execution</code> before being convinced. The recording is <a href="https://www.youtube.com/watch?v=N_lM_Uq1UAc">here</a>.</li>
  <li><em>Rule of 0,1,2,5,6,7,8,9,10?</em> by Jason Turner: There is always something new to learn from Jason’s talks and this one was no exception. A nice refresher on constructors, destructors, assignment, and some operators.</li>
  <li><em>Graphics Programming with SDL 3 Part 2</em> by Mike Shah: I discovered that we can play with shaders in SDL3’s 2D renderer. While my class usually covers graphics programming at a lower level, this could make a nice introduction or first exercise in computer graphics. And Mike Shah is a machine, giving three talks at CppCon this year.</li>
  <li><em>Lightning Talks</em>: One hour of five-minute talks on various subjects related to C++. Two years ago, I was too tired to follow them, but this year, I was happy to be there. It’s also an opportunity for new speakers to try out material or just to gain speaking experience. I considered giving a lightning talk this year, and I regret not submitting one.</li>
</ul>

<p>On Thursday, I attended these talks and events:</p>
<ul>
  <li><em>The Address is Not The Place: Object Residency in C++26</em> by Laurie Kirk: Her talk was about breaking this wall that we have with memory. While I could not follow everything, I found it very interesting, especially from a teacher’s point of view who is going to explain how the memory work very soon. The recording is already online for me to rewatch <a href="https://www.youtube.com/watch?v=ScwvInRZdKE">here</a>.</li>
  <li><em>Meet Guy Davidson</em>: It was an interview with Sherry Sontag where we looked back at Guy’s background and his motivation for serving the community as a convenor. There was an old photo of him with long hair and no beard that I will not put here.</li>
  <li><em>Meet the Presenters Banquet</em>: After meeting Guy, I got my speaker portrait taken by <a href="https://www.jonathanphillipsphotography.com/">Jonathan Phillips</a>. Here is what I will look like in my CppCon 2026 thumbnail:</li>
</ul>

<p><img src="/images/2026/cppcon/Farhan,%20Elias%2001.jpg" alt="" /></p>

<p>Then I got to meet Guy Davidson again:</p>

<p><img src="/images/2026/cppcon/20260917_183834.jpg" alt="" /></p>

<p>And we enjoyed a good dinner in good company, talking about C++ and our different jobs around the table:</p>

<div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; align-items: start; margin-bottom: 1em;">
  <img src="/images/2026/cppcon/20260917_185141.jpg" alt="" style="width: 100%; height: auto;" />
  <img src="/images/2026/cppcon/20260917_185138.jpg" alt="" style="width: 100%; height: auto;" />
  <img src="/images/2026/cppcon/20260917_190508.jpg" alt="" style="width: 100%; height: auto;" />
</div>

<ul>
  <li><em>Lightning Talks</em>: Part 2, and I managed to attend again (with some help from caffeine). There is a special energy during the lightning talks. The session lasted until 10:30pm, so with my jet lag, I was exhausted by the end. I’m looking forward to watching them again when they are available in the CppCon archive.</li>
</ul>

<p>On Friday, I went to these talks:</p>
<ul>
  <li><em>Safety in Numbers</em> by Jason Turner: Another talk by Jason Turner, where we reviewed operations on integers smaller than 32 bits, overflow, and all the nice features added to modern C++ to deal with those cases.</li>
  <li><em>Refactoring C++: Techniques, Smells, and Discussion</em> by Dave Steffen: I really enjoyed the <em>Back to Basics: Testing</em> talk that Dave gave 2 years ago. This was an Open Content session, so it was intentionally incomplete. The discussion of code smells with the audience was interesting, and I’m looking forward to the final talk/book.</li>
  <li><em>Code You Didn’t Write: Understanding Large and Unfamiliar Codebases</em> by Mike Shah and Chris Croft-White: I approached this talk from the perspective of my students and of a newcomer to an unfamiliar codebase. Now with a lot of projects being vibe-coded, it’s even more daunting to get started on a massive new project. There were some good examples of how to use LLMs effectively in certain situations.</li>
  <li><em>In Pursuit of a 6,000 FPS Game Boy Emulator</em> by Tom Tesch:</li>
</ul>

<p><img src="/images/2026/cppcon/20260918_143238.jpg" alt="" /></p>

<p>It was the last talk I could attend. This talk was the end of the Tetris Game Boy trilogy. As a fellow teacher, I really relate to the freedom to work on projects we want to make if we can use them in our courses, and to this slide in particular:</p>

<p><img src="/images/2026/cppcon/20260918_150544.jpg" alt="" /></p>

<p>The Game Boy was my first console and I had always wanted to go back to it when working in gamedev. I did so a few years ago with <a href="https://teamkwakwa.itch.io/soup-raiders-jailbreak">Soup Raiders: Jailbreak</a>. But Tom ventured into emulator territory. Quoting him: “To really know something, make an emulator out of it.” A very entertaining talk!</p>

<p>Then it was time to go to the airport to catch my flight. I headed home with my head full of ideas, ready for the future, and keeping in mind that C++ is an expert language.</p>

<p><img src="/images/2026/cppcon/20260915_153735.jpg" alt="cpp hard" /></p>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[After two years, I went back to CppCon this week to give a talk about game engines and my little summer experiment with Soup Raiders (blog series here). But CppCon is more than just talks!]]></summary></entry><entry><title type="html">Soup Raiders Goes Native: Porting the demo from Unity</title><link href="https://eliasfarhan.ch/gamedev/cpp/2026/08/20/srnative-02-porting-unity.html" rel="alternate" type="text/html" title="Soup Raiders Goes Native: Porting the demo from Unity" /><published>2026-08-20T00:00:00+00:00</published><updated>2026-08-20T00:00:00+00:00</updated><id>https://eliasfarhan.ch/gamedev/cpp/2026/08/20/srnative-02-porting-unity</id><content type="html" xml:base="https://eliasfarhan.ch/gamedev/cpp/2026/08/20/srnative-02-porting-unity.html"><![CDATA[<p><img src="/images/2026/sr/sr_docks_full.png" alt="The docks of the Red Island in Soup Raiders" /></p>

<p>A port is not simply “the same game in another engine” or “just an emulator”. It is a long series of decisions about what to do with the original content (art, code and audio) to create a new version with its own set of constraints. In this blog post, I will look at three different approaches to porting, then show what I exported from my old Unity demo and how. The next blog post will cover how I imported those assets into my own game engine.</p>

<!--more-->

<h2 id="porting-games">Porting games</h2>

<p>You might have heard terms such as <em>remaster</em>, <em>remake</em> or even <em>recompilation</em>. Their meanings can be confusing, and there are nearly as many approaches to porting as there are game ports. Here are three concrete examples and how I personally classify them.</p>

<h3 id="demons-souls-ps5-2020">Demon’s Souls PS5 (2020)</h3>

<p><img src="/images/2026/demonsouls.jpg" alt="Demon's Souls on PS5" /></p>

<p>For the release of the PS5, Bluepoint Games, with Japan Studio, remade FromSoftware’s PS3 game <em>Demon’s Souls</em>. There is a very good <a href="https://www.youtube.com/watch?v=hCBJ2fiiUXk">/noclip documentary</a><sup id="fnref:demons-souls-noclip" role="doc-noteref"><a href="#fn:demons-souls-noclip" class="footnote" rel="footnote">1</a></sup> on the subject, featuring interviews with the team behind the game. In a <a href="https://www.youtube.com/watch?v=9O--CnN056E">Digital Foundry interview</a><sup id="fnref:demons-souls-digital-foundry" role="doc-noteref"><a href="#fn:demons-souls-digital-foundry" class="footnote" rel="footnote">2</a></sup>, the developers at Bluepoint described the port as using two engines. Based on their explanation, I understand the architecture roughly as follows:</p>

<p><img src="/images/2026/sr/demons_souls_bluepoint.svg" alt="Diagram showing the original Demon's Souls game systems running alongside Bluepoint technology" /></p>

<p>This allows the PS5 game to look radically different while its combat feels extraordinarily close to that of the PS3 game. Visually, an enormous amount was rebuilt. On the technical side, Marco Thrush, Bluepoint’s president and an engineer, described starting by compiling the codebase one error at a time, then running the game and fixing one crash at a time. The team did not simply recompile the old code: they also introduced modernised character creation, a photo mode, the mirrored Fractured World, quality-of-life adjustments, much faster loading, updated controls and other changes. Given the amount of work involved, I would classify <em>Demon’s Souls</em> on PS5 as a <strong>remake</strong>.</p>

<h3 id="metal-wolf-chaos-xd">Metal Wolf Chaos XD</h3>

<p><img src="/images/2026/metal-wolf-chaos-xd-cover.jpg" alt="Metal Wolf Chaos XD cover art" /></p>

<p><em>Metal Wolf Chaos</em> is a hilarious game by FromSoftware, released for the original Xbox only in Japan in December 2004. The player takes the role of the president of the United States, piloting a mech. You can watch a <a href="https://www.youtube.com/watch?v=J1n0pVxS4sw">speedrun from SGDQ 2014</a><sup id="fnref:metal-wolf-speedrun" role="doc-noteref"><a href="#fn:metal-wolf-speedrun" class="footnote" rel="footnote">3</a></sup>, with commentary explaining the gameplay. Funnily enough, Devolver unexpectedly offered to work with FromSoftware to rerelease the game:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Count us in to help Metal Wolf Chaos get out to more gamers if From Software wants some help. <a href="https://x.com/hashtag/FreeMetalWolf?src=hash&amp;ref_src=twsrc%5Etfw">#FreeMetalWolf</a> <a href="https://t.co/867qay70j8">pic.twitter.com/867qay70j8</a></p>&mdash; Devolver Digital (@devolverdigital) <a href="https://x.com/devolverdigital/status/692330482176884736?ref_src=twsrc%5Etfw">January 27, 2016</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<p>And it actually worked!<sup id="fnref:devolver-metal-wolf" role="doc-noteref"><a href="#fn:devolver-metal-wolf" class="footnote" rel="footnote">4</a></sup> A General Arcade team consisting of five software engineers, two QA engineers, a producer, an artist and two 3D modellers spent sixteen months on the project, according to its <a href="https://more.generalarcade.com/projects/metal-wolf-chaos-xd/">project page</a><sup id="fnref:general-arcade-metal-wolf" role="doc-noteref"><a href="#fn:general-arcade-metal-wolf" class="footnote" rel="footnote">5</a></sup>. They used a modified version of Sony’s PhyreEngine, C++ and FMOD, with Direct3D 11 on Xbox One and GNM on PlayStation 4.</p>

<p><img src="/images/2026/sr/metal_wolf_chaos_xd_port.svg" alt="Diagram showing how General Arcade modernised the original Metal Wolf Chaos for PC, PS4, and Xbox One" /></p>

<p>Because General Arcade describes the project as a modernisation of the original game—upgrading its presentation, controls and platform support rather than rebuilding it as a new game—I would classify <em>Metal Wolf Chaos XD</em> as a <strong>remaster</strong>, not a <strong>remake</strong>.</p>

<h3 id="dusklight">Dusklight</h3>

<p><img src="/images/2026/dusklight.jpg" alt="Dusklight running Twilight Princess natively" /></p>

<p>I got a GameCube at the end of its life (you can call me a patient gamer), and one of my favourite games on it—and in general—is <em>The Legend of Zelda: Twilight Princess</em>. Fast-forward a few years and I discovered that the community had created a native version of the game. It was released under the name Dusk in May 2026 and is now called <a href="https://twilitrealm.dev/posts/2026-05-09-dusk-v1-released/">Dusklight</a><sup id="fnref:dusklight-release" role="doc-noteref"><a href="#fn:dusklight-release" class="footnote" rel="footnote">6</a></sup>.</p>

<p>For Dusklight to exist, another project first had to decompile the original <em>Twilight Princess</em>. This project is <a href="https://github.com/zeldaret/tp">ZeldaRET/tp</a><sup id="fnref:zeldaret-tp" role="doc-noteref"><a href="#fn:zeldaret-tp" class="footnote" rel="footnote">7</a></sup>. Its goal is not merely to write code that behaves similarly, but to produce C/C++ code that compiles to the same machine instructions as the original executable.</p>

<p><img src="/images/2026/sr/dusklight_reverse_engineering.svg" alt="Diagram showing how community reverse engineering reconstructs source code that compiles to a matching GameCube executable" /></p>

<p>To reverse-engineer the PowerPC executable, they used <a href="https://github.com/encounter/decomp-toolkit">decomp-toolkit (or dtk)</a><sup id="fnref:decomp-toolkit" role="doc-noteref"><a href="#fn:decomp-toolkit" class="footnote" rel="footnote">8</a></sup>. This tool analyses the original GameCube/Wii executable, discovers functions, splits the binary into relocatable objects and generates linker information, symbols and splits. This provides the pieces of the “puzzle” that contributors need to recreate the game. For each piece—a compiled <code class="language-plaintext highlighter-rouge">.o</code> file—they recreate the C/C++ equivalent, then compile it using the Metrowerks CodeWarrior compilers and linkers used by Nintendo at the time. They compare the newly generated PowerPC <code class="language-plaintext highlighter-rouge">.o</code> with the original using <a href="https://github.com/encounter/objdiff">objdiff</a><sup id="fnref:objdiff" role="doc-noteref"><a href="#fn:objdiff" class="footnote" rel="footnote">9</a></sup>, then repeat the process until the two match.</p>

<p><img src="/images/2026/sr/dusklight_matching_loop.svg" alt="Diagram showing the dtk, CodeWarrior, and objdiff loop used to recreate matching GameCube object files" /></p>

<p>ZeldaRET is not itself a PC port, but Dusklight builds upon its work. Dusklight uses <a href="https://github.com/encounter/aurora">Aurora</a><sup id="fnref:aurora" role="doc-noteref"><a href="#fn:aurora" class="footnote" rel="footnote">10</a></sup>, a source-level GameCube and Wii compatibility layer, to link the game-derived code, including Nintendo’s JSystem code. This greatly simplifies the recompilation work because the developers do not need to refactor every call to the original GameCube APIs. Aurora uses SDL3 for its application layer and input, while its GX graphics compatibility layer is built on WebGPU. I also use SDL3 in the <em>Soup Raiders</em> native port. In the end, the architecture of Dusklight looks like this:</p>

<p><img src="/images/2026/sr/dusklight_architecture.svg" alt="Diagram of the Dusklight application, Twilight Princess framework, GameCube-style APIs, and Aurora compatibility layers" /></p>

<p>Because it turns matching decompiled source code into a native application for modern platforms, I would classify Dusklight as a <strong>recompilation</strong>.</p>

<h2 id="and-soup-raiders">And Soup Raiders?</h2>

<p>I started working on <em>Soup Raiders</em> with Unity 2017.4.1f1 in July 2018. I remember it well because we were developing a demo to submit to <a href="https://prohelvetia.ch/en/find-support/game-design-emerging-talents-production/">Pro Helvetia</a> (the Swiss Arts Council) in the hope of securing funding to begin production. At the time, the game looked like this:</p>

<p><img src="/images/2026/sr/sr_old.png" alt="The initial Soup Raiders demo" /></p>

<p>And I looked like this:</p>

<p><img src="/images/2026/elias_pirate.jpg" alt="Elias dressed as a pirate" /></p>

<p>After cancelling the project in mid-2019, I restarted it in March 2020, just as the COVID pandemic hit Europe. A new prototype began to take shape and looked like this:</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/R-jQGsZWEio?si=NOOZ8W9sYOOb6Gqp" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<p>The latest vertical slice, which is <a href="https://teamkwakwa.itch.io/soup-raiders">playable on itch.io</a>, ran on Unity 2022.3.2f1 and featured three levels with three fights, one mini-boss and one boss. This is the view from the beginning of the demo, in the docks of the Red Island:</p>

<p><img src="/images/2026/sr/sr_docks_full.png" alt="The opening view of the Red Island docks" /></p>

<p>Many things need to be migrated to my custom engine to reproduce this view, and each component of the demo has its own requirements. Let’s explore the different parts of this porting process. This blog post covers what I export from Unity; the next will cover how I import it into my game engine.</p>

<h3 id="the-audio">The audio</h3>

<p>This is the easiest part of the game to port. In Unity, I used <a href="https://www.fmod.com/">FMOD</a>, middleware that allows a sound designer to add events in the FMOD editor and generate banks for the game to import.</p>

<p>Fortunately, FMOD is not limited to Unity and provides native libraries for PC and Nintendo Switch, as well as other modern platforms such as PS4, PS5 and even HTML5. Porting the audio therefore meant copying the banks and statically linking the libraries provided by FMOD.</p>

<h3 id="3d-models">3D models</h3>

<p><a href="https://juba.artstation.com/">Julie Baechtold</a> did incredible work on <em>Soup Raiders</em>. She created the 3D models in Blender, then placed them in Unity to create the levels. As she was not familiar with <code class="language-plaintext highlighter-rouge">git</code>, I created a tool that exported her Unity scene and all its dependencies so that I could import them into my Unity project.</p>

<p>There are several ways to export those models and the layout that Julie created in Unity:</p>

<ol>
  <li>Use Unity’s FBX Exporter package.</li>
  <li>Use Blender as the asset source (exported as <code class="language-plaintext highlighter-rouge">.glb</code>) and Unity for level composition.</li>
  <li>Parse the <code class="language-plaintext highlighter-rouge">.unity</code> YAML file in my engine.</li>
  <li>Build a custom Unity scene exporter that generates glTF 2.0 <code class="language-plaintext highlighter-rouge">.glb</code> files.</li>
</ol>

<p>I went with solution 4. The Unity Editor script <code class="language-plaintext highlighter-rouge">ExportLevelGeometry.cs</code> generates a GLB file for the entire level. A GLB file contains two main parts:</p>

<ul>
  <li>A JSON part describing the glTF asset, scenes, nodes, meshes, accessors, buffers and buffer views.</li>
  <li>A binary part containing the mesh positions, UV coordinates and indices.</li>
</ul>

<p>Meanwhile, another script, <code class="language-plaintext highlighter-rouge">ExportLevelRenderers.cs</code>, exports the materials linked to the renderers—in short, their visibility, textures and related data. These are exported to a simple <code class="language-plaintext highlighter-rouge">.json</code> file that references each texture that must also be exported. I use Unity’s <strong>GlobalObjectId</strong> to match each renderer across the two exports. This allows me to export all the static meshes, including static sprites:</p>

<p><img src="/images/2026/sr/sr_static_mesh.png" alt="The static meshes exported from the Red Island docks" /></p>

<p>The renderers JSON looks like this:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
  </span><span class="nl">"coordinateSpace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"native_render_x_flipped_from_unity"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"renderers"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LevelDocks/DOCKS-DETAILS/barrel"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GlobalObjectId_V1-2-88d0b98e1cbd76d418edf891b923eadb-
          16941762359777390037-2086583302"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MeshRenderer"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"active"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">-46.179996490478519</span><span class="p">,</span><span class="w"> </span><span class="mf">-10.490001678466797</span><span class="p">,</span><span class="w"> </span><span class="mf">-112.6961898803711</span><span class="p">],</span><span class="w">
      </span><span class="nl">"center"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">-46.16727066040039</span><span class="p">,</span><span class="w"> </span><span class="mf">-10.471028327941895</span><span class="p">,</span><span class="w"> </span><span class="mf">-112.69300842285156</span><span class="p">],</span><span class="w">
      </span><span class="nl">"materials"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"barrel"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"guid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bd4b0fa6863613a48b2d6fc9d4a12681"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"assetPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Assets/Materials/Red Island/Props/barrel.mat"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"mainTexture"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Assets/Textures/Red Island/Docks/barrel.jpg"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"renderType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Opaque"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"shader"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Custom/Toon Shading"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"alphaCutoff"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.5</span><span class="p">,</span><span class="w">
          </span><span class="nl">"textureScale"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">,</span><span class="w"> </span><span class="mf">1.0</span><span class="p">],</span><span class="w">
          </span><span class="nl">"textureOffset"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="p">,</span><span class="w"> </span><span class="mf">0.0</span><span class="p">],</span><span class="w">
          </span><span class="nl">"outlineColor"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">0.22745098173618318</span><span class="p">,</span><span class="w"> </span><span class="mf">0.10588235408067703</span><span class="p">,</span><span class="w"> 
              </span><span class="mf">0.06666667014360428</span><span class="p">,</span><span class="w"> </span><span class="mf">0.5</span><span class="p">],</span><span class="w">
          </span><span class="nl">"outlineWidth"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.019999999552965165</span><span class="p">,</span><span class="w">
          </span><span class="nl">"outlineAngle"</span><span class="p">:</span><span class="w"> </span><span class="mf">89.0</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"articulated"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
      </span><span class="nl">"lookAtCamera"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>A little note on <code class="language-plaintext highlighter-rouge">native_render_x_flipped_from_unity</code>, it just means that positions go <code class="language-plaintext highlighter-rouge">(x,y,z) → (-x,y,z)</code> and Euler angles <code class="language-plaintext highlighter-rouge">(x,y,z) → (x,-y,-z)</code>. I will talk about outlines in a following blog post on the renderer.</p>

<p>However, the brownish water of the Soup is missing! This mesh is a little unusual. It was not created in Blender, but directly in Unity, and uses a specific <code class="language-plaintext highlighter-rouge">ToonWater</code> shader rather than a more standard shader like the rest of the static level. It is actually just four quads. Its geometry is also exported by <code class="language-plaintext highlighter-rouge">ExportLevelGeometry.cs</code>, because I want to preserve the Unity geometry, but it goes through <code class="language-plaintext highlighter-rouge">ExportDocksWater()</code> to generate <code class="language-plaintext highlighter-rouge">water_geometry.glb</code> and <code class="language-plaintext highlighter-rouge">water_instances.json</code>. Here it is:</p>

<figure class="image-comparison" style="--comparison-position: 50%;">
  <div class="image-comparison__frame">
    <img class="image-comparison__image" src="/images/2026/sr/sr_water_wit_meshes.png" alt="Soup Raiders docks with the water" />
    <img class="image-comparison__image image-comparison__image--left" src="/images/2026/sr/sr_static_mesh.png" alt="Soup Raiders docks without the water" />
    <span class="image-comparison__label image-comparison__label--left">Without water</span>
    <span class="image-comparison__label image-comparison__label--right">With water</span>
    <span class="image-comparison__divider" aria-hidden="true">
      <span class="image-comparison__handle">&#8596;</span>
    </span>
    <div class="image-comparison__control" role="slider" tabindex="0" aria-label="Compare Without water and With water" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50"></div>
  </div>
  
</figure>
<script src="/assets/js/image-comparison.js" defer=""></script>

<h3 id="colliders">Colliders</h3>

<p>Visuals are nice, but my main character needs something to walk on. In the Unity demo, I spent a lot of time adjusting each level’s physics shapes to ensure that the main character did not fall through the ground or get stuck in a wall or on the stairs. To reproduce this behaviour in my engine, I also needed to export the colliders. The <code class="language-plaintext highlighter-rouge">ExportLevelCoilliders.cs</code> script exports the following shapes:</p>

<ul>
  <li>Box colliders: emitted as hulls (eight world-space corners).</li>
  <li>Convex mesh colliders: hulls (world-space vertices).</li>
  <li>Non-convex mesh colliders: meshes (vertices and triangles).</li>
  <li>Sphere colliders.</li>
  <li>Capsule colliders.</li>
</ul>

<p>In addition to the collider geometry, I add the surface type from the GameObject tag: <code class="language-plaintext highlighter-rouge">Wood</code> becomes <code class="language-plaintext highlighter-rouge">1</code>, <code class="language-plaintext highlighter-rouge">Stairs</code> becomes <code class="language-plaintext highlighter-rouge">2</code>, and so on. The main character’s footsteps differ depending on whether they step on wood or stone. The exporter also excludes specific objects such as moving characters: Dorothy has a collider, but it is not static. In the end, the exported colliders look like this:</p>

<p><img src="/images/2026/sr/sr_physics_box.png" alt="The colliders exported from the Red Island docks" /></p>

<p>Here is how the collider JSON looks like:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
  </span><span class="nl">"coordinateSpace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"native_render_x_flipped_from_unity"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"shapes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hull"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"materialIndex"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
      </span><span class="nl">"points"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="mf">31.5793</span><span class="p">,</span><span class="w"> </span><span class="mf">7.09921</span><span class="p">,</span><span class="w"> </span><span class="mf">6.9241</span><span class="p">,</span><span class="w">
        </span><span class="mf">31.5299</span><span class="p">,</span><span class="w"> </span><span class="mf">9.76265</span><span class="p">,</span><span class="w"> </span><span class="mf">2.54655</span><span class="p">,</span><span class="w">
        </span><span class="mf">37.85232</span><span class="p">,</span><span class="w"> </span><span class="mf">7.13064</span><span class="p">,</span><span class="w"> </span><span class="mf">6.87243</span><span class="p">,</span><span class="w">
        </span><span class="mf">37.80292</span><span class="p">,</span><span class="w"> </span><span class="mf">9.79409</span><span class="p">,</span><span class="w"> </span><span class="mf">2.49488</span><span class="p">,</span><span class="w">
        </span><span class="mf">31.5793</span><span class="p">,</span><span class="w"> </span><span class="mf">7.43721</span><span class="p">,</span><span class="w"> </span><span class="mf">7.12975</span><span class="p">,</span><span class="w">
        </span><span class="mf">31.5299</span><span class="p">,</span><span class="w"> </span><span class="mf">10.10066</span><span class="p">,</span><span class="w"> </span><span class="mf">2.7522</span><span class="p">,</span><span class="w">
        </span><span class="mf">37.85232</span><span class="p">,</span><span class="w"> </span><span class="mf">7.46865</span><span class="p">,</span><span class="w"> </span><span class="mf">7.07808</span><span class="p">,</span><span class="w">
        </span><span class="mf">37.80292</span><span class="p">,</span><span class="w"> </span><span class="mf">10.13209</span><span class="p">,</span><span class="w"> </span><span class="mf">2.70054</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">type</code>is one of the box3d shapes that matches the Unity collider</li>
</ul>

<h3 id="billboards">Billboards</h3>

<p>Much of <em>Soup Raiders</em>’ charm comes from its animated 2D characters. The main characters were animated by <a href="https://camillebovey.com/">Camille Bovey</a>, while Julie added many seagulls to the levels. They behave like billboards, always facing the camera. The textures were already available for export, but, as with the level’s meshes, I also needed to export the placement of the billboards. This is the job of <code class="language-plaintext highlighter-rouge">ExportLevelBillboards.cs</code>, which exports their world position, lossy world scale, <code class="language-plaintext highlighter-rouge">flipX</code> value, texture, pixel rectangle, pivot in pixels (where the character’s feet are) and pixels per unit. It also exports the single-frame talk icons used by talkative characters. This is what the billboard JSON looks like in the end:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
  </span><span class="nl">"coordinateSpace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"native_render_x_flipped_from_unity"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"billboards"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Seagull Talk (1)"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">-10.2153</span><span class="p">,</span><span class="w"> </span><span class="mf">6.30011</span><span class="p">,</span><span class="w"> </span><span class="mf">-41.96577</span><span class="p">],</span><span class="w">
      </span><span class="nl">"parentName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"StaticCharacter_Seagull_05 (5)"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"parentPosition"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">-11.19866</span><span class="p">,</span><span class="w"> </span><span class="mf">2.86357</span><span class="p">,</span><span class="w"> </span><span class="mf">-45.25861</span><span class="p">],</span><span class="w">
      </span><span class="nl">"parentOffset"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mf">4.86</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">],</span><span class="w">
      </span><span class="nl">"scale"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">0.3</span><span class="p">,</span><span class="w"> </span><span class="mf">0.3</span><span class="p">],</span><span class="w">
      </span><span class="nl">"flipX"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
      </span><span class="nl">"texture"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Assets/Sprites/UI/Level/talk.PNG"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"rect"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">512</span><span class="p">,</span><span class="w"> </span><span class="mi">512</span><span class="p">],</span><span class="w">
      </span><span class="nl">"pivotPx"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">256</span><span class="p">,</span><span class="w"> </span><span class="mi">256</span><span class="p">],</span><span class="w">
      </span><span class="nl">"pixelsPerUnit"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><img src="/images/2026/sr/sr_billboards.png" alt="The static character billboards in the Red Island docks" /></p>

<p>Two characters are missing:</p>

<ul>
  <li>The player-controlled character.</li>
  <li>Dorothy, who moves during cinematics.</li>
</ul>

<p>With them, the world is more crowded:</p>

<p><img src="/images/2026/sr/sr_all_characters.png" alt="The Red Island docks with all moving characters included" /></p>

<p>The final detail in this section is animation. Because my <code class="language-plaintext highlighter-rouge">Animator</code> clips simply switched sprites at specific frames, their animations were straightforward to export by reading the Unity YAML. Here is what an animation looks like in YAML:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span> <span class="kt">!u!74</span> <span class="nl">&amp;7400000</span>
<span class="na">AnimationClip</span><span class="pi">:</span>
  <span class="na">m_Name</span><span class="pi">:</span> <span class="s">WalkSide</span>
  <span class="na">m_SampleRate</span><span class="pi">:</span> <span class="m">12</span>
  <span class="na">m_PPtrCurves</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">curve</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">time</span><span class="pi">:</span> <span class="m">0</span>
      <span class="na">value</span><span class="pi">:</span> <span class="pi">{</span><span class="nv">fileID</span><span class="pi">:</span> <span class="nv">21300000</span><span class="pi">,</span> <span class="nv">guid</span><span class="pi">:</span> <span class="nv">3f2a...</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">3</span><span class="pi">}</span>
    <span class="pi">-</span> <span class="na">time</span><span class="pi">:</span> <span class="m">0.0833333</span>
      <span class="na">value</span><span class="pi">:</span> <span class="pi">{</span><span class="nv">fileID</span><span class="pi">:</span> <span class="nv">21300000</span><span class="pi">,</span> <span class="nv">guid</span><span class="pi">:</span> <span class="nv">9c41...</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">3</span><span class="pi">}</span>
    <span class="na">attribute</span><span class="pi">:</span> <span class="s">sprite</span>
    <span class="na">path</span><span class="pi">:</span>
  <span class="na">m_Events</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">time</span><span class="pi">:</span> <span class="m">0.25</span>
    <span class="na">functionName</span><span class="pi">:</span> <span class="s">ManageAnimEvent</span>
    <span class="na">intParameter</span><span class="pi">:</span> <span class="m">1</span>
  <span class="na">m_AnimationClipSettings</span><span class="pi">:</span>
    <span class="na">m_StopTime</span><span class="pi">:</span> <span class="m">0.5</span>
    <span class="na">m_LoopTime</span><span class="pi">:</span> <span class="m">1</span>
</code></pre></div></div>
<p>Although <code class="language-plaintext highlighter-rouge">m_PPtrCurves</code> looks like a barbaric name, it contains the times at which the animation changes from one image to another.</p>

<h3 id="skybox">Skybox</h3>

<p>As the skybox already consists of six textures, it does not need a Unity-side export. I will cover its import into my engine in more detail in the next blog post. Of course, the level looks better with it:</p>

<figure class="image-comparison" style="--comparison-position: 15%;">
  <div class="image-comparison__frame">
    <img class="image-comparison__image" src="/images/2026/sr/sr_level_skybox.png" alt="Soup Raiders docks with the skybox" />
    <img class="image-comparison__image image-comparison__image--left" src="/images/2026/sr/sr_water_wit_meshes.png" alt="Soup Raiders docks without the skybox" />
    <span class="image-comparison__label image-comparison__label--left">Without skybox</span>
    <span class="image-comparison__label image-comparison__label--right">With skybox</span>
    <span class="image-comparison__divider" aria-hidden="true">
      <span class="image-comparison__handle">&#8596;</span>
    </span>
    <div class="image-comparison__control" role="slider" tabindex="0" aria-label="Compare Without skybox and With skybox" aria-valuemin="0" aria-valuemax="100" aria-valuenow="15"></div>
  </div>
  
</figure>
<script src="/assets/js/image-comparison.js" defer=""></script>

<h3 id="snap-rails">Snap rails</h3>

<p>In <em>Soup Raiders</em>, players do not control the camera. Instead of using Cinemachine—I tend not to use every Unity tool when I can implement something myself—I created a system that interpolates between snap points. Each point stores the camera position and direction, as well as the character position. It also stores the character’s Euler angles, but I do not export them. This allows the camera to move around while following the player. The snap points are exported as-is to a <code class="language-plaintext highlighter-rouge">.json</code> file that looks like this:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
  </span><span class="nl">"coordinateSpace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"native_render_x_flipped_from_unity"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"snapshots"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"cameraPos"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">-52.23652</span><span class="p">,</span><span class="w"> </span><span class="mf">-2.154821</span><span class="p">,</span><span class="w"> </span><span class="mf">-132.7845</span><span class="p">],</span><span class="w">
      </span><span class="nl">"cameraAngles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">35.454903</span><span class="p">,</span><span class="w"> </span><span class="mf">2.0682678</span><span class="p">,</span><span class="w"> </span><span class="mf">0.00016311444</span><span class="p">],</span><span class="w">
      </span><span class="nl">"characterPos"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">-51.905075</span><span class="p">,</span><span class="w"> </span><span class="mf">-8.694596</span><span class="p">,</span><span class="w"> </span><span class="mf">-117.0</span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"cameraPos"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">-82.17407</span><span class="p">,</span><span class="w"> </span><span class="mf">-3.3850465</span><span class="p">,</span><span class="w"> </span><span class="mf">-141.92406</span><span class="p">],</span><span class="w">
      </span><span class="nl">"cameraAngles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">9.106377</span><span class="p">,</span><span class="w"> </span><span class="mf">58.706604</span><span class="p">,</span><span class="w"> </span><span class="mf">0.0001880661</span><span class="p">],</span><span class="w">
      </span><span class="nl">"characterPos"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">-51.903656</span><span class="p">,</span><span class="w"> </span><span class="mf">-9.063084</span><span class="p">,</span><span class="w"> </span><span class="mf">-123.52415</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"transitions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"a"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"b"</span><span class="p">:</span><span class="w"> </span><span class="mi">36</span><span class="p">,</span><span class="w">
      </span><span class="nl">"radius"</span><span class="p">:</span><span class="w"> </span><span class="mf">5.0</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>I had a Play Mode snapshot-recording system that let me move through the level with a free-orbit camera, find a good viewpoint and save the snapshot. The snap system would then connect the nearest points when an unobstructed raycast existed between them. This is what it looks like in Unity, with red spheres representing the snap points and red lines representing the connections between them:</p>

<p><img src="/images/2026/sr/sr_rails.png" alt="Camera snap points and their connections in Unity" /></p>

<h3 id="source-code">Source code</h3>

<p>The Unity demo contains 174 C# files with 18,421 lines of code. While not enormous, it is still a substantial body of code to migrate to C++. One example of a C#-to-C++ migration is the PS Vita port of <em>Bastion</em>:</p>

<p><img src="/images/2026/bastion.jpg" alt="Bastion running on PS Vita" /></p>

<p><em>Bastion</em> was built with XNA, .NET and C#. After porting <em>FEZ</em> to PlayStation platforms, BlitWorks began work on the PS4 and PS Vita versions of <em>Bastion</em>. For the Vita version, the team first tried running the game on its ports of Mono and MonoGame. The beginning of the first level ran at around 30 FPS, with additional stalls caused by Mono’s SGen garbage collector. BlitWorks then created <strong>Unsharper</strong>, a tool that converts C# features directly into C++ and replaces garbage collection with reference counting. Although it addresses the same broad problem as Unity’s IL2CPP, Unsharper does not rely on .NET intermediate language and generates C++ that resembles the original C# code.<sup id="fnref:bastion-vita" role="doc-noteref"><a href="#fn:bastion-vita" class="footnote" rel="footnote">11</a></sup></p>

<p>Another option would be to use Native AOT for the C# behaviour while implementing the core engine in C++. Noel Berry discusses the portability of Native AOT in <a href="https://noelberry.ca/posts/making_games_in_2025/"><em>Making Video Games in 2025 (Without an Engine)</em></a><sup id="fnref:noelberry" role="doc-noteref"><a href="#fn:noelberry" class="footnote" rel="footnote">12</a></sup>. In the end, because my C# code was closely tied to Unity’s <code class="language-plaintext highlighter-rouge">GameObject</code> and <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> model, I decided to migrate all of it manually to a more ECS-like C++ architecture. This also provided a good opportunity to refactor it.</p>

<p>Another consideration is the shader source code. Unity already has its own renderer and pipeline, so much of the shader code is provided by the engine. This is not the case for my game engine, which uses a custom renderer with GLSL. I will discuss its features and specifics in another blog post. Fortunately, Unity’s Built-in Render Pipeline shaders can be found <a href="https://github.com/chsxf/unity-built-in-shaders/tree/master">online</a><sup id="fnref:unity-built-in-shaders" role="doc-noteref"><a href="#fn:unity-built-in-shaders" class="footnote" rel="footnote">13</a></sup>. Unity uses Cg/HLSL-style shader code, derived from <a href="https://developer.nvidia.com/cg-toolkit">NVIDIA Cg</a><sup id="fnref:nvidia-cg" role="doc-noteref"><a href="#fn:nvidia-cg" class="footnote" rel="footnote">14</a></sup>, a shader language designed to target both OpenGL and Direct3D. NVIDIA discontinued its development in April 2012.</p>

<p>My renderer instead uses GLSL, which I cross-compile to SPIR-V, HLSL, MSL and WGSL using tools from the Vulkan SDK. It might make sense to switch to <a href="https://shader-slang.org/">Slang</a><sup id="fnref:slang" role="doc-noteref"><a href="#fn:slang" class="footnote" rel="footnote">15</a></sup> in the near future, as a modern successor to NVIDIA Cg.</p>

<h3 id="dialogue">Dialogue</h3>

<p>The dialogue system went through several iterations during the production of the demo. I started with <a href="https://www.inklestudios.com/ink/">Ink from inkle</a>. The file format looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>That's a painting of Rene Descarps, one of the most famous Philosofishes. #WF
I'm already bored… #BW
"I sink, therefore I am." What an enlightened soul! #WF
Nobody cares... #BW
Actually, now I want to eat some fish! #KR
Well, that's the problem with this world: 
    people are more guided by their stomach, than their mind! #WF
-&gt;END
</code></pre></div></div>

<p>I used hashtags to identify the speaking character (<code class="language-plaintext highlighter-rouge">#BW</code> for Black Whiskers, <code class="language-plaintext highlighter-rouge">#WF</code> for White Fur and <code class="language-plaintext highlighter-rouge">#KR</code> for Krokoss). I hired one of my students, <a href="https://www.linkedin.com/in/nicolas-schneider-14135a170/">Nicolas Schneider</a>, as an intern to create a tool that integrated Ink into our dialogue scene. He now works at <a href="https://www.oldskullgames.com/">Old Skull Games</a>. The internal tool turned each line into an asset containing additional information, such as the characters’ emotions and placement.</p>

<p><img src="/images/2026/sr/sr_dialog.png" alt="The Soup Raiders dialogue tool" /></p>

<p>However, as production advanced, the intern’s tool became obsolete. I eventually took matters into my own hands and switched to <a href="https://www.yarnspinner.dev/">Yarn Spinner</a>, which is closer to writing dialogue as a program, with conditions and other logic. Its file format looks like this:</p>

<pre><code class="language-yarn">title: DocksPhilosophiesLore
---
WhiteFur: That's a painting of Rene Descarps,
    one of the most famous Philosofishes. #line:0063522
BlackWhiskers: I'm already bored...  #line:0b2d361
WhiteFur: "I sink, therefore I am." What an enlightened soul! #line:02a3da8
BlackWhiskers: Nobody cares... #line:0b46948
Krokoss: Actually, now I want to eat some fish! #line:01e0687
WhiteFur: Well, that's the problem with this world:
    people are more guided by their stomach, than their mind! #line:068f343
===
</code></pre>
<p>One major advantage is the line ID at the end of each line; this time, I also used each character’s full name to identify the speaker. Another advantage is the ability to put multiple dialogues in one file, whereas I had used a separate Ink file for each dialogue. Exporting all the <code class="language-plaintext highlighter-rouge">.yarn</code> scripts—the dialogue files—is straightforward, but Yarn Spinner normally runs them through its C# runtime. In the end, I wrote a simple interpreter for my use case.</p>

<h3 id="others">Others</h3>

<p><img src="/images/2026/sr/sr_battle.png" alt="A ground battle in Soup Raiders" /></p>

<p>I have not covered the ground-battle migration in this blog post. It is a part of the game that relies heavily on 2D animation, with a UI layout that also needs to be exported. For animation, I can use the same technique as before and import the <code class="language-plaintext highlighter-rouge">m_PPtrCurves</code> from every animation clip used by the <code class="language-plaintext highlighter-rouge">Animator</code>. This allows me to identify all the textures needed to play those animations.</p>

<p><img src="/images/2026/sr/sr_menu.png" alt="The Soup Raiders title screen" /></p>

<p>The title screen is fairly simple. Its UI layout is exported as JSON and the rest consists of images. However, it contains a large soup-boat animation made from 105 PNGs, which requires some processing by my engine’s asset compiler. That is a subject for the next blog post.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Porting a game may not be as difficult as starting again from scratch. When restarting a project, it can be tempting to delete everything and import every asset again by hand. I tried something different: because I may still use Unity as an editor, I made deliberate choices about how to export the assets I needed. This demo is substantial enough for the port to be non-trivial, but it is not a AAA game or a game whose executable must be reverse-engineered from another console architecture.</p>

<p>Now that I have all these assets, the next step is the asset compiler, which determines how the exported assets are imported into my game engine.</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:demons-souls-noclip" role="doc-endnote">
      <p>Noclip, <em>Demon’s Souls: Remaking a PlayStation Classic</em>, 12 July 2021. <a href="https://www.youtube.com/watch?v=hCBJ2fiiUXk">https://www.youtube.com/watch?v=hCBJ2fiiUXk</a> <a href="#fnref:demons-souls-noclip" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:demons-souls-digital-foundry" role="doc-endnote">
      <p>Digital Foundry, <em>Demon’s Souls Remake on PlayStation 5: The Digital Foundry Tech Review</em>, 17 November 2020. <a href="https://www.youtube.com/watch?v=9O--CnN056E">https://www.youtube.com/watch?v=9O--CnN056E</a> <a href="#fnref:demons-souls-digital-foundry" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:metal-wolf-speedrun" role="doc-endnote">
      <p>MURPHAGATOR!, <em>Metal Wolf Chaos</em> speedrun, Summer Games Done Quick 2014. <a href="https://www.youtube.com/watch?v=J1n0pVxS4sw">https://www.youtube.com/watch?v=J1n0pVxS4sw</a> <a href="#fnref:metal-wolf-speedrun" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:devolver-metal-wolf" role="doc-endnote">
      <p>Devolver Digital (@devolverdigital), post on X, 27 January 2016. <a href="https://x.com/devolverdigital/status/692330482176884736">https://x.com/devolverdigital/status/692330482176884736</a> <a href="#fnref:devolver-metal-wolf" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:general-arcade-metal-wolf" role="doc-endnote">
      <p>General Arcade, <em>Metal Wolf Chaos XD</em>. <a href="https://more.generalarcade.com/projects/metal-wolf-chaos-xd/">https://more.generalarcade.com/projects/metal-wolf-chaos-xd/</a> <a href="#fnref:general-arcade-metal-wolf" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:dusklight-release" role="doc-endnote">
      <p>Twilit Realm team, <em>Dusklight is now available</em>, 9 May 2026; updated 12 May 2026. <a href="https://twilitrealm.dev/posts/2026-05-09-dusk-v1-released/">https://twilitrealm.dev/posts/2026-05-09-dusk-v1-released/</a> <a href="#fnref:dusklight-release" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:zeldaret-tp" role="doc-endnote">
      <p>ZeldaRET, <em>The Legend of Zelda: Twilight Princess decompilation</em>. <a href="https://github.com/zeldaret/tp">https://github.com/zeldaret/tp</a> <a href="#fnref:zeldaret-tp" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:decomp-toolkit" role="doc-endnote">
      <p>encounter, <em>decomp-toolkit: a GameCube and Wii decompilation toolkit</em>. <a href="https://github.com/encounter/decomp-toolkit">https://github.com/encounter/decomp-toolkit</a> <a href="#fnref:decomp-toolkit" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:objdiff" role="doc-endnote">
      <p>encounter, <em>objdiff: a local diffing tool for decompilation projects</em>. <a href="https://github.com/encounter/objdiff">https://github.com/encounter/objdiff</a> <a href="#fnref:objdiff" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:aurora" role="doc-endnote">
      <p>encounter, <em>Aurora: a source-level GameCube and Wii compatibility layer</em>. <a href="https://github.com/encounter/aurora">https://github.com/encounter/aurora</a> <a href="#fnref:aurora" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:bastion-vita" role="doc-endnote">
      <p>Miguel Angel Horna, <em>Postmortem: Porting Bastion to PSVita</em>, Game Developer, 21 December 2015. <a href="https://www.gamedeveloper.com/programming/postmortem-porting-bastion-to-psvita">https://www.gamedeveloper.com/programming/postmortem-porting-bastion-to-psvita</a> <a href="#fnref:bastion-vita" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:noelberry" role="doc-endnote">
      <p>Noel Berry, <em>Making Video Games in 2025 (without an engine)</em>, 18 May 2025. <a href="https://noelberry.ca/posts/making_games_in_2025/">https://noelberry.ca/posts/making_games_in_2025/</a> <a href="#fnref:noelberry" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unity-built-in-shaders" role="doc-endnote">
      <p>chsxf, <em>Unity Built-in Shaders</em>. <a href="https://github.com/chsxf/unity-built-in-shaders/tree/master">https://github.com/chsxf/unity-built-in-shaders/tree/master</a> <a href="#fnref:unity-built-in-shaders" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:nvidia-cg" role="doc-endnote">
      <p>NVIDIA, <em>Cg Toolkit</em>. <a href="https://developer.nvidia.com/cg-toolkit">https://developer.nvidia.com/cg-toolkit</a> <a href="#fnref:nvidia-cg" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:slang" role="doc-endnote">
      <p>The Slang Project, <em>Slang</em>. <a href="https://shader-slang.org/">https://shader-slang.org/</a> <a href="#fnref:slang" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="gamedev" /><category term="cpp" /><summary type="html"><![CDATA[A port is not simply “the same game in another engine” or “just an emulator”. It is a long series of decisions about what to do with the original content (art, code and audio) to create a new version with its own set of constraints. In this blog post, I will look at three different approaches to porting, then show what I exported from my old Unity demo and how. The next blog post will cover how I imported those assets into my own game engine.]]></summary></entry><entry><title type="html">Soup Raiders Goes Native: What You Gain by Building Your Own Game Engine</title><link href="https://eliasfarhan.ch/gamedev/cpp/2026/08/14/srnative-01-why-a-custom-engine.html" rel="alternate" type="text/html" title="Soup Raiders Goes Native: What You Gain by Building Your Own Game Engine" /><published>2026-08-14T00:00:00+00:00</published><updated>2026-08-14T00:00:00+00:00</updated><id>https://eliasfarhan.ch/gamedev/cpp/2026/08/14/srnative-01-why-a-custom-engine</id><content type="html" xml:base="https://eliasfarhan.ch/gamedev/cpp/2026/08/14/srnative-01-why-a-custom-engine.html"><![CDATA[<p><img src="/images/2026/game_engine_share_release.png" alt="" /></p>

<p>According to VGInsights, about 13% of games released on Steam in 2024 used a custom engine, down from 71% in 2012<sup id="fnref:vginsights" role="doc-noteref"><a href="#fn:vginsights" class="footnote" rel="footnote">1</a></sup>. So looking at this trend, why should anybody try to make their own game engine?</p>

<!--more-->

<p>But…</p>

<p><img src="/images/2026/game_engine_share_sold.png" alt="" /></p>

<p>Those games still accounted for 43% of units sold in 2024<sup id="fnref:vginsights:1" role="doc-noteref"><a href="#fn:vginsights" class="footnote" rel="footnote">1</a></sup>. Even if most were AAA games built with custom engines, the figure shows that developing your own engine can still make business sense. So this blog post is about why you should make a game engine.</p>

<p>However, before answering this question, we need to define what a game engine is. The term is broad enough that Claude Code has even been compared to a small game engine<sup id="fnref:thariq" role="doc-noteref"><a href="#fn:thariq" class="footnote" rel="footnote">2</a></sup>:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Most people&#39;s mental model of Claude Code is that &quot;it&#39;s just a TUI&quot; but it should really be closer to &quot;a small game engine&quot;.<br /><br />For each frame our pipeline constructs a scene graph with React then<br />-&gt; layouts elements<br />-&gt; rasterizes them to a 2d screen<br />-&gt; diffs that against the…</p>&mdash; Thariq (@trq212) <a href="https://x.com/trq212/status/2014051501786931427?ref_src=twsrc%5Etfw">January 21, 2026</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<h2 id="whats-a-game-engine-and-what-do-we-mean-by-custom">What’s a game engine and what do we mean by custom?</h2>

<p>A game engine is a reusable software framework that lets you create and run a computer game. It sits between the actual game code (gameplay logic) and the operating system (Windows, SteamOS, Android):</p>

<p><img src="/images/2026/game_engine_stack.svg" alt="" /></p>

<p>The game engine’s primary role is to keep the game loop running and handle interactions with the operating system. This game loop runs as follows:</p>

<p><img src="/images/2026/game_loop.svg" alt="" /></p>

<ul>
  <li><strong>Event Polling</strong>: window resizing, keyboard/mouse/controller input changes, exiting the game…</li>
  <li><strong>Gameplay</strong>: physics, calling the gameplay code</li>
  <li><strong>Rendering</strong>: generating the commands to be sent to the GPU to then present the new images on the screen</li>
</ul>

<p>A game engine does much more than this. It also loads and manages the game’s assets: 3D models, animations, sprites, music, and sound. Yet none of these systems implements the game’s specific rules or content. We will go into more detail in the next blog posts about each part and how it is implemented in the <em>Soup Raiders</em> native port.</p>

<p>At the beginning of any game project, then, the question of whether to build a custom engine naturally arises. The goal of commercial game engines like Unreal or Unity is to sell you the game engine so that you only have to focus on making your game, whereas making a custom game engine means building that layer yourself, or at least gluing frameworks and libraries together to do the under-the-hood engineering. Here are some examples:</p>

<ul>
  <li>I use Unity, but I develop a lot of internal tools &lt;- <strong>not</strong> a custom game engine.</li>
  <li>I use SDL3 + Assimp to make a 3D game &lt;- this is a custom game engine.</li>
  <li>I use Unreal, but I rewrote the renderer &lt;- <strong>not</strong> a custom game engine.</li>
  <li>I use SFML to make a simple 2D networking game &lt;- this is a custom game engine.</li>
  <li>I use Godot and extend it with C++ &lt;- <strong>not</strong> a custom game engine.</li>
  <li>I handwrote the renderer from scratch in DX12 and opened a window with the Windows API &lt;- this is a custom game engine.</li>
</ul>

<p>It is worth looking at some games built with custom engines. I still remember <a href="http://www.indiegamethemovie.com/about/"><em>Indie Game: The Movie</em></a><sup id="fnref:indie-game-movie" role="doc-noteref"><a href="#fn:indie-game-movie" class="footnote" rel="footnote">3</a></sup>, in which every featured game was built with a custom engine. Many other successful games have also been built with custom engines<sup id="fnref:custom-engine-games" role="doc-noteref"><a href="#fn:custom-engine-games" class="footnote" rel="footnote">4</a></sup>:</p>

<table>
  <thead>
    <tr>
      <th>Game</th>
      <th>Studio</th>
      <th>Release Date</th>
      <th>Engine / Technology</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Bastion</td>
      <td>Supergiant Games</td>
      <td>July 20, 2011</td>
      <td>Custom C# technology, Microsoft XNA</td>
    </tr>
    <tr>
      <td>Legend of Grimrock</td>
      <td>Almost Human</td>
      <td>April 11, 2012</td>
      <td>Custom C++ engine, Lua</td>
    </tr>
    <tr>
      <td>FTL: Faster Than Light</td>
      <td>Subset Games</td>
      <td>September 14, 2012</td>
      <td>Custom C++ engine, SDL</td>
    </tr>
    <tr>
      <td>Pixel Dungeon</td>
      <td>Watabou</td>
      <td>November 29, 2012</td>
      <td>Java (later ported with libGDX)</td>
    </tr>
    <tr>
      <td>Don’t Starve</td>
      <td>Klei Entertainment</td>
      <td>April 23, 2013</td>
      <td>Custom C++ engine, Lua</td>
    </tr>
    <tr>
      <td>Banished</td>
      <td>Shining Rock Software</td>
      <td>February 18, 2014</td>
      <td>Custom C++ engine</td>
    </tr>
    <tr>
      <td>Shovel Knight</td>
      <td>Yacht Club Games</td>
      <td>June 26, 2014</td>
      <td>Custom C++ engine, DirectX/OpenGL</td>
    </tr>
    <tr>
      <td>This War of Mine</td>
      <td>11 bit studios</td>
      <td>November 14, 2014</td>
      <td>Liquid Engine</td>
    </tr>
    <tr>
      <td>Crypt of the NecroDancer</td>
      <td>Brace Yourself Games</td>
      <td>April 23, 2015</td>
      <td>Custom C++ engine, Lua</td>
    </tr>
    <tr>
      <td>Prison Architect</td>
      <td>Introversion Software</td>
      <td>October 6, 2015</td>
      <td>Custom C++ engine</td>
    </tr>
    <tr>
      <td>Darkest Dungeon</td>
      <td>Red Hook Studios</td>
      <td>January 19, 2016</td>
      <td>Custom C++ engine, Spine</td>
    </tr>
    <tr>
      <td>Stardew Valley</td>
      <td>ConcernedApe</td>
      <td>February 26, 2016</td>
      <td>C#, Microsoft XNA/MonoGame</td>
    </tr>
    <tr>
      <td>Starbound</td>
      <td>Chucklefish</td>
      <td>July 22, 2016</td>
      <td>Custom C++ engine, Lua, SDL, OpenGL</td>
    </tr>
    <tr>
      <td>Into the Breach</td>
      <td>Subset Games</td>
      <td>February 27, 2018</td>
      <td>Custom C++ engine, Lua, SDL2</td>
    </tr>
    <tr>
      <td>Factorio</td>
      <td>Wube Software</td>
      <td>August 14, 2020</td>
      <td>Custom C++ engine, Lua</td>
    </tr>
  </tbody>
</table>

<p>But of course, this does not mean that every successful indie game has its own custom game engine. Unity gained a large share of both the mobile and indie game markets. For example, here is a list of games made with Unity<sup id="fnref:unity-games" role="doc-noteref"><a href="#fn:unity-games" class="footnote" rel="footnote">5</a></sup>:</p>

<table>
  <thead>
    <tr>
      <th>Game</th>
      <th>Studio</th>
      <th>Release Date</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>INSIDE</td>
      <td>Playdead</td>
      <td>June 29, 2016</td>
    </tr>
    <tr>
      <td>Hollow Knight</td>
      <td>Team Cherry</td>
      <td>February 24, 2017</td>
    </tr>
    <tr>
      <td>Cuphead</td>
      <td>Studio MDHR</td>
      <td>September 29, 2017</td>
    </tr>
    <tr>
      <td>Subnautica</td>
      <td>Unknown Worlds Entertainment</td>
      <td>January 23, 2018</td>
    </tr>
    <tr>
      <td>The Forest</td>
      <td>Endnight Games</td>
      <td>April 30, 2018 (1.0)</td>
    </tr>
    <tr>
      <td>Outer Wilds</td>
      <td>Mobius Digital</td>
      <td>May 28, 2019</td>
    </tr>
    <tr>
      <td>Untitled Goose Game</td>
      <td>House House</td>
      <td>September 20, 2019</td>
    </tr>
    <tr>
      <td>Risk of Rain 2</td>
      <td>Hopoo Games</td>
      <td>August 11, 2020 (1.0)</td>
    </tr>
    <tr>
      <td>Unpacking</td>
      <td>Witch Beam</td>
      <td>November 2, 2021</td>
    </tr>
    <tr>
      <td>Neon White</td>
      <td>Angel Matrix</td>
      <td>June 16, 2022</td>
    </tr>
  </tbody>
</table>

<p>But we still repeat the “make a game, not a game engine” advice to computer science students who dream of creating an MMO before they have even finished their bachelor’s degree. Yuri Karabatov makes a similar argument in <a href="https://norikitech.com/posts/make-your-game/"><em>Make Your Game, Not an Engine</em></a><sup id="fnref:karabatov" role="doc-noteref"><a href="#fn:karabatov" class="footnote" rel="footnote">6</a></sup>. As Tyler Glaiel puts it in <a href="https://medium.com/geekculture/how-to-make-your-own-game-engine-and-why-ddf0acbc5f3"><em>How to Make Your Own Game Engine (and Why)</em></a><sup id="fnref:glaiel" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>:</p>
<blockquote>
  <p>You think you can just make something better than Unity or Unreal (or Godot or GameMaker) in general. You can’t. It’s possible to make something that is better than these for specific use cases […], but you, as an individual or a tiny team, are not going to compete with these for general purpose stuff. Especially if you have never made your own game engine before.</p>
</blockquote>

<p>I really like the approach Noel Berry describes in <a href="https://noelberry.ca/posts/making_games_in_2025/"><em>Making Video Games in 2025 (Without an Engine)</em></a><sup id="fnref:noelberry" role="doc-noteref"><a href="#fn:noelberry" class="footnote" rel="footnote">8</a></sup>. The engine and editor he created let him quickly build the kinds of 2D games he wants to make, drawing on 20 years of game-development experience. His game engine is written in C# and uses SDL3, with Dear ImGui powering its custom level editors. He works mostly on Linux and uses Native AOT to cross-compile for platforms that do not support VMs.</p>

<p>The goal of the <em>Soup Raiders</em> native port is not to compete with Unity or Unreal, but to build an engine specifically for this game, following the <a href="https://x.com/TylerGlaiel/status/1806476966973116539">game-specific approach T. Glaiel describes here</a><sup id="fnref:glaiel-game-specific" role="doc-noteref"><a href="#fn:glaiel-game-specific" class="footnote" rel="footnote">9</a></sup>. So now that we know what a custom game engine is, we can try to answer the question of why you would make one.</p>

<h2 id="curiosity--learning">Curiosity &amp; Learning</h2>

<p>T. Glaiel (him again) considers learning how game engines work one of the best reasons to build your own<sup id="fnref:glaiel:1" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>. I started making games by writing my own custom 2D game engine in Python with <a href="https://github.com/pygame/pygame">Pygame</a>. That was in the early 2010s, and Unity was not yet where it is now. For example, here is my first game-jam entry, <em><a href="https://teamkwakwa.itch.io/trials">Trials</a></em>:</p>

<p><img src="/images/trials.png" alt="Trials" /></p>

<p>Early in my game-development career, I used Python, Pygame, and Box2D. Later, a colleague from EPFL and I built a C/C++ engine with an embedded Python interpreter that could run my games faster. Finally, for <a href="/gamedev/2015/04/27/ludum-dare.html">Super Splash Fisticuffs</a> (which became <em>Splash Blast Panic</em>), I switched to Unity 5. Apart from a small <a href="/gamedev/2016/10/17/soup-raiders-jailbreak-post-mortem-of-gbjam-5-doing-a-real-homebrew-gameboy-rom.html">Game Boy game</a> that I built in C with <a href="https://gbdk.sourceforge.net/">GBDK</a>, I did not return to creating game engines until I began teaching.</p>

<p>But why should you learn to make a custom game engine when Unity and Unreal already exist? In fact, I would argue that making your own game engine teaches you a lot about how Unity or Unreal (or Godot) work. For students who want to enter the game industry, building an engine offers insight into proprietary in-house engines that we cannot access in the classroom. So instead of just learning the Unity way or the Unreal way, building a custom engine lets you explore the range of possible game-engine architectures.</p>

<p>In the computer graphics module at SAE Institute Geneva, games-programming students build a 3D scene with OpenGL. I explain <a href="/graphics/cpp/2026/01/27/why-i-teach-opengles.html">why I still teach OpenGL instead of a more modern API</a> in a separate post. Through this exercise, they learn the fundamentals of rasterisation-based rendering. For example, when I introduce shadow mapping, I show them an implementation of cascaded shadow maps.</p>

<p><img src="/images/2026/romulan_cascaded.png" alt="cascaded shadow map" /></p>

<p>The three shadow-map cascades appear on the right, while the scene is tinted to show which cascade each pixel uses. Funnily enough, while implementing the first version of <em>Soup Raiders</em> in Unity, I came across this option in the rendering settings:</p>

<p><img src="/images/2026/unity_cascade.png" alt="cascaded unity setting" /></p>

<p>Faced with such a setting, I have three options:</p>
<ol>
  <li>I don’t know what a cascaded shadow map is, so I don’t touch anything.</li>
  <li>I don’t know what a cascaded shadow map is, so I search online (or ask an LLM), get a sense of what it is, and then change this setting and try to guess how it impacts my performance.</li>
  <li>I implemented cascaded shadow mapping in an OpenGL sample by hand, so I know exactly what it means and I choose the correct setting accordingly.</li>
</ol>

<p>For <em>Soup Raiders</em>, I chose a single shadow map with no cascades and applied filtering while drawing the meshes and sprites (I did not really care about the quality of the shadows far away, as this is not an open-world game). A different game and context require different reasoning, and therefore a different decision.</p>

<p>Another example comes from <em>Beach Slap</em>. While I was optimising the game for mobile (I have an old iPad Air 2 from 2014 for that), profiling showed me that a lot of time was spent on occlusion culling, a technique that discards draw calls for objects hidden behind other objects. It’s a very useful technique, for example in games with a lot of geometry that is often hidden. But look at my game:</p>

<p><img src="/images/2026/gamescom26_SS%20(6).jpg" alt="beach slap" /></p>

<p>Because no objects occlude one another, the game does not benefit from occlusion culling. Having implemented the technique for my course, I understand its cost and can confidently disable it.</p>

<p>This experience is not unique. Mathieu Ropert, who worked on Paradox’s custom C++ engine, described a consulting job using UE5<sup id="fnref:ropert" role="doc-noteref"><a href="#fn:ropert" class="footnote" rel="footnote">10</a></sup>:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Wrapped up a consulting job on UE5 with no prior engine experience outside hobby interest.<br />Within hours I had significant performance improvements and found architectural design flaws.<br />Making CV hard requirements on specific engines is deeply unserious about software engineering.</p>&mdash; Mathieu Ropert (@MatRopert) <a href="https://x.com/MatRopert/status/2049803204385226827?ref_src=twsrc%5Etfw">April 30, 2026</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<h2 id="independence">Independence</h2>

<p>At the end of 2024, I went through a <strong>degoogling</strong> phase: I moved most of my files and photos from Google to Proton Drive, cancelled Xbox Game Pass, and left Spotify for Qobuz. I may eventually return to simply buying music or listening on YouTube. Big tech is everywhere, and I still want to get rid of GitHub, Microsoft Windows and Google Calendar, but their convenience is really hard to give up.</p>

<p>Indie developers may be independent from publishers, but they still depend on platform holders (Nintendo, Sony, Microsoft, Valve, Apple) and engine providers (Unity, Unreal, etc.). What about version control? Do you push your code to GitHub, which Microsoft owns? Do you share assets through Google Drive? And which operating system do you use? Many game developers use Windows because important tools and middleware either work only there or support it best; others use macOS.</p>

<p>In <a href="https://www.gamedeveloper.com/programming/real-reasons-not-to-build-custom-game-engines-in-2024"><em>Real Reasons (Not) to Build Custom Game Engines in 2024</em></a><sup id="fnref:kiselev" role="doc-noteref"><a href="#fn:kiselev" class="footnote" rel="footnote">11</a></sup>, Maxim Kiselev identifies three forms of dependency created by third-party engines.</p>

<h3 id="technical-independence">Technical Independence</h3>

<p>On technical dependency, M. Kiselev writes<sup id="fnref:kiselev:1" role="doc-noteref"><a href="#fn:kiselev" class="footnote" rel="footnote">11</a></sup>:</p>
<blockquote>
  <p>If you’re using a third-party engine and encounter a bug or missing feature that impacts your game, you often have no choice but to wait for the engine’s developers to fix it.</p>
</blockquote>

<p>This dependency exists with both proprietary and open-source engines, although with an open-source engine you can inspect the code and fix problems yourself. In my view, a third-party engine locks developers into a particular way of implementing their game. That is acceptable for most projects but can become restrictive in unusual cases.</p>

<p>Unity’s transition from OpenGL and DX11 to Vulkan and DX12 illustrates this problem: its modern rendering hardware interface still had to emulate the older model, limiting its potential performance<sup id="fnref:aaltonen-rhi" role="doc-noteref"><a href="#fn:aaltonen-rhi" class="footnote" rel="footnote">12</a></sup>.</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Vulkan and DX12 was initially slower in Unreal/Unity because their RHIs didn&#39;t match the retained mode grouping of Vulkan/DX12. Developers had to add hash maps under the RHI to map dynamic API to retained PSO and descriptor set APIs. Which added complexity and CPU cost a lot.</p>&mdash; Sebastian Aaltonen (@SebAaltonen) <a href="https://x.com/SebAaltonen/status/1880889196363251790?ref_src=twsrc%5Etfw">January 19, 2025</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<p>Of course, Unity could not simply break RHI compatibility between versions. However, a new custom engine designed specifically around Vulkan or DX12 could outperform Unity at the RHI level by matching those APIs more closely. Sebastian Aaltonen actually made a prototype during Unity Hackweek 2019<sup id="fnref:aaltonen-hackweek" role="doc-noteref"><a href="#fn:aaltonen-hackweek" class="footnote" rel="footnote">13</a></sup>:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">When I was working at Unity, we did a simple (4 day) prototype at Hackweek 2019 with fully persistent data and descriptor sets. The performance was super good:<a href="https://t.co/NPyXHP3NiM">https://t.co/NPyXHP3NiM</a><br /><br />This is how Vulkan was designed to be used. When you try to emulate DX11 the perf sucks.</p>&mdash; Sebastian Aaltonen (@SebAaltonen) <a href="https://x.com/SebAaltonen/status/1532988130290241536?ref_src=twsrc%5Etfw">June 4, 2022</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<p>With a custom game engine, you have full control over your development process and tech stack.</p>

<h3 id="legal-independence">Legal Independence</h3>

<p>You do not own a third-party engine; you use it under a licence that the provider may revoke if it determines that you have violated its terms of service. It has happened several times to developers using Unity:</p>
<ul>
  <li><a href="https://techcrunch.com/2019/01/11/improbable-urges-unity-to-unsuspend-their-game-engine-license-or-clarify-terms/">Improbable/SpatialOS in 2019</a><sup id="fnref:improbable" role="doc-noteref"><a href="#fn:improbable" class="footnote" rel="footnote">14</a></sup></li>
  <li><a href="https://www.reddit.com/r/unity/comments/1ur9j7m/was_gonna_launch_my_games_demo_tomorrow_but_am/">A student who got their licence revoked the day before releasing their game demo in July 2026</a><sup id="fnref:unity-revoke-2026" role="doc-noteref"><a href="#fn:unity-revoke-2026" class="footnote" rel="footnote">15</a></sup></li>
  <li><a href="https://discussions.unity.com/t/my-personal-license-has-been-revoked/942826">Mike Thorn getting his licence revoked - March 2024</a><sup id="fnref:unity-revoke-thorn" role="doc-noteref"><a href="#fn:unity-revoke-thorn" class="footnote" rel="footnote">16</a></sup></li>
  <li><a href="https://discussions.unity.com/t/license-revoked-editor-closed-work-lost/926284">License Revoked, Editor Closed, Work Lost - Aug 2023</a><sup id="fnref:unity-revoke-2023" role="doc-noteref"><a href="#fn:unity-revoke-2023" class="footnote" rel="footnote">17</a></sup></li>
</ul>

<p>Using a free and open-source engine such as Godot avoids this particular risk. It is released under the permissive <a href="https://godotengine.org/license/">MIT licence</a><sup id="fnref:godot-license" role="doc-noteref"><a href="#fn:godot-license" class="footnote" rel="footnote">18</a></sup>. Unreal is not open source, but it does ship its full source code, which gives a degree of technical independence that Unity’s licence activation system doesn’t necessarily provide.</p>

<p><img src="/images/2026/RenderWare_(2002).png" alt="renderware" /></p>

<p>In the PS2 era, Criterion Games created RenderWare, a cross-platform 3D game engine for PC, PlayStation 2, Xbox and GameCube. The big deal was that each of those consoles had a different CPU architecture (PS2 was MIPS, GameCube was PowerPC, Xbox was x86 like the PC). So a game engine that let you target all those platforms was the Unreal Engine of the PS2 generation. It powered many popular games across different studios<sup id="fnref:renderware" role="doc-noteref"><a href="#fn:renderware" class="footnote" rel="footnote">19</a></sup>, including:</p>

<table>
  <thead>
    <tr>
      <th>Game</th>
      <th>Studio</th>
      <th>Release Date</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Grand Theft Auto III</td>
      <td>DMA Design</td>
      <td>October 23, 2001</td>
    </tr>
    <tr>
      <td>Tony Hawk’s Pro Skater 3</td>
      <td>Neversoft</td>
      <td>October 28, 2001</td>
    </tr>
    <tr>
      <td>Grand Theft Auto: Vice City</td>
      <td>Rockstar North</td>
      <td>October 29, 2002</td>
    </tr>
    <tr>
      <td>Manhunt</td>
      <td>Rockstar North</td>
      <td>November 18, 2003</td>
    </tr>
    <tr>
      <td>Sonic Heroes</td>
      <td>Sonic Team USA</td>
      <td>December 30, 2003</td>
    </tr>
    <tr>
      <td>Burnout 3: Takedown</td>
      <td>Criterion Games</td>
      <td>September 7, 2004</td>
    </tr>
    <tr>
      <td>Grand Theft Auto: San Andreas</td>
      <td>Rockstar North</td>
      <td>October 26, 2004</td>
    </tr>
    <tr>
      <td>The Warriors</td>
      <td>Rockstar Toronto</td>
      <td>October 17, 2005</td>
    </tr>
    <tr>
      <td>Black</td>
      <td>Criterion Games</td>
      <td>February 24, 2006</td>
    </tr>
    <tr>
      <td>Bully</td>
      <td>Rockstar Vancouver</td>
      <td>October 17, 2006</td>
    </tr>
  </tbody>
</table>

<p>However, after EA acquired Criterion in 2004, RenderWare was gradually withdrawn as commercial middleware and had effectively disappeared from the market by 2007 (see <a href="https://www.gamespot.com/articles/ea-assimilates-criterion/">EA’s acquisition of Criterion</a><sup id="fnref:ea-criterion" role="doc-noteref"><a href="#fn:ea-criterion" class="footnote" rel="footnote">20</a></sup>). According to <a href="https://www.gamedeveloper.com/business/bing-there-done-that-ea-s-cco-talks-everything">EA’s William “Bing” Gordon</a><sup id="fnref:bing-gordon" role="doc-noteref"><a href="#fn:bing-gordon" class="footnote" rel="footnote">21</a></sup>:</p>
<blockquote>
  <p>Renderware didn’t get the next-gen parts that we needed.</p>
</blockquote>

<p><img src="/images/2026/Our_Machinery.webp" alt="the machinery" /></p>

<p>In July 2021, Our Machinery announced a new game engine, “The Machinery”, available by subscription for US$50 per year for independent developers and US$450 per year for industry professionals. The paid subscriptions included every feature, technical support, and source-code access; a free version was also available without the source code. They also published a <a href="https://ruby0x1.github.io/machinery_blog_archive/">series of blog posts</a><sup id="fnref:machinery-blog" role="doc-noteref"><a href="#fn:machinery-blog" class="footnote" rel="footnote">22</a></sup> explaining the engine’s development process (the blog goes back to 2017).</p>

<p>However, in August 2022, licensees were abruptly told that development had ended and that they must delete all copies of The Machinery’s source code and binaries, <a href="https://www.nme.com/news/gaming-news/the-machinery-game-engine-cancelled-as-developers-told-to-delete-all-source-code-3283633">as reported by <em>NME</em></a><sup id="fnref:machinery-nme" role="doc-noteref"><a href="#fn:machinery-nme" class="footnote" rel="footnote">23</a></sup>. Ryan Fleury discussed the consequences in <a href="https://www.dgtlgrove.com/p/ships-icebergs-game-engines"><em>Ships, Icebergs, Game Engines</em></a><sup id="fnref:fleury" role="doc-noteref"><a href="#fn:fleury" class="footnote" rel="footnote">24</a></sup>:</p>
<blockquote>
  <p>What becomes of work authored for a project that used The Machinery as its engine? If a game developer is lucky, their investment in The Machinery was kept to a minimum, and their content is largely decoupled from The Machinery technology. That, though, would be a very lucky (or careful) user of The Machinery—what is more likely, for that game developer, is that a hefty majority of the authored content—levels, gameplay code, assets—is, in fact, quite coupled to The Machinery technology, and so the value of that game developer’s content has dropped dramatically close to zero.</p>
</blockquote>

<p><img src="/images/2026/unity_runtimefee.png" alt="unity" /></p>

<p>In September 2023, Unity <a href="https://discussions.unity.com/t/unity-plan-pricing-and-packaging-updates/927079">announced a runtime fee based on game installs</a><sup id="fnref:unity-runtime-fee" role="doc-noteref"><a href="#fn:unity-runtime-fee" class="footnote" rel="footnote">25</a></sup>, scheduled to take effect on January 1, 2024. After an immediate backlash, Unity revised the policy on September 22 with <a href="https://unity.com/blog/an-open-letter-to-our-community">Marc Whitten’s open letter</a><sup id="fnref:whitten-letter" role="doc-noteref"><a href="#fn:whitten-letter" class="footnote" rel="footnote">26</a></sup> but did not cancel the fee entirely until September 2024, when new CEO Matt Bromberg <a href="https://unity.com/blog/unity-is-canceling-the-runtime-fee">announced its cancellation</a><sup id="fnref:runtime-fee-cancelled" role="doc-noteref"><a href="#fn:runtime-fee-cancelled" class="footnote" rel="footnote">27</a></sup>.</p>

<p>The controversy had a lasting impact on the indie game-development community, prompting many developers to adopt Godot or build their own engines:</p>
<ul>
  <li><a href="https://www.elegacorp.com/blog/kalling-kingdom-engine-port-and-youtube-hiatus.html">Kalling Kingdom Engine Port and YouTube Hiatus</a><sup id="fnref:kalling-kingdom" role="doc-noteref"><a href="#fn:kalling-kingdom" class="footnote" rel="footnote">28</a></sup>. Elega Corporation on leaving Unity after the Runtime Fee controversy and building a proprietary game engine in Rust.</li>
  <li><a href="https://spielmannspiel.com/blog/transitioning_from_unity_to_godot">Godot Private Asset Library — Transitioning from Unity to Godot</a><sup id="fnref:spielmannspiel" role="doc-noteref"><a href="#fn:spielmannspiel" class="footnote" rel="footnote">29</a></sup>. A developer’s experience gradually moving new projects from Unity to Godot following the 2023 announcement.</li>
  <li><a href="https://forum.godotengine.org/t/from-unity-to-godot-my-journey-with-my-no-escape-game-and-open-source-projects/126568">From Unity to Godot: My Journey with “No Escape?!”</a><sup id="fnref:no-escape-godot" role="doc-noteref"><a href="#fn:no-escape-godot" class="footnote" rel="footnote">30</a></sup>. A developer recounts rebuilding an existing Unity game from scratch in Godot 4.</li>
  <li><a href="https://itch.io/jam/scrapyard-jam">Scrapyard Jam</a><sup id="fnref:scrapyard-jam" role="doc-noteref"><a href="#fn:scrapyard-jam" class="footnote" rel="footnote">31</a></sup>. A game jam created in the aftermath of Unity’s pricing controversy, encouraging developers to build their own engines or experiment with alternatives.</li>
</ul>

<p>One possible consequence was visible in 2026, when Godot became the most-used engine among submissions to the Game Maker’s Toolkit Game Jam<sup id="fnref:gmtk-jam" role="doc-noteref"><a href="#fn:gmtk-jam" class="footnote" rel="footnote">32</a></sup>:</p>

<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:uwucgubhwifmvz4kcpuzjaxf/app.bsky.feed.post/3mrmx5mi3mk24" data-bluesky-cid="bafyreiepprqcjplsxbvrrqkvycr2geat2rq4i3lds4kjccmoyf7ojlttle" data-bluesky-embed-color-mode="system"><p lang="en">Well, it finally happened! After 9 years of Unity dominance, Godot was the most used game engine for GMTK Game Jam 2026 submissions.

🤖 Godot - 47%
🎮 Unity - 34%
🛠️GameMaker - 5%
🚀 Unreal Engine - 3%
✨ Other - 11%<br /><br /><a href="https://bsky.app/profile/did:plc:uwucgubhwifmvz4kcpuzjaxf/post/3mrmx5mi3mk24?ref_src=embed">[image or embed]</a></p>&mdash; Game Maker&#x27;s Toolkit (<a href="https://bsky.app/profile/did:plc:uwucgubhwifmvz4kcpuzjaxf?ref_src=embed">@gamemakerstoolkit.com</a>) <a href="https://bsky.app/profile/did:plc:uwucgubhwifmvz4kcpuzjaxf/post/3mrmx5mi3mk24?ref_src=embed">July 27, 2026 at 2:58 PM</a></blockquote>
<script async="" src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>

<h3 id="financial-independence">Financial Independence</h3>

<p>I remember a talk by Rami Ismail at Reboot Develop 2017 where he spoke about this triangle:</p>

<p><img src="/images/2026/sr/motivation_money_knowledge.svg" alt="Motivation, money and knowledge triangle" /></p>

<p>His point was that motivation is the most important of the three, because:</p>
<ul>
  <li>Motivation + Knowledge + No Money = You can work part-time on your game</li>
  <li>Motivation + Money + No Knowledge = You can pay people to make your game</li>
  <li>Money + Knowledge + No Motivation = You do not make a game at all</li>
</ul>

<p>In <a href="https://lisyarus.github.io/blog/posts/so-you-want-to-make-a-game-engine.html"><em>So, You Want to Make a Game Engine</em></a><sup id="fnref:lisyarus" role="doc-noteref"><a href="#fn:lisyarus" class="footnote" rel="footnote">33</a></sup>, Nikita Lisitsa writes:</p>
<blockquote>
  <p>Why not make your own game engine? […] You might find yourself too deep into engine development instead of making an actual game, which can easily lead to a burnout.</p>
</blockquote>

<p>This brings us back to the students mentioned earlier who want to build an engine for their ambitious MMO. His warning is simple<sup id="fnref:lisyarus:1" role="doc-noteref"><a href="#fn:lisyarus" class="footnote" rel="footnote">33</a></sup>:</p>
<blockquote>
  <p>It is hard and time-consuming.</p>
</blockquote>

<p>But imagine that we have plenty of motivation (or a well-scoped game engine) and the necessary knowledge (or the willingness to learn). If we have no money at all (or no willingness to give away a single penny when the game eventually comes out), then making a custom game engine could be financially better than using a third-party engine. T. Glaiel offers a more cautious view of whether building your own engine saves money<sup id="fnref:glaiel:2" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>:</p>
<blockquote>
  <p>You most likely won’t (save money). Making an engine takes time, and time=money. […] Using your own engine won’t make you sell more copies of your game automatically. And while you <em>can</em> save time in the long run, this usually means having your engine be good enough to carry you across multiple projects, while also providing you with significant workflow improvements compared to commercial engines. It’s not easy to get this right, and you definitely won’t if its your first try at it (and extremely unlikely if you’re doing 3D instead of 2D).</p>
</blockquote>

<p>For context, the following prices are an August 2026 snapshot; pricing and licensing terms may change.</p>

<ul>
  <li><strong>Unity:</strong> Free with Unity Personal for up to <strong>US$200,000</strong> in gross revenue and/or funding over the previous 12 months. From <strong>US$200,001 to US$24,999,999</strong>, Unity Pro is mandatory and costs <strong>US$2,310/year/seat</strong> or <strong>US$210/month/seat</strong>. At <strong>US$25M or more</strong>, Unity Enterprise with custom pricing is mandatory. There is no revenue share or royalty.<sup id="fnref:unity-pricing" role="doc-noteref"><a href="#fn:unity-pricing" class="footnote" rel="footnote">34</a></sup></li>
  <li><strong>Unreal Engine 5:</strong> Free until a game earns <strong>US$1M in lifetime gross revenue</strong>, then Epic charges a <strong>5% royalty</strong>. Revenue earned through the Epic Games Store is royalty-free.<sup id="fnref:unreal-license" role="doc-noteref"><a href="#fn:unreal-license" class="footnote" rel="footnote">35</a></sup></li>
  <li><strong>GameMaker:</strong> Free for non-commercial use and a <strong>US$99.99 one-time fee</strong> for commercial games. Console exports require Enterprise, which costs <strong>US$79.99/month</strong> or <strong>US$799.99/year</strong>.<sup id="fnref:gamemaker-pricing" role="doc-noteref"><a href="#fn:gamemaker-pricing" class="footnote" rel="footnote">36</a></sup></li>
  <li><strong>CRYENGINE:</strong> Free upfront, with a <strong>5% royalty</strong> after the first <strong>US$5,000/year/project</strong>. Full source code is included.<sup id="fnref:cryengine-license" role="doc-noteref"><a href="#fn:cryengine-license" class="footnote" rel="footnote">37</a></sup></li>
  <li><strong>Godot:</strong> Completely free under the MIT licence, with no royalties, subscriptions, or revenue limits.<sup id="fnref:godot-license:1" role="doc-noteref"><a href="#fn:godot-license" class="footnote" rel="footnote">18</a></sup></li>
</ul>

<p>One note from M. Kiselev to finish this section (taking into account that time is money)<sup id="fnref:kiselev:2" role="doc-noteref"><a href="#fn:kiselev" class="footnote" rel="footnote">11</a></sup>:</p>

<blockquote>
  <p>It’s easy to think that building your own engine will save time and money, but that’s rarely the case. To develop a game using an engine like Unity or Unreal Engine 5, you need to invest time learning them. While this can take a while, it’s still a much smaller time investment compared to building your own engine from scratch and learning the underlying technology needed for it.</p>
</blockquote>

<h2 id="low-level-control">Low-Level Control</h2>

<p><a href="https://handmade.network/manifesto"><em>The Handmade Manifesto</em></a><sup id="fnref:handmade-manifesto" role="doc-noteref"><a href="#fn:handmade-manifesto" class="footnote" rel="footnote">38</a></sup> argues that programmers should understand how computers and their technology stacks work instead of relying on layers of opaque frameworks and dependencies. One of its stated values is “We like to reinvent the wheel.” In a way, creating a custom engine means reimplementing solutions to problems that have already been solved, but tailoring them to the specific needs of our games.</p>

<p>Building some low-level systems yourself does not mean rebuilding everything from scratch. <a href="https://noelberry.ca/posts/making_games_in_2025/">N. Berry</a><sup id="fnref:noelberry:1" role="doc-noteref"><a href="#fn:noelberry" class="footnote" rel="footnote">8</a></sup> delegates platform, input, and rendering work to SDL3 and uses FMOD for audio. N. Lisitsa also recommends using libraries like SDL, GLFW, SFML or OpenAL<sup id="fnref:lisyarus:2" role="doc-noteref"><a href="#fn:lisyarus" class="footnote" rel="footnote">33</a></sup>.</p>

<p>In their <a href="https://www.reddit.com/r/factorio/comments/in5d3i/developer_technicaloriented_ama/">Reddit AMA</a><sup id="fnref:factorio-ama" role="doc-noteref"><a href="#fn:factorio-ama" class="footnote" rel="footnote">39</a></sup>, the <em>Factorio</em> developers argued that standard engines were unsuitable for their game logic:</p>
<blockquote>
  <p>‘standard engines’ are so restrictive in what can be done and leave so much performance sitting there that I wouldn’t ever consider using one for something like Factorio.</p>
</blockquote>

<p>A custom engine can also dramatically affect compilation times and build sizes. Developers using Unreal or Unity, particularly IL2CPP builds, may be familiar with these problems<sup id="fnref:ashkan" role="doc-noteref"><a href="#fn:ashkan" class="footnote" rel="footnote">40</a></sup>:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">We upgraded a project from Unity 2022.3 to 6.3 and Unity is still the slow , cluncky thing it was before. The builds are as slow. compiling IL2CPP takes 1.5 hours on the machien that compiles UE5 source in 40 mins. It is hard to go back to Unity.</p>&mdash; Ashkan Saeidi Mazdeh (@Ashkan_GC) <a href="https://x.com/Ashkan_GC/status/2019446029175693724?ref_src=twsrc%5Etfw">February 5, 2026</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<p>The problem became so severe that I dedicated a separate computer to building <em>Beach Slap</em> for every platform at the end of each development day, using a single <code class="language-plaintext highlighter-rouge">.bat</code> script.</p>

<p>Dan Baker argues in <a href="https://www.gamesindustry.biz/a-case-for-building-your-own-tech-opinion"><em>A Case for Building Your Own Tech</em></a> that owning a custom engine can also reduce compilation times<sup id="fnref:dan-baker" role="doc-noteref"><a href="#fn:dan-baker" class="footnote" rel="footnote">41</a></sup>:</p>
<blockquote>
  <p>For example, our Nitrous Engine compiles very quickly. The engine compiles in less than 30 seconds. The engine code is clean and modular, so we can implement features in the engine or fix a bug in around a tenth of the time it would take in an off-the-shelf engine. Not only can we do a clean build of Ara in less than 2 minutes, but our debug version of the Ara and Nitrous is so fast that we can still run at 30 fps in full debug.</p>
</blockquote>

<p>Sébastien de Graffenried’s custom engine also produces tiny builds compared with default Unity or Unreal builds<sup id="fnref:degraff" role="doc-noteref"><a href="#fn:degraff" class="footnote" rel="footnote">42</a></sup>:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">A nice thing about using your own C engine are the small build sizes.<br />And I didn&#39;t even try that hard to reduce the size! <a href="https://t.co/d85Po9ztUU">https://t.co/d85Po9ztUU</a> <a href="https://t.co/OO2QncB2lk">pic.twitter.com/OO2QncB2lk</a></p>&mdash; Sébastien de Graffenried (@seb_degraff) <a href="https://x.com/seb_degraff/status/1889649086560600088?ref_src=twsrc%5Etfw">February 12, 2025</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<p>Jonathan Blow also demonstrates the fast compilation times achieved by the compiler for his own programming language<sup id="fnref:blow" role="doc-noteref"><a href="#fn:blow" class="footnote" rel="footnote">43</a></sup>:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I did some work on the compiler that had the side effect of speeding things up! Here are the current compile speeds on my desktop.<br /><br />Current compiler size: 55,273 lines. <a href="https://t.co/HmYKzZ3z3q">pic.twitter.com/HmYKzZ3z3q</a></p>&mdash; Jonathan Blow (@Jonathan_Blow) <a href="https://x.com/Jonathan_Blow/status/1353939518588502018?ref_src=twsrc%5Etfw">January 26, 2021</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<p>These three examples show that custom game engines can improve not only your game’s performance but also the performance of your workflow. As T. Glaiel puts it<sup id="fnref:glaiel:3" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>:</p>
<blockquote>
  <p>it’s nice to just have your own tech, […]. It’s nice to be able to actually debug the internals of your game if something goes wrong.</p>
</blockquote>

<p>However, he also warns of the risks<sup id="fnref:glaiel:4" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>:</p>
<blockquote>
  <p>But it can also suck if you made a couple of bad design choices and everything falls apart entirely, and there’s no resources online to help you. You have full control and full responsibility, and all the pros and cons that come with that.</p>
</blockquote>

<p>A custom engine can improve performance and workflow, but poor design choices can still leave you with a C++ project that takes ages to compile. Optimising its compilation times is then your responsibility.</p>

<h2 id="unique-game-mechanics">Unique Game Mechanics</h2>

<p>Unique or innovative game ideas sometimes require custom technology and the freedom that a custom engine provides. Although general-purpose engines can often support such ideas, working around their constraints may be difficult. For example, in Unity’s GameObject model, creating many objects with separate <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> scripts can make the overhead of their <code class="language-plaintext highlighter-rouge">Update()</code> calls prohibitively expensive. A common workaround is to use a single manager <code class="language-plaintext highlighter-rouge">MonoBehaviour</code> to control all those entities. Unity’s DOTS architecture addresses this limitation, although it introduces a different set of trade-offs.</p>

<p>T. Glaiel uses <em>Noita</em> as an example<sup id="fnref:glaiel:5" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>:</p>
<blockquote>
  <p>A great example of a justified use of a custom game engine is the well-known Noita. The game has a truly unique concept that likely wouldn’t have worked with any existing proprietary engine, a uniqueness captured in the name of its engine, “Falling Everything.” Yet, Noita doesn’t rely on advanced graphical effects, it’s single-player, and it was built exclusively for one platform — PC.</p>
</blockquote>

<p><img src="/images/2026/noita-feel-alive.jpg" alt="noita" /></p>

<p><em>Noita</em> is a roguelite by Nolla Games in which every pixel is simulated. In his <a href="https://www.gdcvault.com/play/1025695/Exploring-the-Tech-and-Design">GDC talk</a><sup id="fnref:purho" role="doc-noteref"><a href="#fn:purho" class="footnote" rel="footnote">44</a></sup>, Petri Purho explains that the physics system began with experiments in QuickBASIC: each pixel checked whether there was empty space beneath it and fell accordingly. He developed the system further in <em>Bloody Zombies</em>, eventually adding rigid bodies. <em>Noita</em> then grew out of this existing C++ physics engine.</p>

<p><img src="/images/2026/Miegakure.png" alt="Miegakure" /></p>

<p>For <em>Miegakure</em>, an unusual 4D puzzle game, Marc ten Bosch had to reimplement many fundamental systems. As he explained to <a href="https://www.vice.com/en/article/what-happened-to-miegakure-the-game-that-promised-the-4th-dimension/"><em>Vice</em></a><sup id="fnref:miegakure" role="doc-noteref"><a href="#fn:miegakure" class="footnote" rel="footnote">45</a></sup>:</p>
<blockquote>
  <p>People have very much figured out how to build 2D and 3D games: proof of that is that game engines like Unity exist. In 4D, none of that knowledge exists, and it is more difficult to come up with because you can’t fully visualize it and the math is more complicated. So anything a game developer would take for granted in a 3D game—like collision detection, lighting, sound, modeling props—is a new, difficult problem that has to be figured out. It takes a long time to solve these issues, but it is also extremely fun for me!</p>
</blockquote>

<p><img src="/images/2026/teardown.webp" alt="teardown" /></p>

<p>Similarly, Dennis Gustafsson <a href="https://www.gamedeveloper.com/design/combining-bombastic-heists-with-a-fully-destructible-voxel-world-in-i-teardown-i-">explained</a><sup id="fnref:teardown" role="doc-noteref"><a href="#fn:teardown" class="footnote" rel="footnote">46</a></sup> that <em>Teardown</em> grew out of his experiments with voxel and destruction technology:</p>
<blockquote>
  <p>The engine and tool pipeline are created from scratch in C++ for this game specifically, […], but since the game relies on novel technology I don’t think it would have been possible using an off-the-shelf engine.</p>
</blockquote>

<h2 id="specialisation">Specialisation</h2>

<p>T. Glaiel lists “I can do better” (than other game engines) as a bad reason for making a game engine. In his own words<sup id="fnref:glaiel:6" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>:</p>
<blockquote>
  <p>You think you can just make something better than Unity or Unreal (or Godot or GameMaker) in general. You can’t. It’s possible to make something that is better than these for specific use cases […], but you, as an individual or a tiny team, are not going to compete with these for general purpose stuff.</p>
</blockquote>

<p>For a custom engine to make sense, it must be specialised for the game, its artistic direction, or the platform’s technical constraints.</p>

<p>Because N. Berry’s artist uses <a href="https://www.aseprite.org/">Aseprite</a>, he added direct support for its file format to the engine. He also builds level editors tailored to each game and the team’s workflow<sup id="fnref:noelberry:2" role="doc-noteref"><a href="#fn:noelberry" class="footnote" rel="footnote">8</a></sup>. He can do that because he does not need most of the features of a general-purpose game engine.</p>

<p><img src="/images/2026/Animal_Well.jpg" alt="animal well" /></p>

<p>According to <a href="https://www.gamedeveloper.com/design/why-animal-well-s-home-brewed-engine-was-key-to-its-success">Sam Machkovech’s article on <em>Animal Well</em></a><sup id="fnref:animal-well" role="doc-noteref"><a href="#fn:animal-well" class="footnote" rel="footnote">47</a></sup>, Billy Basso built a custom engine partly because his experience with bloated, sluggish off-the-shelf engines in mobile development had left a “sour taste.” His engine allowed him to combine a rim-lighting shader, which highlights silhouettes in otherwise dark scenes, with a full-screen Navier–Stokes simulation for smoke and water. B. Basso recommends an engine-first approach to game development:</p>
<blockquote>
  <p>[…] That opens up a lot of unique creative avenues. Once I get a cool system working, then I can play around with it and see what it allows for.</p>
</blockquote>

<p>N. Lisitsa makes the point that a custom game engine does not necessarily need complex material graphs, global illumination, advanced 3D physics, multiplayer infrastructure or industrial-scale editor tooling<sup id="fnref:lisyarus:3" role="doc-noteref"><a href="#fn:lisyarus" class="footnote" rel="footnote">33</a></sup>. For T. Glaiel, specialisation is almost a requirement for a custom engine. He adds<sup id="fnref:glaiel:7" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>:</p>
<blockquote>
  <p>[…] you can make your asset pipeline / level editor / whatever way smoother to use when considering your specific use cases instead of needing it to be general purpose.</p>
</blockquote>

<p>This is one of the central ideas in <a href="https://vimeo.com/644068002">Andreas Fredriksson’s <em>Context Is Everything</em></a><sup id="fnref:fredriksson" role="doc-noteref"><a href="#fn:fredriksson" class="footnote" rel="footnote">48</a></sup> talk at Handmade Seattle 2021: once you understand your specific context, a custom engine’s low-level control lets you optimise as deeply as necessary.</p>

<p>Specialisation can also provide long-term benefits: a custom engine may support the next game or even an entire series. This was the case for the <a href="https://witcher-games.fandom.com/wiki/REDengine">RED Engine of CD Projekt Red</a><sup id="fnref:redengine" role="doc-noteref"><a href="#fn:redengine" class="footnote" rel="footnote">49</a></sup>:</p>
<ul>
  <li>(2007) Aurora Engine: CDPR licensed and heavily modified BioWare’s engine to create <em>The Witcher</em>.</li>
  <li>(2011) REDengine 1: CDPR introduced its own proprietary engine with <em>The Witcher 2</em>.</li>
  <li>(2012) REDengine 2: The engine evolved to support consoles and multiplatform development.</li>
  <li>(2015) REDengine 3: REDengine was redesigned for the large open world of <em>The Witcher 3</em>.</li>
  <li>(2020) REDengine 4: The engine evolved again for the dense urban world of <em>Cyberpunk 2077</em>.</li>
  <li>(2023) REDengine 4 (cont.): <em>Phantom Liberty</em> became the final major release built with REDengine.</li>
</ul>

<p>CD Projekt Red has since switched to Unreal Engine for <em>The Witcher IV</em>, but the evolution of REDengine still shows how a custom engine’s foundations can support several generations of games.</p>

<h2 id="multiplatform">Multiplatform</h2>

<p>When releasing a game on multiple platforms, Unity feels like magic. Even if the result is not perfectly optimised, porting a Unity game to a console can take far less time than implementing the port by hand in a custom engine, especially when the platform is unfamiliar. N. Lisitsa describes the cost of multiplatform support in a custom engine<sup id="fnref:lisyarus:4" role="doc-noteref"><a href="#fn:lisyarus" class="footnote" rel="footnote">33</a></sup>:</p>
<blockquote>
  <p>Supporting multiple platforms adds more pain, since different platforms have different executable file formats, dynamic library formats, etc. You’ll have to compile several different versions of your game, one for each platform.</p>
</blockquote>

<p>Compare that with the “time to triangle” (the time required to render an engine’s first triangle on a console) that <a href="https://www.youtube.com/watch?v=xHXrBnipHyA">Mark Cerny discussed for several PlayStation generations at Gamelab 2013</a><sup id="fnref:cerny" role="doc-noteref"><a href="#fn:cerny" class="footnote" rel="footnote">50</a></sup>:</p>

<table>
  <thead>
    <tr>
      <th>Platform</th>
      <th style="text-align: right">“Time to triangle”</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>PlayStation</td>
      <td style="text-align: right"><strong>1–2 months</strong></td>
    </tr>
    <tr>
      <td>PlayStation 2</td>
      <td style="text-align: right"><strong>3–6 months</strong></td>
    </tr>
    <tr>
      <td><strong>PlayStation 3</strong></td>
      <td style="text-align: right"><strong>6–12 months</strong></td>
    </tr>
    <tr>
      <td>PlayStation 4</td>
      <td style="text-align: right"><strong>1–2 months</strong></td>
    </tr>
  </tbody>
</table>

<p>Custom engines have a clearer advantage on niche and retro platforms. At the time of writing, Unity 6.4 officially supports these consoles<sup id="fnref:unity-consoles" role="doc-noteref"><a href="#fn:unity-consoles" class="footnote" rel="footnote">51</a></sup>:</p>

<ul>
  <li>PlayStation 4 (including PlayStation VR)</li>
  <li>PlayStation 5 (including PlayStation VR2)</li>
  <li>Xbox One</li>
  <li>Xbox Series X</li>
  <li>Xbox Series S</li>
  <li>Nintendo Switch</li>
  <li>Nintendo Switch 2</li>
</ul>

<p>These are all recent console generations. Targeting older or more unusual hardware often requires a custom engine or specialised tooling. On the <a href="https://play.date/">Playdate</a>, you are required to program in C or Lua with its SDK. The <a href="https://www.arduboy.com/">Arduboy</a> similarly uses C++ through the Arduino ecosystem, under severe hardware constraints.</p>

<p>For GBJam 2016, I made a Game Boy game in C using GBDK, a process I described in <a href="/gamedev/2016/10/17/soup-raiders-jailbreak-post-mortem-of-gbjam-5-doing-a-real-homebrew-gameboy-rom.html">a separate post</a>. <a href="https://www.gbstudio.dev/">GB Studio</a> did not exist at the time, so I had to endure the painful process of programming for a system without a debugger (the screen would just be blank at boot when there was a problem).</p>

<p>Similarly, Elias Daler is developing a PS1 game with his own custom engine<sup id="fnref:elias-daler" role="doc-noteref"><a href="#fn:elias-daler" class="footnote" rel="footnote">52</a></sup>:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I started making stuff for PS1 exactly one year ago.<br /><br />It&#39;s been a wild journey since then. I&#39;m thinking of writing a big article about it (and later a video, maybe).<br /><br />And this is just the beginning of my PS1 journey! <br />Now that I have a pretty good engine, I can make a real game. <a href="https://t.co/fA1cWJfNlp">pic.twitter.com/fA1cWJfNlp</a></p>&mdash; Elias Daler (@EliasDaler) <a href="https://x.com/EliasDaler/status/1948300981130727607?ref_src=twsrc%5Etfw">July 24, 2025</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<p>Commercial engines must follow current technology and console generations, whereas custom engines can continue targeting whichever platforms their developers choose.</p>

<h2 id="modding">Modding</h2>

<p>On file management, T. Glaiel writes<sup id="fnref:glaiel:8" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>:</p>
<blockquote>
  <p>You might want to be able to build mod support or dynamic loading/unloading or whatever off of this, as long as you have the basics here and only ever load files through the file manager, you can easily add whatever other functionality you want into this later.</p>
</blockquote>

<p>But what can we load and allow the players to change?</p>

<p><img src="/images/2026/civ_iv.jpg" alt="civ4 mods" /></p>

<p>One of the defining games of my teenage years was <em>Civilization IV</em>, whose unusually moddable, data-driven architecture was discussed in the <a href="https://media.gdcvault.com/GD_Mag_Archives/GDM_August_2005.pdf">August 2005 issue of <em>Game Developer</em></a><sup id="fnref:civ4-gdmag" role="doc-noteref"><a href="#fn:civ4-gdmag" class="footnote" rel="footnote">53</a></sup>. It featured four tiers of moddability:</p>

<ul>
  <li><strong>In-game WorldBuilder:</strong> An in-game map editor for customising terrain and placing units, cities, buildings, and resources.</li>
  <li><strong>XML/data modification:</strong> All game variables, text, asset paths, and rules were stored in standard XML files.</li>
  <li><strong>Python scripting:</strong> High-level game functionality was exposed to Python, allowing scripts to generate maps, modify the interface, trigger game events, and override AI.</li>
  <li><strong>C++ GameCore DLL SDK:</strong> Users could modify, replace, or extend the low-level C++ code that ran the game.</li>
</ul>

<p>While researching this post, I discovered <a href="https://www.moddb.com/mods/realism-invictus/news/realism-invictus-38-released-20-year-anniversary-of-realism-invictus"><em>Realism Invictus</em></a><sup id="fnref:realism-invictus" role="doc-noteref"><a href="#fn:realism-invictus" class="footnote" rel="footnote">54</a></sup>, a total-overhaul mod that has remained in development for more than 20 years. It adds over 130 technologies, entire families of distinct units, more than 160 leaders, and 21 additional civics, making it feel almost like <em>Civilization IV 2.0</em>. All this is possible because the game developers spent a lot of time making the game (and thus the engine) moddable.</p>

<p><img src="/images/2026/arma3.jpg" alt="arma 3" /></p>

<p>Bohemia Interactive’s <em>ARMA III</em> offers another example. In his talk <a href="https://gdcvault.com/play/1027043/The-Pandora-s-Box-of">THE PANDORA’S BOX OF MODDING IN ‘ARMA’ GAMES</a><sup id="fnref:moricky" role="doc-noteref"><a href="#fn:moricky" class="footnote" rel="footnote">55</a></sup>, Karel Mořický describes three relationships between games and mods:</p>

<ul>
  <li><strong>Standard game:</strong> Players cannot modify it, so the code’s internal mess remains hidden.</li>
  <li><strong>Improvised modding:</strong> Determined players modify the game without official support, often producing messy or unstable results.</li>
  <li><strong>Moddable game:</strong> The developer provides an API so mods can add content. At this point, the product becomes a platform.</li>
</ul>

<p>I find it fascinating that <em>ARMA</em> supports multiple simultaneous mods, dependencies between mods, patch mods that override existing content, and multiplayer-specific server and client mods. At the time of the talk, the community had created 70,174 mods. The game exposes its editors directly through the main menu, and modders can use a development branch alongside the main release to prepare their mods for upcoming updates.</p>

<p><img src="/images/2026/minecraft_mod.jpg" alt="minecraft mods" /></p>

<p>By contrast, the <em>Minecraft: Java Edition</em> modding ecosystem grew largely without an official engine-level API from Mojang. Early modders simply decompiled the game and replaced or modified classes directly inside <code class="language-plaintext highlighter-rouge">minecraft.jar</code>. Over time, different communities built shared modding layers. Today, the main ecosystems include Fabric, Forge, NeoForge, and Quilt, alongside server platforms such as Paper and its derivatives. Updates to <em>Minecraft: Java Edition</em> have often been painful for modders. Version 1.13 introduced substantial internal changes, making large mods difficult to port and forcing even Forge itself to undergo a major redesign<sup id="fnref:minecraft-forge-porting" role="doc-noteref"><a href="#fn:minecraft-forge-porting" class="footnote" rel="footnote">56</a></sup>.</p>

<p>Interestingly, <em>Minecraft: Bedrock Edition</em> experimented with a <a href="https://www.minecraft.net/en-us/article/scripting-api-now-public-beta">JavaScript API</a><sup id="fnref:minecraft-js-api" role="doc-noteref"><a href="#fn:minecraft-js-api" class="footnote" rel="footnote">57</a></sup> in 2018. Its modern <a href="https://learn.microsoft.com/en-us/minecraft/creator/documents/scripting/introduction?view=minecraft-bedrock-stable">Script API</a><sup id="fnref:minecraft-script-api" role="doc-noteref"><a href="#fn:minecraft-script-api" class="footnote" rel="footnote">58</a></sup> still uses JavaScript or TypeScript, while <a href="https://learn.microsoft.com/en-us/minecraft/creator/documents/molang/introduction?view=minecraft-bedrock-stable">Molang</a><sup id="fnref:molang" role="doc-noteref"><a href="#fn:molang" class="footnote" rel="footnote">59</a></sup> serves as a separate expression language embedded in add-on JSON files.</p>

<p><img src="/images/2026/factorio.jpg" alt="factorio" /></p>

<p><a href="https://lua-api.factorio.com/latest/auxiliary/data-lifecycle.html"><em>Factorio</em> mods</a><sup id="fnref:factorio-lua" role="doc-noteref"><a href="#fn:factorio-lua" class="footnote" rel="footnote">60</a></sup> can run Lua code both during startup and while a map is being played. Because <em>Factorio</em> supports deterministic multiplayer, its developers found that even iterating over a Lua hash table could make a mod nondeterministic and cause <a href="https://www.factorio.com/blog/post/fff-70">desynchronisation</a><sup id="fnref:factorio-fff70" role="doc-noteref"><a href="#fn:factorio-fff70" class="footnote" rel="footnote">61</a></sup>. Mods can also crash. Initially, <em>Factorio</em> presented mod failures in the same way as game crashes, but the developers later <a href="https://www.factorio.com/blog/post/fff-178">added clearer error reporting</a><sup id="fnref:factorio-fff178" role="doc-noteref"><a href="#fn:factorio-fff178" class="footnote" rel="footnote">62</a></sup> that identified the responsible mod. One of <em>Factorio 2.0</em>’s most significant modding changes was to <a href="https://www.factorio.com/blog/post/fff-391">treat the game itself as a mod</a><sup id="fnref:factorio-fff391" role="doc-noteref"><a href="#fn:factorio-fff391" class="footnote" rel="footnote">63</a></sup>, shipping first-party components through the same dependency infrastructure.</p>

<p>For <em>Beach Slap</em>, we designed the level editor as an in-game tool from the start so that we could share it with players. Levels are stored as JSON files, making them easy to save locally, upload, and download. I do not, however, plan to support modding in the native version of <em>Soup Raiders</em>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>This post grew far beyond my original idea, but I wanted to set out my thoughts on building a custom engine before delving into the technology itself. Building an engine takes time, but it gives us control over how the game loads and updates, removes engine-licence fees, and teaches us about asset compilation, rendering, and optimisation. We can even expose parts of the engine to players, allowing them to improve the game or create mods (and potentially entirely new games) with our technology. After Unity’s runtime-fee announcement, technological independence became an even more important concern for game developers.</p>

<p>The rest of this series will explore the implementation of the <em>Soup Raiders</em> native port and how it targets 60 Hz on Nintendo Switch with loading times below three seconds. One final principle comes from <a href="https://www.youtube.com/watch?v=Ca53JTohdN4">Ted Bendixson’s Better Software Conference 2025 talk</a><sup id="fnref:bendixson" role="doc-noteref"><a href="#fn:bendixson" class="footnote" rel="footnote">64</a></sup>: build an actual game with your custom engine, not merely an impressive technical demo. T. Glaiel expresses the same principle more forcefully<sup id="fnref:glaiel:9" role="doc-noteref"><a href="#fn:glaiel" class="footnote" rel="footnote">7</a></sup>:</p>

<blockquote>
  <p><strong>Make a game at the same time as you’re making the engine.</strong> This is an unbreakable rule. The only unbreakable rule. Get the basics in as fast as you possibly can and then immediately start making a game on top of it. An engine is nothing without a game.</p>
</blockquote>

<p>I will leave you with one final thought on building a game engine with the help of LLMs<sup id="fnref:aaltonen-llm" role="doc-noteref"><a href="#fn:aaltonen-llm" class="footnote" rel="footnote">65</a></sup>:</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Now is the perfect time to write your own engine. The argument in the past was that you don&#39;t have time to write all the less important generic features. Now, the LLM will do that for you. You can focus on things that differentiate your product. <a href="https://t.co/ydEaaZOx12">https://t.co/ydEaaZOx12</a></p>&mdash; Sebastian Aaltonen (@SebAaltonen) <a href="https://x.com/SebAaltonen/status/2033460617282031835?ref_src=twsrc%5Etfw">March 16, 2026</a></blockquote>
<script async="" src="https://platform.x.com/widgets.js" charset="utf-8"></script>

<p>Continue with part 2: <a href="/gamedev/cpp/2026/08/20/srnative-02-porting-unity.html">Porting the <em>Soup Raiders</em> Demo from Unity</a>.</p>

<h2 id="further-reading">Further Reading</h2>

<p>These are not cited above, but they cover the same ground and are worth your time:</p>

<ul>
  <li>Rez Graham, <em>Independent Games Summit: A Case for Making Your Own Engine</em>, GDC 2024. <a href="https://www.gdcvault.com/play/1034506/Independent-Games-Summit-A-Case">https://www.gdcvault.com/play/1034506/Independent-Games-Summit-A-Case</a></li>
  <li>raysan5, <em>Custom Game Engines: A Small Study</em>. <a href="https://gist.github.com/raysan5/909dc6cf33ed40223eb0dfe625c0de74">https://gist.github.com/raysan5/909dc6cf33ed40223eb0dfe625c0de74</a></li>
  <li>Angelo “Encelo” Theodorou, <em>nCine: a world with custom in-house engines is possible</em>. <a href="https://encelo.github.io/CustomEnginesPresentation/#1">https://encelo.github.io/CustomEnginesPresentation/#1</a></li>
</ul>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:vginsights" role="doc-endnote">
      <p>Video Game Insights, <em>The Big Game Engines Report of 2025</em>. <a href="https://vginsights.com/assets/reports/The_Big_Game_Engines_Report_of_2025.pdf">https://vginsights.com/assets/reports/The_Big_Game_Engines_Report_of_2025.pdf</a> <a href="#fnref:vginsights" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:vginsights:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:thariq" role="doc-endnote">
      <p>Thariq (@trq212), post on X, 21 January 2026. <a href="https://x.com/trq212/status/2014051501786931427">https://x.com/trq212/status/2014051501786931427</a> <a href="#fnref:thariq" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:indie-game-movie" role="doc-endnote">
      <p>James Swirsky and Lisanne Pajot, <em>Indie Game: The Movie</em>, 2012. <a href="http://www.indiegamethemovie.com/about/">http://www.indiegamethemovie.com/about/</a> <a href="#fnref:indie-game-movie" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:custom-engine-games" role="doc-endnote">
      <p>Release dates and engine attributions compiled from developer statements, press coverage and store pages for each title. <a href="#fnref:custom-engine-games" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unity-games" role="doc-endnote">
      <p>Release dates compiled from store pages; engine attribution from developer statements and Unity case studies. <a href="#fnref:unity-games" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:karabatov" role="doc-endnote">
      <p>Yuri Karabatov, <em>Make your game, not an engine</em>, NorikiTech, 22 May 2025. <a href="https://norikitech.com/posts/make-your-game/">https://norikitech.com/posts/make-your-game/</a> <a href="#fnref:karabatov" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:glaiel" role="doc-endnote">
      <p>Tyler Glaiel, <em>How to make your own game engine (and why)</em>, Geek Culture (Medium), 11 November 2021. <a href="https://medium.com/geekculture/how-to-make-your-own-game-engine-and-why-ddf0acbc5f3">https://medium.com/geekculture/how-to-make-your-own-game-engine-and-why-ddf0acbc5f3</a> <a href="#fnref:glaiel" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:glaiel:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:glaiel:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:glaiel:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:glaiel:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:glaiel:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:glaiel:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a> <a href="#fnref:glaiel:7" class="reversefootnote" role="doc-backlink">&#8617;<sup>8</sup></a> <a href="#fnref:glaiel:8" class="reversefootnote" role="doc-backlink">&#8617;<sup>9</sup></a> <a href="#fnref:glaiel:9" class="reversefootnote" role="doc-backlink">&#8617;<sup>10</sup></a></p>
    </li>
    <li id="fn:noelberry" role="doc-endnote">
      <p>Noel Berry, <em>Making Video Games in 2025 (without an engine)</em>, 18 May 2025. <a href="https://noelberry.ca/posts/making_games_in_2025/">https://noelberry.ca/posts/making_games_in_2025/</a> <a href="#fnref:noelberry" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:noelberry:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:noelberry:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:glaiel-game-specific" role="doc-endnote">
      <p>Tyler Glaiel (@TylerGlaiel), post on X, 27 June 2024. <a href="https://x.com/TylerGlaiel/status/1806476966973116539">https://x.com/TylerGlaiel/status/1806476966973116539</a> <a href="#fnref:glaiel-game-specific" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ropert" role="doc-endnote">
      <p>Mathieu Ropert (@MatRopert), post on X, 30 April 2026. <a href="https://x.com/MatRopert/status/2049803204385226827">https://x.com/MatRopert/status/2049803204385226827</a> <a href="#fnref:ropert" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:kiselev" role="doc-endnote">
      <p>Maxim Kiselev, <em>Real reasons (not) to build custom game engines in 2024</em>, Game Developer, 19 September 2024. <a href="https://www.gamedeveloper.com/programming/real-reasons-not-to-build-custom-game-engines-in-2024">https://www.gamedeveloper.com/programming/real-reasons-not-to-build-custom-game-engines-in-2024</a> <a href="#fnref:kiselev" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:kiselev:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:kiselev:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:aaltonen-rhi" role="doc-endnote">
      <p>Sebastian Aaltonen (@SebAaltonen), post on X, 19 January 2025. <a href="https://x.com/SebAaltonen/status/1880889196363251790">https://x.com/SebAaltonen/status/1880889196363251790</a> <a href="#fnref:aaltonen-rhi" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:aaltonen-hackweek" role="doc-endnote">
      <p>Sebastian Aaltonen (@SebAaltonen), post on X, 4 June 2022. <a href="https://x.com/SebAaltonen/status/1532988130290241536">https://x.com/SebAaltonen/status/1532988130290241536</a> <a href="#fnref:aaltonen-hackweek" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:improbable" role="doc-endnote">
      <p><em>Improbable urges Unity to unsuspend their game engine license or clarify terms</em>, TechCrunch, 11 January 2019. <a href="https://techcrunch.com/2019/01/11/improbable-urges-unity-to-unsuspend-their-game-engine-license-or-clarify-terms/">https://techcrunch.com/2019/01/11/improbable-urges-unity-to-unsuspend-their-game-engine-license-or-clarify-terms/</a> <a href="#fnref:improbable" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unity-revoke-2026" role="doc-endnote">
      <p><em>Was gonna launch my game’s demo tomorrow, but am…</em>, r/unity, Reddit, July 2026. <a href="https://www.reddit.com/r/unity/comments/1ur9j7m/was_gonna_launch_my_games_demo_tomorrow_but_am/">https://www.reddit.com/r/unity/comments/1ur9j7m/was_gonna_launch_my_games_demo_tomorrow_but_am/</a> <a href="#fnref:unity-revoke-2026" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unity-revoke-thorn" role="doc-endnote">
      <p>Mike Thorn, <em>My personal license has been revoked</em>, Unity Discussions, March 2024. <a href="https://discussions.unity.com/t/my-personal-license-has-been-revoked/942826">https://discussions.unity.com/t/my-personal-license-has-been-revoked/942826</a> <a href="#fnref:unity-revoke-thorn" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unity-revoke-2023" role="doc-endnote">
      <p><em>License revoked, editor closed, work lost</em>, Unity Discussions, August 2023. <a href="https://discussions.unity.com/t/license-revoked-editor-closed-work-lost/926284">https://discussions.unity.com/t/license-revoked-editor-closed-work-lost/926284</a> <a href="#fnref:unity-revoke-2023" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:godot-license" role="doc-endnote">
      <p><em>License</em>, Godot Engine. <a href="https://godotengine.org/license/">https://godotengine.org/license/</a> <a href="#fnref:godot-license" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:godot-license:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:renderware" role="doc-endnote">
      <p><em>RenderWare</em>, Wikipedia. <a href="https://en.wikipedia.org/wiki/RenderWare">https://en.wikipedia.org/wiki/RenderWare</a> <a href="#fnref:renderware" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ea-criterion" role="doc-endnote">
      <p><em>EA assimilates Criterion</em>, GameSpot. <a href="https://www.gamespot.com/articles/ea-assimilates-criterion/">https://www.gamespot.com/articles/ea-assimilates-criterion/</a> <a href="#fnref:ea-criterion" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:bing-gordon" role="doc-endnote">
      <p><em>Bing there, done that: EA’s CCO talks everything</em>, Game Developer. <a href="https://www.gamedeveloper.com/business/bing-there-done-that-ea-s-cco-talks-everything">https://www.gamedeveloper.com/business/bing-there-done-that-ea-s-cco-talks-everything</a> <a href="#fnref:bing-gordon" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:machinery-blog" role="doc-endnote">
      <p>Our Machinery, <em>The Machinery blog archive</em>, mirrored by ruby0x1. <a href="https://ruby0x1.github.io/machinery_blog_archive/">https://ruby0x1.github.io/machinery_blog_archive/</a> <a href="#fnref:machinery-blog" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:machinery-nme" role="doc-endnote">
      <p><em>The Machinery game engine cancelled as developers told to delete all source code</em>, NME, August 2022. <a href="https://www.nme.com/news/gaming-news/the-machinery-game-engine-cancelled-as-developers-told-to-delete-all-source-code-3283633">https://www.nme.com/news/gaming-news/the-machinery-game-engine-cancelled-as-developers-told-to-delete-all-source-code-3283633</a> <a href="#fnref:machinery-nme" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:fleury" role="doc-endnote">
      <p>Ryan Fleury, <em>Ships, Icebergs, Game Engines</em>, Digital Grove, 5 August 2022. <a href="https://www.dgtlgrove.com/p/ships-icebergs-game-engines">https://www.dgtlgrove.com/p/ships-icebergs-game-engines</a> <a href="#fnref:fleury" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unity-runtime-fee" role="doc-endnote">
      <p>Unity, <em>Unity plan pricing and packaging updates</em>, Unity Discussions, 12 September 2023. <a href="https://discussions.unity.com/t/unity-plan-pricing-and-packaging-updates/927079">https://discussions.unity.com/t/unity-plan-pricing-and-packaging-updates/927079</a> <a href="#fnref:unity-runtime-fee" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:whitten-letter" role="doc-endnote">
      <p>Marc Whitten, <em>An open letter to our community</em>, Unity, 22 September 2023. <a href="https://unity.com/blog/an-open-letter-to-our-community">https://unity.com/blog/an-open-letter-to-our-community</a> <a href="#fnref:whitten-letter" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:runtime-fee-cancelled" role="doc-endnote">
      <p>Matt Bromberg, <em>Unity is canceling the Runtime Fee</em>, Unity, September 2024. <a href="https://unity.com/blog/unity-is-canceling-the-runtime-fee">https://unity.com/blog/unity-is-canceling-the-runtime-fee</a> <a href="#fnref:runtime-fee-cancelled" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:kalling-kingdom" role="doc-endnote">
      <p>Elega Corporation, <em>Kalling Kingdom engine port and YouTube hiatus</em>. <a href="https://www.elegacorp.com/blog/kalling-kingdom-engine-port-and-youtube-hiatus.html">https://www.elegacorp.com/blog/kalling-kingdom-engine-port-and-youtube-hiatus.html</a> <a href="#fnref:kalling-kingdom" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:spielmannspiel" role="doc-endnote">
      <p><em>Godot private asset library — transitioning from Unity to Godot</em>, Spielmannspiel. <a href="https://spielmannspiel.com/blog/transitioning_from_unity_to_godot">https://spielmannspiel.com/blog/transitioning_from_unity_to_godot</a> <a href="#fnref:spielmannspiel" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:no-escape-godot" role="doc-endnote">
      <p><em>From Unity to Godot: my journey with “No Escape?!”</em>, Godot Forum. <a href="https://forum.godotengine.org/t/from-unity-to-godot-my-journey-with-my-no-escape-game-and-open-source-projects/126568">https://forum.godotengine.org/t/from-unity-to-godot-my-journey-with-my-no-escape-game-and-open-source-projects/126568</a> <a href="#fnref:no-escape-godot" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:scrapyard-jam" role="doc-endnote">
      <p><em>Scrapyard Jam</em>, itch.io. <a href="https://itch.io/jam/scrapyard-jam">https://itch.io/jam/scrapyard-jam</a> <a href="#fnref:scrapyard-jam" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:gmtk-jam" role="doc-endnote">
      <p>Game Maker’s Toolkit (@gamemakerstoolkit.com), post on Bluesky, 27 July 2026. <a href="https://bsky.app/profile/gamemakerstoolkit.com/post/3mrmx5mi3mk24">https://bsky.app/profile/gamemakerstoolkit.com/post/3mrmx5mi3mk24</a> <a href="#fnref:gmtk-jam" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:lisyarus" role="doc-endnote">
      <p>Nikita Lisitsa (lisyarus), <em>So, you want to make a game engine</em>, 15 September 2023. <a href="https://lisyarus.github.io/blog/posts/so-you-want-to-make-a-game-engine.html">https://lisyarus.github.io/blog/posts/so-you-want-to-make-a-game-engine.html</a> <a href="#fnref:lisyarus" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:lisyarus:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:lisyarus:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:lisyarus:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:lisyarus:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a></p>
    </li>
    <li id="fn:unity-pricing" role="doc-endnote">
      <p>Unity, <em>Unity pricing updates</em>. <a href="https://unity.com/products/pricing-updates">https://unity.com/products/pricing-updates</a> <a href="#fnref:unity-pricing" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unreal-license" role="doc-endnote">
      <p>Epic Games, <em>Unreal Engine licensing options</em>. <a href="https://www.unrealengine.com/license">https://www.unrealengine.com/license</a> <a href="#fnref:unreal-license" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:gamemaker-pricing" role="doc-endnote">
      <p>GameMaker, <em>November 2023 pricing/terms change FAQ</em>. <a href="https://gamemaker.io/en/help/articles/november-2023-pricing-terms-change-faq">https://gamemaker.io/en/help/articles/november-2023-pricing-terms-change-faq</a> <a href="#fnref:gamemaker-pricing" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:cryengine-license" role="doc-endnote">
      <p>CRYENGINE, <em>Licensing</em>. <a href="https://www.cryengine.com/support/view/licensing">https://www.cryengine.com/support/view/licensing</a> <a href="#fnref:cryengine-license" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:handmade-manifesto" role="doc-endnote">
      <p>Handmade Network, <em>The Handmade Manifesto</em>. <a href="https://handmade.network/manifesto">https://handmade.network/manifesto</a> <a href="#fnref:handmade-manifesto" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:factorio-ama" role="doc-endnote">
      <p>Wube Software, <em>Developer technical-oriented AMA</em>, r/factorio, Reddit, September 2020. <a href="https://www.reddit.com/r/factorio/comments/in5d3i/developer_technicaloriented_ama/">https://www.reddit.com/r/factorio/comments/in5d3i/developer_technicaloriented_ama/</a> <a href="#fnref:factorio-ama" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ashkan" role="doc-endnote">
      <p>Ashkan Saeidi Mazdeh (@Ashkan_GC), post on X, 5 February 2026. <a href="https://x.com/Ashkan_GC/status/2019446029175693724">https://x.com/Ashkan_GC/status/2019446029175693724</a> <a href="#fnref:ashkan" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:dan-baker" role="doc-endnote">
      <p>Dan Baker, <em>A case for building your own tech</em>, GamesIndustry.biz. <a href="https://www.gamesindustry.biz/a-case-for-building-your-own-tech-opinion">https://www.gamesindustry.biz/a-case-for-building-your-own-tech-opinion</a> <a href="#fnref:dan-baker" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:degraff" role="doc-endnote">
      <p>Sébastien de Graffenried (@seb_degraff), post on X, 12 February 2025. <a href="https://x.com/seb_degraff/status/1889649086560600088">https://x.com/seb_degraff/status/1889649086560600088</a> <a href="#fnref:degraff" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:blow" role="doc-endnote">
      <p>Jonathan Blow (@Jonathan_Blow), post on X, 26 January 2021. <a href="https://x.com/Jonathan_Blow/status/1353939518588502018">https://x.com/Jonathan_Blow/status/1353939518588502018</a> <a href="#fnref:blow" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:purho" role="doc-endnote">
      <p>Petri Purho, <em>Exploring the Tech and Design of ‘Noita’</em>, Independent Games Summit, GDC 2019. <a href="https://www.gdcvault.com/play/1025695/Exploring-the-Tech-and-Design">https://www.gdcvault.com/play/1025695/Exploring-the-Tech-and-Design</a> <a href="#fnref:purho" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:miegakure" role="doc-endnote">
      <p>Patrick Klepek, <em>What Happened to ‘Miegakure,’ the Game That Promised the 4th Dimension?</em>, Vice (Waypoint), 24 July 2018. <a href="https://www.vice.com/en/article/what-happened-to-miegakure-the-game-that-promised-the-4th-dimension/">https://www.vice.com/en/article/what-happened-to-miegakure-the-game-that-promised-the-4th-dimension/</a> <a href="#fnref:miegakure" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:teardown" role="doc-endnote">
      <p>Joel Couture, <em>Combining bombastic heists with a fully destructible voxel world in Teardown</em>, Game Developer, 21 July 2021. <a href="https://www.gamedeveloper.com/design/combining-bombastic-heists-with-a-fully-destructible-voxel-world-in-i-teardown-i-">https://www.gamedeveloper.com/design/combining-bombastic-heists-with-a-fully-destructible-voxel-world-in-i-teardown-i-</a> <a href="#fnref:teardown" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:animal-well" role="doc-endnote">
      <p>Sam Machkovech, <em>Why Animal Well’s home-brewed engine was key to its success</em>, Game Developer, 13 November 2024. <a href="https://www.gamedeveloper.com/design/why-animal-well-s-home-brewed-engine-was-key-to-its-success">https://www.gamedeveloper.com/design/why-animal-well-s-home-brewed-engine-was-key-to-its-success</a> <a href="#fnref:animal-well" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:fredriksson" role="doc-endnote">
      <p>Andreas Fredriksson, <em>Context is Everything</em>, Handmade Seattle 2021. <a href="https://vimeo.com/644068002">https://vimeo.com/644068002</a> <a href="#fnref:fredriksson" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:redengine" role="doc-endnote">
      <p><em>REDengine</em>, The Witcher Games Wiki. <a href="https://witcher-games.fandom.com/wiki/REDengine">https://witcher-games.fandom.com/wiki/REDengine</a> <a href="#fnref:redengine" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:cerny" role="doc-endnote">
      <p>Mark Cerny, <em>The Road to PS4</em>, Gamelab 2013. <a href="https://www.youtube.com/watch?v=xHXrBnipHyA">https://www.youtube.com/watch?v=xHXrBnipHyA</a> <a href="#fnref:cerny" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unity-consoles" role="doc-endnote">
      <p>Unity, <em>System requirements for Unity 6.4</em>, Unity Manual. <a href="https://docs.unity3d.com/6000.4/Documentation/Manual/system-requirements.html">https://docs.unity3d.com/6000.4/Documentation/Manual/system-requirements.html</a> <a href="#fnref:unity-consoles" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:elias-daler" role="doc-endnote">
      <p>Elias Daler (@EliasDaler), post on X, 24 July 2025. <a href="https://x.com/EliasDaler/status/1948300981130727607">https://x.com/EliasDaler/status/1948300981130727607</a> <a href="#fnref:elias-daler" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:civ4-gdmag" role="doc-endnote">
      <p><em>Game Developer</em>, August 2005 issue. <a href="https://media.gdcvault.com/GD_Mag_Archives/GDM_August_2005.pdf">https://media.gdcvault.com/GD_Mag_Archives/GDM_August_2005.pdf</a> <a href="#fnref:civ4-gdmag" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:realism-invictus" role="doc-endnote">
      <p><em>Realism Invictus 3.8 released — 20 year anniversary of Realism Invictus</em>, ModDB. <a href="https://www.moddb.com/mods/realism-invictus/news/realism-invictus-38-released-20-year-anniversary-of-realism-invictus">https://www.moddb.com/mods/realism-invictus/news/realism-invictus-38-released-20-year-anniversary-of-realism-invictus</a> <a href="#fnref:realism-invictus" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:moricky" role="doc-endnote">
      <p>Karel Mořický, <em>The Pandora’s Box of Modding in ‘Arma’ Games</em>, Bohemia Interactive, GDC 2021. <a href="https://gdcvault.com/play/1027043/The-Pandora-s-Box-of">https://gdcvault.com/play/1027043/The-Pandora-s-Box-of</a> <a href="#fnref:moricky" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:minecraft-forge-porting" role="doc-endnote">
      <p>Minecraft Forge, <em>Porting from 1.12 to 1.13/1.14</em>, Forge Documentation. <a href="https://docs.minecraftforge.net/en/1.14.x/legacy/porting1214/">https://docs.minecraftforge.net/en/1.14.x/legacy/porting1214/</a> <a href="#fnref:minecraft-forge-porting" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:minecraft-js-api" role="doc-endnote">
      <p>Mojang, <em>Scripting API now in public beta</em>, Minecraft.net, 2018. <a href="https://www.minecraft.net/en-us/article/scripting-api-now-public-beta">https://www.minecraft.net/en-us/article/scripting-api-now-public-beta</a> <a href="#fnref:minecraft-js-api" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:minecraft-script-api" role="doc-endnote">
      <p>Microsoft, <em>Introduction to scripting</em>, Minecraft Creator Documentation. <a href="https://learn.microsoft.com/en-us/minecraft/creator/documents/scripting/introduction?view=minecraft-bedrock-stable">https://learn.microsoft.com/en-us/minecraft/creator/documents/scripting/introduction?view=minecraft-bedrock-stable</a> <a href="#fnref:minecraft-script-api" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:molang" role="doc-endnote">
      <p>Microsoft, <em>Introduction to Molang</em>, Minecraft Creator Documentation. <a href="https://learn.microsoft.com/en-us/minecraft/creator/documents/molang/introduction?view=minecraft-bedrock-stable">https://learn.microsoft.com/en-us/minecraft/creator/documents/molang/introduction?view=minecraft-bedrock-stable</a> <a href="#fnref:molang" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:factorio-lua" role="doc-endnote">
      <p>Wube Software, <em>Data lifecycle</em>, Factorio Lua API documentation. <a href="https://lua-api.factorio.com/latest/auxiliary/data-lifecycle.html">https://lua-api.factorio.com/latest/auxiliary/data-lifecycle.html</a> <a href="#fnref:factorio-lua" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:factorio-fff70" role="doc-endnote">
      <p>Kovarex, <em>Friday Facts #70 - The smooth FPS</em>, Factorio, 23 January 2015, “Deterministic scripting” section. <a href="https://www.factorio.com/blog/post/fff-70">https://www.factorio.com/blog/post/fff-70</a> <a href="#fnref:factorio-fff70" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:factorio-fff178" role="doc-endnote">
      <p>Wube Software, <em>Friday Facts #178 - Minimal mode and Mini-tutorials</em>, Factorio, 17 February 2017. <a href="https://www.factorio.com/blog/post/fff-178">https://www.factorio.com/blog/post/fff-178</a> <a href="#fnref:factorio-fff178" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:factorio-fff391" role="doc-endnote">
      <p>Wube Software, <em>Friday Facts #391 - 2023 recap</em>, Factorio, 29 December 2023. <a href="https://www.factorio.com/blog/post/fff-391">https://www.factorio.com/blog/post/fff-391</a> <a href="#fnref:factorio-fff391" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:bendixson" role="doc-endnote">
      <p>Ted Bendixson, <em>Most of your projects are stupid. Please make some actual games.</em>, Better Software Conference 2025. <a href="https://www.youtube.com/watch?v=Ca53JTohdN4">https://www.youtube.com/watch?v=Ca53JTohdN4</a> <a href="#fnref:bendixson" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:aaltonen-llm" role="doc-endnote">
      <p>Sebastian Aaltonen (@SebAaltonen), post on X, 16 March 2026. <a href="https://x.com/SebAaltonen/status/2033460617282031835">https://x.com/SebAaltonen/status/2033460617282031835</a> <a href="#fnref:aaltonen-llm" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="gamedev" /><category term="cpp" /><summary type="html"><![CDATA[According to VGInsights, about 13% of games released on Steam in 2024 used a custom engine, down from 71% in 20121. So looking at this trend, why should anybody try to make their own game engine? Video Game Insights, The Big Game Engines Report of 2025. https://vginsights.com/assets/reports/The_Big_Game_Engines_Report_of_2025.pdf &#8617;]]></summary></entry><entry><title type="html">Soup Raiders Goes Native: Introduction</title><link href="https://eliasfarhan.ch/gamedev/cpp/2026/08/05/srnative-00.html" rel="alternate" type="text/html" title="Soup Raiders Goes Native: Introduction" /><published>2026-08-05T00:00:00+00:00</published><updated>2026-08-05T00:00:00+00:00</updated><id>https://eliasfarhan.ch/gamedev/cpp/2026/08/05/srnative-00</id><content type="html" xml:base="https://eliasfarhan.ch/gamedev/cpp/2026/08/05/srnative-00.html"><![CDATA[<iframe src="https://www.linkedin.com/embed/feed/update/urn:li:share:7483892203300229120?collapsed=1" height="543" width="504" frameborder="0" allowfullscreen="" title="Embedded post"></iframe>

<p>Two weeks ago, I announced that my talk was accepted for CppCon 2026, happening in about a month in Denver (on Tuesday, September 15th, to be more exact). The talk is about building a small but non-trivial C++ game engine and all the nice things modern C++ provides to help with this task; you can find it in the <a href="https://cppcon2026.sched.com/event/2RT5K/inside-a-small-game-engine-an-architecture-tour-in-modern-c++">CppCon schedule</a>. I was not very comfortable giving a purely theoretical talk without any practical example to back it up.</p>

<!--more-->

<h2 id="the-game">The game</h2>

<p><img src="/images/2026/sr_docks.png" alt="soup raiders docks" /></p>

<p>Just playing around with a simple game engine was not what I wanted to do during my summer holidays. After working at <a href="https://6it.dev">6it.dev</a>, I realised that even though I like working on and understanding game engines, my goal is still to make games. And so, to have a manageable but not too trivial project, I decided to resurrect <em>Soup Raiders</em>, which had been <a href="/jekyll/update/2024/12/23/soup-raiders-pause.html">on hold since 2024</a>.</p>

<p>So the goal is pretty simple: take my old Unity game and port it to a new custom C++ engine using the Neko3d renderer. Through this process, I can test that the content of my CppCon talk is valid in practice. However, there are a lot of points that I simply will not have time to bring up during my 60-minute talk, so I decided to write a few blog posts about them. I don’t know how big this series is going to be, but I want to write about interesting subjects like load-time optimisation, profiling, GPU optimisation, error handling, and platform porting.</p>

<h2 id="3d-renderer">3D renderer</h2>

<p>For the 3D renderer, a part of a game engine that can take a lot of time to build, I already have CompGraphEditor, a tool that I use in my course to explain the basic concepts of computer graphics. Behind the scenes, I have been refactoring this project into a new version for next year, renamed Neko3d:</p>

<p><img src="/images/2026/neko3d.png" alt="neko3d logo" /></p>

<p>Neko3d uses SDL GPU as its rendering hardware interface (RHI) on top of Vulkan, DirectX, and Metal. Above the RHI, Neko3d adds shaders, pipelines, meshes, models, materials, draw commands, render passes, and scenes that can be customised with C++ “scripts” to control the rendering. The main shader language I use is GLSL (I should probably switch to Slang one day).</p>

<h2 id="the-target">The target</h2>

<p><img src="/images/2026/sr/sr_switch.jpg" alt="soup raiders switch" /></p>

<p>I want this game to run on the Nintendo Switch at 60 Hz, with sub-three-second loading times between levels (and menus). The bottlenecks are going to be the GPU and the loading of visual assets. But in the previous Unity version, I managed to get the game running close to this target, and now I have full control over the codebase.</p>

<h2 id="the-series">The series</h2>

<p>So here begins a whole series about this little summer project of mine: bringing my <em>Soup Raiders</em> demo to a custom game engine.</p>

<ul>
  <li>(1) <a href="/gamedev/cpp/2026/08/14/srnative-01-why-a-custom-engine.html">What You Gain by Building Your Own Game Engine</a></li>
  <li>(2) <a href="/gamedev/cpp/2026/08/20/srnative-02-porting-unity.html">Porting from Unity</a></li>
  <li>(3) The asset compiler</li>
  <li>(4) Profiling with Tracy</li>
  <li>(5) Level load time</li>
  <li>(6) Rendering optimisation: culling, cutout silhouettes, and depth sharing</li>
  <li>(7) When to use ECS and when not to</li>
  <li>(8) Error handling</li>
  <li>(9) Memory: fixed-capacity containers and heap budgeting</li>
  <li>(10) Porting to a closed platform</li>
  <li>(11) The future of <em>Soup Raiders</em>: Unity as an editor, or my own?</li>
</ul>]]></content><author><name></name></author><category term="gamedev" /><category term="cpp" /><summary type="html"><![CDATA[Two weeks ago, I announced that my talk was accepted for CppCon 2026, happening in about a month in Denver (on Tuesday, September 15th, to be more exact). The talk is about building a small but non-trivial C++ game engine and all the nice things modern C++ provides to help with this task; you can find it in the CppCon schedule. I was not very comfortable giving a purely theoretical talk without any practical example to back it up.]]></summary></entry><entry><title type="html">Why I still teach OpenGL ES 3.0</title><link href="https://eliasfarhan.ch/graphics/cpp/2026/01/27/why-i-teach-opengles.html" rel="alternate" type="text/html" title="Why I still teach OpenGL ES 3.0" /><published>2026-01-27T00:00:00+00:00</published><updated>2026-01-27T00:00:00+00:00</updated><id>https://eliasfarhan.ch/graphics/cpp/2026/01/27/why-i-teach-opengles</id><content type="html" xml:base="https://eliasfarhan.ch/graphics/cpp/2026/01/27/why-i-teach-opengles.html"><![CDATA[<div id="webgl-container" style="text-align: center; margin: 20px 0;">
  <canvas id="canvas" width="800" height="600" style="border: 1px solid #000; background-color: #000;">
  </canvas>
  <div id="status" style="margin-top: 10px;">Loading...</div>
</div>
<script>
  // Lock the title
  const originalTitle = document.title;
  
  Object.defineProperty(document, 'title', {
    get: function() {
      return originalTitle;
    },
    set: function(newTitle) {
      // Ignore any attempts to change title
      console.log('Blocked title change to:', newTitle);
    }
  });
</script>

<script>
  var Module = {
    canvas: document.getElementById('canvas'),
    print: function(text) {
      console.log(text);
    },
    setStatus: function(text) {
      document.getElementById('status').innerText = text;
    },
    locateFile: function(path) {
      return '/assets/webgl/hello-transform/' + path;
    },
  };
</script>

<script async="" src="/assets/webgl/hello-transform/HelloTransform.js"></script>

<style>
  #canvas {
    width: 100% !important;
    height: auto !important;
    aspect-ratio: 4/3;
    display: block !important;
  }
</style>

<!--more-->
<p><em>Little C++ OpenGL ES 3.0 sample compiled to the web with emscripten</em></p>

<h2 id="introduction">Introduction</h2>

<p>It’s been a few years that I have been giving this Introduction to Computer Graphics module (a trimester) at <a href="https://www.sae.edu/ch-ge/">SAE Institute Geneva</a> and I have always been wondering if and when I would need to transition my course from OpenGL ES 3.0 to more modern API.</p>

<p>Back in November, I was at the Graphics Programming Conference (my blog post <a href="jekyll/update/2025/11/21/graphics_programming_conf.html">here</a>), and this subject reemerged for me during the conversations with people from the industry as well as from the talks. From the Teardown talk (all the conference’s talks are available on the <a href="https://graphicsprogrammingconference.com/2025/">website</a>), the original game worked in OpenGL 3.3 and from the X-Plane talk, the rendering engine was using OpenGL 2.1. But both those talks were about how they transitioned to more modern API to have better graphics. This sounds like the sunset of OpenGL.</p>

<p>Two talks really caught my attention about education for Computer Graphics: the talk <em>Bridging Pixels and Code: Teaching Computer Graphics to Technical Artists</em> from Matthieu Delaere that focus on students creating their own rasterizer from scratch and the talk from Mike Shah that focuses on the next steps after the tutorial. The latter talk actually made me think about if the actual idea to do the transition to modern API for my course made still sense.</p>

<p>What have to be taken into account for a transition is that all the courses need to be translated to this new API/framework and updated depending on the new concept added. So if there is a transition to be made, it needs to be really worth it compare to OpenGL ES 3.0.</p>

<h2 id="the-course-content">The course content</h2>
<p>The goal of the introduction in computer graphics module is for the students to implement a 3d scene with a graphics API. What is important to understand is that they should not implement a generic 3d renderer, but specifically a 3d scene, so the final executable is not supposed to be data-driven. For example, it would be completely ok to have such implementation:</p>

<pre><code class="language-C++">void DrawScene(const Pipeline&amp; pipeline, const Camera&amp; camera)
{
  //Cull the scene from the camera POV

  //Draw each 3d object by hand

}
</code></pre>

<p>Since when I started to give this module, I have been following the <a href="https://learnopengl.com/">learnopengl.com</a> structure but using OpenGL ES 3.0 instead. Each chapter is like a list of samples that have to be implemented and tested (Hello Triangle, Hello Texture, etc…)</p>

<p>But why specifically this version of OpenGL? OpenGL ES 3.0 is a mobile graphics API, released in August 2012, so about 14 years ago (which is a LONG time in computer graphics). I chose it because it is the most cross-platform version of OpenGL in my opinion, with this version you can run the same C++ program (with some #ifdef) and the same shaders for:</p>
<ul>
  <li>Windows: while I encountered some driver issues throughout the year, OpenGL ES 3.0 is compatible with OpenGL 4.3, so I just assume OpenGL 4.3 on Windows not to worry.</li>
  <li>Linux: some of my workstations boot on Linux by default, and it is good practice to compile the same C++ code with another compiler.</li>
  <li>Android: this one is a bit complicated as the actual android app boots from Java and calls the native lib containing the code.</li>
  <li>Nintendo Switch: we have two devkits at school and except removing glew or glad and replacing it with the embedding OpenGL wrapper, it works fine.</li>
  <li>WebGL2: my favorite reason of using OpenGL ES 3.0 is that it is compatible with WebGL2 with emscripten (except float color attachment that is an extension…), which allows my students to port their samples to their own website/blog. It is the case with this blog post where I simply put the demo out of emscripten on the web page.</li>
  <li>iOS or MacOSX: unfortunately not supported anymore, but none of my students work on a Mac as this is a Games Programming bachelor.</li>
</ul>

<p><img src="/images/2026/cube_switch.jpg" alt="The school devkit with the rotating cubes running" /></p>

<p><em>The same demo but running on the Switch</em></p>

<p>Compare to more modern API, synchronization is taken care of by the driver in OpenGL. This allows to skip most of the setup of a lot of boilerplate initialization. This is nice, but is paid with the cost of the driver overhead added for this.</p>

<p>However, one of the hassles of the first course is having the students’ laptop chooses the wrong GPU on laptop (using the Intel integrated GPU instead of the Nvidia GPU). Last year, you just needed to set the whole Nvidia configuration to be on “Performant” mode, but with a new Windows update, you need to set individually the application that is to be performant in the Windows settings, not the Nvidia settings anymore. Also, there is this trick for Windows:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifdef __cplusplus
</span><span class="k">extern</span> <span class="s">"C"</span> <span class="p">{</span>
<span class="cp">#endif
</span>
<span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span> <span class="n">DWORD</span> <span class="n">NvOptimusEnablement</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span> <span class="kt">int</span> <span class="n">AmdPowerXpressRequestHighPerformance</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>

<span class="cp">#ifdef __cplusplus
</span><span class="p">}</span>
<span class="cp">#endif
</span></code></pre></div></div>
<p>And this (supposedely) enables the discrete GPU and not the integrated one (I am happy this is a solved problem in modern API like Vulkan). Let’s see how we tackle other subjects!</p>

<h2 id="core-concepts">Core concepts</h2>
<p>Every week, the students need to finish about 2-3 samples on a specific feature (for example: implement a sample that uses SSAO using the previous idea from the deferred rendering sample). 
When working on their samples, the students usually start to build their own abstractions. This year, we decided to work together and build the abstraction in a common git repository such that those abstraction could be used by all the students of the class as soon as it was implemented. This is something that I want to continue in the next years.</p>

<h3 id="pipeline">Pipeline</h3>

<p>I really dislike OpenGL naming… Pipelines are called program, so when my students comes from learnopengl.com, they name their pipelines <code class="language-plaintext highlighter-rouge">shaderProgram</code>. Anyway, we created an abstraction called <code class="language-plaintext highlighter-rouge">Pipeline</code> that loads with a vertex shader and a fragment shader (also a specific abstraction) that looks like this:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Shader</span>
<span class="p">{</span>
<span class="nl">public:</span>
<span class="c1">//shader target being GL_VERTEX_SHADER or GL_FRAGMENT_SHADER</span>
    <span class="kt">void</span> <span class="n">Load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">path</span><span class="p">,</span> <span class="n">GLenum</span> <span class="n">shader_target</span><span class="p">);</span> 
<span class="c1">//[...]</span>

<span class="k">class</span> <span class="nc">Pipeline</span>
<span class="p">{</span>
<span class="nl">public:</span>
    <span class="kt">void</span> <span class="n">Load</span><span class="p">(</span><span class="k">const</span> <span class="n">Shader</span><span class="o">&amp;</span> <span class="n">vertex_shader</span><span class="p">,</span> <span class="k">const</span> <span class="n">Shader</span><span class="o">&amp;</span> <span class="n">fragment_shader</span><span class="p">);</span>
<span class="c1">//[...]</span>
</code></pre></div></div>
<p>Shaders are “compiled” with <em>glslangValidator</em> that I configure with <em>CMake</em>. This allows to check for syntax errors during compile-time instead of seeing a weird error in the run-time compilation of the glsl shader by the driver.</p>

<p>Because of OpenGL implementation of uniform buffer, we added a bunch of function to set those uniforms and to retrieve the uniform locations (and cache them for future retrieval):</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Pipeline</span>
<span class="p">{</span>
<span class="nl">public:</span>
    <span class="c1">//[...]</span>
    <span class="kt">void</span> <span class="n">SetInt</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">uniform_name</span><span class="p">,</span> <span class="kt">int</span> <span class="n">new_value</span><span class="p">);</span>
    <span class="kt">void</span> <span class="n">SetFloat</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">uniform_name</span><span class="p">,</span> <span class="kt">float</span> <span class="n">new_value</span><span class="p">);</span>
    <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
    <span class="k">requires</span> <span class="n">core</span><span class="o">::</span><span class="n">IsVector3</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="kt">float</span><span class="o">&gt;</span>
    <span class="kt">void</span> <span class="n">SetVec3</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">uniform_name</span><span class="p">,</span> <span class="k">const</span> <span class="n">T</span><span class="o">&amp;</span> <span class="n">v</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">const</span> <span class="n">GLint</span> <span class="n">loc</span> <span class="o">=</span> <span class="n">GetUniformLocation</span><span class="p">(</span><span class="n">uniform_name</span><span class="p">);</span>
        <span class="n">glUniform3f</span><span class="p">(</span><span class="n">loc</span><span class="p">,</span> <span class="n">v</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">v</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">v</span><span class="p">.</span><span class="n">z</span><span class="p">);</span>
    <span class="p">}</span>
<span class="nl">private:</span>
    <span class="n">GLint</span> <span class="n">GetUniformLocation</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">name</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">,</span> <span class="n">GLint</span><span class="p">,</span> <span class="n">StringHash</span><span class="p">,</span> <span class="n">StringEqual</span><span class="o">&gt;</span> <span class="n">uniform_location_map_</span><span class="p">;</span>
</code></pre></div></div>
<p>Several things to unpack here:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">SetVec3</code> uses the concept <code class="language-plaintext highlighter-rouge">IsVector3</code> that check if the type has the three attributes x, y, z at the correct type.</li>
  <li><code class="language-plaintext highlighter-rouge">StringHash</code> and <code class="language-plaintext highlighter-rouge">StringEqual</code> allows the use of <code class="language-plaintext highlighter-rouge">std::string_view</code> without the need to create a <code class="language-plaintext highlighter-rouge">std::string</code> when trying to find a uniform location in the map.</li>
</ul>

<h3 id="vertex-inputs">Vertex inputs</h3>

<p>One of the weird thing for me going into OpenGL is how memory is allocated (the glGen<em>, glBind</em>, and then upload the data and probably uplaod at the same time). For example, for a vertex input buffer:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">vbo_</span><span class="p">;</span>
<span class="n">glGenBuffers</span><span class="p">(</span><span class="n">vboCount</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">vbo_</span><span class="p">);</span>
<span class="n">glBindBuffer</span><span class="p">(</span><span class="n">GL_ARRAY_BUFFER</span><span class="p">,</span> <span class="n">vbo_</span><span class="p">);</span>
<span class="n">glBufferData</span><span class="p">(</span><span class="n">GL_ARRAY_BUFFER</span><span class="p">,</span> <span class="n">vertex_buffer</span><span class="p">.</span><span class="n">size</span><span class="p">,</span> <span class="n">vertex_buffer</span><span class="p">.</span><span class="n">data</span><span class="p">,</span> <span class="n">GL_STATIC_DRAW</span><span class="p">);</span>

</code></pre></div></div>

<p>For vertex inputs, you have those buffers that are to be setup in a Vertex Array Object, sort of like a container of vertex buffers, to define how the pipeline is going to read the vertex buffers, with something like this:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">vao_</span><span class="p">;</span>
<span class="n">glGenVertexArrays</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">vao_</span><span class="p">);</span>
<span class="n">glBindVertexArray</span><span class="p">(</span><span class="n">vao_</span><span class="p">);</span>

<span class="n">glBindBuffer</span><span class="p">(</span><span class="n">GL_ARRAY_BUFFER</span><span class="p">,</span> <span class="n">vbo_</span><span class="p">);</span>

<span class="n">glVertexAttribPointer</span><span class="p">(</span>
  <span class="n">vertex_input_location</span><span class="p">,</span> <span class="c1">// in the shader (location = 0) </span>
  <span class="n">vertexAttribData</span><span class="p">.</span><span class="n">size</span><span class="p">,</span> <span class="c1">// how many of the primitive: float =&gt; 1, vec2 =&gt; 2, vec3 =&gt; 3, vec4 =&gt; 4</span>
  <span class="n">vertexAttribData</span><span class="p">.</span><span class="n">type</span><span class="p">,</span>
  <span class="n">GL_FALSE</span><span class="p">,</span>
		<span class="n">vertexAttribData</span><span class="p">.</span><span class="n">stride</span><span class="p">,</span>
		<span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">vertexAttribData</span><span class="p">.</span><span class="n">offset</span><span class="p">);</span>
<span class="n">glEnableVertexAttribArray</span><span class="p">(</span><span class="n">vertexAttribData</span><span class="p">.</span><span class="n">index</span><span class="p">);</span>
<span class="c1">//when using instancing</span>
<span class="n">glVertexDivisor</span><span class="p">(</span><span class="n">vertexAttribData</span><span class="p">.</span><span class="n">index</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</code></pre></div></div>
<p>All this is abstracted in the VertexInput class with the Buffer abstraction:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">template</span><span class="o">&lt;</span><span class="n">GLenum</span> <span class="n">target</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">Buffer</span> <span class="p">{</span>
<span class="nl">public:</span>
  <span class="kt">void</span> <span class="n">Bind</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
  <span class="kt">void</span> <span class="n">Load</span><span class="p">();</span>
  <span class="cm">/**
   * @brief Allocate and upload the data in the buffer
   */</span>

  <span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
  <span class="kt">void</span> <span class="n">UploadRange</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">span</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">T</span><span class="o">&gt;</span> <span class="n">range</span><span class="p">,</span> <span class="n">GLenum</span> <span class="n">usage</span> <span class="o">=</span> <span class="n">GL_STATIC_DRAW</span><span class="p">);</span>
  <span class="cm">/**
   * @brief Update the data in the buffer (to be used after using UploadRange
   */</span>
  <span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
  <span class="kt">void</span> <span class="n">UpdateRange</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">span</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">T</span><span class="o">&gt;</span> <span class="n">range</span><span class="p">,</span> <span class="n">GLintptr</span> <span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">GLenum</span> <span class="n">usage</span> <span class="o">=</span> <span class="n">GL_STATIC_DRAW</span><span class="p">);</span>
<span class="p">};</span>

<span class="k">using</span> <span class="n">VertexBuffer</span> <span class="o">=</span> <span class="n">Buffer</span><span class="o">&lt;</span><span class="n">GL_ARRAY_BUFFER</span><span class="o">&gt;</span><span class="p">;</span>
<span class="k">using</span> <span class="n">IndexBuffer</span> <span class="o">=</span> <span class="n">Buffer</span><span class="o">&lt;</span><span class="n">GL_ELEMENT_ARRAY_BUFFER</span><span class="o">&gt;</span><span class="p">;</span>

<span class="k">struct</span> <span class="nc">VertexBufferAttribute</span> <span class="p">{</span>
  <span class="n">GLuint</span> <span class="n">location</span><span class="p">;</span>
  <span class="n">GLint</span> <span class="n">size</span><span class="p">;</span>
  <span class="n">GLenum</span> <span class="n">type</span><span class="p">;</span>
  <span class="n">GLsizei</span> <span class="n">stride</span><span class="p">;</span>
  <span class="kt">size_t</span> <span class="n">offset</span><span class="p">;</span>
  <span class="kt">bool</span> <span class="n">is_instanced</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">VertexInput</span> <span class="p">{</span>
 <span class="nl">public:</span>
  <span class="kt">void</span> <span class="n">Load</span><span class="p">();</span>
  <span class="kt">void</span> <span class="n">BindVertexBuffer</span><span class="p">(</span><span class="k">const</span> <span class="n">VertexBuffer</span><span class="o">&amp;</span> <span class="n">vbo</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">span</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">VertexBufferAttribute</span><span class="o">&gt;</span> <span class="n">attributes</span><span class="p">);</span>
  <span class="kt">void</span> <span class="n">BindIndexBuffer</span><span class="p">(</span><span class="k">const</span> <span class="n">IndexBuffer</span><span class="o">&amp;</span> <span class="n">ebo</span><span class="p">);</span>
  <span class="kt">void</span> <span class="n">Bind</span><span class="p">();</span>
<span class="p">};</span>
</code></pre></div></div>
<ul>
  <li><code class="language-plaintext highlighter-rouge">VertexBufferAttribute::is_instanced</code> allows Buffer to be binded as per instance buffer as we don’t have support for SSBO in OpenGL ES 3.0 (section below).</li>
</ul>

<h3 id="textures">Textures</h3>

<p>The logic for texture in OpenGL is counter-intuitive. One has to understand the difference between texture name (the handle given by <code class="language-plaintext highlighter-rouge">glGenTextures</code>) and the texture unit (<code class="language-plaintext highlighter-rouge">GL_TEXTURE0</code> for example). It confuses a lot of my students each year. So binding a texture to a uniform sample looks like this:</p>
<pre><code class="language-C++">glUniform1i(uniform_location, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE2D, my_texture);
</code></pre>
<p>This is put in a function in the pipeline to look this:</p>
<pre><code class="language-C++">pipeline_.SetTexture("uniform_name", my_texture, 0);

//with implementation looking like this:
void Pipeline::SetTexture(std::string_view uniform_name, const Texture&amp; texture, int texture_unit)
{
    const auto uniformLocation = GetUniformLocation(uniformName);
    glUniform1i(uniformLocation, textureUnit);
    glActiveTexture(GL_TEXTURE0 + textureUnit);
    texture.Bind(); 
}
</code></pre>

<p>For loading, in the beginning of the module, we start with <a href="https://github.com/nothings/stb"><em>stb_image</em></a> to load JPG, PNG, BMP, etc… This header-only library decompresses the images such that we can upload them to the GPU, something that looks like this:</p>
<pre><code class="language-C++">image.pixels = stbi_load(imagePath.data(), &amp;image.width, &amp;image.height, &amp;image.comp, 0);
	
glGenTextures(1, &amp;my_texture);
glBindTexture(GL_TEXTURE2D, name_);
switch (image.comp)
	{
	case 3:
		glTexImage2D(GL_TEXTURE2D, 
      0, GL_RGB, image.width, image.height, 
      0, GL_RGB, GL_UNSIGNED_BYTE, image.pixels);
		break;
	case 4:
		glTexImage2D(GL_TEXTURE2D, 
      0, GL_RGBA, image.width, image.height, 
      0, GL_RGBA, GL_UNSIGNED_BYTE, image.pixels);
		break;
</code></pre>
<p>All this is course hidden in a <code class="language-plaintext highlighter-rouge">Texture</code> abstraction with <code class="language-plaintext highlighter-rouge">Load</code> and <code class="language-plaintext highlighter-rouge">Bind</code>. Then for cubemaps, we add:</p>
<pre><code class="language-C++">class Texture
{
  public:
  //[...]
  void LoadCubemaps(std::span&lt;const std::string_view, 6&gt; paths);
}
</code></pre>
<p>The <code class="language-plaintext highlighter-rouge">Texture</code> holds the “target” (<code class="language-plaintext highlighter-rouge">GL_TEXTURE2D</code> or <code class="language-plaintext highlighter-rouge">GL_TEXTURE_CUBE_MAP</code>, why not texture3d?) and then the <code class="language-plaintext highlighter-rouge">Bind</code> will bind the correct target.</p>

<h3 id="model-loading">Model Loading</h3>
<p>We use <a href="https://www.assimp.org/">Assimp</a> as it simplifies the setup of the loading of 3d models and also allows to import and export in a lot of different formats. This would look like this:</p>
<pre><code class="language-C++">
void Model::LoadModel(std::string_view path)
{
  Assimp::Importer importer;
  scene = importer.ReadFile(path.data(),
                                aiProcess_Triangulate | aiProcess_FlipUVs |
                                aiProcess_GenNormals |
                                aiProcess_CalcTangentSpace);

  if (!scene || scene-&gt;mFlags &amp; AI_SCENE_FLAGS_INCOMPLETE || !scene-&gt;mRootNode)
  {
      ...
  }
  ProcessNode(scene-&gt;mRootNode, scene);
}

void Model::ProcessNode(aiNode* node, const aiScene* scene)
{
    // process all the node's meshes (if any)
    for (unsigned int i = 0; i &lt; node-&gt;mNumMeshes; i++)
    {
        aiMesh* mesh = scene-&gt;mMeshes[node-&gt;mMeshes[i]];
        meshes_.push_back(ProcessMesh(mesh, scene));
    }
    // then do the same for each of its children
    for (unsigned int i = 0; i &lt; node-&gt;mNumChildren; i++)
    {
        ProcessNode(node-&gt;mChildren[i], scene);
    }
}

Mesh Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
{
  // Processing the meshes
</code></pre>
<p>It is also used in the same way in <a href="learnopengl.com">learnopengl.com</a>, so the students can have a working implementation to check when they encounter a mistake.</p>

<h3 id="draw-commands">Draw commands</h3>

<p>In OpenGL, the driver is adding our commands into its own command buffer. This forbids multi-threading draw commands generation (compare to more modern API) and it gives this feeling that drawing happens immediately. Because of <a href="learnopengl.com">learnopengl.com</a>, most of my students put their draw call in the <code class="language-plaintext highlighter-rouge">Mesh</code> contained in their <code class="language-plaintext highlighter-rouge">Model</code>. So it looks like this:</p>
<pre><code class="language-C++">void Model::Draw(const Pipeline&amp; pipeline)
{
  pipeline.Bind();
  for(auto&amp; mesh: meshes)
  {
    mesh.Draw(pipeline);
  }
}

void Mesh::Draw(const Pipeline&amp; pipeline)
{
  material_.Bind(pipeline); //binding all textures to their samples
  glDrawElements(GL_TRIANGLES, indices_count, GL_UNSIGNED_INT, nullptr);
  //or depending on the abstraction
  vao_.Draw();
}

</code></pre>
<p>After working on my computer graphics editor (I wrote a blog post <a href="/jekyll/update/2023/11/29/compgrapheditorv1.html">here</a>), I really prefer a separated approach where a scene has materials (pipeline + texture reference) and meshes, and that models do not exist as scene abstraction. However again, it is not about making a data-driven renderer, but just implement a 3d scene.</p>

<h3 id="render-passes--framebuffer">Render passes &amp; Framebuffer</h3>

<p>Finally, the last big OpenGL core concept is about multi passes. The first half of the module is spent in one pass. Post-processing is the first example of multi-pass, where my students have to use a framebuffer to draw their scene, and then use another pipeline and the render targets as a sampled texture. The first example is several variations of post-processing (like grayscale, inversing the color). Setting a certain framebuffer in OpenGL is starting a new render pass (RenderDoc will usually write it as Color Pass). With the students, we worked out an <code class="language-plaintext highlighter-rouge">Framebuffer</code> abstraction that use a <code class="language-plaintext highlighter-rouge">FramebufferCreateInfo</code> struct (very similar to Vulkan) with the color attachments and the depth color:</p>
<pre><code class="language-C++">
struct ColorAttachmentInfo {
  GLenum target = GL_TEXTURE_2D;
  GLint internal_format = GL_RGB8;
  bool is_active = false;
};

struct DepthAttachmentInfo {
  GLenum target = GL_TEXTURE_2D;
  GLenum internal_format = GL_DEPTH24_STENCIL8;
  bool is_active = false;
  bool is_rbo = true;
};

struct FramebufferCreateInfo {
  //OpenGL has maximum 16 color attachments
  std::array&lt;ColorAttachmentInfo, 16&gt; color_attachment_infos{};
  DepthAttachmentInfo depth_stencil_attachment_info{};
  core::Vec2I size{};
};

class Framebuffer
{
void Load(const FramebufferCreateInfo&amp; info);
void Bind();
static void BindBackbuffer();
const Texture&amp; color_attachments(int index);
const Texture&amp; depth_attachment();
};
</code></pre>

<ul>
  <li>RBO (render buffer object, yet another type of buffers) is sort of like a GPU write-only kind of resource.</li>
</ul>

<p>Usually, we want one or several color attachments as <code class="language-plaintext highlighter-rouge">Texture</code> and a depth-buffer as RBO, except for a shadow pass, where we want a depth-only pass (that outputs to a <code class="language-plaintext highlighter-rouge">Texture</code>).</p>

<p>A big step in using framebuffer in the module comes with the introduction to HDR (High-Dynamic Range), basically the fact that we can use color in a bigger range than [0 - 255]. One of such applications is deferred rendering and the creation of G-Buffer (Geometry Buffer that contains position, normals and baseColor for example) to minimize the amount of lighting calculation per pixel. So first, you render a color pass on a G-Buffer with several color attachments each containing important geometry value (position, normal, base color) and then there is a light pass where lighting is calculated per pixel. This technique see a huge performance improvement when drawing a lot of lights on the screen. With our abstraction, you just need to use a HDR type of internal format (for example <code class="language-plaintext highlighter-rouge">GL_RGB16F</code>).</p>

<p>Of course, not all monitor outputs HDR, so we need a way to go back to LDR (Low-Dynamic Range, [0 - 255]) and this is done at the end of the lighting pass fragment shader:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//Reinhard tonemapping</span>
<span class="kt">vec3</span> <span class="n">result</span> <span class="o">=</span> <span class="n">lighting</span> <span class="o">/</span> <span class="p">(</span><span class="n">lighting</span> <span class="o">+</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">));</span>
<span class="n">FragColor</span> <span class="o">=</span> <span class="kt">vec4</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="missing-modern-opengl-features-in-es-30">Missing modern OpenGL features in ES 3.0</h2>

<p>Using OpenGL ES 3.0 (to allow use of WebGL 2.0) has still a cost though. Some core concepts of modern rendering are explained in course content but not implemented (except if the students want to get rid of WebGL2 compatibility).</p>

<h3 id="ssbo">SSBO</h3>

<p>As of OpenGL 4.3 and OpenGL ES 3.1, we can define shader storage buffer object. This is useful to remove the need to pass by the vertex inputs when doing instancing. Of course, we need to had another type of buffer that can be binded to an uniform. This would look like this:</p>
<pre><code class="language-C++">//in C++
glBufferData(GL_SHADER_STORAGE_BUFFER, instancing_buffer.size, instancing_buffer.data, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding_point, ssbo);

//in shader
layout(std430, binding = 3) buffer layoutName
{
    vec4 data_SSBO[];
};

void main()
{
  vec4 instancing_data = data_SSBO[gl_InstanceID];
}
</code></pre>

<h3 id="compute-shader">Compute shader</h3>
<p>Compute shader only appeared in OpenGL ES 3.1 (so not WebGL2). Coming back to the post-processing example explained before, instead of giving the color attachment of the scene as a <code class="language-plaintext highlighter-rouge">sampler2D</code>, we could give it as an <code class="language-plaintext highlighter-rouge">image2D</code> and actually do the post-processing in compute. Typically, every the-whole-screen kind of shader are easier to implement with a compute pipeline with a single shader, rather than a graphics pipeline with a plane in NDC (Normal Device Coordinate) going from [-1, 1] just to recover the whole screen in the vertex shader and implementing the transformation in the fragment shader.</p>

<h3 id="spir-v">SPIR-V</h3>

<p>Starting with OpenGL 4.6 (not even available in OpenGL ES 3.2), we can load SPIR-V instead of plain text GLSL. This allow for quick loading of shader as the format is binary and it allows to add a compilation step to shaders (something we sort of do anyway with glslandValidator).</p>

<h3 id="bindless">Bindless</h3>

<p>Modern OpenGL desktop allows for “bindless” rendering where textures are not binded to a texture unit but can be referenced via a 64-bit handle stored in a buffer (enabling access to virtually an unlimited textures without expensive state changes) with the extension <code class="language-plaintext highlighter-rouge">ARB_bindless_texture</code> (supported on Nvidia and AMD, but not on Intel).</p>

<p>For buffers, <code class="language-plaintext highlighter-rouge">ARB_buffer_storage</code> (core in OpenGL 4.4 and as an extension <code class="language-plaintext highlighter-rouge">EXT_buffer_storage</code> since OpenGL ES 3.1) allows to keep the GPU pointer to enables persistent mapped buffers, avoiding repeated map/unmap cycles.</p>

<p>This is not available in OpenGL ES 3.0, so the workarounds are usually texture atlases or texture arrays, combined with batching (draw calls with different vertex inputs, but the same textures) to avoid the costly switch of textures.</p>

<h3 id="indirect-drawing">Indirect Drawing</h3>
<p>Another powerful feature that is unavailable in OpenGL ES 3.0 is indirect drawing. In our module, the CPU is issuing all the draw calls with <code class="language-plaintext highlighter-rouge">glDrawArrays</code> and <code class="language-plaintext highlighter-rouge">glDrawElements</code> or their instanced versions.
<code class="language-plaintext highlighter-rouge">glDrawArraysIndirect</code> and <code class="language-plaintext highlighter-rouge">glDrawElementsIndirect</code> are part of core since OpenGL 4.0 and OpenGL ES 3.1 allowing draw parameters to come from a <code class="language-plaintext highlighter-rouge">GL_DRAW_INDIRECT_BUFFER</code>. More power comes with Multi-Draw Indirect (core in OpenGL 4.3 and extension <code class="language-plaintext highlighter-rouge">EXT_multi_draw_indirect</code> since OpenGL ES 3.1), which allows through <code class="language-plaintext highlighter-rouge">glMultiDrawArraysIndirect</code> and <code class="language-plaintext highlighter-rouge">glMultiDrawElementsIndirect</code> calls to dispatch entire arrays of draw commands which combined with compute shaders that enables fully GPU-driven rendering pipelines where the GPU performs frustum culling, LOD selection, and occlusion culling. <code class="language-plaintext highlighter-rouge">ARB_indirect_parameters</code> allows even the draw count itself be GPU-determined.</p>

<h3 id="debugging">Debugging</h3>
<p>For my students, what used to be the most time they were spending was with <a href="https://renderdoc.org/">RenderDoc</a>. Now I see more students just using LLM to check their code and see what is wrong (even one of my students who said “they told me there was an error there”). However, the exam explicitly forbids LLM to solve, so my students still have to go through the debugging process by themselves if they want to pass.</p>

<p>Starting with OpenGL 4.3, <code class="language-plaintext highlighter-rouge">KHR_debug</code> is part of core and allows to have the driver sends error, warning, debug messages through a callback. As OpenGL 4.3 is fully compatible, I realized while writing this blog post that we could use it on Desktop to have better debug capabilities (registering OpenGL objects with <code class="language-plaintext highlighter-rouge">glObjectLabel</code> with a macro for example).</p>

<h2 id="missing-features-from-modern-api">Missing features from Modern API</h2>
<p>Sticking with OpenGL means also missing a lot about newer features of modern API like Vulkan and DX12. Of course, modern API are more than just features, they are also another mental model. But let’s focus on the features.</p>

<h3 id="raytracing">Raytracing</h3>
<p>With all their marketing on RTX, Nvidia surely brought raytracing in every gamer’s mouth when talking about computer graphics. Of course, raytracing exists since before the advent of graphics card. However in 2018, DXR was released, allowing for hardware raytracing on the GPU using new kind of shaders (closest-hit, miss, raygen, etc…) and Vulkan followed with the Nvidia extension and then the generic extension.</p>

<p>Most games are still living in a hybrid rendering world where some parts are done in rasterization and some are done in raytracing, with custom options for players. In its <a href="https://asawicki.info/articles/state_of_gpu_hardware_2025.php">State of GPU Hardware</a>, Dmytro “Boolka” Bulatov mentions that only games like <em>Indiana Jones and the Great Circle</em> (released in late 2024) that puts DXR as a requirement. So for me, as a teacher, I still showcase the hardware raytracing API, its advantages and disadvantages, and how the future looks like, but as it is an introduction to computer graphics, I can’t ask all my students to have to implement a hardware raytracing pipeline.</p>

<h3 id="multithreaded-rendering">Multithreaded rendering</h3>
<p>When thinking about modern CPU architecture, we are living in a multi-core world now, but most of what we program in games is still happening in one thread on one core. To truly unlock the power of our CPU, we can divide our work between several threads. A common pattern is to have a threading model with Game/Cull/Render on the CPU, or having the loading done async and those still work with OpenGL.</p>

<p>However, certain games (like <a href="https://www.youtube.com/watch?v=IUiNUky-ibM">Halo Infinite</a>) require the use of multiple threads to generate all the GPU commands. This is definitely possible with DX12 and Vulkan, but not in OpenGL where the render states are in the driver implementation in one thread.</p>

<h2 id="the-results">The results</h2>

<p>Here are some of the works of my students during this module over the years:</p>
<ul>
  <li><a href="https://blog.stowy.ch/posts/how-i-implemented-a-deferred-pbr-renderer-in-opengl/">Fabian Huber</a></li>
  <li><a href="https://sstyles93.github.io/projects/openglscene/">Samuel Styles</a></li>
  <li><a href="https://chocolive24.github.io/blog/how-i-created-an-opengl-3d-scene/">Olivier Pachoud</a></li>
  <li><a href="https://cochta.github.io/work/nested/renderer">Constantin Verine</a></li>
  <li><a href="https://remlamb.github.io/3d_scene/scene.html">Remy Lambert</a></li>
  <li><a href="https://mebearwhodis.github.io/projects/opengl-renderer">Maxime Roch</a></li>
</ul>

<h2 id="what-about-a-framework">What about a framework?</h2>
<p>I love SDL GPU API. I am currently porting my computer graphics editor to it, and it allows to target Vulkan, DirectX and Metal at the same time. However, there is still a bit of bloat for an introduction course for me to use SDL GPU. I heard some good things about bgfx also and raylib as well. WebGPU, on top of adding a new shader language, is very similar to Vulkan 1.0 that caused me more pain that joy.</p>

<p>When having students working on their samples and they do a mistake in OpenGL, it can often be pretty clear why there is a mistake, but facing a framework, the code of the library has also to be taken into account. And my goal is to teach them a graphics API and build abstractions on top of it.</p>

<h2 id="conclusion">Conclusion</h2>

<p>OpenGL ES 3.0 has still a lot of advantages to be taught even in 2026 (especially WebGL2), at the cost of going through the struggle of the legacy of this API. 14 years is a lot of time in Computer Graphics. For example, at the end of his <a href="https://asawicki.info/articles/graphics_apis_yesterday_today_tomorrow_en.php">graphics API blog post</a>, Adam Sawicki does not recommend OpenGL as a choice (as it is rarely used anymore).</p>

<p>This course has allowed throughout the years all of my students to discover how graphics programming work. For some of them, it was their first step in their computer graphics career and I am glad that I helped them in that. 
I want to write another blog post about why we are not yet switching to Vulkan (talking about synchronization, PSO, driver overhead), as I have a few things to say about this specific API. Don’t hesitate to comment on the social media or just send me an e-mail (I am interested to know how other teachers are doing and what people working in the industry want to see from graphics programming students). Here the rotating cubes demo running on Android:</p>

<p><img src="/images/2026/cube_android.jpg" alt="Showing rotating cubes on Android" /></p>]]></content><author><name></name></author><category term="graphics" /><category term="cpp" /><summary type="html"><![CDATA[Loading...]]></summary></entry><entry><title type="html">2025 wrapup</title><link href="https://eliasfarhan.ch/cpp/thoughts/2026/01/05/wrapup.html" rel="alternate" type="text/html" title="2025 wrapup" /><published>2026-01-05T00:00:00+00:00</published><updated>2026-01-05T00:00:00+00:00</updated><id>https://eliasfarhan.ch/cpp/thoughts/2026/01/05/wrapup</id><content type="html" xml:base="https://eliasfarhan.ch/cpp/thoughts/2026/01/05/wrapup.html"><![CDATA[<p><img src="/images/2025/dumb_selfie.jpg" alt="dumb selfie" /></p>

<!--more-->

<p>2025 is coming to an end. A lot of things happened for me. I wanted to write a wrap-up and project myself into 2026 with all the new projects.</p>

<h2 id="working-at-6itdev">Working at 6it.dev</h2>

<p>After the initial rush of end of 2024 right after CppCon, I needed to rebalance my work-life balance to be able to continue to work at 6it. I can’t really talk about what I am working on, but I could take the time to learn a lot about C++ and API design, as well as production management that I put into practice at SAE Institute with my students.</p>

<p>However, one of the big challenges of the beginning of the year was to not push myself to work too much. When freelancing, as we might get tired, we’re thinking of taking the afternoon off, but a simple multiplication of the hour with the hourly rate gives us a cost of a break. I needed to learn not to take this into consideration and that if I needed a break, I should take that break. I think that I have found the balance that works for me.</p>

<h2 id="addon">ADDON</h2>

<p><img src="/images/2025/addon/addon2025.jpg" alt="addon" /></p>

<p>This year I went again to ADDON in Rennes, France, and for detail, you can read my post about it <a href="/jekyll/update/2025/04/19/addon.html">here</a>. I really enjoyed the time there and to learn new things. I was very thankful to meet <a href="https://www.linkedin.com/in/thomas-poulet/">Thomas Poulet</a> who famously wrote the <a href="https://blog.thomaspoulet.fr/posts/ni-no-kuni-2-frame-analysis/">frame analysis of Ni No Kuni 2</a>. I gave a translated and updated version of my CppCon 2024 talk about multiplayer game and using rollback. It was also a nice opportunity to take some days off in Paris.</p>

<h2 id="marathon-de-genève">Marathon de Genève</h2>

<p><img src="/images/2025/Marathon_Geneve_Carte.jpg" alt="marathon de Geneve - map" /></p>

<p>Running a marathon was last year resolution and in the end, I ran the Fribourg by Night trail which was a lot of fun, but also a good realization that after about 2 hours, my body is completely empty and I need a lot of supplies during a long race.</p>

<p>So I registered for the Geneva Marathon in May as a challenge, not really knnowing how it would look like. Maybe one regret is that the training might have not been to the best for a marathon as I was counting on my mental to make me go through.</p>

<p><img src="/images/2025/marathon_geneve_start.jpg" alt="marathon de geneve - start" /></p>

<p>So fast forward to May the 11th, in the morning, a very sunny day, packed with supplies at the starting line. When the race began, I did not rush, did not try to follow the people too much, trusted my heartbeat and my footstep. All went good, until I crossed the middle mark.</p>

<p><img src="/images/2025/mont_blanc.jpg" alt="mid race - mont-blanc" /></p>

<p>My legs started to cramp, so I needed to slow down, making the struggle for the marathon really start. This race was really emotional, I was switching music style when needed, I could see the Mont Blanc. Except for the feed zone, I would not stop running, even though I was running pretty slowly. The closer that I was from Geneva the harder it got. Finally I could see the lake.</p>

<p><img src="/images/2025/the_lake.jpg" alt="The lake" /></p>

<p>But my pain was not over as they were still a bit less than 10k to run. My gf sheered me up in Genève-Plage. Then N. Siorak ran with me in Bel-Air. The hardest part was the last 2k with a loop in Les Pâquis where I was running with someone that was going just a bit too fast for me. I sprinted to the end! ANd I finished my first marathon. It took me about half an hour to recover (my belly was not happy). And I did 5:09:18, next time better!</p>

<p><img src="/images/2025/marathon_geneve_medal.jpg" alt="medal" /></p>

<h2 id="sierre-zinal">Sierre-Zinal</h2>

<p><img src="/images/2025/sierre_zinal_view.jpg" alt="4 mountains" /></p>

<p>People who train marathon usually have a small downtime or depression after the event, as the main goal of the year is done, but I registered for an even crazier race this year after the marathon, so I could not stop training just yet.</p>

<p>Registration happened in April and because of the amount of people who wants to register, I needed to be ready on time. When the register button appeared, I instantly clicked and needed to wait 1 hour and 15 minutes to be able to register. I finally had the confirmation, so off to the race.</p>

<p>Sierre-Zinal happened on August the 9th, during a heatwave. Fortunately, my race started at 6:45am, so temperature was at around 20°C. I needed to take a train at 2am. When on board, I realized that I forgot my small trail bag that I had during Geneva Marathon… Unfortunately, I could not go back at home to get my bag and nobody could go back to my place to get it for me and bring it to me on time for the race. In the end, I found a way to bring most of my heavy stuff to the arrival, but used my bigger bag to go up for the first part.</p>

<p><img src="/images/2025/sierre_zinal_start.jpg" alt="beginning" /></p>

<p>The first part of Sierre-Zinal goes from Sierre to Chandolin. It goes up about 1500m so it is considered the first half of the race. The slope is very intense as soon as we leave Niouc. I practice this part with N. Siorak, so I knew what to expect. However, there is a little but important detail…</p>

<p>My mom was expecting to arrive in Chandolin at 9:55, with me leaving at 6:45, I had 3 hours to go up. However, when practicing, it took 3 hours and a half from Sierre train station, and we were walking and taking break. So there was a point during my climb that I needed to slow down, not running, otherwise I would have needed to wait half an hour in Chandolin to give my bag. Like in the hike, I could enjoy the view and the moment.</p>

<p><img src="/images/2025/sierre_zinal_the_view2.jpg" alt="the view" /></p>

<p>In the end, I waited about ten minutes, quick supply fill, giving my back, putting some sunscream and I was ready to go. The real race started now. I felt fresh after going pretty slow on the first “half”, but there was still a lot of kilometers to do, and the sun was hitting hard. Fortunaley, we were high in altitude ~2337m for the hotel Weisshorn.</p>

<p><img src="/images/2025/weisshorn.jpg" alt="weisshorn" /></p>

<p>It kept going up! Until finally, it went down, Zinal coming closer. I was feeling much better than the marathon as I was alternating between running and walking. But the hardest part for me was the sudden way down to Zinal. I should definitely practice it for next time. It was so steep and my legs were too sour that I could not go as fast as I wanted. Finally, arriving in the village, I simply sprinted with the slope and arrived after 7:21:40.</p>

<p><img src="/images/2025/sierre_zinal_arrival.jpg" alt="Arrival" /></p>

<p>I told my mom I would arrive at around 3:30pm, but I arrived at 2:30pm. Like the marathon, I needed half an hour to be able to walk again. After a meal, we finally took the bus to bring us back to Sierre where the heat was waiting for us. You can find the video of my race <a href="https://t6q6n.app.goo.gl/c71AY">here</a>.</p>

<h2 id="bic-fest--awards">BIC Fest + awards</h2>

<p><img src="/images/2025/bic/20250817_114742.jpg" alt="bic fest" /></p>

<p>A few days after Sierre-Zinal, I took the plane to South Korea, to showcase Beach Slap at BIC festival in Busan (I wrote about it <a href="/jekyll/update/2025/08/19/bicfest.html">here</a>). Before that, my gf helped me localize the game to korean, so Beach Slap became 해변의 귓방망이 (which sort of translate to “Slap to ear of the beach”). This title made it very successful, as the title is very funny in korean (everytime I tell the title, I get a big laugh out of korean players).</p>

<p><img src="/images/2025/bic_slap.jpg" alt="getting an award" /></p>

<p>We got the E-sport Excellence Award which is rewarding multiplayer competitive game (and that’s me giving a slap to the public). It was the first award that we ever got at Team KwaKwa which was a first sign for us that we were onto something with Beach Slap. There is still a long way ahead before the release, but getting a recognition is always nice.</p>

<h2 id="korean-trip">Korean trip</h2>

<p><img src="/images/2025/korea/ktrip_busan_temple.jpg" alt="temple by the sea" /></p>

<p>I enjoy some good time in Korea afterwards. Thanks to 6it full-remote policy, I could enjoy a half-work half-holiday trip. I went all around Korea, from Gangneug to Gwangju, Gyeongju and Jeonju, of course Seoul. I could buy a few second-hand games, update to the Nintendo Switch 2. The heat was often unbearable, but AC makes it livable, especially to sleep. I ate so much… I saw my first baseball match, went to the sea, did a small hike in Daegu, did countless hours in buses and trains. Looking forward for the next time.</p>

<h2 id="herofest--sae-convention--stunfest">Herofest + SAE convention + Stunfest</h2>

<p><img src="/images/2025/herofest_beachslap.jpg" alt="october festival" /></p>

<p>In October, we went to showcase Beach Slap at Herofest in Bern and in Rennes for Stunfest. I already wrote about it <a href="/jekyll/update/2025/10/27/stunfest_herofest.html">here</a>. Seeing David Wise was a high point of this year, of course, it reminded me of Soup Raiders and I sohuld probably write about what I think made the project stop (in terms of design). We won a prize again, which surprised us again, but it means that we are onto seomthing with Beach Slap.</p>

<p><img src="/images/2025/train.jpg" alt="train photo" /></p>

<p>I had a week end of October when I took the train to go to Leukerbad, Koln, Geneva, Paris and Rennes. I like travelling by train, but this week was intense. I think what I really like with train’s travel is that I can just take my computer and start to code or write, which is harder for me in a plane. Also I can take a break and just look outside. Stunfest and SAE convention have something of nostalgia for me. I remember the campus in Koln the first year I went to SAE convention being a rookie and Stunfest around the same time when I was starting to showcase Splash Blast Panic. Those two events have a different taste now, we won the SAE awards this year and the talks were all about AI and Stunfest is way smaller in scope than back in 2018.</p>

<h2 id="graphics-programming-conference">Graphics Programming Conference</h2>

<p><img src="/images/2025/graphics_conf.jpg" alt="graphics programming conference" /></p>

<p>I already wrote about it <a href="/jekyll/update/2025/11/21/graphics_programming_conf.html">here</a>, but what I really liked with this conference, compared to other conferences I went to, was that it was pushing my technical boundries. I want to try new things in computer graphics, but I also want to try new things in teaching! As the tech world is changing to more AI and CPU and GPU now adds NPU, I am also questionning how and where I should push my efforts. In the end, I am a game developer and I just want to make new games and sometimes also tell stories. So the new cool tech is only interesting for me from two points of view, as a teacher or a game developer, and both have different means and goals. In the end, I came back home with my head full of ideas.</p>

<h2 id="spelkollektivet">Spelkollektivet</h2>

<p><img src="/images/2025/spelkollektive_own.jpg" alt="spelkollektivet" /></p>

<p>While my gf was going to see her family during the Winter Holidays, I decided to spend this period at Spelkollektivet, a community of game developers living in the Swedish countryside, sort of like an incubator of game companies. While the perspective of being with complete strangers for one week and half sort of pertrified me, I still decided to go, such that I could spend some time to work on my own projects: Beach Slap and Welcome at Houdini’s.</p>

<p><img src="/images/2025/spell_office_without_snow.jpg" alt="office" /></p>

<p>This was also the opportunity to get back to working out, as there was a gym available and I decided that I would go there everyday. I tried for the first time to run on a treadmill, one time with full incline and one time as flat surface. I did some sword practice as well, which was a lot of fun.</p>

<p><img src="/images/2025/spell_sword.jpg" alt="sword" /></p>

<p>It felt good to be back at the gym, lifting some weights, pushing myself. As I did not need to focus on anything else than my games and the gym, I felt like in paradise. It even snowed.</p>

<p><img src="/images/2025/spell_office.jpg" alt="snow in spelkollektivet" /></p>

<p>I was afraid that I would mostly be on my own as I don’t like to mingle anymore. I think that came from my big trip to Japan in 2022 (some photos <a href="/jekyll/update/2022/09/05/japan-trip.html">here</a>). I spent there 5 weeks, mostly by myself and enough though solitude was hard, I kinda got used to be with myself, work on my things ad I feel frustrated when gets on my way. But more recently, I started to question myself and got to spend more time with dear friends, going out of my bubble (Spelkollektivet being one of those decisions). Finally, on the last day when departing, I had a few homies coming to say good bye, and I actually felt happy to have spent some time there. As I said to James: “I was productive and more happy to be at Spelkollektivet than at home.”</p>

<h2 id="next-year">Next year?</h2>

<p>So that was 2025, what about 2026? I plan to release a few games, Beach Slap being the main one, Welcome at Houdini’s and some other small projects. I think I finally pass the depression period after Soup Raiders. I love making games. I just spent a whole week and a half doing that in Sweden and I could have go on forever (but reality comes back). It will also be a year of change, I don’t know exactly how, but I want to try some things (like this blog here). I need to keep in shape, what is going to be my new objectives, marathon and/or trianthlon of Lausanne? We’ll see.</p>

<h2 id="conclusion">Conclusion</h2>

<p>At the end of 2025, I felt lazy. I did not do enough sport (while I still did a marathon and a mountain race), I did not work hard enough (even though I have technically three jobs) or I did not improve enough. There is a point where I can’t push myself more than what’s possible, so I need to accept my shortcomings. Let me continue to try more for 2026.</p>

<p><img src="/images/2025/end_marathon.jpg" alt="dumb selfie or photo" /></p>]]></content><author><name></name></author><category term="cpp" /><category term="thoughts" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Graphics Programming Conference 2025</title><link href="https://eliasfarhan.ch/graphics/travel/2025/11/21/graphics_programming_conf.html" rel="alternate" type="text/html" title="Graphics Programming Conference 2025" /><published>2025-11-21T00:00:00+00:00</published><updated>2025-11-21T00:00:00+00:00</updated><id>https://eliasfarhan.ch/graphics/travel/2025/11/21/graphics_programming_conf</id><content type="html" xml:base="https://eliasfarhan.ch/graphics/travel/2025/11/21/graphics_programming_conf.html"><![CDATA[<p><img src="/images/2025/industry_badge.jpg" alt="First image" /></p>

<!--more-->

<p>This week, I went to Breda to attend the Graphics Programming Conference, thanks to SAE Institute. My goal was to delve into more advanced Computer Graphics before starting the module the week after.</p>

<p>Breda is a lovely city, south of the Netherlands, that you can easily access from Schiphol airport by train. Originally, I wanted to go by train from Lausanne with TGV and Eurostar, but in the end it did not work out with my schedule.</p>

<p><img src="/images/2025/breda_canal.jpg" alt="Photo of Breda" /></p>

<p>The conference is divided in three days, with very technical talks (which I love), some sponsored talks and some more “academic” talks. Sometimes, some talks are concurrent, but because they are all recorded, it is pretty easy to just go to the one that is most interesting for us. Overall, the quality of the talks is really high.</p>

<p><img src="/images/2025/conference.jpg" alt="Conference photo" /></p>

<p>As a teacher, those were the talks that I was the most interested in:</p>
<ul>
  <li><strong>Getting to know Slang</strong> by <a href="https://www.linkedin.com/in/shannon-woods-5a43b42/">Shannon Woods</a>: I am more used to GLSL, so the very HLSL-flavor of SLang does not attract me by default, however the whole struct, generic, interface looks very promising for bigger games/engines.</li>
  <li><strong>Root signatures &amp; shader ISA</strong> by <a href="https://www.linkedin.com/in/wesseldegroot">Wessel de Groot</a>: Going a bit low-level would help me be on par on the GPU compared to the GPU.</li>
  <li><strong>After the Tutorial: Where to Continue your Graphics Programming Journey</strong> by <a href="https://www.mshah.io/">Mike Shah</a>. I really liked how Mike diffirentiate the basics from the more advanced features regarding graphics and how he introduced the different abstractions one after the other along the graphics journey.</li>
</ul>

<p>Those talks were directly impactful for me for teaching Graphics Programming to my students. I will definitely share them with my students when they are on youtube.</p>

<p>Of course, going to a conference is also a break in the usual day-to-day life and it allows to have the brain breath and find new ideas, so here are the resulting ideas that came out of different talks:</p>

<p><img src="/images/2025/talk.a-decade-of-seed-lessons-from-10-years-of-r&amp;d-in-games.talk-wide-1x.webp" alt="SEED" /></p>
<ul>
  <li><strong>A Decade of SEED: Lessons from 10 Years of R&amp;D in Games</strong>: It made me think of research in the different areas of rendering, computer vision, game ai, physics, and animation. But also EA SEED is part of a big AAA studio and I was wondering what a research lab could look like for indie games. Currently, we mostly rely on Unreal or Unity or Godot (some still have the courage to use custom engine), but the sharing of custom tools and technology could really be improved and it made me think how I could help other devs around me (RGB conf was a try).</li>
</ul>

<p><img src="/images/2025/talk.eras-in-mobile-graphics.talk-wide-1x.webp" alt="Mobile" /></p>

<ul>
  <li><strong>Eras in mobile graphics</strong>: ARM is putting NPU in GPU and GPU will start to look more like NPU. Something in me dislikes AI, but another part of me is thinking how AI can help solve a lot of problems. Just maybe not at the price of stealing everybody’s work…</li>
</ul>

<p><img src="/images/2025/talk.a-beautiful-hell-path-tracing-in-doom-the-dark-ages.talk-wide-1x.webp" alt="DOOM" /></p>

<ul>
  <li><strong>A Beautiful Hell: Path Tracing in DOOM The Dark Ages</strong>: I really want to experiment more with raytracing and path-tracing, maybe try to find solutions at a smaller scale than DOOM, but a fascinating talk.</li>
</ul>

<p><img src="/images/2025/talk.water-simulation-and-rendering-in-enshrouded.talk-wide-1x.webp" alt="Water" /></p>

<ul>
  <li><strong>Water Simulation &amp; Rendering in Enshrouded</strong>: Very interesting approach to simulate and then render water. Now I want to play with procedural content generation and water.</li>
</ul>

<p><img src="/images/2025/talk.battlefield-6-pushing-visual-fidelity-while-optimizing-for-all-hardware.talk-wide-1x.webp" alt="Battlefield" /></p>

<ul>
  <li><strong>Battlefield 6: Pushing visual fidelity while optimizing for all hardware</strong>. Debug tools! I need to investigate how to make my like programming on the GPU easier. Maybe I can sneak ShaderToHuman in my projects.</li>
</ul>

<p><img src="/images/2025/talk.the-aircraft-of-theseus-shipping-x-plane-for-30-years.talk-wide-1x.webp" alt="X-plane" /></p>

<ul>
  <li><strong>The aircraft of Theseus - Shipping X-Plane for 30 years.</strong> I really enjoyed this talk. It is very interesting to see how the team managed to update an OpenGL <strong><em>2.1</em></strong> engine with external plugins also using OpenGL 2.1 and updating it to Vulkan with Zink.</li>
</ul>

<p><img src="/images/2025/talk.raytracing-voxels-in-teardown-and-beyond.talk-wide-1x.webp" alt="Teardown" /></p>

<ul>
  <li><strong>Raytracing Voxels in Teardown and Beyond</strong>. Again a very interesting talk that feels closer from home for me. Using OpenGL 3.3 and now updating to Vulkan, what an achievement.</li>
</ul>

<p>I really enjoyed the ranges of talks and expertises. There were a lot of AAA studios, but also AA and smaller studios with very different problematics to solve. What I think might be missing is the point of view of the students, seeing some student work showcase, as they are the future of our industry in the end.</p>

<p>All in all, I really enjoyed this second Graphics Programming Conference, learned a ton of stuffs, got inspired for a whole bunch of projects and I am ready to teach my computer graphics module next week!</p>

<p><img src="/images/2025/breda.jpg" alt="Photo of Breda last" /></p>]]></content><author><name></name></author><category term="graphics" /><category term="travel" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Herofest + Stunfest 2025</title><link href="https://eliasfarhan.ch/gamedev/2025/10/27/stunfest_herofest.html" rel="alternate" type="text/html" title="Herofest + Stunfest 2025" /><published>2025-10-27T00:00:00+00:00</published><updated>2025-10-27T00:00:00+00:00</updated><id>https://eliasfarhan.ch/gamedev/2025/10/27/stunfest_herofest</id><content type="html" xml:base="https://eliasfarhan.ch/gamedev/2025/10/27/stunfest_herofest.html"><![CDATA[<p><img src="/images/2025/herofest_beachslap.jpg" alt="First image" /></p>

<!--more-->

<p>This month was eventful for <a href="https://team-kwakwa.com/beach-slap">Beach Slap</a>. First stop was Herofest in Bern. We bought a new TV that we needed to setup on the first day. After this littel rush, we could let the game play. I added a special expo mode in the game that automatically reset the game to the title screen, then after a little while shows a trailer, so I don’t need to manually intervene.</p>

<p>I also got the chance to have the booth right next to a stage where David Wise and his band were playing. David Wise worked on the soundtrack of Soup Raiders (that we unfortunately paused since then) and it was really nice to see him in the real life (instead that on skype). Here is a selfie of both of us:</p>

<p><img src="/images/2025/20251003_173846.jpg" alt="David Wise selfie" /></p>

<p>I was sick most of the weekend, hiding behind my mask and surviving, but I could see the game getting a good reception. Between the game and the music, there was also an indie award. As I was too sick, I asked my colleague to go while I stayed at the booth. He came back with an prize:</p>

<p><img src="/images/HeroFest_25_5_ValiantIndieGameAwards.png" alt="Award photo" /></p>

<p>With <a href="/jekyll/update/2025/08/19/bicfest.html">BIC Fest</a>, that’s two awards in a row. I think that with Beach Slap, we finally arrived to an multiplayer arcade game that is not too weird and has a broader appeal (especially compared to Splash Blast Panic). There is still a lot of work to do, but I am looking forward for next year’s release and see the reactions of people playing it.</p>

<p>Later in the month, I went to Stunfest in Rennes, France. Usually, Stunfest used to happen in May. Funny enough, there was a Japanese player that, back in the day, said that he won Stunfest and when people argued his name was not in the winner’s list, he said that he won the fall-edition of the Stunfest, that did not exist. Well now it does! There have been a lot of changes since the first time I went to Stunfest to showcase a game. This year was a new beginning.</p>

<p><img src="/images/2025/20251025_180041.jpg" alt="Stunfest photo" /></p>

<p>The booth was free, we were a lot of indies from all around France (except me from Switzerland). However, being a multiplayer game, I was not given enough space for 4-player so I was always a bit in the way. There was an evening where all the games were presented:</p>

<p><img src="/images/2025/20251025_203919.jpg" alt="Stunfest stage" /></p>

<p>As nobody told me anything, I still went to see the awards, knowing that we probably did not win anything. I was a bit upset that nobody told me there was presentations about all games going on and I only realized while listening to all the presentations that my turn would actually come and it did. In the end, we did not win an award (two out of three is still very good).</p>

<p>Mandatory crepe photo:
<img src="/images/2025/20251027_124725.jpg" alt="Crepe photo" /></p>]]></content><author><name></name></author><category term="gamedev" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">SAE convention 2025</title><link href="https://eliasfarhan.ch/travel/2025/10/22/sae-convention.html" rel="alternate" type="text/html" title="SAE convention 2025" /><published>2025-10-22T00:00:00+00:00</published><updated>2025-10-22T00:00:00+00:00</updated><id>https://eliasfarhan.ch/travel/2025/10/22/sae-convention</id><content type="html" xml:base="https://eliasfarhan.ch/travel/2025/10/22/sae-convention.html"><![CDATA[<p><img src="/images/2025/20251021_210500.jpg" alt="First image" /></p>

<!--more-->

<p>This week, my colleague from SAE Geneva and I went to Cologne for the SAE Convention, a gathering of the different SAE in the world. Like last year for Frankfurt, I took the train early. I arrived in Cologne in the early afternoon to have a meeting with the colleagues from the other SAE.</p>

<p><img src="/images/2025/20251020_135011.jpg" alt="The campus" /></p>

<p>I came at the SAE convention a few years ago (2016 if I am right), so I already knew the campus, located in the industrial area of Mülheim.</p>

<p>Here were the four subjects that I came to share:</p>
<ol>
  <li>Common projects: Adding to the 3rd year common project on Unreal with the game arts students, we started to implement a common project from the beginning of the 2nd year on Unity. The goal is to have the Games Programming students learn to work together, production tools and how to manage production on a less ambitious project, before switching to the main project with the game art.</li>
  <li>Personal work time: Still a big seller of the formation, we had some exchanges with the staffs from Zurich on how their voluntary implementation went compared to our mandatory implementation. We see a huge increase in the quality of our students in Geneva derived from this initiative and each month we see the progress from the first to the last year.</li>
  <li>Entrance exams: It is always hard to balance between the business want and the quality when new students apply for the formation. Our goal is to have students who know what they signed off for. Our discussion come to the idea of a programming introduction test, than an exam. Our goal is for everything students that start on the first day to know about the content of the next 3 years and to have tried programming beforehand.</li>
  <li>Use of IA: Our approach is pretty simple. Unlike modern approach that would simply use AI everywhere in game development (at least this is what we are getting sold), our approch is more conservative. It is always important for us that the students understand the output of the AI and not just blindly copy-paste the code. So in the first year, AI is very discouraged (to the point of making the students explain or rewrite functions if necessary) and is tolerated in the second year (except during theory exam).</li>
</ol>

<p><img src="/images/2025/20251021_142005.jpg" alt="palladium" /></p>

<p>The next day, there were a bunch of conference at the Palladium, mostly about AI. Nothing mindblowing, we are trying to sell us AI as this revolution, but I see it more like an assistant or a helper currently.</p>

<p><img src="/images/2025/20251021_214518.jpg" alt="awards photo1" /></p>

<p>What I was most interested in was the SAE Awards where my students were nominated again this year for the Game Production category with the game <a href="https://store.steampowered.com/app/3807810/Ruby_and_The_Lost_Crystals/">Ruby and the Lost Crystals</a> (Play it on Steam!). And they won the prize (again)! I am very proud of their work, but SAE awards are just a internal award and now we need to win awards against other schools. Cheers!</p>

<p><img src="/images/2025/20251021_210841.jpg" alt="awards photo2" /></p>]]></content><author><name></name></author><category term="travel" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Korean trip 2025</title><link href="https://eliasfarhan.ch/travel/2025/09/19/korean-trip.html" rel="alternate" type="text/html" title="Korean trip 2025" /><published>2025-09-19T00:00:00+00:00</published><updated>2025-09-19T00:00:00+00:00</updated><id>https://eliasfarhan.ch/travel/2025/09/19/korean-trip</id><content type="html" xml:base="https://eliasfarhan.ch/travel/2025/09/19/korean-trip.html"><![CDATA[<p><img src="/images/2025/korea/ktrip_josean_me.jpg" alt="First image" /></p>

<!--more-->

<p>Right after <a href="/jekyll/update/2025/08/19/bicfest.html">Busan Indie Connect</a>, I spent some times visiting South Korea, starting from Busan:</p>

<p><img src="/images/2025/korea/ktrip_busan.jpg" alt="Haeundae Beach" /></p>

<p><img src="/images/2025/korea/ktrip_busan_temple.jpg" alt="Temple" /></p>

<p>Then I went to Gyeongju, the capital of the Silla kingdom back in the day:</p>

<p><img src="/images/2025/korea/ktrip_tombs.jpg" alt="Tombs" /></p>

<p><img src="/images/2025/korea/ktrip_gyeongju_face.jpg" alt="Face" /></p>

<p>Next stop was Daegu:</p>

<p><img src="/images/2025/korea/ktrip_daegu.jpg" alt="Hiking view" /></p>

<p>Heading back to Seoul to go to the Seoul Indies meetup, where I presented Beach Slap:</p>

<p><img src="/images/2025/korea/ktrip_meetup.jpg" alt="Meetup" /></p>

<p><img src="/images/2025/korea/ktrip_seoul_hill.jpg" alt="Seoul hill" /></p>

<p>Afterwards, I went eastwards to Gangneung:</p>

<p><img src="/images/2025/korea/ktrip_beach.jpg" alt="The beach" /></p>

<p><img src="/images/2025/korea/ktrip_statue.jpg" alt="Statue" /></p>

<p>After a very long trip with several trains, I arrived in Jeonju:</p>

<p><img src="/images/2025/korea/ktrip_jeonju.jpg" alt="Jeonju" /></p>

<p>I joined with my gf in Gwangju:</p>

<p><img src="/images/2025/korea/ktrip_gwangju_park.jpg" alt="Gwangju" /></p>

<p>I visited the penguin village, named after the elders living there that look like penguins walking:</p>

<p><img src="/images/2025/korea/ktrip_gwangju_penguin.jpg" alt="Gwangju penguin" /></p>

<p>We went to see a baseball match in Daegu:</p>

<p><img src="/images/2025/korea/ktrip_baseball.jpg" alt="Baseball" /></p>

<p>Then, we went to her hometown of Yeongju:</p>

<p><img src="/images/2025/korea/ktrip_floating_rock.jpg" alt="Temple" /></p>

<p>I even prepared some gimbap:</p>

<p><img src="/images/2025/korea/ktrip_gimbap.jpg" alt="Gimbap" /></p>

<p>Before finally, heading to Seoul:</p>

<p><img src="/images/2025/korea/ktrip_seoul_lake.jpg" alt="Seoul" /></p>

<p>That was it for my trip to Korea. Still a lot of details that I could not put in this post, but looking forward to come back and eat all the food.</p>

<p><img src="/images/2025/korea/ktrip_seoul_museum.jpg" alt="Museum" /></p>]]></content><author><name></name></author><category term="travel" /><summary type="html"><![CDATA[]]></summary></entry></feed>