DDirectorWikiDirector & Lingo Encyclopedia

Native Rendering Internals

The recovered Iml32.dll compositor - blit route, operation state, final callback families, matte and mask coverage streams, keying, blend math, and the sprite-to-compositor bridge.

The big picture

Native Director never flattens a sprite into a premultiplied RGBA bitmap early. From member media to screen, these stay separate streams until the last per-pixel callback:

  • visible RGB (possibly palette-indexed with index provenance preserved),
  • source alpha (32-bit members with useAlpha),
  • explicit coverage (matte or mask),
  • key state (background-transparent key color),
  • ink operation id,
  • blend byte.

Sprite drawing is a staged pipeline:

text
1. authored score channel state + runtime sprite mutations
2. member media -> source pixels (palette/index, alpha, regPoint, rect provenance kept)
3. operation state: ink, blend, foreColor/backColor, mask member, rects, quad, dirty flags
4. native operation + coverage route selection
5. final compositor callback (per-pixel)
6. frame snapshot published for hit testing

Most emulator rendering bugs (wrong halos, vanished white UI, broken arithmetic inks, stale buttons) are one of these streams collapsed too early.

The IML32 blit route

  • FUN_69001bf0 is the central image/blit route: prepares source, destination, palette, key, mask/coverage, ink, and blend state, then selects a final compositor callback.
  • FUN_69005130 consumes operation state: maps internal operation ids, short-circuits full-copy/full-skip cases, stores the blend byte in a global, and prepares color-filter parameters for filter modes (0x1a, 0x1c).
  • FUN_69003310 maps non-coverage operation ids to callbacks; FUN_69002dd0 maps coverage-backed ids; FUN_690036b0 maps mask/matte source depth to coverage converters.

Final callback families

CallbackBehavior (per pixel)
Full copyCopies the source word verbatim.
Keyed copy (FUN_6900b440)Skips pixels whose prepared source RGB equals the resolved key.
Uniform blend (FUN_6900b550)dst = (src*a + dst*(0x100 - a)) >> 8 fixed-point with a 0..256 alpha scale; copies the source alpha byte to output.
Keyed blend (FUN_6900b6e0)Key skip first, then the same blend formula.
Saturating add (FUN_6900b830)Per-channel add, clamps at 0xFF (Add Pin).
Saturating subtract (FUN_6900b8d0)Per-channel subtract, clamps at 0 (Subtract Pin).
Lightest (FUN_6900b930)Writes the lighter channel values.
Darkest (FUN_6900b9d0)Writes the darker channel values.
Color filter (FUN_6900ba30)Scales/offsets source RGB (fixed point), writes source alpha byte; result is written as callback output, NOT alpha-composited over the destination. Lighten/Darken family.
4-bit coverage (FUN_6900a5f0)Packed nibble coverage: full nibble copies source, zero leaves dst, partial expands to a coverage byte then blends.
Byte coverage (FUN_6900adb0)0xFF copies, 0 leaves, partial blends using coverage xor 0xFF as the destination complement.

Key implications:

  • Arithmetic callbacks ignore blend: they write RGB results directly and copy the source alpha byte; sprite blend does not attenuate Add/Subtract/Darkest/Lightest.
  • Mask partial coverage uses its own formula (shifted products with the xor-complement), distinct from uniform sprite blend. Keep the two code paths separate.
  • A fully-allowed mask pixel takes the full-copy branch; if the source also has active source alpha, that alpha remains the coverage input through the normal source-alpha blend helper.

Keying (Background Transparent)

Key setup is operation state: the core loop resolves the key RGB through a helper that either returns packed RGB from the key record or resolves the record's palette index through the active palette. The final callback receives resolved RGB in a global; it never re-inspects source index bytes.

Recovered rules for emulators:

  • With an explicit key record (sprite backColor), resolve and use it.
  • Without one, the default key comes from source palette index 0 while the source retains indexed provenance; plain RGB sources default to white.
  • Surviving pixels keep their source colors: keying is not recoloring, and no foreColor/backColor ramp pass runs over survivors.

