Plugin UIs struggling in Logic's "Dual Mono" mode - SOLVED

@TBProAudio - here is a link to a video capture of DSEQ3 running on Logic on my Mac in Dual Mono mode. Note how the UI “goes blank” at times - which is exactly what my plugins do. If I continue to toggle back and forth it eventually crashes Logic with the error report I posted above.

https://drive.google.com/file/d/1ZT_K2VtXZysGs8X_nvpjOQ3byACzfsvR/view?usp=sharing

Any repeated redrawing results in a system crash. I tested the same thing with me. It’s weird!

Right, GFX card should be OK.

LoadAPIBitmap which is called from LoadBitmap is pure virtual and possibly the culprit here. Since i can’t test myself just a random idea to check if the Bitmap loading fails somehow:
Whenever the UI is closed and reopend, mLayoutFunc will completely reconstruct your controls and ‘reload’ your bitmaps if you placed it there. Try doing the bitmap loading only once by checking against a simple bool when initing the plug. Assign it to a member variable and use that in your controls.

@RexMartin2 - it’s probably the earlier bit of the call stack that would help more (we can’t see why LoadBitmap is being called), but the pure virtual bug looks like you are likely calling LoadBitmap (or it is being called) during a constructor or destructor call to IGraphics - it means that the base class exists, but not the inherited classes that should provide the functionality (likely LoadAPIBitmap) - so you don’t have a fully valid object at the time of the call.

