2

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 into DrawImage, 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 the Task.Delay is before the place where image.Source is set, it's fine, but if the Task.Delay comes between the image.Source assignment and the WritePixels 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...)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • The code is ok, and it works fine in a .NET 6 WPF application on my development laptop. – Clemens Sep 23 '22 at 19:19
  • It works for me as well (on a desktop PC). Not sure, could this perhaps be some strange issue with some GPU driver? For what's worth, i am using a Nvidia GTX 1070 Ti. –  Sep 23 '22 at 19:21
  • Is the overall plan a good one? I've never tried to translate a camera feed like this. I wonder about using a new bitmap per frame, freezing and multi threading rather than dispatching. No idea how good an idea that is though. – Andy Sep 23 '22 at 19:28
  • @Clemens: Bizarrely, it works fine on my other laptop too. It's very odd. – Jon Skeet Sep 23 '22 at 19:43
  • @Andy: I'm already dispatching once per frame for various other aspects. (And I only know whether I need to render it at all once I'm on the dispatcher thread.) The code's been working fine for a while - it's absolutely fine on the desktop machine. And weirdly, it works on my other laptop as well. It's all highly confusing. – Jon Skeet Sep 23 '22 at 19:45
  • https://stackoverflow.com/questions/33283077/writeablebitmap-writepixels-not-refreshing-on-some-pcs – Andy Sep 23 '22 at 19:46
  • @Andy: You star, thanks! Will try that update now... – Jon Skeet Sep 23 '22 at 19:48
  • @JonSkeet, From my personal experience with cameras, I think it's better to use asynchronous frame creation: - The event is processed in the Task on the thread pool; - A frame is received in the Task; - The frame is converted to a derivative of ImageSource; - ImageSource is frozen; - After freezing, it is assigned to the property to which the Image.Source property is bind. – EldHasp Sep 23 '22 at 20:10
  • @EldHasp: As noted earlier, I don't actually know whether or not I need to render the frame until I'm on the dispatcher thread. (There are three cameras; it's unusual for more than one to be active at a time.) But I may give that a try when I have time. – Jon Skeet Sep 23 '22 at 20:12

0 Answers0