Recent

Author Topic: Cocoa - BigSur - solution to the massive paint performance problem  (Read 1454 times)

freq

  • New Member
  • *
  • Posts: 22
Hi all,

let me share our experiences and solution finding proposals to an super annoying issue that created massive paint performance problems under macOS BigSur. It causes us weeks to find out and we're happy for any feedback and cocoa expert reviews (and of coarse please feel free to patch it in if you find it valuable):

The drawrect issue was discovered by macOS developers late 2020 and is well described here:

https://developer.apple.com/forums/thread/663256

And also the following thread discussed the same issue in the context of the JUCE framework:

https://forum.juce.com/t/apparent-serious-juce-painting-issue-in-big-sur/43430/22

Regarding Lazarus / Cocoa WidgetSet we could easily reproduce the issue by observing several UI thread blocking and unnecessary calls to the following procedure once invalidating individual controls:
 
Code: Pascal  [Select][+][-]
  1. cocoawscommon.pas
  2.  
  3. procedure TLCLCommonCallback.Draw(ControlContext: NSGraphicsContext; const bounds, dirty: NSRect);  


Here is our solution:

#1 Issue & Fix

The first problem we discovered was that the call to

Code: Pascal  [Select][+][-]
  1.  
  2. procedure TLCLCommonCallback.Draw(ControlContext: NSGraphicsContext; const bounds, dirty: NSRect);
  3. ...
  4. LCLSendPaintMsg(Target, HDC(FContext), PS)

also repaints the parent control that might not be invalidated because macOS doesn't provides the right clipping of the context by default (as expected):

The fix is to include the dirty area into the clip region:
Code: Pascal  [Select][+][-]
  1. cocoawscommon.pas
  2.  
  3.  
  4. var  clipr:TRect;
  5.        SaveIndex:integer;    
  6.  
  7.  if FContext.InitDraw(Round(bounds.size.width), Round(bounds.size.height)) then
  8.     begin
  9.       nsr:=dirty;
  10.  
  11.       nsr.origin.y:=bounds.size.height-dirty.origin.y-dirty.size.height;
  12.  
  13.       clipr:= NSRectToRect(nsr);
  14.  
  15.       SaveIndex := SaveDC(HDC(FContext));
  16.  
  17.       IntersectClipRect(HDC(FContext),clipr.Left,clipr.Top,clipr.Right,clipr.Bottom);  // this will fix the unecessary redrawing of the non affected parent control
  18.  
  19.      if FIsOpaque and (Target.Color<>clDefault) then
  20.       begin
  21.         FContext.BkMode:=OPAQUE;
  22.         FContext.BkColor:=Target.Color;
  23.         FContext.BackgroundFill(nsr);
  24.         //debugln('Background '+Target.name+Dbgs(NSRectToRect(dirty)));
  25.       end;
  26.  
  27.       New(PS);
  28.       try
  29.         FillChar(PS^, SizeOf(TPaintStruct), 0);
  30.         PS^.hdc := HDC(FContext);
  31.         PS^.rcPaint := NSRectToRect(nsr);
  32.  
  33.  
  34.        LCLSendPaintMsg(Target, HDC(FContext), PS);
  35.  
  36.         if FHasCaret then
  37.          DrawCaret;
  38.       finally
  39.         Dispose(PS);
  40.       end;
  41.  
  42.  
  43.       RestoreDC(HDC(FContext), SaveIndex);

#2 Issue and Fix

Unfortunately that didn't fixed the random calls of the drawrect for non invalidated areas - it became a bit better when we applied the following fix (inspired by the developer discussions mentioned above):

Code: Pascal  [Select][+][-]
  1. cocoaprivate.pas
  2.  
  3. TCocoaCustomControl = objcclass(NSControl)  
  4. ...
  5. procedure viewWillDraw; override;
  6. ..
  7.  
  8. var
  9. kCAContentsFormatRGBA8Uint: NSString; cvar; external;  
  10.  
  11. type
  12. LCLCALayerExtensions = objccategory external(CALayer)
  13.      procedure setContentsFormat(newValue: NSString); message 'setContentsFormat:';
  14. end;  
  15.  
  16. implementation
  17. procedure TCocoaCustomControl.viewWillDraw;
  18.  
  19. begin
  20.  inherited;
  21.   LCLCALayerExtensions(self.layer).setContentsFormat(kCAContentsFormatRGBA8Uint); //NSStringUTF8('kCAContentsFormatRGBA8Uint'));
  22.  
  23. end;


#3 Issue and Fix

That reduced the calls effectively but we're still observed unnecessary calls and UI thread blockings - the last step we applied was to make the draw layers async:

Code: Pascal  [Select][+][-]
  1. cocoawscommon.pas
  2.  
  3. class function TCocoaWSCustomControl.CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLIntfHandle;
  4. ...
  5.   ctrl.setWantsLayer(True);                    
  6.   ctrl.layer.setDrawsAsynchronously(True);    
  7. ...
  8.      
  9.  

With these 3 fixes we were able to increase the ui performance under macOS BigSur massively. Happy for your review and feedback!

Best
freq
« Last Edit: February 10, 2021, 01:31:22 pm by freq »

skalogryz

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 2770
    • havefunsoft.com
Re: Cocoa - BigSur - solution to the massive paint performance problem
« Reply #1 on: February 11, 2021, 02:13:49 am »
is it BigSur only issue?
shouldn't it be fixed by Apple in BigSur itself?


Apple documentation is a bit "vague" about using setDrawsAsynchronously:
Quote
Performing these commands asynchronously can improve performance in some apps. However, you should always measure the actual performance benefits before enabling this capability.
From this description it feels like "it might or might not help... good luck". Very uncertain.
Having a background thread working is a nice consumption of resources as well. (We shouldn't mention that there are plenty of background threads working already anyway)


It might be that setWantsLayer should be set once for a Window content view.
IIRC using the layers drawing is forced since macOS 10.14, not really use if the explicit call is needed at all.
« Last Edit: February 11, 2021, 02:30:24 am by skalogryz »

VTwin

  • Hero Member
  • *****
  • Posts: 1215
  • Former Turbo Pascal 3 user
Re: Cocoa - BigSur - solution to the massive paint performance problem
« Reply #2 on: February 11, 2021, 03:23:39 am »
Has this been filed as a bug report?

skalogryz has been on task in fixing cocoa bugs.
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 12.1: Lazarus 2.2.6 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.6 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.6 (64 bit on VBox)

trev

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 2020
  • Former Delphi 1-7, 10.2 user
Re: Cocoa - BigSur - solution to the massive paint performance problem
« Reply #3 on: February 11, 2021, 07:36:27 am »
Has this been filed as a bug report?

It seems the jury is still out as to whether this is a Big Sur bug that Apple will (eventually) fix provided enough developers log a bug/feedback with them. It would be more useful if someone would hit up Apple with a TSI to be sure.

 

TinyPortal © 2005-2018