lo kibystu pe la poros

projects

blog

2017-12-14

Writeup: 512b rms intro: making of

Intros?

Intros are a subset of demos, programs that show an (often really cool) audiovisual presentation “in real time” (i.e. while it is running). There’s a whole subculture built around them, called the demoscene.

An intro is a really small demo, often 64k (65536 bytes), 4k (4096) or sometimes even smaller. This poses a challenge, as using clever hacks is required to get anything done at all, and this is what makes it interesting, and — above all — fun.

The intro

This intro, called “I’d just like to interject for a moment”, shows a bitmap of Richard Stallman while playing the Free Software Song, all in 512 bytes. It was made as a ‘bet’ — a friend didn’t believe me I could manage it — but I entered it in the Alternative Platform compo of this year’s Evoke demoparty. (It was my first demoparty ever, and it was very awesome!)

I was originally planning on releasing a 4k intro, but I accidentally overwrote the source code while working on it at the partyplace. In the end, I endered this goofy thing instead. (“No prod, no party”, as they say.) It… ended up last, which was to be expected.

If you haven’t seen it yet, here’s the prod on Pouët, a video capture is avaiable here. The source code can be found on my GitLab account. (Yep, that’s all in Lojban, because I can.)

Actual explanation

Sound

The sound was the first thing I worked on. It’s a simple bytebeat-based rectangleish wave, and the frequencies are read from a LUT, because I couldn’t find a function to generate the correct frequencies in time, and the LUT compresses quite well anyway. It’s not as esoteric as it might seem.

The sound data is then written to standard output, which is piped to…

aplay. Opening the /dev/snd/* files and calling all the relevant ioctls would take too much space, and with my setup (see the “Size-optimalisation” sub-heading), dynamic linking wasn’t possible at all.

Visuals

This is a bit tougher. Using anything like SDL or OpenGL isn’t possible, and using raw X11 socket communication or raw DRI ioctls would take too much space. There’s only one thing left: Linux framebuffers, a relatively unused (and, in my opinion, under-appreciated) part of the kernel.

Framebuffers are basically dumb pixel buffers, and they can be used without having any ‘graphics stack’ (a VGA connector, for example, is still required, for obvious reasons).

Setting one up is quite simple: just opening a /dev/fb* file and mmaping it is enough to access and modify the pixels. A few ioctls can be used to query for the size, etc. (this page covers it in detail), but I ended up hardcoding these instead in order to keep it under 512 bytes. The rms bitmap was eating quite a lot of space.

That brings us to the other half.

The bitmap was drawn in the GIMP, and a simple C program was used to pack it and transform it into something that didn’t take several kilobytes of space, and would play nice with the compression program.

The packed version is included in the final binary, and the code simply dumps it (after resizing it) to the framebuffer.

Size-optimalisation

The first prototype was written in C, and by the time I finished the audio part, the final binary — without compression, using BOLD — was about 800 bytes, which is unacceptable!

(On a sidenode: I tried using Faemiyah’s dnload, but it seems to segfault no matter what, and I haven’t had the time yet to debug the issue.)

I restarted from scratch, this time in 32-bit x86 assembly, using the hacks described here to achieve a small file size. I used as much packing on the data as possible, but not many other assembly hacks were used, as my knowledge on those is quite limited.

For compression, I used a simple dropper script for an LZMA-compressed binary, something along the lines of the following:

sed 1d $0|lzcat>j;chmod +x j;./j|aplay

As you can see, I managed to squeeze everything within the 512 byte limit, without too much of a hassle.

A note about sizecoding on Linux

Sadly, Linux seems to be a relatively negelected platform in the demoscene (one of my objectives is to change this), even though a number of (sometimes very) active sceners do use it daily.

Then there’s the case of sizecoding on Linux. An ELF header can get quite large (unless made by hand, but then you’re limited to assembly, and I don’t really have the time to do larger prods completely in asm), and while it’s relatively easy, framebuffer initialization isn’t exactly free either. I don’t see 256b intros like the ones currently made for MS-DOS coming to an objectively superior platform anytime soon, but the 512-byte limit seems to be fine.

Some people are trying to work around this limit by compiling the code at runtime (most of the time even using external libraries), which I find quite hacky. Hence, sometimes even Python is used.

Then again, most of these are still XOR textures, munching squares, or a variation thereof, leaving lots to be desired.

Not that I’m that happy with my prod, either. It would’ve been much more impressive if it didn’t store a bitmap or a tone LUT. It might even fit under the 256b limit if it was all ‘magic’ functions.

I’m currently exploring size-coded framebuffer intros more, and I’m getting something ready for a next demoparty. Sadly, it won’t be this year’s Under Construction, though, as exams are getting in the way…




Comment on forum.fork.sh

Toggle theme


Days without lynx compatibility: 0


Website source and SSG


license

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Last update: May 14, 2018