diff --git a/Frameworks/CoreGraphics/CGContext.mm b/Frameworks/CoreGraphics/CGContext.mm index e3a4e32bb9..0ee0f46a19 100644 --- a/Frameworks/CoreGraphics/CGContext.mm +++ b/Frameworks/CoreGraphics/CGContext.mm @@ -1238,3 +1238,39 @@ void CGContextDrawGlyphRun(CGContextRef ctx, const DWRITE_GLYPH_RUN* glyphRun) { void _CGContextSetScaleFactor(CGContextRef ctx, float scale) { ctx->Backing()->_CGContextSetScaleFactor(scale); } + +#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 \ No newline at end of file diff --git a/Frameworks/CoreGraphics/CGContextCairo.mm b/Frameworks/CoreGraphics/CGContextCairo.mm index fce073be00..ce25167cea 100644 --- a/Frameworks/CoreGraphics/CGContextCairo.mm +++ b/Frameworks/CoreGraphics/CGContextCairo.mm @@ -105,6 +105,11 @@ // An OSS request has been submitted. The link for the request is: // https://osstool.microsoft.com/palamida/RequestDetails.htm?rid=40072&projectId=1 void CGContextCairo::_cairoImageSurfaceBlur(cairo_surface_t* surface) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + double blur = curState->shadowBlur; if (surface == NULL || blur <= 0) { @@ -171,6 +176,11 @@ } void CGContextCairo::_cairoContextStrokePathShadow() { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + int i; double width, height, deltaWidth, deltaHeight; @@ -283,6 +293,11 @@ } void CGContextCairo::Clear(float r, float g, float b, float a) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); CGRect rct; @@ -303,6 +318,11 @@ } void CGContextCairo::DrawImage(CGImageRef img, CGRect src, CGRect dest, bool tiled) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); if (dest.size.width == 0.0f || dest.size.height == 0.0f) @@ -854,6 +874,11 @@ } void CGContextCairo::CGContextClearRect(CGRect rct) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -886,6 +911,11 @@ } void CGContextCairo::CGContextFillRect(CGRect rct) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1102,6 +1132,11 @@ } void CGContextCairo::CGContextStrokeEllipseInRect(CGRect rct) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1135,6 +1170,11 @@ } void CGContextCairo::CGContextFillEllipseInRect(CGRect rct) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1174,6 +1214,11 @@ } void CGContextCairo::CGContextStrokePath() { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1197,6 +1242,11 @@ } void CGContextCairo::CGContextStrokeRect(CGRect rct) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1230,6 +1280,11 @@ } void CGContextCairo::CGContextStrokeRectWithWidth(CGRect rct, float width) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1260,6 +1315,11 @@ } void CGContextCairo::CGContextFillPath() { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1288,6 +1348,11 @@ } void CGContextCairo::CGContextEOFillPath() { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1316,6 +1381,11 @@ } void CGContextCairo::CGContextEOClip() { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); TraceWarning(TAG, L"CGContextEOClip not supported"); @@ -1326,6 +1396,11 @@ } void CGContextCairo::CGContextDrawPath(CGPathDrawingMode mode) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1426,6 +1501,11 @@ } void CGContextCairo::CGContextDrawLinearGradient(CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint, DWORD options) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1467,6 +1547,11 @@ void CGContextCairo::CGContextDrawRadialGradient( CGGradientRef gradient, CGPoint startCenter, float startRadius, CGPoint endCenter, float endRadius, DWORD options) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); _isDirty = true; @@ -1673,6 +1758,11 @@ } void CGContextCairo::CGContextClip() { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); LOCK_CAIRO(); @@ -1710,6 +1800,11 @@ } void CGContextCairo::CGContextClipToRect(CGRect rect) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); LOCK_CAIRO(); cairo_path_t* oldPath = cairo_copy_path(_drawContext); @@ -1738,6 +1833,11 @@ } void CGContextCairo::CGContextBeginTransparencyLayerWithRect(CGRect rect, id auxInfo) { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); LOCK_CAIRO(); cairo_save(_drawContext); @@ -1756,6 +1856,11 @@ } void CGContextCairo::CGContextEndTransparencyLayer() { + // TODO #1635: It's unsafe to edit the bitmap not through D2D1RenderTarget, during a BeginDraw() + // Escape any Begin/EndDraw() pairs for the lifetime of this function (to be removed during the CGD2D merge) + _CGContextEscapeBeginEndDrawStack(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextUnescapeBeginEndDrawStack(this->_rootContext); }); + ObtainLock(); LOCK_CAIRO(); // Retrieve the group as a pattern @@ -1863,7 +1968,7 @@ CGContextStrokePath(); ID2D1RenderTarget* imgRenderTarget = _imgDest->Backing()->GetRenderTarget(); - THROW_NS_IF_NULL(E_UNEXPECTED, imgRenderTarget); + THROW_HR_IF_NULL(E_UNEXPECTED, imgRenderTarget); // Apply the required transformations as set in the context. // We need some special handling in transform as CoreText in iOS renders from bottom left but DWrite on Windows does top left. @@ -1898,7 +2003,9 @@ D2D1::ColorF brushColor = D2D1::ColorF(curState->curFillColor.r, curState->curFillColor.g, curState->curFillColor.b, curState->curFillColor.a); - imgRenderTarget->BeginDraw(); + _CGContextPushBeginDraw(_rootContext); + auto popEnd = wil::ScopeExit([this]() { _CGContextPopEndDraw(this->_rootContext); }); + // Draw the glyph using ID2D1RenderTarget ComPtr brush; THROW_IF_FAILED(imgRenderTarget->CreateSolidColorBrush(brushColor, &brush)); @@ -1931,8 +2038,6 @@ userTransform = CGAffineTransformTranslate(userTransform, glyphRun->glyphAdvances[i], 0); } } - - THROW_IF_FAILED(imgRenderTarget->EndDraw()); } // TODO 1077:: Remove once D2D render target is implemented diff --git a/Frameworks/CoreGraphics/CGContextImpl.mm b/Frameworks/CoreGraphics/CGContextImpl.mm index ed2054cba4..c01c33a52a 100644 --- a/Frameworks/CoreGraphics/CGContextImpl.mm +++ b/Frameworks/CoreGraphics/CGContextImpl.mm @@ -743,4 +743,4 @@ // TODO 1077:: Remove once D2D render target is implemented void CGContextImpl::_CGContextSetScaleFactor(float scale) { -} +} \ No newline at end of file diff --git a/Frameworks/CoreText/CTFrame.mm b/Frameworks/CoreText/CTFrame.mm index 029086cf13..bfebb4a5cd 100644 --- a/Frameworks/CoreText/CTFrame.mm +++ b/Frameworks/CoreText/CTFrame.mm @@ -19,6 +19,7 @@ #import "DWriteWrapper_CoreText.h" #import "CoreTextInternal.h" +#import "CGContextInternal.h" #import "CGPathInternal.h" const CFStringRef kCTFrameProgressionAttributeName = static_cast(@"kCTFrameProgressionAttributeName"); @@ -123,6 +124,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); diff --git a/Frameworks/CoreText/CTLine.mm b/Frameworks/CoreText/CTLine.mm index e43b176632..a19190635b 100644 --- a/Frameworks/CoreText/CTLine.mm +++ b/Frameworks/CoreText/CTLine.mm @@ -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) { diff --git a/Frameworks/QuartzCore/CALayer.mm b/Frameworks/QuartzCore/CALayer.mm index 29242dd6df..db45a41ed1 100644 --- a/Frameworks/QuartzCore/CALayer.mm +++ b/Frameworks/QuartzCore/CALayer.mm @@ -368,6 +368,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; @@ -410,8 +417,6 @@ - (void)renderInContext:(CGContextRef)ctx { LLTREE_FOREACH(curLayer, priv) { [curLayer->self renderInContext:ctx]; } - - CGContextRestoreGState(ctx); } /** @@ -494,6 +499,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; @@ -538,7 +550,6 @@ - (void)display { } CGContextReleaseLock(drawContext); - CGContextRelease(drawContext); // If we've drawn anything, set it as our contents if (!CGContextIsDirty(drawContext)) { @@ -2348,7 +2359,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); @@ -2358,11 +2369,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; } @@ -2416,10 +2427,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; diff --git a/Frameworks/UIKit/NSLayoutManager.mm b/Frameworks/UIKit/NSLayoutManager.mm index 85dedf6ec7..0769d8fe40 100644 --- a/Frameworks/UIKit/NSLayoutManager.mm +++ b/Frameworks/UIKit/NSLayoutManager.mm @@ -23,6 +23,7 @@ #include #include "CoreTextInternal.h" +#include "CGContextInternal.h" #include #include @@ -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]; diff --git a/Frameworks/UIKit/NSString+UIKitAdditions.mm b/Frameworks/UIKit/NSString+UIKitAdditions.mm index e6d92a2a5e..5ea4493ef2 100644 --- a/Frameworks/UIKit/NSString+UIKitAdditions.mm +++ b/Frameworks/UIKit/NSString+UIKitAdditions.mm @@ -22,6 +22,7 @@ #import #import "CoreGraphics/CGContext.h" #import "CoreTextInternal.h" +#import "CGContextInternal.h" #import "NSParagraphStyleInternal.h" #import #import "LoggingNative.h" @@ -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)); diff --git a/Frameworks/UIKit/UITabBarButton.mm b/Frameworks/UIKit/UITabBarButton.mm index e866d4115e..915f684361 100644 --- a/Frameworks/UIKit/UITabBarButton.mm +++ b/Frameworks/UIKit/UITabBarButton.mm @@ -45,6 +45,8 @@ - (void)drawRect:(CGRect)pos { CGRect rect; rect = [self bounds]; + CGContextRef context = UIGraphicsGetCurrentContext(); + id tabBar = [self superview]; id selectedItem = [tabBar selectedItem]; if (selectedItem == _item) { @@ -52,8 +54,8 @@ - (void)drawRect:(CGRect)pos { if (selectionIndicatorImage != nil) { [selectionIndicatorImage drawInRect:rect]; } else { - CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), (CGColorRef)[UIColor grayColor]); - CGContextFillRect(UIGraphicsGetCurrentContext(), rect); + CGContextSetFillColorWithColor(context, (CGColorRef)[UIColor grayColor]); + CGContextFillRect(context, rect); } } @@ -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]; @@ -128,7 +133,7 @@ - (void)drawRect:(CGRect)pos { textRect.size.height = size.height; EbrCenterTextInRectVertically(&textRect, &size, font); - CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), (CGColorRef)[UIColor whiteColor]); + CGContextSetFillColorWithColor(context, (CGColorRef)[UIColor whiteColor]); size = [title drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeClip alignment:UITextAlignmentCenter]; } } diff --git a/Frameworks/UIKit/UITextView.mm b/Frameworks/UIKit/UITextView.mm index b359854c51..7a320195c8 100644 --- a/Frameworks/UIKit/UITextView.mm +++ b/Frameworks/UIKit/UITextView.mm @@ -397,7 +397,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; diff --git a/Frameworks/UIKit/UIView.mm b/Frameworks/UIKit/UIView.mm index f17765c4a5..3086cc5afc 100644 --- a/Frameworks/UIKit/UIView.mm +++ b/Frameworks/UIKit/UIView.mm @@ -41,6 +41,7 @@ #import "UIGestureRecognizerInternal.h" #import "CALayerInternal.h" #import "CAAnimationInternal.h" +#import "CGContextInternal.h" #import #import #import "NSLayoutAnchorInternal.h" @@ -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) { @@ -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 @@ -2252,12 +2251,16 @@ - (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; bounds = CGContextGetClipBoundingBox(context); [self drawRect:bounds]; - - UIGraphicsPopContext(); } /** diff --git a/Frameworks/include/CGContextInternal.h b/Frameworks/include/CGContextInternal.h index 11ad29f616..b82cf70dec 100644 --- a/Frameworks/include/CGContextInternal.h +++ b/Frameworks/include/CGContextInternal.h @@ -26,6 +26,7 @@ #import "Starboard.h" #import +#import class CGContextImpl; COREGRAPHICS_EXPORT void EbrCenterTextInRectVertically(CGRect* rect, CGSize* textSize, id font); @@ -49,6 +50,21 @@ COREGRAPHICS_EXPORT bool CGContextIsPointInPath(CGContextRef c, bool eoFill, flo COREGRAPHICS_EXPORT void CGContextDrawGlyphRun(CGContextRef ctx, const DWRITE_GLYPH_RUN* glyphRun); +// 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); + // TODO 1077:: Remove once D2D render target is implemented COREGRAPHICS_EXPORT void _CGContextSetScaleFactor(CGContextRef ctx, float scale); @@ -57,6 +73,13 @@ class __CGContext : private objc_object { float scale; CGContextImpl* _backing; + // 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 }; + __CGContext(CGImageRef pDest); ~__CGContext(); diff --git a/build/CoreGraphics/dll/CoreGraphics.def b/build/CoreGraphics/dll/CoreGraphics.def index d66a935b08..c57a1a2aaf 100644 --- a/build/CoreGraphics/dll/CoreGraphics.def +++ b/build/CoreGraphics/dll/CoreGraphics.def @@ -239,6 +239,10 @@ LIBRARY CoreGraphics CGContextIsPointInPath EbrCenterTextInRectVertically _CGContextSetScaleFactor + _CGContextPushBeginDraw + _CGContextPopEndDraw + _CGContextEscapeBeginEndDrawStack + _CGContextUnescapeBeginEndDrawStack ; CGDataConsumer.mm CGDataConsumerCreate diff --git a/docs/CoreText/WinObjC.CoreText.docx b/docs/CoreText/WinObjC.CoreText.docx index cddca51d36..bba8007d8c 100644 Binary files a/docs/CoreText/WinObjC.CoreText.docx and b/docs/CoreText/WinObjC.CoreText.docx differ diff --git a/samples/XAMLCatalog/XAMLCatalog/CustomTextControlViewController.m b/samples/XAMLCatalog/XAMLCatalog/CustomTextControlViewController.m index d80b0f0ef9..1d15e42ff7 100644 --- a/samples/XAMLCatalog/XAMLCatalog/CustomTextControlViewController.m +++ b/samples/XAMLCatalog/XAMLCatalog/CustomTextControlViewController.m @@ -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; diff --git a/samples/XAMLCatalog/XAMLCatalog/XAMLCatalog.storyboard b/samples/XAMLCatalog/XAMLCatalog/XAMLCatalog.storyboard index 8efcbbc29b..60a8224739 100644 --- a/samples/XAMLCatalog/XAMLCatalog/XAMLCatalog.storyboard +++ b/samples/XAMLCatalog/XAMLCatalog/XAMLCatalog.storyboard @@ -360,7 +360,7 @@ - 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. + 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.