WPF - 实时渲染的最佳实践

6

实现说明:

在每个CompositionTarget.Rendering事件中,我使用Writablebitmap绘制4条相邻的线,(这是用于“实时”线图的绘图器)。

问题:

这很好运行,直到UI线程似乎很忙,然后下一个CompositionTarget.Rendering事件需要更长时间才能触发。

问题:

是否有一些机制可以保持恒定的渲染间隔,并将其优先于任何其他UI操作?

可能的解决方案:

我正在考虑创建一个HostVisual,并以某种方式分配它一个DispatcherTimer,这种方法可行吗?

还有其他方法吗?

提前致谢。


难道跟踪实际时间,然后在最后的渲染间隔内绘制尽可能多的相邻线条不是更合适吗?例如,在60 fps下绘制4条线,但如果帧率降至30,则绘制8条线。 - Clemens
我正在做这件事,但问题在于一次只能渲染8行。当线程繁忙后,它看起来不像实时绘图,它会卡顿一会儿,然后再绘制整个图形。例如:下一个间隔所需的时间是相对恒定间隔的4倍,那么你将绘制16行,这对人眼来说不会呈现出“实时”的效果。 - eran otzap
如果你还卡住了,能否分享一下你所拥有的工作样本呢?我在渲染方面有一些经验。 - pushpraj
我已经解决了这个问题, 但是我想看看你做了什么,无论如何我会分享我所做的,实际上是使用HostVisual。 - eran otzap
1个回答

4
如果您已经在渲染到WritableBitmap,为什么不在ThreadPool上启动异步任务呢? WritableBitmap支持来自不同线程的更新,请参考this questionthe class documentation remarks上的信息。或者,如果线程池不适合您,可以直接运行自己的线程。这样,您就可以控制时序,并且除了同步图像之外,不需要依赖UI线程进行其他任何操作。
下面是我刚编写的一些简单示例代码,它应该能让您了解我的意思:
public partial class MainWindow : Window
{
    private WriteableBitmap mImage;
    private bool mShutdown;
    private object mUpdateLock = new object();
    private IntPtr mBuffer;
    private int mStride;

    public MainWindow()
    {
        InitializeComponent();

        mImage = new WriteableBitmap(10, 2, 96, 96, PixelFormats.Bgr32, null);
        wImage.Source = mImage;

        Closed += delegate {
            CompositionTarget.Rendering -= CompositionTarget_Rendering;
            lock (mUpdateLock)
            {
                mShutdown = true;
                mImage.Unlock();
            }
        };

        mImage.Lock();
        mBuffer = mImage.BackBuffer;
        mStride = mImage.BackBufferStride;
        CompositionTarget.Rendering += CompositionTarget_Rendering;

        UpdateAsync();
    }

    private void CompositionTarget_Rendering(object sender, EventArgs e)
    {
        lock (mUpdateLock)
        {
            // for a large image you can optimize that by only updating the parts that changed,
            // collect dirty-areas in a list or something (while under the lock of course!)
            mImage.AddDirtyRect(new Int32Rect(0, 0, 10, 2));
            mImage.Unlock();
            mImage.Lock();

            // I don't know if these can changes, but docs say to acquire them after locking ...
            mBuffer = mImage.BackBuffer;
            mStride = mImage.BackBufferStride;
        }
    }

    private async void UpdateAsync()
    {
        int x = 0;
        int y = 0;
        int color = 0xFF0000;

        for (; ;)
        {
            lock (mUpdateLock)
            {
                if (mShutdown)
                    return;

                // feel free to do 'unsafe' code here if you know what you're doing
                Marshal.WriteInt32(new IntPtr(mBuffer.ToInt64() + x * 4 + y * mStride), color);
            }

            if (++x == 10)
            {
                x = 0;
                if (++y == 2)
                {
                    y = 0;
                    color ^= 0xFF00FF;
                }
            }

            await Task.Delay(500).ConfigureAwait(false);
        }
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Thread.Sleep(2000);
    }
}

以及相应的 XAML

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="Slow" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click"/>
        <Image x:Name="wImage"/>
    </Grid>
</Window>

那是一个非常好的方法,我可能会尝试一下。但是渲染本身仍然在 UI 线程上进行,如果它很忙,我仍然可能遇到我所描述的问题。 - eran otzap
我不明白你的意思,UI线程上没有渲染发生,更新位图在UI线程被阻塞时仍在继续。只是直到UI线程解除阻塞后才与显示同步,这就是UI线程被阻塞的本质,无法避免。除非你启动第二个UI线程来显示你的控件(这是可能的,但有很多限制),并小心避免阻塞它。 - Zarat
这正是我所说的,也正是我所做的。我使用一个叫做HostVisual的东西创建了第二个专门用于我的可写位图作为源的图像的调度程序。你看,在你的答案中,渲染瓶颈仍然可能存在。 - eran otzap
如果这已经适用于您,并且您可以接受其限制,那就没问题了。在这种情况下,我不会回到我的建议解决方案;我的解决方案的主要优点是它(通常)更简单易行且限制较少。 - Zarat
顺便说一句,我的解决方案中不应该有瓶颈,除非渲染步骤本身是瓶颈(我假设它不是,因为你谈论的是实时渲染)。CompositionTarget_Rendering 不应该成为瓶颈(但当图像很大时,你需要管理脏区域)。 - Zarat
在某个时刻,另一个控件上会有另一种渲染操作,在发生这种情况时,调度程序似乎会在不同的操作之间切换,你可以清楚地看到绘图器没有停止,但渲染不平滑。 - eran otzap

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接