nlordell [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=44357 Extended Controller Support <p>First of all, this post is <strong>not a feature</strong> request, but a proof-of-concept PICO-8 controller adapter over the GPIO <code>SERIAL</code> interface, allowing for extended controller input support including analogue sticks, analogue shoulder pads, and more buttons.</p> <img style="margin-bottom:16px" border=0 src="/media/44357/demo.gif" alt="" /> <h2>How It Works</h2> <p>GPIO works very differently on various PICO-8 targets. Because of this, the way it works on Desktop (Running PICO-8 locally on your computer) and on Web (so HTML/JS exported PICO-8 games) are very different.</p> <h3>Desktop</h3> <p>For desktop targets, you first start a controller host process that creates <code>controller.data</code> and <code>controller.clock</code> named pipe &quot;serial&quot; lines that can be &quot;connected&quot; to a PICO-8 console:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>pico8 -i controller.data &gt; controller.clock</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Additional controller data is sent over a named pipe attached to PICO-8 process's <code>-i</code> input file (serial channel <code>0x806</code>). PICO-8 <code>SERIAL</code> command allows scheduling reading a certain amount from the named pipe with:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>serial(0x806, target_address, data_length)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>The basic concept is to periodically send controller data over the named pipe for it to be read with <code>SERIAL</code> commands. The tricky bit is synchronizing the reading and writing of controller data. For this, we use the PICO-8 process's standard output. Specifically the controller application will wait for some well-formed message indicating that the game is requesting controller data. Once this message is received, controller state is read, encoded and sent over <code>controller.data</code>, which is attached to PICO-8's <code>0x806</code> serial channel. We use a single digit numerical value alone on a line representing the controller index being polled to request controller data. For example, to get the controller data for controllers index 2 and 5, you could:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>printh&quot;2\n5&quot; -- request new controller data for controllers 2 and 5 over stdout serial(0x806, 0x9a00, 60) -- read 60 bytes from input file, each controller state is 30 bytes long</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h4>Why Not <code>-o</code>?</h4> <p>In theory, it should be possible to use serial channels for both data and clock lines:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>pico8 -i controller.data -o controller.clock</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>This would have a minor advantage over piping PICO-8's standard output into the <code>controller.clock</code> serial line of not losing the PICO-8 cart's standard output. This allows, for example, for <code>PRINTH</code> to be used for debugging.</p> <p>Unfortunately, this also has a small issue and introduces a frame of controller input delay. This additional frame delay is interesting as it appears, in part, <em>to be a PICO-8 quirk</em> (and possibly even a bug). Specifically, PICO-8 will always wait for <code>SERIAL</code> reads before writes, even if the write was queued first. For example:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>serial(0x807, ...) -- write to file specified in `-o` parameter serial(0x806, ...) -- read from file specified in `-i` parameter</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>In this case, unintuitively, PICO-8 will first read from the file specified by the <code>-i</code> parameter <strong>before</strong> writing to the file specified by the <code>-o</code> parameter. Therefore, we need to request controller data one frame early. This leads to a total of 2 frames of delay for receiving controller input:</p> <ul> <li>Frame 0: request controller data <ul> <li><code>FLIP</code> causes the request to get flushed over the <code>SERIAL</code> interface</li> </ul></li> <li>Frame 1: read controller data <ul> <li>The controller data gets written to memory between frames</li> </ul></li> <li>Frame 2: controller data is in memory and can be used in the <code>_UPDATE</code> function</li> </ul> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- Frame 0 serial(0x807, ...) -- signal that we want controller data flip() -- flush the serial output -- Frame 1 serial(0x807, ...) -- already request controller data for next frame serial(0x806, ...) -- queue read of the controller data flip() -- flush the serial output and read serial data to memory -- Frame 2 peek(...) -- we can now read controller data from memory</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>By using the fact that <code>PRINTH</code> flushes immediately and piping PICO-8's standard output to the <code>controller.clock</code> serial line, we only have 1 frame of delay.</p> <h4>Controller Detection</h4> <p>To detect if no controller is attached:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>poke(0x9a0c, 0x9a) -- write marker value to memory location of first button printh&quot;0&quot; -- request controller 0 data serial(0x806, 0x9a00, 30) -- queue read of the controller data flip() -- read serial data to memory if @0x9a0c == 0x9a then -- controller not connected end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h3>Web</h3> <p>For web targets, it is very straight forward.<br /> On each animation frame, we first set the <code>pico8_gpio[0]</code> pin to a marker value, so that the PICO-8 cart can detect that it is in web mode.<br /> Then we read controller state and write it directly to <code>pico8_gpio</code>.<br /> Easy-peasy-lemon-squeezy.</p> <h3>More Info</h3> <p>I created a <a href="https://github.com/nlordell/p8-controller">GitHub project</a> with the code used for the demo included in the GIF. It includes a C program for the controller host (tested on macOS and Linux) as well as the demo cart featured in the GIF included above.</p> <h3>POOM</h3> <p>Just for fun, I also adapted the <strong>AWESOME</strong> POOM cart to use extended controller support. It was changed to have &quot;modern&quot; first person shooter controls:</p> <ul> <li>The left analogue stick is used for moving back and forth as well as strafing from left to right</li> <li>The right analogue stick is used for turning left and right</li> <li>The right shoulder button is used for shooting</li> </ul> <p>The changes required to adapt POOM <a href="https://github.com/nlordell/p8-controller/blob/main/cart/poom/poom.patch">were minimal</a>. You can try out the modified version online <a href="https://bafybeibfnaruyd4aeczgndezup6zieactifxmhm2b73ncdmskuj6ntxy4a.ipfs.infura-ipfs.io/">here</a>. Instructions for running POOM with extended controller support is also included in the aforementioned GitHub repo.</p> https://www.lexaloffle.com/bbs/?tid=48443 https://www.lexaloffle.com/bbs/?tid=48443 Thu, 07 Jul 2022 19:22:13 UTC PkZ - Pokemon Gen 1 Sprite Compression Algorithm <p>This post is mostly just to share an experiment I did when playing around with compressing graphics.</p> <p>I noticed when looking around, that many PICO-8 carts that do compress graphics were using some form of LZ or RLE compression. Out of curiosity, I decided to do some research on how the Generation 1 Pokemon games compressed an impressive amount of graphics data into the small cart size. It turns out that they use a form of RLE compression. I won't go into detail myself, but instead refer to a great <a href="https://www.youtube.com/watch?v=aF1Yw_wu2cM">YouTube video that very precisely describes how it works</a>.</p> <p>Essentially, this compression algorithm stands out because:</p> <ul> <li>It splits the graphics into bit planes (i.e. 1 bit-per-pixel images)</li> <li>It operates on pixel pairs, only encoding runs of pairs of 0's</li> <li>It uses several flags to control how the bit planes are mixed in order to increase compression ratios</li> <li>The original algorithm compresses 2bpp images (i.e. 4 colours). That being said, there is no reason why it cannot be extended to higher colour depths</li> </ul> <p>Anyway, here is an implementation of a decompression routine for the Pokemon Generation 1 RLE image compression format. Overall, I'm not sure how useful this will be. It costs 400 tokens and decoding the sample 15x15 sprite in the demo cart takes <strong>12</strong> frames@60fps. On the plus side, it fits neatly into 544 bytes (less than a single row in the sprite editor), compared to its decompressed size of 7200 bytes, albeit with only a quarter of the colours. Note that the compression ratio greatly depends on the data. Also, I did not put much effort into optimizing the implementation for neither tokens nor CPU cyles.</p> <p>In my opinion some of the &quot;cool stuff&quot; that this approach to compression does (that can be applied to other algorithms) is:</p> <ul> <li>Using a reduced colour depth greatly reduces graphics size</li> <li>RLE compressing a bit plane has some neat properties (the linked video goes into some more detail about this)</li> </ul> <p> <table><tr><td> <a href="/bbs/?pid=88121#p"> <img src="/bbs/thumbs/pico8_pkz_01-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=88121#p"> pkz 0.1</a><br><br> by <a href="/bbs/?uid=44357"> nlordell</a> <br><br><br> <a href="/bbs/?pid=88121#p"> [Click to Play]</a> </td></tr></table> </p> https://www.lexaloffle.com/bbs/?tid=41742 https://www.lexaloffle.com/bbs/?tid=41742 Wed, 24 Feb 2021 21:00:43 UTC