public final class JActiveRenderer extends java.lang.Object implements JRenderer
JRendererPanel
.
To use this renderer a client constructs a JRendererPanel
and passes
it to the constructor with a flag indicating if the passed component will
have children that need to be rendered and a JRendererTarget
implementation. A typical sequence would be
JFrame frame = new JFrame("Renderer Demonstration"); final JRendererPanel on = new JRendererPanel(); frame.setContentPane(on); final JRendererTarget<GraphicsConfiguration, Graphics2D> target = this; JRenderer renderer = new JActiveRenderer(on, target, true);In the above snippet on will be rendered to. Swing children will be added to on by the client code (indicated by passing true as the second argument) and should be shown. The enclosing instance, this, implements
JRendererTarget
and will be called to
customize what is displayed on-screen.
The JRendererTarget
implementation is called according to the
following protocol.
JRendererTarget.renderSetup(Object)
is called once when
on is made visible. It allows the client to perform any necessary
setup.JRendererTarget.renderUpdate()
is called at the start of each
rendering cycle to allow the client to update its state prior to rendering.JRendererTarget.render(Object, int, int)
is called after
renderUpdate() during each rendering cycle to allow the client to
control what is displayed on-screen.JRendererTarget.renderShutdown()
is called once after
shutdown()
to allow the implementation to perform any
necessary cleanup.
There are two threads involved in active rendering: (1) a rendering thread
and (2) the Swing Event Dispatch Thread (EDT). When the passed
JComponent
is made visible this is detected in the EDT and two tasks
are submitted to be executed in the rendering thread: (1) The
JRendererTarget.renderSetup(Object)
method is called to let the
client perform its setup. (2) The rendering loop is started.
The rendering thread runs the following sequence as fast as it can:
JRendererTarget.renderUpdate()
to allow the
client to update its state.CountDownLatch.await()
is called on the latch that is
waiting for the EDT thread to finish painting (skipped during the first
iteration).JRendererTarget.render(Object, int, int)
to
allow the client to render onto an off-screen image.SwingUtilities.invokeLater(Runnable)
asks the EDT
thread to paint the off-screen image on the screen.The EDT thread, in addition to its normal Swing processing, performs the following:
CountDownLatch.countDown()
to "pass" the off-screen image
back to the animator thread.
This two-thread active rendering approach has several advantages. First, it
never blocks the EDT. Second, it allows the
JRendererTarget.renderUpdate()
to execute concurrently with painting
to the screen. Third, while the render thread can block, this only occurs if
painting to the screen takes longer than invoking
JRendererTarget.renderUpdate()
.
The implementation depends upon the safe sharing of the BufferedImage
used as the off-screen image between the rendering thread and the EDT. The
use of SwingUtilities.invokeLater(Runnable)
safely "passes" the image
from the rendering thread to the EDT and the use of the
CountDownLatch
safely "passes" from the EDT back to the rendering
thread.
Clients can execute code in the rendering thread by calling
invokeLater(Runnable)
(in a manner similar to
SwingUtilities.invokeLater(Runnable)
). This call is essential to
safely notify the rendering thread about events that occur in the EDT or
other program threads.
Several statistics are tracked which can be queried to understand active rendering performance.
getFPS()
provides "frames per second" or how many times per
second the screen is painted. This is the best measure of overall performance
and is often displayed on-screen.getAverageCycleTimeNanos()
provides how many nanoseconds, on
average, it tools to execute one complete rendering cycle.getAveragePaintTimeNanos()
provides how many nanoseconds, on
average, it took the EDT to paint the off-screen image to the screen.getAverageRenderTimeNanos()
provides how many nanoseconds, on
average, it took the rendering thread to render to the off-screen image (by
calling JRendererTarget.render(Object, int, int)
).getAveragePaintWaitTimeNanos()
provides how many nanoseconds, on
average, the rendering thread "blocked" waiting for the EDT to finish
painting to the screen. Because the rendering thread invokes
JRendererTarget.renderUpdate()
before it has to "block" and wait for
the EDT to finish painting, this time can be significantly smaller than
getAveragePaintTimeNanos()
. If this time is not smaller than
getAveragePaintTimeNanos()
then this two-thread active rendering
approach should probably not be used, i.e., a simple loop in a single thread
will be more efficient (i.e., provide more FPS).
Adding Swing components as children of on is supported. Ensure that
true is passed as the third argument to
JActiveRenderer(JRendererPanel, JRendererTarget, boolean)
. The
children are drawn in the EDT and never accessed in the rendering thread.
Use of the Timing Framework is supported via a ManualTimingSource
that can be obtained via getTimingSource()
. This timing source is
"ticked" once per rendering cycle. Client code should consider setting the
timing source as the default for animations, similar to the snippet below.
AnimatorBuilder.setDefaultTimingSource(renderer.getTimingSource());Then the timing source's tick() method is invoked within the rendering thread just prior to the call to renderUpdate().
JRendererTarget
,
ManualTimingSource
Constructor and Description |
---|
JActiveRenderer(JRendererPanel on,
JRendererTarget<java.awt.GraphicsConfiguration,java.awt.Graphics2D> target,
boolean hasChildren)
Constructs a new active renderer.
|
Modifier and Type | Method and Description |
---|---|
long |
getAverageCycleTimeNanos()
Calculates the total average time for each rendering cycle.
|
long |
getAveragePaintTimeNanos()
The time spent within the EDT thread painting to the screen.
|
long |
getAveragePaintWaitTimeNanos()
Gets the average amount of time spent waiting in the animator thread for
the EDT thread to complete painting to the screen.
|
long |
getAverageRenderTimeNanos()
Calculates the average time spent rendering in the rendering thread.
|
long |
getFPS()
Calculates the frames per second being drawn to the screen.
|
TimingSource |
getTimingSource()
Gets the timing source being used by the renderer.
|
void |
invokeLater(java.lang.Runnable task)
Submits a task to be run by the renderer in the same thread context that
its
JRendererTarget uses. |
void |
shutdown()
Shuts down rendering.
|
public JActiveRenderer(JRendererPanel on, JRendererTarget<java.awt.GraphicsConfiguration,java.awt.Graphics2D> target, boolean hasChildren)
Should only be invoked from the Swing EDT.
on
- the Swing component to render on.target
- to be called to control what is rendered.hasChildren
- true
if on has child components that need to be
painted. If on has no child components passing
false
can improve rendering performance.java.lang.IllegalArgumentException
- if either on or target are null
.java.lang.IllegalStateException
- if invoked outside of the Swing EDT.public void shutdown()
JRenderer
Safe to be called at any time within any thread.
public long getFPS()
JRenderer
Safe to be called at any time within any thread.
public long getAverageCycleTimeNanos()
JRenderer
Safe to be called at any time within any thread.
getAverageCycleTimeNanos
in interface JRenderer
public long getAverageRenderTimeNanos()
JRendererTarget.render(Object, int, int)
.
Safe to be called at any time within any thread.
public long getAveragePaintWaitTimeNanos()
Safe to be called at any time within any thread.
public long getAveragePaintTimeNanos()
Safe to be called at any time within any thread.
public TimingSource getTimingSource()
JRenderer
getTimingSource
in interface JRenderer
public void invokeLater(java.lang.Runnable task)
JRenderer
JRendererTarget
uses.
Safe to be called at any time within any thread.
invokeLater
in interface JRenderer
task
- a task for the renderer.