Understood and what you suggest makes sense - but how do we load bitmaps and fonts outside the mLayoutFunc? That would be a completely different approach to what I have been doing. I am currently loading all bitmaps and fonts within the mLayoutFunc (because that’s how it is shown in the iPlug2 example projects) like this:

  mLayoutFunc = [&](IGraphics* pGraphics)
  {
    pGraphics->AttachBackground(BACKGROUND_FN);
    pGraphics->LoadFont("Poppins-Medium", POPPINS_FN);
    pGraphics->AttachCornerResizer(EUIResizerMode::Scale, false, IColor(COLOR_GRAY), IColor(COLOR_GRAY), IColor(COLOR_GRAY), 17.f);

    IBitmap knob = pGraphics->LoadBitmap(MGMETER_FN, kGainMeterFrames);
    pGraphics->AttachControl(new IBitmapControl(587, 156, knob), kCtrlTagMGMeterL);
...

Are you suggesting something like this:

In plugin header:

    IBitmap bMeter;
    bool bMeterloaded = false;

In mLayoutFunct:

    if(!bMeterloaded) bMeter = pGraphics->LoadBitmap(MGMETER_FN, kGainMeterFrames), bMeterloaded = true;
    pGraphics->AttachControl(new IBitmapControl(587, 156, bMeter), kCtrlTagMGMeterL);

That works and would only load control bitmaps once - but what about the Background bitmap and Fonts? How would we load them only once?

Yes you could do it like this.
Since i have a lot of bitmaps/svgs i do all related loading stuff in a seperate function called in mLayoutFunc. I skip that if it has already been executed.
Background and Fonts are loaded as usual in mLayoutFunc.
I simply made it that way because it didn’t make sense to me to repeat that again an again if the UI is closed and reopend.
However since i didn’t double check if and how this affects gui behaviour i don’t know if that is a reasonable solution for the crashes you encounter. Just an idea to check if it could be a workaround.

@TBProAudio @AlexHarker @stw @wcr_hlo

I found the problem here and it is not my plugin - it is Logic Pro X - and I can recreate it with any plugin working in Dual Mono mode.

If I toggle back and forth between the Left and Right instance buttons in Dual Mono mode it works fine. But if the mouse cursor is near the line between the Left and Right buttons when I click it instantly crashes Logic. So it seems, in that case, Logic is trying to display both Left and Right instances at the same time and is crashing as a result.

Please see screen capture here where I tested a known-good plugin (TB’s DSEQ3):
https://drive.google.com/file/d/1ZT_K2VtXZysGs8X_nvpjOQ3byACzfsvR/view?usp=sharing

Please keep this discovery in mind if anyone complains to you about your plugin crashing Logic in Dual Mono mode!

In addition, as @stw suggested, plugin redraw when switching between left/right can be sped up considerably by loading bitmaps only once. I achieved this by simply creating member variables for each bitmap in MyPlugin.h and then checking for their current status when loading in mLayoutFunc like this:

if (!bRedLED.IsValid()) bRedLED = pGraphics->LoadBitmap(REDLED_FN, 2);

Adding this check avoids re-loading the bitmap if it already exists and speeds up redraw time considerably if you have a lot of bitmap controls. If anyone sees a problem with doing this please advise but I’ve been testing and it seems good. (@stw’s approach of checking/loading all at once in a separate function is even faster). Only possible drawback is having bitmaps loaded in RAM when the UI is not open (but RAM is cheap, right?).

So it seems my plugin was working properly all along (but now faster with above change).

2 Likes

Thank you. This is a nice catch.

@AlexHarker @stw - Word of caution on this. I just discovered that - for some odd reason - this “load bitmaps once” approach does not work with IBSliderControl background bitmaps (the “track” bitmaps). Those bitmaps, I have found, have to be loaded every time just before the control is set up in mLayoutFunc otherwise it produces new, random track backgrounds every time the UI is closed and re-opened.

Somehow that control is corrupting the saved bitmap when the UI is closed/reopened if the bitmap is saved as a member variable. The Slider knob itself can be loaded once and saved but not the background bitmap. Very strange.

@RexMartin2 @stw The approaches you have discussed above are not safe or recommended I’m afraid. I’ll explain below.

Bitmaps are not static across the lifetime of the plugin. They are static across the life of the UI(s) (meaning if the plugin has no windows open they don’t exist anymore). This is particularly important in NanoVG where bitmaps are specific to a plugin UI instance whereas for Skia they are shared between UI instances.

Therefore you cannot assume that if mLayoutFunc (or LayoutUI()) has run before you have a valid bitmap loaded (there was a period in iplug2s history where that might have worked, but now UIs are always transitory, so it won’t). If you want to test if you need to redo things when layout is called (which can also be called for other reasons such as window resizing) then you should test more safely. The test I normally do is:

if (!pGraphics->NControls())
{
// build UI
}

in other words - has the UI already been built for the current pGraphics or not?

However - the test above is not always necessary. The default setup is that mLayoutFunc/LayoutUI() are only called on a new UI (so the test is not needed). However, if you’ve called SetLayoutOnResize(true) then you will get called here at other times and in this case if you do not check to see if there are already controls on your UI you risk stacking up layers of controls on each call to mLayoutFunc

Additionally to all of this because bitmap loading is cached, when you call LoadBitmap() it checks the cache to see if the bitmap is already loaded, and if so just returns that, and so:

A - it isn’t related to the issue discussed here
B - isn’t really saving any resources to try not to call it

Fonts and SVGs are similarly managed.

Generally it is unwise to store pointers/objects relating to graphics in the plugin class, because of the transient lifetime of the UI.

From the look of the above Logic is likely not managing memory correctly. The lack of consistency between different stack traces would make that my guess.

@AlexHarker - Yes, understood here. The UI is created and destroyed every time it is opened and closed.

In creating the UI, bitmaps are handled in two steps in mLayoutFunc. The bitmaps are first “loaded” to local IBitmap variables after which they are “attached” to the UI (pGraphics). Because the bitmaps are loaded locally in pGraphics they are destroyed with pGraphics when the UI closes and, therefore, have to be reloaded every time mLayoutFunc is called.

What I am doing is loading the bitmaps to IBitmap member variables declared outside mLayoutFunc (in MyPlug.h). The first time mLayoutFunc runs the bitmaps are “loaded” to those outside members. The next time the UI is opened those members already contain the bitmaps so that “loading” step can be skipped thereby reducing redraw time. It works! (Mostly)

As I cautioned above, this approach does not work - for some reason - with the IBSliderControl “track” (background) bitmaps. Those bitmaps have to be loaded every time mLayoutFunc is called otherwise the slider background corrupts on UI close/re-open. Maybe that control uses the background bitmap memory location as a buffer when creating the slider bitmap overlay? IDK, but this “load bitmap one time” shortcut does not work there.

Except it’s only cached there while the UI is open, right? (because it loads them to local variables inside mLayoutFunc). What we are doing is “cache-ing” those bitmaps to plugin member variables outside mLayoutFunc that stay in memory as long as the plugin itself exists.

Testing in Logic’s Dual Mono mode - where we can toggle rapidly between two UI instances - shows the UIs switch faster with bitmaps loaded only once vs. every time mLayoutFunc is called. The UI also seems to re-open faster after being closed in other DAWs too. So it does seem there is an advantage to it.

My apologies for mixing two topics in this thread. Is this comment in regard to Logic crashing in Dual Mono mode (the OP)? What I have concluded is that Logic’s Left/Right UI controls are erroneously overlapped. So if you happen to click near the line between those two buttons it tries to load both Left and Right plugin UIs at the same time - which crashes Logic. This crash is repeatable every time.

You could also be right in that there may be memory management issues in Logic as well. In either case, the Dual Mono crash issue seems to be an issue in Logic and not our plugins.

There are several crucial bits of info here:

1 - the IBitmap structure doesn’t hold the actual bitmap - it just references one that is stored with IGraphics.

2 - the caching of the actual bitmaps is done in IGraphics using StaticStorage<>. The members you get back from LoadBitmap are lightweight references that are invalid once the bitmap has left the cache.

3 - the bitmaps get destroyed with the UI on NanoVG, but with skia if you have two (or more) plugin windows open there is only one bitmap in memory and that stays around until both windows (or all windows) are closed).

