220 Commits

Author SHA1 Message Date
dranke c9dadaca2e try add safe.directory 2023-01-30 09:48:57 +01:00
dranke 5dc3e281ce and again 2023-01-30 09:45:28 +01:00
dranke ce3afb821f another attempt changing the owner to fix permission issue 2023-01-30 09:43:54 +01:00
dranke 2652a351ad remove chown again 2023-01-30 09:38:57 +01:00
dranke 9109722409 fix main.yml 2023-01-30 09:37:12 +01:00
dranke f861c262a1 update ci actions, hopefully fix permission error 2023-01-30 09:15:09 +01:00
dranke bbfb5eba49 add back event debouncing in file watcher 2023-01-30 00:09:25 +01:00
dranke 9e19ff1761 prepare for 0.2.2 release 2023-01-28 12:47:16 +01:00
dranke 5d85aeac09 call an exported start function if it exists 2023-01-28 12:31:16 +01:00
dranke 30eb953d5d remove test code that was accidentally committed 2023-01-28 11:37:05 +01:00
dranke abdf780533 only open browser once cart has been compiled succesfully 2023-01-28 11:33:49 +01:00
dranke fc05a54e2c fix control codes 4-6 as parameters to other control codes 2023-01-28 10:58:28 +01:00
dranke b33099c828 update more dependencies 2023-01-28 02:11:25 +01:00
dranke d478d3ad49 enable timeapi in winapi crate 2023-01-27 00:13:32 +01:00
dranke 502852e59a update uw8-window dependencies 2023-01-26 23:40:54 +01:00
dranke 5efa8b3465 first batch of dependency updates 2023-01-26 22:45:34 +01:00
dranke daf2a02cd8 fix sndGes name & add missing auto-import 2023-01-23 23:21:45 +01:00
dranke 8d5374a867 add support to ignore empty memory section when packing 2022-11-03 23:27:53 +01:00
dranke 142b6a4c15 fix typo in sample rate selection 2022-10-30 00:07:58 +02:00
dranke 877fceb089 add --fps parameter to output fps 2022-07-25 23:44:06 +02:00
dranke f0ba0f2b99 update site with 0.2.1 release 2022-07-25 08:46:23 +02:00
dranke 760664eb77 add some command line switches for the gpu window 2022-07-23 22:34:59 +02:00
dranke 465e66ff4b slight improvement to packed size display 2022-07-23 00:30:14 +02:00
dranke e4579d81bc add chromatic version of fast crt shader + auto crt shader 2022-07-21 23:03:52 +02:00
dranke 1f5042059c fix inputs getting stuck 2022-07-21 19:37:36 +02:00
dranke 499bb02f2c restructure control flow of uw8-window to hopefully make it work on MacOS 2022-07-21 08:51:17 +02:00
dranke 57a92ba79a update version number 2022-07-17 22:44:46 +02:00
dranke 7ec1e68a00 very slightly improve frame timings when not quite reaching 60 fps 2022-07-14 00:05:15 +02:00
dranke 539d19e0d7 fix square filter, arrow keys on windows 2022-07-12 20:18:06 +02:00
dranke e9a5f702b4 finish fast_crt shader 2022-07-12 00:22:05 +02:00
dranke ba0b037ec2 add first version of fast crt shader 2022-07-11 09:26:11 +02:00
dranke 0130d1c906 implement square filter 2022-07-10 23:56:19 +02:00
dranke 379ece5cbf restructuring for multiple filters 2022-07-10 23:17:31 +02:00
dranke c9c5cb76bd start refactoring wgpu code to allow for different upscale filters 2022-07-10 16:37:39 +02:00
dranke a6d6615231 only draw area covered by framebuffer 2022-07-10 12:18:53 +02:00
dranke fbc86fa78d implement input for gpu window 2022-07-09 21:16:25 +02:00
dranke eb724e8785 keyboard input is working for cpu window again 2022-07-09 13:18:51 +02:00
dranke f559c5b7d4 restructure run_native to report errors back to caller 2022-07-09 12:24:59 +02:00
dranke 9dabf75732 first somewhat working version of uw8 using gpu window 2022-07-08 23:29:39 +02:00
dranke b0adf7748d add uw8-window crate 2022-07-08 22:11:00 +02:00
dranke 7aa70ef39d some more tunnel optimization 2022-07-08 22:08:30 +02:00
dranke 2ce91ef49c continue TomCat's optimization of the tunnel 2022-06-22 00:18:20 +02:00
dranke 7caad08b7c print fractional compressed size 2022-06-22 00:16:35 +02:00
dranke 1f6de62e5d add test program for filled circle drawing 2022-06-12 23:54:56 +02:00
dranke caeaa82787 optimize hline function, update wasmtime 2022-06-12 14:28:01 +02:00
dranke e0450c9039 successfully reprod audio recording in firefox 2022-06-09 23:54:43 +02:00
dranke 95d0d92a6f first try for a simple repro of firefox resample fail (failed) 2022-06-09 23:36:23 +02:00
dranke 7a6dd0ab6d improve formatting of change log 2022-05-09 01:13:20 +02:00
dranke e7a00dd9c6 prepare for v0.2.0 release 2022-05-09 00:51:51 +02:00
dranke a02243d98c add sound to skipahead 2022-05-08 20:28:06 +02:00
dranke 599873890a add docs for debug output 2022-05-08 19:47:07 +02:00
dranke 8e9bb002bc improve sleep timer resolution on windows 2022-05-08 18:08:29 +02:00
dranke b2b990333e prepare for v0.2.0-rc3 release 2022-05-08 00:51:11 +02:00
dranke d1556f7be8 add support for writing debug output to the console 2022-05-08 00:41:11 +02:00
dranke 9f548cd6f0 update curlywas 2022-05-08 00:19:49 +02:00
dranke 7cea4eebd3 improve frame timings some more in both runtimes 2022-05-05 09:53:36 +02:00
dranke 3f67e92c5c prepare for 0.2.0-rc2 release 2022-05-04 08:48:16 +02:00
dranke a2714f25e4 fix unstable playback in browser runtime 2022-05-04 00:32:41 +02:00
dranke 7e203d93e6 implement scheduled sound updates in native runtime 2022-05-02 08:19:34 +02:00
dranke e44c87d1f6 add port of cracklebass 2022-04-29 00:22:55 +02:00
dranke 614b7cf358 add 0.2.0-rc1 download links to site 2022-04-26 23:20:05 +02:00
dranke c42a484adb Update version number to 0.2.0-rc1 2022-04-26 22:38:31 +02:00
dranke 3a5f2bf865 add another small doc paragraph for snd function 2022-04-26 19:41:05 +02:00
dranke 2dee1b30a4 add support for non 44.1kHz audio configs (resampling) 2022-04-26 00:02:34 +02:00
dranke 42f7887ab2 install missing alsa dependency on ci 2022-04-24 23:48:58 +02:00
dranke 4c82f4ad02 add 0.2.0rc1 web runtime 2022-04-24 23:32:28 +02:00
dranke 4dd8c3b029 add audio track to recorded video 2022-04-24 23:17:58 +02:00
dranke 2bf8938183 remove back-channel from audio thread for now
It needs some more thought before committing to it.
2022-04-24 15:52:19 +02:00
dranke 491bf88ade add first version of sound doku 2022-04-24 00:01:54 +02:00
dranke e05701300c implement backchannel from audio thread 2022-04-22 00:28:19 +02:00
dranke df0c169d54 use the last byte 2022-04-20 23:19:26 +02:00
dranke 61941bceeb add simple example for doing music just using playNote function 2022-04-20 21:48:03 +02:00
dranke 8fa64519e4 add playNote fn and bell chr (7) 2022-04-19 00:27:53 +02:00
dranke 7a52ce4e4c implement custom base address for GES 2022-04-18 16:40:16 +02:00
dranke 6f20d303c8 adjust tim_ges to latest sound chip revision 2022-04-18 11:52:07 +02:00
dranke a5edeb21d8 update some dependencies 2022-04-17 22:32:59 +02:00
dranke 2839fe5be4 implement sound in native runtime 2022-04-17 22:16:17 +02:00
dranke 893158e136 use setTimeout instead of requestAnimationFrame for 60 fps update 2022-04-17 12:26:01 +02:00
dranke 7c5f43f152 Merge branch 'master' into sound 2022-04-11 00:23:08 +02:00
dranke f32b0762b0 update curlywas 2022-04-11 00:18:26 +02:00
dranke 9ebb6b6d34 pause module when page doesn't have focus 2022-04-10 23:24:04 +02:00
dranke 8a10b99eeb fix non-windows build 2022-04-08 21:22:34 +02:00
dranke 6c064a1dd8 enable ansi terminal on windows 10 cmd 2022-04-08 21:11:51 +02:00
dranke 37f12f5a2c update web runtime 2022-04-04 09:31:43 +02:00
dranke 8ad2885a55 implement ring modulation 2022-04-02 18:19:45 +02:00
dranke 1917057b81 fixed one pole filter, wide stereo bit 2022-04-02 00:06:04 +02:00
dranke 82c1ddb867 improve attack and noise 2022-04-01 00:40:56 +02:00
dranke 8713aa8930 adjust tim_ges to new soundchip version 2022-03-19 22:55:40 +01:00
dranke 0f82e6e711 removed aliasing in rect and saw oscilators 2022-03-19 14:53:21 +01:00
dranke 0ade24ebf6 add source file to build wasm module with just ges emulation 2022-03-19 11:03:05 +01:00
dranke 29186c806f add pulse width support to other wave types 2022-03-10 23:05:07 +01:00
dranke b626d2609a implement proper exponential envelope timings 2022-03-09 23:03:15 +01:00
dranke 39ead8220f slight optimization, add pulse width modulation to melody voices 2022-03-09 09:22:27 +01:00
dranke ce18a8a162 add initial pulse width support 2022-03-08 22:52:12 +01:00
dranke a15e796489 first full version of time for ges 2022-03-08 22:20:54 +01:00
dranke f178076b86 all basic wave forms, filters, panning 2022-03-08 09:46:35 +01:00
dranke 81adcf0198 implement more of the sound-chip 2022-03-07 23:58:54 +01:00
dranke 780caf965a sync sound registers to sound thread 2022-03-07 09:35:11 +01:00
dranke 2033f9a172 wait for audio ready before starting cart, add button to unsuspend audio
fixes missing sound when auto-starting cart in chrome
2022-03-06 14:08:44 +01:00
dranke 0d514c7dd3 steady on now down to 197 bytes 2022-03-06 10:13:48 +01:00
dranke a8eb3bda27 some clean up and optimization on steady on tim 2022-03-05 23:54:13 +01:00
dranke 8b765a5742 Merge branch 'master' into sound 2022-03-05 23:10:14 +01:00
dranke 7197c11586 fix microw8.html no-autoload mode 2022-03-05 23:10:04 +01:00
dranke 99a423619e fix watch mode not working when initial compile fails 2022-03-05 21:18:55 +01:00
dranke 9063e872d3 ported steady on tim as a sound test 2022-03-04 23:38:27 +01:00
dranke 85240599e8 first working version with sound 2022-03-04 09:50:10 +01:00
dranke 35ec5fdb59 add simple bytebeat example to test first implementation with 2022-03-02 22:42:23 +01:00
dranke a6a82ff5a1 update wasmtime version 2022-03-02 21:50:26 +01:00
dranke 973814a629 update hero link to 0.1.2 2022-03-02 08:54:34 +01:00
dranke 00d21b4745 add include dir to release script 2022-02-28 23:59:51 +01:00
dranke d11b46576a prepare for 0.1.2 release 2022-02-28 23:19:58 +01:00
dranke add49a1f8b fix #2: Crash when drawing zero sized line 2022-02-27 22:09:54 +01:00
dranke 8815a8e02e add full dependencies to file watcher 2022-02-27 21:50:33 +01:00
dranke eb7c33d412 update curlywas, use 'include "microw8-api.cwa"' in examples 2022-02-26 23:41:15 +01:00
dranke 47ad3b4f30 add command to uw8-tool to write api include file 2022-02-26 22:13:15 +01:00
dranke 0f668fb6e9 fix typo in atan2 docs, change 'i' character in font 2022-02-26 21:01:57 +01:00
dranke f876f59e80 Merge branch 'browser-run' into next 2022-02-26 21:00:58 +01:00
dranke 44b8656f29 make both native and browser runtime optional 2022-02-21 22:54:14 +01:00
dranke 90467f7c5b add some more polish to web runner 2022-02-20 23:54:28 +01:00
dranke c56196bd2e actual run --browser instead of just optimized technotunnel 2022-02-20 18:26:57 +01:00
dranke 266493ca1c run --browser is working (very unpolished) 2022-02-19 23:58:13 +01:00
dranke 4c75ba2e44 start implementing run --browser 2022-02-19 16:21:02 +01:00
dranke f1493ebded Split core web runtime into separate js 2022-02-17 23:27:57 +01:00
dranke 5d41733142 add download links to 0.1.1 2022-02-04 23:23:29 +01:00
dranke c25a52b61b Prepare 0.1.1 release
add devkit mode to web runtime
add unpack and compile commands to uw8
2022-02-04 22:25:17 +01:00
dranke 619ea903ba add support for table/element section in pack command 2022-02-02 00:02:55 +01:00
dranke 9b900a49e4 add screenshot on F9, increase video rate + docs 2022-01-30 20:03:13 +01:00
dranke f21497dd2e implement more robust file watcher 2022-01-29 15:22:14 +01:00
dranke 381eaf970f Add first basic logo version 2022-01-29 15:21:35 +01:00
dranke 9632adb57f add basic video recording on F10 in web runtime 2022-01-24 09:05:10 +01:00
dranke 33e08e9b73 add watchdog to interupt hanging update 2022-01-23 20:18:07 +01:00
dranke cacde9136c update dependencies, disable wayland support
wayland support doesn't fully work with gnome
(missing window decoration)
2022-01-23 13:16:04 +01:00
dranke 81a38c2d75 Add download links to released 0.1.0 2022-01-13 06:40:01 +01:00
dranke c9ae04e652 last preparations for 0.1.0 release 2022-01-13 06:20:00 +01:00
dranke 179b3eaed1 Finish docs, add hero screenshot 2022-01-12 22:18:36 +01:00
dranke b7d0004307 write up usage instructions for the uw8 tool 2022-01-12 00:03:54 +01:00
dranke b34685b1c2 finished api docs 2022-01-10 23:52:09 +01:00
dranke 3d79239a01 work on api documentation 2022-01-09 23:30:14 +01:00
dranke f062e545f6 implement "itch.io support" 2022-01-04 22:34:21 +01:00
dranke 68890e62ad don't exit on runtime error in watch mode 2022-01-03 23:36:58 +01:00
dranke 10ba7ed3bb make use of nontrapping-fptoint, add tunnel.cwa as reference 2022-01-02 16:34:11 +01:00
dranke acea5cb6e0 enable nontrapping fptoint feature in c and zig example 2022-01-02 16:06:01 +01:00
dranke d239775411 forgot to add file 2022-01-02 15:42:28 +01:00
dranke 20365a0dd0 port tunnel example to C 2022-01-02 15:22:11 +01:00
dranke 88ee0e1bef port rust tunnel example to zig 2022-01-02 13:58:56 +01:00
dranke 26206a312a add filter-exports command to automatically remove unused exports
this removes the need for a manual step in the rust example
2022-01-01 19:02:58 +01:00
dranke 6a75988489 update rust example 2022-01-01 15:51:18 +01:00
dranke 397ff19d80 test remaining control codes 2022-01-01 15:37:17 +01:00
dranke b7e8ddf0f1 test new control codes 2022-01-01 14:19:58 +01:00
dranke ed9d9fdeb5 implement more control codes 2021-12-31 23:09:49 +01:00
dranke cd1275a78f implement framework for control codes 2021-12-31 22:12:21 +01:00
dranke 32345876b9 v0.1pre5 + some virtual fireworks for today 2021-12-31 11:08:31 +01:00
dranke 6ca63b87e5 add rectangle_outline function 2021-12-30 22:03:46 +01:00
dranke bf7604d385 implement circle_outline function 2021-12-30 16:25:53 +01:00
dranke 0f22b1efb9 some more loader size optimizations 2021-12-28 21:43:58 +01:00
dranke 4521fb73ef size optimized uncompress function in loader 2021-12-28 17:32:11 +01:00
dranke 6f22e00487 update upkr version 2021-12-27 23:46:03 +01:00
dranke cf0e40a0e5 add uw8 pack command 2021-12-27 22:02:27 +01:00
dranke 462dc3a1c6 update upkr (new tweaked compressed format) 2021-12-27 17:34:00 +01:00
dranke 0f795ae061 binaries should be built with --release 2021-12-22 21:49:57 +01:00
dranke 2eca3fa8c8 install libxkbcommon-dev for linux build 2021-12-22 21:38:51 +01:00
dranke 7db0d66ad4 first attempt at ci setup to build uw8 binaries 2021-12-22 21:31:29 +01:00
dranke 28713bd1df implement drop handler to accept carts via drag'n'drop 2021-12-19 14:45:16 +01:00
dranke a41f9ebe4d add support for load from url 2021-12-19 00:12:26 +01:00
dranke 0d0ecdc344 put skipahead.uw8 into the correct directory 2021-12-19 00:06:44 +01:00
dranke 83c9d46cea add skipahead.uw8 to site to try out load from url 2021-12-18 23:59:25 +01:00
dranke 581740cbdb add support for .wat files, add xorscroll.wat example 2021-12-16 23:58:58 +01:00
dranke 299c08af5b fix isButtonTriggered in web runtime, release v0.1pre4 2021-12-12 17:06:19 +01:00
dranke 00cbe656cf add graphicsText mode and finish font_palette example 2021-12-12 00:54:58 +01:00
dranke 6f92fd8bb3 add line function to platform 2021-12-11 23:56:46 +01:00
dranke 72e977c4ba fix line drawing 2021-12-11 23:27:38 +01:00
dranke 88182f94ee further improve line drawing, start/end pixels still buggy 2021-12-11 00:04:58 +01:00
dranke 940d6a1957 start line implementation, still a little buggy 2021-12-09 22:21:50 +01:00
dranke ed24904d60 more font and text functions 2021-12-08 23:59:58 +01:00
dranke 1e9910a983 continued with the upper half of the font 2021-12-08 00:04:20 +01:00
dranke 126cceee30 release web runtime with gamepad support 2021-12-03 09:01:01 +01:00
dranke b73cad3092 add gamepad support 2021-12-03 00:24:26 +01:00
dranke aba00b1cae add input to web runtime, add v0.1pre3 to site 2021-12-01 23:36:39 +01:00
dranke 04c2c1aeb0 improved font 2021-12-01 19:49:32 +01:00
dranke 9ff4440238 increased gravity 2021-12-01 19:17:41 +01:00
dranke e7333279e0 saved 10 bytes by chosing better float constants 2021-12-01 17:13:54 +01:00
dranke 1cc480cd48 add first iteration font and print functions 2021-12-01 09:08:21 +01:00
dranke 8695a222f0 add support for game input 2021-11-28 22:47:23 +01:00
dranke 667a222331 tic(i32) => upd(), time(), first version of palette 2021-11-27 21:38:48 +01:00
dranke 0cb169d62e added -p -c -l -o options to uw8 run command 2021-11-26 23:45:29 +01:00
dranke fcf24110c5 update to new upkr version, use css to scale canvas 2021-11-24 22:51:27 +01:00
dranke 88bc4fe364 compress platform module 2021-11-21 23:38:01 +01:00
dranke f6d0bdfa8f add simple rust "script" to build platform binaries 2021-11-21 16:46:20 +01:00
dranke d174f46db3 update native runtime to changes in loader 2021-11-21 16:04:37 +01:00
dranke 5aabf49b63 clean up web runtime 2021-11-21 15:47:27 +01:00
dranke 40d684ea9a loader.wasm binary now includes compressed base 2021-11-21 13:36:09 +01:00
dranke 93b2bb60bd implement upkr unpacker in wasm, use to load compressed base 2021-11-20 23:36:16 +01:00
dranke f7e3202c39 add support to directly run cwa files 2021-11-16 23:54:58 +01:00
dranke 14810b7fc8 fix export section not written when needed 2021-11-16 07:52:06 +01:00
dranke 812847d9e5 fix start function not being remapped 2021-11-14 17:19:01 +01:00
dranke dbe59a45f5 fix rectangle function, change resolution to 320x240 2021-11-14 17:09:37 +01:00
dranke 289382f351 implement watch mode 2021-11-14 15:18:21 +01:00
dranke bce7d82dae move native microw8 code into object to prepare for file watch 2021-11-14 14:43:02 +01:00
dranke 294b9f5634 add palette support (default is still greyscale for now) 2021-11-13 22:29:20 +01:00
dranke 944856b267 implement first batch of drawing functions (filled rect/circle) 2021-11-13 17:44:15 +01:00
dranke 3be4e7b101 add platform module providing implementation of random functions 2021-11-08 23:16:17 +01:00
dranke 35ff01d7a8 get native implementation working again 2021-11-08 22:30:34 +01:00
dranke 021acb960b add support for data and start section in uw8-tool 2021-11-08 09:21:02 +01:00
dranke 39121acd24 add support for global vars 2021-11-07 22:22:21 +01:00
dranke b474f2581e sort exported functions to the front 2021-11-07 08:36:53 +01:00
dranke d86f91789b move all imports to module "env", some tweaks to rust example 2021-11-06 19:46:16 +01:00
dranke 32d2519d2d add simple rust example 2021-11-06 18:52:52 +01:00
dranke 2178ca5296 Added some more documentation. 2021-11-06 12:38:12 +01:00
dranke c63dc94487 add links from github repo and back 2021-11-05 20:17:40 +01:00
dranke 104ee1fa5d Fix gh-pages deply token 2021-11-05 20:06:22 +01:00
dranke 4b8272e5dc Create website build action 2021-11-05 19:57:05 +01:00
dranke 492f151059 some cleanup, add skeleton of zola based website 2021-11-05 19:44:44 +01:00
dranke 31cc63a93b first version of uw8-tool pack command 2021-11-04 23:55:08 +01:00
dranke 348657163d improve file selection button, first version of uw8-tool to build base.wasm 2021-11-03 23:58:44 +01:00
dranke 160afba8b0 added unlicense, more style, started math imports 2021-11-03 22:06:04 +01:00
dranke a6336c5d11 some styling and uw8.ram -> env.memory 2021-11-03 09:15:31 +01:00
dranke b34ff18a60 add file input to load cart from filesystem 2021-11-02 23:27:46 +01:00
dranke a39226b908 first version with uw8loader 2021-11-02 00:04:19 +01:00
dranke ddc9a70006 first poc for a web runtime 2021-10-28 21:44:30 +02:00
dranke 57eed68be8 first hacky uw8 runner 2021-10-26 20:33:15 +02:00
135 changed files with 17315 additions and 1637 deletions
+50
View File
@@ -0,0 +1,50 @@
name: Rust
on:
push:
branches: [ master ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
strategy:
matrix:
build: [ linux, windows, macos ]
include:
- build: linux
os: ubuntu-latest
exe: uw8
- build: windows
os: windows-latest
exe: uw8.exe
- build: macos
os: macos-latest
exe: uw8
runs-on: ${{ matrix.os }}
steps:
- name: Install dependencies
run: sudo apt-get install -y libxkbcommon-dev libasound2-dev
if: matrix.os == 'ubuntu-latest'
- name: Checkout
uses: actions/checkout@v3
- name: Cache build dirs
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build
run: cargo build --release --verbose
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: uw8-${{ matrix.build }}
path: target/release/${{ matrix.exe }}
+21
View File
@@ -0,0 +1,21 @@
on:
push:
branches:
- master
name: Build and deploy GH Pages
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
- name: Add safe directory /github/workspace
run: git config --global --add safe.directory /github/workspace
- name: build_and_deploy
uses: shalzz/zola-deploy-action@v0.16.1
env:
# Target branch
PAGES_BRANCH: gh-pages
BUILD_DIR: site
# Provide personal access token
TOKEN: $GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}
+5
View File
@@ -0,0 +1,5 @@
/target
.cargo/
.vscode/
/examples/**/*.wasm
/examples/**/*.uw8
+3
View File
@@ -0,0 +1,3 @@
[submodule "site/themes/juice"]
path = site/themes/juice
url = https://github.com/huhu/juice
-3
View File
@@ -1,3 +0,0 @@
<!doctype html>
<title>404 Not Found</title>
<h1>404 Not Found</h1>
Generated
+4421
View File
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
[package]
name = "uw8"
version = "0.2.2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["native", "browser"]
native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
[dependencies]
wasmtime = { version = "5.0.0", optional = true }
anyhow = "1"
env_logger = "0.10"
log = "0.4"
uw8-window = { path = "uw8-window", optional = true }
notify-debouncer-mini = { version = "0.2.1", default-features = false }
pico-args = "0.5"
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" }
wat = "1"
uw8-tool = { path = "uw8-tool" }
same-file = "1"
warp = { version = "0.3.3", optional = true }
tokio = { version = "1.24.0", features = ["sync", "rt"], optional = true }
tokio-stream = { version = "0.1.11", features = ["sync"], optional = true }
webbrowser = { version = "0.8.6", optional = true }
ansi_term = "0.12.1"
cpal = { version = "0.14.2", optional = true }
rubato = { version = "0.12.0", optional = true }
+103
View File
@@ -0,0 +1,103 @@
# MicroW8
MicroW8 is a WebAssembly based fantasy console inspired by the likes of [TIC-80](https://tic80.com/), [WASM-4](https://wasm4.org/) and [PICO-8](https://www.lexaloffle.com/pico-8.php).
The initial motivation behind MicroW8 was to explore whether there was a way to make WebAssembly viable for size-coding. (Size coding being the art of creating tiny (often <= 256 bytes) graphical effects and games.) The available examples so far are all in this space, however, I very carefully made sure that all design decisions make sense from the point of view of bigger projects as well.
See [here](https://exoticorn.github.io/microw8/) for more information and docs.
## Specs
* Screen: 320x240, 256 colors, 60Hz
* Modules: Up to 256KB (WASM)
* Memory: 256KB
* Gamepad input (D-Pad + 4 Buttons)
## Downloads
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-windows.zip)
The download includes
* `microw8.html`: The web runtime, a small, self-contained html file that can be opened in any modern browser to load and run MicroW8 carts.
* `uw8`/`uw8.exe`: The MicroW8 dev tool, including a native runtime.
* `examples`: Example source code in CurlyWas and Wat (WebAssembly text format).
* `carts`: The examples compiled to `.uw8` carts.
## uw8 dev tool
```
uw8 run [<options>] <file>
Runs <file> which can be a binary WebAssembly module, an `.uw8` cart, a wat (WebAssembly text format) source file or a CurlyWas source file.
Options:
-b, --browser : Run in browser instead of using native runtime
-t, --timeout FRAMES : Sets the timeout in frames (1/60s)
-w, --watch : Reloads the given file every time it changes on disk.
-p, --pack : Pack the file into an .uw8 cart before running it and print the resulting size.
-u, --uncompressed : Use the uncompressed uw8 format for packing.
-l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow.
-o FILE, --output FILE : Write the loaded and optionally packed cart back to disk.
when using the native runtime:
-m, --no-audio : Disable audio, also reduces cpu load a bit
--no-gpu : Force old cpu-only window code
--filter FILTER : Select an upscale filter at startup
--fullscreen : Start in fullscreen mode
Note that the cpu-only window does not support fullscreen nor upscale filters.
Unless --no-gpu is given, uw8 will first try to open a gpu accelerated window, falling back to the old cpu-only window if that fails.
Therefore you should rarely need to manually pass --no-gpu. If you prefer the old pixel doubling look to the now default crt filter,
you can just pass "--filter nearest" or "--filter 1".
The upscale filter options are:
1, nearest : Anti-aliased nearest filter
2, fast_crt : Very simple, cheap crt filter, not very good below a window size of 960x720
3, ss_crt : Super sampled crt filter, a little more demanding on the GPU but scales well to smaller window sizes
4, chromatic_crt : Variant of fast_crt with a slight offset of the three color dots of a pixel, still pretty cheap
5, auto_crt (default) : ss_crt below 960x720, chromatic_crt otherwise
You can switch the upscale filter at any time using the keys 1-5. You can toggle fullscreen with F.
uw8 pack [<options>] <infile> <outfile>
Packs the WebAssembly module or text file, or CurlyWas source file into a .uw8 cart.
Options:
-u, --uncompressed : Use the uncompressed uw8 format for packing.
-l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow.
uw8 unpack <infile> <outfile>
Unpacks a MicroW8 module into a standard WebAssembly module.
uw8 compile [<options>] <infile> <outfile>
Compiles a CurlyWas source file to a standard WebAssembly module. Most useful together with
the --debug option to get a module that works well in the Chrome debugger.
Options:
-d, --debug : Generate a name section to help debugging
uw8 filter-exports <infile> <outfile>
Reads a binary WebAssembly module, removes all exports not used by the MicroW8 platform + everything that is unreachable without those exports and writes the resulting module to <outfile>.
```
## Examples
* [Fireworks](https://exoticorn.github.io/microw8/v0.1pre5#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
* [Skip Ahead](https://exoticorn.github.io/microw8/v0.1pre5#AgyfpZ80wkW28kiUZ9VIK4v+RPnVxqjK1dz2BcDoNyQPsS2g4OgEzkTe6jyoAfFOmqKrS8SM2aRljBal9mjNn8i4fP9eBK+RehQKxxGtJa9FqftvqEnh3ez1YaYxqj7jgTdzJ/WAYVmKMovBT1myrX3FamqKSOgMsNedLhVTLAhQup3sNcYEjGNo8b0HZ5+AgMgCwYRGCe//XQOMAaAAzqDILgmpEZ/43RKHcQpHEQwbURfNQJpadJe2sz3q5FlQnTGXQ9oSMokidhlC+aR/IpNHieuBGLhFZ2GfnwVQ0geBbQpTPA==) (229 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21
* [OhNoAnotherTunnel](https://exoticorn.github.io/microw8/v0.1pre4#Ag95rdCB5Ww5NofyQaKF4P1mrNRso4azgiem4hK99Gh8OMzSpFq3NsNDo7O7pqln10D11l9uXr/ritw7OEzKwbEfCdvaRnS2Z0Kz0iDEZt/gIqOdvFmxsL1MjPQ4XInPbUJpQUonhQq29oP2omFabnQxn0bzoK7mZjcwc5GetHG+hGajkJcRr8oOnjfCol8RD+ha33GYtPnut+GLe4ktzf5UxZwGs6oT9qqC61lRDakN) (177 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
* [Technotunnel](https://exoticorn.github.io/microw8/v0.1pre4#AqL8HeK1M9dn2nWNIF5vaq/Vh64pMt5nJIFoFKpBMPUsGtDtpqjo1JbT9LzPhAxCqJ7Yh4TA6oTGd4xhLowf+cWZMY73+7AZmfXJJsBi4cej/hH+4wlAgxFIrnOYnr/18IpnZbsHf0eGm1BhahX74+cVR0TRmNQmYC7GhCNS3mv/3MJn74lCj7t28aBJPjEZhP9fGXdG2u5Egh/Tjdg=) (158 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
+6
View File
@@ -0,0 +1,6 @@
with import <nixpkgs> {};
stdenv.mkDerivation {
name = "dev-environment"; # Probably put a more meaningful name here
buildInputs = [ pkg-config libxkbcommon ];
}
-689
View File
@@ -1,689 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Docs | </title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" type="image/png" href="/favicon.ico">
<style>
:root {
/* Primary theme color */
--primary-color: #202024;
/* Primary theme text color */
--primary-text-color: #808070;
/* Primary theme link color */
--primary-link-color: #8080a0;
/* Secondary color: the background body color */
--secondary-color: #e0e0e8;
--secondary-text-color: #1a1818;
/* Highlight text color of table of content */
--toc-highlight-text-color: #d46e13;
}
</style>
<link href="https://fonts.googleapis.com/css?family=Alfa+Slab+One&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Fira+Sans:400,500,600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/normalize.css">
<link rel="stylesheet" href="https://exoticorn.github.io/microw8/juice.css">
</head>
<body>
<header class="box-shadow">
<a href="https:&#x2F;&#x2F;exoticorn.github.io&#x2F;microw8&#x2F;">
<div class="logo">
<img src="https://exoticorn.github.io/microw8/img/microw8.svg" alt="logo">
MicroW8
</div>
</a>
<nav>
<a class="nav-item subtitle-text" href="https:&#x2F;&#x2F;exoticorn.github.io&#x2F;microw8&#x2F;versions&#x2F;"></a>
<a class="nav-item subtitle-text" href="https:&#x2F;&#x2F;exoticorn.github.io&#x2F;microw8&#x2F;docs&#x2F;">Docs</a>
<a class="nav-item subtitle-text" href="https:&#x2F;&#x2F;github.com&#x2F;exoticorn&#x2F;microw8">Github</a>
</nav>
</header>
<main>
<div class="toc">
<div class="toc-sticky">
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#overview">Overview</a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#memory-map">Memory map</a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#api">API</a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#math"><small>- Math</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#random"><small>- Random</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#graphics"><small>- Graphics</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#input"><small>- Input</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#text-output"><small>- Text output</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#sound"><small>- Sound</small></a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#the-uw8-tool">The uw8 tool</a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#uw8-run"><small>- uw8 run</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#uw8-pack"><small>- uw8 pack</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#uw8-unpack"><small>- uw8 unpack</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#uw8-compile"><small>- uw8 compile</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#uw8-filter-exports"><small>- uw8 filter-exports</small></a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#other-useful-tools">Other useful tools</a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#distribution">Distribution</a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#base64-encoded-link"><small>- Base64 encoded link</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#url-parameter"><small>- url parameter</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#html-uw8"><small>- .html + .uw8</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#itch-io"><small>- Itch.io</small></a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#uw8-format">.uw8 format</a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#format-version-00"><small>- Format version 00:</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#format-version-01"><small>- Format version 01:</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#format-version-02"><small>- Format version 02:</small></a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#the-web-runtime">The web runtime</a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#input-1"><small>- Input</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#video-recording"><small>- Video recording</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#screenshot"><small>- Screenshot</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/docs/#devkit-mode"><small>- Devkit mode</small></a>
</div>
</div>
</div>
<div class="content text">
<div class="heading-text">Docs</div>
<h1 id="overview">Overview</h1>
<p>MicroW8 loads WebAssembly modules with a maximum size of 256kb. Your module needs to export
a function <code>fn upd()</code> which will be called once per frame.
After calling <code>upd</code> MicroW8 will display the 320x240 8bpp framebuffer located
at offset 120 in memory with the 32bpp palette located at 0x13000.</p>
<p>The memory has to be imported as <code>env</code> <code>memory</code> and has a maximum size of 256kb (4 pages).</p>
<p>If the module exports a function called <code>start</code>, it will be called once after the module is
loaded.</p>
<h1 id="memory-map">Memory map</h1>
<pre style="background-color:#ffffff;color:#202020;"><code><span>00000-00040: user memory
</span><span>00040-00044: time since module start in ms
</span><span>00044-0004c: gamepad state
</span><span>0004c-00050: number of frames since module start
</span><span>00050-00070: sound data (synced to sound thread)
</span><span>00070-00078: reserved
</span><span>00078-12c78: frame buffer
</span><span>12c78-12c7c: sound registers/work area base address (for sndGes function)
</span><span>12c7c-13000: reserved
</span><span>13000-13400: palette
</span><span>13400-13c00: font
</span><span>13c00-14000: reserved
</span><span>14000-40000: user memory
</span></code></pre>
<h1 id="api">API</h1>
<p>All API functions are found in the <code>env</code> module.</p>
<h2 id="math">Math</h2>
<p>These all do what you'd expect them to. All angles are in radians.</p>
<h3 id="fn-asin-x-f32-f32">fn asin(x: f32) -&gt; f32</h3>
<p>Returns the arcsine of <code>x</code>.</p>
<h3 id="fn-acos-x-f32-f32">fn acos(x: f32) -&gt; f32</h3>
<p>Returns the arccosine of <code>x</code>.</p>
<h3 id="fn-atan-f32-f32">fn atan(f32) -&gt; f32</h3>
<p>Returns the arctangent of <code>x</code>.</p>
<h3 id="fn-atan2-y-f32-x-f32-f32">fn atan2(y: f32, x: f32) -&gt; f32</h3>
<p>Returns the angle between the point <code>(x, y)</code> and the positive x-axis.</p>
<h3 id="fn-sin-angle-f32-f32">fn sin(angle: f32) -&gt; f32</h3>
<p>Returns the sine of <code>angle</code>.</p>
<h3 id="fn-tan-angle-f32-f32">fn tan(angle: f32) -&gt; f32</h3>
<p>Returns the tangent of <code>angle</code>.</p>
<h3 id="fn-cos-angle-f32-f32">fn cos(angle: f32) -&gt; f32</h3>
<p>Returns the cosine of <code>angle</code>.</p>
<h3 id="fn-exp-x-f32-f32">fn exp(x: f32) -&gt; f32</h3>
<p>Returns <code>e^x</code>.</p>
<h3 id="fn-log-x-f32-f32">fn log(x: f32) -&gt; f32</h3>
<p>Returns the natural logarithmus of <code>x</code>. Ie. <code>e^log(x) == x</code>.</p>
<h3 id="fn-pow-x-f32-y-f32-f32">fn pow(x: f32, y: f32) -&gt; f32</h3>
<p>Returns <code>x^y</code>.</p>
<h3 id="fn-fmod-x-f32-y-f32-f32">fn fmod(x: f32, y: f32) -&gt; f32</h3>
<p>Returns <code>x</code> modulo <code>y</code>, ie. <code>x - floor(x / y) * y</code>. This means the sign of the result of <code>fmod</code> is the same as <code>y</code>.</p>
<h2 id="random">Random</h2>
<p>MicroW8 provides a pretty good PRNG, namely xorshift64*. It is initialized to a constant seed at each startup, so if you
want to vary the random sequence you'll need to provide a seed yourself.</p>
<h3 id="fn-random-i32">fn random() -&gt; i32</h3>
<p>Returns a (pseudo-)random 32bit integer.</p>
<h3 id="fn-randomf-f32">fn randomf() -&gt; f32</h3>
<p>Returns a (pseudo-)random float equally distributed in <code>[0,1)</code>.</p>
<h3 id="fn-randomseed-seed-i32">fn randomSeed(seed: i32)</h3>
<p>Seeds the PRNG with the given seed. The seed function is reasonably strong so that you can use</p>
<pre style="background-color:#ffffff;color:#202020;"><code><span>randomSeed(index);
</span><span>random()
</span></code></pre>
<p>as a cheap random-access PRNG (aka noise function).</p>
<h2 id="graphics">Graphics</h2>
<p>The default palette can be seen <a href="../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ">here</a>. (Press Z on the keyboard to switch to palette.)</p>
<p>The palette can be changed by writing 32bit rgba colors to addresses 0x13000-0x13400.</p>
<p>The drawing functions are sub-pixel accurate where applicable (line, circle). Pixel centers lie halfway between integer
coordinates. Ie. the top-left pixel covers the area <code>0,0 - 1,1</code>, with <code>0.5,0.5</code> being the pixel center.</p>
<h3 id="fn-cls-color-i32">fn cls(color: i32)</h3>
<p>Clears the screen to the given color index. Also sets the text cursor to <code>0, 0</code> and disables graphical text mode.</p>
<h3 id="fn-setpixel-x-i32-y-i32-color-i32">fn setPixel(x: i32, y: i32, color: i32)</h3>
<p>Sets the pixel at <code>x, y</code> to the given color index.</p>
<h3 id="fn-getpixel-x-i32-y-i32-i32">fn getPixel(x: i32, y: i32) -&gt; i32</h3>
<p>Returns the color index at <code>x, y</code>. Returns <code>0</code> if the given coordinates are outside the screen.</p>
<h3 id="fn-hline-left-i32-right-i32-y-i32-color-i32">fn hline(left: i32, right: i32, y: i32, color: i32)</h3>
<p>Fills the horizontal line <code>[left, right), y</code> with the given color index.</p>
<h3 id="fn-rectangle-x-f32-y-f32-w-f32-h-f32-color-i32">fn rectangle(x: f32, y: f32, w: f32, h: f32, color: i32)</h3>
<p>Fills the rectangle <code>x,y - x+w,y+h</code> with the given color index.</p>
<p>(Sets all pixels where the pixel center lies inside the rectangle.)</p>
<h3 id="fn-circle-cx-f32-cy-f32-radius-f32-color-i32">fn circle(cx: f32, cy: f32, radius: f32, color: i32)</h3>
<p>Fills the circle at <code>cx, cy</code> and with <code>radius</code> with the given color index.</p>
<p>(Sets all pixels where the pixel center lies inside the circle.)</p>
<h3 id="fn-rectangleoutline-x-f32-y-f32-w-f32-h-f32-color-i32">fn rectangleOutline(x: f32, y: f32, w: f32, h: f32, color: i32)</h3>
<p>Draws a one pixel outline on the inside of the given rectangle.</p>
<p>(Draws the outermost pixels that are still inside the rectangle area.)</p>
<h3 id="fn-circleoutline-cx-f32-cy-f32-radius-f32-color-i32">fn circleOutline(cx: f32, cy: f32, radius: f32, color: i32)</h3>
<p>Draws a one pixel outline on the inside of the given circle.</p>
<p>(Draws the outermost pixels that are still inside the circle area.)</p>
<h3 id="fn-line-x1-f32-y1-f32-x2-f32-y2-f32-color-i32">fn line(x1: f32, y1: f32, x2: f32, y2: f32, color: i32)</h3>
<p>Draws a line from <code>x1,y1</code> to <code>x2,y2</code> in the given color index.</p>
<h3 id="fn-blitsprite-spritedata-i32-size-i32-x-i32-y-i32-control-i32">fn blitSprite(spriteData: i32, size: i32, x: i32, y: i32, control: i32)</h3>
<p>Copies the pixel data at <code>spriteData</code> onto the screen at <code>x</code>, <code>y</code>. The size of the sprite is passed as <code>width | (height &lt;&lt; 16)</code>.
If the height is given as 0, the sprite is is treated as square (width x width).</p>
<p>The control parameter controls masking and flipping of the sprite:</p>
<ul>
<li>bits 0-7: color mask index</li>
<li>bit 8: switch on masked blit (pixel with color mask index are treated as transparent)</li>
<li>bit 9: flip sprite x</li>
<li>bit 10: flip sprite y</li>
</ul>
<h3 id="fn-grabsprite-spritedata-i32-size-i32-x-i32-y-i32-control-i32">fn grabSprite(spriteData: i32, size: i32, x: i32, y: i32, control: i32)</h3>
<p>Copies the pixel data on the screen at <code>x</code>, <code>y</code> to <code>spriteData</code>. Parameters are exactly the same as <code>blitSprite</code>.</p>
<h2 id="input">Input</h2>
<p>MicroW8 provides input from a gamepad with one D-Pad and 4 buttons, or a keyboard emulation thereof.</p>
<p>The buttons are numbered</p>
<table><thead><tr><th>Button</th><th>Keyboard</th><th>Index</th></tr></thead><tbody>
<tr><td>Up</td><td>Arrow-Up</td><td>0</td></tr>
<tr><td>Down</td><td>Arrow-Down</td><td>1</td></tr>
<tr><td>Left</td><td>Arrow-Left</td><td>2</td></tr>
<tr><td>Right</td><td>Arrow-Right</td><td>3</td></tr>
<tr><td>A</td><td>Z</td><td>4</td></tr>
<tr><td>B</td><td>X</td><td>5</td></tr>
<tr><td>X</td><td>A</td><td>6</td></tr>
<tr><td>Y</td><td>S</td><td>7</td></tr>
</tbody></table>
<p>In addition to using the API functions below, the gamepad state can also be read as a bitfield of
pressed buttons at address 0x44. 0x48 holds the buttons that were pressed last frame.</p>
<h3 id="fn-isbuttonpressed-btn-i32-i32">fn isButtonPressed(btn: i32) -&gt; i32</h3>
<p>Returns whether the buttons with the given index is pressed this frame.</p>
<h3 id="fn-isbuttontriggered-btn-i32-i32">fn isButtonTriggered(btn: i32) -&gt; i32</h3>
<p>Returns whether the given button is newly pressed this frame.</p>
<h3 id="fn-time-f32">fn time() -&gt; f32</h3>
<p>Returns the time in seconds since the start of the cart.</p>
<p>The integer time in milliseconds can also be read at address 0x40.</p>
<h2 id="text-output">Text output</h2>
<p>The default font can be seen <a href="../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ">here</a>.</p>
<p>The font can be changed by writing 1bpp 8x8 characters to addresses 0x13400-0x13c00.</p>
<p>All text printing is done at the cursor position, which is advanced after printing each character.
The cursor is not visible.</p>
<p>Text printing can operate in two modes - normal and graphics. After startup and after <code>cls()</code> normal mode is active.</p>
<h3 id="normal-mode">Normal mode</h3>
<p>In normal mode, text printing is constrained to an 8x8 character grid. Setting the cursor position to <code>2,3</code> will start printing at pixel coordinates <code>16,24</code>.</p>
<p>When printing characters, the full 8x8 pixels are painted with the text and background colors according to the character graphics in the font.</p>
<p>When moving/printing past the left or right border the cursor will automatically wrap to the previous/next line. When moving/printing past the upper/lower border, the screen will be scrolled down/up 8 pixels, filling the fresh line with the background color.</p>
<h3 id="graphics-mode">Graphics mode</h3>
<p>In graphics mode, text can be printed to any pixel position, the cursor position is set in pixel coordinates.</p>
<p>When printing characters only the foreground pixels are set, the background is &quot;transparent&quot;.</p>
<p>Moving/printing past any border does not cause any special operation, the cursor just goes off-screen.</p>
<h3 id="text-scale">Text scale</h3>
<p>An integer text scale factor in the range 1x-16x can be set with control char 30. An attempt to
set a scale outside that range will reset the scale to 1x.</p>
<p>After startup and <code>cls</code> the scale is initialized to 1x.</p>
<h3 id="control-chars">Control chars</h3>
<p>Characters 0-31 are control characters and don't print by default. They take the next 0-2 following characters as parameters.
Avoid the reserved control chars, they are currently NOPs but their behavior can change in later MicroW8 versions.</p>
<table><thead><tr><th>Code</th><th>Parameters</th><th>Operation</th></tr></thead><tbody>
<tr><td>0</td><td>-</td><td>Nop</td></tr>
<tr><td>1</td><td>char</td><td>Print char (including control chars)</td></tr>
<tr><td>2-3</td><td>-</td><td>Reserved</td></tr>
<tr><td>4</td><td>-</td><td>Switch to normal mode, reset cursor to 0,0</td></tr>
<tr><td>5</td><td>-</td><td>Switch to graphics mode</td></tr>
<tr><td>6</td><td>-</td><td>Switch output to (debug) console</td></tr>
<tr><td>7</td><td>-</td><td>Bell / trigger sound channel 0</td></tr>
<tr><td>8</td><td>-</td><td>Move cursor left</td></tr>
<tr><td>9</td><td>-</td><td>Move cursor right</td></tr>
<tr><td>10</td><td>-</td><td>Move cursor down</td></tr>
<tr><td>11</td><td>-</td><td>Move cursor up</td></tr>
<tr><td>12</td><td>-</td><td>do <code>cls(background_color)</code></td></tr>
<tr><td>13</td><td>-</td><td>Move cursor to the left border</td></tr>
<tr><td>14</td><td>color</td><td>Set the background color</td></tr>
<tr><td>15</td><td>color</td><td>Set the text color</td></tr>
<tr><td>16-23</td><td>-</td><td>Reserved</td></tr>
<tr><td>24</td><td>-</td><td>Swap text/background colors</td></tr>
<tr><td>25-29</td><td>-</td><td>Reserved</td></tr>
<tr><td>30</td><td>scale</td><td>Set text scale (1-16)</td></tr>
<tr><td>31</td><td>x, y</td><td>Set cursor position (*)</td></tr>
</tbody></table>
<p>(*) In graphics mode, the x coordinate is doubled when using control char 31 to be able to cover the whole screen with one byte.</p>
<h4 id="debug-output">Debug output</h4>
<p>Control code 6 switches all text output (except codes 4 and 5 to switch output back to the screen) to the console. Where exactly this ends
up (if at all) is an implementation detail of the runtimes. The native dev-runtime writes the debug output to <code>stdout</code>, the web runtime to
the debug console using <code>console.log</code>. Both implementations buffer the output until they encounter a newline character (10) in the output stream.</p>
<p>There may be future runtimes that ignore the debug output completely.</p>
<p>In CurlyWas, a simple way to log some value might look like this:</p>
<pre style="background-color:#ffffff;color:#202020;"><code><span>printChar(&#39;\06V: &#39;); // switch to console out, print some prefix
</span><span>printInt(some_value);
</span><span>printChar(&#39;\n\4&#39;); // newline and switch back to screen
</span></code></pre>
<h3 id="fn-printchar-char-i32">fn printChar(char: i32)</h3>
<p>Prints the character in the lower 8 bits of <code>char</code>. If the upper 24 bits are non-zero, right-shifts <code>char</code> by 8 bits and loops back to the beginning.</p>
<h3 id="fn-printstring-ptr-i32">fn printString(ptr: i32)</h3>
<p>Prints the zero-terminated string at the given memory address.</p>
<h3 id="fn-printint-num-i32">fn printInt(num: i32)</h3>
<p>Prints <code>num</code> as a signed decimal number.</p>
<h3 id="fn-settextcolor-color-i32">fn setTextColor(color: i32)</h3>
<p>Sets the text color.</p>
<h3 id="fn-setbackgroundcolor-color-i32">fn setBackgroundColor(color: i32)</h3>
<p>Sets the background color.</p>
<h3 id="fn-setcursorposition-x-i32-y-i32">fn setCursorPosition(x: i32, y: i32)</h3>
<p>Sets the cursor position. In normal mode <code>x</code> and <code>y</code> are multiplied by 8 to get the pixel position, in graphics mode they are used as is.</p>
<h2 id="sound">Sound</h2>
<h3 id="low-level-operation">Low level operation</h3>
<p>MicroW8 actually runs two instances of your module. On the first instance, it calls <code>upd</code> and displays the framebuffer found in its memory. On the
second instance, it calls <code>snd</code> instead with an incrementing sample index and expects that function to return sound samples for the left and right
channel at 44100 Hz. If your module does not export a <code>snd</code> function, it calls the api function <code>sndGes</code> instead.</p>
<p>As the only means of communication, 32 bytes starting at address 0x00050 are copied from main to sound memory after <code>upd</code> returns.</p>
<p>By default, the <code>sndGes</code> function generates sound based on the 32 bytes at 0x00050. This means that in the default configuration those 32 bytes act
as sound registers. See the <code>sndGes</code> function for the meaning of those registers.</p>
<h3 id="export-fn-snd-sampleindex-i32-f32">export fn snd(sampleIndex: i32) -&gt; f32</h3>
<p>If the module exports a <code>snd</code> function, it is called 88200 times per second to provide PCM sample data for playback (44.1kHz stereo).
The <code>sampleIndex</code> will start at 0 and increments by 1 for each call. On even indices the function is expected to return a sample value for
the left channel, on odd indices for the right channel.</p>
<h3 id="fn-playnote-channel-i32-note-i32">fn playNote(channel: i32, note: i32)</h3>
<p>Triggers a note (1-127) on the given channel (0-3). Notes are semitones with 69 being A4 (same as MIDI). A note value of 0 stops the
sound playing on that channel. A note value 128-255 will trigger note-128 and immediately stop it (playing attack+release parts of envelope).</p>
<p>This function assumes the default setup, with the <code>sndGes</code> registers located at 0x00050.</p>
<h3 id="fn-sndges-sampleindex-i32-f32">fn sndGes(sampleIndex: i32) -&gt; f32</h3>
<p>This implements a sound chip, generating sound based on 32 bytes of sound registers.</p>
<p>The spec of this sound chip are:</p>
<ul>
<li>4 channels with individual volume control (0-15)</li>
<li>rect, saw, tri, noise wave forms selectable per channel</li>
<li>each wave form supports some kind of pulse width modulation</li>
<li>each channel has an optional automatic low pass filter, or can be sent to one of two manually controllable filters</li>
<li>each channel can select between a narrow and a wide stereo positioning. The two stereo positions of each channel are fixed.</li>
<li>optional ring modulation</li>
</ul>
<p>This function requires 1024 bytes of working memory, the first 32 bytes of which are interpreted as the sound registers.
The base address of its working memory can be configured by writing the address to 0x12c78. It defaults to 0x00050.</p>
<p>Here is a short description of the 32 sound registers.</p>
<pre style="background-color:#ffffff;color:#202020;"><code><span>00 - CTRL0
</span><span>06 - CTRL1
</span><span>0c - CTRL2
</span><span>12 - CTRL3
</span><span> | 7 6 | 5 | 4 | 3 2 | 1 | 0 |
</span><span> | wave | ring | wide | filter | trigger | note on |
</span><span>
</span><span> note on: stay in decay/sustain part of envelope
</span><span> trigger: the attack part of the envlope is triggered when either this changes
</span><span> or note on is changed from 0 to 1.
</span><span> filter : 0 - no filter
</span><span> 1 - fixed 6db 1-pole filter with cutoff two octaves above note
</span><span> 2 - programmable filter 0
</span><span> 3 - programmable filter 1
</span><span> wide : use wide stereo panning
</span><span> ring : ring modulate with triangle wave at frequency of previous channel
</span><span> wave : 0 - rectangle
</span><span> 1 - saw
</span><span> 2 - triangle
</span><span> 3 - noise
</span><span>
</span><span>01 - PULS0
</span><span>07 - PULS1
</span><span>0d - PULS2
</span><span>13 - PULS3
</span><span> Pulse width 0-255, with 0 being the plain version of each wave form.
</span><span> rectangle - 50%-100% pulse width
</span><span> saw - inverts 0%-100% of the saw wave form around the center
</span><span> triangle - morphs into an octave up triangle wave
</span><span> noise - blends into a decimated saw wave (just try it out)
</span><span>
</span><span>02 - FINE0
</span><span>08 - FINE1
</span><span>0e - FINE2
</span><span>14 - FINE3
</span><span> Fractional note
</span><span>
</span><span>03 - NOTE0
</span><span>09 - NOTE1
</span><span>0f - NOTE2
</span><span>15 - NOTE3
</span><span> Note, 69 = A4
</span><span>
</span><span>04 - ENVA0
</span><span>0a - ENVA1
</span><span>10 - ENVA2
</span><span>16 - ENVA3
</span><span> | 7 6 5 4 | 3 2 1 0 |
</span><span> | decay | attack |
</span><span>
</span><span>05 - ENVB0
</span><span>0b - ENVB1
</span><span>11 - ENVB2
</span><span>17 - ENVB3
</span><span> | 7 6 5 4 | 3 2 1 0 |
</span><span> | release | sustain |
</span><span>
</span><span>18 - VO01
</span><span> | 7 6 5 4 | 3 2 1 0 |
</span><span> | volume 1 | volume 0 |
</span><span>
</span><span>19 - VO23
</span><span> | 7 6 5 4 | 3 2 1 0 |
</span><span> | volume 3 | volume 2 |
</span><span>
</span><span>1a - FCTR0
</span><span>1b - FCTR1
</span><span> | 7 6 5 4 | 3 | 2 | 1 | 0 |
</span><span> | resonance | 0 | band | high | low |
</span><span>
</span><span>1c - FFIN0
</span><span>1e - FFIN1
</span><span> cutoff frequency - fractional note
</span><span>
</span><span>1d - FNOT0
</span><span>1f - FNOT1
</span><span> cutoff frequency - note
</span></code></pre>
<h1 id="the-uw8-tool">The <code>uw8</code> tool</h1>
<p>The <code>uw8</code> tool included in the MicroW8 download includes a number of useful tools for developing MicroW8 carts. For small productions written in
wat or CurlyWas you don't need anything apart from <code>uw8</code> and a text editor of your choice.</p>
<h2 id="uw8-run"><code>uw8 run</code></h2>
<p>Usage:</p>
<p><code>uw8 run [&lt;options&gt;] &lt;file&gt;</code></p>
<p>Runs <code>&lt;file&gt;</code> which can be a binary WebAssembly module, an <code>.uw8</code> cart, a wat (WebAssembly text format) source file or a <a href="https://github.com/exoticorn/curlywas">CurlyWas</a> source file.</p>
<p>Options:</p>
<ul>
<li><code>-b</code>, <code>--browser</code>: Run in browser instead of using native runtime</li>
<li><code>-t FRAMES</code>, <code>--timeout FRAMES</code>: Sets the timeout in frames (1/60s). If the start or update function runs longer than this it is forcibly interupted
and execution of the cart is stopped. Defaults to 30 (0.5s)</li>
<li><code>-w</code>, <code>--watch</code>: Reloads the given file every time it changes on disk.</li>
<li><code>-p</code>, <code>--pack</code>: Pack the file into an <code>.uw8</code> cart before running it and print the resulting size.</li>
<li><code>-u</code>, <code>--uncompressed</code>: Use the uncompressed <code>uw8</code> format for packing.</li>
<li><code>-l LEVEL</code>, <code>--level LEVEL</code>: Compression level (0-9). Higher compression levels are really slow.</li>
<li><code>-o FILE</code>, <code>--output FILE</code>: Write the loaded and optionally packed cart back to disk.</li>
</ul>
<p>when using the native runtime:</p>
<ul>
<li><code>-m</code>, <code>--no-audio</code>: Disable audio, also reduces cpu load a bit</li>
<li><code>--no-gpu</code>: Force old cpu-only window code</li>
<li><code>--filter FILTER</code>: Select an upscale filter at startup</li>
<li><code>--fullscreen</code>: Start in fullscreen mode</li>
<li><code>--scale-fill</code>: Scale to fill whole screen, potentially cropping parts of the frame buffer.</li>
</ul>
<p>Note that the cpu-only window does not support fullscreen nor upscale filters.</p>
<p>Unless --no-gpu is given, uw8 will first try to open a gpu accelerated window, falling back to the old cpu-only window if that fails.
Therefore you should rarely need to manually pass --no-gpu. If you prefer the old pixel doubling look to the now default crt filter,
you can just pass <code>--filter nearest</code> or <code>--filter 1</code>.</p>
<p>The upscale filter options are:</p>
<pre style="background-color:#ffffff;color:#202020;"><code><span>1, nearest : Anti-aliased nearest filter
</span><span>2, fast_crt : Very simple, cheap crt filter, not very good below a window size of 960x720
</span><span>3, ss_crt : Super sampled crt filter, a little more demanding on the GPU but scales well to smaller window sizes
</span><span>4, chromatic_crt : Variant of fast_crt with a slight offset of the three color dots of a pixel, still pretty cheap
</span><span>5, auto_crt (default) : ss_crt below 960x720, chromatic_crt otherwise
</span></code></pre>
<p>You can switch the upscale filter at any time using the keys 1-5. You can toggle fullscreen with F. You can toggle between scale modes 'fit' and 'fill' with M.</p>
<h2 id="uw8-pack"><code>uw8 pack</code></h2>
<p>Usage:</p>
<p><code>uw8 pack [&lt;options&gt;] &lt;infile&gt; &lt;outfile&gt;</code></p>
<p>Packs the WebAssembly module or text file, or <a href="https://github.com/exoticorn/curlywas">CurlyWas</a> source file into a <code>.uw8</code> cart.</p>
<p>Options:</p>
<ul>
<li><code>-u</code>, <code>--uncompressed</code>: Use the uncompressed <code>uw8</code> format for packing.</li>
<li><code>-l LEVEL</code>, <code>--level LEVEL</code>: Compression level (0-9). Higher compression levels are really slow.</li>
</ul>
<h2 id="uw8-unpack"><code>uw8 unpack</code></h2>
<p>Usage:</p>
<p><code>uw8 unpack &lt;infile&gt; &lt;outfile&gt;</code></p>
<p>Unpacks a MicroW8 module into a standard WebAssembly module.</p>
<h2 id="uw8-compile"><code>uw8 compile</code></h2>
<p>Usage:</p>
<p><code>uw8 compile [&lt;options&gt;] &lt;infile&gt; &lt;outfile&gt;</code></p>
<p>Compiles a <a href="https://github.com/exoticorn/curlywas">CurlyWas</a> source file to a standard WebAssembly module. Most useful together with
the <code>--debug</code> option to get a module that works well in the Chrome debugger.</p>
<p>Options:</p>
<ul>
<li><code>-d</code>, <code>--debug</code>: Generate a name section to help debugging</li>
</ul>
<h2 id="uw8-filter-exports"><code>uw8 filter-exports</code></h2>
<p>Usage:</p>
<p><code>uw8 filter-exports &lt;infile&gt; &lt;outfile&gt;</code></p>
<p>Reads a binary WebAssembly module, removes all exports not used by the MicroW8 platform + everything that is unreachable without those exports and writes the resulting module to <code>outfile</code>.</p>
<p>When compiling C code (or Rust, zig or others) to WebAssembly, you end up with a few exported global variables that are used for managing the heap and C stack, even if the code doesn't actually use those features. You can use this command to automatically remove them and gain a few bytes. See the C, Rust and zig examples in the MicroW8 repository.</p>
<h1 id="other-useful-tools">Other useful tools</h1>
<p>The <a href="https://github.com/WebAssembly/wabt">Web Assembly Binary Toolkit</a> includes
a few useful tools, eg. <code>wat2wasm</code> to compile the WebAssemby text format to binary
wasm and <code>wasm2wat</code> to disassemble wasm binaries.</p>
<p><a href="https://github.com/WebAssembly/binaryen">Binaryen</a> includes <code>wasm-opt</code> which enable additional optimizations over what LLVM (the backend that is used by most compilers that target WebAssembly) can do.</p>
<h1 id="distribution">Distribution</h1>
<p>The classical distribution option is just to put the <code>.uw8</code> cart into a zip file, let people run it themselves, either in the <code>uw8</code> tool or in the web runtime.</p>
<p>If you want to go this way, you might consider including <code>microw8.html</code> in your download. It's specifically designed to be a small (~10KB at the moment), self-contained HTML file for just this reason. That way, anyone who has downloaded you production can run it, even when offline, provided they have a modern web browser at hand. Also, should future versions of MicroW8 ever introduce any kind of incompatibilities, they'd still have a compatible version right there without hunting arround for an old version.</p>
<h2 id="base64-encoded-link">Base64 encoded link</h2>
<p>For small productions (&lt;= 1024 bytes), when you load them in the web runtime, the URL is automatically updated to include the cart as base64 encoded data. You can just give that URL to others for them to run your prod.</p>
<h2 id="url-parameter">url parameter</h2>
<p>Another option is to put the cart on a webserver and add <code>#url=url/to/the/cart.uw8</code> to the end of the web runtime URL. (<a href="../v0.1pre5#url=../uw8/skipahead.uw8">Like this</a>)</p>
<p>If the cart and the web runtime are on different domains, you'll have to make sure that <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#the_http_response_headers">CORS header</a> are enabled for the cart, otherwise the web runtime won't be able to load it.</p>
<p>Feel free to put the web runtime on your own server if it makes sense to you, its <a href="https://unlicense.org/">license</a> allows you to do anything you want with it.</p>
<h2 id="html-uw8"><code>.html</code> + <code>.uw8</code></h2>
<p>At startup the web runtime will try to load a cart in the same directory as the <code>.html</code> file. If the URL of the web runtime ends in <code>.html</code> it will try to load a cart with the same name and the extension <code>.uw8</code>. If the URL of the web runtime ends in a <code>/</code> it will try to load a <code>cart.uw8</code> at that location.</p>
<p>So, you could for example serve the web runtime as <code>https://example.org/mytunnel.html</code> and the cart as <code>https://example.org/mytunnel.uw8</code> and send people to the HTML page to run the cart. Or you could put them up as <code>https://example.org/mytunnel/index.html</code> and <code>https://example.org/mytunnel/cart.uw8</code> and send people to <code>https://example.org/mytunnel</code>.</p>
<p>If a cart is found and loaded in this way, the load button is hidden.</p>
<h2 id="itch-io">Itch.io</h2>
<p>The above <code>.html</code> + <code>.uw8</code> option works great on <a href="https://itch.io">Itch.io</a> as well. Put these two files into a zip archive:</p>
<ul>
<li><code>index.html</code>: a copy of the web runtime (<code>microw8.html</code> in the MicroW8 download)</li>
<li><code>index.uw8</code>: Your game cart</li>
</ul>
<p>Upload the zip file to itch.io and make sure to set the embedded viewport size to exactly (!) 640x480 pixel. At that exact size the web runtime hides everything except for the MicroW8 screen.</p>
<p>If instead you actually <em>want</em> to display the border around the screen and the byte size you can try a size of about 720x620.</p>
<p><a href="https://exoticorn.itch.io/skipahead">See here for an example upload.</a></p>
<h1 id="uw8-format"><code>.uw8</code> format</h1>
<p>The first byte of the file specifies the format version:</p>
<h2 id="format-version-00">Format version <code>00</code>:</h2>
<p>This file is simply a standard WebAssembly module</p>
<h2 id="format-version-01">Format version <code>01</code>:</h2>
<p>The rest of this file is the same as a WebAssembly
module with the 8 byte header removed. This module
can leave out sections which are then taken from
a base module provided by MicroW8.</p>
<p>You can generate this base module yourself using
<code>uw8-tool</code>. As a quick summary, it provides all function
types with up to 7 parameters (i32 or f32) where the
<code>f32</code> parameters always preceed the <code>i32</code> parameters.
Then it includes all imports that MicroW8 provides,
a function section with a single function of type
<code>() -&gt; void</code> and an export section that exports
the first function in the file under the name <code>upd</code>.</p>
<h2 id="format-version-02">Format version <code>02</code>:</h2>
<p>Same as version <code>01</code> except everything after the first byte is compressed
using a <a href="https://github.com/exoticorn/upkr">custom LZ compression scheme</a>.</p>
<h1 id="the-web-runtime">The web runtime</h1>
<p>Load carts into the web runtime either by using the &quot;Load cart...&quot; button, or by dragging the file
onto the screen area.</p>
<h2 id="input-1">Input</h2>
<p>For input, you can either use a standard gamepad or keyboard. On a keyboard use the arrow keys and the keys Z, X, A and S to emulate the A, B, X and Y buttons.</p>
<h2 id="video-recording">Video recording</h2>
<p>Press F10 to start recording, press again to stop. Then a download dialog will open for the video file.
The file might miss some metadata needed to load in some video editing tools, in that case you can run
it through ffmpeg like this `ffmpeg -i IN_NAME.webm -c copy -o OUT_NAME.webm to fix it up.</p>
<p>To convert it to 1280x720, for example for a lovebyte upload, you can use:</p>
<pre style="background-color:#ffffff;color:#202020;"><code><span>ffmpeg -i IN.webm -vf &quot;scale=960:720:flags=neighbor,pad=1280:720:160:0&quot; -r 60 OUT.mp4
</span></code></pre>
<h2 id="screenshot">Screenshot</h2>
<p>Pressing F9 opens a download dialog with a screenshot.</p>
<h2 id="devkit-mode">Devkit mode</h2>
<p>Append <code>#devkit</code> to the web runtime url in order to switch to devkit mode. In devkit mode, standard web assembly modules
are loaded bypassing the loader, removing all size restrictions. At the same time, the memory limit is increased to 1GB.</p>
</div>
</main>
</body>
<script>
function highlightNav(heading) {
let pathname = location.pathname;
document.querySelectorAll(".toc a").forEach((item) => {
item.classList.remove("active");
});
document.querySelector(".toc a[href$='" + pathname + "#" + heading + "']").classList.add("active");
}
let currentHeading = "";
window.onscroll = function () {
let h = document.querySelectorAll("h1,h2,h3,h4,h5,h6");
let elementArr = [];
h.forEach(item => {
if (item.id !== "") {
elementArr[item.id] = item.getBoundingClientRect().top;
}
});
elementArr.sort();
for (let key in elementArr) {
if (!elementArr.hasOwnProperty(key)) {
continue;
}
if (elementArr[key] > 0 && elementArr[key] < 300) {
if (currentHeading !== key) {
highlightNav(key);
currentHeading = key;
}
break;
}
}
}
</script>
</html>
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
clang -O2 -Wno-incompatible-library-redeclaration --no-standard-libraries -ffast-math -Xclang -target-feature -Xclang +nontrapping-fptoint -Wl,--no-entry,--export-all,--import-memory,--initial-memory=262144,--global-base=81920,-zstack-size=4096 -o cart.wasm cart.c --target=wasm32 && \
uw8 filter-exports cart.wasm cart.wasm && \
wasm-opt -Oz --fast-math --strip-producers -o cart.wasm cart.wasm && \
uw8 pack -l 9 cart.wasm cart.uw8
+27
View File
@@ -0,0 +1,27 @@
#define IMPORT(MODULE, NAME) __attribute__((import_module(MODULE), import_name(NAME)))
IMPORT("env", "atan2") extern float atan2(float, float);
IMPORT("env", "time") extern float time();
float sqrt(float v) {
return __builtin_sqrt(v);
}
#define FRAMEBUFFER ((unsigned char*)120)
void upd() {
int i = 0;
for( ;; ) {
float t = time() * 63.0f;
float x = (float)(i % 320 - 160);
float y = (float)(i / 320 - 120);
float d = 40000.0f / sqrt(x * x + y * y);
float u = atan2(x, y) * 512.0f / 3.141f;
unsigned char c = (unsigned char)((int)(d + t * 2.0f) ^ (int)(u + t)) >> 4;
FRAMEBUFFER[i] = c;
i += 1;
if(i >= 320*240) break;
}
}
+22
View File
@@ -0,0 +1,22 @@
include "../include/microw8-api.cwa"
export fn upd() {
printString(0x20000);
}
data 0x20000 {
i8(14, 0xfd, 15, 15, 12) // clear screen to color 0xfd
"Top left"
i8(14, 10, 11, 11, 11, 11) // scroll up 4 lines
i8(31, 28, 29, 14, 0xfd) "Bottom right"
i8(14, 10, 10, 10) // scroll down 2 lines
i8(31, 40, 3, 14, 10, 15, 0xf0) "Other colors"
i8(24, 0xb0) "inverted"
i8(13, 10, 8, 8) "->"
i8(10, 10, 9, 9, 1) "|<-"
i8(5, 31, 7, 28+17, 15, 0xe3) "Graphics text!"
i8(5, 31, 6, 28+16, 15, 0xe5) "Graphics text!"
i8(4, 24, 14, 10, 0x90, 0x80, 0xf1)
i8(31, 37, 29, 0xf1, 0x80, 0x90)
i8(0)
}
+55
View File
@@ -0,0 +1,55 @@
// port of cracklebass by pestis (originally on TIC-80)
include "../include/microw8-api.cwa"
const MUSIC_DATA = 0x20000;
export fn upd() {
let inline t = 32!32 * 6 / 100;
let inline p = t / 1024;
let channel:i32;
loop channels {
let inline e = t * channel?MUSIC_DATA / 8;
let lazy pattern = (8 * channel + p)?(MUSIC_DATA + 56);
let lazy n = !!pattern * (8 * pattern + e / 16 % 8)?MUSIC_DATA;
let inline prev_ctrl = (channel * 6)?80;
(channel * 6)?80 = if n {
let inline base_note = 12 + 12 * channel?(MUSIC_DATA + 4) + n;
let inline pitch_drop = e % 16 * channel?(MUSIC_DATA + 94);
let inline key_pattern = p?(MUSIC_DATA + 8*4 + 56);
let inline key = select(key_pattern, (8 * key_pattern + t / 128 % 8)?MUSIC_DATA, 1);
(channel * 6)?83 = base_note - pitch_drop / 4 + key;
prev_ctrl & 0xfc | (e / 8 & 2) | 1
} else {
prev_ctrl & 0xfe
};
branch_if (channel := channel + 1) < 4: channels;
}
}
data 80 {
i8(
0x44, 0, 0, 0, 0x50, 0x40,
0x4, 0x50, 0, 0, 0x80, 0x80,
0x40, 0x80, 0, 0, 0x40, 0x40,
0, 0, 0, 0, 0x50, 0x50
)
}
data MUSIC_DATA {
i8(
16, 2, 8, 8, 1, 2, 2, 3, 1, 0,
1,13,16, 0, 1, 8, 1, 0, 1,13,
16, 1, 1, 8, 1, 0, 8,13,13, 0,
16,13, 1, 0, 1, 0, 1, 0, 1, 1,
1, 0, 0, 0, 1, 0,13, 1, 1, 1,
6, 8, 1, 1, 6, 8, 1, 1, 2, 1,
2, 1, 2, 0, 0, 0, 0, 3, 3, 3,
5, 0, 0, 2, 1, 2, 1, 2, 1, 2,
0, 4, 4, 0, 4, 4, 4, 4, 0, 0,
0, 0, 6, 6, 0, 0, 0, 8
)
}
+25
View File
@@ -0,0 +1,25 @@
include "../include/microw8-api.cwa"
export fn upd() {
cls(0);
let i: i32;
loop pixels {
let inline rocket = i #>> 9;
let lazy local_time = fmod(time() + rocket as f32 / 5 as f32, 2 as f32);
let lazy rocket = rocket + nearest(time() - local_time) as i32 * 10;
randomSeed(rocket);
let inline x = randomf() * 645 as f32;
let y = randomf() * 133 as f32;
let lazy angle = { randomSeed(i); randomf() } * 44 as f32;
let inline dx = sin(angle);
let inline dy = cos(angle);
let lazy dist = local_time * (randomf() * 44 as f32);
circle(
x + dx * dist,
y + dy * dist + local_time * local_time * 24 as f32,
1 as f32, (rocket % 11 + 1) * 16 - (local_time * 7 as f32) as i32 - (i % 4)
);
branch_if (i := i + 1) < 5120: pixels;
}
}
+44
View File
@@ -0,0 +1,44 @@
include "../include/microw8-api.cwa"
global mut mode: i32 = 0;
export fn upd() {
cls(0);
if isButtonTriggered(BUTTON_A) {
mode = !mode;
}
setTextColor(15);
printString(mode * USER_MEM);
let y: i32;
loop y {
line(0 as f32, (y * 9 + 39) as f32, (14+16*9) as f32, (y * 9 + 39) as f32, 1);
line((y * 9 + 15) as f32, 24 as f32, (y * 9 + 15) as f32, (38+16*9) as f32, 1);
setTextColor(15);
setCursorPosition(y * 9 + 16, 24);
let lazy hexChar = select(y < 10, y + 48, y + 87);
printChar(hexChar);
setCursorPosition(0, y * 9 + 24+16);
printChar(hexChar);
let x = 0;
loop x {
setCursorPosition(x * 9 + 16, y * 9 + 24+16);
setTextColor(select(mode, x + y * 16, -9));
if y >= 2 | mode {
printChar(select(mode, 0xa4, x + y * 16));
}
branch_if (x := x + 1) < 16: x;
}
branch_if (y := y + 1) < 16: y;
}
}
data 0 {
"Default font: (press " i8(0xcc) " for palette)" i8(5, 0)
}
data USER_MEM {
"Default palette: (press " i8(0xcc) " for font)" i8(5, 0)
}
+15
View File
@@ -0,0 +1,15 @@
include "../include/microw8-api.cwa"
export fn upd() {
cls(0);
let i: i32;
loop lines {
let angle = i as f32 * (3.1415 / 25.0) + time() * 0.125;
line(
160 as f32, 120 as f32,
160 as f32 + sin(angle) * 100 as f32,
120 as f32 + cos(angle) * 100 as f32,
47);
branch_if (i := i + 1) < 50: lines;
}
}
+38
View File
@@ -0,0 +1,38 @@
include "../include/microw8-api.cwa"
global mut frame = 0;
export fn upd() {
if frame % 16 == 0 {
let ch: i32;
loop channels {
playNote(ch, (ch * 32 + (frame / 16) % 32)?0x20000);
branch_if ch := (ch + 1) % 4: channels;
}
}
frame = frame + 1;
}
data 0x20000 {
i8(
0x4e, 0x0, 0x0, 0x4c, 0x49, 0x0, 0x45, 0x47,
0x49, 0x47, 0x45, 0x44, 0x42, 0x0, 0x3d, 0x41,
0x44, 0x0, 0x0, 0x47, 0x49, 0x47, 0x45, 0x41,
0x44, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0,
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
0x2a, 0x0, 0x0, 0x0, 0x2d, 0x0, 0x0, 0x0,
0x2c, 0x0, 0x28, 0x0, 0x2a, 0x0, 0x0, 0x0,
0x25, 0x0, 0x0, 0x0, 0x29, 0x0, 0x0, 0x0,
0x2c, 0x0, 0x2d, 0x0, 0x2a, 0x0, 0x25, 0x0,
0x0, 0x0, 0x31, 0x0, 0x34, 0x0, 0x0, 0x36,
0x38, 0x39, 0x38, 0x34, 0x36, 0x0, 0x0, 0x0,
0x0, 0x3d, 0x3b, 0x39, 0x38, 0x0, 0x0, 0x0,
0x0, 0x39, 0x38, 0x39, 0x38, 0x0, 0x36, 0x0
)
}
+54
View File
@@ -0,0 +1,54 @@
include "../include/microw8-api.cwa"
global mut pz: i32 = 4;
global mut px: f32 = 2.0;
global mut py: f32 = 2.0;
global mut s: f32 = 2.0;
global mut f: f32 = 2.0;
export fn upd() {
let y: i32;
let inline zero = 0_f;
let lazy control_speed = 0.03125;
s = s + 0.1875 - (f + control_speed) * isButtonPressed(4 <| cls(4)) as f32;
f = f * 0.5625;
printInt(pz);
loop lines {
let lazy z = (4000 / (y := y + 1) + pz) / 20;
let lazy x = px - ({randomSeed(z); random()} >> 30) as f32;
let lazy w = 9 as f32 / sqrt(z as f32);
let lazy rx = 160 as f32 - (y as f32 * x);
let inline rw = y as f32 * w;
let inline c = (z & 1) * -2;
let inline yf = y as f32;
rectangle(rx, yf, rw, yf / 6 as f32, c + 1);
rectangle(rx, yf, rw, 1 as f32, c - 4);
if y == 180 & py > zero {
if x > w | x < zero {
0?80 = 0xc3;
3?80 = 32;
return;
}
py = zero;
s = zero;
f = 2 as f32;
}
branch_if y < 240: lines;
}
circle(160 as f32, 160 as f32 + py, 22 as f32, -28);
circle((160 - 6) as f32, (160 - 6) as f32 + py, 6 as f32, -26);
0?86 = py < zero;
3?86 = 32 - py as i32;
px = px + (isButtonPressed(3) - isButtonPressed(2)) as f32 * control_speed;
py = py + s;
pz = pz + 1;
}
+38
View File
@@ -0,0 +1,38 @@
// Steady On Tim, It's Only A Budget Game
// by Gasman / Hooy-Program
// ported to MicroW8 by exoticorn/icebird
include "../include/microw8-api.cwa"
fn melody(t: i32, T: i32) -> i32 {
let inline riff_pos = abs(((T&31) - 16) as f32) as i32;
let lazy shift = ((1-((T>>5)&3))%2-1) as f32 / 6 as f32;
let inline note_count = 5 - (T >= 512);
let inline octave = (riff_pos/5) as f32;
let inline riff_note = 5514 >> (riff_pos % note_count * 4) & 15;
let inline melody_freq = pow(2 as f32, shift + octave - (riff_note as f32 / 12 as f32));
let inline melody = (t as f32 * melody_freq) as i32 & 128;
let inline arp_note = ((0x85>>((t>>12)%3*4)) & 15) - 1;
let inline arp_freq = pow(2 as f32, shift + (arp_note as f32 / 12 as f32));
let inline arp_vol = (T >= 256) * (12-T%12);
let inline arpeggio = ((t as f32 * arp_freq) as i32 & 128) * arp_vol / 12;
melody + arpeggio
}
export fn snd(t: i32) -> f32 {
let lazy T = t/10000;
let inline mel_arp = melody(t, T)/3 + melody(t, T-3)/5;
let inline bass_vol = (T >= 128) & (197 >> (T % 8));
let inline bass_freq = pow(2 as f32, (((T & 4) * ((T & 7) - 1)) as f32 / 24 as f32 - 5 as f32));
let inline bass = ((t as f32 * bass_freq) as i32 & 63) * bass_vol;
let inline snare_ish = (random() & 31) * (8 - (T + 4) % 8) / 8;
let inline sample = mel_arp + bass + snare_ish;
sample as f32 / 255 as f32
}
+26
View File
@@ -0,0 +1,26 @@
include "../include/microw8-api.cwa"
export fn upd() {
let x: i32;
let y: i32;
loop screen {
let inline t = time() / 2 as f32;
let lazy o = sin(t) * 0.75;
let inline q = x as f32 - 160.5;
let inline w = (y - 120) as f32;
let lazy r = sqrt(q*q + w*w);
let lazy z = q / r;
let lazy s = z * o + sqrt(z * z * o * o + 1 as f32 - o * o);
let inline q2 = (z * s - o) * 10 as f32 + t;
let inline w2 = w / r * s * 10 as f32 + t;
let inline s2 = s * 100 as f32 / r;
let inline color = max(
0 as f32,
((q2 as i32 ^ w2 as i32 & ((s2 + time()) * 10 as f32) as i32) & 5) as f32 *
(4 as f32 - s2) as f32
) as i32 - 32;
setPixel(x, y, color);
branch_if x := (x + 1) % 320: screen;
branch_if y := (y + 1) % 320: screen;
}
}
+64
View File
@@ -0,0 +1,64 @@
// Steady On Tim, It's Only A Budget Game
// original bytebeat by Gasman / Hooy-Program
// ported to MicroW8/GES by exoticorn/icebird
import "env.memory" memory(4);
fn melody(ch: i32, t: i32, T: i32) {
let lazy riff_pos = abs(((T&31) - 16) as f32) as i32;
let lazy shift = ((1-((T>>5)&3))%2-1) * 2;
let inline note_count = 5 - (T >= 512);
let inline octave = (riff_pos/5) * 12;
let inline riff_note = 5514 >> (riff_pos % note_count * 4) & 15;
let inline melody_note = shift + octave - riff_note;
ch?1 = 230 - riff_pos * 14;
ch?3 = melody_note + 64;
let inline arp_note = shift + ((0x85>>((t/2)%3*4)) & 15) - 1;
80?3 = arp_note + 64;
}
export fn upd() {
let lazy t = 32!32 / (1000/60);
let lazy T = t / 7;
melody(98, t, T - 3);
melody(92, t, T);
80?0 = ((T >= 256) & (T/12+(T-3)/12)) * 2 | 0x48; // arp trigger
if T >= 128 {
let inline bass_step = T % 8;
86?3 = if bass_step / 2 == 2 {
86?0 = 0xd6;
81
} else {
86?0 = ((197 >> bass_step) & 1) | 0x48;
((T & 4) * ((T & 7) - 1)) / 2 + 28
};
}
}
data 80 {
i8(
0, 0x90, 0, 0, 0, 0x90,
0, 0x4c, 0, 0, 0, 0x4c,
0x19, 0, 0, 0, 0, 0x4c,
0x19, 0, 0, 0, 0, 0x4c,
0xfa, 0x84,
0xc1, 0xc1, 0, 107, 0, 0x4c
)
}
/*
include "../../platform/src/ges.cwa"
import "env.pow" fn pow(f32, f32) -> f32;
import "env.exp" fn exp(f32) -> f32;
import "env.sin" fn sin(f32) -> f32;
export fn snd(t: i32) -> f32 {
gesSnd(t)
}
*/
+24
View File
@@ -0,0 +1,24 @@
include "../include/microw8-api.cwa"
export fn upd() {
let i: i32;
loop pixels {
let lazy x = (i % 320 - 160) as f32;
let lazy y = (i / 320) as f32 - 120.5;
let lazy z = time() + 20 as f32 / sqrt(x*x + y*y);
let inline z_int = z as i32;
let lazy q = select(z_int % 9 >= 6, z, (z_int - (z_int % 9 - 6)) as f32);
let lazy w = 9 as f32 / y + time();
let inline s = q - time();
let inline m = x * s / 50 as f32;
i?120 = select(y > 0 as f32 & w < q,
select(abs(x * (w - time())) < 9 as f32, -2, -18) - w as i32 % 2,
select(y * s > -99 as f32 / (m * m + 1 as f32),
select(z_int % 9 >= 6, z_int % 2 - 31, -27),
(-10 as f32 + y / 23 as f32 + fmod(y / 4 as f32, 1 as f32)) as i32
)
);
branch_if (i := i + 1) < 320*240: pixels;
}
}
+16
View File
@@ -0,0 +1,16 @@
include "../include/microw8-api.cwa"
export fn upd() {
let i: i32;
loop pixels {
let inline t = time() * 63 as f32;
let lazy x = (i % 320 - 160) as f32;
let lazy y = (i / 320 - 120) as f32;
let inline d = 40000 as f32 / sqrt(x * x + y * y);
let inline u = atan2(x, y) * (512.0 / 3.141);
let inline c = ((i32.trunc_sat_f32_s(d + t * 2 as f32) ^ i32.trunc_sat_f32_s(u + t)) & 255) >> 4;
i?FRAMEBUFFER = c;
branch_if (i := i + 1) < 320*240: pixels;
}
}
+52
View File
@@ -0,0 +1,52 @@
// MicroW8 APIs, to be `include`d in CurlyWas sources
import "env.memory" memory(4);
import "env.sin" fn sin(f32) -> f32;
import "env.cos" fn cos(f32) -> f32;
import "env.tan" fn tan(f32) -> f32;
import "env.asin" fn asin(f32) -> f32;
import "env.acos" fn acos(f32) -> f32;
import "env.atan" fn atan(f32) -> f32;
import "env.atan2" fn atan2(f32, f32) -> f32;
import "env.pow" fn pow(f32, f32) -> f32;
import "env.log" fn log(f32) -> f32;
import "env.fmod" fn fmod(f32, f32) -> f32;
import "env.random" fn random() -> i32;
import "env.randomf" fn randomf() -> f32;
import "env.randomSeed" fn randomSeed(i32);
import "env.cls" fn cls(i32);
import "env.setPixel" fn setPixel(i32, i32, i32);
import "env.getPixel" fn getPixel(i32, i32) -> i32;
import "env.hline" fn hline(i32, i32, i32, i32);
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
import "env.circle" fn circle(f32, f32, f32, i32);
import "env.line" fn line(f32, f32, f32, f32, i32);
import "env.time" fn time() -> f32;
import "env.isButtonPressed" fn isButtonPressed(i32) -> i32;
import "env.isButtonTriggered" fn isButtonTriggered(i32) -> i32;
import "env.printChar" fn printChar(i32);
import "env.printString" fn printString(i32);
import "env.printInt" fn printInt(i32);
import "env.setTextColor" fn setTextColor(i32);
import "env.setBackgroundColor" fn setBackgroundColor(i32);
import "env.setCursorPosition" fn setCursorPosition(i32, i32);
import "env.rectangle_outline" fn rectangle_outline(f32, f32, f32, f32, i32);
import "env.circle_outline" fn circle_outline(f32, f32, f32, i32);
import "env.exp" fn exp(f32) -> f32;
import "env.playNote" fn playNote(i32, i32);
import "env.sndGes" fn sndGes(i32) -> f32;
const TIME_MS = 0x40;
const GAMEPAD = 0x44;
const FRAMEBUFFER = 0x78;
const PALETTE = 0x13000;
const FONT = 0x13400;
const USER_MEM = 0x14000;
const BUTTON_UP = 0x0;
const BUTTON_DOWN = 0x1;
const BUTTON_LEFT = 0x2;
const BUTTON_RIGHT = 0x3;
const BUTTON_A = 0x4;
const BUTTON_B = 0x5;
const BUTTON_X = 0x6;
const BUTTON_Y = 0x7;
+54
View File
@@ -0,0 +1,54 @@
;; MicroW8 APIs, in WAT (Wasm Text) format
(import "env" "memory" (memory 4))
(import "env" "sin" (func $sin (param f32) (result f32)))
(import "env" "cos" (func $cos (param f32) (result f32)))
(import "env" "tan" (func $tan (param f32) (result f32)))
(import "env" "asin" (func $asin (param f32) (result f32)))
(import "env" "acos" (func $acos (param f32) (result f32)))
(import "env" "atan" (func $atan (param f32) (result f32)))
(import "env" "atan2" (func $atan2 (param f32) (param f32) (result f32)))
(import "env" "pow" (func $pow (param f32) (param f32) (result f32)))
(import "env" "log" (func $log (param f32) (result f32)))
(import "env" "fmod" (func $fmod (param f32) (param f32) (result f32)))
(import "env" "random" (func $random (result i32)))
(import "env" "randomf" (func $randomf (result f32)))
(import "env" "randomSeed" (func $randomSeed (param i32)))
(import "env" "cls" (func $cls (param i32)))
(import "env" "setPixel" (func $setPixel (param i32) (param i32) (param i32)))
(import "env" "getPixel" (func $getPixel (param i32) (param i32) (result i32)))
(import "env" "hline" (func $hline (param i32) (param i32) (param i32) (param i32)))
(import "env" "rectangle" (func $rectangle (param f32) (param f32) (param f32) (param f32) (param i32)))
(import "env" "circle" (func $circle (param f32) (param f32) (param f32) (param i32)))
(import "env" "line" (func $line (param f32) (param f32) (param f32) (param f32) (param i32)))
(import "env" "time" (func $time (result f32)))
(import "env" "isButtonPressed" (func $isButtonPressed (param i32) (result i32)))
(import "env" "isButtonTriggered" (func $isButtonTriggered (param i32) (result i32)))
(import "env" "printChar" (func $printChar (param i32)))
(import "env" "printString" (func $printString (param i32)))
(import "env" "printInt" (func $printInt (param i32)))
(import "env" "setTextColor" (func $setTextColor (param i32)))
(import "env" "setBackgroundColor" (func $setBackgroundColor (param i32)))
(import "env" "setCursorPosition" (func $setCursorPosition (param i32) (param i32)))
(import "env" "rectangle_outline" (func $rectangle_outline (param f32) (param f32) (param f32) (param f32) (param i32)))
(import "env" "circle_outline" (func $circle_outline (param f32) (param f32) (param f32) (param i32)))
(import "env" "exp" (func $exp (param f32) (result f32)))
(import "env" "playNote" (func $playNote (param i32) (param i32)))
(import "env" "sndGes" (func $sndGes (param i32) (result f32)))
;; to use defines, include this file with a preprocessor
;; like gpp (https://logological.org/gpp).
#define TIME_MS 0x40;
#define GAMEPAD 0x44;
#define FRAMEBUFFER 0x78;
#define PALETTE 0x13000;
#define FONT 0x13400;
#define USER_MEM 0x14000;
#define BUTTON_UP 0x0;
#define BUTTON_DOWN 0x1;
#define BUTTON_LEFT 0x2;
#define BUTTON_RIGHT 0x3;
#define BUTTON_A 0x4;
#define BUTTON_B 0x5;
#define BUTTON_X 0x6;
#define BUTTON_Y 0x7;
+4
View File
@@ -0,0 +1,4 @@
rustc --target=wasm32-unknown-unknown -C target-feature=+nontrapping-fptoint --crate-type cdylib -C opt-level="z" -C "link-args=--import-memory --initial-memory=262144 -zstack-size=90000" -o tunnel.wasm tunnel.rs && \
uw8 filter-exports tunnel.wasm tunnel.wasm && \
wasm-opt -Oz --strip-producers -o tunnel.wasm tunnel.wasm && \
uw8 pack -l 9 tunnel.wasm tunnel.uw8
+27
View File
@@ -0,0 +1,27 @@
A small example how to produce somewhat reasonably small MicroW8
carts in rust.
A nightly rust compiler is needed for the unstable sqrtf32
intrinsic.
Simply compiling with rustc as shown in build.sh results in a
361 byte tunnel.wasm. Using wasm-opt this can be reduced to
255 bytes.
When you disassemble this wasm file using wasm2wat you can see
these globals and exports:
(global (;0;) i32 (i32.const 90000))
(global (;1;) i32 (i32.const 90000))
(export "__data_end" (global 0))
(export "__heap_base" (global 1))
They are meant to be used for heap allocations and stack for any
values that are not simple scalars (i32, f32, etc.). Since our
code doesn't actually use any of that, the globals are only
referenced by the exports and we can remove them using
'uw8 filter-exports' (preferably before running wasm-opt) which
removes all exports except those used by the MicroW8 platform.
This gives us a 211 byte wasm file. Running this through
uw8 pack brings us to the final size of 119 bytes.
+53
View File
@@ -0,0 +1,53 @@
#![no_std]
#![feature(core_intrinsics)]
mod env {
// "env" is the default module for imports, but it is still needed here
// since there is a compiler builtin of the same name which is used
// if we don't make it clear that this is a module import.
#[link(wasm_import_module = "env")]
extern "C" {
pub fn atan2(x: f32, y: f32) -> f32;
}
extern "C" {
pub fn time() -> f32;
}
}
fn atan2(x: f32, y: f32) -> f32 {
unsafe { env::atan2(x, y) }
}
fn time() -> f32 {
unsafe { env::time() }
}
fn sqrt(v: f32) -> f32 {
unsafe { core::intrinsics::sqrtf32(v) }
}
#[no_mangle]
pub fn upd() {
let mut i: i32 = 0;
loop {
let t = time() * 63.;
let x = (i % 320 - 160) as f32;
let y = (i / 320 - 120) as f32;
let d = 40000 as f32 / sqrt(x * x + y * y);
let u = atan2(x, y) * 512. / 3.141;
let c = ((d + t * 2.) as i32 ^ (u + t) as i32) as u8 >> 4;
unsafe {
*((120 + i) as *mut u8) = c;
}
i += 1;
if i >= 320*240 {
break;
}
}
}
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}
+2
View File
@@ -0,0 +1,2 @@
K@دkلخ]زيتTm=ص}يرُ%d$ٍرdCB~حO تAz6 قLزأ"iيG^لc]قز
rد  انع0افTئ¤
+74
View File
@@ -0,0 +1,74 @@
(module
(import "env" "atan2" (func $atan2 (param f32 f32) (result f32)))
(import "env" "time" (func $time (result f32)))
(import "env" "memory" (memory 4))
(func (export "upd")
(local $y i32)
(local $i i32)
(local $x i32)
(loop $pixels
i32.const 1
local.get $i
local.get $i
i32.const 36928
f32.convert_i32_s
local.get $i
i32.const 320
i32.rem_s
i32.const 160
i32.sub
local.tee $x
local.get $x
i32.mul
local.get $i
i32.const 320
i32.div_s
i32.const 120
i32.sub
local.tee $y
local.get $y
i32.mul
i32.add
f32.convert_i32_s
f32.sqrt
f32.div
call $time
i32.const 163
f32.convert_i32_s
f32.mul
f32.add
i32.trunc_sat_f32_s
local.get $x
f32.convert_i32_s
local.get $y
f32.convert_i32_s
call $atan2
i32.const 163
f32.convert_i32_s
f32.mul
call $time
i32.const 64
f32.convert_i32_s
f32.mul
f32.add
i32.trunc_f32_s
i32.xor
i32.const 4
i32.shr_s
i32.const 15
i32.and
i32.store8 offset=120
i32.add
local.tee $i
i32.const 76800
i32.rem_s
br_if $pixels
)
)
)
+26
View File
@@ -0,0 +1,26 @@
(module
(import "env" "memory" (memory 4))
(func (export "upd")
(local $i i32) ;; local variables are zero initialized
(loop $pixels
local.get $i ;; pixel index to write to
(i32.rem_u (local.get $i) (i32.const 320)) ;; x
(i32.div_u (i32.load (i32.const 64)) (i32.const 10)) ;; time / 10
i32.add
(i32.div_u (local.get $i) (i32.const 320)) ;; y
i32.xor ;; (x + time / 10) ^ y
(i32.shr_u (i32.const 3)) ;; .. >> 3
(i32.and (i32.const 127)) ;; .. & 127
i32.store8 offset=120 ;; store at pixel index + 120
(i32.add (local.get $i) (i32.const 1)) ;; i + 1
local.tee $i ;; write it back but keep it on the stack
(br_if $pixels (i32.lt_s (i32.const 76800))) ;; branch to start of loop if i < 320*240
)
)
)
+2
View File
@@ -0,0 +1,2 @@
/zig-cache/
/zig-out/
+41
View File
@@ -0,0 +1,41 @@
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
const mode = std.builtin.Mode.ReleaseSmall;
const lib = b.addSharedLibrary("cart", "main.zig", .unversioned);
lib.setBuildMode(mode);
lib.setTarget(.{
.cpu_arch = .wasm32,
.os_tag = .freestanding,
.cpu_features_add = std.Target.wasm.featureSet(&.{ .nontrapping_fptoint })
});
lib.import_memory = true;
lib.initial_memory = 262144;
lib.max_memory = 262144;
lib.global_base = 81920;
lib.stack_size = 8192;
lib.install();
if (lib.install_step) |install_step| {
const run_filter_exports = b.addSystemCommand(&[_][]const u8{
"uw8", "filter-exports", "zig-out/lib/cart.wasm", "zig-out/lib/cart-filtered.wasm"
});
run_filter_exports.step.dependOn(&install_step.step);
const run_wasm_opt = b.addSystemCommand(&[_][]const u8{
"wasm-opt", "-Oz", "-o", "zig-out/cart.wasm", "zig-out/lib/cart-filtered.wasm"
});
run_wasm_opt.step.dependOn(&run_filter_exports.step);
const run_uw8_pack = b.addSystemCommand(&[_][]const u8{
"uw8", "pack", "-l", "9", "zig-out/cart.wasm", "zig-out/cart.uw8"
});
run_uw8_pack.step.dependOn(&run_wasm_opt.step);
const make_opt = b.step("make_opt", "make size optimized cart");
make_opt.dependOn(&run_uw8_pack.step);
b.default_step = make_opt;
}
}
+20
View File
@@ -0,0 +1,20 @@
extern fn atan2(x: f32, y: f32) f32;
extern fn time() f32;
pub const FRAMEBUFFER: *[320*240]u8 = @intToPtr(*[320*240]u8, 120);
export fn upd() void {
var i: u32 = 0;
while(true) {
var t = time() * 63.0;
var x = @intToFloat(f32, (@intCast(i32, i % 320) - 160));
var y = @intToFloat(f32, (@intCast(i32, i / 320) - 120));
var d = 40000.0 / @sqrt(x * x + y * y);
var u = atan2(x, y) * 512.0 / 3.141;
var c = @intCast(u8, (@floatToInt(i32, d + t * 2.0) ^ @floatToInt(i32, u + t)) & 255) >> 4;
FRAMEBUFFER[@as(usize, i)] = c;
i += 1;
if(i >= 320*240) { break; }
}
}
-215
View File
@@ -1,215 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" type="image/png" href="/favicon.ico">
<style>
:root {
/* Primary theme color */
--primary-color: #202024;
/* Primary theme text color */
--primary-text-color: #808070;
/* Primary theme link color */
--primary-link-color: #8080a0;
/* Secondary color: the background body color */
--secondary-color: #e0e0e8;
--secondary-text-color: #1a1818;
/* Highlight text color of table of content */
--toc-highlight-text-color: #d46e13;
}
</style>
<link href="https://fonts.googleapis.com/css?family=Alfa+Slab+One&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Fira+Sans:400,500,600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/normalize.css">
<link rel="stylesheet" href="https://exoticorn.github.io/microw8/juice.css">
</head>
<body>
<header class="pos-absolute" style="background-color: transparent">
<a href="https:&#x2F;&#x2F;exoticorn.github.io&#x2F;microw8&#x2F;">
<div class="logo">
<img src="https://exoticorn.github.io/microw8/img/microw8.svg" alt="logo">
MicroW8
</div>
</a>
<nav>
<a class="nav-item subtitle-text" href="https:&#x2F;&#x2F;exoticorn.github.io&#x2F;microw8&#x2F;versions&#x2F;"></a>
<a class="nav-item subtitle-text" href="https:&#x2F;&#x2F;exoticorn.github.io&#x2F;microw8&#x2F;docs&#x2F;">Docs</a>
<a class="nav-item subtitle-text" href="https:&#x2F;&#x2F;github.com&#x2F;exoticorn&#x2F;microw8">Github</a>
</nav>
</header>
<div class="hero">
<div>
<section>
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
</section>
<a href="v0.4.1">
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
</a>
</div>
<div class="explore-more text"
onclick="document.getElementById('features').scrollIntoView({behavior: 'smooth'})">
Explore More ⇩
</div>
</div>
<main>
<div class="toc">
<div class="toc-sticky">
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/#about">About</a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/#specs">Specs</a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/#examples">Examples</a>
</div>
<div class="toc-item">
<a class="subtext" href="https://exoticorn.github.io/microw8/#versions">Versions</a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/#v0-4-1"><small>- v0.4.1</small></a>
</div>
<div class="toc-item-child">
<a class="subtext" href="https://exoticorn.github.io/microw8/#older-versions"><small>- Older versions</small></a>
</div>
</div>
</div>
<div class="content text">
<div id="features" class="heading-text">Overview</div>
<h2 id="about">About</h2>
<p>MicroW8 is a WebAssembly based fantasy console inspired by the likes of <a href="https://tic80.com/">TIC-80</a>, <a href="https://wasm4.org/">WASM-4</a> and <a href="https://www.lexaloffle.com/pico-8.php">PICO-8</a>.</p>
<p>The initial motivation behind MicroW8 was to explore whether there was a way to make WebAssembly viable for size-coding. (Size coding being the art of creating tiny (often &lt;= 256 bytes) graphical effects and games.) The available examples so far are all in this space, however, I very carefully made sure that all design decisions make sense from the point of view of bigger projects as well.</p>
<h2 id="specs">Specs</h2>
<ul>
<li>Screen: 320x240, 256 colors, 60Hz</li>
<li>Modules: Up to 256KB (WASM)</li>
<li>Memory: 256KB</li>
<li>Gamepad input (D-Pad + 4 Buttons)</li>
</ul>
<p>For detailed <a href="docs">documentation see here</a>.</p>
<h2 id="examples">Examples</h2>
<ul>
<li><a href="v0.2.0#AgVfq24KI2Ok2o8qVtPYj27fSuGnfeSKgbOkIOsaEQMov8TDYQ6UjdjwkZrYcM1i9alo4/+Bhm1PRFEa0YHJlJAk/PGoc2K41rejv9ZSqJqIHNjr7cappqhOR2jT+jk+0b0+U6hO+geRCTP2aufWs7L+f/Z27NFY8LKlqPSv+C6Rd6+ohoKi6sYl5Kcrlf1cyTinV7jTTnmbcXWVDBA5rRKxAGMUTDS8rHxqSztRITOaQVP1pSdYgi/BDdOJOxSOIkeaId84S+Ycls5na7EgwSfVIpgqF+tcfkUecb8t2mQrXA7pyKrh/wzHn5N6Oe5aOgmzY2YpTIct">Skip Ahead</a> (249 bytes): A port of my <a href="http://tic80.com/play?cart=1735">TIC-80 256byte game</a> from LoveByte'21, now with sound</li>
<li><a href="v0.2.0#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==">Fireworks</a> (127 bytes): Some fireworks to welcome 2022.</li>
<li><a href="v0.2.0#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==">OhNoAnotherTunnel</a> (175 bytes): A port of my <a href="http://tic80.com/play?cart=1871">entry</a> in the Outline'21 bytebattle final</li>
<li><a href="v0.2.0#AhPXpq894LaUhp5+HQf39f39/Jc8g5zUrBSc0uyKh36ivskczhY84h55zL8gWpkdvKuRQI+KIt80isKzh8jkM8nILcx0RUvyk8yjE8TgNsgkcORVI0RY5k3qE4ySjaycxa2DVZH61UWZuLsCouuwT7I80TbmmetQSbMywJ/avrrCZIAH0UzQfvOiCJNG48NI0FFY1vjB7a7dcp8Uqg==">Technotunnel</a> (157 bytes): A port of my <a href="https://tic80.com/play?cart=1873">entry</a> in the Outline'21 bytebattle quater final</li>
<li><a href="v0.2.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ">Font &amp; Palette</a>: Just a simple viewer for the default font and palette.</li>
</ul>
<p>Examplers for older versions:</p>
<ul>
<li><a href="v0.1pre2#AQrDAQHAAQIBfwp9A0AgAUEAsiABQcACb7JDmhkgQ5MiBCAEIASUIAFBwAJtQfgAa7IiBSAFlJKRIgaVIgcgByAAskHQD7KVIgIQAEPNzEw/lCIDlCAHIAeUIAOUIAOUQQGykiADIAOUk5GSIgiUIAOTQQqylCACkiIJqCAFIAaVIAiUQQqylCACkiIKqHMgCEEyspQgBpUiCyACkkEUspSocUEFcbJBArIgC5OUQRaylJeoOgB4IAFBAWoiAUGA2ARIDQALCw==">Technotunnel B/W</a> (199 bytes uncompressed): A port of my <a href="https://tic80.com/play?cart=1873">entry</a> in the Outline'21 bytebattle quater final (older MicroW8 version with monochrome palette)</li>
<li><a href="v0.1pre2#AQovAS0BAX8DQCABIAFBwAJvIABBCm1qIAFBwAJtczoAeCABQQFqIgFBgNgESA0ACws=">XorScroll</a> (50 bytes uncompressed): A simple scrolling XOR pattern. Fun fact: This is the pre-loaded effect when entering a bytebattle.</li>
<li><a href="v0.1pre2#AQp7AXkCAX8CfUEgEA0DQCABskEEspUiAkECspUgALJBiCeylSIDQQWylJIQAEEBspJBoAGylCACQQOylSADQQSylJIQAEEBspJB+ACylCADQRGylCACQQKylJIQAEECspJBELKUIAFBAmxBP2oQEiABQQFqIgFBP0gNAAsL">CircleWorm</a> (126 bytes uncompressed): Just a test for the circle fill function.</li>
</ul>
<h2 id="versions">Versions</h2>
<h3 id="v0-4-1">v0.4.1</h3>
<ul>
<li><a href="../v0.4.1">Web runtime</a></li>
<li><a href="https://github.com/exoticorn/microw8/releases/download/v0.4.1/microw8-0.4.1-linux.tgz">Linux</a></li>
<li><a href="https://github.com/exoticorn/microw8/releases/download/v0.4.1/microw8-0.4.1-macos.tgz">MacOS</a></li>
<li><a href="https://github.com/exoticorn/microw8/releases/download/v0.4.1/microw8-0.4.1-windows.zip">Windows</a></li>
</ul>
<p>Changes:</p>
<ul>
<li>Windows: fix bad/inconsistent frame rate</li>
<li>fix choppy sound on devices with sample rates != 44100 kHz</li>
<li>add scale mode 'fill' option</li>
</ul>
<h3 id="older-versions">Older versions</h3>
<p><a href="versions">Find older versions here.</a></p>
</div>
</main>
</body>
<script>
function highlightNav(heading) {
let pathname = location.pathname;
document.querySelectorAll(".toc a").forEach((item) => {
item.classList.remove("active");
});
document.querySelector(".toc a[href$='" + pathname + "#" + heading + "']").classList.add("active");
}
let currentHeading = "";
window.onscroll = function () {
let h = document.querySelectorAll("h1,h2,h3,h4,h5,h6");
let elementArr = [];
h.forEach(item => {
if (item.id !== "") {
elementArr[item.id] = item.getBoundingClientRect().top;
}
});
elementArr.sort();
for (let key in elementArr) {
if (!elementArr.hasOwnProperty(key)) {
continue;
}
if (elementArr[key] > 0 && elementArr[key] < 300) {
if (currentHeading !== key) {
highlightNav(key);
currentHeading = key;
}
break;
}
}
}
</script>
</html>
-1
View File
@@ -1 +0,0 @@
.text-center{text-align:center}.pos-absolute{right:0;left:0;position:absolute}.box-shadow{box-shadow:0 2px 10px 2px #ddd}.heading-text{font-family:"Fira Sans", sans-serif;font-size:32px;font-weight:600;padding:10px 0 25px 0;color:var(--primary-text-color)}h1,.title-text{font-family:"Fira Sans", sans-serif;font-size:25px;font-weight:500;color:var(--primary-text-color);border-left:var(--primary-color) 8px solid;padding-left:10px}h2,.subtitle-text{font-family:"Fira Sans", sans-serif;font-size:20px;font-weight:500;color:var(--primary-text-color)}.text{font-family:"Fira Sans", sans-serif;font-size:18px;font-weight:400;line-height:26px;letter-spacing:0.2px;color:var(--primary-text-color)}.subtext{font-family:"Fira Sans", sans-serif;font-size:16px;font-weight:400;letter-spacing:0.1px}.content{padding:0 40px;display:flex;flex-direction:column;overflow-x:auto}.content pre{overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal;background-color:white;color:#4a4a4a;font-size:.875em;font-family:monospace}.content code{background-color:white;color:#4a4a4a;font-size:.875em;font-weight:normal;padding:0.25em 0.5em;font-family:monospace}.content pre code{padding:0}.content a{color:var(--primary-link-color)}.content a:hover{text-decoration:underline}.content blockquote{border-left:#e2dede 8px solid;margin:0;background-color:#f2f1f0;padding:0 20px}body{padding:0;margin:0;box-sizing:border-box;background-color:var(--secondary-color);display:flex;flex-direction:column;min-height:100vh}a{text-decoration:none}ul{margin-top:0.5rem}ul>li{padding:0.3rem 0}p>img{width:100%;height:auto}header{background-color:var(--primary-color);color:black;padding:20px 50px;display:flex;align-items:center;justify-content:space-between}.logo{font-family:"Alfa Slab One", serif;font-size:32px;color:var(--primary-text-color);display:flex;align-items:center;margin:0 40px}.logo img{width:60px;margin:0 25px}.nav-item{margin:0 10px;text-decoration:none;font-size:18px;font-weight:bold}.nav-item:hover{color:#000;text-decoration:underline}.hero{display:flex;align-items:center;justify-content:space-evenly;height:100vh;background-color:var(--primary-color);overflow-x:hidden;padding:0 40px}.hero .explore-more{position:absolute;bottom:20px;cursor:pointer}main{display:flex;padding:50px 100px;flex-grow:1}main .toc{max-width:260px;min-width:240px}main .toc-item{padding:10px 20px;color:#424242}main .toc-item a,main .toc-item-child a{color:var(--secondary-text-color)}main .toc-item a:hover,main .toc-item-child a:hover{cursor:pointer;text-decoration:underline}main .toc-item a.active,main .toc-item-child a.active{color:var(--toc-highlight-text-color)}main .toc-item-child{padding:0 30px 5px;color:#424242}.toc-sticky{border-radius:3px;border-top:5px solid var(--primary-color);background-color:white;position:sticky;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;top:10px;padding:10px 0 20px;max-height:100vh;overflow:auto}footer{padding:50px;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:#202020;color:#fcfcfc}footer a{color:#fcfcfc;text-decoration:underline}@media screen and (min-width: 1280px){.content{max-width:60%;min-width:800px}}@media screen and (max-width: 768px){header{padding:10px 30px;flex-direction:column;align-items:center;justify-content:center}.logo{font-size:28px;margin:10px}.logo img{width:45px;margin:0 10px 0 0}.nav-item{margin:0 5px;font-size:14px}.hero{padding:40px 30px}main{padding:30px}.content{padding:0}.explore-more,.toc{display:none}}
+65
View File
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="120"
height="120"
viewBox="0 0 120 120"
version="1.1"
id="svg5"
sodipodi:docname="logo.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="3.6041667"
inkscape:cx="21.780347"
inkscape:cy="63.260116"
inkscape:window-width="1916"
inkscape:window-height="1041"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#85a6b2;fill-rule:evenodd;fill-opacity:1"
id="rect31"
width="112.05161"
height="109.64198"
x="3.9731982"
y="6.3613" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:117.333px;line-height:1.25;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;fill-opacity:1;stroke:none"
x="-6.2686396"
y="117.70291"
id="text2691"><tspan
sodipodi:role="line"
id="tspan2689"
x="-6.2686396"
y="117.70291">W8</tspan></text>
<circle
style="fill:#ffffff;stroke-width:1.32327"
id="path121"
cx="60.962471"
cy="6.7644148"
r="14.855442" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

-349
View File
@@ -1,349 +0,0 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}
+1
View File
@@ -0,0 +1 @@
/target/
+625
View File
@@ -0,0 +1,625 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
dependencies = [
"const-random",
]
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "anyhow"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
[[package]]
name = "ariadne"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7080ae01b2f0c312065d4914cd0f0de045eb8832e9415b355106a6cff3073cb4"
dependencies = [
"yansi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bytemuck"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f"
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cdivsufsort"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edefce019197609da416762da75bb000bbd2224b2d89a7e722c2296cbff79b8c"
dependencies = [
"cc",
"sacabase",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chumsky"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
dependencies = [
"ahash 0.3.8",
]
[[package]]
name = "const-random"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f590d95d011aa80b063ffe3253422ed5aa462af4e9867d43ce8337562bac77c4"
dependencies = [
"const-random-macro",
"proc-macro-hack",
]
[[package]]
name = "const-random-macro"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615f6e27d000a2bffbc7f2f6a8669179378fa27ee4d0a509e985dfc0a7defb40"
dependencies = [
"getrandom",
"lazy_static",
"proc-macro-hack",
"tiny-keccak",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "curlywas"
version = "0.1.0"
source = "git+https://github.com/exoticorn/curlywas.git?rev=0e7ea50#0e7ea508cd0e76836283ae68a44c9097df83c8ac"
dependencies = [
"anyhow",
"ariadne",
"chumsky",
"pico-args 0.4.2",
"wasm-encoder 0.10.0",
"wasmparser 0.83.0",
]
[[package]]
name = "fallible_collections"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f57ccc32870366ae684be48b32a1a2e196f98a42a9b4361fe77e13fd4a34755"
dependencies = [
"hashbrown",
]
[[package]]
name = "flate2"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash 0.7.6",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "id-arena"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "leb128"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "lexopt"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "lodepng"
version = "3.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0ad39f75bbaa4b10bb6f2316543632a8046a5bcf9c785488d79720b21f044f8"
dependencies = [
"crc32fast",
"fallible_collections",
"flate2",
"libc",
"rgb",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "pbr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff5751d87f7c00ae6403eb1fcbba229b9c76c9a30de8c1cf87182177b168cea2"
dependencies = [
"crossbeam-channel",
"libc",
"time",
"winapi",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pico-args"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "platform"
version = "0.1.0"
dependencies = [
"anyhow",
"curlywas",
"lodepng",
"uw8-tool",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rgb"
version = "0.8.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3"
dependencies = [
"bytemuck",
]
[[package]]
name = "sacabase"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9883fc3d6ce3d78bb54d908602f8bc1f7b5f983afe601dabe083009d86267a84"
dependencies = [
"num-traits",
]
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "unicode-bidi"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "upkr"
version = "0.2.1"
source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
dependencies = [
"anyhow",
"cdivsufsort",
"lexopt",
"thiserror",
]
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "uw8-tool"
version = "0.1.0"
dependencies = [
"anyhow",
"pbr",
"pico-args 0.5.0",
"upkr",
"walrus",
"wasm-encoder 0.22.0",
"wasmparser 0.99.0",
]
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "walrus"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8"
dependencies = [
"anyhow",
"id-arena",
"leb128",
"log",
"walrus-macro",
"wasmparser 0.77.0",
]
[[package]]
name = "walrus-macro"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-encoder"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-encoder"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef126be0e14bdf355ac1a8b41afc89195289e5c7179f80118e3abddb472f0810"
dependencies = [
"leb128",
]
[[package]]
name = "wasmparser"
version = "0.77.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
[[package]]
name = "wasmparser"
version = "0.83.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
[[package]]
name = "wasmparser"
version = "0.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ef3b717afc67f848f412d4f02c127dd3e35a0eecd58c684580414df4fde01d3"
dependencies = [
"indexmap",
"url",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yansi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "platform"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="0e7ea50" }
uw8-tool = { path="../uw8-tool" }
anyhow = "1"
lodepng = "3.7.2"
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.
+222
View File
@@ -0,0 +1,222 @@
const GesChannelState.Trigger = 0;
const GesChannelState.EnvState = 1;
const GesChannelState.EnvVol = 2;
const GesChannelState.Phase = 4;
const GesChannelState.Size = 8;
const GesState.Filter = GesChannelState.Size * 4;
const GesState.Size = GesState.Filter + 8*4;
const GesStateOffset = 32;
const GesBufferOffset = 32 + GesState.Size;
export fn sndGes(t: i32) -> f32 {
let baseAddr = 0!0x12c78;
if !(t & 127) {
let i: i32;
loop clearLoop {
(baseAddr + i)!GesBufferOffset = 0;
branch_if (i := i + 4) < 128*4: clearLoop;
}
let ch: i32;
loop channelLoop {
let lazy channelState = baseAddr + GesStateOffset + ch * GesChannelState.Size;
let lazy channelReg = baseAddr + ch * 6;
let envState = channelState?GesChannelState.EnvState;
let envVol = i32.load16_u(channelState, GesChannelState.EnvVol);
let lazy oldTrigger = channelState?GesChannelState.Trigger;
let lazy ctrl = channelReg?0;
if (oldTrigger ^ ctrl) & (ctrl | 2) & 3 {
envState = 1;
envVol = 0;
}
channelState?GesChannelState.Trigger = ctrl;
if envState {
let lazy attack = channelReg?4 & 15;
envVol = envVol + 12 * pow(1.675, (15 - attack) as f32) as i32;
if envVol >= 65535 {
envVol = 65535;
envState = 0;
}
} else {
let inline decay = (channelReg - (ctrl & 1))?5 >> 4;
let inline dec = 8 * pow(1.5625, (15 - decay) as f32) as i32;
envVol = envVol - ((dec * (envVol + 8192)) >> 16);
let inline sustain = (channelReg?5 & 15) << 12;
let lazy targetVol = (ctrl & 1) * sustain;
if envVol < targetVol {
envVol = targetVol;
}
}
channelState?GesChannelState.EnvState = envState;
i32.store16(envVol, channelState, GesChannelState.EnvVol);
let inline note = i32.load16_u(channelReg, 2);
let lazy freq = 440 as f32 * pow(2.0, (note - 69*256) as f32 / (12*256) as f32);
let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
let phase = channelState!GesChannelState.Phase;
let inline pulseWidth = channelReg?1;
let phaseShift = (pulseWidth - 128) * 255;
let invPhaseInc = 1 as f32 / phaseInc as f32;
i = 0;
let wave = ctrl >> 6;
if wave < 2 {
if wave {
let pulsePhase1 = pulseWidth << 23;
let pulsePhase2 = (511 - pulseWidth) << 23;
loop sawLoop {
let p = (phase ^ 32768) << 16;
let saw = (p >> 16) - polyBlep(phase, invPhaseInc, -32767);
let saw2 = select(p #>= pulsePhase1 & p #< pulsePhase2, -saw, saw);
let saw2 = saw2 -
polyBlep((p - pulsePhase1) >> 16, invPhaseInc, -saw) -
polyBlep((p - pulsePhase2) >> 16, invPhaseInc, saw);
(baseAddr + i)!(GesBufferOffset + 128*4) = saw2;
phase = phase + phaseInc;
branch_if (i := i + 4) < 64*4: sawLoop;
}
}
else
{
let pulsePhase = 32768 + pulseWidth * 128;
loop rectLoop {
(baseAddr + i)!(GesBufferOffset + 128*4) = select((phase & 65535) < pulsePhase, -32768, 32767) -
polyBlep(phase, invPhaseInc, -32767) -
polyBlep(phase - pulsePhase, invPhaseInc, 32767);
phase = phase + phaseInc;
branch_if (i := i + 4) < 64*4: rectLoop;
}
}
} else {
if wave == 2 {
let scale = pulseWidth + 256;
loop triLoop {
let s = phase << 16;
s = (s ^ (s >> 31));
s = (s >> 8) * scale;
s = (s ^ (s >> 31));
(baseAddr + i)!(GesBufferOffset + 128*4) = (s >> 15) - 32768;
phase = phase + phaseInc;
branch_if (i := i + 4) < 64*4: triLoop;
}
} else {
loop noiseLoop {
let s = phase >> 12;
let inline pulse = ((phase >> 8) & 255) >= pulseWidth;
s = s * 0x6746ba73;
s = s ^ (s >> 15) * pulse;
(baseAddr + i)!(GesBufferOffset + 128*4) = (s * 0x835776c7) >> 16;
phase = phase + phaseInc;
branch_if (i := i + 4) < 64*4: noiseLoop;
}
}
}
channelState!GesChannelState.Phase = phase;
if ctrl & 32 {
let lazy modSrc = (ch - 1) & 3;
let inline channelState = baseAddr + GesStateOffset + modSrc * GesChannelState.Size;
let inline channelReg = baseAddr + modSrc * 6;
let inline note = i32.load16_u(channelReg, 2);
let inline freq = 440 as f32 * pow(2.0, (note - 69*256) as f32 / (12*256) as f32);
let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
let phase = channelState!GesChannelState.Phase;
if modSrc > ch {
phase = phase - (phaseInc << 6);
}
i = 0;
loop ringLoop {
let s = phase << 16;
s = (s ^ (s >> 31));
(baseAddr + i)!(GesBufferOffset + 128*4) = ((baseAddr + i)!(GesBufferOffset + 128*4) * ((s >> 15) - 32768)) >> 15;
phase = phase + phaseInc;
branch_if (i := i + 4) < 64*4: ringLoop;
}
}
let channelVol = ((baseAddr + (ch >> 1))?24 >> ((ch & 1) * 4)) & 15;
envVol = envVol * channelVol / 15;
let leftVol = (select(ctrl & 16, 0x3d5b, 0x6a79) >> (ch * 4)) & 15;
let rightVol = 16 - leftVol;
let lazy filter = (ctrl >> 2) & 3;
i = 0;
if filter < 2 {
if filter {
let f = (4096 as f32 - min(4096 as f32, 4096 as f32 * exp(freq * (-8.0 * 3.141 / 44100.0)))) as i32;
let low = (baseAddr + ch * 8)!(GesStateOffset + GesState.Filter);
loop filterLoop {
let in = ((baseAddr + i)!(GesBufferOffset + 128*4) * envVol) >> 18;
low = low + (((in - low) * f) >> 12);
(baseAddr + i * 2)!GesBufferOffset = (baseAddr + i * 2)!GesBufferOffset + ((low * leftVol) >> 4);
(baseAddr + i * 2)!(GesBufferOffset + 4) = (baseAddr + i * 2)!(GesBufferOffset + 4) + ((low * rightVol) >> 4);
branch_if (i := i + 4) < 64*4: filterLoop;
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter) = low;
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4) = 0;
}
} else {
loop mixLoop {
let sample = ((baseAddr + i)!(GesBufferOffset + 128*4) * envVol) >> 18;
(baseAddr + i * 2)!GesBufferOffset = (baseAddr + i * 2)!GesBufferOffset + ((sample * leftVol) >> 4);
(baseAddr + i * 2)!(GesBufferOffset + 4) = (baseAddr + i * 2)!(GesBufferOffset + 4) + ((sample * rightVol) >> 4);
branch_if (i := i + 4) < 64*4: mixLoop;
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter) = sample;
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4) = 0;
}
}
} else {
filter = filter - 2;
let ctrl = (baseAddr + filter)?26;
let note = i32.load16_u(baseAddr + filter * 2, 28);
let inline freq = 440 as f32 * pow(2.0, (note - 69*256) as f32 / (12*256) as f32);
let F = (8192 as f32 * sin(min(0.25, freq / 44100 as f32) * 3.1415)) as i32;
let Q = 8192 - (ctrl >> 4) * (7000/15);
let Qlimit = (8192*4096/F - F/2) * 3 / 4;
if Q > Qlimit {
Q = Qlimit;
}
let low_out = ctrl & 1;
let high_out = (ctrl >> 1) & 1;
let band_out = (ctrl >> 2) & 1;
let low = (baseAddr + ch * 8)!(GesStateOffset + GesState.Filter);
let band = (baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4);
loop filterLoop {
let in = ((baseAddr + i)!(GesBufferOffset + 128*4) * envVol) >> 18;
let high = in - low - ((band * Q) >> 12);
band = band + ((F * high) >> 12);
low = low + ((F * band) >> 12);
let sample = low * low_out + high * high_out + band * band_out;
(baseAddr + i * 2)!GesBufferOffset = (baseAddr + i * 2)!GesBufferOffset + ((sample * leftVol) >> 4);
(baseAddr + i * 2)!(GesBufferOffset + 4) = (baseAddr + i * 2)!(GesBufferOffset + 4) + ((sample * rightVol) >> 4);
branch_if (i := i + 4) < 64*4: filterLoop;
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter) = low;
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4) = band;
}
}
branch_if (ch := ch + 1) < 4: channelLoop;
}
}
((baseAddr + (t & 127) * 4)!GesBufferOffset) as f32 / 32768 as f32
}
fn polyBlep(transientPhase: i32, invPhaseInc: f32, magnitude: i32) -> i32 {
let lazy t = ((transientPhase << 16) >> 16) as f32 * invPhaseInc;
let lazy x = max(0 as f32, 1 as f32 - abs(t));
(f32.copysign(x * x, t) * magnitude as f32) as i32
}
+6
View File
@@ -0,0 +1,6 @@
import "env.memory" memory(1);
import "env.sin" fn sin(f32) -> f32;
import "env.pow" fn pow(f32, f32) -> f32;
import "env.exp" fn exp(f32) -> f32;
include "ges.cwa"
+166
View File
@@ -0,0 +1,166 @@
import "env.memory" memory(4);
global mut base_end: i32 = 0;
export fn load_uw8(module_size: i32) -> i32 {
let lazy version = 0?0 - 1;
if version < 0 {
return module_size;
}
let module_end = 0x1e000 + module_size;
if version & 1 {
module_end = uncompress(1, 0x1e001);
} else {
copy(0x1e000, 0, module_size);
}
copy(0, 0x3c200, 8);
let base_start = 0x3c208;
let dest = 8;
let src = 0x1e001;
loop sections {
if src < module_end & (base_start >= base_end | src?0 <= base_start?0) {
let lazy length2 = copy_section(dest, src);
dest = dest + length2;
if base_start < base_end & src?0 == base_start?0 {
base_start = base_start + section_size(base_start);
}
src = src + length2;
branch sections;
}
if base_start < base_end {
let lazy length3 = copy_section(dest, base_start);
dest = dest + length3;
base_start = base_start + length3;
branch sections;
}
}
dest
}
fn section_size(ptr: i32) -> i32 {
let p = ptr;
let l: i32;
let shift: i32;
loop size {
let lazy b = (p := p + 1)?0;
l = l | ((b & 127) << shift);
shift = shift + 7;
branch_if b >> 7: size;
}
p + 1 - ptr + l
}
fn copy_section(dest: i32, src: i32) -> i32 {
let lazy length = section_size(src);
copy(dest, src, length);
length
}
fn copy(dest: i32, src: i32, len: i32) {
loop bytes {
if len > 0 {
(dest + (len := len - 1))?0 = (src + len)?0;
branch bytes;
}
}
}
// upkr unpacker
global mut upkr_src_ptr: i32 = 0;
global mut upkr_state: i32 = 0;
// uncompress upkr compressed data at `src` into the buffer at `dest`
// returns the end of the uncompressed data
export fn uncompress(src_ptr: i32, dest_ptr: i32) -> i32 {
upkr_src_ptr = src_ptr;
upkr_state = 0;
let offset: i32;
let i: i32;
loop init_contexts {
i?0x3c000 = 0x80;
branch_if (i := i + 1) < 256 + 1 + 128: init_contexts;
}
let prev_was_match: i32;
block finished {
loop unpack_loop {
let lazy is_match = upkr_bit(0);
if is_match {
let inline new_offset = if prev_was_match { 1 } else { upkr_bit(256) };
if new_offset {
branch_if !(offset := upkr_length(257) - 1): finished;
}
let length = upkr_length(257 + 64);
loop copy {
dest_ptr?0 = (dest_ptr - offset)?0;
dest_ptr = dest_ptr + 1;
branch_if (length := length - 1): copy;
}
} else {
// literal
let byte = 1;
loop literal {
branch_if (byte := (byte << 1) | upkr_bit(byte)) < 256: literal;
}
dest_ptr?0 = byte;
dest_ptr = dest_ptr + 1;
}
prev_was_match = is_match;
branch unpack_loop;
}
}
dest_ptr
}
fn upkr_length(context_index: i32) -> i32 {
let length: i32;
let bit_pos: i32;
loop bits {
if upkr_bit(context_index + bit_pos) {
length = length | (upkr_bit(context_index + bit_pos + 32) << bit_pos);
bit_pos = bit_pos + 1;
branch bits;
}
}
length | (1 << bit_pos)
}
fn upkr_bit(context_index: i32) -> i32 {
let lazy prob = context_index?0x3c000;
loop refill {
if upkr_state < 1<<12 {
upkr_state = (upkr_state << 8) | upkr_src_ptr?0;
upkr_src_ptr = upkr_src_ptr + 1;
branch refill;
}
}
let lazy state_low = upkr_state & 0xff;
let lazy state_hi = upkr_state >> 8;
let lazy bit = state_low < prob;
upkr_state = state_low + select(bit, prob * state_hi, (0x100 - prob) * state_hi - prob);
context_index?0x3c000 = prob + ((7 + bit * 257 - prob) >> 4);
bit
}
start fn unpack_base() {
base_end = uncompress(0, 0x3c200);
}
data 0 {
file("../target/base.upk")
}
+55
View File
@@ -0,0 +1,55 @@
use std::io::prelude::*;
use std::{fs::File, path::Path};
use anyhow::Result;
fn main() -> Result<()> {
println!("Generating compressed base module");
uw8_tool::BaseModule::create_binary(&Path::new("target/base.upk"))?;
println!("Converting font");
convert_font()?;
println!("Compiling loader module");
let loader = curlywas::compile_file("src/loader.cwa", curlywas::Options::default()).0?;
File::create("bin/loader.wasm")?.write_all(&loader)?;
println!("Loader (including base module): {} bytes", loader.len());
println!("Compiling platform module");
let platform = curlywas::compile_file("src/platform.cwa", curlywas::Options::default()).0?;
println!("Compressing platform module");
let platform = uw8_tool::pack(
&platform,
&uw8_tool::PackConfig::default().with_compression_level(4),
)?;
File::create("bin/platform.uw8")?.write_all(&platform)?;
println!("Platform module: {} bytes", platform.len());
Ok(())
}
fn convert_font() -> Result<()> {
let image = lodepng::decode32_file("src/font.png")?;
assert!(image.width == 128 && image.height == 128);
let mut font = vec![];
for char in 0..256 {
for y in 0..8 {
let mut byte = 0u8;
let base = (char % 16 * 8) + (char / 16 * 8 + y) * 128;
for x in 0..8 {
byte += byte;
if image.buffer[base + x].r > 128 {
byte |= 1;
}
}
font.push(byte);
}
}
File::create("target/font.bin")?.write_all(&font)?;
Ok(())
}
+673
View File
@@ -0,0 +1,673 @@
import "env.memory" memory(4);
import "env.sin" fn sin(f32) -> f32;
import "env.cos" fn cos(f32) -> f32;
import "env.pow" fn pow(f32, f32) -> f32;
import "env.exp" fn exp(f32) -> f32;
import "env.logChar" fn logChar(i32);
export fn time() -> f32 {
(0!64) as f32 / 1000 as f32
}
///////////
// INPUT //
///////////
export fn isButtonPressed(btn: i32) -> i32 {
(68!0 >> btn) & 1
}
export fn isButtonTriggered(btn: i32) -> i32 {
((68!0 & (-1 - 68!4)) >> btn) & 1
}
////////////
// RANDOM //
////////////
global mut randomState: i64 = 37i64;
export fn random() -> i32 {
(random64() >> 32i64) as i32
}
export fn random64() -> i64 {
let lazy state = randomState ^ (randomState #>> 12i64);
let lazy state = state ^ (state << 25i64);
randomState = state ^ (state #>> 27i64);
randomState * 0x2545f4914f6cdd1di64
}
export fn randomf() -> f32 {
f32.reinterpret_i32(0x3f800000 | (random() #>> 9)) - 1 as f32
}
export fn randomSeed(s: i32) {
randomState = (s as i64 << 32i64) ^ ((63 - s) as i64);
randomState = random64();
randomState = random64();
}
export fn fmod(a: f32, b: f32) -> f32 {
a - floor(a / b) * b
}
/////////////
// DRAWING //
/////////////
export fn cls(col: i32) {
let i: i32;
textCursorX = 0;
textCursorY = 0;
outputChannel = 0;
memory.fill(120, col, 320*240);
}
export fn setPixel(x: i32, y: i32, col: i32) {
if x #< 320 & y #< 240 {
(x + y * 320)?120 = col
}
}
export fn getPixel(x: i32, y: i32) -> i32 {
if x #< 320 & y #< 240 {
(x + y * 320)?120
} else {
0
}
}
fn clamp(v: i32, min: i32, max: i32) -> i32 {
select(v < min, min, select(v > max, max, v))
}
export fn hline(x1: i32, x2: i32, y: i32, col: i32) {
x1 = clamp(x1, 0, 320);
x2 = clamp(x2, 0, 320);
if y #>= 240 {
return;
}
let word_start = (x1 + 3) & -4;
let word_end = x2 & -4;
if word_end > word_start {
col = (col & 255) * 0x1010101;
let ptr = y * 320 + x1;
let end = ptr + word_start - x1;
if ptr + 2 <= end {
ptr?120 = col;
ptr?121 = col;
ptr += 2;
}
if ptr < end {
ptr?120 = col;
ptr += 1;
}
end += word_end - word_start;
loop words {
if ptr + 16 <= end {
ptr!120 = col;
ptr!124 = col;
ptr!128 = col;
ptr!132 = col;
ptr += 16;
branch words;
}
if ptr + 8 <= end {
ptr!120 = col;
ptr!124 = col;
ptr += 8;
}
if ptr < end {
ptr!120 = col;
ptr += 4;
}
}
end += x2 - word_end;
if ptr + 2 <= end {
ptr?120 = col;
ptr?121 = col;
ptr += 2;
}
if ptr < end {
ptr?120 = col;
}
} else {
let ptr = y * 320 + x1;
let end = ptr + x2 - x1;
if ptr + 4 <= end {
ptr?120 = col;
ptr?121 = col;
ptr?122 = col;
ptr?123 = col;
ptr += 4;
}
if ptr + 2 <= end {
ptr?120 = col;
ptr?121 = col;
ptr += 2;
}
if ptr < end {
ptr?120 = col;
}
}
}
export fn rectangle(x: f32, y: f32, w: f32, h: f32, col: i32) {
if abs(w) == w & abs(h) == h {
let x1 = nearest(x) as i32;
let y1 = clamp(nearest(y) as i32, 0, 240);
let x2 = nearest(x + w) as i32;
let y2 = clamp(nearest(y + h) as i32, 0, 240);
block done {
loop lines {
branch_if y1 >= y2: done;
hline(x1, x2, y1, col);
y1 = y1 + 1;
branch lines;
}
}
}
}
export fn rectangle_outline(x: f32, y: f32, w: f32, h: f32, col: i32) {
let xl = nearest(x) as i32;
let xr = nearest(x + w) as i32;
let yt = nearest(y) as i32;
let yb = nearest(y + h) as i32;
hline(xl, xr, yt, col);
if yt < yb {
hline(xl, xr, yb - 1, col);
loop y {
setPixel(xl, yt, col);
if xl < xr {
setPixel(xr - 1, yt, col);
}
branch_if (yt := yt + 1) < yb: y;
}
}
}
export fn circle(cx: f32, cy: f32, radius: f32, col: i32) {
let y = clamp(nearest(cy - radius) as i32, 0, 240);
let maxY = clamp(nearest(cy + radius) as i32, 0, 240);
block done {
loop lines {
branch_if y >= maxY: done;
let lazy dy = y as f32 - cy + 0.5;
let lazy q = radius * radius - dy * dy;
if abs(q) == q {
let lazy w = sqrt(q);
hline(nearest(cx - w) as i32, nearest(cx + w) as i32, y, col);
}
y = y + 1;
branch lines;
}
}
}
export fn circle_outline(cx: f32, cy: f32, radius: f32, col: i32) {
let prev_w: f32;
let y = clamp(nearest(cy - radius) as i32, -1, 241);
let maxY = clamp(nearest(cy + radius) as i32, -1, 241);
loop lines {
let lazy dy = y as f32 - cy + 0.5;
let inline q = radius * radius - dy * dy;
let w = sqrt(max(0 as f32, q));
let xlp = nearest(cx - prev_w) as i32;
let xl = nearest(cx - w) as i32;
let xrp = nearest(cx + prev_w) as i32;
let xr = nearest(cx + w) as i32;
if w >= prev_w {
if xl < xlp {
hline(xl, xlp, y, col);
} else {
if xl < xr {
setPixel(xl, y, col);
}
}
if xr > xrp {
hline(xrp, xr, y, col);
} else {
if xl < xr {
setPixel(xr - 1, y, col);
}
}
} else {
if xl > xlp {
hline(xlp, xl, y - 1, col);
} else {
if xlp < xrp {
setPixel(xlp, y - 1, col);
}
}
if xr < xrp {
hline(xr, xrp, y - 1, col);
} else {
if xlp < xrp {
setPixel(xrp - 1, y - 1, col);
}
}
}
y = y + 1;
prev_w = w;
branch_if y <= maxY: lines;
}
}
export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
let swapTmp: f32;
if x1 > x2 {
swapTmp = x1;
x1 = x2;
x2 = swapTmp;
swapTmp = y1;
y1 = y2;
y2 = swapTmp;
}
if x1 < 0 as f32 & x2 >= 0 as f32 {
y1 = y1 + (y2 - y1) * -x1 / (x2 - x1);
x1 = 0 as f32;
}
if x1 < 320 as f32 & x2 >= 320 as f32 {
y2 = y2 + (y2 - y1) * (320 as f32 - x2) / (x2 - x1);
x2 = 320 as f32;
}
if y1 > y2 {
swapTmp = x1;
x1 = x2;
x2 = swapTmp;
swapTmp = y1;
y1 = y2;
y2 = swapTmp;
}
if y1 < 0 as f32 & y2 >= 0 as f32 {
x1 = x1 + (x2 - x1) * -y1 / (y2 - y1);
y1 = 0 as f32;
}
if y1 < 240 as f32 & y2 >= 240 as f32 {
x2 = x2 + (x2 - x1) * (240 as f32 - y2) / (y2 - y1);
y2 = 240 as f32;
}
let lazy dx = x2 - x1;
let lazy dy = y2 - y1;
let max_axis: f32;
let p: f32;
if abs(dx) >= dy {
max_axis = dx;
p = x1;
} else {
max_axis = dy;
p = y1;
}
if max_axis == 0 as f32 {
setPixel(x1 as i32, y1 as i32, col);
return;
}
let steps = floor(p + max_axis) as i32 - floor(p) as i32;
p = floor(p) + 0.5 - p;
if max_axis < 0 as f32 {
steps = -steps;
p = -p;
max_axis = -max_axis;
}
dx = dx / max_axis;
dy = dy / max_axis;
let f = min(max_axis, max(0 as f32, p));
setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col);
if !steps {
return;
}
x1 = x1 + (1 as f32 + p) * dx;
y1 = y1 + (1 as f32 + p) * dy;
p = p + steps as f32;
loop pixels {
if steps := steps - 1 {
setPixel(i32.trunc_sat_f32_s(x1), i32.trunc_sat_f32_s(y1), col);
x1 = x1 + dx;
y1 = y1 + dy;
branch pixels;
}
}
f = min(max_axis, p) - p;
setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col);
}
//////////
// TEXT //
//////////
global mut textCursorX = 0;
global mut textCursorY = 0;
global mut textColor = 15;
global mut bgColor = 0;
global mut outputChannel = 0;
export fn printChar(char: i32) {
loop chars {
printSingleChar(char & 255);
branch_if (char := char #>> 8): chars;
}
}
global mut controlCodeLength = 0;
fn printSingleChar(char: i32) {
if outputChannel >= 2 & (char < 4 | char > 6) {
logChar(char);
return;
}
controlCodeLength?0x12d20 = char;
controlCodeLength = controlCodeLength + 1;
char = 0x12d20?0;
if char < 32 & controlCodeLength < char?0x12d00 {
return;
}
controlCodeLength = 0;
if char == 1 {
drawChar(0x12d20?1);
return;
}
if char >= 4 & char <= 6 {
outputChannel = char - 4;
if !outputChannel {
textCursorX = 0;
textCursorY = 0;
}
return;
}
if char == 7 {
80?0 = 80?0 ^ 2;
return;
}
if char == 8 {
textCursorX = textCursorX - 8;
if !outputChannel & textCursorX < 0 {
textCursorX = 320-8;
printSingleChar(11);
}
return;
}
if char == 9 {
if !outputChannel & textCursorX >= 320 {
printChar(0xd0a);
}
textCursorX = textCursorX + 8;
return;
}
if char == 10 {
textCursorY = textCursorY + 8;
if !outputChannel & textCursorY >= 240 {
textCursorY = 240 - 8;
let i: i32;
loop scroll_copy {
i!120 = i!(120 + 320 * 8);
branch_if (i := i + 4) < 320 * (240 - 8): scroll_copy;
}
rectangle(0 as f32, (240 - 8) as f32, 320 as f32, 8 as f32, bgColor);
}
return;
}
if char == 11 {
textCursorY = textCursorY - 8;
if !outputChannel & textCursorY < 0 {
textCursorY = 0;
let i = 320 * (240 - 8);
loop scroll_copy {
i!(116 + 320 * 8) = i!116;
branch_if (i := i - 4): scroll_copy;
}
rectangle(0 as f32, 0 as f32, 320 as f32, 8 as f32, bgColor);
}
return;
}
if char == 12 {
cls(bgColor);
return;
}
if char == 13 {
textCursorX = 0;
return;
}
if char == 14 {
bgColor = 0x12d20?1;
return;
}
if char == 15 {
textColor = 0x12d20?1;
return;
}
if char == 24 {
let tmp = textColor;
textColor = bgColor;
bgColor = tmp;
return;
}
if char == 31 {
textCursorX = 0x12d20?1 * (8 - outputChannel * 6);
textCursorY = 0x12d20?2 * (8 - outputChannel * 7);
return;
}
if char < 31 {
return;
}
drawChar(char);
}
data(0x12d00) {
i8(
1, 2, 1, 1, // 0-3
1, 1, 1, 1, // 4-7
1, 1, 1, 1, // 8-11
1, 1, 2, 2, // 12-15,
1, 1, 1, 1, // 16-19,
1, 1, 1, 1, // 20-23,
1, 1, 1, 1, // 24-27,
1, 1, 1, 3 // 28-31
)
}
fn drawChar(char: i32) {
if !outputChannel & textCursorX >= 320 {
printChar(0xd0a);
}
let y: i32;
loop rows {
let bits = (char * 8 + y)?0x13400;
let x = 0;
if outputChannel {
loop pixels {
if (bits := bits << 1) & 256 {
setPixel(textCursorX + x, textCursorY + y, textColor);
}
branch_if (x := x + 1) < 8: pixels;
}
} else {
loop pixels {
setPixel(textCursorX + x, textCursorY + y, select((bits := bits << 1) & 256, textColor, bgColor));
branch_if (x := x + 1) < 8: pixels;
}
}
branch_if (y := y + 1) < 8: rows;
}
textCursorX = textCursorX + 8;
}
export fn printString(ptr: i32) {
loop chars {
let lazy char = ptr?0;
if char {
printChar(char);
ptr = ptr + 1;
branch chars;
}
}
}
export fn printInt(num: i32) {
let lazy p = 0x12fff;
p?0 = 0;
if num < 0 {
printChar(45);
num = -num;
}
loop digits {
(p := p - 1)?0 = 48 + num #% 10;
branch_if (num := num #/ 10): digits;
}
printString(p);
}
export fn setTextColor(col: i32) {
textColor = col;
}
export fn setBackgroundColor(col: i32) {
bgColor = col;
}
export fn setCursorPosition(x: i32, y: i32) {
let lazy scale = select(outputChannel, 1, 8);
textCursorX = x * scale;
textCursorY = y * scale;
}
///////////
// SOUND //
///////////
include "ges.cwa"
export fn playNote(channel: i32, note: i32) {
(channel * 6)?80 = (channel * 6)?80 & 0xfe ^ if note {
(channel * 6)?83 = note & 127;
2 | !(note >> 7)
} else {
0
};
}
data 80 {
i8(
0x80, 0xc0, 0, 81, 0xa0, 0x50,
0xc4, 0, 0, 69, 0x60, 0x40,
0x44, 0xb0, 0, 69, 0x90, 0x43,
0x4, 0xf0, 0, 69, 0xa4, 0x44,
0xff, 0xff,
1, 1, 0, 100, 0, 100
)
}
///////////
// SETUP //
///////////
export fn endFrame() {
68!4 = 68!0;
}
start fn setup() {
let i: i32 = 12*16*3-1;
let avg: f32;
loop gradients {
let lazy scale = (i % 48) as f32 / 48 as f32;
let inline angle = i as f32 * (3.1416 / 1.5 - 3.1416 / (11.0 * 16.0 * 1.5));
let lazy c = 0.4 - cos(angle);
let inline ulimit = avg + 0.8;
let inline llimit = avg - 0.8;
let lazy a = max(llimit, min(ulimit, c)) * (scale + 0.05);
let lazy b = scale * scale * 0.8;
let inline v = (select(i < 11*16*3, max(0 as f32, min(a + b - a * b, 1 as f32)), scale) * 255 as f32) as i32;
(i%3 + i/3*4)?0x13000 = v;
avg = (avg + c) * 0.5;
branch_if i := i - 1: gradients;
}
i = 255;
loop expand_sweetie {
let lazy channel = i & 3;
let lazy index = i >> 2;
let lazy first_step = index >= 32;
let inline src1 = select(first_step, index % 32 / 2, index * 2);
let inline src2 = select(first_step, (index + 1) % 32 / 2, index * 2 + 1);
let inline c1 = (src1 * 4 + channel)?(0x13000+192*4);
let inline c2 = (src2 * 4 + channel)?(0x13000+192*4);
i?(0x13000+192*4) = (c1 + c2) * (3 + first_step) / 8;
branch_if (i := i - 1) >= 0: expand_sweetie;
}
memory.fill(0, 0, 64);
memory.fill(112, 0, 8);
memory.fill(0x14000, 0, 0x2c000);
cls(0);
randomSeed(random());
}
data 0x12c78 {
i32(80)
}
data 0x13000+192*4 {
i32(
0x2c1c1a,
0x5d275d,
0x533eb1,
0x577def,
0x75cdff,
0x70f0a7,
0x64b738,
0x797125,
0x6f3629,
0xc95d3b,
0xf6a641,
0xf7ef73,
0xf4f4f4,
0xc2b094,
0x866c56,
0x573c33
)
}
data 0x13400 {
file("../target/font.bin")
}
+2
View File
@@ -0,0 +1,2 @@
/binaries
/build
+56
View File
@@ -0,0 +1,56 @@
#!/bin/bash
set -e
cd $(dirname $0)
# build web runtime
pushd ../web
rm -rf .parcel-cache
yarn parcel build src/index.html
popd
rm -rf build
mkdir -p build/microw8-linux
mkdir build/microw8-macos
mkdir build/microw8-windows
# unzip binaries build by github actions
pushd binaries
unzip -o uw8-linux.zip
chmod +x uw8
mv uw8 ../build/microw8-linux
unzip -o uw8-macos.zip
chmod +x uw8
mv uw8 ../build/microw8-macos
unzip -o uw8-windows.zip
mv uw8.exe ../build/microw8-windows
popd
for dir in build/*; do
mkdir $dir/examples
for example in ../examples/curlywas/*.cwa; do
cp $example $dir/examples
done
for example in ../examples/wat/*.wat; do
cp $example $dir/examples
done
cp -r ../examples/include $dir/include
mkdir $dir/carts
for example in $dir/examples/*; do
build/microw8-linux/uw8 pack -l 9 $example $dir/carts/$(basename ${example%.*}).uw8
done
cp ../web/dist/index.html $dir/microw8.html
cp ../README.md $dir
cp ../UNLICENSE $dir
done
VERSION=$(build/microw8-linux/uw8 version)
cd build
tar czf microw8-$VERSION-linux.tgz microw8-linux
tar czf microw8-$VERSION-macos.tgz microw8-macos
zip -r -9 microw8-$VERSION-windows.zip microw8-windows
-4
View File
@@ -1,4 +0,0 @@
User-agent: *
Disallow:
Allow: /
Sitemap: https://exoticorn.github.io/microw8/sitemap.xml
View File
+1
View File
@@ -0,0 +1 @@
/public/
+24
View File
@@ -0,0 +1,24 @@
# The URL the site will be built for
base_url = "https://exoticorn.github.io/microw8"
# Whether to automatically compile all Sass files in the sass directory
compile_sass = true
# Whether to build a search index to be used later on by a JavaScript library
build_search_index = false
theme = "juice"
[markdown]
# Whether to do syntax highlighting
# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
highlight_code = true
highlight_theme = "ascetic-white"
[extra]
# Put all your custom variables here
juice_logo_name = "MicroW8"
juice_logo_path = "img/microw8.svg"
juice_extra_menu = [
{ title = "Github", link = "https://github.com/exoticorn/microw8" }
]
+81
View File
@@ -0,0 +1,81 @@
+++
+++
## About
MicroW8 is a WebAssembly based fantasy console inspired by the likes of [TIC-80](https://tic80.com/), [WASM-4](https://wasm4.org/) and [PICO-8](https://www.lexaloffle.com/pico-8.php).
The initial motivation behind MicroW8 was to explore whether there was a way to make WebAssembly viable for size-coding. (Size coding being the art of creating tiny (often <= 256 bytes) graphical effects and games.) The available examples so far are all in this space, however, I very carefully made sure that all design decisions make sense from the point of view of bigger projects as well.
## Specs
* Screen: 320x240, 256 colors, 60Hz
* Modules: Up to 256KB (WASM)
* Memory: 256KB
* Gamepad input (D-Pad + 4 Buttons)
For detailed [documentation see here](docs).
## Examples
* [Skip Ahead](v0.2.0#AgVfq24KI2Ok2o8qVtPYj27fSuGnfeSKgbOkIOsaEQMov8TDYQ6UjdjwkZrYcM1i9alo4/+Bhm1PRFEa0YHJlJAk/PGoc2K41rejv9ZSqJqIHNjr7cappqhOR2jT+jk+0b0+U6hO+geRCTP2aufWs7L+f/Z27NFY8LKlqPSv+C6Rd6+ohoKi6sYl5Kcrlf1cyTinV7jTTnmbcXWVDBA5rRKxAGMUTDS8rHxqSztRITOaQVP1pSdYgi/BDdOJOxSOIkeaId84S+Ycls5na7EgwSfVIpgqF+tcfkUecb8t2mQrXA7pyKrh/wzHn5N6Oe5aOgmzY2YpTIct) (249 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21, now with sound
* [Fireworks](v0.2.0#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
* [OhNoAnotherTunnel](v0.2.0#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==) (175 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
* [Technotunnel](v0.2.0#AhPXpq894LaUhp5+HQf39f39/Jc8g5zUrBSc0uyKh36ivskczhY84h55zL8gWpkdvKuRQI+KIt80isKzh8jkM8nILcx0RUvyk8yjE8TgNsgkcORVI0RY5k3qE4ySjaycxa2DVZH61UWZuLsCouuwT7I80TbmmetQSbMywJ/avrrCZIAH0UzQfvOiCJNG48NI0FFY1vjB7a7dcp8Uqg==) (157 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
* [Font & Palette](v0.2.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette.
Examplers for older versions:
* [Technotunnel B/W](v0.1pre2#AQrDAQHAAQIBfwp9A0AgAUEAsiABQcACb7JDmhkgQ5MiBCAEIASUIAFBwAJtQfgAa7IiBSAFlJKRIgaVIgcgByAAskHQD7KVIgIQAEPNzEw/lCIDlCAHIAeUIAOUIAOUQQGykiADIAOUk5GSIgiUIAOTQQqylCACkiIJqCAFIAaVIAiUQQqylCACkiIKqHMgCEEyspQgBpUiCyACkkEUspSocUEFcbJBArIgC5OUQRaylJeoOgB4IAFBAWoiAUGA2ARIDQALCw==) (199 bytes uncompressed): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final (older MicroW8 version with monochrome palette)
* [XorScroll](v0.1pre2#AQovAS0BAX8DQCABIAFBwAJvIABBCm1qIAFBwAJtczoAeCABQQFqIgFBgNgESA0ACws=) (50 bytes uncompressed): A simple scrolling XOR pattern. Fun fact: This is the pre-loaded effect when entering a bytebattle.
* [CircleWorm](v0.1pre2#AQp7AXkCAX8CfUEgEA0DQCABskEEspUiAkECspUgALJBiCeylSIDQQWylJIQAEEBspJBoAGylCACQQOylSADQQSylJIQAEEBspJB+ACylCADQRGylCACQQKylJIQAEECspJBELKUIAFBAmxBP2oQEiABQQFqIgFBP0gNAAsL) (126 bytes uncompressed): Just a test for the circle fill function.
## Versions
### v0.2.2
* [Web runtime](v0.2.2)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-windows.zip)
Changes:
* call `start` function after loading cart if the cart exports one
* fix `sndGes` having the wrong name and not being included in the auto imports
* fix control codes 4-6 (change text output mode) being invoked when used as parameters in other control sequences
* only open browser window once a cart was compiled sucessfully when running with `-b`
### v0.2.1
* [Web runtime](v0.2.1)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-windows.zip)
Changes:
* new gpu accelerated renderer with (optional) crt filter
* optimized `hline` function, a big speed-up when drawing large filled circles or rectangles
* print fractional size of packed `uw8` cart
### v0.2.0
* [Web runtime](v0.2.0)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-windows.zip)
Changes:
* [add sound support!](docs#sound)
* add support to redirect text output to the console for debugging using control code 6
* update curlywas:
* add support for `else if`
* add support for escape sequences in strings
* add support for char literals
* add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
* "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
### Older versions
[Find older versions here.](versions)
+614
View File
@@ -0,0 +1,614 @@
+++
title = "Docs"
description = "Docs"
+++
# Overview
MicroW8 loads WebAssembly modules with a maximum size of 256kb. You module needs to export
a function `fn upd()` which will be called once per frame.
After calling `upd` MicroW8 will display the 320x240 8bpp framebuffer located
at offset 120 in memory with the 32bpp palette located at 0x13000.
The memory has to be imported as `env` `memory` and has a maximum size of 256kb (4 pages).
If the module exports a function called `start`, it will be called once after the module is
loaded.
# Memory map
```
00000-00040: user memory
00040-00044: time since module start in ms
00044-0004c: gamepad state
0004c-00050: reserved
00050-00070: sound data (synced to sound thread)
00070-00078: reserved
00078-12c78: frame buffer
12c78-12c7c: sound registers/work area base address (for sndGes function)
12c7c-13000: reserved
13000-13400: palette
13400-13c00: font
13c00-14000: reserved
14000-40000: user memory
```
# API
All API functions are found in the `env` module.
## Math
These all do what you'd expect them to. All angles are in radians.
### fn asin(x: f32) -> f32
Returns the arcsine of `x`.
### fn acos(x: f32) -> f32
Returns the arccosine of `x`.
### fn atan(f32) -> f32
Returns the arctangent of `x`.
### fn atan2(y: f32, x: f32) -> f32
Returns the angle between the point `(x, y)` and the positive x-axis.
### fn sin(angle: f32) -> f32
Returns the sine of `angle`.
### fn tan(angle: f32) -> f32
Returns the tangent of `angle`.
### fn cos(angle: f32) -> f32
Returns the cosine of `angle`.
### fn exp(x: f32) -> f32
Returns `e^x`.
### fn log(x: f32) -> f32
Returns the natural logarithmus of `x`. Ie. `e^log(x) == x`.
### fn pow(x: f32, y: f32) -> f32
Returns `x^y`.
### fn fmod(x: f32, y: f32) -> f32
Returns `x` modulo `y`, ie. `x - floor(x / y) * y`. This means the sign of the result of `fmod` is the same as `y`.
## Random
MicroW8 provides a pretty good PRNG, namely xorshift64*. It is initialized to a constant seed at each startup, so if you
want to vary the random sequence you'll need to provide a seed yourself.
### fn random() -> i32
Returns a (pseudo-)random 32bit integer.
### fn randomf() -> f32
Returns a (pseudo-)random float equally distributed in `[0,1)`.
### fn randomSeed(seed: i32)
Seeds the PRNG with the given seed. The seed function is reasonably strong so that you can use
```
randomSeed(index);
random()
```
as a cheap random-access PRNG (aka noise function).
## Graphics
The default palette can be seen [here](../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ). (Press Z on the keyboard to switch to palette.)
The palette can be changed by writing 32bit rgba colors to addresses 0x13000-0x13400.
The drawing functions are sub-pixel accurate where applicable (line, circle). Pixel centers lie halfway between integer
coordinates. Ie. the top-left pixel covers the area `0,0 - 1,1`, with `0.5,0.5` being the pixel center.
### fn cls(color: i32)
Clears the screen to the given color index. Also sets the text cursor to `0, 0` and disables graphical text mode.
### fn setPixel(x: i32, y: i32, color: i32)
Sets the pixel at `x, y` to the given color index.
### fn getPixel(x: i32, y: i32) -> i32
Returns the color index at `x, y`. Returns `0` if the given coordinates are outside the screen.
### fn hline(left: i32, right: i32, y: i32, color: i32)
Fills the horizontal line `[left, right), y` with the given color index.
### fn rectangle(x: f32, y: f32, w: f32, h: f32, color: i32)
Fills the rectangle `x,y - x+w,y+h` with the given color index.
(Sets all pixels where the pixel center lies inside the rectangle.)
### fn circle(cx: f32, cy: f32, radius: f32, color: i32)
Fills the circle at `cx, cy` and with `radius` with the given color index.
(Sets all pixels where the pixel center lies inside the circle.)
### fn rectangle_outline(x: f32, y: f32, w: f32, h: f32, color: i32)
Draws a one pixel outline on the inside of the given rectangle.
(Draws the outermost pixels that are still inside the rectangle area.)
### fn circle_outline(cx: f32, cy: f32, radius: f32, color: i32)
Draws a one pixel outline on the inside of the given circle.
(Draws the outermost pixels that are still inside the circle area.)
### fn line(x1: f32, y1: f32, x2: f32, y2: f32, color: i32)
Draws a line from `x1,y1` to `x2,y2` in the given color index.
## Input
MicroW8 provides input from a gamepad with one D-Pad and 4 buttons, or a keyboard emulation thereof.
The buttons are numbered
| Button | Keyboard | Index |
| ------ | ----------- | ----- |
| Up | Arrow-Up | 0 |
| Down | Arrow-Down | 1 |
| Left | Arrow-Left | 2 |
| Right | Arrow-Right | 3 |
| A | Z | 4 |
| B | X | 5 |
| X | A | 6 |
| Y | S | 7 |
In addition to using the API functions below, the gamepad state can also be read as a bitfield of
pressed buttons at address 0x44. 0x48 holds the buttons that were pressed last frame.
### fn isButtonPressed(btn: i32) -> i32
Returns whether the buttons with the given index is pressed this frame.
### fn isButtonTriggered(btn: i32) -> i32
Returns whether the given button is newly pressed this frame.
### fn time() -> f32
Returns the time in seconds since the start of the cart.
The integer time in milliseconds can also be read at address 0x40.
## Text output
The default font can be seen [here](../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ).
The font can be changed by writing 1bpp 8x8 characters to addresses 0x13400-0x13c00.
All text printing is done at the cursor position, which is advanced after printing each character.
The cursor is not visible.
Text printing can operate in two modes - normal and graphics. After startup and after `cls()` normal mode is active.
### Normal mode
In normal mode, text printing is constrained to an 8x8 character grid. Setting the cursor position to `2,3` will start printing at pixel coordinates `16,24`.
When printing characters, the full 8x8 pixels are painted with the text and background colors according to the character graphics in the font.
When moving/printing past the left or right border the cursor will automatically wrap to the previous/next line. When moving/printing past the upper/lower border, the screen will be scrolled down/up 8 pixels, filling the fresh line with the background color.
### Graphics mode
In graphics mode, text can be printed to any pixel position, the cursor position is set in pixel coordinates.
When printing characters only the foreground pixels are set, the background is "transparent".
Moving/printing past any border does not cause any special operation, the cursor just goes off-screen.
### Control chars
Characters 0-31 are control characters and don't print by default. They take the next 0-2 following characters as parameters.
Avoid the reserved control chars, they are currently NOPs but their behavior can change in later MicroW8 versions.
| Code | Parameters | Operation |
| ----- | ---------- | ------------------------------------------ |
| 0 | - | Nop |
| 1 | char | Print char (including control chars) |
| 2-3 | - | Reserved |
| 4 | - | Switch to normal mode, reset cursor to 0,0 |
| 5 | - | Switch to graphics mode |
| 6 | - | Switch output to (debug) console |
| 7 | - | Bell / trigger sound channel 0 |
| 8 | - | Move cursor left |
| 9 | - | Move cursor right |
| 10 | - | Move cursor down |
| 11 | - | Move cursor up |
| 12 | - | do `cls(background_color)` |
| 13 | - | Move cursor to the left border |
| 14 | color | Set the background color |
| 15 | color | Set the text color |
| 16-23 | - | Reserved |
| 24 | - | Swap text/background colors |
| 25-30 | - | Reserved |
| 31 | x, y | Set cursor position (*) |
(*) In graphics mode, the x coordinate is doubled when using control char 31 to be able to cover the whole screen with one byte.
#### Debug output
Control code 6 switches all text output (except codes 4 and 5 to switch output back to the screen) to the console. Where exactly this ends
up (if at all) is an implementation detail of the runtimes. The native dev-runtime writes the debug output to `stdout`, the web runtime to
the debug console using `console.log`. Both implementation buffer the output until they encounter a newline character (10) in the output stream.
There may be future runtimes that ignore the debug output completely.
In CurlyWas, a simple way to log some value might look like this:
```
printChar('\06V: '); // switch to console out, print some prefix
printInt(some_value);
printChar('\n\4'); // newline and switch back to screen
```
### fn printChar(char: i32)
Prints the character in the lower 8 bits of `char`. If the upper 24 bits are non-zero, right-shifts `char` by 8 bits and loops back to the beginning.
### fn printString(ptr: i32)
Prints the zero-terminated string at the given memory address.
### fn printInt(num: i32)
Prints `num` as a signed decimal number.
### fn setTextColor(color: i32)
Sets the text color.
### fn setBackgroundColor(color: i32)
Sets the background color.
### fn setCursorPosition(x: i32, y: i32)
Sets the cursor position. In normal mode `x` and `y` are multiplied by 8 to get the pixel position, in graphics mode they are used as is.
## Sound
### Low level operation
MicroW8 actually runs two instances of your module. On the first instance, it calls `upd` and displays the framebuffer found in its memory. On the
second instance, it calls `snd` instead with an incrementing sample index and expects that function to return sound samples for the left and right
channel at 44100 Hz. If your module does not export a `snd` function, it calls the api function `sndGes` instead.
As the only means of communication, 32 bytes starting at address 0x00050 are copied from main to sound memory after `upd` returns.
By default, the `sndGes` function generates sound based on the 32 bytes at 0x00050. This means that in the default configuration those 32 bytes act
as sound registers. See the `sndGes` function for the meaning of those registers.
### export fn snd(sampleIndex: i32) -> f32
If the module exports a `snd` function, it is called 88200 times per second to provide PCM sample data for playback (44.1kHz stereo).
The `sampleIndex` will start at 0 and increments by 1 for each call. On even indices the function is expected to return a sample value for
the left channel, on odd indices for the right channel.
### fn playNote(channel: i32, note: i32)
Triggers a note (1-127) on the given channel (0-3). Notes are semitones with 69 being A4 (same as MIDI). A note value of 0 stops the
sound playing on that channel. A note value 128-255 will trigger note-128 and immediately stop it (playing attack+release parts of envelope).
This function assumes the default setup, with the `sndGes` registers located at 0x00050.
### fn sndGes(sampleIndex: i32) -> f32
This implements a sound chip, generating sound based on 32 bytes of sound registers.
The spec of this sound chip are:
- 4 channels with individual volume control (0-15)
- rect, saw, tri, noise wave forms selectable per channel
- each wave form supports some kind of pulse width modulation
- each channel has an optional automatic low pass filter, or can be sent to one of two manually controllable filters
- each channel can select between a narrow and a wide stereo positioning. The two stereo positions of each channel are fixed.
- optional ring modulation
This function requires 1024 bytes of working memory, the first 32 bytes of which are interpreted as the sound registers.
The base address of its working memory can be configured by writing the address to 0x12c78. It defaults to 0x00050.
Here is a short description of the 32 sound registers.
```
00 - CTRL0
06 - CTRL1
0c - CTRL2
12 - CTRL3
| 7 6 | 5 | 4 | 3 2 | 1 | 0 |
| wave | ring | wide | filter | trigger | note on |
note on: stay in decay/sustain part of envelope
trigger: the attack part of the envlope is triggered when either this changes
or note on is changed from 0 to 1.
filter : 0 - no filter
1 - fixed 6db 1-pole filter with cutoff two octaves above note
2 - programmable filter 0
3 - programmable filter 1
wide : use wide stereo panning
ring : ring modulate with triangle wave at frequency of previous channel
wave : 0 - rectangle
1 - saw
2 - triangle
3 - noise
01 - PULS0
07 - PULS1
0d - PULS2
13 - PULS3
Pulse width 0-255, with 0 being the plain version of each wave form.
rectangle - 50%-100% pulse width
saw - inverts 0%-100% of the saw wave form around the center
triangle - morphs into an octave up triangle wave
noise - blends into a decimated saw wave (just try it out)
02 - FINE0
08 - FINE1
0e - FINE2
14 - FINE3
Fractional note
03 - NOTE0
09 - NOTE1
0f - NOTE2
15 - NOTE3
Note, 69 = A4
04 - ENVA0
0a - ENVA1
10 - ENVA2
16 - ENVA3
| 7 6 5 4 | 3 2 1 0 |
| decay | attack |
05 - ENVB0
0b - ENVB1
11 - ENVB2
17 - ENVB3
| 7 6 5 4 | 3 2 1 0 |
| release | sustain |
18 - VO01
| 7 6 5 4 | 3 2 1 0 |
| volume 1 | volume 0 |
19 - VO23
| 7 6 5 4 | 3 2 1 0 |
| volume 3 | volume 2 |
1a - FCTR0
1b - FCTR1
| 7 6 5 4 | 3 | 2 | 1 | 0 |
| resonance | 0 | band | high | low |
1c - FFIN0
1e - FFIN1
cutoff frequency - fractional note
1d - FNOT0
1f - FNOT1
cutoff frequency - note
```
# The `uw8` tool
The `uw8` tool included in the MicroW8 download includes a number of useful tools for developing MicroW8 carts. For small productions written in
wat or CurlyWas you don't need anything apart from `uw8` and a text editor of your choice.
## `uw8 run`
Usage:
`uw8 run [<options>] <file>`
Runs `<file>` which can be a binary WebAssembly module, an `.uw8` cart, a wat (WebAssembly text format) source file or a [CurlyWas](https://github.com/exoticorn/curlywas) source file.
Options:
* `-b`, `--browser`: Run in browser instead of using native runtime
* `-t FRAMES`, `--timeout FRAMES`: Sets the timeout in frames (1/60s). If the start or update function runs longer than this it is forcibly interupted
and execution of the cart is stopped. Defaults to 30 (0.5s)
* `-w`, `--watch`: Reloads the given file every time it changes on disk.
* `-p`, `--pack`: Pack the file into an `.uw8` cart before running it and print the resulting size.
* `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing.
* `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow.
* `-o FILE`, `--output FILE`: Write the loaded and optionally packed cart back to disk.
when using the native runtime:
* `-m`, `--no-audio`: Disable audio, also reduces cpu load a bit
* `--no-gpu`: Force old cpu-only window code
* `--filter FILTER`: Select an upscale filter at startup
* `--fullscreen`: Start in fullscreen mode
Note that the cpu-only window does not support fullscreen nor upscale filters.
Unless --no-gpu is given, uw8 will first try to open a gpu accelerated window, falling back to the old cpu-only window if that fails.
Therefore you should rarely need to manually pass --no-gpu. If you prefer the old pixel doubling look to the now default crt filter,
you can just pass `--filter nearest` or `--filter 1`.
The upscale filter options are:
```
1, nearest : Anti-aliased nearest filter
2, fast_crt : Very simple, cheap crt filter, not very good below a window size of 960x720
3, ss_crt : Super sampled crt filter, a little more demanding on the GPU but scales well to smaller window sizes
4, chromatic_crt : Variant of fast_crt with a slight offset of the three color dots of a pixel, still pretty cheap
5, auto_crt (default) : ss_crt below 960x720, chromatic_crt otherwise
```
You can switch the upscale filter at any time using the keys 1-5. You can toggle fullscreen with F.
## `uw8 pack`
Usage:
`uw8 pack [<options>] <infile> <outfile>`
Packs the WebAssembly module or text file, or [CurlyWas](https://github.com/exoticorn/curlywas) source file into a `.uw8` cart.
Options:
* `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing.
* `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow.
## `uw8 unpack`
Usage:
`uw8 unpack <infile> <outfile>`
Unpacks a MicroW8 module into a standard WebAssembly module.
## `uw8 compile`
Usage:
`uw8 compile [<options>] <infile> <outfile>`
Compiles a [CurlyWas](https://github.com/exoticorn/curlywas) source file to a standard WebAssembly module. Most useful together with
the `--debug` option to get a module that works well in the Chrome debugger.
Options:
* `-d`, `--debug`: Generate a name section to help debugging
## `uw8 filter-exports`
Usage:
`uw8 filter-exports <infile> <outfile>`
Reads a binary WebAssembly module, removes all exports not used by the MicroW8 platform + everything that is unreachable without those exports and writes the resulting module to `outfile`.
When compiling C code (or Rust, zig or others) to WebAssembly, you end up with a few exported global variables that are used for managing the heap and C stack, even if the code doesn't actually use those features. You can use this command to automatically remove them and gain a few bytes. See the C, Rust and zig examples in the MicroW8 repository.
# Other useful tools
The [Web Assembly Binary Toolkit](https://github.com/WebAssembly/wabt) includes
a few useful tools, eg. `wat2wasm` to compile the WebAssemby text format to binary
wasm and `wasm2wat` to disassemble wasm binaries.
[Binaryen](https://github.com/WebAssembly/binaryen) includes `wasm-opt` which enable additional optimizations over what LLVM (the backend that is used by most compilers that target WebAssembly) can do.
# Distribution
The classical distribution option is just to put the `.uw8` cart into a zip file, let people run it themselves, either in the `uw8` tool or in the web runtime.
If you want to go this way, you might consider including `microw8.html` in your download. It's specifically designed to be a small (~10KB at the moment), self-contained HTML file for just this reason. That way, anyone who has downloaded you production can run it, even when offline, provided they have a modern web browser at hand. Also, should future versions of MicroW8 ever introduce any kind of incompatibilities, they'd still have a compatible version right there without hunting arround for an old version.
## Base64 encoded link
For small productions (<= 1024 bytes), when you load them in the web runtime, the URL is automatically updated to include the cart as base64 encoded data. You can just give that URL to others for them to run your prod.
## url parameter
Another option is to put the cart on a webserver and add `#url=url/to/the/cart.uw8` to the end of the web runtime URL. ([Like this](../v0.1pre5#url=../uw8/skipahead.uw8))
If the cart and the web runtime are on different domains, you'll have to make sure that [CORS header](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#the_http_response_headers) are enabled for the cart, otherwise the web runtime won't be able to load it.
Feel free to put the web runtime on your own server if it makes sense to you, its [license](https://unlicense.org/) allows you to do anything you want with it.
## `.html` + `.uw8`
At startup the web runtime will try to load a cart in the same directory as the `.html` file. If the URL of the web runtime ends in `.html` it will try to load a cart with the same name and the extension `.uw8`. If the URL of the web runtime ends in a `/` it will try to load a `cart.uw8` at that location.
So, you could for example serve the web runtime as `https://example.org/mytunnel.html` and the cart as `https://example.org/mytunnel.uw8` and send people to the HTML page to run the cart. Or you could put them up as `https://example.org/mytunnel/index.html` and `https://example.org/mytunnel/cart.uw8` and send people to `https://example.org/mytunnel`.
If a cart is found and loaded in this way, the load button is hidden.
## Itch.io
The above `.html` + `.uw8` option works great on [Itch.io](https://itch.io) as well. Put these two files into a zip archive:
* `index.html`: a copy of the web runtime (`microw8.html` in the MicroW8 download)
* `index.uw8`: Your game cart
Upload the zip file to itch.io and make sure to set the embedded viewport size to exactly (!) 640x480 pixel. At that exact size the web runtime hides everything except for the MicroW8 screen.
If instead you actually *want* to display the border around the screen and the byte size you can try a size of about 720x620.
[See here for an example upload.](https://exoticorn.itch.io/skipahead)
# `.uw8` format
The first byte of the file specifies the format version:
## Format version `00`:
This file is simply a standard WebAssembly module
## Format version `01`:
The rest of this file is the same as a WebAssembly
module with the 8 byte header removed. This module
can leave out sections which are then taken from
a base module provided by MicroW8.
You can generate this base module yourself using
`uw8-tool`. As a quick summary, it provides all function
types with up to 5 parameters (i32 or f32) where the
`f32` parameters always preceed the `i32` parameters.
Then it includes all imports that MicroW8 provides,
a function section with a single function of type
`() -> void` and an export section that exports
the first function in the file under the name `upd`.
## Format version `02`:
Same as version `01` except everything after the first byte is compressed
using a [custom LZ compression scheme](https://github.com/exoticorn/upkr).
# The web runtime
Load carts into the web runtime either by using the "Load cart..." button, or by dragging the file
onto the screen area.
## Input
For input, you can either use a standard gamepad or keyboard. On a keyboard use the arrow keys and the keys Z, X, A and S to emulate the A, B, X and Y buttons.
## Video recording
Press F10 to start recording, press again to stop. Then a download dialog will open for the video file.
The file might miss some metadata needed to load in some video editing tools, in that case you can run
it through ffmpeg like this `ffmpeg -i IN_NAME.webm -c copy -o OUT_NAME.webm to fix it up.
To convert it to 1280x720, for example for a lovebyte upload, you can use:
```
ffmpeg -i IN.webm -vf "scale=960:720:flags=neighbor,pad=1280:720:160:0" -r 60 OUT.mp4
```
## Screenshot
Pressing F9 opens a download dialog with a screenshot.
## Devkit mode
Append `#devkit` to the web runtime url in order to switch to devkit mode. In devkit mode, standard web assembly modules
are loaded bypassing the loader, removing all size restrictions. At the same time, the memory limit is increased to 1GB.
+105
View File
@@ -0,0 +1,105 @@
+++
description = "Versions"
+++
### v0.2.0
* [Web runtime](v0.2.0)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-windows.zip)
Changes:
* [add sound support!](docs#sound)
* add support to redirect text output to the console for debugging using control code 6
* update curlywas:
* add support for `else if`
* add support for escape sequences in strings
* add support for char literals
* add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
* "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
### v0.2.0-rc3
* [Web runtime](v0.2.0-rc3)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-windows.zip)
Changes:
* improve timing stability some more. essentially now guaranteeing that "frame = time_ms * 6 / 100" returns
consecutive frame numbers, provided the module can be run at 60 fps
* add support to redirect text output to the console for debugging using control code 6
* update curlywas:
* add support for `else if`
* add support for escape sequences in strings
* add support for char literals
* add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
### v0.2.0-rc2
* [Web runtime](v0.2.0-rc2)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-windows.zip)
Changes:
* fix timing issues of sound playback, especially on systems with large sound buffers
### v0.2.0-rc1
* [Web runtime](v0.2.0-rc1)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc1/microw8-0.2.0-rc1-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc1/microw8-0.2.0-rc1-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc1/microw8-0.2.0-rc1-windows.zip)
Changes:
* [add sound support](docs#sound)
* "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
Known issues:
* timing accuracy/update frequency of sound support currently depends on sound buffer size
### v0.1.2
* [Web runtime](v0.1.2)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip)
Changes:
* add option to `uw8 run` to run the cart in the browser using the web runtime
* CurlyWas: implement `include` support
* CurlyWas: implement support for constants
* fix crash when trying to draw zero sized line
### v0.1.1
* [Web runtime](v0.1.1)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-windows.zip)
Changes:
* implement more robust file watcher
* add basic video recording on F10 in web runtime
* add screenshot on F9
* add watchdog to interrupt hanging update in native runtime
* add devkit mode to web runtime
* add unpack and compile commands to uw8
* add support for table/element section in pack command
* disable wayland support (caused missing window decorations in gnome)
### v0.1.0
* [Web runtime](v0.1.0)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-windows.zip)

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

+15
View File
@@ -0,0 +1,15 @@
<style>
:root {
/* Primary theme color */
--primary-color: #202024;
/* Primary theme text color */
--primary-text-color: #808070;
/* Primary theme link color */
--primary-link-color: #8080a0;
/* Secondary color: the background body color */
--secondary-color: #e0e0e8;
--secondary-text-color: #1a1818;
/* Highlight text color of table of content */
--toc-highlight-text-color: #d46e13;
}
</style>
+18
View File
@@ -0,0 +1,18 @@
{% extends "juice/templates/index.html" %}
{% block hero %}
<div>
<section>
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
</section>
<a href="v0.2.2">
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
</a>
</div>
<div class="explore-more text"
onclick="document.getElementById('features').scrollIntoView({behavior: 'smooth'})">
Explore More ⇩
</div>
{% endblock hero %}
{% block footer %}
{% endblock footer %}
+1
Submodule site/themes/juice added at a5eb57d278
-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://exoticorn.github.io/microw8/</loc>
</url>
<url>
<loc>https://exoticorn.github.io/microw8/docs/</loc>
</url>
<url>
<loc>https://exoticorn.github.io/microw8/versions/</loc>
</url>
</urlset>
+73
View File
@@ -0,0 +1,73 @@
use anyhow::{anyhow, bail, Result};
use notify_debouncer_mini::{
new_debouncer,
notify::{self, RecommendedWatcher},
DebouncedEvent, DebouncedEventKind, Debouncer,
};
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
pub struct FileWatcher {
debouncer: Debouncer<RecommendedWatcher>,
watched_files: BTreeSet<PathBuf>,
directories: BTreeSet<PathBuf>,
rx: mpsc::Receiver<DebouncedEvent>,
}
impl FileWatcher {
pub fn new() -> Result<FileWatcher> {
let (tx, rx) = mpsc::channel();
let debouncer = new_debouncer(Duration::from_millis(100), None, move |res| match res {
Ok(events) => {
for event in events {
let _ = tx.send(event);
}
}
Err(errs) => {
for err in errs {
eprintln!("Error watching for file changes: {err}");
}
}
})?;
Ok(FileWatcher {
debouncer,
watched_files: BTreeSet::new(),
directories: BTreeSet::new(),
rx,
})
}
pub fn add_file<P: Into<PathBuf>>(&mut self, path: P) -> Result<()> {
let path = path.into();
let parent = path.parent().ok_or_else(|| anyhow!("File has no parent"))?;
if !self.directories.contains(parent) {
self.debouncer
.watcher()
.watch(parent, notify::RecursiveMode::NonRecursive)?;
self.directories.insert(parent.to_path_buf());
}
self.watched_files.insert(path);
Ok(())
}
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
match self.rx.try_recv() {
Ok(event) => match event.kind {
DebouncedEventKind::Any => {
let handle = same_file::Handle::from_path(&event.path)?;
for file in &self.watched_files {
if handle == same_file::Handle::from_path(file)? {
return Ok(Some(event.path));
}
}
}
_ => (),
},
Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"),
_ => (),
}
Ok(None)
}
}
+19
View File
@@ -0,0 +1,19 @@
mod filewatcher;
#[cfg(feature = "native")]
mod run_native;
#[cfg(feature = "browser")]
mod run_web;
pub use filewatcher::FileWatcher;
#[cfg(feature = "native")]
pub use run_native::MicroW8;
#[cfg(feature = "browser")]
pub use run_web::RunWebServer;
use anyhow::Result;
pub trait Runtime {
fn is_open(&self) -> bool;
fn load(&mut self, module_data: &[u8]) -> Result<()>;
fn run_frame(&mut self) -> Result<()>;
}
+314
View File
@@ -0,0 +1,314 @@
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process;
use anyhow::Result;
use pico_args::Arguments;
#[cfg(feature = "native")]
use uw8::MicroW8;
#[cfg(feature = "browser")]
use uw8::RunWebServer;
#[cfg(any(feature = "native", feature = "browser"))]
use uw8::Runtime;
fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default()).init();
let mut args = Arguments::from_env();
// try to enable ansi support in win10 cmd shell
#[cfg(target_os = "windows")]
let _ = ansi_term::enable_ansi_support();
match args.subcommand()?.as_deref() {
Some("version") => {
println!("{}", env!("CARGO_PKG_VERSION"));
Ok(())
}
#[cfg(any(feature = "native", feature = "browser"))]
Some("run") => run(args),
Some("pack") => pack(args),
Some("unpack") => unpack(args),
Some("compile") => compile(args),
Some("filter-exports") => filter_exports(args),
Some("help") | None => {
println!("uw8 {}", env!("CARGO_PKG_VERSION"));
println!();
println!("Usage:");
#[cfg(any(feature = "native", feature = "browser"))]
println!(" uw8 run [-t/--timeout <frames>] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>");
println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>");
println!(" uw8 unpack <in-file> <out-file>");
println!(" uw8 compile [-d/--debug] <in-file> <out-file>");
println!(" uw8 filter-exports <in-wasm> <out-wasm>");
Ok(())
}
Some(other) => {
eprintln!("Unknown command '{}'", other);
process::exit(1);
}
}
}
#[cfg(any(feature = "native", feature = "browser"))]
fn run(mut args: Arguments) -> Result<()> {
let watch_mode = args.contains(["-w", "--watch"]);
#[allow(unused)]
let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?;
let mut config = Config::default();
if args.contains(["-p", "--pack"]) {
let mut pack = uw8_tool::PackConfig::default();
if args.contains(["-u", "--uncompressed"]) {
pack = pack.uncompressed();
}
if let Some(level) = args.opt_value_from_str(["-l", "--level"])? {
pack = pack.with_compression_level(level);
}
config.pack = Some(pack);
}
if let Some(path) =
args.opt_value_from_os_str::<_, _, bool>(["-o", "--output"], |s| Ok(s.into()))?
{
config.output_path = Some(path);
}
#[cfg(feature = "native")]
let run_browser = args.contains(["-b", "--browser"]);
#[cfg(not(feature = "native"))]
let run_browser = args.contains(["-b", "--browser"]) || true;
#[allow(unused)]
let disable_audio = args.contains(["-m", "--no-audio"]);
#[cfg(feature = "native")]
let window_config = {
let mut config = uw8_window::WindowConfig::default();
if !run_browser {
config.parse_arguments(&mut args);
}
config
};
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let mut watcher = uw8::FileWatcher::new()?;
use std::process::exit;
let mut runtime: Box<dyn Runtime> = if !run_browser {
#[cfg(not(feature = "native"))]
unimplemented!();
#[cfg(feature = "native")]
{
let mut microw8 = MicroW8::new(timeout, window_config)?;
if disable_audio {
microw8.disable_audio();
}
Box::new(microw8)
}
} else {
#[cfg(not(feature = "browser"))]
unimplemented!();
#[cfg(feature = "browser")]
Box::new(RunWebServer::new())
};
let mut first_run = true;
while runtime.is_open() {
if first_run || watcher.poll_changed_file()?.is_some() {
let (result, dependencies) = start_cart(&filename, &mut *runtime, &config);
if watch_mode {
for dep in dependencies {
watcher.add_file(dep)?;
}
}
if let Err(err) = result {
eprintln!("Load error: {}", err);
if !watch_mode {
exit(1);
}
}
first_run = false;
}
if let Err(err) = runtime.run_frame() {
eprintln!("Runtime error: {}", err);
if !watch_mode {
exit(1);
}
}
}
Ok(())
}
#[derive(Default)]
struct Config {
pack: Option<uw8_tool::PackConfig>,
output_path: Option<PathBuf>,
}
fn load_cart(filename: &Path, config: &Config) -> (Result<Vec<u8>>, Vec<PathBuf>) {
let mut dependencies = Vec::new();
fn inner(filename: &Path, config: &Config, dependencies: &mut Vec<PathBuf>) -> Result<Vec<u8>> {
let mut cart = match SourceType::of_file(filename)? {
SourceType::Binary => {
let mut cart = vec![];
File::open(filename)?.read_to_end(&mut cart)?;
cart
}
SourceType::Wat => {
let cart = wat::parse_file(filename)?;
cart
}
SourceType::CurlyWas => {
let (module, deps) = curlywas::compile_file(filename, curlywas::Options::default());
*dependencies = deps;
module?
}
};
if let Some(ref pack_config) = config.pack {
cart = uw8_tool::pack(&cart, pack_config)?;
println!(
"\npacked size: {} bytes ({:.2})",
cart.len(),
uw8_tool::compressed_size(&cart)
);
}
if let Some(ref path) = config.output_path {
File::create(path)?.write_all(&cart)?;
}
Ok(cart)
}
let result = inner(filename, config, &mut dependencies);
if dependencies.is_empty() {
dependencies.push(filename.to_path_buf());
}
(result, dependencies)
}
enum SourceType {
Binary,
Wat,
CurlyWas,
}
impl SourceType {
fn of_file(filename: &Path) -> Result<SourceType> {
if let Some(extension) = filename.extension() {
if extension == "uw8" || extension == "wasm" {
return Ok(SourceType::Binary);
} else if extension == "wat" || extension == "wast" {
return Ok(SourceType::Wat);
} else if extension == "cwa" {
return Ok(SourceType::CurlyWas);
}
}
let mut cart = vec![];
File::open(filename)?.read_to_end(&mut cart)?;
let ty = if cart[0] < 10 {
SourceType::Binary
} else {
let src = String::from_utf8(cart)?;
if src.chars().find(|&c| !c.is_whitespace() && c != ';') == Some('(') {
SourceType::Wat
} else {
SourceType::CurlyWas
}
};
Ok(ty)
}
}
#[cfg(any(feature = "native", feature = "browser"))]
fn start_cart(
filename: &Path,
runtime: &mut dyn Runtime,
config: &Config,
) -> (Result<()>, Vec<PathBuf>) {
let (cart, dependencies) = load_cart(filename, config);
let cart = match cart {
Ok(cart) => cart,
Err(err) => return (Err(err), dependencies),
};
if let Err(err) = runtime.load(&cart) {
eprintln!("Load error: {}", err);
(Err(err), dependencies)
} else {
(Ok(()), dependencies)
}
}
fn pack(mut args: Arguments) -> Result<()> {
let mut pack_config = uw8_tool::PackConfig::default();
if args.contains(["-u", "--uncompressed"]) {
pack_config = pack_config.uncompressed();
}
if let Some(level) = args.opt_value_from_str(["-l", "--level"])? {
pack_config = pack_config.with_compression_level(level);
}
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let cart = load_cart(
&in_file,
&Config {
pack: Some(pack_config),
output_path: None,
},
)
.0?;
File::create(out_file)?.write_all(&cart)?;
Ok(())
}
fn unpack(mut args: Arguments) -> Result<()> {
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
uw8_tool::unpack_file(&in_file, &out_file)
}
fn compile(mut args: Arguments) -> Result<()> {
let mut options = curlywas::Options::default();
if args.contains(["-d", "--debug"]) {
options = options.with_debug();
}
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let module = curlywas::compile_file(in_file, options).0?;
File::create(out_file)?.write_all(&module)?;
Ok(())
}
fn filter_exports(mut args: Arguments) -> Result<()> {
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
uw8_tool::filter_exports(&in_file, &out_file)?;
Ok(())
}
+1
View File
File diff suppressed because one or more lines are too long
+481
View File
@@ -0,0 +1,481 @@
use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration;
use std::{thread, time::Instant};
use anyhow::{anyhow, Result};
use cpal::traits::*;
use rubato::Resampler;
use uw8_window::{Window, WindowConfig};
use wasmtime::{
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
};
pub struct MicroW8 {
window: Window,
stream: Option<cpal::Stream>,
engine: Engine,
loader_module: Module,
disable_audio: bool,
module_data: Option<Vec<u8>>,
timeout: u32,
instance: Option<UW8Instance>,
}
struct UW8Instance {
store: Store<()>,
memory: Memory,
end_frame: TypedFunc<(), ()>,
update: Option<TypedFunc<(), ()>>,
start_time: Instant,
watchdog: Arc<Mutex<UW8WatchDog>>,
sound_tx: Option<mpsc::SyncSender<RegisterUpdate>>,
}
impl Drop for UW8Instance {
fn drop(&mut self) {
if let Ok(mut watchdog) = self.watchdog.lock() {
watchdog.stop = true;
}
}
}
struct UW8WatchDog {
engine: Engine,
stop: bool,
}
impl MicroW8 {
pub fn new(timeout: Option<u32>, window_config: WindowConfig) -> Result<MicroW8> {
let mut config = wasmtime::Config::new();
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
if timeout.is_some() {
config.epoch_interruption(true);
}
let engine = wasmtime::Engine::new(&config)?;
let loader_module =
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
let window = Window::new(window_config)?;
Ok(MicroW8 {
window,
stream: None,
engine,
loader_module,
disable_audio: false,
module_data: None,
timeout: timeout.unwrap_or(0),
instance: None,
})
}
pub fn disable_audio(&mut self) {
self.disable_audio = true;
}
}
impl super::Runtime for MicroW8 {
fn is_open(&self) -> bool {
self.window.is_open()
}
fn load(&mut self, module_data: &[u8]) -> Result<()> {
self.stream = None;
self.instance = None;
let mut store = wasmtime::Store::new(&self.engine, ());
store.set_epoch_deadline(60);
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
let mut linker = wasmtime::Linker::new(&self.engine);
linker.define("env", "memory", memory)?;
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
let load_uw8 = loader_instance.get_typed_func::<i32, i32>(&mut store, "load_uw8")?;
let platform_data = include_bytes!("../platform/bin/platform.uw8");
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
let platform_length =
load_uw8.call(&mut store, platform_data.len() as i32)? as u32 as usize;
let platform_module =
wasmtime::Module::new(&self.engine, &memory.data(&store)[..platform_length])?;
memory.data_mut(&mut store)[..module_data.len()].copy_from_slice(module_data);
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
add_native_functions(&mut linker, &mut store)?;
let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
engine: self.engine.clone(),
stop: false,
}));
{
let watchdog = watchdog.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(17));
if let Ok(watchdog) = watchdog.lock() {
if watchdog.stop {
break;
}
watchdog.engine.increment_epoch();
} else {
break;
}
});
}
let instance = linker.instantiate(&mut store, &module)?;
let end_frame = platform_instance.get_typed_func::<(), ()>(&mut store, "endFrame")?;
let update = instance.get_typed_func::<(), ()>(&mut store, "upd").ok();
if let Some(start) = instance.get_typed_func::<(), ()>(&mut store, "start").ok() {
start.call(&mut store, ())?;
}
let (sound_tx, stream) = if self.disable_audio {
(None, None)
} else {
match init_sound(&self.engine, &platform_module, &module) {
Ok(sound) => {
sound.stream.play()?;
(Some(sound.tx), Some(sound.stream))
}
Err(err) => {
eprintln!("Failed to init sound: {}", err);
(None, None)
}
}
};
self.instance = Some(UW8Instance {
store,
memory,
end_frame,
update,
start_time: Instant::now(),
watchdog,
sound_tx,
});
self.stream = stream;
self.module_data = Some(module_data.into());
Ok(())
}
fn run_frame(&mut self) -> Result<()> {
let input = self.window.begin_frame();
if input.reset {
if let Some(module_data) = self.module_data.take() {
self.load(&module_data)?;
}
}
let now = Instant::now();
let mut result = Ok(());
if let Some(mut instance) = self.instance.take() {
let time = (now - instance.start_time).as_millis() as i32;
let next_frame = {
let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6;
let max = now + Duration::from_millis(17);
let next_center = now + Duration::from_millis((16 - offset) as u64);
next_center.min(max)
};
{
let mem = instance.memory.data_mut(&mut instance.store);
mem[64..68].copy_from_slice(&time.to_le_bytes());
mem[68..72].copy_from_slice(&input.gamepads);
}
instance.store.set_epoch_deadline(self.timeout as u64);
if let Some(ref update) = instance.update {
if let Err(err) = update.call(&mut instance.store, ()) {
result = Err(err);
}
}
instance.end_frame.call(&mut instance.store, ())?;
let memory = instance.memory.data(&instance.store);
let mut sound_regs = [0u8; 32];
sound_regs.copy_from_slice(&memory[80..112]);
if let Some(ref sound_tx) = instance.sound_tx {
let _ = sound_tx.send(RegisterUpdate {
time,
data: sound_regs,
});
}
let framebuffer_mem = &memory[120..(120 + 320 * 240)];
let palette_mem = &memory[0x13000..];
self.window
.end_frame(framebuffer_mem, palette_mem, next_frame);
if result.is_ok() {
self.instance = Some(instance);
}
}
result?;
Ok(())
}
}
fn add_native_functions(
linker: &mut wasmtime::Linker<()>,
store: &mut wasmtime::Store<()>,
) -> Result<()> {
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
linker.func_wrap("env", "asin", |v: f32| v.asin())?;
linker.func_wrap("env", "atan", |v: f32| v.atan())?;
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
linker.func_wrap("env", "log", |v: f32| v.ln())?;
linker.func_wrap("env", "sin", |v: f32| v.sin())?;
linker.func_wrap("env", "tan", |v: f32| v.tan())?;
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
for i in 10..64 {
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
}
let log_line = std::sync::Mutex::new(String::new());
linker.func_wrap("env", "logChar", move |c: i32| {
let mut log_line = log_line.lock().unwrap();
if c == 10 {
println!("{}", log_line);
log_line.clear();
} else {
log_line.push(c as u8 as char);
}
})?;
for i in 0..16 {
linker.define(
"env",
&format!("g_reserved{}", i),
wasmtime::Global::new(
&mut *store,
GlobalType::new(ValType::I32, Mutability::Const),
0.into(),
)?,
)?;
}
Ok(())
}
fn instantiate_platform(
linker: &mut wasmtime::Linker<()>,
store: &mut wasmtime::Store<()>,
platform_module: &wasmtime::Module,
) -> Result<wasmtime::Instance> {
let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
for export in platform_instance.exports(&mut *store) {
linker.define(
"env",
export.name(),
export
.into_func()
.expect("platform surely only exports functions"),
)?;
}
Ok(platform_instance)
}
struct RegisterUpdate {
time: i32,
data: [u8; 32],
}
struct Uw8Sound {
stream: cpal::Stream,
tx: mpsc::SyncSender<RegisterUpdate>,
}
fn init_sound(
engine: &wasmtime::Engine,
platform_module: &wasmtime::Module,
module: &wasmtime::Module,
) -> Result<Uw8Sound> {
let mut store = wasmtime::Store::new(engine, ());
store.set_epoch_deadline(60);
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
let mut linker = wasmtime::Linker::new(engine);
linker.define("env", "memory", memory)?;
add_native_functions(&mut linker, &mut store)?;
let platform_instance = instantiate_platform(&mut linker, &mut store, platform_module)?;
let instance = linker.instantiate(&mut store, module)?;
let snd = instance
.get_typed_func::<(i32,), f32>(&mut store, "snd")
.or_else(|_| platform_instance.get_typed_func::<(i32,), f32>(&mut store, "sndGes"))?;
let host = cpal::default_host();
let device = host
.default_output_device()
.ok_or_else(|| anyhow!("No audio output device available"))?;
let mut configs: Vec<_> = device
.supported_output_configs()?
.filter(|config| {
config.channels() == 2 && config.sample_format() == cpal::SampleFormat::F32
})
.collect();
configs.sort_by_key(|config| {
let rate = 44100
.max(config.min_sample_rate().0)
.min(config.max_sample_rate().0);
if rate >= 44100 {
rate - 44100
} else {
(44100 - rate) * 1000
}
});
let config = configs
.into_iter()
.next()
.ok_or_else(|| anyhow!("Could not find float output config"))?;
let sample_rate = cpal::SampleRate(44100)
.max(config.min_sample_rate())
.min(config.max_sample_rate());
let config = config.with_sample_rate(sample_rate);
let buffer_size = match *config.buffer_size() {
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
cpal::SupportedBufferSize::Range { min, max } => {
cpal::BufferSize::Fixed(256.max(min).min(max))
}
};
let config = cpal::StreamConfig {
buffer_size,
..config.config()
};
let sample_rate = config.sample_rate.0 as usize;
let (tx, rx) = mpsc::sync_channel::<RegisterUpdate>(30);
struct Resampler {
resampler: rubato::FftFixedIn<f32>,
input_buffers: Vec<Vec<f32>>,
output_buffers: Vec<Vec<f32>>,
output_index: usize,
}
let mut resampler: Option<Resampler> = if sample_rate == 44100 {
None
} else {
let rs = rubato::FftFixedIn::new(44100, sample_rate, 128, 1, 2)?;
let input_buffers = rs.input_buffer_allocate();
let output_buffers = rs.output_buffer_allocate();
Some(Resampler {
resampler: rs,
input_buffers,
output_buffers,
output_index: usize::MAX,
})
};
let mut sample_index = 0;
let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30);
let mut current_time = 0;
let stream = device.build_output_stream(
&config,
move |mut outer_buffer: &mut [f32], _| {
let mut first_update = true;
while let Ok(update) = rx.try_recv() {
if first_update {
current_time += update.time.wrapping_sub(current_time) / 8;
first_update = false;
}
pending_updates.push(update);
}
while !outer_buffer.is_empty() {
store.set_epoch_deadline(30);
while pending_updates
.first()
.into_iter()
.any(|u| u.time.wrapping_sub(current_time) <= 0)
{
let update = pending_updates.remove(0);
memory.write(&mut store, 80, &update.data).unwrap();
}
let duration = if let Some(update) = pending_updates.first() {
((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
} else {
outer_buffer.len()
};
let step_size = (duration.max(64) * 2).min(outer_buffer.len());
let mut buffer = &mut outer_buffer[..step_size];
{
let mem = memory.data_mut(&mut store);
mem[64..68].copy_from_slice(&current_time.to_le_bytes());
}
if let Some(ref mut resampler) = resampler {
while !buffer.is_empty() {
let copy_size = resampler.output_buffers[0]
.len()
.saturating_sub(resampler.output_index)
.min(buffer.len() / 2);
if copy_size == 0 {
resampler.input_buffers[0].clear();
resampler.input_buffers[1].clear();
for _ in 0..resampler.resampler.input_frames_next() {
resampler.input_buffers[0]
.push(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
resampler.input_buffers[1]
.push(snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0));
sample_index = sample_index.wrapping_add(2);
}
resampler
.resampler
.process_into_buffer(
&resampler.input_buffers,
&mut resampler.output_buffers,
None,
)
.unwrap();
resampler.output_index = 0;
} else {
for i in 0..copy_size {
buffer[i * 2] =
resampler.output_buffers[0][resampler.output_index + i];
buffer[i * 2 + 1] =
resampler.output_buffers[1][resampler.output_index + i];
}
resampler.output_index += copy_size;
buffer = &mut buffer[copy_size * 2..];
}
}
} else {
for v in buffer {
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
sample_index = sample_index.wrapping_add(1);
}
}
outer_buffer = &mut outer_buffer[step_size..];
current_time =
current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
}
},
move |err| {
dbg!(err);
},
)?;
Ok(Uw8Sound { stream, tx })
}
+97
View File
@@ -0,0 +1,97 @@
use anyhow::Result;
use std::{
net::SocketAddr,
sync::{Arc, Mutex},
thread,
};
use tokio::sync::broadcast;
use tokio_stream::{wrappers::BroadcastStream, Stream, StreamExt};
use warp::{http::Response, Filter};
pub struct RunWebServer {
cart: Arc<Mutex<Vec<u8>>>,
tx: broadcast::Sender<()>,
socket_addr: SocketAddr,
}
impl RunWebServer {
pub fn new() -> RunWebServer {
let cart = Arc::new(Mutex::new(Vec::new()));
let (tx, _) = broadcast::channel(1);
let socket_addr = "127.0.0.1:3030"
.parse::<SocketAddr>()
.expect("Failed to parse socket address");
let server_cart = cart.clone();
let server_tx = tx.clone();
let server_addr = socket_addr.clone();
thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()
.expect("Failed to create tokio runtime");
rt.block_on(async {
let html = warp::path::end().map(|| {
Response::builder()
.header("Content-Type", "text/html")
.body(include_str!("run-web.html"))
});
let cart = warp::path("cart")
.map(move || server_cart.lock().map_or(Vec::new(), |c| c.clone()));
let events = warp::path("events").and(warp::get()).map(move || {
fn event_stream(
tx: &broadcast::Sender<()>,
) -> impl Stream<Item = Result<warp::sse::Event, std::convert::Infallible>>
{
BroadcastStream::new(tx.subscribe())
.map(|_| Ok(warp::sse::Event::default().data("L")))
}
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
});
let server_future = warp::serve(html.or(cart).or(events)).bind(server_addr);
server_future.await
});
});
RunWebServer {
cart,
tx,
socket_addr,
}
}
}
impl super::Runtime for RunWebServer {
fn load(&mut self, module_data: &[u8]) -> Result<()> {
if let Ok(mut lock) = self.cart.lock() {
if lock.is_empty() && !module_data.is_empty() {
println!("Point browser at http://{}", self.socket_addr);
let _ignore_result = webbrowser::open(&format!("http://{}", self.socket_addr));
}
lock.clear();
lock.extend_from_slice(module_data);
}
let _ignore_result = self.tx.send(());
Ok(())
}
fn is_open(&self) -> bool {
true
}
fn run_frame(&mut self) -> Result<()> {
std::thread::sleep(std::time::Duration::from_millis(100));
Ok(())
}
}
impl Default for RunWebServer {
fn default() -> RunWebServer {
RunWebServer::new()
}
}
+62
View File
@@ -0,0 +1,62 @@
include "../examples/include/microw8-api.cwa"
global mut counter = 0;
export fn upd() {
cls(0);
let col: i32 = 1;
loop colors {
if !testCircle(counter, col) {
printInt(counter);
return;
}
counter += 1;
branch_if (col +:= 1) < 256: colors;
}
}
fn testCircle(seed: i32, col: i32) -> i32 {
randomSeed(seed);
let cx = randomf() * 640_f - 160_f;
let cy = randomf() * 480_f - 120_f;
let radius = randomf() * 4_f;
radius *= radius;
radius *= radius;
circle(cx, cy, radius, col);
let min_x = max(0_f, floor(cx - radius - 1_f)) as i32;
let min_y = max(0_f, floor(cy - radius - 1_f)) as i32;
let max_x = min(320_f, ceil(cx + radius + 1_f)) as i32;
let max_y = min(240_f, ceil(cy + radius + 1_f)) as i32;
let x = min_x;
loop xloop {
if x < max_x {
let y = min_y;
loop yloop {
if y < max_y {
let rx = x as f32 + 0.5 - cx;
let ry = y as f32 + 0.5 - cy;
let d = sqrt(rx*rx + ry*ry) - radius;
if abs(d) > 0.001 {
let is_inside = d < 0_f;
let is_plotted = getPixel(x, y) == col;
if is_inside != is_plotted {
return 0;
}
}
y += 1;
branch yloop;
}
}
x += 1;
branch xloop;
}
}
1
}
+20
View File
@@ -0,0 +1,20 @@
include "../examples/include/microw8-api.cwa"
global mut pos = 0;
global mut next = 0;
export fn upd() {
let lazy t = 32!32;
let lazy tick = t * 6 / 100;
let lazy rel = t - tick * 100 / 6;
setBackgroundColor(select(tick == next, 0, select(tick < next, 0x35, 0x55)));
setCursorPosition(pos % 13 * 3, pos / 13 % 30);
if rel < 10 {
printChar(32);
}
printInt(rel);
pos = pos + 1;
next = tick + 1;
}
+31
View File
@@ -0,0 +1,31 @@
import "env.memory" memory(4);
import "env.pow" fn pow(f32, f32) -> f32;
import "env.sin" fn sin(f32) -> f32;
import "env.cls" fn cls(i32);
import "env.exp" fn exp(f32) -> f32;
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
include "../platform/src/ges.cwa"
export fn snd(t: i32) -> f32 {
sndGes(t)
}
export fn upd() {
80?0 = 32!32 / 200 & 2 | 0x41;
80?3 = (32!32 / 400)%8*12/7 + 40;
let pulse = (32!32 * 256 / 2000) & 511;
if pulse >= 256 {
pulse = 511 - pulse;
}
80?1 = pulse;
cls(0);
rectangle(0.0, 100.0, (pulse * 320 / 256) as f32, 16.0, 15);
}
data 80 {
i8(
0x41, 0, 0, 80, 0x70, 0
)
}
+7
View File
@@ -0,0 +1,7 @@
include "../examples/include/microw8-api.cwa"
export fn upd() {
printChar('\06f: ');
printInt(32!32 * 6 / 100);
printChar('\n\4');
}
+59
View File
@@ -0,0 +1,59 @@
include "../examples/include/microw8-api.cwa"
export fn upd() {
80?0 = (32!32 >> 11 << 6) | 5;
80?1 = (sin(time() * 6 as f32) * 95 as f32) as i32 + 128;
plotGes();
}
data 80 { i8 (
1, 128, 0, 69, 0, 15,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0xff, 0xff,
0xc1, 0, 0, 110, 0, 0
) }
//import "env.gesSnd" fn gesSnd(i32) -> f32;
include "../platform/src/ges.cwa"
export fn snd(t: i32) -> f32 {
gesSnd(t)
}
global mut samplePos: i32 = 0;
const SoundBuffer = 0x30000;
fn plotGes() {
rectangle(0 as f32, 10 as f32, 320 as f32, 320 as f32, 0);
let count = (time() * 44100 as f32) as i32 * 2 - samplePos;
let i: i32;
loop genLoop {
(i*4)$SoundBuffer = gesSnd(samplePos + i);
branch_if (i := i + 1) < count: genLoop;
}
samplePos = samplePos + count;
let ch: i32;
loop channelLoop {
let offset = 159;
i = 0;
loop searchLoop {
offset = offset + 1;
branch_if (offset * 8 + ch - 8)$SoundBuffer < 0 as f32 | (offset * 8 + ch)$SoundBuffer >= 0 as f32 & offset + 160 < count: searchLoop;
}
offset = ch + (offset - 160) * 8;
i = 0;
loop plotLoop {
setPixel(i, floor((i * 8 + offset)$SoundBuffer * 127 as f32) as i32 + 60 + ch * (120/8), 15);
branch_if (i := i + 1) < 320: plotLoop;
}
branch_if (ch := ch + 8) < 16: channelLoop;
}
}
+5
View File
@@ -0,0 +1,5 @@
include "../examples/include/microw8-api.cwa"
export fn start() {
printChar('Test');
}
+13
View File
@@ -0,0 +1,13 @@
include "../examples/include/microw8-api.cwa"
export fn upd() {
printString(USER_MEM);
}
data USER_MEM {
i8(12, 31, 5, 6) "Text mode"
i8(5, 31, 4, 5) "Graphics mode"
i8(6) "Console output\nSecond line\n"
i8(4, 31, 4, 12) "Back to text mode"
i8(0)
}
+3
View File
@@ -0,0 +1,3 @@
/target/
*.wasm
*.uw8
+398
View File
@@ -0,0 +1,398 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cdivsufsort"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edefce019197609da416762da75bb000bbd2224b2d89a7e722c2296cbff79b8c"
dependencies = [
"cc",
"sacabase",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "id-arena"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "leb128"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "lexopt"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
[[package]]
name = "libc"
version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "pbr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff5751d87f7c00ae6403eb1fcbba229b9c76c9a30de8c1cf87182177b168cea2"
dependencies = [
"crossbeam-channel",
"libc",
"time",
"winapi",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "sacabase"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9883fc3d6ce3d78bb54d908602f8bc1f7b5f983afe601dabe083009d86267a84"
dependencies = [
"num-traits",
]
[[package]]
name = "syn"
version = "1.0.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "unicode-bidi"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "upkr"
version = "0.2.1"
source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
dependencies = [
"anyhow",
"cdivsufsort",
"lexopt",
"thiserror",
]
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "uw8-tool"
version = "0.1.0"
dependencies = [
"anyhow",
"pbr",
"pico-args",
"upkr",
"walrus",
"wasm-encoder",
"wasmparser 0.99.0",
]
[[package]]
name = "walrus"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8"
dependencies = [
"anyhow",
"id-arena",
"leb128",
"log",
"walrus-macro",
"wasmparser 0.77.0",
]
[[package]]
name = "walrus-macro"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-encoder"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef126be0e14bdf355ac1a8b41afc89195289e5c7179f80118e3abddb472f0810"
dependencies = [
"leb128",
]
[[package]]
name = "wasmparser"
version = "0.77.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
[[package]]
name = "wasmparser"
version = "0.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ef3b717afc67f848f412d4f02c127dd3e35a0eecd58c684580414df4fde01d3"
dependencies = [
"indexmap",
"url",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "uw8-tool"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
wasmparser = "0.99"
wasm-encoder = "0.22"
walrus = "0.19"
anyhow = "1"
pico-args = "0.5"
upkr = { git = "https://github.com/exoticorn/upkr.git", rev = "080db40d0088bbee2bdf3c5c75288ac7853d6b7a" }
pbr = "1"
+413
View File
@@ -0,0 +1,413 @@
use std::{collections::HashMap, fs::File, path::Path};
use anyhow::{bail, Result};
use std::io::prelude::*;
use wasm_encoder::{
CodeSection, EntityType, ExportKind, ExportSection, Function, FunctionSection, ImportSection,
Instruction, MemoryType, Module, TypeSection, ValType,
};
use ValType::*;
pub struct BaseModule {
pub types: Vec<FunctionType>,
pub function_imports: Vec<(&'static str, String, u32)>,
pub global_imports: Vec<(&'static str, String, GlobalType)>,
pub functions: Vec<u32>,
pub exports: Vec<(&'static str, u32)>,
pub memory: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GlobalType {
pub type_: ValType,
pub mutable: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FunctionType {
pub params: Vec<ValType>,
pub result: Option<ValType>,
}
impl BaseModule {
pub fn for_format_version(version: u8) -> Result<BaseModule> {
if version != 1 {
bail!("Unsupported format version ({})", version);
}
let mut types = vec![];
let mut type_map = HashMap::new();
for num_params in 0..6 {
for num_f32 in 0..=num_params {
for &result in &[None, Some(ValType::I32), Some(ValType::F32)] {
let mut params = vec![];
for _ in 0..num_f32 {
params.push(F32);
}
for _ in num_f32..num_params {
params.push(I32);
}
let type_ = FunctionType { params, result };
type_map.insert(type_.clone(), types.len() as u32);
types.push(type_);
}
}
}
let mut functions = vec![];
add_function(&mut functions, &type_map, "sin", &[F32], Some(F32));
add_function(&mut functions, &type_map, "cos", &[F32], Some(F32));
add_function(&mut functions, &type_map, "tan", &[F32], Some(F32));
add_function(&mut functions, &type_map, "asin", &[F32], Some(F32));
add_function(&mut functions, &type_map, "acos", &[F32], Some(F32));
add_function(&mut functions, &type_map, "atan", &[F32], Some(F32));
add_function(&mut functions, &type_map, "atan2", &[F32, F32], Some(F32));
add_function(&mut functions, &type_map, "pow", &[F32, F32], Some(F32));
add_function(&mut functions, &type_map, "log", &[F32], Some(F32));
add_function(&mut functions, &type_map, "fmod", &[F32, F32], Some(F32));
add_function(&mut functions, &type_map, "random", &[], Some(I32));
add_function(&mut functions, &type_map, "randomf", &[], Some(F32));
add_function(&mut functions, &type_map, "randomSeed", &[I32], None);
add_function(&mut functions, &type_map, "cls", &[I32], None);
add_function(
&mut functions,
&type_map,
"setPixel",
&[I32, I32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"getPixel",
&[I32, I32],
Some(I32),
);
add_function(
&mut functions,
&type_map,
"hline",
&[I32, I32, I32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"rectangle",
&[F32, F32, F32, F32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"circle",
&[F32, F32, F32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"line",
&[F32, F32, F32, F32, I32],
None,
);
add_function(&mut functions, &type_map, "time", &[], Some(F32));
add_function(
&mut functions,
&type_map,
"isButtonPressed",
&[I32],
Some(I32),
);
add_function(
&mut functions,
&type_map,
"isButtonTriggered",
&[I32],
Some(I32),
);
add_function(&mut functions, &type_map, "printChar", &[I32], None);
add_function(&mut functions, &type_map, "printString", &[I32], None);
add_function(&mut functions, &type_map, "printInt", &[I32], None);
add_function(&mut functions, &type_map, "setTextColor", &[I32], None);
add_function(
&mut functions,
&type_map,
"setBackgroundColor",
&[I32],
None,
);
add_function(
&mut functions,
&type_map,
"setCursorPosition",
&[I32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"rectangle_outline",
&[F32, F32, F32, F32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"circle_outline",
&[F32, F32, F32, I32],
None,
);
add_function(&mut functions, &type_map, "exp", &[F32], Some(F32));
add_function(&mut functions, &type_map, "playNote", &[I32, I32], None);
add_function(&mut functions, &type_map, "sndGes", &[I32], Some(F32));
for i in functions.len()..64 {
add_function(
&mut functions,
&type_map,
&format!("reserved{}", i),
&[],
None,
);
}
let mut global_imports = vec![];
for i in 0..16 {
global_imports.push((
"env",
format!("g_reserved{}", i),
GlobalType {
type_: I32,
mutable: false,
},
));
}
let first_function = functions.len() as u32;
Ok(BaseModule {
types,
function_imports: functions,
global_imports,
functions: vec![lookup_type(&type_map, &[], None)],
exports: vec![("upd", first_function)],
memory: 4,
})
}
pub fn to_wasm(&self) -> Vec<u8> {
let mut module = Module::new();
{
let mut types = TypeSection::new();
for type_ in &self.types {
types.function(type_.params.iter().cloned(), type_.result.iter().cloned());
}
module.section(&types);
}
{
let mut imports = ImportSection::new();
for (module, name, type_) in &self.function_imports {
imports.import(*module, name.as_str(), EntityType::Function(*type_));
}
for (module, name, import) in &self.global_imports {
imports.import(
*module,
name.as_str(),
EntityType::Global(wasm_encoder::GlobalType {
val_type: import.type_,
mutable: import.mutable,
}),
);
}
imports.import(
"env",
"memory",
MemoryType {
minimum: self.memory as u64,
maximum: None,
memory64: false,
shared: false,
},
);
module.section(&imports);
}
{
let mut functions = FunctionSection::new();
for type_ in &self.functions {
functions.function(*type_);
}
module.section(&functions);
}
{
let mut exports = ExportSection::new();
for (name, fnc) in &self.exports {
exports.export(*name, ExportKind::Func, *fnc);
}
module.section(&exports);
}
{
let mut code = CodeSection::new();
for _ in &self.functions {
let mut function = Function::new([]);
function.instruction(&Instruction::End);
code.function(&function);
}
module.section(&code);
}
module.finish()
}
pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
File::create(path)?.write_all(&self.to_wasm())?;
Ok(())
}
pub fn create_binary(path: &Path) -> Result<()> {
let base1 = BaseModule::for_format_version(1)?.to_wasm();
let data = upkr::pack(&base1, 4, &upkr::Config::default(), None);
File::create(path)?.write_all(&data)?;
Ok(())
}
pub fn write_as_cwa<P: AsRef<Path>>(&self, path: P) -> Result<()> {
fn inner(mut file: File, base: &BaseModule) -> Result<()> {
writeln!(
file,
"// MicroW8 APIs, to be `include`d in CurlyWas sources"
)?;
writeln!(file, "import \"env.memory\" memory({});", base.memory)?;
writeln!(file)?;
for &(module, ref name, type_id) in &base.function_imports {
if !name.contains("reserved") {
let ty = &base.types[type_id as usize];
let params: Vec<&str> = ty.params.iter().copied().map(type_to_str).collect();
write!(
file,
"import \"{}.{}\" fn {}({})",
module,
name,
name,
params.join(", ")
)?;
if let Some(result) = ty.result {
write!(file, " -> {}", type_to_str(result))?;
}
writeln!(file, ";")?;
}
}
writeln!(file)?;
for &(name, value) in CONSTANTS {
writeln!(file, "const {} = 0x{:x};", name, value)?;
}
Ok(())
}
inner(File::create(path)?, self)
}
pub fn write_as_wat<P: AsRef<Path>>(&self, path: P) -> Result<()> {
fn inner(mut file: File, base: &BaseModule) -> Result<()> {
writeln!(file, ";; MicroW8 APIs, in WAT (Wasm Text) format")?;
writeln!(file, "(import \"env\" \"memory\" (memory {}))", base.memory)?;
writeln!(file)?;
for &(module, ref name, type_id) in &base.function_imports {
if !name.contains("reserved") {
let ty = &base.types[type_id as usize];
write!(file, "(import \"{}\" \"{}\" (func ${}", module, name, name)?;
for &param in &ty.params {
write!(file, " (param {})", type_to_str(param))?;
}
if let Some(result) = ty.result {
write!(file, " (result {})", type_to_str(result))?;
}
writeln!(file, "))")?;
}
}
writeln!(file)?;
writeln!(file, ";; to use defines, include this file with a preprocessor\n;; like gpp (https://logological.org/gpp).")?;
for &(name, value) in CONSTANTS {
writeln!(file, "#define {} 0x{:x};", name, value)?;
}
Ok(())
}
inner(File::create(path)?, self)
}
}
fn add_function(
functions: &mut Vec<(&'static str, String, u32)>,
type_map: &HashMap<FunctionType, u32>,
name: &str,
params: &[ValType],
result: Option<ValType>,
) {
functions.push((
"env".into(),
name.to_string(),
lookup_type(type_map, params, result),
));
}
fn lookup_type(
type_map: &HashMap<FunctionType, u32>,
params: &[ValType],
result: Option<ValType>,
) -> u32 {
let key = FunctionType {
params: params.to_vec(),
result,
};
*type_map.get(&key).unwrap()
}
fn type_to_str(ty: ValType) -> &'static str {
match ty {
ValType::I32 => "i32",
ValType::I64 => "i64",
ValType::F32 => "f32",
ValType::F64 => "f64",
_ => unimplemented!(),
}
}
const CONSTANTS: &[(&str, u32)] = &[
("TIME_MS", 0x40),
("GAMEPAD", 0x44),
("FRAMEBUFFER", 0x78),
("PALETTE", 0x13000),
("FONT", 0x13400),
("USER_MEM", 0x14000),
("BUTTON_UP", 0),
("BUTTON_DOWN", 1),
("BUTTON_LEFT", 2),
("BUTTON_RIGHT", 3),
("BUTTON_A", 4),
("BUTTON_B", 5),
("BUTTON_X", 6),
("BUTTON_Y", 7),
];
+21
View File
@@ -0,0 +1,21 @@
use std::path::Path;
use anyhow::Result;
pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> {
let mut module = walrus::Module::from_file(in_path)?;
let exports_to_delete: Vec<_> = module.exports.iter().filter_map(|export| match export.name.as_str() {
"upd" => None,
_ => Some(export.id())
}).collect();
for id in exports_to_delete {
module.exports.delete(id);
}
walrus::passes::gc::run(&mut module);
module.emit_wasm_file(out_path)?;
Ok(())
}

Some files were not shown because too many files have changed in this diff Show More