Settling the score
Google Summer of Code 2020 officially started today, although throughout May I worked regularly on the Macromedia Director engine’s rendering pipeline. In the middle of May I replaced the long-standing frame-based renderer – which redrew the entire screen every frame – with a channel-based approach. This first part of the project brought me in touch with most of the other ScummVM Director devs, which was a nice touch for GSoC’s Community Bonding Period.
Modern operating systems use a desktop metaphor, with folders and notepads and option drawers. Director uses a cinematic metaphor, which works like this: Each movie has some cast members (shapes, bitmaps, text, etc.) which can be instantiated as sprites to appear on the stage (user viewing area). Each sprite inhabits a channel in the score. Rather confusingly, in fact, the terms sprite and channel are almost used interchangeably. One channel can display many different cast members throughout a movie. As you can see, this jargon (I dare not say lingo) departs from the usual understanding of sprite.
Just as its musical connotation implies, the score compactly describes the activity of each sprite and provides ready access to Lingo scripts and other customizations.
As you might imagine from this screenshot of a Director 4 (1994) score, scores can be viewed as large matrices indexed by frames on the vertical and channels on the horizontal. Each entry (cell) of this matrix thus describes one item on stage at one point in time. On each frame, Director draws the sprites in ascending channel order.
At first, I naively thought that like Photoshop I could just toggle “layers” and be on my way. I quickly discovered, however, that in Director this convenience was exactly the functionality that we lacked. The score needed keep track of exactly what sprites were on stage at any given moment. It would no longer be enough to blindly draw whatever the current frame said its sprites were. Our engine needed to think more horizontally, rather than vertically.
After creating numerous test movies to understand Director’s rendering rules, I devised a simple mask-based approach. I took stock of all the dirty areas of the screen and just iterated over the channels to redraw the parts of sprites that intersected there. Nothing could be simpler, but it took me quite a while to understand all the eccentricities of the code and complete the refactoring required to get the engine alive again.
My approach gives fairly complete consistency with the original. Many difficult
commands, like zoomBox
, can now work properly. Moreover, my work enabled
puppeted sprites – sprites controlled by Lingo, and not by the instructions in
the score – to work properly. If a sprite is puppeted, its channel in the score
is simply not updated with sprites from new frames. The same sprite stays on
stage until its puppet is disabled. This was not possible when every frame
brought a new set of sprites to draw. But it was trivial when the
score kept track of channels and sprites and chose when to update and redraw them.
My changes did not enhance much visually – in fact, there was a regression that drew hideous white boxes instead of transparent hotspots. However, this morning I got to the bottom of those issues, and now Captain Hammer looks as fearsome as ever.
Stay tuned for my adventures with QuickDraw and our Macintosh GUI emulator!