Jekyll2020-09-22T18:02:01+00:00https://ajor.co.uk/feed.xmlAlastair RobertsonPorting the Chip8 emulator to the web2017-09-17T00:00:00+00:002017-09-17T00:00:00+00:00https://ajor.co.uk/2017/09/17/emscripten-emulation<p><a href="https://kripken.github.io/emscripten-site">Emscripten</a> is an LLVM-based compiler for converting C and C++ code into asm.js, a highly optimisable subset of JavaScript, which allows programs to run in a web browser at near native speed. This post will go through the steps required to port my Chip8 emulator to the web browser.</p>
<h2 id="main-loop">Main Loop</h2>
<p>Being my first emulation project and mainly done a learning exercise, my Chip8 emulator features a slightly… unusual architecture. Among other issues, its design made it difficult to compile to JavaScript using Emscripten. The emulator was originally written to run with two threads - one handling the emulation itself (CPU, graphics, audio) and the other handling the OpenGL interface. While support for shared memory multithreading has <a href="https://github.com/tc39/ecmascript_sharedmem">recently been added</a> to the EMCAScript specification, there is no need for anything that complicated in a Chip8 emulator - it should happily run in a single thread on any machine.</p>
<p>Running the emulation in the application’s main thread was as simple as moving the <code class="language-plaintext highlighter-rouge">step()</code> emulation function and the OpenGL rendering code into a single <code class="language-plaintext highlighter-rouge">run_frame()</code> function. The only difference is that now the game speed is dependent on the FPS the emulator is running at. However, since the Chip8 was never a real physical console, it doesn’t have a defined speed to be run at and most games will require different speed settings anyway.</p>
<p>Assuming a frame rate of 60fps, running at 10 instructions per frame (or a little higher) works well for most Chip-8 games tested, but Connect4 needs to run at only 1 instruction per frame to be playable. Super-Chip games generally need to be run a bit faster - somewhere in the 20-60 range seems to work well.</p>
<p>Now that the emulator runs in a single thread, we can tell Emscripten what it should run as its main loop:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#ifdef __EMSCRIPTEN__
</span> <span class="n">emscripten_set_main_loop_arg</span><span class="p">(</span><span class="n">run_frame</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="cp">#else
</span> <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">glfwWindowShouldClose</span><span class="p">(</span><span class="n">window</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">run_frame</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="n">glfwSwapBuffers</span><span class="p">(</span><span class="n">window</span><span class="p">);</span>
<span class="n">glfwPollEvents</span><span class="p">();</span>
<span class="p">}</span>
<span class="cp">#endif</span></code></pre></figure>
<h2 id="webgl">WebGL</h2>
<p>WebGL is based on OpenGL ES, which supports approximately a subset of the features of full OpenGL, so some slight modifications were needed to the OpenGL API calls and shaders.</p>
<h3 id="shaders">Shaders</h3>
<p>In newer versions of OpenGL, the <code class="language-plaintext highlighter-rouge">attribute</code> and <code class="language-plaintext highlighter-rouge">varying</code> keywords have been replaced by <code class="language-plaintext highlighter-rouge">in</code> and <code class="language-plaintext highlighter-rouge">out</code>, with the choice of replacement depending on whether we’re working on a vertex or fragment shader. To make our shaders work with WebGL, we’ll have to downgrade them to use the older syntax. Additionally, WebGL requires that we specify a precision that we want to work with in the fragment shader.</p>
<p>Vertex shader:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">in</code> -> <code class="language-plaintext highlighter-rouge">attribute</code></li>
<li><code class="language-plaintext highlighter-rouge">out</code> -> <code class="language-plaintext highlighter-rouge">varying</code></li>
</ul>
<p>Fragment shader:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">in</code> -> <code class="language-plaintext highlighter-rouge">varying</code></li>
<li><code class="language-plaintext highlighter-rouge">out</code> -> gl_FragColor builtin</li>
</ul>
<h4 id="old-vertex-shader">Old vertex shader</h4>
<figure class="highlight"><pre><code class="language-glsl" data-lang="glsl"><span class="cp">#version 330 core
</span><span class="k">in</span> <span class="kt">vec2</span> <span class="n">position</span><span class="p">;</span>
<span class="k">in</span> <span class="kt">vec2</span> <span class="n">texCoord</span><span class="p">;</span>
<span class="k">out</span> <span class="kt">vec2</span> <span class="n">TexCoord</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="nb">gl_Position</span> <span class="o">=</span> <span class="kt">vec4</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</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">TexCoord</span> <span class="o">=</span> <span class="n">texCoord</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>
<h4 id="webgl-compatible-vertex-shader">WebGL-compatible vertex shader</h4>
<figure class="highlight"><pre><code class="language-glsl" data-lang="glsl"><span class="k">attribute</span> <span class="kt">vec2</span> <span class="n">position</span><span class="p">;</span>
<span class="k">attribute</span> <span class="kt">vec2</span> <span class="n">texCoord</span><span class="p">;</span>
<span class="k">varying</span> <span class="kt">vec2</span> <span class="n">TexCoord</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="nb">gl_Position</span> <span class="o">=</span> <span class="kt">vec4</span><span class="p">(</span><span class="n">position</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</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">TexCoord</span> <span class="o">=</span> <span class="n">texCoord</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>
<h4 id="old-fragment-shader">Old fragment shader</h4>
<figure class="highlight"><pre><code class="language-glsl" data-lang="glsl"><span class="cp">#version 330 core
</span><span class="k">in</span> <span class="kt">vec2</span> <span class="n">TexCoord</span><span class="p">;</span>
<span class="k">out</span> <span class="kt">vec4</span> <span class="n">colour</span><span class="p">;</span>
<span class="k">uniform</span> <span class="kt">sampler2D</span> <span class="n">display</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">colour</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">display</span><span class="p">,</span> <span class="n">TexCoord</span><span class="p">);</span>
<span class="p">};</span></code></pre></figure>
<h4 id="webgl-compatible-fragment-shader">WebGL-compatible fragment shader</h4>
<figure class="highlight"><pre><code class="language-glsl" data-lang="glsl"><span class="cp">#ifdef __EMSCRIPTEN__
</span><span class="k">precision</span> <span class="kt">mediump</span> <span class="kt">float</span><span class="p">;</span>
<span class="cp">#endif
</span><span class="k">varying</span> <span class="kt">vec2</span> <span class="n">TexCoord</span><span class="p">;</span>
<span class="k">uniform</span> <span class="kt">sampler2D</span> <span class="n">display</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="nb">gl_FragColor</span> <span class="o">=</span> <span class="n">texture2D</span><span class="p">(</span><span class="n">display</span><span class="p">,</span> <span class="n">TexCoord</span><span class="p">);</span>
<span class="p">};</span></code></pre></figure>
<p>WebGL runs on a different set of version numbers to OpenGL, so I’ve just removed them entirely to make life easier.</p>
<h3 id="opengl-api">OpenGL API</h3>
<p>Aside from some unnecessary OpenGL calls that could just be removed, only <code class="language-plaintext highlighter-rouge">glTexImage2D</code> had compatibility issues. This function tells the graphics card how to interpret the block of memory representing the Chip8’s display output.</p>
<p>The Chip8 can only display two colours - black and white - so doesn’t need to use the more standard <code class="language-plaintext highlighter-rouge">GL_RGB</code> / <code class="language-plaintext highlighter-rouge">GL_RGBA</code> formats. In OpenGL the pair <code class="language-plaintext highlighter-rouge">GL_RGBA8</code> / <code class="language-plaintext highlighter-rouge">GL_LUMINANCE</code> works to use a single byte as a greyscale colour output, but this doesn’t work in WebGL. After some experimentation with different format combinations, the pair <code class="language-plaintext highlighter-rouge">GL_LUMINANCE</code> / <code class="language-plaintext highlighter-rouge">GL_LUMINANCE</code> was found to work in WebGL.</p>
<p>Unfortunately there doesn’t seem to be a format which is compatible between OpenGL and WebGL, so we’ll leave it up to the C++ preprocessor to pick which to use:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#ifdef __EMSCRIPTEN__
</span> <span class="n">glTexImage2D</span><span class="p">(</span><span class="n">GL_TEXTURE_2D</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">GL_LUMINANCE</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">GL_LUMINANCE</span><span class="p">,</span> <span class="n">GL_UNSIGNED_BYTE</span><span class="p">,</span> <span class="n">disp</span><span class="p">);</span>
<span class="cp">#else
</span> <span class="n">glTexImage2D</span><span class="p">(</span><span class="n">GL_TEXTURE_2D</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">GL_RGBA8</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">GL_LUMINANCE</span><span class="p">,</span> <span class="n">GL_UNSIGNED_BYTE</span><span class="p">,</span> <span class="n">disp</span><span class="p">);</span>
<span class="cp">#endif</span></code></pre></figure>
<h2 id="javascript-to-c-communication">JavaScript to C++ communication</h2>
<p>The final step in porting the Chip8 emulator to the web browser was to enable communication from the JavaScript UI to the C++ core. This allows us to control settings in the emulator and, more importantly, to load games.</p>
<h3 id="loading-games">Loading Games</h3>
<p>Starting with the default HTML file Emscripten generates, we need to make a few changes so that our emulator’s main function gets called with the arguments it needs.</p>
<p>First off, we’ll say that we don’t want the emulator to start as soon as the page is loaded by adding the following line in the definition of Emscripten’s <code class="language-plaintext highlighter-rouge">Module</code>:</p>
<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">noInitialRun</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span></code></pre></figure>
<p>Secondly, we need to add a file picker and some JavaScript to load the chosen ROM into the emulator:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt"><input</span> <span class="na">type=</span><span class="s">"file"</span> <span class="na">id=</span><span class="s">"rom"</span> <span class="nt">/></span></code></pre></figure>
<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="kd">function</span> <span class="nx">loadROM</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">f</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">files</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">f</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">r</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FileReader</span><span class="p">();</span>
<span class="nx">r</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">contents</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">result</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">file_name</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">rom</span><span class="dl">"</span><span class="p">).</span><span class="nx">files</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">name</span><span class="p">;</span>
<span class="nx">FS</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">file_name</span><span class="p">,</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">contents</span><span class="p">),</span> <span class="p">{</span><span class="na">encoding</span><span class="p">:</span> <span class="dl">"</span><span class="s2">binary</span><span class="dl">"</span><span class="p">});</span>
<span class="c1">// Make sure any saved games are loaded before running</span>
<span class="nx">FS</span><span class="p">.</span><span class="nx">syncfs</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">Module</span><span class="p">.</span><span class="nx">callMain</span><span class="p">([</span><span class="nx">file_name</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="nx">r</span><span class="p">.</span><span class="nx">readAsArrayBuffer</span><span class="p">(</span><span class="nx">f</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">rom</span><span class="dl">"</span><span class="p">).</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">none</span><span class="dl">"</span><span class="p">;</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">controls</span><span class="dl">"</span><span class="p">).</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">block</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
<span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">failed to load file</span><span class="se">\n</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">rom</span><span class="dl">"</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">change</span><span class="dl">"</span><span class="p">,</span> <span class="nx">loadROM</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span></code></pre></figure>
<p>This code uses Emscripten’s <a href="https://kripken.github.io/emscripten-site/docs/api_reference/Filesystem-API.html">file system API</a> to save the chosen ROM into an in-memory filesystem, before calling the emulator’s main function with the ROM’s file path. This is necessary since the emulator expects to load ROMs from disk.</p>
<h3 id="controlling-game-speed">Controlling game speed</h3>
<p>In order to make all games playable, we need to provide a way of letting the user pick what speed they should run at.</p>
<p>We create a C function which can be called from JavaScript, and call it when the value in the “instructions per step” text box is updated:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">extern</span> <span class="s">"C"</span>
<span class="p">{</span>
<span class="kt">void</span> <span class="n">set_instructions_per_step</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">chip8</span><span class="p">.</span><span class="n">instructions_per_step</span> <span class="o">=</span> <span class="n">n</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-html" data-lang="html">Instructions per step: <span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">id=</span><span class="s">"speed"</span> <span class="nt">/></span></code></pre></figure>
<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="kd">function</span> <span class="nx">updateSpeed</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">n</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">speed</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="p">;</span>
<span class="nx">Module</span><span class="p">.</span><span class="nx">ccall</span><span class="p">(</span><span class="dl">'</span><span class="s1">set_instructions_per_step</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">void</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">[number]</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span><span class="nx">n</span><span class="p">]);</span>
<span class="p">}</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">speed</span><span class="dl">"</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">input</span><span class="dl">"</span><span class="p">,</span> <span class="nx">updateSpeed</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span></code></pre></figure>
<h2 id="try-it-out">Try it out!</h2>
<p>The JavaScript version of the Chip8 emulator is <a href="/chip8">running on this site</a>.</p>Emscripten is an LLVM-based compiler for converting C and C++ code into asm.js, a highly optimisable subset of JavaScript, which allows programs to run in a web browser at near native speed. This post will go through the steps required to port my Chip8 emulator to the web browser.