Categories
Coding Projects WASM

Converting an old ray tracer to Wasm

Back around the time the first iPhone was released (feeling old! 😬) I was taking a course where we built a CPU-based ray tracer in C.

If you’re not familiar with the ray tracing technique, check it out here.

What I mostly remember from the course was math, pointers, and segfaults. Oh the segfaults. By the end of the course, however, I had a decent grasp on C and that’s been a valuable skill so many times in my career and hobbies.

How the original project functioned

  1. You wrote an input file that describes various shapes, light sources, and attributes of the environment.
  2. You fed the file into the C program.
  3. You waited a bit (remember, we’re doing everything on the CPU in ~2007).
  4. You got a fancy PPM file. (PPM was a great image format due to its simplicity – we were dealing with enough!)

Wasm motivation

Recently I decided that I wanted to learn more about the inner workings of WebAssembly (Wasm) and figured this would be a great candidate project. It’s fully contained without any external dependencies, *I wrote all the code so it shouldn’t be too mysterious, and if I got it to work there would be a visual payoff.

*Feel free to judge some of the rough spots in the code – it was a long time ago!

Process

The first thing I made sure of was that I could compile the project locally the non-Wasm way. There were no hiccups there – it worked on the first try using the Makefile. ✅

I then started reading this tutorial on converting a C project to Wasm. After installing emscripten on macOS (I used Homebrew) I decided to add a new C source file to the project and added a function that looked something like:

#include "emscripten.h"

EMSCRIPTEN_KEEPALIVE
int version() {
  return 42;
}

If I could get this to work I could at least get information from C -> JS for starters. All that it took to make this work was:

  1. Substituting gcc with emcc in the Makefile
  2. Making sure I added the EXPORTED_RUNTIME_METHODS='["cwrap"]' compiler flag
  3. Calling Module.cwrap from JS to use the function

That was pretty much it. I’m not going to go super in-depth with this blog post because I think most of it can be figured out from the source.

Next challenges

I had a bit more to go but was surprised at how easy it was to send a value from C to JS. The next items to figure out were:

  1. The ray tracer expected an input file and an output file, how would this work with a browser?
  2. We can pass integers easily, but what about the big array of pixel data when we ultimately generate an image?
  3. Where would our C calls to fprintf and its siblings go when trying to debug to stdout and stderr?
  4. What about main – does it run?

I’ll go ahead and spoil these really quickly in their respective order:

  1. fmemopen saved the day by taking the input string (which is a char *) and providing a FILE type in return which is an in-memory buffer stream. In other words, no massive overhauling needed although we aren’t using “real” files anymore. In addition a slight refactoring was done to the project to return an array of pixels rather than write out an image file.
  2. From what I understand, Wasm and C can share a heap and both just need to know where to find the data via pointers. Here’s an example of sending a pointer to C, and here’s an example of how JS grabs a pointer from C. In the latter, C sets a global (gross, I know, but they used it in their examples as well) and JS calls a function to get that int. It then is able to initialize an array of UInt8s.
  3. They automagically show up in the browser console! This is a really nice feature, and stderr calls are even properly displayed as errors.
  4. Yes! In my case I got rid of it because it was prompting for the CLI input, but it was interesting to see that it automatically ran. There may be a compiler setting to disable this.

A summary of what it took to convert the ray tracer to Wasm

  1. Tweaked the Makefile to use emcc.
  2. Removed main() because I didn’t need it.
  3. Used fmemopen to substitute a real file with a char array.
  4. Refactored the project to not try to write to a file, but instead return a big array of pixels that ultimately get passed to JS to write to a Canvas.
  5. Expanded the pixel struct to include an alpha channel for the expected RGBA format. Yay for properly using sizeof throughout the code.
  6. Wrote a C source file with everything we needed to interface with JS.
  7. Created an HTML page that calls our compiled JS and gives us access to the exposed functions.
  8. Created a big string using a JS template literal for our input.

That’s mostly it! Check out the GitHub repo here. 🚀

Categories
Arduino Projects

GFX prototype tool

