I’m working on a custom WPF Framework element that writes to a WriteableBitmap, and then displays the bitmap in the elements OnRender().
As the writing to the WriteableBitmap can be a little slow (due to the algorithm I am computing at that point) and the fact that I need to display 36 of these elements, I want to do the work of updating the WriteableBitmap on a background thread.
So I have come up with the following which greatly improves the performance. The problem is that it works fine if I only create 8 or less of these elements, but if I create any more, like the requested 36, I get flicker on all elements past the first 8 when you resize the window?
Any ideas what might be causing this?
Renderer element :
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());
});
}
}
MainWindow :
<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;
}
}