Note: this was fixed by updating the drivers, as noted in the duplicate.
In my real WPF (.NET 6) application, I have an Image
that I want to use to display a camera feed. I receive 30fps and I want to render those frames as efficiently as possible.
I had expected to be able to create a single WriteableBitmap
and a single Image
, set the bitmap as the source for the image, then call WriteableBitmap.WritePixels
for each frame. I'm already dispatching to the UI thread, so there should be no threading issues there.
In reality, this creates a very jerky image - at least on my development laptops. (Weirdly, it doesn't appear to be a problem on the desktop I use for "production" with this application. The desktop is significantly more powerful than the laptops, if that's relevant.) If I create a new WriteableBitmap
on each frame, the problem goes away - but that's very inefficient. (If I'm going to create a new source on each frame, I can just use BitmapSource.Create
.)
I've reproduced the problem in the sample code below. The intention is that every time you click the "Generate" button, it replaces the content of the image with random bytes (white noise with colours, effectively). In reality, with the code as shown, the "noise" is never generated on one laptop. On the other laptop (where the real app is still jerky) the demo app works fine. I'm baffled.
Code:
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Demo;
public partial class MainWindow : Window
{
private WriteableBitmap bitmap;
public MainWindow()
{
InitializeComponent();
// If this code is moved into DrawImage, it's fine
bitmap = new WriteableBitmap(128, 128, 96.0, 96.0, PixelFormats.Pbgra32, null);
image.Source = bitmap;
}
private void DrawImage(object sender, RoutedEventArgs e)
{
var data = new byte[128 * 128 * 4];
var random = new Random();
random.NextBytes(data);
bitmap.WritePixels(new Int32Rect(0, 0, 128, 128), data, 128 * 4, 0);
}
}
XAML:
<Window x:Class="Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="200" Width="150">
<StackPanel>
<Image Width="128" Height="128" x:Name="image" />
<Button Margin="5" Click="DrawImage" HorizontalAlignment="Center">Generate</Button>
</StackPanel>
</Window>
As noted in the comment, if you move the lines in the constructor to the DrawImage
method (creating a new WriteableBitmap
for each button click) it works fine.
Some variations on this:
- If you move just
image.Source
intoDrawImage
, then the first image is generated correctly, but after that it doesn't work - If you make the method asynchronous add a
Task.Delay
call, then if theTask.Delay
is before the place whereimage.Source
is set, it's fine, but if theTask.Delay
comes between theimage.Source
assignment and theWritePixels
call, the image isn't shown.
I've also tried using explicit Lock
and Unlock
calls (with AddDirtyRect
) - it doesn't help.
The example code in the WriteableBitmap
documentation behaves similarly: the first pixel to be drawn "works", but that's the only one.
I've tried this in .NET 4.8 as well, but that fails too.
This looks to be pretty simple code - what am I missing? (I'm sure I'll kick myself...)