我想强制渲染,但只能尽快绘制(InvalidateVisual / CompositionTarget.Rendering)

时间:2012-01-05 23:37:46

标签: c# wpf performance graphics game-loop

我正在开发一个实时WPF / Silverlight(很快就是WP7)可视化组件,我正在寻找最佳解决方案来强制以游戏循环风格重绘整个组件。重绘应该是按需的,但我不想用重新绘制调用来备份消息泵。我的组件中的大多数绘图是使用非WPF基元(例如Bitmap Interop,Direct2D)完成的,因此我的代码不使用InvalidateVisual,因此,当前看起来像这样

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

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

        DoDrawInternal();

        CurrentlyDrawing = false;            
    }
}

好的,这太好了。如果我多次调用InvalidateElement,我会得到很好的响应。但是,我想要做的是确保我可以尽可能快地将数据推送到我的可视化组件,但只能在组件能够绘制时绘制,而不是在输入流完成后继续绘制以赶上数据。

不,我无法覆盖OnRender,我在WPF中使用非WPF绘图; - )

基本上我想要的是类似于WindowsForms中的旧Invalidate()/ OnPaint,或者更好的是,DirectX中的游戏循环。

目前我得到的情况是,如果我有一个外部线程以高速率将数据推送到可视化组件,那么如果我停止推送数据,那么在组件停止绘制之前我需要再刷新20秒才能通过。我想在数据进入后立即停止绘图。

我的另一个想法是在可视化组件中处理CompositionTarget.Rendering,然后实现某种基本的Queue来将数据推送到,并且Rendering事件尽可能快地消耗这些数据。

摘要

鉴于WPF可视化组件V和每1ms推送数据的数据源D,我如何确保无论D的数据速率如何,V都以30FPS(或无论它能做什么)绘制数据并自行更新在块中,类似于DirectX中游戏渲染循环的作用?

当数据停止时,V应该一次性重绘它到目前为止所拥有的一切。当数据太快时,V一次绘制更大的块以进行补偿。

如果您需要更多信息,我很乐意与您分享。现在我刚刚发布了一个概要来衡量是否有任何快速修复,但可以根据要求提供更完整的Q代码示例。

致以最诚挚的问候,

5 个答案:

答案 0 :(得分:3)

您可能需要考虑在CompositionTarget.Rendering事件上进行渲染并对无效状态进行限制。

Silverlight游戏循环示例(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
    )

玩游戏:http://trelford.com/blog/post/LightCycles.aspx

阅读来源:https://bitbucket.org/ptrelford/lightcycles

答案 1 :(得分:1)

您是否考虑过使用以30FPS运行的调度计时器,然后拍摄当前数据的快照并在每个计时器滴答时进行渲染?如果你想在没有任何改变的情况下避免重绘,你可以简单地为LastChanged和LastRendered保留时间戳,只在LastChanged>中执行实际的重绘。 LastRendered。基本上更新数据和渲染数据是彼此分离的;主要技巧是确保在呈现线程想要呈现数据时以某种方式获得数据的连贯快照(即,您需要某种锁定。)

答案 2 :(得分:1)

我最近在处理一个需要类似游戏循环的项目。虽然我的例子纯粹是在F#中,你可以弄清楚如何在C#中这样做,也可以使用一些互操作代码来初始化定时器并挂钩事件,如下面的链接所示,

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

示例没有显示如何重绘,它只是每500ms更新基础股票数据,它应该适用于任何类型的WPF绘图机制。核心思想是使用可组合事件,在F#中,事件是一等公民+ IObservable(C#的反应式扩展),因此我们可以轻松地编写函数,然后返回一组事件或单个事件。有一个函数Observable.await,它接受一个Observable并且还有一个返回状态。

            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)

我刚刚在这里使用了一些FP术语,但它应该可以正常使用C#(OO)方式在这里做事。

希望这有帮助!

-Fahad

答案 3 :(得分:1)

您可以收听CompositionTarget.Rendering事件,该事件在WPF呈现UI之前触发,并在那里进行绘制。

另一个小问题.. InvalidateVisuals()与Form.Invalidate()完全不同,因为它也会导致重新布局,这是很昂贵的。如果你想要Form.Invalidate()之类的东西,那么创建一个DrawingGroup(或位图图像)“backingStore”,在OnRender()期间将它放在DrawingContext中,然后随时更新它。 WPF将自动更新并重新绘制UI。

答案 4 :(得分:0)

如果您使用非WPF元素进行绘制并需要WinForms提供的Invalidate()方法,我不确定为什么要将WPF用于前端?你不能只是切换UI来使用WinForms吗?