BR createCopy + bitmapData + setBackupEnabled (false)

on master 8.0.8.
it seems that Image::createCopy() doesn’t work when the image is setBackupEnabled (false) and painted via BitmapData.
Here is a pip to reproduce.
painting the image directly works fine, but if i do a copy i get an blank (not updated) image.

edit: thinking about it this example doesn’t make much sense, as when we manipulate the image directly like that it’s better to just keep the image on the cpu with SoftwareImageType (or at least to not disable backup).

/*******************************************************************************
 The block below describes the properties of this PIP. A PIP is a short snippet
 of code that can be read by the Projucer and used to generate a JUCE project.

 BEGIN_JUCE_PIP_METADATA

  name:             MyComponentPIP

  dependencies:     juce_core, juce_data_structures, juce_events, juce_graphics, juce_gui_basics
  exporters:        XCODE_MAC

  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1

  type:             Component
  mainClass:        MyComponent

 END_JUCE_PIP_METADATA

*******************************************************************************/

#pragma once


//==============================================================================
class MyComponent : public juce::Component
                  , private juce::Timer
{
public:
    //==============================================================================
    MyComponent()
    {
        setSize (400, 400);

        image = juce::Image (juce::Image::ARGB, 200, 200, true);
        image.setBackupEnabled (false);
        startTimerHz (60);
    }

    void paint (juce::Graphics& g) override
    {
        // this works fine
        // g.drawImage (image, getLocalBounds().toFloat());

        // but creating a copy doesnt
        auto tempImage = image.createCopy();
        g.drawImage (tempImage, getLocalBounds().toFloat());
    }

    void resized() override {}

    // create a small anim in the image
    void timerCallback() override
    {
        const int w = image.getWidth();
        const int h = image.getHeight();
        const int x = frame++ % w;

        juce::Image::BitmapData data (image, x, 0, 1, h, juce::Image::BitmapData::writeOnly);

        auto randomColour = juce::Colour::fromHSV (juce::Random::getSystemRandom().nextFloat(), 1.0f, 1.0f, 1.0f);

        for (int y = 0; y < h; ++y)
        {
            auto pixel = (juce::PixelARGB*) data.getPixelPointer (0, y);
            pixel->set (randomColour.getPixelARGB());
        }

        repaint();
    }

private:
    //==============================================================================
    juce::Image image;
    int frame = 0;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyComponent)
};


Although I’m not sure that I’d recommend this usage pattern, I do think that this is a bug. When creating a copy of the image, we should always copy the most up-to-date version of that image’s data, even if backups are disabled. In this case, that means we should be flushing the CPU-only changes, made via BitmapData, into GPU memory before making the copy.

As you mentioned, if you’re frequently using BitmapData to write into an image, you’re probably best off just leaving backup enabled on a Direct2D image. In the current D2D ImagePixelData implementation we try to minimise flushes between GPU memory and CPU memory. As an example, if you use BitmapData to write into an image, this will initially only touch main memory. The CPU copy won’t be flushed to GPU memory until the image is accessed from a D2D graphics context.

To put it another way, whatever image type you use, you’ll always need to pay for at least one CPU->GPU flush in order to draw a given image created via BitmapData into a D2D context. However, in the case of a SoftwareImage the contents will need to be flushed each time that image is drawn, whereas the D2D NativeImageType is smart enough to avoid flushing the image repeatedly if there haven’t been any CPU/BitmapData accesses to the image content. So, in the case where the image-update frequency is lower than the repaint frequency, I’d expect the NativeImageType to perform better.

1 Like

We’ve pushed a fix for this issue: