-
Notifications
You must be signed in to change notification settings - Fork 806
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for "enhanced error handling" to CGContext and CALayer #2119
Add support for "enhanced error handling" to CGContext and CALayer #2119
Conversation
Frameworks/CoreGraphics/CGContext.mm
Outdated
@@ -47,6 +47,7 @@ | |||
#import <cmath> | |||
#import <list> | |||
#import <vector> | |||
#import <array> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oof
Frameworks/CoreGraphics/CGContext.mm
Outdated
@@ -471,21 +475,32 @@ inline void ClearPath() { | |||
} | |||
|
|||
inline bool ShouldDraw() { | |||
return CurrentGState().ShouldDraw(); | |||
return FAILED(_firstErrorHr) || CurrentGState().ShouldDraw(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hilarious. prefer SUCCEEDED
or !FAILED
#Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And probably &&
#Closed
@@ -274,6 +275,9 @@ inline HRESULT GetTarget(ID2D1Image** pTarget) { | |||
// Since nothing needs to actually be put on a stack, just increment a counter insteads | |||
std::atomic_uint32_t _beginEndDrawDepth = { 0 }; | |||
|
|||
bool _useEnhancedErrorHandling{ false }; | |||
HRESULT _firstErrorHr{ S_OK }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: just _firstError, no need to put type in name
deviceContext->BeginDraw(); | ||
} | ||
} | ||
|
||
inline HRESULT PopEndDraw() { | ||
HRESULT hr = S_OK; | ||
if (--(_beginEndDrawDepth) == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in PushBeginDraw we'll not increment _beginEndDrawDepth if FAILED, but here we always decrement, so we may wrap around into the middle of nowhere. Should we even attempt to draw here if the context failed at some previous point? #Resolved
Frameworks/CoreGraphics/CGContext.mm
Outdated
#pragma endregion | ||
|
||
#pragma region Enhanced Error Handling | ||
const CFStringRef kCGErrorDomainIslandwood = CFSTR("kCGErrorDomainIslandwood"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: WinObjC?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aballway hm. we go back and forth on this a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like standardizing on WinOBJC :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I promise I removed the comment above before submitting my review. :/
return hr; | ||
} | ||
|
||
inline void EnableEnhancedErrorHandling() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this make more sense as a global setting vs. per context? #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aballway no; external bitmap context consumers are not enlightened as to error handling. #ByDesign
Frameworks/CoreGraphics/CGContext.mm
Outdated
auto error = woc::MakeStrongCF<CFErrorRef>(CFErrorCreate(nullptr, kCGErrorDomainIslandwood, errorCode, nullptr)); | ||
|
||
if (outError) { | ||
*outError = error.detach(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: just make the error here, rather than making StrongCF and detaching #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cleverer. #Closed
Frameworks/QuartzCore/CALayer.mm
Outdated
CGContextSetFillColorWithColor(drawContext, [static_cast<UIColor*>(priv->_backgroundColor) CGColor]); | ||
// UIKit and CALayer consumers expect the origin to be in the top left. | ||
// CoreGraphics defaults to the bottom left, so we must flip and translate the canvas. | ||
CGContextTranslateCTM(drawContext, 0, heightInPoints); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: squish this into one transformation like in the other places this is done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bah, i suppose ;)
Frameworks/QuartzCore/CALayer.mm
Outdated
priv->savedContext = CGContextRetain(drawContext); | ||
priv->contents = CGImageRetain(target); | ||
break; | ||
} while (tries < 3); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this is a for loop with a fake nose and a handlebar mustache
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, no magic number. Maybe stuff it in the context (if we keep the enabled flag in there) or a global that can be set by user #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know that there's worth in exposing this. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know about exposing it, but I agree that giving it a name/placing it next to the declaration of tries might be slightly more readable. #Resolved
Frameworks/QuartzCore/CALayer.mm
Outdated
} else { | ||
priv->contents = target; | ||
if (!priv->contents) { | ||
NSTraceError(TAG, @"Failed to render layer %@", self); | ||
} | ||
} else if (priv->contents) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I know you didn't modify this but please get rid of the if here
NSTraceInfo(TAG, @"Hardware device disappeared when rendering %@; retrying.", self); | ||
++tries; | ||
continue; | ||
default: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use the constant here for this case, and have a default "something went terribly wrong" case. #ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aballway default IS the "something went terribly wrong" case. #ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aye but it could also be "something went terribly horribly wrong" where we don't even have a name for it 😜 #ByDesign
Frameworks/CoreGraphics/CGContext.mm
Outdated
@@ -2367,6 +2382,10 @@ void CGContextClearRect(CGContextRef context, CGRect rect) { | |||
HRESULT __CGContext::Draw(_CGCoordinateMode coordinateMode, CGAffineTransform* additionalTransform, Lambda&& drawLambda) { | |||
auto& state = CurrentGState(); | |||
|
|||
if (FAILED(_firstErrorHr)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I assume S_FALSE is valid to have here?
or should we take care of that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
S_FALSE
is a valid "success" return :)
if (--(_beginEndDrawDepth) == 0) { | ||
RETURN_IF_FAILED(deviceContext->EndDraw()); | ||
hr = deviceContext->EndDraw(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm thinking about S_FALSE, looking at the documentation, i don't see much about it for EndDraw(), but given it's technically a success, I assume we don't have to worry about it? #ByDesign
Frameworks/CoreGraphics/CGContext.mm
Outdated
@@ -2402,12 +2421,6 @@ void CGContextClearRect(CGContextRef context, CGRect rect) { | |||
} | |||
|
|||
auto cleanup = wil::ScopeExit([this, layer]() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto cleanup = wil::ScopeExit(this, layer { [](start = 3, length = 48)
no longer needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
Frameworks/QuartzCore/CALayer.mm
Outdated
priv->backgroundColor.a); | ||
} else { | ||
CGContextClearToColor(drawContext, 0, 0, 0, 0); | ||
CGRect wholeRect = CGRectMake(0, 0, width, height); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ect wholeRect = CGRectMake(0, 0, width, height); [](start = 19, length = 48)
nit: you can directly pass it
include/CoreGraphics/CGContext.h
Outdated
// A developer using enhanced error handling MUST check the status of a completed set of drawing | ||
// operations using CGContextIwGetError() and determine whether to redraw the frame. | ||
COREGRAPHICS_EXPORT void CGContextIwEnableEnhancedErrorHandling(CGContextRef context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we should have some tag to indicate we have an extension?
Also it would make it easier to look at all the extensions we support/have
include/CoreGraphics/CGContext.h
Outdated
|
||
typedef CF_ENUM(CFIndex, CGContextIwErrorCode) { | ||
kCGContextErrorDeviceReset, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: set the values explicitly to reduce future mishaps (if it were to ever happen)
include/CoreGraphics/CGContext.h
Outdated
}; | ||
|
||
COREGRAPHICS_EXPORT bool CGContextIwGetError(CGContextRef context, CFErrorRef* /* returns-retained */ error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/* returns-retained */ [](start = 79, length = 22)
since this is a public api and we are adding it, we should add c/c++ style document to it, to explicitly say what it returns and what's going on.
Frameworks/CoreGraphics/CGContext.mm
Outdated
if (!FAILED(_firstErrorHr) && (_beginEndDrawDepth)++ == 0) { | ||
deviceContext->BeginDraw(); | ||
if ((_beginEndDrawDepth)++ == 0) { | ||
if (__builtin_expect(SUCCEEDED(_firstErrorHr), true)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fancy #Resolved
The doc comment format here matches that used in CoreFoundation (!) |
return false; | ||
} | ||
|
||
CGContextIwErrorCode errorCode = kCGContextErrorInvalidParameter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: nice case for a ternary
@@ -85,6 +86,10 @@ | |||
NSString* const kCAFilterNearest = @"kCAFilterNearest"; | |||
NSString* const kCAFilterTrilinear = @"kCAFilterTrilinear"; | |||
|
|||
// The number of rendering attempts a CALayer will make when | |||
// its backing device disappears. | |||
static const unsigned int _kCALayerRenderAttempts = 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super nit: _skCA.. ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or s_kCA... or sc_CA...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about static_const_CFStyleConstantWithAK_CALayer...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or just inline the value since it's only used in one place 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯
That can't be good.
|
Frameworks/CoreGraphics/CGContext.mm
Outdated
} | ||
|
||
CGContextIwErrorCode errorCode = kCGContextErrorInvalidParameter; | ||
switch (hr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switch missing default. Also could use an if here instead. Other errors are likely catastrophic with no way to recover.
Frameworks/CoreGraphics/CGContext.mm
Outdated
} | ||
|
||
inline void PushBeginDraw() { | ||
if ((_beginEndDrawDepth)++ == 0) { | ||
deviceContext->BeginDraw(); | ||
if (__builtin_expect(SUCCEEDED(_firstErrorHr), true)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
other than the ICE, there is really no need to use __builtin_expect to optimize here - this is not a hot code path :).
Frameworks/CoreGraphics/CGContext.mm
Outdated
#pragma endregion | ||
|
||
#pragma region Enhanced Error Handling | ||
const CFStringRef kCGErrorDomainIslandwood = CFSTR("kCGErrorDomainIslandwood"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like standardizing on WinOBJC :)
Frameworks/QuartzCore/CALayer.mm
Outdated
continue; | ||
default: { | ||
NSTraceError(TAG, @"Error %@ rendering %@.", renderError.get(), self); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fail_fast here instead. It would give us enough to up the number of retries. Another thing to note is that we should probably have another counter that falls back to warp if the particular device fails many times. That is, if each frame takes 2 attempts to draw, we are probably much better off using warp. This of course isn't necessary until we move to h/w acceleration.
Frameworks/QuartzCore/CALayer.mm
Outdated
@@ -577,7 +577,10 @@ - (void)display { | |||
++tries; | |||
continue; | |||
default: { | |||
NSTraceError(TAG, @"Error %@ rendering %@.", renderError.get(), self); | |||
FAIL_FAST_MSG("Failed to render <%hs %p>: %hs", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error description can be augmented to include the failing HRESULT, printing it plus the layer should allow us to correlate events and layer classes.
Nit: Could you update the description with Fixes# before merge? |
Frameworks/CoreGraphics/CGContext.mm
Outdated
#pragma endregion | ||
|
||
#pragma region Enhanced Error Handling | ||
const CFStringRef kCGErrorDomainIslandwood = CFSTR("kCGErrorDomainIslandwood"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I promise I removed the comment above before submitting my review. :/
@msft-Jeyaram: Whichever of this pull request or #2133 merges second will have to add error handling to |
Ugh, the build agent is stuck downloading LFS file again. |
The latest change here adds this cool thing:
The important bit being |
Frameworks/QuartzCore/CALayer.mm
Outdated
} else { | ||
CGContextClearToColor(drawContext, 0, 0, 0, 0); | ||
|
||
if (priv->_backgroundColor != nil && (int)[static_cast<UIColor*>(priv->_backgroundColor) _type] != solidBrush) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MSFTFox this is straight from your pull request. however, i'm concerned about what it's checking.
It looks like it will NOT fill the background if there is a solid color/solid brush background color?
@@ -524,51 +531,69 @@ - (void)display { | |||
return; | |||
} | |||
|
|||
// Create the contents | |||
CGContextRef drawContext = CreateLayerContentsBitmapContext32(width, height, priv->contentsScale); | |||
unsigned int tries = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: for (unsigned int tries = 0; tries < _kCA...; ++tries)
object_getClassName(self), | ||
self, | ||
[[static_cast<NSError*>(renderError.get()) debugDescription] UTF8String]); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: do we need a break if we're FAIL_FAST?
Direct2D can emit a failure or remove the backing hardware device from your render target at the end of any drawing operation. However, CoreGraphics does not natively have support for communicating failure states out to interested consumers.
Enhanced Error Handling, when enabled, allows a sufficiently motivated developer to become part of CGContext's rendering lifecycle.
The error of first and foremost importance is
D2DERR_RECREATE_TARGET
: It is not an error, but a plea to retry the last rendering operation after regenerating the render target. While CoreGraphics could batch up a list of pending drawing operations and replay them on a new target, it opts instead to report this to an Enhanced Error Handling consumer throughkCGContextErrorDeviceReset
.All other failing drawing operations result in
kCGContextErrorInvalidParameter
.In this pull request, CALayer has been augmented (in a bid to better prepare us for a hardware-accelerated future) to retry its rendering up to three (3) times if the device goes away. Since a layer can draw itself from start to finish,
-display
is the best point at which to handle the newly-minted CGContext errors.Fixes #1194.