COCKOS
CONFEDERATED FORUMS
Cockos : REAPER : NINJAM : Forums
Forum Home : Register : FAQ : Members List : Search :

Go Back   Cockos Incorporated Forums > Other Software Discussion > WDL users forum

Reply
 
Thread Tools Display Modes
Old 08-06-2020, 10:51 PM   #1
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default ISwitchControl bitmap getting out of sync on AAX

Problem with ISwitchControl running in Pro Tools (AAX). Clicking on a switch when the transport is running changes the control’s state (toggles it) but the bitmap SOMETIMES doesn’t change - causing the bitmap and control state to become out of sync.

This does not happen when playback is stopped nor in VST, VST3 or AU versions. There's obviously an issue with Draw called in the ISwitchControl but I don’t know why it only happens in Pro Tools and only on Mac OS.

I made a workaround by calling parameterIdx->Redraw() for the control in OnParamChange(). It works but is it “safe” to force a redraw from there? I would rather fix the problem with ISwitchControl if possible.

Anyone else come across this?

TBPro pointed me to the cause - it's in iGraphics.cpp - so it could actually be a latent problem in all formats. Every occurrence of pControl->Draw(this) in the IGraphics Draw() method needs to be immediately followed with pControl->SetClean(). In other words they have to form a pair. I found that pControl2 (in a loop through all controls) had no SetClean() at all so I added it. With these changes the iSwitch sync problem is solved. Could be that other plugin formats have faster/different GUI threads so this issue only appeared in Pro Tools, IDK.

Last edited by Nonlinear; 08-29-2020 at 09:36 AM.
Nonlinear is offline   Reply With Quote
Old 08-29-2020, 09:41 AM   #2
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

Quote:
Originally Posted by Nonlinear View Post
TBPro pointed me to the cause - it's in iGraphics.cpp - so it could actually be a latent problem in all formats. Every occurrence of pControl->Draw(this) in the IGraphics Draw() method needs to be immediately followed with pControl->SetClean(). In other words they have to form a pair. I found that pControl2 (in a loop through all controls) had no SetClean() at all so I added it. With these changes the iSwitch sync problem is solved. Could be that other plugin formats have faster/different GUI threads so this issue only appeared in Pro Tools, IDK.
Nope - still a problem in AAX. Everything works fine when transport is stopped but if transport is running buttons don't always update correctly - ISwitch status changes but the associated bitmap SOMETIMES does not causing the displayed value and actual value to become out of sync.

I can FORCE a redraw of the control in OnParamChange() - and it works - but why do I need to do that? (It's a "band aid" I'd rather not have to use)

Any other suggestions? Again, it works fine everywhere else - just not AAX/Pro Tools and only on Mac (works fine in Windows).

Last edited by Nonlinear; 08-29-2020 at 08:37 PM.
Nonlinear is offline   Reply With Quote
Old 08-29-2020, 10:29 PM   #3
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

UPDATE: It seems I found the fix. In addition to TBPro's code changes I made this additional change in IControl.cpp:

Code:
void IControl::SetClean()
{
 //  mDirty = mRedraw;
  mDirty = true;
  mRedraw = false;
}
So it seems you can't rely on mRedraw being true when entering this code. Forcing it true here fixed the problem in Pro Tools on Mac. Also checked good in Reaper and Logic Pro.
Nonlinear is offline   Reply With Quote
Old 08-30-2020, 10:34 PM   #4
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
Default

But doesn't that cause the GUI to be redrawn constantly (presumably at only 24 FPS, but still)?
Tale is offline   Reply With Quote
Old 08-31-2020, 05:34 PM   #5
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

Quote:
Originally Posted by Tale View Post
But doesn't that cause the GUI to be redrawn constantly (presumably at only 24 FPS, but still)?
I didn't see a performance hit in Pro Tools after making these changes so I didn't think to check. But now I have and, yes, "SetClean()" is getting called constantly. Maybe it has been doing this all along, IDK, I wasn't looking for it. I update my GUI on every block (meters, etc.) so maybe the performance hit is being masked by those updates. I temporarily disabled my GUI update code in the processing blocks but SetClean() is still being called from somewhere.