I’m starting to dust off some seasonal projects and realized I hadn’t made this simple tool public which others may find handy. With projects like the NeoPixel Tree it can be much quicker to code visual sequences locally instead of waiting for new firmware to upload to the MCU every time you want to tweak something.

The tool just wraps around the Adafruit GFX Graphics Library and instead of outputting to physical pixels it runs through SDL2. It’s somewhat similar to what I did in swift-gfx-wrapper but keeps it all in C.

GitHub repo: https://github.com/twstokes/gfx-proto

Categories
Projects Video

RC Gyro

Maybe if I share videos of half-finished projects it’ll motivate me to finish them!

Categories
Fun Projects Raspberry Pi

64×64 LED Matrix + Doom

From the weekend hacks department: I now have DOOM running on my 64×64 matrix!

GitHub repo: https://github.com/twstokes/doom-matrix

Categories
Apple Projects Retro Computing

OpenBSD + iMac G4

Fun fact: You can put the latest version of OpenBSD on a PPC 32-bit processor like the G4. Fun to dual boot with Mac OS 9 if you want a modern, secure computer!

Some notes

The OpenBSD docs are really good and thorough. Open Firmware needs some tweaking if you want to boot directly into OpenBSD, so this is what I did after booting into it with command+option+o+f:

setenv auto-boot? True
setenv boot-device hd:,ofwboot
reset-all

I failed to get a USB install working

Initially I didn’t want to mess with the internal drive of the iMac since I had both Mac OS 9 and Mac OS X installed, so I tried to install to a USB drive. Although the installation succeeded (albeit extremely slowly due to USB 1.1), the boot into the system failed due to the following error:

panic: rootfilesystem has size 0

Looking at the trace of the kernel boot process it was evident why: Even though we installed the OS to sd0 (the mounted USB device), the kernel kept trying to mount wd0 which is the internal IDE drive.

I tried what I knew:

  1. Tweaking the boot-device variable in Open Firmware
  2. Using a different USB slot
  3. Booting into the recovery kernel (bsd.rd) and mounting the USB to see if I could tweak fstab

Supposedly if we get to the boot prompt we can pass a -a flag for the root device (docs), but I couldn’t figure out how to get there.

Ultimately I decided to install OpenBSD to the main internal drive for now. If I get a hankering for Mac OS 9 I still have the trusty Power Mac G4.

The best setup will eventually be a dual or triple-boot. Trying to make the super-slow USB drive work is probably a terrible idea unless we plan to run it in a ramdisk mode the entire time.

The graphics driver kinda works

As you can see from the glxgears output above graphics are not accelerated. I’ve mostly played with the machine over SSH in a headless state so this hasn’t bothered me too much. I did glance at dmesg and saw that the expected driver, nv, was loaded and detected the card so I’m not totally sure what’s happening. I’m having flashbacks of when I used to spend hours tweaking xorg.conf and that may be on the horizon again.

If just running the console we still want the screen to sleep and I found I needed to make a couple tweaks for that to work.

First I needed to shut down X Windows:

rcctl stop xenodm

Then I needed to disable output activity from waking the screen:

display.outact=off

After that the screen would shut off after however many milliseconds were set for display.screen_off.

Copying over /etc/examples/wsconsctl.conf to /etc/ is a great starter config.

Turn-key graphics support depends on the model

If you’re running a G4 iMac 15″ that’s less than 1GHz, you have the GeForce2 MX which doesn’t have support out of the box via the nouveau driver. This means an unaccelerated graphics environment, low framerates, and sometimes a console that has inverted colors making it really hard to use.

The GeForce4 MX integrated on the 1GHz 15″ model and most of the 17″ iMacs is supported.

My 17″ iMac with the GeForce4 MX running glxgears at a blazing 12FPS.

Oh yeah, it runs DOOM

(Very poorly, presumably until the graphics driver is tweaked)

Running Chocolate Doom was painful. Even the setup utility had a good second or so input lag!

Categories
Arduino Coding Fun iOS Projects

Flip Dots! The technical bits.

I first want to acknowledge that I did the thing that I try to never do: I showed off a snazzy project, left some hints here and there of how it worked, said I would follow up with full details… and never did. That’s lame.

