Issues regarding CMultiFrameBitmap


Updating to VSTGUI 4.12 and using the new CMultiFrameBitmap class in order to facilitate the D2D hardware based renderer on Windows, I made some observations:

  1. The frameIndex calculation is inconsistent with the old convention in e.g. CAnimKnob::draw(): auto frameIndex = static_cast<uint16_t> (std::min (mfb->getNumFrames () - 1.f, getValueNormalized () * mfb->getNumFrames ())); yields a different quantization than CCoord tmp = heightOfOneImage * (getNumSubPixmaps () - 1); where.y = floor (val * tmp);
    I gather this is in order to provide for compatibility with VST3’s quantization scheme (e.g. in StringListParameter), but it breaks downward compatibility when updating to CMultiFrameBitmap and the only way to work around this is to come up with vendor specific controls and override ::draw() where required. I see that CMovieBitmap for instance offers a useLegacyFrameCalculation mode, but CAnimKnob doesn’t have that.
    I think it should be rendered consistent and thus the above line should be changed to auto frameIndex = static_cast<uint16_t> (getValueNormalized () * (mfb->getNumFrames () - 1.f));
    Another option that comes to mind would be adding a CMultiFrameBitmap::valueToFrameIndex() function that is used by all the relevant controls’ ::draw() functions so it can be overriden in the bitmap class by the GUI developer when required.

  2. The bInverseMode calculation is off by 1 in CAnimKnob::draw(), i.e. if (bInverseBitmap) frameIndex = mfb->getNumFrames () - frameIndex; should be if (bInverseBitmap) frameIndex = mfb->getNumFrames () - 1.f - frameIndex;

  3. I noticed opening the editor sometimes stalls the host for a couple of seconds before the plugin GUI comes up when the D2D hardware renderer is enabled via auto win32Factory = getPlatformFactory().asWin32Factory(); if(win32Factory) win32Factory->useD2DHardwareRenderer(true);
    This doesn’t happen when I use the default software mode. I haven’t investigated this any further but has someone else also observed this?
    EDIT: This seems to happen when a session is restored and there is at least a second plugin which uses the D2D render target and whose GUI is opened. Can be reproduced in virtually any DAW it seems.


Hi Ray,
I’ve created an issue on GitHub for issue 1 and 2. I will provide a fix for them.
The 3rd issue is a little bit strange as this only relates to the non DirectComposition strategy which should only be used when DirectComposition is not available.

1 Like

Hi Arne,

Thanks for the quick reply/action. Regarding 3.: That’s only what I can observe. It’s 100% reproducible and goes away when I set the D2D hardware rendering to false (or don’t set it at all, which seems to be the default mode). I’ll try to come up with a minimal example and steps to reproduce ASAP. Perhaps it’s worth mentioning that this happens on Windows 7.


Ah this explains it as DirectComposition is not available on Windows 7. I would suggest to just don’t set it at all. Because when you run your plug-in on Win10/11 VSTGUI will use DirectComposition which uses the GPU for its rendering.

Perfect and very easy solution. Thanks for the clarification!

I noticed another one in cmoviebitmap.cpp:

mfb->drawFrame (pContext, frameIndex, getViewSize ().getTopLeft () + offset); (line 87)

which imposes the offset point to where the mfb slice is drawn on its container as opposed to

bitmap->draw (pContext, getViewSize (), offset); (line 108)

which imposes the offset point inside the bitmap, so the new mfb implementation is wrong / incompatible in that regard.

The correct implementation would be to add an optional offset to the CMultiFrameBitmap::draw() API as follows:

void CMultiFrameBitmap::drawFrame (CDrawContext* context, uint16_t frameIndex, CPoint pos, CPoint offset = CPoint(0, 0))
	auto fr = calcFrameRect (frameIndex);
	auto r = CRect (pos, getFrameSize ());
	draw (context, r, fr.getTopLeft () + offset);

This is also related to Reintroduce setBackOffset() / getBackOffset() and respect the backOffset in all views / controls · Issue #266 · steinbergmedia/vstgui · GitHub.


A little bit too late for now. See Release Release Version 4.12.1 · steinbergmedia/vstgui · GitHub

