How to: Render on a Per Frame Interval Using CompositionTarget

The WPF animation engine provides many features for creating frame-based animation. However, there are application scenarios in which you need finer-grained control over rendering on a per frame basis. The CompositionTarget object provides the ability to create custom animations based on a per-frame callback.

CompositionTarget is a static class which represents the display surface on which your application is being drawn. The Rendering event is raised each time the application's scene is drawn. The rendering frame rate is the number of times the scene is drawn per second.

Note

For a complete code sample using CompositionTarget, see Using the CompositionTarget Sample.

Example

The Rendering event fires during the WPF rendering process. The following example shows how you register an EventHandler delegate to the static Rendering method on CompositionTarget.

// Add an event handler to update canvas background color just before it is rendered.
CompositionTarget.Rendering += UpdateColor;
' Add an event handler to update canvas background color just before it is rendered.
AddHandler CompositionTarget.Rendering, AddressOf UpdateColor

You can use your rendering event handler method to create custom drawing content. This event handler method gets called once per frame. Each time that WPF marshals the persisted rendering data in the visual tree across to the composition scene graph, your event handler method is called. In addition, if changes to the visual tree force updates to the composition scene graph, your event handler method is also called. Note that your event handler method is called after layout has been computed. However, you can modify layout in your event handler method, which means that layout will be computed once more before rendering.

The following example shows how you can provide custom drawing in a CompositionTarget event handler method. In this case, the background color of the Canvas is drawn with a color value based on the coordinate position of the mouse. If you move the mouse inside the Canvas, its background color changes. In addition, the average frame rate is calculated, based on the current elapsed time and the total number of rendered frames.

// Called just before frame is rendered to allow custom drawing.
protected void UpdateColor(object sender, EventArgs e)
{
    if (_frameCounter++ == 0)
    {
        // Starting timing.
        _stopwatch.Start();
    }

    // Determine frame rate in fps (frames per second).
    long frameRate = (long)(_frameCounter / this._stopwatch.Elapsed.TotalSeconds);
    if (frameRate > 0)
    {
        // Update elapsed time, number of frames, and frame rate.
        myStopwatchLabel.Content = _stopwatch.Elapsed.ToString();
        myFrameCounterLabel.Content = _frameCounter.ToString();
        myFrameRateLabel.Content = frameRate.ToString();
    }

    // Update the background of the canvas by converting MouseMove info to RGB info.
    byte redColor = (byte)(_pt.X / 3.0);
    byte blueColor = (byte)(_pt.Y / 2.0);
    myCanvas.Background = new SolidColorBrush(Color.FromRgb(redColor, 0x0, blueColor));
}
' Called just before frame is rendered to allow custom drawing.
Protected Sub UpdateColor(ByVal sender As Object, ByVal e As EventArgs)

    If _frameCounter = 0 Then
        ' Starting timing.
        _stopwatch.Start()
    End If
    _frameCounter = _frameCounter + 1

    ' Determine frame rate in fps (frames per second).
    Dim frameRate As Long = CLng(Fix(_frameCounter / Me._stopwatch.Elapsed.TotalSeconds))
    If frameRate > 0 Then
        ' Update elapsed time, number of frames, and frame rate.
        myStopwatchLabel.Content = _stopwatch.Elapsed.ToString()
        myFrameCounterLabel.Content = _frameCounter.ToString()
        myFrameRateLabel.Content = frameRate.ToString()
    End If

    ' Update the background of the canvas by converting MouseMove info to RGB info.
    Dim redColor As Byte = CByte(_pt.X / 3.0)
    Dim blueColor As Byte = CByte(_pt.Y / 2.0)
    myCanvas.Background = New SolidColorBrush(Color.FromRgb(redColor, &H0, blueColor))
End Sub

You may discover that your custom drawing runs at different speeds on different computers. This is because your custom drawing is not frame-rate independent. Depending on the system you are running and the workload of that system, the Rendering event may be called a different number of times per second. For information on determining the graphics hardware capability and performance for a device that runs a WPF application, see Graphics Rendering Tiers.

Adding or removing a rendering EventHandler delegate while the event is firing will be delayed until after the event is finished firing. This is consistent with how MulticastDelegate-based events are handled in the Common Language Runtime (CLR). Also note that rendering events are not guaranteed to be called in any particular order. If you have multiple EventHandler delegates that rely on a particular order, you should register a single Rendering event and multiplex the delegates in the correct order yourself.

See also