4

I have an application where performance-sensitive drawings occur using a WriteableBitmap. An event is called with CompositionTarget.Rendering to actually update the back buffer of the WriteableBitmap. From the MSDN documentation, that means the event is fired once per frame, right before the control is rendered.

The issue that I am having is that the WriteableBitmap's Lock() function takes an extremely long time, especially at larger bitmap sizes. I have previously read that AddDirtyRegion() has a bug that causes the entire bitmap to invalidate, leading to poor performance. However, that doesn't seem to be the case here. From a good bit of low-level checking, it seems that Lock() opens the bitmap's backbuffer for writing on the render thread, which means every time my event handler is called, it has to thread block until the render thread is ready for it. This leads to a noticeable lag when updating the graphics of the bitmap.

I have already tried adding a timeout to the event handler, using TryLock(), so that it won't block for such a long time and cause the performance degradation. This, however, causes a similar effect in that it appears to lag, because larger numbers of bitmap updates get lumped together.

Here is the relevant code from the event handler to show what exactly I am doing. The UpdatePixels() function was written to avoid using the potentially bugged AddDirtyRect():

void updateBitmap(object sender, EventArgs e)
{
    if (!form.ResizingWindow)
    {
        // Lock and unlock are important... Make sure to keep them outside of the loop for performance reasons.
        if (canvasUpdates.Count > 0)
        {
            //bool locked = scaledDrawingMap.TryLock(bitmapLockDuration);
            scaledDrawingMap.Lock();
            //if (locked)
            //{
            unsafe
            {
                int* pixData = (int*)scaledDrawingMap.BackBuffer;
                foreach (Int32Rect i in canvasUpdates)
                {
                    // The graphics object isn't directly shown, so this isn't actually necessary.  We do a sort of manual copy from the drawingMap, which acts similarly
                    //    to a back buffer. 
                    Int32Rect temp = GetValidDirtyRegion(i);
                    UpdatePixels(temp, pixData);
                }
                scaledDrawingMap.Unlock();
                canvasUpdates.Clear();
            }
            //}
        }
    }
}

private unsafe void UpdatePixels(Int32Rect temp, int* pixData)
{
    //int* pixData = (int*)scaledDrawingMap.BackBuffer;
     // Directly copy the backbuffer into a new buffer, to use WritePixels().
    var stride = temp.Width * scaledDrawingMap.Format.BitsPerPixel / 8;
    int[] relevantPixData = new int[stride  * temp.Height];
    int srcIdx = 0;
    int pWidth = scaledDrawingMap.PixelWidth;
    int yLess = temp.Y + temp.Height;
    int xLess = temp.X + temp.Width;
    for (int y = temp.Y; y < yLess; y++)
    {
        for (int x = temp.X; x < xLess; x++)
        {
            relevantPixData[srcIdx++] = pixData[y * pWidth + x];
        }
    }
    scaledDrawingMap.WritePixels(temp, relevantPixData, stride, 0);
}

I can't seem to figure out how to avoid the issue of thread blocking with the WriteableBitmap, and I can't see any obvious faults in the code I have written. Any help or pointers would be much appreciated.

red_sky
  • 834
  • 9
  • 17
  • Did you find any solution in the meantime? – Florian Straub Oct 04 '16 at 07:55
  • I found out something really weird: If you make the WriteableBitmap a specific width and height, the Lock() takes pretty much zero time, whereas other sizes, it has a big wait time, sometimes 200+ ms for very large bitmaps. Two of these specific sizes seems to be 3353x960 and 3329x960 (w*h). I even closed the app then reopened, manually resized, and it was the same; zero performance loss. I don't really understand why this happens... but I feel like there could be a pattern. – REghZY Jul 22 '23 at 00:49

2 Answers2

0

Looks like you are not actually using the BackBuffer to write - only to read.
WritePixels writes to the "front" buffer and does not require a lock. I don't know if you have some other reason to lock it (other threads doing something), but for the code that's here i don't see why you would need to.

ILIA BROUDNO
  • 1,539
  • 17
  • 24
  • WritePixels will throw an exception if you don't lock the bitmap first, at least in my situation. – red_sky Mar 07 '14 at 00:30
0

I guess I was wrong about not needing a lock to read from BackBuffer (*pixData) - I thought it was only for writes, but I am positive you do not need to to call Lock for WritePixels.

As far as I can tell, you are doing:

  1. Lock the back buffer
  2. Copy something from it to an array
  3. Call WritePixels using this new array
  4. Unlock the back buffer.

How about switching 3 and 4?

WritePixels may internally cause rendering thread (which has higher priority on the message queue) to get a lock on its behalf which is probably a factor in the delay you are seeing.

ILIA BROUDNO
  • 1,539
  • 17
  • 24
  • The comment I left last time was inaccurate. AddDirtyRect is the function that throws an exception if the bitmap isn't locked when you call it. Write will lock the bitmap if it's not already locked. What you're suggesting will lock the backbuffer, copy the pixels, unlock the backbuffer, and then lock it and unlock it again (for the Write call). That seems inefficient to me. – red_sky May 02 '14 at 18:50
  • Right, but it's a similar function that I've been playing with on and off. Given the time that I posted this comment is when I was going back and forth very frequently, I got some of the caveats of the two approaches mixed up. – red_sky May 12 '14 at 17:09