emacs.d/clones/lisp/stevelosh.com/blog/2016/12/chip8-graphics/index.html
2022-10-07 15:47:14 +02:00

572 lines
No EOL
48 KiB
HTML

<!DOCTYPE html>
<html lang='en'><head><meta charset='utf-8' /><meta name='pinterest' content='nopin' /><link href='http://stevelosh.com/static/css/style.css' rel='stylesheet' type='text/css' /><link href='http://stevelosh.com/static/css/print.css' rel='stylesheet' type='text/css' media='print' /><title>CHIP-8 in Common Lisp: Graphics / Steve Losh</title></head><body><header><a id='logo' href='http://stevelosh.com/'>Steve Losh</a><nav><a href='http://stevelosh.com/blog/'>Blog</a> - <a href='http://stevelosh.com/projects/'>Projects</a> - <a href='http://stevelosh.com/photography/'>Photography</a> - <a href='http://stevelosh.com/links/'>Links</a> - <a href='http://stevelosh.com/rss.xml'>Feed</a></nav></header><hr class='main-separator' /><main id='page-blog-entry'><article><h1><a href='index.html'>CHIP-8 in Common Lisp: Graphics</a></h1><p class='date'>Posted on December 21st, 2016.</p><p>In the previous post we looked at how to emulate a <a href="https://en.wikipedia.org/wiki/CHIP-8">CHIP-8</a> CPU with Common
Lisp. But a CPU alone isn't much fun to play, so in this post we'll add
a screen to the emulator with <a href="https://www.qt.io/">Qt</a>.</p>
<p>The full series of posts so far:</p>
<ol>
<li><a href="../chip8-cpu/index.html">CHIP-8 in Common Lisp: The CPU</a></li>
<li><a href="index.html">CHIP-8 in Common Lisp: Graphics</a></li>
<li><a href="../chip8-input/index.html">CHIP-8 in Common Lisp: Input</a></li>
<li><a href="../chip8-sound/index.html">CHIP-8 in Common Lisp: Sound</a></li>
<li><a href="../../../2017/01/chip8-disassembly/index.html">CHIP-8 in Common Lisp: Disassembly</a></li>
<li><a href="../../../2017/01/chip8-debugging-infrastructure/index.html">CHIP-8 in Common Lisp: Debugging Infrastructure</a></li>
<li><a href="../../../2017/01/chip8-menus/index.html">CHIP-8 in Common Lisp: Menus</a></li>
</ol>
<p>The full emulator source is on <a href="https://bitbucket.org/sjl/cl-chip8">BitBucket</a> and <a href="https://github.com/sjl/cl-chip8">GitHub</a>.</p>
<ol class="table-of-contents"><li><a href="index.html#s1-qtools">Qtools</a></li><li><a href="index.html#s2-architecture">Architecture</a></li><li><a href="index.html#s3-the-emulation-layer">The Emulation Layer</a><ol><li><a href="index.html#s4-video-memory-and-performance">Video Memory and Performance</a></li><li><a href="index.html#s5-fonts">Fonts</a></li><li><a href="index.html#s6-clearing-the-screen-cls">Clearing the Screen: CLS</a></li><li><a href="index.html#s7-loading-fonts-ld-f-vx">Loading Fonts: LD F, Vx</a></li><li><a href="index.html#s8-drawing-sprites-drw-x-y-size">Drawing Sprites: DRW X, Y, Size</a></li></ol></li><li><a href="index.html#s9-the-user-interface-layer">The User Interface Layer</a><ol><li><a href="index.html#s10-basic-plan">Basic Plan</a></li><li><a href="index.html#s11-screen-widget">Screen Widget</a></li><li><a href="index.html#s12-drawing-frames">Drawing Frames</a></li><li><a href="index.html#s13-wrapping-up">Wrapping Up</a></li></ol></li><li><a href="index.html#s14-results">Results</a></li><li><a href="index.html#s15-future">Future</a></li></ol>
<h2 id="s1-qtools"><a href="index.html#s1-qtools">Qtools</a></h2>
<p><a href="https://shinmera.github.io/qtools/">Qtools</a> is a library that wraps up a few other libraries to make it easier to
write Qt interfaces with Common Lisp. It's a big library so I'm not going to
try to explain everything about it here. Most of the code here should be pretty
easy to follow even if you haven't used it before (I'll explain the high-level
concepts), but if you're interested in <em>exactly</em> how some code works you should
check out its documentation.</p>
<h2 id="s2-architecture"><a href="index.html#s2-architecture">Architecture</a></h2>
<p>Let's take a moment to look at the overall architecture of the project. So far
we've got a <code>chip</code> struct that holds the state of the emulated system, and
a bunch of <code>op-...</code> instruction functions that emulate the instructions. We
could start plugging in drawing calls right in the appropriate instructions, and
this is the approach a lot of emulators take. But I'd like to separate things
a bit more strictly.</p>
<p>My goal is to keep the emulated system entirely self-contained, and then layer
the screen and user interface on top. The emulator should ideally know
<em>nothing</em> about the existence of an interface. This keeps the emulator simple
and (mostly) free of cruft. It will also let us play around with alternate
interfaces if we want to — I think it might be fun to add an ASCII screen with
<a href="https://en.wikipedia.org/wiki/Ncurses">ncurses</a> and <a href="https://github.com/HiTECNOLOGYs/cl-charms">cl-charms</a> some day!</p>
<p>With that said, we will make a <em>few</em> concessions to performance along the way.</p>
<h2 id="s3-the-emulation-layer"><a href="index.html#s3-the-emulation-layer">The Emulation Layer</a></h2>
<p>We'll start with the emulation side of things.</p>
<h3 id="s4-video-memory-and-performance"><a href="index.html#s4-video-memory-and-performance">Video Memory and Performance</a></h3>
<p>The CHIP-8 has a 64x32 pixel display, and each pixel has only two colors: on and
off. It's about as simple a screen as you can get.</p>
<p>To keep the emulator from having to know about the user interface we'll model
the screen as a big array of video memory. The emulator can set the video
memory appropriately, and the user interface can read it to determine what to
draw to the screen at any given point.</p>
<p>There are other ways we could have separated things, but let's run with this
strategy for now.</p>
<p>We'll add a video memory array to our <code>chip</code> struct:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +screen-width+ 64</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +screen-height+ 32</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> chip
<span class="comment">; ...
</span> <span class="paren2">(<span class="code">video <span class="paren3">(<span class="code">make-array <span class="paren4">(<span class="code">* +screen-height+ +screen-width+</span>)</span> <span class="keyword">:element-type</span> 'fixnum</span>)</span>
<span class="keyword">:type</span> <span class="paren3">(<span class="code">simple-array fixnum <span class="paren4">(<span class="code">#.<span class="paren5">(<span class="code">* +screen-height+ +screen-width+</span>)</span></span>)</span></span>)</span>
<span class="keyword">:read-only</span> t</span>)</span>
<span class="comment">; ...
</span> </span>)</span></span></code></pre>
<p>Here we already see a first concession to performance. We could have used
a multidimensional array to make the indexing a bit nicer, but by using a simple
flat array we'll be able to pass it directly to OpenGL later.</p>
<p>OpenGL is going to want this array to be in &quot;X-major&quot; order, so the array will
need to look like:</p>
<pre><code>[(x₀, y₀), (x₁, y₀), (x₂, y₀), ...,
(x₀, y₁), (x₁, y₁), (x₂, y₁), ...,
...]
</code></pre>
<p>We'll add a couple of helper functions to make the indexing of this array a bit
less painful:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun-inline</span></i> vref <span class="paren2">(<span class="code">chip x y</span>)</span>
<span class="paren2">(<span class="code">aref <span class="paren3">(<span class="code">chip-video chip</span>)</span> <span class="paren3">(<span class="code">+ <span class="paren4">(<span class="code">* +screen-width+ y</span>)</span> x</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun-inline</span></i> <span class="paren2">(<span class="code">setf vref</span>)</span> <span class="paren2">(<span class="code">new-value chip x y</span>)</span>
<span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">aref <span class="paren4">(<span class="code">chip-video chip</span>)</span> <span class="paren4">(<span class="code">+ <span class="paren5">(<span class="code">* +screen-width+ y</span>)</span> x</span>)</span></span>)</span>
new-value</span>)</span></span>)</span></span></code></pre>
<p>Now we can simply say <code>(vref chip 5 15)</code> to get the pixel at (5, 15) instead of
manually calculating out the <code>aref</code>.</p>
<p>We'll add one more field to <code>chip</code> before moving on, a &quot;dirty&quot; flag:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> chip
<span class="comment">; ...
</span> <span class="paren2">(<span class="code">video-dirty t <span class="keyword">:type</span> boolean</span>)</span>
<span class="comment">; ...
</span> </span>)</span></span></code></pre>
<p>This will make it easier for any interface to determine whether it needs to
update the display or not.</p>
<h3 id="s5-fonts"><a href="index.html#s5-fonts">Fonts</a></h3>
<p>The CHIP-8 spec sets aside a portion of main memory starting at address <code>#x50</code>
to contain sprites for the hex digits 0 through F. Check out <a href="http://devernay.free.fr/hacks/chip8/C8TECH10.HTM">the display
chapter of Cowgod's guide</a> for an overview of how the CHIP-8 defines
sprites.</p>
<p>We'll need to load these sprites into our emulator's memory at the correct
location when resetting it. We'll also clear out the video memory while we're
at it:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> load-font <span class="paren2">(<span class="code">chip</span>)</span>
<span class="comment">;; Thanks http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/
</span> <span class="paren2">(<span class="code">replace <span class="paren3">(<span class="code">chip-memory chip</span>)</span>
#<span class="paren3">(<span class="code">#xF0 #x90 #x90 #x90 #xF0 <span class="comment">; 0
</span> #x20 #x60 #x20 #x20 #x70 <span class="comment">; 1
</span> #xF0 #x10 #xF0 #x80 #xF0 <span class="comment">; 2
</span> #xF0 #x10 #xF0 #x10 #xF0 <span class="comment">; 3
</span> #x90 #x90 #xF0 #x10 #x10 <span class="comment">; 4
</span> #xF0 #x80 #xF0 #x10 #xF0 <span class="comment">; 5
</span> #xF0 #x80 #xF0 #x90 #xF0 <span class="comment">; 6
</span> #xF0 #x10 #x20 #x40 #x40 <span class="comment">; 7
</span> #xF0 #x90 #xF0 #x90 #xF0 <span class="comment">; 8
</span> #xF0 #x90 #xF0 #x10 #xF0 <span class="comment">; 9
</span> #xF0 #x90 #xF0 #x90 #x90 <span class="comment">; A
</span> #xE0 #x90 #xE0 #x90 #xE0 <span class="comment">; B
</span> #xF0 #x80 #x80 #x80 #xF0 <span class="comment">; C
</span> #xE0 #x90 #x90 #x90 #xE0 <span class="comment">; D
</span> #xF0 #x80 #xF0 #x80 #xF0 <span class="comment">; E
</span> #xF0 #x80 #xF0 #x80 #x80</span>)</span> <span class="comment">; F
</span> <span class="keyword">:start1</span> #x50</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> reset <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code">fill memory 0</span>)</span>
<span class="paren3">(<span class="code">fill registers 0</span>)</span>
<span class="paren3">(<span class="code">fill video 0</span>)</span> <span class="comment">; NEW
</span> <span class="paren3">(<span class="code">load-font chip</span>)</span> <span class="comment">; NEW
</span> <span class="paren3">(<span class="code">replace memory <span class="paren4">(<span class="code">read-file-into-byte-vector loaded-rom</span>)</span>
<span class="keyword">:start1</span> #x200</span>)</span>
<span class="paren3">(<span class="code">setf running t
video-dirty t <span class="comment">; NEW
</span> program-counter #x200
<span class="paren4">(<span class="code">fill-pointer stack</span>)</span> 0</span>)</span></span>)</span>
<span class="paren2">(<span class="code">values</span>)</span></span>)</span></span></code></pre>
<p>Once again the handy <code>replace</code> function makes things easy.</p>
<h3 id="s6-clearing-the-screen-cls"><a href="index.html#s6-clearing-the-screen-cls">Clearing the Screen: CLS</a></h3>
<p>Now we can start implementing the graphics-related instructions. The first is
the very simple <code>CLS</code> to clear the screen:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-instruction</span></i> op-cls <span class="paren2">(<span class="code"></span>)</span> <span class="comment">;; CLS
</span> <span class="paren2">(<span class="code">fill video 0</span>)</span>
<span class="paren2">(<span class="code">setf video-dirty t</span>)</span></span>)</span></span></code></pre>
<h3 id="s7-loading-fonts-ld-f-vx"><a href="index.html#s7-loading-fonts-ld-f-vx">Loading Fonts: LD F, Vx</a></h3>
<p>Next up is the &quot;load font&quot; instruction, which sets the index register to the
address of the sprite for the digit in the argument register. So <code>LD F, V2</code>
where register 2 contains <code>6</code> would set the index register to the address of the
<code>6</code> sprite.</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun-inline</span></i> font-location <span class="paren2">(<span class="code">character</span>)</span>
<span class="paren2">(<span class="code">+ #x50 <span class="paren3">(<span class="code">* character 5</span>)</span></span>)</span></span>)</span> <span class="comment">; each sprite is 5 bytes wide
</span>
<span class="paren1">(<span class="code"><i><span class="symbol">define-instruction</span></i> op-ld-font&lt;vx <span class="paren2">(<span class="code">_ r _ _</span>)</span> <span class="comment">;; LD F, Vx
</span> <span class="paren2">(<span class="code">setf index <span class="paren3">(<span class="code">font-location <span class="paren4">(<span class="code">register r</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<h3 id="s8-drawing-sprites-drw-x-y-size"><a href="index.html#s8-drawing-sprites-drw-x-y-size">Drawing Sprites: DRW X, Y, Size</a></h3>
<p>The most complicated part of the emulator's code is certainly the portion that
draws sprites. The instruction itself will delegate to a helper function:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-instruction</span></i> op-draw <span class="paren2">(<span class="code">_ rx ry size</span>)</span> <span class="comment">;; DRW Vx, Vy, size
</span> <span class="paren2">(<span class="code">draw-sprite chip <span class="paren3">(<span class="code">register rx</span>)</span> <span class="paren3">(<span class="code">register ry</span>)</span> size</span>)</span></span>)</span></span></code></pre>
<p>Check out <a href="http://devernay.free.fr/hacks/chip8/C8TECH10.HTM">Cowgod's guide</a> for a good overview of how the CHIP-8
drawing system works. I'll assume you've read that before moving on.</p>
<p>Let's implement the <code>draw-sprite</code> function in chunks, because it can be a bit
intimidating if I just slap it all down at once. Before we start drawing
anything we reset <code>flag</code> to 0, and we'll mark the dirty flag at the end:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> draw-sprite <span class="paren2">(<span class="code">chip start-x start-y size</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code">setf flag 0</span>)</span>
<span class="comment">; ... draw the sprite ...
</span> <span class="paren3">(<span class="code">setf video-dirty t</span>)</span></span>)</span>
nil</span>)</span></span></code></pre>
<p>Simple enough. Now we need to loop through each row of the sprite and draw it.
The address of the sprite we're drawing is given by the index register, and each
row in the sprite is represented by a byte of memory. Again, check out Cowgod's
guide for the full details.</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> draw-sprite <span class="paren2">(<span class="code">chip start-x start-y size</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code">setf flag 0</span>)</span>
<span class="paren3">(<span class="code">iterate <span class="comment">; NEW
</span> <span class="paren4">(<span class="code">repeat size</span>)</span> <span class="comment">; NEW
</span> <span class="paren4">(<span class="code">for i <span class="keyword">:from</span> index</span>)</span> <span class="comment">; NEW
</span> <span class="paren4">(<span class="code">for y <span class="keyword">:from</span> start-y</span>)</span> <span class="comment">; NEW
</span> <span class="paren4">(<span class="code">for sprite = <span class="paren5">(<span class="code">aref memory i</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="comment">; ... draw the row ...
</span> </span>)</span>
<span class="paren3">(<span class="code">setf video-dirty t</span>)</span></span>)</span>
nil</span>)</span></span></code></pre>
<p>To draw a row, we just have to draw each of the eight pixels in it:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> draw-sprite <span class="paren2">(<span class="code">chip start-x start-y size</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code">setf flag 0</span>)</span>
<span class="paren3">(<span class="code">iterate
<span class="paren4">(<span class="code">repeat size</span>)</span>
<span class="paren4">(<span class="code">for i <span class="keyword">:from</span> index</span>)</span>
<span class="paren4">(<span class="code">for y <span class="keyword">:from</span> start-y</span>)</span>
<span class="paren4">(<span class="code">for sprite = <span class="paren5">(<span class="code">aref memory i</span>)</span></span>)</span>
<span class="paren4">(<span class="code">iterate <span class="comment">; NEW
</span> <span class="paren5">(<span class="code">for x <span class="keyword">:from</span> start-x</span>)</span> <span class="comment">; NEW
</span> <span class="paren5">(<span class="code">for col <span class="keyword">:from</span> 7 <span class="keyword">:downto</span> 0</span>)</span> <span class="comment">; NEW
</span> <span class="comment">; ... draw the pixel ...
</span> </span>)</span></span>)</span>
<span class="paren3">(<span class="code">setf video-dirty t</span>)</span></span>)</span>
nil</span>)</span></span></code></pre>
<p>Unfortunately we hit a snag at this point. All the references I've found say
that if any X or Y values go outside of the range of valid screen coordinates
the sprite should wrap around the screen. And indeed, some ROMs (e.g.
<code>ufo.rom</code>) require this behavior to work properly. But unfortunately some
<em>other</em> ROMs (e.g. <code>blitz.rom</code>) expect the screen to <em>clip</em>, not wrap!</p>
<p>This is the first case where our emulator will need to bend the rules of the
spec to accommodate buggy ROMs. Sadly this is not uncommon in the emulation
world.</p>
<p>We'll deal with this by adding a setting to the emulator:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> chip
<span class="comment">; ...
</span> <span class="paren2">(<span class="code">screen-wrapping-enabled t <span class="keyword">:type</span> boolean</span>)</span>
<span class="comment">; ...
</span> </span>)</span></span></code></pre>
<p>Then we can hide the &quot;to wrap, or not to wrap&quot; logic in its own helper:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun-inline</span></i> wrap <span class="paren2">(<span class="code">chip x y</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">cond</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">chip-screen-wrapping-enabled chip</span>)</span>
<span class="paren4">(<span class="code">values <span class="paren5">(<span class="code">mod x +screen-width+</span>)</span>
<span class="paren5">(<span class="code">mod y +screen-height+</span>)</span>
t</span>)</span></span>)</span>
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">and <span class="paren5">(<span class="code">in-range-p 0 x +screen-width+</span>)</span>
<span class="paren5">(<span class="code">in-range-p 0 y +screen-height+</span>)</span></span>)</span>
<span class="paren4">(<span class="code">values x y t</span>)</span></span>)</span>
<span class="paren3">(<span class="code">t <span class="paren4">(<span class="code">values nil nil nil</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p><code>wrap</code> will take <code>x</code> and <code>y</code> coordinates and return three values:</p>
<ul>
<li>The screen X coordinate to draw to (if any).</li>
<li>The screen Y coordinate to draw to (if any).</li>
<li>A boolean that will be <code>t</code> when the pixel should be drawn, or <code>nil</code> if not.</li>
</ul>
<p><a href="https://github.com/sjl/cl-losh/blob/master/DOCUMENTATION.markdown#in-range-p-function"><code>in-range-p</code></a> is a predicate from my utility library that checks if
<code>low &lt;= val &lt; high</code> (which is <a href="https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html">often useful</a>).</p>
<p>Now we can use this in <code>draw-sprite</code> to determine whether and where to draw each
pixel:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> draw-sprite <span class="paren2">(<span class="code">chip start-x start-y size</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code">setf flag 0</span>)</span>
<span class="paren3">(<span class="code">iterate
<span class="paren4">(<span class="code">repeat size</span>)</span>
<span class="paren4">(<span class="code">for i <span class="keyword">:from</span> index</span>)</span>
<span class="paren4">(<span class="code">for y <span class="keyword">:from</span> start-y</span>)</span>
<span class="paren4">(<span class="code">for sprite = <span class="paren5">(<span class="code">aref memory i</span>)</span></span>)</span>
<span class="paren4">(<span class="code">iterate
<span class="paren5">(<span class="code">for x <span class="keyword">:from</span> start-x</span>)</span>
<span class="paren5">(<span class="code">for col <span class="keyword">:from</span> 7 <span class="keyword">:downto</span> 0</span>)</span>
<span class="paren5">(<span class="code">multiple-value-bind <span class="paren6">(<span class="code">x y should-draw</span>)</span> <span class="comment">; NEW
</span> <span class="paren6">(<span class="code">wrap chip x y</span>)</span> <span class="comment">; NEW
</span> <span class="paren6">(<span class="code">when should-draw <span class="comment">; NEW
</span> <span class="comment">; ... actually draw the damn pixel ...
</span> </span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">setf video-dirty t</span>)</span></span>)</span>
nil</span>)</span></span></code></pre>
<p>Now we come to the second concession to performance. Ideally we'd store the
pixel values as <code>t</code> and <code>nil</code> or <code>1</code> and <code>0</code>. But due to a bug in Qt 4 (which
we'll talk about later) we need to store <code>255</code> and <code>0</code>. So we need to do a bit
of an ugly dance to <code>XOR</code> the pixels together:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> draw-sprite <span class="paren2">(<span class="code">chip start-x start-y size</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
<span class="paren3">(<span class="code">setf flag 0</span>)</span>
<span class="paren3">(<span class="code">iterate
<span class="paren4">(<span class="code">repeat size</span>)</span>
<span class="paren4">(<span class="code">for i <span class="keyword">:from</span> index</span>)</span>
<span class="paren4">(<span class="code">for y <span class="keyword">:from</span> start-y</span>)</span>
<span class="paren4">(<span class="code">for sprite = <span class="paren5">(<span class="code">aref memory i</span>)</span></span>)</span>
<span class="paren4">(<span class="code">iterate
<span class="paren5">(<span class="code">for x <span class="keyword">:from</span> start-x</span>)</span>
<span class="paren5">(<span class="code">for col <span class="keyword">:from</span> 7 <span class="keyword">:downto</span> 0</span>)</span>
<span class="paren5">(<span class="code">multiple-value-bind <span class="paren6">(<span class="code">x y should-draw</span>)</span>
<span class="paren6">(<span class="code">wrap chip x y</span>)</span>
<span class="paren6">(<span class="code">when should-draw
<span class="paren1">(<span class="code">for old-pixel = <span class="paren2">(<span class="code">plusp <span class="paren3">(<span class="code">vref chip x y</span>)</span></span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren1">(<span class="code">for new-pixel = <span class="paren2">(<span class="code">plusp <span class="paren3">(<span class="code">get-bit col sprite</span>)</span></span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren1">(<span class="code">when <span class="paren2">(<span class="code">and old-pixel new-pixel</span>)</span> <span class="comment">; NEW
</span> <span class="paren2">(<span class="code">setf flag 1</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren1">(<span class="code">setf <span class="paren2">(<span class="code">vref chip x y</span>)</span> <span class="comment">; NEW
</span> <span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren3">(<span class="code">xor old-pixel new-pixel</span>)</span> 255 0</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren3">(<span class="code">setf video-dirty t</span>)</span></span>)</span>
nil</span>)</span></span></code></pre>
<p><code>xor</code> is the exclusive-or variant of <code>and</code>/<code>or</code> from Alexandria.</p>
<p>And we're finally done. <code>draw-sprite</code> is 22 lines, which is getting a bit long
by Lisp standards, so it might be worth breaking into separate functions. But
it's also the most performance-critical function in the emulator, so keeping it
as a single loop will let us optimize it heavily later if necessary (spoiler: it
won't be necessary).</p>
<h2 id="s9-the-user-interface-layer"><a href="index.html#s9-the-user-interface-layer">The User Interface Layer</a></h2>
<p>That's it for the emulation side of things. If we run a ROM now the video
memory array in the <code>chip</code> struct will be updated properly. But unless we want
to look at raw memory, we need some kind of a screen.</p>
<p>We'll start off with a fresh package:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">in-package <span class="keyword">:chip8.gui.screen</span></span>)</span>
<span class="paren1">(<span class="code">named-readtables:in-readtable <span class="keyword">:qtools</span></span>)</span></span></code></pre>
<h3 id="s10-basic-plan"><a href="index.html#s10-basic-plan">Basic Plan</a></h3>
<p>We're going to use OpenGL to draw the actual pixels for our screen. The basic
plan will be:</p>
<ul>
<li>Ship the video memory up to the graphics card as a texture each frame.</li>
<li>Draw a single quad with this texture to get the actual pixels onto the display.</li>
</ul>
<p>We can use a tiny 64 by 64 texture so this will be fine for our performance
needs.</p>
<h3 id="s11-screen-widget"><a href="index.html#s11-screen-widget">Screen Widget</a></h3>
<p>The main UI for our screen will be a <code>QGLWidget</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-widget</span></i> screen <span class="paren2">(<span class="code">QGLWidget</span>)</span>
<span class="paren2">(<span class="code"><span class="paren3">(<span class="code">texture <span class="keyword">:accessor</span> screen-texture</span>)</span>
<span class="paren3">(<span class="code">chip <span class="keyword">:accessor</span> screen-chip <span class="keyword">:initarg</span> <span class="keyword">:chip</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> make-screen <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code">make-instance 'screen <span class="keyword">:chip</span> chip</span>)</span></span>)</span></span></code></pre>
<p>We'll define some initializers for this widget:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*scale*</span> 8</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*width*</span> <span class="paren2">(<span class="code">* <span class="special">*scale*</span> 64</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*height*</span> <span class="paren2">(<span class="code">* <span class="special">*scale*</span> 32</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">define-initializer</span></i> <span class="paren2">(<span class="code">screen setup</span>)</span>
<span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">q+:window-title screen</span>)</span> <span class="string">&quot;cl-chip8&quot;</span>
<span class="paren3">(<span class="code">q+:fixed-size screen</span>)</span> <span class="paren3">(<span class="code">values <span class="special">*width*</span> <span class="special">*height*</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">define-override</span></i> <span class="paren2">(<span class="code">screen <span class="string">&quot;initializeGL&quot;</span></span>)</span> <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">screen-texture screen</span>)</span> <span class="paren3">(<span class="code">initialize-texture 64</span>)</span></span>)</span>
<span class="paren2">(<span class="code">stop-overriding</span>)</span></span>)</span></span></code></pre>
<p>Single pixels are almost impossible to see on today's high-resolution displays,
so we'll scale them up by 8 to make them bigger.</p>
<p>We need to do the OpenGL texture initialization in the <code>initializeGL</code> method,
<em>not</em> the normal initializer, because we need the OpenGL context to be ready.
The actual texture initialization code is typical verbose ugly OpenGL, so we'll
tuck it away in a helper function:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> initialize-texture <span class="paren2">(<span class="code">size</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">handle <span class="paren5">(<span class="code">gl:gen-texture</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">gl:bind-texture <span class="keyword">:texture-2d</span> handle</span>)</span>
<span class="paren3">(<span class="code">gl:tex-image-2d <span class="keyword">:texture-2d</span> 0 <span class="keyword">:luminance</span> size size 0 <span class="keyword">:luminance</span>
<span class="keyword">:unsigned-byte</span> <span class="paren4">(<span class="code">cffi:null-pointer</span>)</span></span>)</span>
<span class="paren3">(<span class="code">gl:tex-parameter <span class="keyword">:texture-2d</span> <span class="keyword">:texture-min-filter</span> <span class="keyword">:nearest</span></span>)</span>
<span class="paren3">(<span class="code">gl:tex-parameter <span class="keyword">:texture-2d</span> <span class="keyword">:texture-mag-filter</span> <span class="keyword">:nearest</span></span>)</span>
<span class="paren3">(<span class="code">gl:enable <span class="keyword">:texture-2d</span></span>)</span>
<span class="paren3">(<span class="code">gl:bind-texture <span class="keyword">:texture-2d</span> 0</span>)</span>
handle</span>)</span></span>)</span></span></code></pre>
<p>We'll use <a href="https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation">nearest-neighbor interpolation</a> to get nice sharp sprites.</p>
<p>The texture array will be <code>unsigned-byte</code>s of luminance values, with <code>0</code> being
black and <code>255</code> being white. This explains the dance we had to do back in the
<code>draw-sprite</code>.</p>
<p>A better way to do this would be to have video memory contain <code>0</code> and <code>1</code>, and
use an OpenGL fragment shader to map these to the desired colors. Unfortunately
due to <a href="https://github.com/Shinmera/qtools/issues/17">a nasty bug</a> in Qt 4 on OS X we can't use shaders, so we're
stuck with this workaround for the time being. Computers are awful.</p>
<h3 id="s12-drawing-frames"><a href="index.html#s12-drawing-frames">Drawing Frames</a></h3>
<p>Now that we've got a widget we'll need to paint it on each frame. We'll use
a Qtimer to handle firing off the paint events:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*fps*</span> 60</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">define-subwidget</span></i> <span class="paren2">(<span class="code">screen timer</span>)</span> <span class="paren2">(<span class="code">q+:make-qtimer screen</span>)</span>
<span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">q+:single-shot timer</span>)</span> NIL</span>)</span>
<span class="paren2">(<span class="code">q+:start timer <span class="paren3">(<span class="code">round 1000 <span class="special">*fps*</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">define-slot</span></i> <span class="paren2">(<span class="code">screen update</span>)</span> <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code">declare <span class="paren3">(<span class="code">connected timer <span class="paren4">(<span class="code">timeout</span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren3">(<span class="code">chip8::chip-running <span class="paren4">(<span class="code">screen-chip screen</span>)</span></span>)</span>
<span class="paren3">(<span class="code">q+:repaint screen</span>)</span>
<span class="paren3">(<span class="code">die screen</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> die <span class="paren2">(<span class="code">screen</span>)</span>
<span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">chip8::chip-running <span class="paren4">(<span class="code">screen-chip screen</span>)</span></span>)</span> nil</span>)</span>
<span class="paren2">(<span class="code">q+:close screen</span>)</span></span>)</span></span></code></pre>
<p>The <code>timer</code> widget will fire a Qt <code>timeout</code> signal sixty times per second. The
screen's <code>update</code> slot is connected to this signal, and will either initiate
a repaint or kill the screen, depending on whether the <code>chip</code> is still running.</p>
<p>The <code>die</code> function also tells the <code>chip</code> to stop running. This obviously isn't
necessary here (we just checked that it's not running!), but we'll be using
<code>die</code> in another place later.</p>
<p>(It's really a shame that Qt and Common Lisp both use the words &quot;signal&quot; and
&quot;slot&quot; to mean wildly different things. It makes using Qt with Common Lisp more
painful than it should be...)</p>
<p>Now on to the meat of the code, repainting. We'll define the <code>paint-event</code> and
delegate to a helper function:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-override</span></i> <span class="paren2">(<span class="code">screen paint-event</span>)</span> <span class="paren2">(<span class="code">ev</span>)</span>
<span class="paren2">(<span class="code">declare <span class="paren3">(<span class="code">ignore ev</span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-finalizing</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">painter <span class="paren5">(<span class="code">q+:make-qpainter screen</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">render-screen screen painter</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>And here we go with another pile of verbose graphics code:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> render-screen <span class="paren2">(<span class="code">screen painter</span>)</span>
<span class="paren2">(<span class="code">q+:begin-native-painting painter</span>)</span>
<span class="comment">;; Clear the screen
</span> <span class="paren2">(<span class="code">gl:clear-color 0.0 0.0 0.0 1.0</span>)</span>
<span class="paren2">(<span class="code">gl:clear <span class="keyword">:color-buffer-bit</span></span>)</span>
<span class="paren2">(<span class="code">gl:bind-texture <span class="keyword">:texture-2d</span> <span class="paren3">(<span class="code">screen-texture screen</span>)</span></span>)</span>
<span class="comment">;; Update the texture
</span> <span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">chip <span class="paren5">(<span class="code">screen-chip screen</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">when <span class="paren4">(<span class="code">chip8::chip-video-dirty chip</span>)</span>
<span class="paren4">(<span class="code">setf <span class="paren5">(<span class="code">chip8::chip-video-dirty chip</span>)</span> nil</span>)</span>
<span class="paren4">(<span class="code">gl:tex-sub-image-2d <span class="keyword">:texture-2d</span> 0 0 0 64 32
<span class="keyword">:luminance</span> <span class="keyword">:unsigned-byte</span>
<span class="paren5">(<span class="code">chip8::chip-video chip</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="comment">;; Draw the quad
</span> <span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">tw 1</span>)</span>
<span class="paren4">(<span class="code">th 0.5</span>)</span></span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">gl:with-primitives</span></i> <span class="keyword">:quads</span>
<span class="paren4">(<span class="code">gl:tex-coord 0 0</span>)</span>
<span class="paren4">(<span class="code">gl:vertex 0 0</span>)</span>
<span class="paren4">(<span class="code">gl:tex-coord tw 0</span>)</span>
<span class="paren4">(<span class="code">gl:vertex <span class="special">*width*</span> 0</span>)</span>
<span class="paren4">(<span class="code">gl:tex-coord tw th</span>)</span>
<span class="paren4">(<span class="code">gl:vertex <span class="special">*width*</span> <span class="special">*height*</span></span>)</span>
<span class="paren4">(<span class="code">gl:tex-coord 0 th</span>)</span>
<span class="paren4">(<span class="code">gl:vertex 0 <span class="special">*height*</span></span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code">gl:bind-texture <span class="keyword">:texture-2d</span> 0</span>)</span>
<span class="paren2">(<span class="code">q+:end-native-painting painter</span>)</span></span>)</span></span></code></pre>
<p>Each frame, if <code>video-dirty</code> is set we'll update the texture with the contents
of the <code>chip</code>'s video memory. Then we draw a quad using this texture to get the
pixels on the actual display.</p>
<pre class="lineart">
Screen Texture
┌───────────────────────────────┐ ┌──────────────────────┐
│ │ │ ▉ ▉ ▉ ▉ │
│ ▉▉ ▉▉ ▉▉ ▉▉ │ │ ▉ ▉ ▉ ▉ │
│ ▉▉ ▉▉ ▉▉ ▉▉ │ │ ▉▉▉▉▉ ▉ ▉ │
│ ▉▉ ▉▉ ▉▉ ▉▉ │ │ ▉ ▉ ▉ │
│ ▉▉ ▉▉ ▉▉ ▉▉ │ │ ▉ ▉ ▉ ▉ │
│ ▉▉▉▉▉▉▉▉▉▉ ▉▉ ▉▉ │ │░░░░░░░░░░░░░░░░░░░░░░│
│ ▉▉ ▉▉ ▉▉ ▉▉ │ │░░░░░░░░░░░░░░░░░░░░░░│
│ ▉▉ ▉▉ ▉▉ ▉▉ │ │░░░░░░░░░░░░░░░░░░░░░░│
│ ▉▉ ▉▉ ▉▉ │ │░░░░░░░░░░░░░░░░░░░░░░│
│ ▉▉ ▉▉ ▉▉ ▉▉ │ │░░░░░░░░░░░░░░░░░░░░░░│
│ ▉▉ ▉▉ ▉▉ ▉▉ │ └──────────────────────┘
└───────────────────────────────┘
</pre>
<p>Note that we're only every using the top half of the texture — the screen is
a 2:1 rectangle but OpenGL likes square textures.</p>
<p>Getting the texture coordinates on the quad's vertices correct is important,
otherwise you'll end up drawing whatever garbage happened to be in memory at the
time which, while entertaining, is probably not what you want.</p>
<h3 id="s13-wrapping-up"><a href="index.html#s13-wrapping-up">Wrapping Up</a></h3>
<p>The last thing we need is a function to actually create the GUI:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run-gui <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-main-window</span></i>
<span class="paren3">(<span class="code">window <span class="paren4">(<span class="code">make-screen chip</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>And now we can modify our emulator's <code>run</code> function to start up a GUI in
addition to the system emulation:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run <span class="paren2">(<span class="code">rom-filename</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">chip <span class="paren5">(<span class="code">make-chip</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">setf <span class="special">*c*</span> chip</span>)</span>
<span class="paren3">(<span class="code">load-rom chip rom-filename</span>)</span>
<span class="paren3">(<span class="code">bt:make-thread <span class="paren4">(<span class="code">curry #'run-cpu chip</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren3">(<span class="code">chip8.gui.screen::run-gui chip</span>)</span></span>)</span></span>)</span> <span class="comment">; NEW</span></span></code></pre>
<p>Qt will take control of the thread and block when run, so we'll need to run our
CPU emulation in a separate thread.</p>
<p>The only thing they both write to is the <code>video-dirty</code> flag, so there's not much
synchronization to deal with (yet). It's theoretically possible that a badly
timed repaint could draw a half-finished sprite on the screen, but in practice
it's not noticeable.</p>
<p>A different architecture (e.g. passing down pixel-drawing functions into the
emulator from the UI) could solve that problem while keeping the layer separate,
but it didn't seem worth the extra effort for this little toy project.</p>
<h2 id="s14-results"><a href="index.html#s14-results">Results</a></h2>
<p>And with all that done we've <em>finally</em> got a screen to play games on!</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-screen.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-screen.png" alt="Screenshot of CHIP-8 screen running UFO.rom"></a></p>
<h2 id="s15-future"><a href="index.html#s15-future">Future</a></h2>
<p>That's all for the graphics. In the next post we'll add user input, and then
later we'll look at sound and debugging.</p>
</article></main><hr class='main-separator' /><footer><nav><a href='https://github.com/sjl/'>GitHub</a><a href='https://twitter.com/stevelosh/'>Twitter</a><a href='https://instagram.com/thirtytwobirds/'>Instagram</a><a href='https://hg.stevelosh.com/.plan/'>.plan</a></nav></footer></body></html>