And I think that’s why I don’t like the back offset feature at all (it just makes changes more complex).
Can you tell me why you use this feature and how this is better than the alternative to provide the images in the same size as the view/control?

Hi Arne,

Not a big deal at all, I can rework our GUI code so that it doesn’t rely on the backOffset anymore, which is, however, quite a bit of work, frankly.

We typically use this feature in cases where there are multiple variants of an animation, e.g. the top stack contains a backlit version of an array of radio buttons where as the lower portion contains the same animation but without lighting for the case that the virtual unit is “powered off” (see attachment). Another case would be the reel animation in our tape sim which uses differently colored reels.

The same strategy could still be applied in CMultiFrameBitmaps because the frameSize, number of sub frames and columns is passed explicitly via the CMultiFrameBitmapDescriptor btw.

Using the backOffset it’s quite easy to switch back and forth between the aforementioned variants without having to change the background bitmap and having to issue an additional invalid(). So I don’t think this is such a useless or deprecated feature after all.

Anyway, my main point is that the way CMovieBitmap::draw() and perhaps others are implemented now simply breaks compatibility with existing code in cases where one uses the backOffset which is still accesible through the views’ / controls’ ctor, so this might be confusing and require unnecessary rework on part of the plugin developer. So either these offset leftovers should be removed everywhere including all views’ ctors or they should be properly supported, which I think also implies re-adding the get/setBackOffset() API.

At the very least breaking changes like these should be clearly marked in the changelog imo (no offense here, but this would really help as I found myself in multiple situations where I had to find out what the heck is going on via the debugger after updating to a new version of VSTGUI and I’ve seen other developers going through the same struggle, even using pretty standard stuff).


Thanks, now I understand the use case. It’s like a std::string_view to a std::string.

if breaking changes are not listet in this file then it is not known that there was a breaking change. This can happen as many things in VSTGUI are bad documented if at all in the past (I hope it’s better nowadays). Like the back offset. It is not described anywhere exactly what it does. I’m surprised it is used after all.
Do you have more breaking changes that are not listed?

I will see how the back-offset can be handled in a more elegant way. I think code changes will be required for this to work on your side afterwards as I don’t want to bring it back as this is an implicit feature in the way it is implemented that’s hard to understand as a user of VSTGUI and hard to maintain for a maintainer.

Thanks Arne.

I would be all game for adapting to a new API in order to implement that feature in a sustainable way, as long as we don’t have to implement our own custom control’s whose draw() code must be adapted every time something changes in VSTGUI (== bad software design).

As far as breaking changes go, thanks for the clarification, here are the ones I have noticed so far:

  • The new max. size limits for graphics in CBitmap…saved by the fact that there is CMultiFrameBitmap now, but one needs to adapt to that so I think it would be useful to have the platform specific max. pixel dimensions for bitmaps stated somewhere
  • The get/setBackOffset() stuff discussing above obviously…would be great to have some new and more explicit API here to support the aforementioned use case
  • CFontDescs may not be initialized as statics anymore, instead someone has to use e.g. the new ModuleInitializer and something akin to the implementation in cfont.cpp in order to setup global font refs
  • I have to comment out lines 136/137 in vst2wrapper.cpp (areSizesEqual()…) in order for the GUI resizing to work properly in VST2 (well, to be honest, this qualifies more as a bug in the VST3 SDK)

I’ll do the macOS implementation in the next couple of days and report back / edit the above list in case I find more.

At the risk of becoming off-topic: I noticed that there is a HiDPISupport API in win32dll.h. In VST3 we have IPlugViewContentScaleSupport via which the host can report the scaling factor to the plugin and I use that to propagate that to CFrame::setZoom() in our windows implementation. For all the other formats we use a vendor specific utility function that polls the DPI scaling via IsProcessDPIAware(), GetDC() and GetDeviceCaps() on Windows. I wonder whether it’s possible and/or recommended to replace that by VSTGUI’s HiDPISupport because it would be nice to have things working out of the box as much as possible. Frankly, the High DPI scaling implementation in the Win32 API was always a bit weird IMO.


Is there a particular reason why CAutoAnimation::draw() doesn’t respect the new MFB functions in your 4.12.1 commit?

No, it will be changed to use them too later.