Ugh, OK, so there must be a "root cause" fix for the issue. It's strange - and perhaps a clue - that I'm only seeing this problem in Pro Tools and only on Mac. I've looked through iPlug's Mac and AAX -specific files but don't see a possible cause. Debugger struggles for some reason in Pro Tools so I'm having a tough time tracing the steps there - further complicated by the fact that it's an intermittent problem.

BTW - the iGraphics.cpp code looks a little funny to me too. It checks if(mStrict) and mStrict appears to always be true - which I believe means it constantly redraws. Is that why SetClean() keeps getting called? Also, the function itself seems to be recursive in that it calls pControl->Draw() from inside the Draw() function. Isn't this an endless loop? I don't understand what's going on here...

Last edited by Nonlinear; 08-31-2020 at 09:21 PM.
Nonlinear is offline   Reply With Quote
Old 09-01-2020, 01:14 AM   #6
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
Default

Actually pGraphics->Draw() calls pControl->Draw(), so there is no recursion.

Anyway, this is how it should work: If you change a parameter, then the control is marked as dirty by calling pControl->SetDirty(). The next time pGraphics->IsDirty() is called it will detect that one or more controls are dirty by checking IControl->IsDirty(). If dirty then pGraphics->Draw() will draw controls by calling pControl->Draw() for each of them, and then mark the control as clean by calling pControl->SetClean(). Then the next time pGraphics->IsDirty() is called no controls will be dirty, and so nothing will be redrawn until there is another change.

Now, if you set mDirty to true in pControl->SetClean(), then pGraphics->IsDirty() will always return true, and so pGraphics->Draw() will be called each and every time, by default at 24 FPS.

BTW, you can disable strict drawing by calling pGraphics->SetStrictDrawing(false), but by default strict drawing is enabled.
Tale is offline   Reply With Quote
Old 09-01-2020, 10:57 AM   #7
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

Quote:
Originally Posted by Tale View Post
Now, if you set mDirty to true in pControl->SetClean(), then pGraphics->IsDirty() will always return true, and so pGraphics->Draw() will be called each and every time, by default at 24 FPS.
ISwitchControl does not work reliably in Pro Tools without it so what is the correct fix? There must be something wrong in IControl, IGraphics or IPlugAAX somewhere.

I guess I don't understanding how the GUI is redrawn in iPlug. I thought the GUI was redrawn once per processing block. So, where does "FPS" come into this? Are controls constantly scanned for changes (at 24FPS) but then only updated/redrawn WHEN/by what call?

Again, the problem I'm having with ISwitchControl is only occurring when the transport is running. So apparently the processing block is "stepping on" the controls update - and perhaps "cleaning" - or otherwise interfering with - the ISwitchControls before their bitmap has been updated. How to fix that?

Quote:
Originally Posted by Tale View Post
The next time pGraphics->IsDirty() is called it will detect that one or more controls are dirty by checking IControl->IsDirty(). If dirty then pGraphics->Draw() will draw controls by calling pControl->Draw() for each of them, and then mark the control as clean by calling pControl->SetClean().
The "Draw" function in my IGraphics file (from WDL-OL) doesn't check if the control is Dirty:

Code:
  if (mStrict)
  {
    mDrawRECT = *pR;
    int n = mControls.GetSize();
    IControl** ppControl = mControls.GetList();
    for (int i = 0; i < n; ++i, ++ppControl)
    {
      IControl* pControl = *ppControl;
      if (!(pControl->IsHidden()) && pR->Intersects(pControl->GetRECT()))
      {
        pControl->Draw(this);
	pControl->SetClean();
      }
    }
  }
It DOES check IsDirty() if NOT "mstrict"