So what you store when you do the above is a lightweight reference (a pointer plus other info) that is invalidated when all plugin windows are shut. The fact it appears to working some cases is probably an indicator that some memory has not been touched/cleared (it is working by accident) but it is 100% unsafe, and there is no advantage to it that you can actually gain in the scenario you are looking at. Yes it may be faster if you don’t load the bitmaps on a particular plugin window opening, but the issue is you are not loading them in a situation where they don’t really exist and you do actually need to load them.

My last comment is about crashing. I cannot see anything in this thread at the moment which looks like a definite crash related to iplug2/the specific plugins under test. My reading was that all crashes are explained by the Logic issue. If that is not the case we can discuss further but right now the approach to bitmaps above is not fixing anything and risks crashing/incorrect rendering.

Thanks for clarification @AlexHarker!
Without digging too deep into the code i assumed mGraphics (as owner of StaticStorage<>?) would be bound to the lifetime of the plug. Now i see it’s not which obviously makes my approach a no go. Though the whole time working that way combined with all kind of additional memory allocations never brought me into a some ‘corrupted heap’ situation.
Nevertheless i don’t exactly grasp why it’s designed hat way. I’d guess the most probable usecase for plugins is opening/closing the ui when needed. Which means cashing would be no benefit right now but some noticeable overhead is introduced due to the need of reloading all graphics. Is there any benefit/necessity or “You simply can’t do it differently” i don’t see?

1 Like

I agree with this opinion! Need improvements in the third version of iPlug

Dual-mono mode is definitely crashing with all backends, including Skia CPU in Logic 10.7.2. Made an issue to track it. https://github.com/iPlug2/iPlug2/issues/833. I am not sure if it is a Logic bug or iPlug2 bug, but will investigate. If someone can try with Logic < 10.7.2 that would be interesting.

Reread this thread which has gone a bit OT!

Nice catch @RexMartin2 I will file a bug with Apple for that

1 Like

When I can reproduce with a non-iPlug2 plugin i will be convinced it is a Logic bug

Tested a whole bunch of different plugins from different manufacturers… Result:

Logic 10.5.1 no crashes at all.

Logic 10.7.2 instant crashes ONLY with IPLUG2 builds when hitting the middle area of the L R switches. All other plugs, incl. IPLUG1 don’t crash.
Just for the sake of considering even the weirdest possible source. Look at the small difference in Logics layout of the controls, which might create some sort of double triggering??
V 10.5.1: Bildschirmfoto 2022-02-06 um 17.48.47
V 10.7.2: Bildschirmfoto 2022-02-06 um 17.50.05

short debuging:
IGEditorDelegate wants to open the (new) window while mGraphics gets destructed. So mGraphics is still valid when it’s checked in OpenWindow() before it’s been destructed and therefore no new IGraphics instance is created.

1 Like

@stw Thank you for that screenshot. It will come in handy if someone complains about our plugins crashing Logic in Dual Mono mode!

Your zooming in confirmed what I suspected - the buttons are overlapped. That is a bug in Logic. But if we can make iPlug more robust by adding some kind of check for this would be a good thing - and maybe help elsewhere for situations yet discovered.

Thank you, everyone, for taking a look at this.