Matte coverage

  • createMatte-style coverage has dedicated source converters: 4-bit sources expand stored nibbles into the coverage stream; 8-bit sources copy raw bytes into it. Those stored bytes are coverage, not visible grayscale paint.
  • A real 4-bit grayscale fixture decodes to coverage byte histogram 0=1193, 17=66, 68=56, 255=113: partial coverage values (17, 68) must survive copies (copyPixels with #ink: 8) and reach the baked sprite as alpha while visible pixels stay white. Losing the partial bytes means the coverage stream was flattened somewhere upstream.
  • Matte is a compositing behavior, not bitmap identity: never bake matte transparency into a reusable member bitmap; a later sprite may draw the same member with a different ink.
  • Coverage-carrying 32-bit intermediates are not "alpha images": their RGB is opaque source color, their alpha byte belongs to the coverage compositor, and they must bypass both the visible-alpha fast path and the uniform blend callback.

Text, shapes, and provenance rules

Recovered/validated rules that keep pixel parity in UI-heavy movies:

  • Text member .image is the pre-sprite-compositing surface; ink/background transparency applies at sprite compositing time, not inside the member image. Default member image backing is transparent unless an explicit background was set; wrappers that re-copy text with MATTE/TRANSPARENT inks depend on this split.
  • Glyph placement uses font metrics (advance + kerning above the 14pt default kerningThreshold, 26.6 fixed-point pen with snapped draws), not visible ink bounds; classic field lineHeight is the authoritative line advance.
  • Windows-route raw text bytes decode as Windows-1252; XMED (D7+) text sections decode as MacRoman; missing glyphs in an embedded font stay absent rather than being substituted.
  • Authored shapes with undecodable metadata must render transparent, never as fabricated opaque boxes: the final callback copies prepared spans and cannot repair invented pixels.
  • The stage's live image buffer ((the stage).image) is a persistent backing store: publishing a frame is not a destructive read, and startup/progress UI drawn onto it stays visible until overwritten.

The sprite property to compositor bridge

How set the ink of sprite 5 to 8 reaches pixels (Dirapi side):

  1. The descriptor executor splits ink's payload 0x60a into route byte 0x06 + channel property index 0x0a (blend = 0x1c, foreColor group = 0x07, backColor group = 0x02).
  2. A dispatch route (FUN_68069c6d) sends get/set into a sprite/channel helper object; its vtable methods at +0x0c/+0x10 are the getter/setter for packed property operations, with separate methods (+0x24, +0x28, +0x2c, +0x30) for draw/update, redraw, geometry, and invalidation routes.
  3. Score rebinding is separate from live property access: a rebind route computes a score/channel identity block and refreshes the channel object only when identity changed.
  4. A shared dirty/dispatch route gates updates through channel flags (offsets +0x14c, +0x150, +0x151) before building operation/geometry blocks; property indexes 9 and 10 (ink lives at 0x0a = 10) take a special-cased path with a runtime descriptor block, confirming ink is not a generic scalar.
  5. A copy route moves channel rect/type/color/transform/registration fields into a larger native descriptor block consumed by the draw helpers; cleanup routes clear channel state, cached rects, and flags after processing.

Still open (research targets, not implementation guidance): the exact map from Director-facing ink ids to IML32 internal operation ids; resolved channel offsets for ink/blend/colors post-descriptor; whether score-authored and runtime property writes share one channel route; and precise dirty conditions forcing source rebuild vs recomposite.

Emulator checklist

  1. Keep member pixels, coverage, key, ink, and blend separate until final composition.
  2. Implement the callback families above with their exact formulas (fixed-point 0..256 blend, xor-complement mask blend, saturating vs wrapping arithmetic).
  3. Preserve palette-index provenance through copies; index 0 as default key only while provenance exists.
  4. Never bake ink results into cached member bitmaps; cache keys must include ink-relevant state (member identity, ink, colors, palette version).
  5. Force output alpha opaque when exporting a composited frame to a display that would otherwise re-apply it.
  6. Classify visual bugs by layer (decode, source prep, descriptor mapping, dirtying, operation selection, coverage route, final callback, hit policy) before touching code.