I want to force a render, but only draw as fast as possible (InvalidateVisual / CompositionTarget.Rendering)

I'm working on a real-time WPF/Silverlight (and soon WP7) visualization component and I'm looking for the best solution to force a redraw of the entire component in a Game-loop style. Redraw should be on-demand, but I don't want to back up the message pump with re-draw calls. Most of the drawing in my component is done using non-WPF primitives (e.g. Bitmap Interop, Direct2D) so my code does not use InvalidateVisual, and as a result, currently looks like this

// Pseudocode, doesnt compile, just to convey the meaning
public void InvalidateElement()
{
    if (CurrentlyDrawing)
        return;

    Dispatcher.BeginInvoke(() => 
    {
        CurrentlyDrawing = true;

        DoDrawInternal();

        CurrentlyDrawing = false;            
    }
}

Ok so this is great. If I call InvalidateElement lots of times I get good responsiveness. However, what I want to do is ensure I can push data to my visualization component as fast as possible but only draw when the component is able to draw, and not keep drawing to catch up with the data once the input stream completes.

No I can't override OnRender, I'm using non-WPF drawing inside WPF ;-)

Basically what I want is something like the old Invalidate() / OnPaint in WindowsForms, or better yet, a game loop in DirectX.

At the moment I get the situation where if I have an external thread that pushes data to the visualization component at a high rate then if I Stop pushing data I get another 20 seconds worth of refreshes to get through before the component stops drawing. I want to stop drawing as soon as data has gone in.

Another idea I had was to handle CompositionTarget.Rendering in the visualization component then implement some sort of rudimentary Queue to push data to and the Rendering event consumes this data as fast as it can.

In Summary

Given a WPF visualization component, V, and a datasource which pushes it data every 1ms, D, how can I ensure that no matter the datarate of D, V draws data at 30FPS (or whatever it can do) and updates itself in chunks, sort of how a game render loop does in DirectX?

When the data stops, V should redraw everything it has up to now in one go. When the data is too fast, V draws larger chunks at a time to compensate.

If you need more information I'd be happy to share it. Right now I've just posted a synopsis to gauge if there are any quick fixes but a fuller Q with code examples can be provided on request.

Best regards,

Upvotes: 2

Views: 2128

Answers (5)

David Jeske
David Jeske

Reputation: 2466

You can listen to the CompositionTarget.Rendering event, which is triggered right before WPF renders the UI, and do your drawing in there.

Another tidbit.. InvalidateVisuals() is nothing like Form.Invalidate(), as it also causes re-layout which is expensive. If you want something like Form.Invalidate(), then create a DrawingGroup (or bitmap image) "backingStore", place it in the DrawingContext during OnRender(), and then update it whenever you want. WPF will automatically update and repaint the UI.

Upvotes: 1

Fahad
Fahad

Reputation: 878

I was recently working with a project that required a game loop like style. Although my example is purely in F#, you can figure it out how you can do that way in C# too, may be use some interop code to initialize the timer and hooking up events as given in this below link,

http://dl.dropbox.com/u/23500975/Demos/loopstate.zip

The sample doesn't show how to redraw, it just updates the underlying stock data for every 500ms, It should pretty much work for any kind of drawing mechanisms with WPF. The core idea is to use composable events, in F# an event is a first-class citizen + an IObservable (reactive extensions for C#), so we can easily compose functions that in-turn return a set of events or a single event. There is a function Observable.await, which takes in an Observable and also has a state to return.

            eventSource
            |> Observable.await(fun (t:State.t) e ->
                // return the modified state back on every check or in the end
                match e with
                // start button click
                | Choice1Of3(_) ->
                    {t with start=true}

                // stop button click
                | Choice2Of3(_) ->
                    {t with start=false}

                // timer tick event,
                | Choice3Of3(_) ->
                    if t.start = true then
                        handleStockUpdate(t)
                    else t
            ) (state)

I just used some of FP terms here, but it should work just fine with normal C# (OO) way of doing things here.

Hope this helps!

-Fahad

Upvotes: 1

Phillip Trelford
Phillip Trelford

Reputation: 41

You might want to consider rendering on the CompositionTarget.Rendering event and throttling on the invalidated state.

Silverlight game loop example (F#):

/// Run game
let runGame () =
    let state = gameState.GetEnumerator()
    let rate = TimeSpan.FromSeconds(1.0/50.0)
    let lastUpdate = ref DateTime.Now
    let residual = ref (TimeSpan())
    CompositionTarget.Rendering.Add (fun x -> 
        let now = DateTime.Now
        residual := !residual + (now - !lastUpdate)
        while !residual > rate do
            state.MoveNext() |> ignore
            residual := !residual - rate
        lastUpdate := now
    )

Play the game: http://trelford.com/blog/post/LightCycles.aspx

Read the source: https://bitbucket.org/ptrelford/lightcycles

Upvotes: 4

Dan Bryant
Dan Bryant

Reputation: 27495

Have you thought of using a dispatch timer running at 30FPS, then take a snapshot of the current data and rendering it at each timer tick? If you want to avoid redrawing if nothing has changed, you can simply keep timestamps for LastChanged and LastRendered, only performing an actual redraw if LastChanged > LastRendered. Basically updating the data and rendering the data are decoupled from one-another; the main trick is making sure you can somehow get a coherent snapshot of the data when the rendering thread wants to render it (i.e. you'll need some sort of locking.)

Upvotes: 1

evasilchenko
evasilchenko

Reputation: 1870

I'm not sure why you would use WPF for your front-end if you're drawing using non-WPF elements and require the Invalidate() method that was provided by WinForms? Can't you just switch the UI to use WinForms?

Upvotes: 0

Related Questions