Dpi scaling and VST3, DropShadower bug

I’m running my own dpi scaling without transforms, to avoid blurry lines and because I already had a scaling mechanism in place. So I override setScaleFactor, and if my scaling is enabled I resize accordingly, otherwise I call the base. Thing is, we have this in the VST3 wrapper:

void setEditorScaleFactor (float scale)
{
    if (pluginEditor != nullptr)
    {
        auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); // why?
        {
            const ScopedValueSetter<bool> resizingChildSetter (resizingChild, true);
            pluginEditor->setScaleFactor (scale);
            pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); // why?
        }
        lastBounds = getSizeToContainChild();
        resizeHostWindow();
        repaint();
    }
}

To fix this, I have to resize again in a timer callback. Now, I’ve tried commenting these lines out, and everything seems to be alright, at least on Reaper and Audition. Is there something I’m missing here?

(btw, the standalone plugin doesn’t seem to be dpi aware at all, and on scale changes it crashes with a null owner of a DropShadower, which I’m not using.)

The DropShadower thing is a bug from a recent commit. After a scale change, the DropShadower (but not its owner) has been deleted (it’s all garbage) when componentMovedOrResized and then updateShadows are called. Misteriously enough, if (owner == &c) updateShadows() seems to pass, otherwise updateShadows wouldn’t be called. I don’t know what else to do, as any breakpoint is useless (updateShadows is called every time I switch windows).

Those lines in the VST3 wrapper are there to set the plug-in editor to the correct size after the setScaleFactor() call. Since prevEditorBounds will always be in terms of logical pixels we can call setBounds() and it’ll correctly scale the native window, which will be in terms of physical pixels on Windows, after the scale factor has been updated. I’d be hesitant to remove them since they are needed for certain DAWs to behave correctly on Windows.

I’m not able to reproduce this with JUCE AudioPluginDemo - I’ve built the standalone target on Windows and am moving it between screens with different scale factors but it scales correctly and there is no DropShadower crash occurring. Can you provide some example code that reproduces the issue?

It doesn’t happen with the AudioPluginDemo, indeed. It happens with any of these:

  • Build an empty plugin, run the standalone, change the scale.
  • Run AudioPluginHost, load any plugin, open the plugin, change the scale.

There’s also an issue with floating windows -this happens when changing the scale to 1.25 and back:

I don’t understand what the call to setBounds can possibly achieve with respect to size, because if the base implementation of setScaleFactor is used, there’s no size change -width and height are the same before and after. But if I’m implementing setScaleFactor without transforms, that means I’m calling setBounds with physical pixels, so calling it again with the old bounds leaves me with a cropped window. If setting position to 0, 0 is needed, a call to setTopLeftPosition (0, 0) would suffice.

Just want to note that the crash sounds quite a lot like the one described here: Crash Deleting Windows since addition of isWindowOnCurrentVirtualDesktop

Are you calling AudioProcessorEditor::setScaleFactor() directly? This method should only be called by the plug-in wrapper code in response to a host scale factor change. If you want to scale your editor then the recommended way to do this is to either use Desktop::setGlobalScaleFactor() or, if you want to only scale the contents of your editor instance, to place the contents inside a wrapper Component which is a child of your AudioProcessorEditor and then call Component::setTransform() on this with the desired scale.

I’m not calling setScaleFactor, I’m implementing it, without transforms. The base implementation is ok but gives a blurry result, and I already have a scaling mechanism without transforms to handle window sizes, so extending it to also handle dpi scaling is trivial, and already done. “Without transforms” means that I’m not calling setTransform on anything, I’m quantizing the layout and drawing in a certain way to have most lines on integer coordinates. So my override of setScaleFactor calls setBounds with physical pixels, which is why calling it again with the previous bounds crops the window, because it’s not transformed. In any case, the base implementation doesn’t change the size, so setBounds is not called to actually resize -if it’s called for its side effects, setTopLeftPosition (0, 0) would also do it.

It’s probably the same thing. What happens here is that after calling updateShadows and isWindowOnCurrentVirtualDesktop from doSettingChange in the native peer, the dpi change comes and reenters updateShadows:


If we unwind we end up in win32 land

after we step out of the win32u call we get the crash on the first updateShadows

because the DropShadower has been deleted

even though the owner has not

It really seems like the call to IsWindowOnCurrentVirtualDesktop is somehow dispatching messages. Maybe there’s a better solution checking for reentry somewhere, but this seems to work:

JUCE_DECLARE_WEAK_REFERENCEABLE (DropShadower)
// ...
void DropShadower::updateShadows()
{
    if (reentrant)
        return;

    const ScopedValueSetter<bool> setter (reentrant, true);
    WeakReference<DropShadower> self (this);

    bool shouldUpdate = owner != nullptr
                        && owner->isShowing()
                        && owner->getWidth() > 0 && owner->getHeight() > 0
                        && (Desktop::canUseSemiTransparentWindows() || owner->getParentComponent() != nullptr)
                        && isWindowOnCurrentVirtualDesktop (owner->getWindowHandle());

    if (self == nullptr)
        return;

    if (shouldUpdate) // etc

Can you see if the patch in the linked post fixes the issue for you?

I tried it, sadly it doesn’t. The owner is a StandaloneFilterWindow, or a PluginWindow in the AudioPluginHost case. isJUCEWindow returns true for both.

Now that the bug is solved, I’ll give the main issue another try. The thing is that resetting the previous bounds after rescaling practically forbids overriding setScaleFactor. There’s not much more reason to override setScaleFactor than to use a different method which doesn’t involve transforms. This necessarily changes the editor size. I think it’s a good thing that setScaleFactor is virtual: if one is using vector graphics to avoid the blurring produced by scaled bitmaps, one may very well also want to avoid the blurring produced by unintended fractional bounds and positions. It’s not a minor difference:

blurring

Unless I’m missing something, given that the base implementation doesn’t change the bounds, setBounds (prevEditorBounds.withPosition (0, 0)) should be equivalent to setTopLeftPosition (0, 0), but it would also work with a different implementation that actually changes the size.