我想强制渲染,但尽可能快地绘制(InvalidateVisual / CompositionTarget.Rendering)
Posted
技术标签:
【中文标题】我想强制渲染,但尽可能快地绘制(InvalidateVisual / CompositionTarget.Rendering)【英文标题】:I want to force a render, but only draw as fast as possible (InvalidateVisual / CompositionTarget.Rendering) 【发布时间】:2012-01-05 23:37:46 【问题描述】:我正在开发一个实时 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,然后实现某种基本队列以将数据推送到其中,并且 Rendering 事件会尽可能快地使用这些数据。
总结
给定一个 WPF 可视化组件 V 和一个每 1 毫秒向其推送数据的数据源 D,我如何确保无论 D 的数据速率如何,V 都以 30FPS(或它可以做的任何事情)绘制数据并自行更新在块中,游戏渲染循环在 DirectX 中的作用如何?
当数据停止时,V 应该一次性重绘它到目前为止的所有内容。当数据太快时,V 一次绘制更大的块来补偿。
如果您需要更多信息,我很乐意分享。现在我刚刚发布了一个概要来衡量是否有任何快速修复,但可以根据要求提供更完整的 Q 代码示例。
最好的问候,
【问题讨论】:
【参考方案1】:您可能需要考虑在 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
【讨论】:
您好菲利普,感谢您的意见。是的,我也在对此进行试验,它似乎运行良好,但是当 WPF 属性更新时,我在渲染中确实存在一些异常情况。我认为我的代码不是 WPF,只需要通过所有条件并尝试使其稳定即可。最好的问候,【参考方案2】:您可以监听在 WPF 呈现 UI 之前触发的 CompositionTarget.Rendering
事件,并在其中进行绘图。
另一个花絮.. InvalidateVisuals() 与 Form.Invalidate() 完全不同,因为它还会导致代价高昂的重新布局。如果您想要 Form.Invalidate() 之类的东西,则创建一个 DrawingGroup(或位图图像)“backingStore”,在 OnRender() 期间将其放置在 DrawingContext 中,然后随时更新它。 WPF 将自动更新和重绘 UI。
【讨论】:
有趣的是你应该提到这一点,这正是我最后所做的。该代码仍在生产中!【参考方案3】:您是否想过使用以 30FPS 运行的调度计时器,然后拍摄当前数据的快照并在每个计时器滴答时渲染它?如果您想在没有任何更改的情况下避免重绘,您可以简单地保留 LastChanged 和 LastRendered 的时间戳,仅在 LastChanged > LastRendered 时执行实际重绘。基本上更新数据和渲染数据是相互分离的;主要技巧是确保您可以在渲染线程想要渲染数据时以某种方式获得数据的连贯快照(即您需要某种锁定。)
【讨论】:
这实际上不是一个坏主意。之前没用过dispatch timer,是不是跟winforms timer类似?锁定数据集不会有问题,解决了数据/绘图解耦的问题 好的,我试过了,但奇怪的是它有时会让屏幕空白。原因是有时 WPF 绑定引擎中的更改属性会更改(例如,调用代码更改道具或样式),然后 DispatcherTimer 循环滴答作响,但由于没有数据更改,因此不会呈现。不过,对于大多数操作来说,这很有效!只需要了解如何从 WPF 获取通知。即:只需要出错,重写WPF的失效。大声笑【参考方案4】:我最近正在处理一个需要类似游戏循环样式的项目。虽然我的示例纯粹是在 F# 中,但您也可以弄清楚如何在 C# 中做到这一点,可能是使用一些互操作代码来初始化计时器并连接事件,如下面的链接所示,
http://dl.dropbox.com/u/23500975/Demos/loopstate.zip
该示例没有显示如何重绘,它只是每 500 毫秒更新一次基础股票数据,它应该适用于任何类型的 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) 做事方式。
希望这会有所帮助!
-法赫德
【讨论】:
【参考方案5】:如果您使用非 WPF 元素进行绘图并且需要 WinForms 提供的 Invalidate() 方法,我不确定您为什么要在前端使用 WPF?不能直接切换 UI 来使用 WinForms 吗?
【讨论】:
啊,重点是,它是一个 WPF 组件,可以做其他 WPF 组件不能做的事情 ;-) 此外,Winforms 还遇到很多事情。样式、透明度等等 WinForms 不使用 DirectX,所以不太适合游戏以上是关于我想强制渲染,但尽可能快地绘制(InvalidateVisual / CompositionTarget.Rendering)的主要内容,如果未能解决你的问题,请参考以下文章