How to support larger NSView hierarchy?
How to support larger NSView hierarchy?
- Subject: How to support larger NSView hierarchy?
- From: Keith Knauber <email@hidden>
- Date: Tue, 28 Feb 2012 00:50:36 +0000
- Thread-topic: How to support larger NSView hierarchy?
In my implementation, I know enough about my very large NSView hierarchy that allows me to clip and draw the
visible hierarchy very quickly.
If there was a faster way to getRectsBeingDrawn for an entire view hierarchy in an offscreen window,
and then reset NSWindow dirty rects needing display manually, then my divide and conquer strategy could
smoothly support 1000's of views in several offscreen windows that feed into one onscreen window NSScrollView.
My questions:
Is there a way to quickly reset needsDisplay for all views in an offscreen windows' hierarchy after the
viewWillDraw phase is complete, without incurring the huge overhead of actually drawing the hierarchy?
Most of the time my app only needs to display < 100 NSControls... However, I need to provide the user the option
of displaying a lot more in an NSScrollView.
Can someone at Apple explain what setNeedsDisplay:NO actually does, if anything?
( setNeedsDisplay:NO doesn't clear needsDisplay when you recurse the entire view hierarchy calling setNeedsDisplay:NO,
unless there's something I'm missing)
If it did, then getting the dirty rects would still be slow, but at least be cut in half.
The goal is to have a very responsive UI, while freeing up as much CPU as possible for other apps on my machine that do OpenGL drawing.
// The problem: large NSView hierarchies are very slow at doing just about anything, including tracking dirty rects.
// - Cocoa takes 7ms for a hierarchy of 256 views to redraw 16 dirty views
// This equates to ~ 20% CPU usage when redrawing 30 times per second.
// unavoidable 1.5 ms spent in drawRect: (images/strings/NSFillRect)
// unavoidable 1.2 ms spent in viewWillDraw
// avoidable! 4.3 ms spent clipping, lockFocus, supporting wacky features
// - there is no way to avoid the overhead. things tried:
// - isOpaque. this helps a lot, but not enough
// - layer-backed. this helps very little, and incurs huge overhead in CVDisplayLink thread
// - useOptimizedDrawing. this helps a little.
I'm faced with several choices.
1) convert NSView hierarchy to my own hierarchy of NSCells (started going down this path... layout and mouse handling becomes way too cumbersome)
2) override Cocoa private API (trying really hard to avoid this)
3) override only public API, and use a divide and conquer strategy. Draw smaller NSView hierarchies myself using getRectsBeingDrawn
I've had some success so far with a combo of overriding a ton of public API to create a set of classes that
draws my NSView hierarchy in ~3 ms instead of ~20ms. However, I've had to override so much public API that I'm worried
this won't be supported going forward. So far it works well in both Snow Leopard and Lion, which is encouraging.
Below is a summary of my current solution, without posting my entire solution just yet.
It uses only public API.
In a nutshell, similar to the way that NSWindow only has one NSTextView field editor at a time, my onscreen hierarchy only has one
NSControl onscreen at a time (the one that the user is interacting with).
I would not have to override so much public API if I could just get the rects needing display from NSWindow or NSView,
and then tell the offscreen hierarchy that it no longer needs to be drawn (I tried [NSView setNeedsDisplay:NO]; of course... doesn't solve the problem)
@implementation NSControl (SkipOffscreenDraw)
- (BOOL) wantsDefaultClipping { return !sDisplayingOffscreenWindow; }
- (BOOL) canDraw
- (NSRect) convertRect:(NSRect)aRect toView:(NSView *)aView
- (NSRect) convertRect:(NSRect)aRect fromView:(NSView *)aView
- (NSRect) visibleRect
- (BOOL)isHiddenOrHasHiddenAncestor
#pragma mark -
@implementation NSOffscreenWindow
- (BOOL)canBecomeKeyWindow { return NO; }
- (BOOL)canBecomeMainWindow { return NO; }
- (BOOL)areCursorRectsEnabled { return NO; }
- (BOOL)autorecalculatesKeyViewLoop { return NO; }
- (BOOL)isVisible { return NO; }
- (void)update { return; }
- (BOOL) viewsNeedDisplay
- (NSScreen *)screen { return [NSScreen mainScreen]; }
- (void) setGraphicsContextWithView: (NSView *)iView
-(void)displayIfNeeded
- (void)setOnScreenPseudoRootView:(PseudoRootView *)iView { onScreenPseudoRootView = iView; }
- (void)cursorUpdate:(NSEvent *)event
- (BOOL) acceptsFirstResponder { return NO; }
- (void)invalidateCursorRectsForView:(NSView *)aView
// an NSWindow subclass with every possible feature disabled, because it never draws and it's only used to track dirty rectangles.
// Please enlighten me on a better way to achieve this.
offscreenWindow = [[NSOffscreenWindow alloc] initWithContentRect:[self bounds] styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
[offscreenWindow setFrameOrigin:NSMakePoint(-10000, -10000)]; // , [self bounds].size.width, [self bounds].size.height)];
NSRect fr = [self bounds];
[offscreenWindow setOnScreenPseudoRootView:(PseudoRootView *)self];
offscreenView = [[NSOffscreenView alloc] initWithFrame:fr];
NSClipView *clipView = [[NSClipView alloc] initWithFrame:fr];
[clipView setDocumentView:offscreenView];
[offscreenWindow setContentView:clipView];
[offscreenView setOnScreenPseudoRootView:(PseudoRootView *)self];
[offscreenWindow setAlphaValue:0];
//[offscreenView setAlphaValue:0];
[offscreenWindow setOpaque:NO];
[offscreenWindow setAcceptsMouseMovedEvents:NO];
[offscreenWindow setIgnoresMouseEvents:YES];
[offscreenWindow setAutorecalculatesKeyViewLoop:NO];
[offscreenWindow setExcludedFromWindowsMenu:YES];
[offscreenWindow disableFlushWindow]; // prevent additional latency
[offscreenWindow setAutodisplay:NO];
[offscreenWindow setReleasedWhenClosed:NO];
[offscreenWindow setHasShadow:NO];
#warning useOptimizedDrawing sometimes causes problems
[offscreenWindow useOptimizedDrawing:YES];
if ( [offscreenWindow toolbar] )
[offscreenWindow setToolbar:nil];
//[[self window] addChildWindow:offscreenWindow ordered:NSWindowBelow];
//[offscreenView allocateGState];
if ( [offscreenWindow backgroundColor] )
[offscreenWindow setBackgroundColor:nil];
if ( [self window] )
{
//[offscreenWindow setGraphicsContextWithView:self];
[offscreenWindow orderBack:self]; // also see viewWillMoveToWindow
}
[offscreenWindow disableCursorRects];
//[offscreenWindow orderOut:self];
}
#pragma mark -
@implementation NSOffscreenView
- (const ConvertRectCache *) convertRectCache
- (NSRect) convertRect:(NSRect)aRect toView:(NSView *)aView
- (NSRect) convertRect:(NSRect)aRect fromView:(NSView *)aView
- (NSRect) visibleRect
- (BOOL) wantsDefaultClipping { return NO; }
- (BOOL) isFlipped { return YES; }
- (BOOL) isOpaque { return YES; }
- (void) scrollIfNeeded
- (void)viewWillDraw
- (void)setOnScreenPseudoRootView:(PseudoRootView *)iView { onScreenPseudoRootView = iView; }
#pragma mark -
@implementation PseudoView // a branch in an offscreen view hierarchy
- (void) cleanup
- (void) dealloc
- (void) setOffscreenWindow:(NSOffscreenWindow *)iOffscreenWindow
- (PseudoRootView *)onscreenRoot: (NSPoint *)oOffset
- (void) addSubview:(NSView *)aView
- (NSView *)superview
- (NSArray *)subviews
- (NSMutableArray *)pseudoSubviews
- (NSArray *)reversedPseudoSubviews { return reversedPseudoSubviews; }
- (void)setPseudoSuperview:(PseudoView *)iPseudoSuperview { pseudoSuperview = iPseudoSuperview; }
- (PseudoView *)pseudoSuperview
- (NSView *)pseudoHitTest:(NSPoint)pt // pt should be in this view's coords
- (void) viewWillDrawCore // called by DirtyRectHelper (can just return if there's nothing to be done)
-(void)setNeedsDisplay:(BOOL)flag
-(void)setNeedsDisplayInRect:(NSRect)iRect
- (const ConvertRectCache *) convertRectCache
- (NSRect) convertRect:(NSRect)aRect toView:(NSView *)aView
- (NSRect) convertRect:(NSRect)aRect fromView:(NSView *)aView
- (NSRect) visibleRect
- (BOOL) canDraw
- (void)pseudoSetNeedsDisplay:(NSRect)iFrame { return; }
- (void)updatePseudoViews:(NSPoint)offset rootView:(PseudoRootView *)rootView
- (BOOL) wantsDefaultClipping { return NO; } // !sDisplayingOffscreenWindow; }
- (void)drawRect:(NSRect)iDirtyBigRect
- (bool)recursiveMouseDown:(NSEvent *)ev baseStartPt:(NSPoint)startPt parentView:(PseudoView *)iParentView cumulativeOffset:(NSPoint)iCumulativeOffset
- (void)mouseDown:(NSEvent *)ev
- (void)editPseudoView:(NSView *)iSubView
- (void)setEditingSubView:(NSView *)iSubview onscreenFrame:(NSRect)iTempOnscreenFrame event:(NSEvent *)ev
- (void) updateEditingSubView
- (void)updateTrackingAreas
#pragma mark -
@implementation PseudoRootView // the trunk of an offscreen view hierarchy, that is in the onscreen window
- (void) cleanup
- (void) dealloc
- (void)viewWillDraw
- (void)setFrameSize:(NSSize)newSize
- (void) setFrame:(NSRect)iRect
- (void)viewDidMoveToWindow
- (void) setupClippingPath:(const NSRect **)oRects count:(NSInteger *)oCount
- (void) resetClippingPath { [dirtyHelper resetClippingPath:self]; }
- (NSView *) hoverView { return _hoverView; }
- (void)setHoverView:(NSView *)iHoverView
- (void) adjustTrackingArea
- (void)resetCursorRects
- (void)mouseMoved:(NSEvent *)theEvent
- (void)mouseEntered:(NSEvent *)theEvent
- (void)mouseExited:(NSEvent *)theEvent
- (NSView *)hitTest:(NSPoint)pt
- (void)setNeedsDisplay:(BOOL)flag
- (void)setNeedsDisplayInRect:(NSRect)invalidRect
- (void) drawDirtyRects
- (NSRect) visibleRectCore
- (NSRect) visibleRect
- (void) addViewNeedingDisplay: (NSView *)iView
- (void) clearViewsNeedingDisplay
- (BOOL)lockFocusIfCanDraw
#pragma mark -
@implementation DirtyRectHelper
- (void)addRectToDraw: (NSRect)iRect
- (void) createDrawableFromDirty: (NSView <DirtyRectProtocol> *)iOwnerView maskOpaqueSubviews: (BOOL)iMaskOpaques
- (void) addDirtyRects:(const NSRect *)dirtyRects count:(int)dirtyRectCount offset:(NSPoint)offset owner:(NSView <DirtyRectProtocol> *)iOwnerView
- (NSRect)setupClippingPath:(const NSRect **)oRects count:(NSInteger *)oCount owner:(NSView <DirtyRectProtocol> *)iOwnerView
- (void) resetClippingPath: (NSView <DirtyRectProtocol> *)iOwnerView
- (HIMutableShapeRef) drawableShape { return drawableShape; } // tbd: so owner can clear rects as they are drawn
- (void) drawDirtyRects: (NSView <DirtyRectProtocol> *)iOwnerView
- (BOOL)setNeedsDisplay:(BOOL)flag owner:(NSView <DirtyRectProtocol> *)iOwnerView
- (BOOL)setNeedsDisplayInRect:(NSRect)invalidRect owner: (NSView <DirtyRectProtocol> *)iOwnerView
- (NSRect) visibleRect: (NSView <DirtyRectProtocol> *)iOwnerView
#pragma mark -
// category method to recurse view hierarchy. Called by onscreen window to collect offscreen dirty rectangles.
@implementation NSView (UpdatePseudoViews)
- (void) updatePseudoViews:(NSView *)rootView
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden