Skip to content
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 blending operations to CGContext #2222

Merged
merged 8 commits into from
Mar 15, 2017

Conversation

DHowett-MSFT
Copy link

This pull request adds support for blending to CGContext. There are a number of caveats.

  • kCGBlendModeClear is not supported.
  • The Source In, Destination In, Source Out, Destination Atop, and Source Copy blend modes cannot be used properly without transparency layers. Their use per-primitive will result in unusual artifacts.
  • kCGBlendModePlusDarker is not supported (and was not supported in the original Cairo implementation).

Some blend modes (the ones without Source/Destination) in the name require a full buffer read-back and compose operation. This is unavoidable. 😦

This change unfortunately does include a rewrite of the CGD2D rendering stack. Instead of adding yet another nesting level of command lists and layers, I switched it to a scaffold/commit stack. Depending on various conditions, operations will be queued up, started in order, and committed in reverse order. A great many of those operations involve creating command lists and layers and blending them or otherwise composing them.

There is a not-insignificant performance penalty in certain cases:

|                                    before             after   delta
| CoreText__CTLineDrawComplete       742.51            716.11   -3.5546% faster |
| CoreText__CTLineDrawGroup         292,909           307,828    5.0934% slower |
| CoreText__CTLineDrawSingle         233.43            280.05   19.9746% slower |

times in μs.

While 19% seems like a lot, it's only 47μs/operation; an application running at 60fps requires its rendering operations complete in ~16000μs. It's not significant, but it is a cost taken per operation.

I believe there is opportunity for improvement; specifically, I had to compromise on allocations to achieve the desired effects of polymorphism in the new render operation stack.

Please examine this very closely.

Fixes #1389.

Copy link
Author

@DHowett-MSFT DHowett-MSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests to consider adding:

  • Composition into a transparency layer.
  • Composition of an image.
  • Composition with an image.

// We cannot fulfill this request.
UNIMPLEMENTED_WITH_MSG("Unsupported operator blend mode %4.04x", mode);
} else if (mode == kCGBlendModePlusDarker) {
// No UNIMPLEMENTED here: we will proceed but with an unusual output.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No UNIMPLEMENTED [](start = 11, length = 16)

nit: no, not? not sure what it means.

}

auto& state = context->CurrentGState();
state.blendMode = mode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: context->CurrentGState().blendMode = mode;

Copy link
Contributor

@aballway aballway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some comments I want to make before I forget, still trying to think of any better ways of doing this before giving a :shipit:

deviceContext->Clear({ 0, 0, 0, 0 }); // Clear the original target to transparent black.
RETURN_IF_FAILED(inputBitmap.As(&copiedImage));
}
return __super::Stage(context, deviceContext);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would prefer explicit _CRenderOpCommandListBase::Stage

// It would be ideal to store each operation on the local stack, but that is infeasible because
// of scoping concerns.
std::vector<std::unique_ptr<IRenderOperation>> operations;
operations.reserve(16);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: magic number. Why 16?

// PROPERTIES
// D2D1_BLEND_PROP_MODE: blend mode
ComPtr<ID2D1Effect> blendEffect;
FAIL_FAST_IF_FAILED(deviceContext->CreateEffect(CLSID_D2D1Blend, &blendEffect));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why fail fast here when this returns HRESULT?

@DHowett-MSFT
Copy link
Author

Ping!

Copy link
Contributor

@aballway aballway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 unable to find more elegant solution

@DHowett-MSFT
Copy link
Author

Ping 2: The Pingening!

@DHowett-MSFT
Copy link
Author

Ping 3: Ping Harder

@ms-jihua @msft-Jeyaram

deviceContext->SetTransform(__CGAffineTransformToD2D_F(transform));
auto revertTransform = wil::ScopeExit([this]() { this->deviceContext->SetTransform(D2D1::IdentityMatrix()); });
RETURN_IF_FAILED(std::forward<Lambda>(drawLambda)(this, deviceContext.Get()));
// Stage and scaffold all rendering operations; this will create layers, command lists,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i really really really like this abstraction geez

@DHowett-MSFT
Copy link
Author

@ms-jihua thank you 💯

I'm going to diagram this specific implementation, as well as how the command list blending operations work.

@DHowett-MSFT DHowett-MSFT merged commit 13999bd into microsoft:develop Mar 15, 2017
@DHowett-MSFT DHowett-MSFT deleted the 201703-blending branch March 15, 2017 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[CGD2D] Draw primitives with Porter-Duff blend modes
7 participants