I’ve had multiple people reach out for more info and I’m glad they did, since that’s pushed me to finally get some repos public and this belated follow-up written. Apologies!

To jump straight to it, I’ve published these two repos:

Hardware

Let’s first go over the hardware involved. The most important piece, of course, is the Alfa-Zeta XY5.

In my case, the 14×28 board was made up of two 7×28 panels connected together via RJ-11.

The panels are pricey, but they can be thought of as “hardware easy-mode”. Alfa-Zeta has done the hard job building the controller that drives the hardware and all we have to do is supply power and an RS-485 signal that abides by their protocol.

If you purchase a panel from them there are two important documents to request:

  1. The main manual that describes the specs, features, and things like the DIP switch settings.
  2. The protocol for sending commands to the controllers (which is really simple).

These can easily found by searching around, but if you own a panel the company should supply them. Most of the protocol can be deduced by looking at open source code.

Components

The 24V -> 5V converter isn’t necessary if you supply power to the MCU independently, say through a USB power adapter.

Connection overview

  • 24V DC goes to both panels
  • 24V DC goes to the step-down converter, 5V DC goes to the 5V input of the NodeMCU
  • NodeMCU is wired to the RS-485 to TTL converter
    • VCC -> 3.3v
    • Gnd -> Gnd
    • DE -> 3.3v pulled high because we're always transmitting
    • RE -> 3.3v pulled high because we're always transmitting
    • DI -> TX[x] x being 0 or higher depending on board
    • RO -> RX[x] most boards only have the main serial IO, but boards like the Mega have multiple
  • RS-485 -> Only one panel controller – not both

An Arduino Mega is driving the board in this photo.

Software

The MCU

See https://github.com/twstokes/flipdots for the code that runs on the MCU.

At the moment there isn’t much to it – you can either compile the firmware to run in a mode that writes data from UDP packets to the board, or you can draw “locally” using Adafruit GFX methods.

See the README in the repo above for more details.

iOS / iPadOS / macOS

See https://github.com/twstokes/flipdots-ios for the code that runs on these devices.

Semi-interestingly I utilized Adafruit GFX again, this time via swift-gfx-wrapper to draw to the board over UDP. It’s hacky and experimental, but that’s part of the fun.

See the README in the repo above for more details.

Categories
Apple Projects Retro Computing

Power Mac G4 + SFX Power Supply

The Power Mac G4 didn’t get the “windtunnel” nickname for nothing – its power supply is quite loud. Thankfully with the help of an adapter, a standard ATX (or SFX) power supply can replace it. I love putting “modern” power supplies into my retro machines to get less noise and clean power.

I went with a Corsair SF600 from my previously built Mini-ITX machine and it’s worked really well due to its small form factor. Although it’s rigged in place with two wire ties it’s not going anywhere. 😀

Power Mac G4 with original power supply
Corsair SF600 installed

Resources:

Categories
Fun iOS Projects

SpriteKit SnowFlakes

From the seasonal hacks department, here’s my toy app to make it snow on macOS. ❄️

https://github.com/twstokes/snowflakes

How it works:

When the app is told to make it snow it adds full-screen non-interactive windows on each display and inside those windows adds a SpriteKit view with a scene inside that contains emitters.

That’s basically it!

Categories
AI Fun Projects

Do you hear what I hear?

Thanks to Whisper and this awesome port, the tree is responding to spoken words. 🗣🎄

Since the tree itself only has a low-powered MCU, we need another machine to act as a listener.

The architecture is:

  1. A machine in my office runs the Whisper model and listens for words.
  2. If certain keywords are found it finds a corresponding command to run (e.g. do a theater chase sequence in a green color).
  3. It sends that command to the tree over the network.

For now I’m running it from iOS and macOS, so I wrote the current implementation in Swift. The code is currently still in “hack” status, but working well!

Now it’s time to test it when talking to coworkers at Automattic.

Categories
Arduino Projects

Tree OTA updates

Something I should’ve done long ago – enabling OTA (Over The Air) firmware updates so I don’t have to disassemble the project box and move the tree every time I want to change its code.

To add a touch of UX, the pixels represent the upload progress. 😎

NeoPixel tree being updated via OTA firmware update.