后台线程上的延迟 WriteableBitmap 写入会导致闪烁

问题描述 投票:0回答:1

我正在开发一个自定义 WPF 框架元素,该元素写入 WriteableBitmap,然后在元素 OnRender() 中显示位图。

由于写入 WriteableBitmap 可能会有点慢(由于我当时正在计算的算法),而且我需要显示其中 36 个元素,因此我想在后台更新 WriteableBitmap 的工作线程。

所以我想出了以下大大提高了性能的方法。问题是,如果我只创建 8 个或更少的这些元素,它就可以正常工作,但如果我创建更多元素,例如请求的 36 个,当您调整窗口大小时,前 8 个元素之后的所有元素都会闪烁?

有什么想法可能导致这种情况吗?

渲染器元素:

public class Renderer : FrameworkElement
{
    private WriteableBitmap? _bitmap, _previousBitmap;
    private long _pBackBuffer = 0;
    private int _backBufferStride = 0, _backBufferWidth = 0, _backBufferHeight = 0;
    private SemaphoreSlim _semaphore = new(1, 1);

    private void ResizeBitmap()
    {
        int width = (int)ActualWidth;
        int height = (int)ActualHeight;

        if (width <= 0 || height <= 0) 
            return;
        
        if (_bitmap == null || width != _bitmap.PixelWidth || height != _bitmap.PixelHeight)
        {
            _previousBitmap = _bitmap;
            _bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Pbgra32, null);
            _pBackBuffer = _bitmap.BackBuffer;
            _backBufferStride = _bitmap.BackBufferStride;
            _backBufferWidth = width;
            _backBufferHeight = height;

            // fill with blue for debugging purposes
            byte[] data = new byte[_bitmap.BackBufferStride * _bitmap.PixelHeight];
            for (int i = 0; i < _bitmap.PixelHeight; ++i)
            {
                for (int j = 0; j < _bitmap.PixelWidth; ++j)
                {
                    data[i * _bitmap.BackBufferStride + j * sizeof(int) + 0] = 255;
                    data[i * _bitmap.BackBufferStride + j * sizeof(int) + 1] = 0;
                    data[i * _bitmap.BackBufferStride + j * sizeof(int) + 2] = 0;
                    data[i * _bitmap.BackBufferStride + j * sizeof(int) + 3] = 255;
                }
            }

            _bitmap.WritePixels(new Int32Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight), data, _bitmap.BackBufferStride, 0);
        }
    }

    public void InvalidateRender() => WriteToBitmapInWorkerThread();

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        if (_bitmap == null)
            return;

        _semaphore.Wait();

        _bitmap.Lock();
        _bitmap.AddDirtyRect(new Int32Rect(0, 0, (int)_bitmap.Width, (int)_bitmap.Height));
        _bitmap.Unlock();

        drawingContext.DrawImage(_bitmap, new Rect(0, 0, ActualWidth, ActualHeight));

        _semaphore.Release();
    }

    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        base.OnRenderSizeChanged(sizeInfo);

        _semaphore.Wait();
        ResizeBitmap();
        _semaphore.Release();

        WriteToBitmapInWorkerThread();
    }

    private void WriteToBitmapInWorkerThread()
    {
        // do some writing to the bitmap that is slow (so run on a worker thread)
        Task.Run(() =>
        {
            _semaphore.Wait();

            unsafe
            {
                int* pPointer = (int*)_pBackBuffer;
                int stride = _backBufferStride / 4;

                // simulate slowness
                Thread.Sleep(10);

                // render a gradient for demo purposes
                for (int i = 0; i < _backBufferHeight; ++i)
                {
                    byte x = (byte)(255d / _backBufferHeight * i);
                    for (int j = 0; j < _backBufferWidth; ++j)
                    {
                        pPointer[i * stride + j] = 255 << 24 | x << 16 | x << 8 | 255;
                    }
                }
            }

            _semaphore.Release();

            Dispatcher.BeginInvoke(DispatcherPriority.Render, () => InvalidateVisual());
        });
    }
}

主窗口:

 <Grid Background="Orange">
     <ItemsControl ItemsSource="{Binding Items}">
         <ItemsControl.ItemsPanel>
             <ItemsPanelTemplate>
                 <UniformGrid />
             </ItemsPanelTemplate>
         </ItemsControl.ItemsPanel>
         <ItemsControl.ItemTemplate>
             <DataTemplate>
                 <Border Margin="2"
                         Padding="2"
                         BorderThickness="1"
                         BorderBrush="Red">
                     <local:Renderer />
                 </Border>
             </DataTemplate>
         </ItemsControl.ItemTemplate>
     </ItemsControl>
 </Grid>
 public partial class MainWindow : Window
 {
     public List<string> Items { get; } = Enumerable.Range(0, 36).Select(e => e.ToString()).ToList();

     public MainWindow()
     {
         InitializeComponent();
         DataContext = this;
     }
 }
c# wpf writeablebitmap
1个回答
0
投票

闪烁的主要原因似乎是您在创建位图后立即绘制位图,但在其缓冲区实际写入之前。

除此之外,在后台线程中创建新位图会更简单、更高效。您不需要

WriteableBitmap
,但可以直接从像素数组创建
BitmapSource

public class Renderer : FrameworkElement
{
    private BitmapSource _bitmap;
    private int _bitmapWidth;
    private int _bitmapHeight;

    public Renderer()
    {
        SizeChanged += async (s, e) =>
        {
            _bitmapWidth = (int)e.NewSize.Width;
            _bitmapHeight = (int)e.NewSize.Height;

            await WriteToBitmapInWorkerThread();
        };
    }

    public Task InvalidateRender() => WriteToBitmapInWorkerThread();

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        if (_bitmap != null)
        {
            drawingContext.DrawImage(_bitmap, new Rect(0, 0, ActualWidth, ActualHeight));
        }
    }

    private async Task WriteToBitmapInWorkerThread()
    {
        // do some writing to the bitmap that is slow (so run on a worker thread)
        await Task.Run(() =>
        {
            int width = _bitmapWidth;
            int height = _bitmapHeight;
            int[] buffer = new int[_bitmapWidth * _bitmapHeight];

            // simulate slowness
            Thread.Sleep(10);

            // render a gradient for demo purposes
            for (int i = 0; i < height; ++i)
            {
                byte x = (byte)(255d / height * i);

                for (int j = 0; j < width; ++j)
                {
                    buffer[i * width + j] = (255 << 24) | (x << 16) | (x << 8) | 255;
                }
            }

            _bitmap = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, buffer, width * 4);
            _bitmap.Freeze();
        });

        InvalidateVisual();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.