Skip to content

Commit

Permalink
Add a new "global drawing" experiment to use UIGraphicsRenderer (Text…
Browse files Browse the repository at this point in the history
…ureGroup#1469)

* Add a new "global drawing" experiment to use UIGraphicsRenderer everywhere, when available

* Fix logic

* Use preferredFormat
  • Loading branch information
Adlai-Holler authored and hebertialmeida committed May 10, 2019
1 parent 5995708 commit 069f61b
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 213 deletions.
8 changes: 8 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,8 @@
CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm */; };
CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */; };
CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.mm */; };
CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */; };
CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.mm */; };
CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm */; };
CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.mm */; };
CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; };
Expand Down Expand Up @@ -962,6 +964,8 @@
CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSParagraphStyle+ASText.mm"; sourceTree = "<group>"; };
CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+ASText.h"; sourceTree = "<group>"; };
CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSAttributedString+ASText.mm"; sourceTree = "<group>"; };
CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASGraphicsContext.h; sourceTree = "<group>"; };
CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASGraphicsContext.mm; sourceTree = "<group>"; };
CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionModernDataSourceTests.mm; sourceTree = "<group>"; };
CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = "<group>"; };
CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListAdapter+AsyncDisplayKit.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1425,6 +1429,8 @@
68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.mm */,
E5B225271F1790B5001E1431 /* ASHashing.h */,
E5B225261F1790B5001E1431 /* ASHashing.mm */,
CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */,
CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.mm */,
058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */,
058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */,
68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */,
Expand Down Expand Up @@ -1980,6 +1986,7 @@
68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */,
CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */,
B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */,
CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */,
E5C347B11ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h in Headers */,
CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */,
B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */,
Expand Down Expand Up @@ -2395,6 +2402,7 @@
9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
690ED59B1E36D118000627C0 /* ASImageNode+tvOS.mm in Sources */,
0FAFDF7620EC1C90003A51C0 /* ASLayout+IGListKit.mm in Sources */,
CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.mm in Sources */,
CCCCCCD81EC3EF060087FE10 /* ASTextInput.mm in Sources */,
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
9D9AA56921E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm in Sources */,
Expand Down
33 changes: 16 additions & 17 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#import <AsyncDisplayKit/ASDisplayNode+InterfaceState.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
#import <AsyncDisplayKit/ASLayoutSpec.h>
Expand Down Expand Up @@ -1553,26 +1554,24 @@ - (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor
BOOL isRight = (idx == 1 || idx == 3);

CGSize size = CGSizeMake(radius + 1, radius + 1);
UIGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay);

CGContextRef ctx = UIGraphicsGetCurrentContext();
if (isRight == YES) {
CGContextTranslateCTM(ctx, -radius + 1, 0);
}
if (isTop == NO) {
CGContextTranslateCTM(ctx, 0, -radius + 1);
}

UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius];
[roundedRect setUsesEvenOddFillRule:YES];
[roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]];
[backgroundColor setFill];
[roundedRect fill];
UIImage *newContents = ASGraphicsCreateImageWithOptions(size, NO, self.contentsScaleForDisplay, nil, nil, ^{
CGContextRef ctx = UIGraphicsGetCurrentContext();
if (isRight == YES) {
CGContextTranslateCTM(ctx, -radius + 1, 0);
}
if (isTop == NO) {
CGContextTranslateCTM(ctx, 0, -radius + 1);
}
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius];
[roundedRect setUsesEvenOddFillRule:YES];
[roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]];
[backgroundColor setFill];
[roundedRect fill];
});

// No lock needed, as _clipCornerLayers is only modified on the main thread.
unowned CALayer *clipCornerLayer = _clipCornerLayers[idx];
clipCornerLayer.contents = (id)(UIGraphicsGetImageFromCurrentImageContext().CGImage);
UIGraphicsEndImageContext();
clipCornerLayer.contents = (id)(newContents.CGImage);
clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height);
clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 0.0 : 1.0);
}
Expand Down
3 changes: 2 additions & 1 deletion Source/ASExperimentalFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) {
ASExperimentalFixRangeController = 1 << 12, // exp_fix_range_controller
ASExperimentalOOMBackgroundDeallocDisable = 1 << 13, // exp_oom_bg_dealloc_disable
ASExperimentalTransactionOperationRetainCycle = 1 << 14, // exp_transaction_operation_retain_cycle
ASExperimentalRemoveTextKitInitialisingLock = 1 << 15, // exp_remove_textkit_initialising_lock
ASExperimentalRemoveTextKitInitialisingLock = 1 << 15, // exp_remove_textkit_initialising_lock
ASExperimentalDrawingGlobal = 1 << 16, // exp_drawing_global
ASExperimentalFeatureAll = 0xFFFFFFFF
};

