Skip to content

Commit

Permalink
Add an experimental "no-copy" renderer (TextureGroup#741)
Browse files Browse the repository at this point in the history
* Add "ASGraphicsContext" to skip copying our rendered images

* Zero the buffer before making a context

* Update license header

* Update dangerfile

* Make it a runtime flag

* Restore GState for good measure

* Free buffer if end without image

* Enable the experiment, and cut out the middle-man

* Fix typo
  • Loading branch information
Adlai-Holler authored and bernieperez committed Apr 25, 2018
1 parent 5f1d8cb commit c7542b5
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 44 deletions.
8 changes: 8 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,8 @@
CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */; };
CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */; };
CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */; };
CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */; };
CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */; };
CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */; };
CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; };
Expand Down Expand Up @@ -895,6 +897,8 @@
CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSParagraphStyle+ASText.m"; sourceTree = "<group>"; };
CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+ASText.h"; sourceTree = "<group>"; };
CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+ASText.m"; sourceTree = "<group>"; };
CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASGraphicsContext.h; sourceTree = "<group>"; };
CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASGraphicsContext.m; sourceTree = "<group>"; };
CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionModernDataSourceTests.m; 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 @@ -1270,6 +1274,8 @@
058D09E1195D050800B7D73C /* Details */ = {
isa = PBXGroup;
children = (
CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */,
CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */,
CC5601391F06E9A700DC4FBE /* ASIntegerMap.h */,
CC56013A1F06E9A700DC4FBE /* ASIntegerMap.mm */,
CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */,
Expand Down Expand Up @@ -1836,6 +1842,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 @@ -2268,6 +2275,7 @@
E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */,
9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */,
CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */,
CCCCCCD81EC3EF060087FE10 /* ASTextInput.m in Sources */,
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */,
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424)
- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706)
- [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler)
- Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler)

## 2.6
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)
Expand Down
2 changes: 1 addition & 1 deletion Dangerfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ end

# Ensure new files have proper header
new_source_license_header = <<-HEREDOC
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down
7 changes: 3 additions & 4 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.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 @@ -1507,7 +1508,7 @@ - (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor
BOOL isRight = (idx == 1 || idx == 2);

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

CGContextRef ctx = UIGraphicsGetCurrentContext();
if (isRight == YES) {
Expand All @@ -1524,11 +1525,9 @@ - (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor

// No lock needed, as _clipCornerLayers is only modified on the main thread.
CALayer *clipCornerLayer = _clipCornerLayers[idx];
clipCornerLayer.contents = (id)(UIGraphicsGetImageFromCurrentImageContext().CGImage);
clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage);
clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height);
clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0);

UIGraphicsEndImageContext();
}
[self _layoutClipCornersIfNeeded];
});
Expand Down
25 changes: 10 additions & 15 deletions Source/ASImageNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
Expand Down Expand Up @@ -213,11 +214,10 @@ - (UIImage *)placeholderImage

ASDN::MutexLocker l(__instanceLock__);

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

return image;
}
Expand Down Expand Up @@ -472,7 +472,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 `ASGraphicsBeginImageContextWithOptions` 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 @@ -481,7 +481,7 @@ + (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);
ASGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0);

BOOL contextIsClean = YES;

Expand Down Expand Up @@ -529,9 +529,7 @@ + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(
return nil;
}

UIImage *result = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();
UIImage *result = ASGraphicsGetImageAndEndCurrentContext();

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

// Make the image round
Expand All @@ -758,24 +756,21 @@ extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(
[roundOutline stroke];
}

UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return modifiedImage;
return ASGraphicsGetImageAndEndCurrentContext();
};
}

extern asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color)
{
return ^(UIImage *originalImage) {
UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
ASGraphicsBeginImageContextWithOptions(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 = ASGraphicsGetImageAndEndCurrentContext();

// if the original image was stretchy, keep it stretchy
if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) {
Expand Down
6 changes: 3 additions & 3 deletions Source/ASMapNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASLayout.h>
Expand Down Expand Up @@ -222,7 +223,7 @@ - (void)takeSnapshot

CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height);

UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
ASGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
[image drawAtPoint:CGPointZero];

UIImage *pinImage;
Expand Down Expand Up @@ -254,8 +255,7 @@ - (void)takeSnapshot
}
}

image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
image = ASGraphicsGetImageAndEndCurrentContext();
}

strongSelf.image = image;
Expand Down
6 changes: 3 additions & 3 deletions Source/ASTextNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>

#import <AsyncDisplayKit/ASTextKitCoreTextAdditions.h>
#import <AsyncDisplayKit/ASTextKitRenderer+Positioning.h>
Expand Down Expand Up @@ -907,7 +908,7 @@ - (UIImage *)placeholderImage

ASDN::MutexLocker l(__instanceLock__);

UIGraphicsBeginImageContext(size);
ASGraphicsBeginImageContextWithOptions(size, NO, 1.0);
[self.placeholderColor setFill];

ASTextKitRenderer *renderer = [self _locked_renderer];
Expand All @@ -926,8 +927,7 @@ - (UIImage *)placeholderImage
}
}

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
return image;
}

Expand Down
1 change: 1 addition & 0 deletions Source/AsyncDisplayKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
#import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
#import <AsyncDisplayKit/UIView+ASConvenience.h>
#import <AsyncDisplayKit/UIImage+ASConvenience.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/NSArray+Diffing.h>
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
#import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h>
Expand Down
6 changes: 3 additions & 3 deletions Source/Debug/AsyncDisplayKit+Debug.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASWeakSet.h>
#import <AsyncDisplayKit/UIImage+ASConvenience.h>
Expand Down Expand Up @@ -148,16 +149,15 @@ - (void)layout
UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7];
CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0);

UIGraphicsBeginImageContext(imgRect.size);
ASGraphicsBeginImageContextWithOptions(imgRect.size, NO, 1);

[fillColor setFill];
UIRectFill(imgRect);

[self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect];
[self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect];

UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *debugHighlightImage = ASGraphicsGetImageAndEndCurrentContext();

UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth);
debugOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch];
Expand Down
62 changes: 62 additions & 0 deletions Source/Details/ASGraphicsContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// ASGraphicsContext.h
// Texture
//
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <CoreGraphics/CoreGraphics.h>

@class UIImage;

/**
* Functions for creating one-shot graphics contexts that do not have to copy
* their contents when an image is generated from them. This is efficient
* for our use, since we do not reuse graphics contexts.
*
* The API mirrors the UIGraphics API, with the exception that forming an image
* ends the context as well.
*/

NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN

/**
* Call this to enable the experimental no-copy rendering.
*
* Returns YES if it was enabled, or NO + assert if it's too late because
* rendering has already started. In practice it's fine to call this
* during -didFinishLaunchingWithOptions:.
*/
extern BOOL ASEnableNoCopyRendering(void);

/**
* Creates a one-shot context.
*
* Behavior is the same as UIGraphicsBeginImageContextWithOptions.
*/
extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);

/**
* Generates and image and ends the current one-shot context.
*
* Behavior is the same as UIGraphicsGetImageFromCurrentImageContext followed by UIGraphicsEndImageContext.
*/
extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext(void);

/**
* Call this if you want to end the current context without making an image.
*
* Behavior is the same as UIGraphicsEndImageContext.
*/
extern void ASGraphicsEndImageContext(void);

ASDISPLAYNODE_EXTERN_C_END
NS_ASSUME_NONNULL_END
Loading

0 comments on commit c7542b5

Please sign in to comment.