Switch to instance methods of draw and display

This patch switches to instance methods of draw and display for
ASTextNode and ASImageNode to attempt to increase their performance.

It also fixes some thread safety issues in ASImageNode which appear
to have been regressions (though probably not hit very often).

And it sets up work for allowing modification of CGContexts before
and after a node's contents are drawn.
This commit is contained in:
Garrett Moon 2016-01-27 20:05:03 -08:00
parent 5004935d82
commit 01c1680904
7 changed files with 207 additions and 107 deletions

View File

@ -6,6 +6,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "_ASDisplayLayer.h"
@interface ASDisplayNode (Beta)
+ (BOOL)shouldUseNewRenderingRange;
@ -20,4 +22,25 @@
*/
- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously;
- (void)drawRect:(CGRect)bounds withParameters:(id <NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing;
- (UIImage *)displayWithParameters:(id <NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled;
/**
* @abstract allow modification of a context before the node's content is drawn
*
* @discussion Set the block to be called after the context has been created and before the node's content is drawn.
* You can override this to modify the context before the content is drawn. You are responsible for saving and
* restoring context if necessary. Restoring can be done in contextDidDisplayNodeContent
* This block can be called from *any* thread and it is unsafe to access any UIKit main thread properties from it.
*/
@property (nonatomic, strong) ASDisplayNodeContextModifier willDisplayNodeContentBlock;
/**
* @abstract allow modification of a context after the node's content is drawn
*
* @discussion
*/
@property (nonatomic, strong) ASDisplayNodeContextModifier didDisplayNodeContentBlock;
@end

View File

@ -37,6 +37,11 @@ typedef CALayer * _Nonnull(^ASDisplayNodeLayerBlock)();
*/
typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode * _Nonnull node);
/**
* ASDisplayNode will / did render node content in context.
*/
typedef void (^ASDisplayNodeContextModifier)(CGContextRef context);
/**
Interface state is available on ASDisplayNode and ASViewController, and
allows checking whether a node is in an interface situation where it is prudent to trigger certain

View File

@ -110,8 +110,12 @@ static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *i
flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
if (instance) {
flags.implementsDrawParameters = ([instance respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
flags.implementsInstanceDrawRect = ([instance respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
flags.implementsInstanceImageDisplay = ([instance respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
} else {
flags.implementsDrawParameters = ([c instancesRespondToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
flags.implementsInstanceDrawRect = ([c instancesRespondToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
flags.implementsInstanceImageDisplay = ([c instancesRespondToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
}
return flags;
}
@ -1491,7 +1495,7 @@ static NSInteger incrementIfFound(NSInteger i) {
// Helper method to summarize whether or not the node run through the display process
- (BOOL)__implementsDisplay
{
return _flags.implementsDrawRect == YES || _flags.implementsImageDisplay == YES || self.shouldRasterizeDescendants;
return _flags.implementsDrawRect == YES || _flags.implementsImageDisplay == YES || self.shouldRasterizeDescendants || _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay;
}
- (void)_setupPlaceholderLayer

View File

@ -14,6 +14,7 @@
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import "ASImageNode+CGExtras.h"
@ -22,42 +23,34 @@
@interface _ASImageNodeDrawParameters : NSObject
@property (nonatomic, assign, readonly) BOOL cropEnabled;
@property (nonatomic, assign) BOOL opaque;
@property (nonatomic, retain) UIImage *image;
@property (nonatomic, assign) CGRect bounds;
@property (nonatomic, assign) CGFloat contentsScale;
@property (nonatomic, retain) UIColor *backgroundColor;
@property (nonatomic, assign) UIViewContentMode contentMode;
@property (nonatomic, assign) CGRect cropRect;
@property (nonatomic, copy) asimagenode_modification_block_t imageModificationBlock;
@end
// TODO: eliminate explicit parameters with a set of keys copied from the node
@implementation _ASImageNodeDrawParameters
- (id)initWithCrop:(BOOL)cropEnabled opaque:(BOOL)opaque image:(UIImage *)image bounds:(CGRect)bounds contentsScale:(CGFloat)contentsScale backgroundColor:(UIColor *)backgroundColor contentMode:(UIViewContentMode)contentMode cropRect:(CGRect)cropRect imageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock
- (id)initWithBounds:(CGRect)bounds opaque:(BOOL)opaque contentsScale:(CGFloat)contentsScale backgroundColor:(UIColor *)backgroundColor contentMode:(UIViewContentMode)contentMode
{
self = [self init];
if (!self) return nil;
_cropEnabled = cropEnabled;
_opaque = opaque;
_image = image;
_bounds = bounds;
_contentsScale = contentsScale;
_backgroundColor = backgroundColor;
_contentMode = contentMode;
_cropRect = cropRect;
_imageModificationBlock = [imageModificationBlock copy];
return self;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@ : %p image:%@ cropEnabled:%@ opaque:%@ bounds:%@ contentsScale:%.2f backgroundColor:%@ contentMode:%@ cropRect:%@>", [self class], self, self.image, @(self.cropEnabled), @(self.opaque), NSStringFromCGRect(self.bounds), self.contentsScale, self.backgroundColor, ASDisplayNodeNSStringFromUIContentMode(self.contentMode), NSStringFromCGRect(self.cropRect)];
return [NSString stringWithFormat:@"<%@ : %p opaque:%@ bounds:%@ contentsScale:%.2f backgroundColor:%@ contentMode:%@>", [self class], self, @(self.opaque), NSStringFromCGRect(self.bounds), self.contentsScale, self.backgroundColor, ASDisplayNodeNSStringFromUIContentMode(self.contentMode)];
}
@end
@ -78,6 +71,7 @@
}
@synthesize image = _image;
@synthesize imageModificationBlock = _imageModificationBlock;
- (id)init
{
@ -153,88 +147,104 @@
{
BOOL hasValidCropBounds = _cropEnabled && !CGRectIsNull(_cropDisplayBounds) && !CGRectIsEmpty(_cropDisplayBounds);
return [[_ASImageNodeDrawParameters alloc] initWithCrop:_cropEnabled
opaque:self.opaque
image:self.image
bounds:(hasValidCropBounds ? _cropDisplayBounds : self.bounds)
contentsScale:self.contentsScaleForDisplay
backgroundColor:self.backgroundColor
contentMode:self.contentMode
cropRect:self.cropRect
imageModificationBlock:self.imageModificationBlock];
return [[_ASImageNodeDrawParameters alloc] initWithBounds:self.bounds
opaque:self.opaque
contentsScale:self.contentsScaleForDisplay
backgroundColor:self.backgroundColor
contentMode:self.contentMode];
}
+ (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
- (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
{
UIImage *image = parameters.image;
ASDN::MutexLocker l(_imageLock);
UIImage *image = _image;
if (!image) {
return nil;
}
ASDisplayNodeAssert(parameters.contentsScale > 0, @"invalid contentsScale at display time");
BOOL cropEnabled = _cropEnabled;
CGFloat contentsScale = _contentsScaleForDisplay;
CGRect cropDisplayBounds = _cropDisplayBounds;
CGRect cropRect = _cropRect;
asimagenode_modification_block_t imageModificationBlock = _imageModificationBlock;
ASDN::MutexUnlocker u(_imageLock);
ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentBlock;
ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentBlock;
BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds);
CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds);
BOOL isOpaque = parameters.opaque;
CGFloat contentsScaleForDisplay = parameters.contentsScale;
UIColor *backgroundColor = parameters.backgroundColor;
UIViewContentMode contentMode = parameters.contentMode;
ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time");
// if the image is resizable, bail early since the image has likely already been configured
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
if (stretchable) {
if (parameters.imageModificationBlock != NULL) {
image = parameters.imageModificationBlock(image);
if (imageModificationBlock != NULL) {
image = imageModificationBlock(image);
}
return image;
}
CGRect bounds = parameters.bounds;
CGFloat contentsScale = parameters.contentsScale;
UIViewContentMode contentMode = parameters.contentMode;
CGSize imageSize = image.size;
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale));
BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill
|| contentMode == UIViewContentModeScaleAspectFit
|| contentMode == UIViewContentModeCenter;
|| contentMode == UIViewContentModeScaleAspectFit
|| contentMode == UIViewContentModeCenter;
CGSize backingSize;
CGRect imageDrawRect;
if (boundsSizeInPixels.width * contentsScale < 1.0f ||
boundsSizeInPixels.height * contentsScale < 1.0f ||
imageSizeInPixels.width < 1.0f ||
imageSizeInPixels.height < 1.0f) {
return nil;
}
// If we're not supposed to do any cropping, just decode image at original size
if (!parameters.cropEnabled || !contentModeSupported || stretchable) {
if (!cropEnabled || !contentModeSupported || stretchable) {
backingSize = imageSizeInPixels;
imageDrawRect = (CGRect){.size = backingSize};
} else {
ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels,
boundsSizeInPixels,
contentMode,
parameters.cropRect,
cropRect,
&backingSize,
&imageDrawRect);
}
if (backingSize.width <= 0.0f ||
backingSize.height <= 0.0f ||
imageDrawRect.size.width <= 0.0f ||
imageDrawRect.size.height <= 0.0f) {
return nil;
}
// Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds
// will do its rounding on pixel instead of point boundaries
UIGraphicsBeginImageContextWithOptions(backingSize, parameters.opaque, 1.0);
UIGraphicsBeginImageContextWithOptions(backingSize, isOpaque, 1.0);
// if view is opaque, fill the context with background color
if (parameters.opaque && parameters.backgroundColor) {
[parameters.backgroundColor setFill];
if (isOpaque && backgroundColor) {
[backgroundColor setFill];
UIRectFill({ .size = backingSize });
}
CGContextRef context = UIGraphicsGetCurrentContext();
if (preContextBlock) {
preContextBlock(context);
}
// iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on
// multiple threads concurrently. In fact, instead of crashing, it appears to deadlock.
// The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier,
@ -250,20 +260,24 @@
@synchronized(image) {
[image drawInRect:imageDrawRect];
}
if (postContextBlock) {
postContextBlock(context);
}
if (isCancelled()) {
UIGraphicsEndImageContext();
return nil;
}
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (parameters.imageModificationBlock != NULL) {
result = parameters.imageModificationBlock(result);
if (imageModificationBlock != NULL) {
result = imageModificationBlock(result);
}
return result;
}
@ -271,8 +285,9 @@
{
[super displayDidFinish];
ASDN::MutexLocker l(_imageLock);
// If we've got a block to perform after displaying, do it.
if (self.image && _displayCompletionBlock) {
if (_image && _displayCompletionBlock) {
// FIXME: _displayCompletionBlock is not protected by lock
_displayCompletionBlock(NO);
@ -291,6 +306,7 @@
// Stash the block and call-site queue. We'll invoke it in -displayDidFinish.
// FIXME: _displayCompletionBlock not protected by lock
ASDN::MutexLocker l(_imageLock);
if (_displayCompletionBlock != displayCompletionBlock) {
_displayCompletionBlock = [displayCompletionBlock copy];
}
@ -301,6 +317,7 @@
#pragma mark - Cropping
- (BOOL)isCropEnabled
{
ASDN::MutexLocker l(_imageLock);
return _cropEnabled;
}
@ -311,6 +328,7 @@
- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds
{
ASDN::MutexLocker l(_imageLock);
if (_cropEnabled == cropEnabled)
return;
@ -331,11 +349,13 @@
- (CGRect)cropRect
{
ASDN::MutexLocker l(_imageLock);
return _cropRect;
}
- (void)setCropRect:(CGRect)cropRect
{
ASDN::MutexLocker l(_imageLock);
if (CGRectEqualToRect(_cropRect, cropRect))
return;
@ -354,6 +374,18 @@
});
}
- (asimagenode_modification_block_t)imageModificationBlock
{
ASDN::MutexLocker l(_imageLock);
return _imageModificationBlock;
}
- (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock
{
ASDN::MutexLocker l(_imageLock);
_imageModificationBlock = imageModificationBlock;
}
@end

View File

@ -34,13 +34,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
@interface ASTextNodeDrawParameters : NSObject
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
textOrigin:(CGPoint)textOrigin
backgroundColor:(UIColor *)backgroundColor;
@property (nonatomic, strong, readonly) ASTextKitRenderer *renderer;
@property (nonatomic, assign, readonly) CGPoint textOrigin;
@property (nonatomic, assign, readonly) CGRect bounds;
@property (nonatomic, strong, readonly) UIColor *backgroundColor;
@ -48,30 +42,16 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
@implementation ASTextNodeDrawParameters
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
textOrigin:(CGPoint)textOrigin
backgroundColor:(UIColor *)backgroundColor
- (instancetype)initWithBounds:(CGRect)bounds
backgroundColor:(UIColor *)backgroundColor
{
if (self = [super init]) {
_renderer = renderer;
_textOrigin = textOrigin;
_bounds = bounds;
_backgroundColor = backgroundColor;
}
return self;
}
- (void)dealloc
{
// Destruction of the layout managers/containers/text storage is quite
// expensive, and can take some time, so we dispatch onto a bg queue to
// actually dealloc.
__block ASTextKitRenderer *renderer = _renderer;
ASPerformBlockOnBackgroundThread(^{
renderer = nil;
});
_renderer = nil;
}
@end
@interface ASTextNode () <UIGestureRecognizerDelegate>
@ -237,11 +217,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Renderer Management
//only safe to call on the main thread
- (ASTextKitRenderer *)_renderer
{
return [self _rendererWithBounds:self.bounds];
}
- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds
{
ASDN::MutexLocker l(_rendererLock);
if (_renderer == nil) {
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : self.bounds.size;
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size;
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
constrainedSize:constrainedSize];
}
@ -420,13 +406,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Drawing
+ (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
- (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
{
CGContextRef context = UIGraphicsGetCurrentContext();
ASDisplayNodeAssert(context, @"This is no good without a context.");
CGContextSaveGState(context);
ASTextKitRenderer *renderer = [self _rendererWithBounds:parameters.bounds];
UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer];
CGPoint textOrigin = CGPointMake(parameters.bounds.origin.x - shadowPadding.left, parameters.bounds.origin.y - shadowPadding.top);
// Fill background
if (!isRasterizing) {
UIColor *backgroundColor = parameters.backgroundColor;
@ -435,28 +425,20 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
}
}
// Draw shadow
[[parameters.renderer shadower] setShadowInContext:context];
[[renderer shadower] setShadowInContext:context];
// Draw text
bounds.origin = parameters.textOrigin;
[parameters.renderer drawInContext:context bounds:bounds];
bounds.origin = textOrigin;
[renderer drawInContext:context bounds:bounds];
CGContextRestoreGState(context);
}
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
{
CGRect bounds = self.bounds;
[self _invalidateRendererIfNeededForBoundsSize:bounds.size];
// Offset the text origin by any shadow padding
UIEdgeInsets shadowPadding = [self shadowPadding];
CGPoint textOrigin = CGPointMake(bounds.origin.x - shadowPadding.left, bounds.origin.y - shadowPadding.top);
return [[ASTextNodeDrawParameters alloc] initWithRenderer:[self _renderer]
textOrigin:textOrigin
backgroundColor:self.backgroundColor];
return [[ASTextNodeDrawParameters alloc] initWithBounds:self.bounds backgroundColor:self.backgroundColor];
}
#pragma mark - Attributes
@ -1016,9 +998,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
}
}
//only safe to call on main thread
- (UIEdgeInsets)shadowPadding
{
return [self _renderer].shadower.shadowPadding;
return [self shadowPaddingWithRenderer:[self _renderer]];
}
- (UIEdgeInsets)shadowPaddingWithRenderer:(ASTextKitRenderer *)renderer
{
return renderer.shadower.shadowPadding;
}
#pragma mark - Truncation Message

View File

@ -12,6 +12,7 @@
#import "ASAssert.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Beta.h"
@interface ASDisplayNode () <_ASDisplayLayerDelegate>
@end
@ -224,25 +225,30 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
return image;
};
} else if (_flags.implementsImageDisplay) {
} else if (_flags.implementsInstanceImageDisplay || _flags.implementsImageDisplay) {
// Capture drawParameters from delegate on main thread
id drawParameters = [self drawParameters];
displayBlock = ^id{
__ASDisplayLayerIncrementConcurrentDisplayCount(asynchronous, rasterizing);
if (isCancelledBlock()) {
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
return nil;
}
ASDN_DELAY_FOR_DISPLAY();
UIImage *result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock];
UIImage *result = nil;
if (_flags.implementsInstanceImageDisplay) {
result = [self displayWithParameters:drawParameters isCancelled:isCancelledBlock];
} else {
result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock];
}
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
return result;
};
} else if (_flags.implementsDrawRect) {
} else if (_flags.implementsInstanceDrawRect || _flags.implementsDrawRect) {
CGRect bounds = self.bounds;
if (CGRectIsEmpty(bounds)) {
@ -267,7 +273,20 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
}
[[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
CGContextRef currentContext = UIGraphicsGetCurrentContext();
if (_preContextModifier) {
_preContextModifier(currentContext);
}
if (_flags.implementsInstanceDrawRect) {
[self drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
} else {
[[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
}
if (_postContextModifier) {
_postContextModifier(currentContext);
}
if (isCancelledBlock()) {
if (!rasterizing) {
@ -370,4 +389,28 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
[_displaySentinel increment];
}
- (ASDisplayNodeContextModifier)willDisplayNodeContentBlock
{
ASDN::MutexLocker l(_propertyLock);
return _preContextModifier;
}
- (ASDisplayNodeContextModifier)didDisplayNodeContentBlock
{
ASDN::MutexLocker l(_propertyLock);
return _postContextModifier;
}
- (void)setWillDisplayNodeContentBlock:(ASDisplayNodeContextModifier)contextModifier
{
ASDN::MutexLocker l(_propertyLock);
_preContextModifier = contextModifier;
}
- (void)setDidDisplayNodeContentBlock:(ASDisplayNodeContextModifier)contextModifier;
{
ASDN::MutexLocker l(_propertyLock);
_postContextModifier = contextModifier;
}
@end

View File

@ -87,7 +87,9 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
unsigned hasCustomDrawingPriority:1;
// whether custom drawing is enabled
unsigned implementsInstanceDrawRect:1;
unsigned implementsDrawRect:1;
unsigned implementsInstanceImageDisplay:1;
unsigned implementsImageDisplay:1;
unsigned implementsDrawParameters:1;
@ -101,6 +103,9 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
ASDisplayNodeExtraIvars _extra;
ASDisplayNodeContextModifier _preContextModifier;
ASDisplayNodeContextModifier _postContextModifier;
#if TIME_DISPLAYNODE_OPS
@public
NSTimeInterval _debugTimeToCreateView;