Expand Down
3 changes: 2 additions & 1 deletion Source/ASExperimentalFeatures.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
@"exp_fix_range_controller",
@"exp_oom_bg_dealloc_disable",
@"exp_transaction_operation_retain_cycle",
@"exp_remove_textkit_initialising_lock"]));
@"exp_remove_textkit_initialising_lock",
@"exp_drawing_global"]));
if (flags == ASExperimentalFeatureAll) {
return allNames;
}
Expand Down
146 changes: 64 additions & 82 deletions Source/ASImageNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
Expand Down Expand Up @@ -201,15 +202,11 @@ - (UIImage *)placeholderImage
return nil;
}

AS::MutexLocker l(__instanceLock__);

UIGraphicsBeginImageContextWithOptions(size, NO, 1);
[self.placeholderColor setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

return image;
return ASGraphicsCreateImageWithOptions(size, NO, 1, nil, nil, ^{
AS::MutexLocker l(__instanceLock__);
[_placeholderColor setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
});
}

#pragma mark - Layout and Sizing
Expand Down Expand Up @@ -466,7 +463,7 @@ + (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key drawParameters:

+ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
{
// The following `UIGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an
// The following `ASGraphicsCreateImageWithOptions` call will sometimes take take longer than 5ms on an
// A5 processor for a 400x800 backingSize.
// Check for cancellation before we call it.
if (isCancelled()) {
Expand All @@ -475,55 +472,46 @@ + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(

// 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(key.backingSize, key.isOpaque, 1.0);

BOOL contextIsClean = YES;

CGContextRef context = UIGraphicsGetCurrentContext();
if (context && key.willDisplayNodeContentWithRenderingContext) {
key.willDisplayNodeContentWithRenderingContext(context, drawParameters);
contextIsClean = NO;
}

// if view is opaque, fill the context with background color
if (key.isOpaque && key.backgroundColor) {
[key.backgroundColor setFill];
UIRectFill({ .size = key.backingSize });
contextIsClean = NO;
}

// 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 Premiere,
// as well as iOS games, and a small number of ASDK apps that provide the same image reference
// to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes
// that may get the same pointer for a given UI asset image, etc.
// FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and
// only if the object already exists in the set we should create a semaphore to signal waiting threads
// upon removal of the object from the set when the operation completes.
// Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer.
// Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068

UIImage *image = key.image;
BOOL canUseCopy = (contextIsClean || ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)));
CGBlendMode blendMode = canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal;

@synchronized(image) {
[image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1];
}

if (context && key.didDisplayNodeContentWithRenderingContext) {
key.didDisplayNodeContentWithRenderingContext(context, drawParameters);
}
UIImage *result = ASGraphicsCreateImageWithOptions(key.backingSize, key.isOpaque, 1.0, key.image, isCancelled, ^{
BOOL contextIsClean = YES;

// Check cancellation one last time before forming image.
if (isCancelled()) {
UIGraphicsEndImageContext();
return nil;
}
CGContextRef context = UIGraphicsGetCurrentContext();
if (context && key.willDisplayNodeContentWithRenderingContext) {
key.willDisplayNodeContentWithRenderingContext(context, drawParameters);
contextIsClean = NO;
}

// if view is opaque, fill the context with background color
if (key.isOpaque && key.backgroundColor) {
[key.backgroundColor setFill];
UIRectFill({ .size = key.backingSize });
contextIsClean = NO;
}

// 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 Premiere,
// as well as iOS games, and a small number of ASDK apps that provide the same image reference
// to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes
// that may get the same pointer for a given UI asset image, etc.
// FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and
// only if the object already exists in the set we should create a semaphore to signal waiting threads
// upon removal of the object from the set when the operation completes.
// Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer.
// Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068

UIImage *image = key.image;
BOOL canUseCopy = (contextIsClean || ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)));
CGBlendMode blendMode = canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal;

@synchronized(image) {
[image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1];
}

UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (context && key.didDisplayNodeContentWithRenderingContext) {
key.didDisplayNodeContentWithRenderingContext(context, drawParameters);
}
});

if (key.imageModificationBlock) {
result = key.imageModificationBlock(result);
Expand Down Expand Up @@ -735,40 +723,34 @@ - (NSDictionary *)debugLabelAttributes
asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor)
{
return ^(UIImage *originalImage) {
UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}];

// Make the image round
[roundOutline addClip];
return ASGraphicsCreateImageWithOptions(originalImage.size, NO, originalImage.scale, originalImage, nil, ^{
UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}];

// Draw the original image
[originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
// Make the image round
[roundOutline addClip];

// Draw a border on top.
if (borderWidth > 0.0) {
[borderColor setStroke];
[roundOutline setLineWidth:borderWidth];
[roundOutline stroke];
}
// Draw the original image
[originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
// Draw a border on top.
if (borderWidth > 0.0) {
[borderColor setStroke];
[roundOutline setLineWidth:borderWidth];
[roundOutline stroke];
}
});
};
}

asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color)
{
return ^(UIImage *originalImage) {
UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);

// Set color and render template
[color setFill];
UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];

UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *modifiedImage = ASGraphicsCreateImageWithOptions(originalImage.size, NO, originalImage.scale, originalImage, nil, ^{
// Set color and render template
[color setFill];
UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
});

// if the original image was stretchy, keep it stretchy
if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) {
Expand Down

0 comments on commit 069f61b

Please sign in to comment.