This document describes the SWF runtime component.
NOTE: Flash APIs/behavior are only implemented on a as-needed basis, it's not completely bug-free nor accurate; it works for me.
The flow begins with constructing an AssetLibrary
from an asset manifest. An
asset manifest is a JSON containing the path of asset files and bundled data
definition JSON. Caller can receive loading progress report while the library
is loading.
The loading process would decode the images into IMG elements, sounds into WebAudio buffer, and initialize the relevant character data structures.
Usually there would be just one AssetLibrary
instance for a SWF file; it is
possible share AssetLibrary
instance across different Stage
instance.
The frames are ticked using requestAnimationFrame
and MessageChannel
(emulating requestPostAnimationFrame
). SWF frame is advanced at the beginning
of browser frame, and stage is rendered at the end of browser frame.
The frame rate specified in SWF is respected by both frame advance and render.
Audio is played using WebAudio API. Stage owns a AudioController
instance
(contains an AudioContext
), which AS3 SoundChannel
would connect to.
Scene nodes are a convenient representation of DisplayObject
s for ease of
layout and rendering. Each DisplayObject
corresponds to exactly one
SceneNode
(except some cases). Scene nodes have two major responsibilities:
- Layout: providing layout information nad hit-testing capability to
DisplayObject
. Layout is computed lazily on request. - Render: consolidate render information and provide instruction to renderer.
EventDispatcher
is implemented with simple bubbling & capturing mechanism.
For broadcast events (e.g. ENTER_FRAME
), an EventDispatcher
is associated
with each Stage
instance solely for these events.
For static text, layout is embedded in SWF, so no need to re-compute layout.
For edit text, HTML texts are parsed using DOMParser
and converted into
text segments (i.e. text associated with TextFormat
). The segments are layout
using a basic algorithm, supporting simple word-wrap, multi-line, and alignment.
AVM runtime is needed to emulate some features of AS3.
- Class definitions are registered globally (for
getDefinitionByName
). - Class methods are bound to instances automatically for derived class of
AVMObject
. - Miscellaneous helper functions for translation of AS3 built-in functions.
Timeline construction is (one of) the messiest part of SWF. The emulation is divined from other emulator implementation, so it's mostly inaccurate. At least it works for my purpose.
Instantiating a DisplayObject
(e.g. MovieClip
) from characters is a
complex process. The most problematic behavior is it populates the attributes
and children of a instantiated character within the constructor of runtime
classes. That is, derived class constructor would observe a completely
instantiated, along with recursively instantiated children, when super()
return.
To isolate the timeline object construction logic from framework classes,
we have DisplayObject.__initChar
function to allow caller to insert code to
be ran just before the return of DisplayObject
's constructor.
Frame scripts are added to a MovieClip
using the hidden addFrameScript
method. The timing of its execution is rather erratic: it may be queued and ran
whenever the timeline is updated.
The implementation is largely referenced from Shumway project. It's very incomplete and need serious re-work to be remotely accurate.
The basic rendering primitives of SWF are shapes, and texts. They are treated
as the same for our renderer, since we don't have fancy processing on font
glyphs (e.g. font atlas, SDF). Each shape and text run is modelled as a
RenderObject
.
A SceneNode
may own multiple RenderObject
s. SceneNode
would issue
instructions (i.e. DeferredRender
) to renderer in order to render them.
Due to liberal use of filters and masks in the test target SWF, filters and
masks are processed in batch. Each render instruction in batch is represented by
DeferredRender
. DeferredRender
can be resolved to new DeferredRender
s,
for example:
DeferredRenderTexture -> DeferredRenderFilter -> DeferredRenderFilter -> DeferredRenderObject
All DeferredRender
s would be resolved iteratively until every one of them
represents a simple RenderObject
(i.e. DeferredRenderObject
). Then, they're
also rendered in batch.
Due to liberal use of filters in test target SWF, up to dozens of small
RenderObject
may require off-screen rendered to texture. To reduce render
state switch and optimize memory usage, they are rendered in batch
(DeferredRenderTexture
) to a texture atlas before further processing.
TODO: Render artifacts happens: texture patch leaks to neighbor in atlas.
Blur filter supports processing multiple input blur requests in batch. The requests are grouped input atlas texture and blur passes; blur radius is passed as attributes to shader.
The test target SWF sometimes use a large blur radius (150), so shader programs are generated just-in-time with caching to improve rendering quality.
Due to liberal use of masking in test target SWF (commonly over 20 masks on screen), using stencil for masking would incur heavy performance penalty. Also, it is hard to handle independent overlapping mask correctly.
Instead, masks are rendered to off-screen textures in screen-space, with the mask ID in color channel. The main render shader would use the texture to perform masking.
The test target SWF renders a large amount of DisplayObject
to BitmapData
.
To improve performance, the DisplayObject
s are rendered in batch, by cloning
their SceneNode
s when BitmapData.draw
is called, and render them lazily.
The test target SWF also use pixels in BitmapData
frequently. Pixel data is
loaded and cached from texture lazily first time when BitmapData.getPixel32
is called, and invalidated when rendered again.