new art
An oldschool Amiga demo for Deadline 2024 where it won the anaglyph 3D competition. “Anaglyph” meaning that you need a pair of red/cyan glasses to properly enjoy the effect – it’s an oldschool production in oldschool 3D!
Note: If possible, watch this demo in an emulator, as most video codecs will mess up the red/cyan shifts, ruining the 3D effect in fast-moving sections. (I’ll provide a Coppenheimer link for that when I get around to it.)
Background
In 2018, I had won Deadline’s then-new 3D compo with platte. That was a bit surprising as there wasn’t really much going in the demo – but the soundtrack was ace, it was tightly synced, and the 3D effect worked well. Ever since then, I’ve wanted to win back the first place with a proper demo!
The next year I already had the idea for a demo with a special theme: It would have been the 30th anniversary of the “new art” editions of the Amiga 500 which were launched in November 1989.
I miscalculated the effort for my ideas and had only three real effects going by the time of Deadline 2019, one of which was a mere make-over of the 3D balls in “platte”. But it was a nice theme – 1980s “modern” aesthetics, grids and balls, leopard patterns, funny German marketing lingo, etc.
In 2021 I started another attempt to deliver a demo, full of deadline stress and (again) a gross underestimation of the work load. I guess I wasn’t very good at project managing myself…
A fun relict: Back then I thought, “Hm, it may not be the 30th ‘new art’ anniversary anymore, but 32 is 20 in hexadecimal, that’s also a round number!” and wanted to use that in the opening effect:
A bit long-winded, if you ask me. (But I did use the typing-and-correcting cursor gag elsewhere…)
In the end, it only took three more years to make it happen and successfully win back the 3D compo trophy. :)
New st-art
The plan for 2024: Start from scratch, port and extend some existing effects, add a bunch of new ones. Goals:
- Use overscan again instead of the puny little 320×180 pixel area from previous productions – 352×272 all the way!
- Try out PLaTOS as the demo framework (see below)
- Keep a journal so I can better track the actual work load in the future
- Add some color here and there (taking a hint from kb’s anaglyph how-to)
Development started in May 2024 and ended on October 5th at Deadline, right before the deadline. :)
The following production notes and tech details are a by-product of my work log, and also serve to remind myself of all the little details I tend to shrug off (“time killers”). That’s why it’s such a wall of text…
Framework
I gave PLaTOS a go, platon42’s demo framework used in Hamazing and Inside the Machine (also released at Deadline).
This turned out to be a huge win, development-wise, with all the amenities that come with it. Especially helpful:
- Ready-to-run source code (with Hamazing parts as examples)
- “Batteries included” for everything (assembler, music players, WinUAE, packers, image converters, build scripts for single parts and the whole disk)
- Logging facility (formatted log output in WinUAE)
- Task management (per-frame cooperative multitasking for background loading and your own tasks)
- Transparent compression, selectable per file
- ZX0 decrunching in-place, saving precious RAM
- Palette fading support
- File system, disk layout and I/O taken care of
- Compressing parts in parallel when building the disk
- HD support! You can build a single-file executable of your trackmo with very little extra effort
It took me some days to fully “make the switch” (coming from PLaTOS’s distant ancestor, the Planet Rocklobster framework by Axis/Oxyron), and I added a lot of custom debug logs in the process, but it paid off and I learned a lot.
Go check out the PLaTOS/Hamazing source code and the write-up!
Time killers
- Having no log output because I pointed WinUAE to the wrong config file
- Repeatedly forgetting to declare
pd_SIZEOF
before includingframework.asm
- Encountered a memory corruption in downwards allocation mode (already fixed by gigabates when I had a pull request ready for that)
- Time spent to fully grasp and implement the overall script with regards to memory management (global vs per-task allocation direction; when to free, restore memory; when to flip allocation direction)
- And yet, still not putting enough thought in a proper memory allocation concept for my use cases – for seamless transitions, I simply free all memory and leave the last effect on until the next one takes over, which risks being overwritten by the loading task for the effect after that (especially if decompression starts before the old effect was detached)
- Unrelated to the framework, but the printf-style logging feature in WinUAE uses special addresses that the ACA500+ also uses for its registers, apparently (leading to Gurus when I accidentally left debugging on)
Music
I asked for a track, and bitch delivered. Awesome! This time around, we even had time to discuss the general song structure and some syncing minutiae beforehand. Every update of the ProTracker module was a motivation boost: “Yesss! Now we’re going somewhere with the demo!” and: “This will be perfect to sync to!”
While bitch was composing, I was fleshing out more and more parts, and I could better estimate how large the module may get. Starting with a cautious 170 KB, the final demo had room for a 232,950 byte ProTracker module:
After converting it to LSP’s custom format, this meant a 196,506 byte chunk of chip RAM reserved for the sample data.
Tech-wise, I could make good use of PLaTOS’s music support:
- LSP built in (as one of several options)
- Framework support for scripts synchronized to music ticks
- Part testing with music!
Especially the part testing was a game changer during the syncing phase! Instead of running the whole demo over and over, you can just say “start at song position x” in the part’s code and run/synchronize from there.
Time killers
- Learned that LSP doesn’t like the fade-out trick with ProTracker command
A01
in a repeating pattern; replaced with manualCxx
fade-outs - Learning how LSP counts music ticks for syncing (
F06
has 6 ticks per row, regardless of bpm, etc.)
Loading
Get something on the screen quickly, warn the audience of German humor ahead.
Before the project relaunch, the 2021 version didn’t even have 3D shadows – what was I thinking?
Time killers
- Figure out how to receive a “music and first part is loaded” signal from the loading task so this part can fade out and exit
Group logo
It sure was fun to yank out the graphics tablet to do a 1989 re-imagination of our group logo in the style of Formel Eins, which is the first thing I did for this part. (“Formel Eins” is the German music show where Stefanie Tücking, designer of the “new art” case designs, was a host.)
The surfing lady is a manual conversion of a still frame from the show’s intro. I also hand-traced this pouring effect for good measure, and for authentic 1980s vibes.
The background grid is drawn with sprites (vertical lines) and background color changes (horizontal lines) to save on bitmaps and blit operations. The layered 1989-style logo is combined into an anaglyph image with a little tool combining the red/cyan channels.
The surfer was supposed to move up and down a bit, and I had painstakingly prepared all the frames in Photoshop, but this ate away too much chip RAM in the end.
Time killers
- Trouble with converted bitmaps when using DPaint and the old IFF converter: DPaint using a custom bitmap size for overscan, accidentally leaving the stencil layer on
- Pixel-aligning the grid to its final position
- Baking the grid into the text bitmap for the palette fade
- The sprites for the vertical grid lines needing to have a 1-pixel gap where they cross the horizontal lines (or the colors in the crossing point would be off for that pixel; tiny, but irritating)
- Building a combined 16 color palette for all logos including a non-transparent white and a decorative extra color, plus the iterations it took to find hand-picked color substitutions going from 23 to 16 colors
- Relying on Photoshop’s color reduction at first which would often mess up the red/cyan separation required for the 3D effect
- Pouring effect (blits larger than expected, buffering)
- Learning that vasm may chop off
ds.b
areas at the end of data sections - Number of fade steps needs to be a power of 2 in the framework
- Rewriting the whole picture conversion pipeline for surfer movements (replacing the already existing, hand-optimized 16 color bitmaps) when it wouldn’t fit into RAM anyway
Stomp, stomp, stomp
Filler effect to free up precious chip RAM before loading the next part.
Time killers
- Pixel-perfect grid alignment with adjacent effects
- Keeping the animation frames as draft until the last minute, leaving no time for antialiasing and polish (technically a time saver, but I quickly wore out my mouse-clicking hand touching up all the frames in the last minutes before the deadline)
Driving
On it goes with the quirky German texts from the original Commodore ad: “Für Leute, die immer wissen, wo’s langgeht.” (For people who always know where it’s at – literally: where to go) This scene was the obvious adaptation of that!
I started off with the driving animation. Who doesn’t love dog ears flopping in the wind?
In the reference, “Formel Eins” mascot Tweasy (the dog) drives around in a much larger car, but he also had a whole TV screen for himself there. Here he’s only got six sprites with 6×16 = 96 pixels…
Syncing Tweasy to the beat as he’s driving around aimlessly in his little car was a lot of fun after everything else was in place!
Time killers
- Learning that current JDKs do not come with a JavaScript engine anymore, having to install Nashorn manually (I like to use JavaScript snippets in my tools for fast protoyping, used here for tweaking the floor grid movements)
- Angular delta X/delta Y movements for the floor grid matching the driving direction
- Lots of fiddling to find a nice-looking perspective skew
- Copper list complexity: Cloud layers, background gradient, start and end of scroll text, dynamic start of grid area, all interspersed; different viewport widths
- Effort to make all color changes and palettes fadeable as one (ended up extracting all color register writes from the copper list programatically)
- Forgot that
SPRxPT
commands need to appear early in the copper list - Drawing the horizontal lines with the CPU needing a BLTWAIT when drawing into the same area as the blitter
Rubber balls
One of the first new parts I started implementing for the demo, thinking that 3D balls with a rubber effect could be nice. I think they are!
Using the balls as the dog’s eyes as and having him lift his glasses as a transition was a sudden inspiration when I started working on this effect (thinking “heh, heh”). The subtle pattern of background sprites with their own little shapes and rotations also turned out nice.
An old hardware trick also comes into play here: The center stage with the balls doesn’t span the whole
screen width, so early copper writes to BPL1DAT
are needed to enable sprites outside of the display window.
Time killers
- Setup of the dog-lifting-glasses screen with blits and dual playfield mode
- Lots of glitches and awkward copper jumps before getting a clean separation of texts and balls while keeping the sprite background gap-less
- Setting up the frame buffers for the rubber effect
- Clipping logic for the ball blits
- Still one remaining glitch of the lower text that I just couldn’t get rid of
- Fruitless variations of the background sprite positions; in the end a simple repeating pattern looked best
- Helper tables to have a variable level of “rubberness”
- Different rotation change approaches (first tried hard-coded delta angle changes, then sine-based)
- Lots of hard-coded rotation angle updates in the script when a simple sine-based approach looked better
- Writing a background task to change text bitmaps when they could easily be copied within a dozen raster lines
- Many iterations to map the z value to the ball sizes until it looked good; same for camera projection constants
Between balls
Again a filler part to allow for the next part to load (the rubber balls would have occupied too much chip RAM).
Not really an effect, but a good example of how you can comfortably stall the audience for some seconds to buy loading time – as long as there’s some nice music syncing going on.
Time killers
- Pixel-perfect alignment with adjacent effects
Grid
Another effect whose prototype I had in the bag from earlier demo attempts: It’s 99 Luftball-, erm balls!
This effect doesn’t draw any balls, it only updates bitplane pointers in the copper list. With the
right timing, you can seamlessly update up to three bitplanes pointers mid-scanline. Here, we only
need to update two! (Setting only BPL1PTL
and BPL3PTL
because
all ball bitmaps are in the same 64K block of memory.) This still works when 4 bitplanes are active, so we
can overlay a slightly transparent, anti-aliased 2-bitmap scroller on top of everything. Slick!
000042dc: 2627 fffe ; Wait for vpos >= 0x26 and hpos >= 0x26 000042e0: 00ea d322 ; BPL3PTL := 0xd322 000042e4: 00e2 d37a ; BPL1PTL := 0xd37a 000042e8: 2637 fffe ; Wait for vpos >= 0x26 and hpos >= 0x36 000042ec: 00ea d482 ; BPL3PTL := 0xd482 000042f0: 00e2 d42a ; BPL1PTL := 0xd42a 000042f4: 2647 fffe ; Wait for vpos >= 0x26 and hpos >= 0x46 000042f8: 00ea d4da ; BPL3PTL := 0xd4da 000042fc: 00e2 d58a ; BPL1PTL := 0xd58a 00004300: 2657 fffe ; Wait for vpos >= 0x26 and hpos >= 0x56 00004304: 00ea d692 ; BPL3PTL := 0xd692 00004308: 00e2 d6ea ; BPL1PTL := 0xd6ea ...
The original effect used the CPU to update the bitmap pointers. By using the blitter instead I had enough raster time to add another row to the grid, getting 11×9 = 99 balls in total. Now the DMA raster time is quite maxed out, though:
Due to time constraints, I cheaped out on the grid animations; they’re all scripted. I also could re-use the scroller from the “driving” part, and luckily the copper list didn’t need to be double buffered either. (If you squint hard, you can make out half-updated balls here and there, but it isn’t very noticeable.)
Time killers
- Working out the blit-to-copper logic (calculating per-ball bitplane pointers on init, getting all the blits right and tight)
- Custom tool to pre-render the ball grid animations
- Forgetting more than once that byte-encoding frames of 99 balls may lead to an odd number of bytes in the data, causing crashes without
adding an
even
here and there…
Split screen
Copper splits, again! This time only one horizontal split, though. I was curious how two independently-moving 3D effects would look with a sharp horizontal edge separating them.
After playing around some reaction-diffusion parameters to get a nice repeating leopard pattern for the right side of the effect, I just went for the original and ripped the leopard pattern from the Commodore ad (and made it tileable).
Once the grid was moving and the leopard stripe was wobbling, I started adding little features: the vertical buildup of the sides, the Amigas, up and down hovering, the texts appearing, and the sparkly sprites. Then I found out I could fit in a background color change right in the center, too, adding some more character to an otherwise rather monochromatic screen.
Looking back, the slowly wobbling leopard pattern is quite mesmerizing and one of the nicest-looking 3D effects. It’s almost a pity this effect is only shown for a dozen of seconds.
Time killers
- Wasting time with react/diff formulae instead of ripping the bitmap :)
- Split up left side/right side drawing routines into a start chunk and the rest to avoid display tearing
- Tedious to get the leopard wave working (juggling with current y + sine values + texture y + zoom level = brain fog)
- “Why are there no sprites?” – set the
SPRxPT
values too late, again - Avoiding to move both Amigas at the same time, as it would sometimes exceed the total frame time while the texts are appearing
Rotating Amiga
This effect spent four years in a prototype state. In earlier iterations I had a lot of trouble fitting all the textures into chip RAM, optimizing the textures many times and taking shortcuts with the resolution. This time around, I could comfortably fit two and half textures into RAM – phew!
The initial idea was simple: Make a vector model of the Amiga 500’s side view, wrap a nice 4-color texture around it,
create some horizontally squeezed versions of the texture, and
do an x-rotator with that. The trick: We can get the anaglyph effect nearly for free – we don’t need additional shifted
versions of the texture bitmap! As long as the horizontal red/cyan shift does not exceed 15 pixels, we can create
the anaglyph effect with BPLCON1
shifts and the right palette!
To make things complicated,
the display used 5 bitplanes to allow for a fadeable overlay, and that overlay uses the whole screen width while the
rotating Amiga only uses 256 pixels. In each scanline, the display starts with 1 bitplane, using only the overlay in BPL1PT
,
then switches to 5 bitplanes to enable the rotating Amiga, and then back to 1 bitplane again. As luck would have it, this triggered
weird behaviors in WinUAE and on the real machine, uncovering a new undocumented feature.
When I undusted this effect it was using dedicated cover-up sprites to hide the ugly effect I ran into. Then I started enhancing everything and I realized I didn’t need those sprites at all – weird! I had no time to compare what was different in the older version, but luckily the real hardware agreed that all of this would look fine without cover-up sprites.
Later, when I was working on the rubberballs effect inbetween, it dawned on me: “Hey, I already have a list of ‘rendered’ rows per frame – I can do a rubber effect with that, too!” I could and I did, and it looked nice. :)
Time killers
- Handling the dreaded unmaskable
$8000
bit in copper waits when allowing the stage to be moved up and down all the way (ended up blitting the copper wait commands from a static list) - Double buffered copper list
- Endless Amiga texture optimizations
- Row-lookup indirections for y stretch and rubber effect
- Spent a lot of time half-implementing a bitplane scaler when there was no actual need to save some 40 KB of data this way
Hit.
The final little filler. Wrote this on the train ride from Frankfurt to Berlin before the party, so total time spent must have been around four hours.
This came about half accidentally – I already chose “Der absolute Hit.” as the final text from the magazine ad to be used in the rotating Amiga effect when I realized: “Hey, I can zoom into these specific letters cheaply!” No line drawing, no blitting, just four or five rows of bitmap data stretched across the screen by the copper. Thanks for using Avant Garde in your ads, Commodore!
Going the easiest route of pre-baking the zoomed bitmaps and scripting everything, this could have been done in less than four hours. Turns out, however, if you use a lot of zoom levels in overscan mode, the required bitmaps add up to a huge pile and eat up way too much chip RAM. So the second half of the train ride was spent optimizing that amount down to about 14K. Calculating everything properly without baked bitmaps might have been wiser…
Time killers
- Bitmap optimizing (deduplication etc.)
- My train passed several major cities before line $100 handling was working in the copper writer, tsk tsk
- The whole effect, really – this wouldn’t normally be in the list of planned effects and under the radar, but it still took some hours to get right
End screen
At this point I was slowly running out of time to wrap everything up, so I went for a simple end screen, “revealing” the demo title at the very end. I got the idea to use a huge scrolling background picture when I built the whole demo to disk again and again and the damn disk just wouldn’t get full!
Having the letters wiggle to the beat was kind-of free, as I needed to blit them
every frame anyway – the 16-color background is scrolling with BPLCON1
shifts, and those affect
the foreground bitmap as well, so the letter positions need to counter-act that.
Of course I couldn’t skip the cheap trick to fill some space with a 3D photo of the party place. Actually I’m glad I still managed to take suitable photos for that, as I had some trouble holding my phone steady enough after a long night of party coding…
Everything was composed with my anaglyph tool again, with room for two extra colors. I also tried to paint in 3D a little, with Tweasy’s ear and snout popping out into the front a bit and having the two Amigas at different depths. A nice effect I would have loved to explore further.
At last there was only time for a little end logo – no credits, no greetings, no end scroller. Still an okay-ish wrap-up, although I wish I could have cleaned up the bitmaps a little…
Ugh. But being “zukunftskompatibel”, we only want to look into the future, right?
As for the disk, only 409 of 880 KB were were used. Uncompressed size of everything is 1,859 KB, shrunk down to 22% thanks to ZX0 (plus delta encoding for the sample bank). Not that I thought about compression much, the framework and platon42’s tools took care of it all!
Time killers
- Getting the single-letter blits right, without having to clear the whole foreground bitmap
- Allowing different scroll speeds in the code although not needed
- Cleaning up the large “new art” bitmap (although it was a soothing task)
- Final logo fade-out was too long, the framework only supports 64 steps
- Studying how to do proper overscan
BPLCON1
scrolling (no bitplane jumps on the right border of the screen) by analyzing the copper list in Seven seas, then not implementing it
Tools
I had a bunch of little tools for prototyping and converting. Funnily, testing anaglyph stuff is somewhat easier on the Amiga (thanks to bitplanes) than in the Kotlin/Java prototypes – a simple task like “draw this with the red channel only and this in cyan” requires some arcane ImageOp/XORMode/Composite magic unless you do it yourself on the pixel level.
Other essential tools: Photoshop, Aseprite, IntelliJ, OpenMPT, git.
Time killers
- Being lazy in integrating all the converters into the build chain right away, lots of
“Dang, of course I have to run
makeXyGfxTables.kt
first to get the new graphic assets!” - Abysmal Kotlin build times and the occasional internal build exception (costing half an hour for each “invalidate caches and restart” dance)
Final version
After Inside the Machine was released, there was a PLaTOS update for the trackloader. Also, when testing on a real Amiga 1200 at the party, the demo would hang after the rotating Amiga part.
I updated the framework, tested everything on real hardware, and added a sledgehammer solution for faster Amigas: double check that the old loading task is really, really finished before starting a new one.
Time killers
- The #$&%! cheap USB multicard readers wouldn’t read my Amiga CF cards anymore – both of them! The workaround: Upload test executables to the web, download them on the X-Surf-500-equipped Amiga 500 with HTTPResume, test them, and also copy them onto the the CF card for testing on the Amiga 1200 (which has a CF card reader, but no internet connectivity yet)
- Debugging a crash on the Amiga 1200 until I found out it only occurs after running SetPatch – I suspect (hope) that’s related to sub-optimal 68060 libraries in my installation
Conclusion
I don’t know… I might have wasted some hours with all those little “time killers”, but assembling this write-up also took its sweet time. I somehow underestimated that amount of work, too. :) Here’s to better estimations in the future! 2038, maybe…
To end on a positive note, there were also a bunch of time savers! As for the demo and its outcome – I’m happy, and Deadline 2024 really was “der absolute Hit”. Superb party, great mood, and so many awesome releases!
Time savers
- Cooking up a single-file version of the whole demo (“HD version” for larger Amigas) was basically free thanks to PLaTOS
- All the nice PLaTOS features mentioned earlier
- Building on existing effect prototypes
- Already having clean, optimized font glyphs in two sizes ready from the earlier demo iterations
- Realizing when an effect is good enough and moving on
- Not insisting on having nice transitions between all the effects – sometimes a simple cut also does the job (and I’m pleased with the fancy ones I did manage to include)
Downloads and links
Note: The disk loader used in the party version appears to have problems with some specific disk drives. Please use the final version.
Demo Scene
- new art
- hrrngh!
- B.S.I. – Byte Scene Investigation
- Shall we play a game?
- Worms VBI
- strss
- rotz
- wchrmas pattern
- Bitte hier das mit den…
- More…
Blog
- Keming war gestern
- Worms DeCoded
- VC³ 2024, an early present
- Fun with Worms DC custom levels
- Modding the Windows boot hand
- Unit Arrr
- I just favican’t
- I consider myself more of a Renaissance man
- Zerosphere on Analogue Pocket
- Amiga Topaz 1.4, part 2
- More…