Skip to content

Commit

Permalink
CoreText Performance: Call ID2D1RenderTarget::BeginDraw()/EndDraw() f… (
Browse files Browse the repository at this point in the history
#1705)

* CoreText Performance: Call ID2D1RenderTarget::BeginDraw()/EndDraw() fewer times

Fixes #1620

* - Added Begin/EndDraw pairs to a few more places
- Mitigated an issue with cairo where ID2D1RenderTarget would sometimes cache a before state during begin draw,
  and wipe out any changes cairo made, breaking APIs like CGContextFillRect.
- Changed implementation/naming of the 'escape the current begin/end draw stack' functions
  • Loading branch information
ms-jihua committed Jan 20, 2017
1 parent 5566651 commit d5a86b9
Show file tree
Hide file tree
Showing 14 changed files with 133 additions and 28 deletions.
42 changes: 42 additions & 0 deletions Frameworks/CoreGraphics/CGContext.mm
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ inline HRESULT GetTarget(ID2D1Image** pTarget) {
woc::unique_cf<CGColorSpaceRef> _fillColorSpace;
woc::unique_cf<CGColorSpaceRef> _strokeColorSpace;

// Keeps track of the depth of a 'stack' of PushBeginDraw/PopEndDraw calls
// Since nothing needs to actually be put on a stack, just increment a counter insteads
std::atomic_uint32_t _beginEndDrawDepth = { 0 };

// Keeps track of the depth of a 'stack' of (Un)EscapeBeginEndDrawStack calls
std::atomic_uint32_t _escapeBeginEndDrawDepth = { 0 };

inline HRESULT _SaveD2DDrawingState(ID2D1DrawingStateBlock** pDrawingState) {
RETURN_HR_IF(E_POINTER, !pDrawingState);

Expand Down Expand Up @@ -2922,3 +2929,38 @@ CGContextRef _CGBitmapContextCreateWithFormat(int width, int height, __CGSurface
return StubReturn();
}
#pragma endregion
#pragma region CGContextBeginDrawEndDraw

void _CGContextPushBeginDraw(CGContextRef ctx) {
if ((ctx->_beginEndDrawDepth)++ == 0) {
ID2D1RenderTarget* imgRenderTarget = ctx->Backing()->DestImage()->Backing()->GetRenderTarget();
THROW_HR_IF_NULL(E_UNEXPECTED, imgRenderTarget);
imgRenderTarget->BeginDraw();
}
}

void _CGContextPopEndDraw(CGContextRef ctx) {
if (--(ctx->_beginEndDrawDepth) == 0) {
ID2D1RenderTarget* imgRenderTarget = ctx->Backing()->DestImage()->Backing()->GetRenderTarget();
THROW_HR_IF_NULL(E_UNEXPECTED, imgRenderTarget);
THROW_IF_FAILED(imgRenderTarget->EndDraw());
}
}

void _CGContextEscapeBeginEndDrawStack(CGContextRef ctx) {
if ((ctx->_beginEndDrawDepth > 0) && ((ctx->_escapeBeginEndDrawDepth)++ == 0)) {
ID2D1RenderTarget* imgRenderTarget = ctx->Backing()->DestImage()->Backing()->GetRenderTarget();
THROW_HR_IF_NULL(E_UNEXPECTED, imgRenderTarget);
THROW_IF_FAILED(imgRenderTarget->EndDraw());
}
}

void _CGContextUnescapeBeginEndDrawStack(CGContextRef ctx) {
if ((ctx->_beginEndDrawDepth > 0) && (--(ctx->_escapeBeginEndDrawDepth) == 0)) {
ID2D1RenderTarget* imgRenderTarget = ctx->Backing()->DestImage()->Backing()->GetRenderTarget();
THROW_HR_IF_NULL(E_UNEXPECTED, imgRenderTarget);
imgRenderTarget->BeginDraw();
}
}

#pragma endregion
4 changes: 4 additions & 0 deletions Frameworks/CoreText/CTFrame.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#import "DWriteWrapper_CoreText.h"
#import "CoreTextInternal.h"
#import "CGContextInternal.h"
#import "CGPathInternal.h"

const CFStringRef kCTFrameProgressionAttributeName = static_cast<CFStringRef>(@"kCTFrameProgressionAttributeName");
Expand Down Expand Up @@ -122,6 +123,9 @@ void CTFrameDraw(CTFrameRef frameRef, CGContextRef ctx) {
ctx, CGAffineTransformMake(textMatrix.a, -textMatrix.b, textMatrix.c, -textMatrix.d, textMatrix.tx, textMatrix.ty));
CGContextScaleCTM(ctx, 1.0f, -1.0f);

_CGContextPushBeginDraw(ctx);
auto popEnd = wil::ScopeExit([ctx]() { _CGContextPopEndDraw(ctx); });

for (size_t i = 0; i < frame->_lineOrigins.size() && (frame->_lineOrigins[i].y < frame->_frameRect.size.height); ++i) {
_CTLine* line = static_cast<_CTLine*>([frame->_lines objectAtIndex:i]);
CGContextSetTextPosition(ctx, frame->_lineOrigins[i].x, frame->_lineOrigins[i].y);
Expand Down
3 changes: 3 additions & 0 deletions Frameworks/CoreText/CTLine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ void CTLineDraw(CTLineRef lineRef, CGContextRef ctx) {

_CTLine* line = static_cast<_CTLine*>(lineRef);

_CGContextPushBeginDraw(ctx);
auto popEnd = wil::ScopeExit([ctx]() { _CGContextPopEndDraw(ctx); });

for (size_t i = 0; i < [line->_runs count]; ++i) {
_CTRun* curRun = [line->_runs objectAtIndex:i];
if (i > 0) {
Expand Down
33 changes: 25 additions & 8 deletions Frameworks/QuartzCore/CALayer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,13 @@ - (void)renderInContext:(CGContextRef)ctx {
[self layoutIfNeeded];

CGContextSaveGState(ctx);
_CGContextPushBeginDraw(ctx);

auto popEnd = wil::ScopeExit([ctx]() {
_CGContextPopEndDraw(ctx);
CGContextRestoreGState(ctx);
});

CGContextTranslateCTM(ctx, priv->position.x, priv->position.y);
CGContextTranslateCTM(ctx, -priv->bounds.size.width * priv->anchorPoint.x, -priv->bounds.size.height * priv->anchorPoint.y);
CGRect destRect;
Expand Down Expand Up @@ -435,8 +442,6 @@ - (void)renderInContext:(CGContextRef)ctx {
LLTREE_FOREACH(curLayer, priv) {
[curLayer->self renderInContext:ctx];
}

CGContextRestoreGState(ctx);
}

/**
Expand Down Expand Up @@ -521,6 +526,13 @@ - (void)display {
CGImageRef target = CGBitmapContextGetImage(drawContext);

CGContextRetain(drawContext);
_CGContextPushBeginDraw(drawContext);

auto popEnd = wil::ScopeExit([drawContext]() {
_CGContextPopEndDraw(drawContext);
CGContextRelease(drawContext);
});

CGImageRetain(target);
priv->savedContext = drawContext;

Expand Down Expand Up @@ -561,7 +573,6 @@ - (void)display {
}

CGContextReleaseLock(drawContext);
CGContextRelease(drawContext);

// If we've drawn anything, set it as our contents
if (!CGContextIsDirty(drawContext)) {
Expand Down Expand Up @@ -2371,7 +2382,7 @@ CGPoint _legacyConvertPoint(CGPoint point, CALayer* fromLayer, CALayer* toLayer)
// Convert the point to center-based position
point.x -= fromLayer->priv->bounds.size.width * fromLayer->priv->anchorPoint.x;
point.y -= fromLayer->priv->bounds.size.height * fromLayer->priv->anchorPoint.y;

// Convert to world-view
CGAffineTransform fromTransform;
GetLayerTransform(fromLayer, &fromTransform);
Expand All @@ -2381,11 +2392,11 @@ CGPoint _legacyConvertPoint(CGPoint point, CALayer* fromLayer, CALayer* toLayer)
GetLayerTransform(toLayer, &toTransform);
toTransform = CGAffineTransformInvert(toTransform);
point = CGPointApplyAffineTransform(point, toTransform);

// Convert the point from center-based position
point.x += toLayer->priv->bounds.size.width * toLayer->priv->anchorPoint.x;
point.y += toLayer->priv->bounds.size.height * toLayer->priv->anchorPoint.y;

return point;
}

Expand Down Expand Up @@ -2439,10 +2450,16 @@ + (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer*)fromLayer toLayer:(CA
// How does our new convertPoint logic compare to the legacy logic?
CGPoint legacyPoint = _legacyConvertPoint(point, fromLayer, toLayer);
if (!_floatAlmostEqual(ret.x, legacyPoint.x) || !_floatAlmostEqual(ret.y, legacyPoint.y)) {
TraceWarning(TAG, L"convertPoint: The legacy point {%f, %f} did not match the new point {%f, %f}!", legacyPoint.x, legacyPoint.y, ret.x, ret.y);
TraceWarning(TAG,
L"convertPoint: The legacy point {%f, %f} did not match the new point {%f, %f}!",
legacyPoint.x,
legacyPoint.y,
ret.x,
ret.y);
}

TraceVerbose(TAG, L"convertPoint:{%f, %f} to:{%f, %f}, legacyPoint={%f, %f}", point.x, point.y, ret.x, ret.y, legacyPoint.x, legacyPoint.y);
TraceVerbose(
TAG, L"convertPoint:{%f, %f} to:{%f, %f}, legacyPoint={%f, %f}", point.x, point.y, ret.x, ret.y, legacyPoint.x, legacyPoint.y);
}

return ret;
Expand Down
4 changes: 4 additions & 0 deletions Frameworks/UIKit/NSLayoutManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <UIKit/UIGraphics.h>

#include "CoreTextInternal.h"
#include "CGContextInternal.h"

#include <vector>
#include <functional>
Expand Down Expand Up @@ -257,6 +258,9 @@ - (void)drawGlyphsForGlyphRange:(NSRange)range atPoint:(CGPoint)position {
CGContextSaveGState(curCtx);
CGContextSetTextMatrix(curCtx, CGAffineTransformMakeScale(1.0f, -1.0f));

_CGContextPushBeginDraw(curCtx);
auto popEnd = wil::ScopeExit([curCtx]() { _CGContextPopEndDraw(curCtx); });

int count = [_ctLines count];
for (int curLine = 0; curLine < count; curLine++) {
CTLineRef line = (CTLineRef)_ctLines[curLine];
Expand Down
3 changes: 3 additions & 0 deletions Frameworks/UIKit/NSString+UIKitAdditions.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#import <Foundation/NSMutableDictionary.h>
#import "CoreGraphics/CGContext.h"
#import "CoreTextInternal.h"
#import "CGContextInternal.h"
#import "NSParagraphStyleInternal.h"
#import <assert.h>
#import "LoggingNative.h"
Expand Down Expand Up @@ -145,6 +146,8 @@ - (CGSize)drawInRect:(CGRect)rect withFont:(UIFont*)font lineBreakMode:(UILineBr
}

CGContextRef context = UIGraphicsGetCurrentContext();
_CGContextPushBeginDraw(context);
auto popEnd = wil::ScopeExit([context]() { _CGContextPopEndDraw(context); });

// Invert text matrix so glyphs are drawn with correct orientation
CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0f, -1.0f));
Expand Down
21 changes: 13 additions & 8 deletions Frameworks/UIKit/UITabBarButton.mm
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,17 @@ - (void)drawRect:(CGRect)pos {
CGRect rect;
rect = [self bounds];

CGContextRef context = UIGraphicsGetCurrentContext();

id tabBar = [self superview];
id selectedItem = [tabBar selectedItem];
if (selectedItem == _item) {
id selectionIndicatorImage = [tabBar selectionIndicatorImage];
if (selectionIndicatorImage != nil) {
[selectionIndicatorImage drawInRect:rect];
} else {
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), (CGColorRef)[UIColor grayColor]);
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
CGContextSetFillColorWithColor(context, (CGColorRef)[UIColor grayColor]);
CGContextFillRect(context, rect);
}
}

Expand Down Expand Up @@ -107,16 +109,19 @@ - (void)drawRect:(CGRect)pos {
clipRect.origin.y = 5.0f;
clipRect.size.width = rect.size.width - clipRect.origin.x - 3.0f;
clipRect.size.height = rect.size.height - clipRect.origin.y - 13.0f;
CGContextSaveGState(UIGraphicsGetCurrentContext());
CGContextClipToRect(UIGraphicsGetCurrentContext(), clipRect);
CGContextDrawImage(UIGraphicsGetCurrentContext(), drawRect, CGBitmapContextGetImage(context));
CGContextRestoreGState(UIGraphicsGetCurrentContext());
CGContextSaveGState(context);
CGContextClipToRect(context, clipRect);
CGContextDrawImage(context, drawRect, CGBitmapContextGetImage(context));
CGContextRestoreGState(context);

CGContextRelease(context);
}

NSString* title = [_item title];
if (title != nil) {
_CGContextPushBeginDraw(context);
auto popEnd = wil::ScopeExit([context]() { _CGContextPopEndDraw(context); });

CGSize size;
id font = [UIFont defaultFont];
size = [title sizeWithFont:font constrainedToSize:CGSizeMake(0.0f, 0.0f) lineBreakMode:UILineBreakModeClip];
Expand All @@ -127,9 +132,9 @@ - (void)drawRect:(CGRect)pos {
textRect.size.width = rect.size.width;
textRect.size.height = size.height;
// TODO(DH)
//EbrCenterTextInRectVertically(&textRect, &size, font);
// EbrCenterTextInRectVertically(&textRect, &size, font);

CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), (CGColorRef)[UIColor whiteColor]);
CGContextSetFillColorWithColor(context, (CGColorRef)[UIColor whiteColor]);
size = [title drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeClip alignment:UITextAlignmentCenter];
}
}
Expand Down
8 changes: 6 additions & 2 deletions Frameworks/UIKit/UITextView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,11 @@ - (void)setSpellCheckingType:(UITextSpellCheckingType)spellType {
@Status Interoperable
*/
- (void)drawRect:(CGRect)rect {
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [_textColor CGColor]);
CGContextRef ctx = UIGraphicsGetCurrentContext();
_CGContextPushBeginDraw(ctx);
auto popEnd = wil::ScopeExit([ctx]() { _CGContextPopEndDraw(ctx); });

CGContextSetFillColorWithColor(ctx, [_textColor CGColor]);

NSRange range{ 0, INT_MAX };
CGPoint origin = self.bounds.origin;
Expand Down Expand Up @@ -759,7 +763,7 @@ - (CGSize)sizeThatFits:(CGSize)fitSize {
centerRect.origin.y = 0;
centerRect.size = fontExtent;
// TODO(DH)
//EbrCenterTextInRectVertically(&centerRect, &fontExtent, _font);
// EbrCenterTextInRectVertically(&centerRect, &fontExtent, _font);
rect.origin.y += centerRect.origin.y;

ret.width = ourRect.size.width;
Expand Down
17 changes: 10 additions & 7 deletions Frameworks/UIKit/UIView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#import "UIGestureRecognizerInternal.h"
#import "CALayerInternal.h"
#import "CAAnimationInternal.h"
#import "CGContextInternal.h"
#import <AutoLayout.h>
#import <NSLayoutConstraint+AutoLayout.h>
#import "NSLayoutAnchorInternal.h"
Expand Down Expand Up @@ -243,7 +244,7 @@ - (bool)_processGesturesForTouch:(UITouch*)touch event:(UIEvent*)event touchEven
// scanning DManip Gestures, if one gesture is ongoing, cancel all DManip Gestures
// otherwise, send Touch to DManip gestures
for (int i = 0; i < dManipGestureCount; i++) {
UIGestureRecognizer* dManipGesture = dManipRecognizers[i];
UIGestureRecognizer* dManipGesture = dManipRecognizers[i];
if (gestureOnGoing) {
[dManipGesture _cancelIfActive];
if (DEBUG_GESTURES) {
Expand Down Expand Up @@ -527,9 +528,7 @@ - (UITouchPhase)_processPointerEvent:(WUXIPointerRoutedEventArgs*)pointerEventAr
if (!touchPoint.touch->_view) {
// Ignore if the pointer isn't captured
if (DEBUG_TOUCHES_LIGHT) {
TraceVerbose(TAG,
L"View for touch is nil!, ignoring touch for touchPhase %d.",
touchPhase);
TraceVerbose(TAG, L"View for touch is nil!, ignoring touch for touchPhase %d.", touchPhase);
}
} else if (touchPhase != UITouchPhaseBegan && ![touchPoint.touch->_view->priv->currentTouches containsObject:touchPoint.touch]) {
// Ignore if the pointer isn't captured
Expand Down Expand Up @@ -2252,14 +2251,18 @@ - (CGPoint)convertPoint:(CGPoint)pos fromView:(UIView*)fromView {
*/
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
UIGraphicsPushContext(context);
_CGContextPushBeginDraw(context);

auto popEnd = wil::ScopeExit([context]() {
_CGContextPopEndDraw(context);
UIGraphicsPopContext();
});

CGRect bounds;
// TODO(DH)
bounds = self.bounds;
//bounds = CGContextGetClipBoundingBox(context);
// bounds = CGContextGetClipBoundingBox(context);
[self drawRect:bounds];

UIGraphicsPopContext();
}

/**
Expand Down
18 changes: 17 additions & 1 deletion Frameworks/include/CGContextInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ COREGRAPHICS_EXPORT bool CGContextIsPointInPath(CGContextRef c, bool eoFill, flo
COREGRAPHICS_EXPORT void CGContextDrawGlyphRun(CGContextRef ctx, const DWRITE_GLYPH_RUN* glyphRun);

// Bitmap Context Internal
COREGRAPHICS_EXPORT CGContextRef _CGBitmapContextCreateWithRenderTarget(ID2D1RenderTarget* renderTarget, CGImageRef img, WICPixelFormatGUID outputPixelFormat);
COREGRAPHICS_EXPORT CGContextRef _CGBitmapContextCreateWithRenderTarget(ID2D1RenderTarget* renderTarget,
CGImageRef img,
WICPixelFormatGUID outputPixelFormat);
COREGRAPHICS_EXPORT CGContextRef _CGBitmapContextCreateWithFormat(int width, int height, __CGSurfaceFormat fmt);
COREGRAPHICS_EXPORT CGImageRef CGBitmapContextGetImage(CGContextRef ctx);
// Reduces the number of BeginDraw() and EndDraw() calls needed, by counting in a stack-like manner,
// and only calling BeginDraw()/EndDraw() when the stack is empty/emptied
COREGRAPHICS_EXPORT void _CGContextPushBeginDraw(CGContextRef ctx);
COREGRAPHICS_EXPORT void _CGContextPopEndDraw(CGContextRef ctx);

// If currently in a Begin/EndDraw stack, Escape will EndDraw(), Unescape will BeginDraw()
// For scenarios where a Begin/EndDraw pair needs to be temporarily escaped, to be returned to at a later time
// Ie:
// - Switching render targets - Illegal to do so if currently in a Begin/EndDraw pair
// - Cairo - ID2D1RenderTarget is considered to 'own' the bitmap during Begin/EndDraw,
// unsafe to edit the same bitmap from cairo at this time
// Also counts in a stack-like manner, so that the escape and unescape only happen once
COREGRAPHICS_EXPORT void _CGContextEscapeBeginEndDrawStack(CGContextRef ctx);
COREGRAPHICS_EXPORT void _CGContextUnescapeBeginEndDrawStack(CGContextRef ctx);
4 changes: 4 additions & 0 deletions build/CoreGraphics/dll/CoreGraphics.def
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ LIBRARY CoreGraphics
_CGContextCreateWithD2DRenderTarget
_CGGetD2DFactory
_CGGetPixelFormatProperties
_CGContextPushBeginDraw
_CGContextPopEndDraw
_CGContextEscapeBeginEndDrawStack
_CGContextUnescapeBeginEndDrawStack

; CGDataConsumer.mm
CGDataConsumerCreate
Expand Down
Binary file modified docs/CoreText/WinObjC.CoreText.docx
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,7 @@ - (void)viewDidLoad {
textEdit.autoresizingMask = infoLabel.autoresizingMask = focusButton.autoresizingMask =
UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;

infoLabel.text = @"Click the control to gain focus. Use the keyboard to type and caret nagivate. Hold shift to update the "
infoLabel.text = @"Click the control to gain focus. Use the keyboard to type and caret navigate. Hold shift to update the "
@"selection. Right click or Ctrl+C or V to copy and paste.";
infoLabel.numberOfLines = 0;
infoLabel.textAlignment = NSTextAlignmentCenter;
Expand Down
2 changes: 1 addition & 1 deletion samples/XAMLCatalog/XAMLCatalog/XAMLCatalog.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@
<constraint firstAttribute="height" constant="100" id="jzf-wl-pPs"/>
<constraint firstAttribute="width" constant="300" id="oPb-uK-RMZ"/>
</constraints>
<string key="text">Click the control to gain focus. Use the keyboard to type and caret nagivate. Hold shift to update the selection. Right click or Ctrl+C or V to copy and paste.</string>
<string key="text">Click the control to gain focus. Use the keyboard to type and caret navigate. Hold shift to update the selection. Right click or Ctrl+C or V to copy and paste.</string>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
Expand Down

0 comments on commit d5a86b9

Please sign in to comment.