GRIN Animation Framework
Introduction and Scope
GRIN Animation Framework is a highly efficient animation framework designed around the needs of Blu-ray Java and other TV devices that use PBP. It provides support for doing double-buffered animation with optimized drawing, so that a minimum of pixels are erased and copied with each frame.
Animation Framework Overview
GRIN includes an animation framework that manages displaying
frames of animation on a regular basis.It supports different
drawing modes, like repaint draw and direct draw.
It allows you to define your own code-based animations
by implementing an AnimationClient
interface.These custom
animations
can be painted above or below GRIN's Show-based painting. Of course,
you can also define new GRIN features that do their own painting, and
make them part of a Show. Here's a UML diagram illustrating the
animation framework:
The menu xlet in com.hdcookbook.bookmenu.menu uses the GRIN animation framework. The Gun Bunny game xlet in com.hdcookbook.gunbunny doesn't use GRIN, but it has the same kind of frame pump. Gun Bunny even lets you switch dynamically between direct draw, repaint draw and SFAA. With all three, it maintains time-based animation, dropping frames if it falls behind.
To use the animation framework, you'll need a bit of boilerplate code that looks like this:
import com.hdcookbook.grin.animator.AnimationClient; import com.hdcookbook.grin.animator.AnimationEngine; import com.hdcookbook.grin.animator.AnimationContext; import com.hdcookbook.grin.animator.DirectDrawEngine; import com.hdcookbook.grin.animator.RepaintDrawEngine; import com.hdcookbook.grin.Show; ... public class MyXlet implements Xlet, Runnable, AnimationContext { private XletContext context; private AnimationEngine engine; private Show show; ... public void initXlet(XletContext ctx) throws XletStateChangeException { this.context = ctx; DirectDrawEngine dde = new DirectDrawEngine(); // or repaint draw, ... dde.setFps(24000); engine = dde; engine.initialize(this); } public void startXlet() throws XletStateChangeException { engine.start(); } public void pauseXlet() { engine.pause(); } public void destroyXlet(boolean unconditional) throws XletStateChangeException { engine.destroy(); // Doesn't return until thread is stopped ... destroy HScene ... } // This is called from the animation manager as the xletInit // action. It's run in the animation frame, so it's OK to // do time-consuming things, l public void animationInitialize() throws InterruptedException { ... show = ... a new Show instance ...; ... initialize show ...; engine.checkDestroy(); engine.initNumTargets(... the number you need ...); AnimationClient[] clients = ... your clients (Show is a client) ...; engine.initClients(clients); Rectangle bounds = ... 1920x1080, or a smaller bounds ...; Container c = ... the container, probably just the HScene ...; engine.initContainer(c, bounds); } public void animationFinishInitialization() throws InterruptedException { show.activateSegment(show.getSegment("S:Initialize")); // Or whatever the initial segment's name is }
This creates an Animator worker thread that will display the show, by default at 23.976 times per second.
The animation loop within the AnimationEngine
happens
within a single animation thread, created by the engine. In pseudocode,
the flow of the animtion loop is:
context.animationInitialize(); // Call xlet's initialization code for each AnimationClient c c.initialize() context.animationFinishInitialization(); // sets UI state for first frame for (frame = 0 to infinity) { wait until it's time for the next frame for each AnimationClient c Advance c's model to next frame If the animation isn't behind for each AnimationClient c Tell c we're caught up Ask c where it plans to draw for each AnimationClient c for each rectangle r that needs to be re-drawn erase drawing buffer as necessary set a clipping rectangle Ask c to paint the current frame to drawing buffer for each rectangle needs to be redrawn copy from drawing buffer to the screen }
Optimized Drawing
To achieve good drawing performance, it's essential to minimize drawing.
This usually means avoiding the re-drawing of pixels that are the same as
they were in the last displayed frame of animation. This can be difficult
to figure out manually, so the animatiom framework contains support to
help automate this text. This is centered around the class
DrawRecord
.
An instance of the class DrawRecord
represents a bit of
drawing in a rectangular area of the screen. Within that rectangle,
your drawing can be fully opaque, or have some pixels that are transparent,
or that aren't drawn to at all. The drawing you do can be the same as it
was in the last frame, or it can be different. If you're writing your
own Java code, you can do what the GRIN show graph nodes do: Represent
each logical piece of drawing with a DrawRecord
. If
there's some drawing
that was done in the previous frame that isn't present in the current frame,
DrawRecord
keeps track of this for you, too: It remembers
what was drawn
in the last frame, and if any of those DrawRecord
instances aren't included
in the set of what is drawn in the current frame, those areas of the screen
are automatically erased and re-drawn. In the GRIN scene graph, every
visual feature maintains its own DrawRecord
instance.
The best way to visualize what's going on is to see it for yourself, live.
The GrinView program has a function to do this. Run the program
grin/scripts/shell/run_grinviewer.sh test
, and double-click
S:Initialize
. You'll see a silly little animation, with turtles
and rabbits flying around the screen. Click on "Stop", then on "Watch
drawing," then on "+frame". Now, just click on "next" a bunch of times.
For each frame, it will show you the areas of the screen buffer that are
erased in red, then the areas that are drawn in green, then it will show
you the result.
By default, this GRIN show selects a segment that makes pretty good use
of drawing optimization (S:DrawOpt.Opt.TargetAndGuarantee
).
But to start, double-click on S:DrawOpt.Unoptimized
. You'll
notice that a large area of the screen is updated with each frame: the
bounding rectangle of screen objects that change, in fact.
The framework could treat each DrawRecord
as an independent
drawing operation, and try to merge only some of them together when
necessary. Unfortunately, this is a hard computational problem to solve - the
most straightforward algorithm has O(n3) time complexity, which
means the amount of time it takes is proportional to the cube of the
number of inputs. If you assume a complex scene broken down into 50
drawing operations, each represented by a DrawRecord
, then
the execution time becomes proportional 503, which is
125,000 - a pretty big number.
There are "heuristic algorithms" that can attempt to do this more quickly,
but they take CPU power, too. For the GRIN aniamtion framework, we take
a different approach. Realizing that a BD-J disc has an author, we assume
that the author can spend some time optimizing drawing. It's not too
complicated: The author just needs to specify which drawing operations
belong together, that is, which sets of DrawRecord
instances
should be grouped into the same bounding rectangle. These groupings are called
"draw targets." For a simple animation there might be only one draw target,
but if a few things are going on at once, it might make sense to have up to
four or five draw targets.
This is what is done with the segment called S:DrawOpt.Opt.Target
.
Double-click on that, and step through the animation a few times. You'll see
that the turtles are in one draw target (the default target for the show,
T:Default
), the bunny spaceship is in a second target
(T:Bunny
), and the fading picture of the bunny with the
shotgun in a third (T:Picture
). This results in much more
efficient drawing.
Generally, you'll want to put objects that are close to each other in the same draw target. It's OK if objects in different draw targets overlap - you'll notice that the objects in this sample move all over the place, and sometimes cover each other. No matter how you assign draw targets, the results will always display correctly, but if you assign them well, the screen will be repainted faster.
In the sample GRIN script, there's a further level of optimization
applied in the S:DrawOpt.Opt.TargetAndGuarantee
segment.
Here, a samll additional optimization is made: A GRIN feature
is added to tell the animation framework that the two turtle troopers
completely fill the rectangle they're drawn in with opaque pixels,
so there's no need to erase that part of the graphics buffer. Correctly
applying this can result in a small speedup. Note that there's a slight
wrinkle: The two turtle troopers are actually in seperate .png image
files, and there's a gap between the two. In order to guarantee that a
contiguous rectangle is completely filled, the "guarantee_fill
"
feature actually fills in an opaque black rectangle between the two images.
Summary of AnimationEngine
control flow
The control flow of the animation engine is summarized in the following table:
AnimationEngine decides... | Method called in AnimationClient (e.g. Show) | Method called within the AnimationClient (e.g. the Show's features) |
---|---|---|
1) It's time to advance the logical model to the next frame | #nextFrame() | Each feature on the screen updates its internal data model to the state it should be in for the next frame |
2) It's not behind in the animation loop, so the next frame can be drawn to the screen | ||
2.a) Tell each client it's about to be displayed | #setCaughtUp() | |
2.b) Find out where each AnimationClient plans to draw | #addDisplayAreas(RenderingContext) | Each visual feature calls RenderContext.addArea(DrawRecord) to record this frame's drawing operations |
2.c) Compute the optimized set of "damage rectangles" that need to be re-drawn to guantee a visually correct result | ||
2.d) For each damage rectangle, ask the clients to draw into the screen buffer | #paintFrame(Graphics2D) | Each visual feature paints itself, as clipped by the Graphics2D's clip rect |
2.e) For each damage rectangle, blt the screen buffer out to the screen. |
Conclusion
This animation framework is really quite general and flexible. We hope it will form the basis of efficiently combining drawing down by scene graph frameworks like the GRIN scene graph, as well as drawing done directly in code.