Code:
     else
    {
      for (i = 1; i < n; ++i)   // loop through all controls starting from one (not bg)
      {
        IControl* pControl = mControls.Get(i); // assign control i to pControl
        if (pControl->IsDirty())   // if pControl is dirty
        {

          // printf("control %i is Dirty\n", i);

          mDrawRECT = *(pControl->GetRECT()); // put the rect in the mDrawRect member variable
          for (j = 0; j < n; ++j)   // loop through all controls
          {
            IControl* pControl2 = mControls.Get(j); // assign control j to pControl2

            // if control1 == control2 OR control2 is not hidden AND control2's rect intersects mDrawRect
            if (!pControl2->IsHidden() && (i == j || pControl2->GetRECT()->Intersects(&mDrawRECT)))
            {
              //if ((i == j) && (!pControl2->IsHidden())|| (!(pControl2->IsHidden()) && pControl2->GetRECT()->Intersects(&mDrawRECT))) {
              //printf("control %i and %i \n", i, j);

              pControl2->Draw(this);
			  pControl2->SetClean();
            }
          }
          pControl->SetClean();
        }
      }

Last edited by Nonlinear; 09-01-2020 at 03:14 PM.
Nonlinear is offline   Reply With Quote
Old 09-02-2020, 12:42 AM   #8
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
Default

There is a timer callback in IGraphicsWin.cpp, IGraphicsCocoa.mm, and IGraphicsCarbon.cpp that checks pGraphics->IsDirty(). If it returns true (which means one or more controls are dirty), then it tells the OS that the rectangle containing the dirty controls needs redrawing. Then IPlug gets a callback from the OS to redraw (Windows: WM_PAINT; Cocoa: drawRect; Carbon: kEventControlDraw), which calls pGraphics->Draw() with the rectangle that needs redrawing. In strict mode IPlug then simply redraws everything within that rectangle, dirty or not. It also calls pControl->SetClean() for each control, so after pGraphics->Draw() all controls will be clean.
Tale is offline   Reply With Quote
Old 09-02-2020, 10:28 AM   #9
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

Ok, so what in all of this is likely causing the problem I am seeing? It only occurs in AAX, only on Mac and only when the transport is running - ISwitchControl bitmap not staying in sync with value.

Last edited by Nonlinear; 09-02-2020 at 08:28 PM.
Nonlinear is offline   Reply With Quote
Old 09-03-2020, 12:26 AM   #10
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
Default

I don't know... But I do know it actually works the other way around: When you click on a switch OnMouseDown() is called, which updates the control value, and then forwards this value to parameter by calling SetDirty(), which in turn calls SetParameterFromGUI().

This always happens, so it should always work, but SetParameterFromGUI() is protected by a mutex lock, so maybe this could be a race condition in Pro Tools? If so, then I would expect this to also happen with other controls, also with other WDL-OL AAX plug-ins.

BTW, is this with a stock ISwitchControl, or a control derived from ISwitchControl? How many states does the switch have?
Tale is offline   Reply With Quote
Old 09-03-2020, 11:59 AM   #11
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

Quote:
Originally Posted by Tale View Post
BTW, is this with a stock ISwitchControl, or a control derived from ISwitchControl? How many states does the switch have?
Yes, stock ISwitchControl with 2 states (On/Off). When Pro Tool's transport is running the control's VALUE toggles but the bitmap sometimes does not causing it to become out of sync. When transport is paused the control works perfectly every time.

Constructor:
Code:
       GetParam(kProcessControl)->InitBool("Effect", Process, "Process");
       bitmap = pGraphics->LoadIBitmap(PROCESSBTN_ID, PROCESSBTN_FN, kBtnFrames);
       mProcessControlIdx = new ISwitchControl(this, 297, 69, kProcessControl, &bitmap);
       pGraphics->AttachControl(mProcessControlIdx);
       GetParam(kProcessControl)->SetDisplayText(0, "Bypass");
       GetParam(kProcessControl)->SetDisplayText(1, "Process");
OnParamChange():
Code:
case kProcessControl:
	{
	Process = GetParam(kProcessControl)->Value();
	mProcessControlIdx->Redraw();//--- needed for Pro Tools Mac - don't know why!!
	}
break;
And those are the only places I touch this control!

Quote:
Originally Posted by Tale View Post
I don't know... But I do know it actually works the other way around: When you click on a switch OnMouseDown() is called, which updates the control value, and then forwards this value to parameter by calling SetDirty(), which in turn calls SetParameterFromGUI().
I don't know if it's the bitmap or the control value itself that is not updating - but the parameter value IS changing - I can see and/or hear it's effect when toggled.

Quote:
Originally Posted by Tale View Post
This always happens, so it should always work, but SetParameterFromGUI() is protected by a mutex lock, so maybe this could be a race condition in Pro Tools? If so, then I would expect this to also happen with other controls, also with other WDL-OL AAX plug-ins.
Yes, I suspect it is a race condition/conflict somewhere however I have been unable to debug it as Pro Tools is refusing to play audio while in debug mode. I have seen this ISwitch problem happen in other AAX plugins I have built - that's why I finally brought it up here. It very well could be something derelict in my code but then why does VST/VST3 and AU work properly as well as AAX on Windows? All other controls work as intended.

I have changed SetClean() back to what it was and temporarily fixed the problem by forcing a Redraw after updating the parameter's value in OnParamChange(). I don't know WHY I have to do that but it works and is a better solution than constant redraw.

Yes, it is strange and certainly troubling. I know there is something wrong somewhere.

Last edited by Nonlinear; 09-05-2020 at 09:34 AM.
Nonlinear is offline   Reply With Quote
Old 09-05-2020, 09:32 AM   #12
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

I now suspect this is an issue with Mutex Locks somewhere. It only happens in Pro Tools, only on Mac and only when the transport is running. So, I'm assuming it must be somewhere in one of the iPlug AAX or Mac files.

How to hunt this down?

Last edited by Nonlinear; 09-05-2020 at 10:22 AM.
Nonlinear is offline   Reply With Quote
Old 09-05-2020, 11:07 AM   #13
stw
Human being with feelings
 
stw's Avatar
 
Join Date: Apr 2012
Posts: 279
Default

Quote:
Originally Posted by Nonlinear View Post
I now suspect this is an issue with Mutex Locks somewhere. It only happens in Pro Tools, only on Mac and only when the transport is running. So, I'm assuming it must be somewhere in one of the iPlug AAX or Mac files.

How to hunt this down?
If you want to check for a mutex problem you could temporarily comment out the mutex lock in IPlugAAX.cpp.
Code:
void IPlugAAX::SetParameterFromGUI(int idx, double normalizedValue)
{
//    Trace(TRACELOC, "%d:%f", idx, normalizedValue);
//    WDL_MutexLock lock(&mMutex);                <-- this line
    GetParam(idx)->SetNormalized(normalizedValue);
    InformHostOfParamChange(idx, normalizedValue);
}
stw is offline   Reply With Quote
Old 09-05-2020, 05:34 PM   #14
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

I have narrowed down the source of my problem - but still not the cause.

At the end of my ProcessDoubleReplacing() block, after the "Sampleframes" loop, I have a section "if(GetGUI())" where I update all the plugin's meters and other display controls using "GetGUI()->SetControlFromPlug(Controlldx, value)". I have a dozen IBitmapControls that I update here. If I comment out random parts of this code the ISwitchControl (which is not touched anywhere in this block) works properly.

It's one of those "why is it doing THAT?!" situations that makes no sense. Something in the display update from ProcessDoubleReplacing() is stepping on the ISwitchControl GUI update - and ONLY the ISwitchControl - all other controls and displays work properly.

Any advice on how to solve this?

BTW - in Pro Tools, like many other DAWs, ProcessDoubleReplacing() is called even when the transport is not running. So it is odd that the problem I am seeing only occurs when the transport IS running. Any clues there?

Last edited by Nonlinear; 09-06-2020 at 07:52 AM.
Nonlinear is offline   Reply With Quote
Old 09-06-2020, 02:23 AM   #15
stw
Human being with feelings
 
stw's Avatar
 
Join Date: Apr 2012
Posts: 279
Default

Yes, this very likely seems to be the root of your problem. IMHO it's not a good idea to trigger drawings that way.
For controls that need a regular draw update i have a dedicated control that triggers all drawings on the GUI thread (similar to your IControl approach - mDirty always true, but only for that 'trigger' control) thru a function pointer. So only the added controls inside that function are redrawed at fps rate.
Implementation here looks like this:
Code:
// Trigger Control Template in my ctrls.h

template <class T>
class DrawDummy : public IControl
{
private:
	typedef void (T::*void_Ptr)();
	void_Ptr voidPtr;
	T   *parent;
	
public:
	DrawDummy(IPlugBase *pPlug)
	: IControl(pPlug, IRECT(0,0,0,0)){}
	
	void setPtr(T *_Parent, void (T::*f)())
	{
		voidPtr = f;
		parent = _Parent;
	}
	
	bool IsDirty()
	{
		((parent)->*(this->voidPtr))();
		return true;
	}
	
	bool Draw(IGraphics* pGraphics) { return true;};
};
Attach and set drawing function in the GUI init section:
Code:
pGraphics->AttachControl(MyDrawdummy = new DrawDummy<MyPlug>(this));
MyDrawdummy->setPtr(this, &MyPlug::DoGraphics);
and then doing all the drawing stuff inside the DoGraphics function:
Code:
void MyPlug::DoGraphics()
{
        ctrl1->SetValueFromPlug(x);
        ctrl2->SetValueFromPlug(y)

...etc
};
Maybe you can try it this way.
stw is offline   Reply With Quote
Old 09-06-2020, 08:24 AM   #16
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

Yes, my next question/thought was how to update all display control values and then redraw ONCE at the end? AFAIK, SetControlFromPlug() triggers a redraw every time it is called (a dozen times per block in this case). I thought perhaps the total GUI update was just taking too long - however I tried forcing a total redraw of everything at the end of the GUI block using SetAllControlsDirty() and it works! So apparently it's NOT taking too long and something else is causing the problem (i.e., something is "cleaning" my ISwitchControl before it has been redrawn).

Will take a look at what you've presented here. Thank you.

Also, is there ANY other means to update control values besides SetControlFromPlug()? With parameters you can directly set/get the mValue - any way to do that with controls?

Last edited by Nonlinear; 09-06-2020 at 08:52 AM.
Nonlinear is offline   Reply With Quote
Old 09-07-2020, 03:11 PM   #17
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

After further digging I now understand what is going on.

My iSwitchControl sets a parameter (audio Process/Bypass) which is checked in my ProcessReplacing() GUI update section. If "bypassed" I set all meters and display controls to 0, otherwise I update them.

If that switch control is changed (by user input or automation) during the display update it's redraw is interrupted and never takes place. If I check IsDirty() for the switch control after that happens it shows "true". If I then force a redraw of the control it updates and matches the parameter value.

So, now the question, why is updating the display controls in ProcessReplacing() interfering with pending redraw of the changed parameter control? Does this point back to a mutex lock issue - or something more obvious?

Last edited by Nonlinear; 09-07-2020 at 04:09 PM.
Nonlinear is offline   Reply With Quote
Old 09-08-2020, 12:49 AM   #18
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
Default

Quote:
Originally Posted by Nonlinear View Post
So, now the question, why is updating the display controls in ProcessReplacing() interfering with pending redraw of the changed parameter control? Does this point back to a mutex lock issue - or something more obvious?
AFAIK IPlug only uses mutex locks to protect parameters from changing while processing audio. I don't think there are any mutex locks protecting the GUI. So I guess the scenario is:
  1. User clicks control, which toggles control value; it tries to update parameter, but it can't right now, so it waits for mutex to release.
  2. While mutex is still locked control is set to zero (i.e. control value changes, dirty flag is set, but don't push to plug).
  3. Mutex is released, so param is updated to control value from when user clicked on it.

In this scenario the parameter will have the value the user expects it to, but the control value will always end up being zero.
Tale is offline   Reply With Quote
Old 09-08-2020, 10:24 AM   #19
Nonlinear
Human being with feelings
 
Join Date: Apr 2018
Posts: 396
Default

Quote:
Originally Posted by Tale View Post
AFAIK IPlug only uses mutex locks to protect parameters from changing while processing audio. I don't think there are any mutex locks protecting the GUI. So I guess the scenario is:
  1. User clicks control, which toggles control value; it tries to update parameter, but it can't right now, so it waits for mutex to release.
  2. While mutex is still locked control is set to zero (i.e. control value changes, dirty flag is set, but don't push to plug).
  3. Mutex is released, so param is updated to control value from when user clicked on it.

In this scenario the parameter will have the value the user expects it to, but the control value will always end up being zero.
That appears to be the scenario except for your last sentence - the control doesn't always end up zero, it's that it isn't redrawn. The parameter changes but the control bitmap does not.

I have "solved" the problem by redrawing the control upon return to OnParamChange(). It works but seems a band aid for something systemically not right, i.e., why isn't redraw of the control automatically resumed after the audio thread mutex lock is released? The control update is being left in a "half finished" state.

Last edited by Nonlinear; 09-08-2020 at 10:37 AM.
Nonlinear is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 04:29 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.