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

RadialGradientBrush initial commit #1831

Merged
merged 3 commits into from
Jan 14, 2020

Conversation

jesbis
Copy link
Member

@jesbis jesbis commented Jan 11, 2020

This is a basic implementation of a RadialGradientBrush for #266.

Description

RadialGradientBrush uses XamlCompositionBrushBase to render a CompositionRadialGradientBrush.

The API is similar to CompositionRadialGradientBrush and RadialGradientBrush in WPF.

For spec details see:
microsoft/microsoft-ui-xaml-specs/pull/27

Notes:

  1. There are some minor differences between this API and previous Xaml gradient brushes (e.g. WPF), defaulting to consistency with other WinRT APIs including Composition - the spec link above has more info. It should be relatively trivial to convert any old examples
  2. The brush will fall back to solid color rendering (using XCBB's FallbackColor) on pre-1909 OS versions - the spec link above has more info
  3. The composition API enables choosing an interpolation color space that is HSL and not RGB but it's actually unsupported and throws a debug error. This implementation just passes through to and from the composition layer and does not attempt to change the system behavior. This could just be documented, the same as the composition API
  4. This API does not support direct animation since it's using a CompositionBrush. I think ideally this would be animatable using IAnimationObject, but facade animations aren't currently available downlevel below 1809 (RS5) - we could revisit this after WinUI 3 rather than attempting to fork the implementation now since it would rely on the brush implementing an interface that wouldn't exist on all supported OS versions. The spec link above has more info

Motivation and Context

I was trying out the public developer guidance to see where it could use more details, and it could definitely use a few more details 🙂 So, I made some assumptions for this feature - hopefully nothing majorly wrong? I'm hoping to update the developer guidance at some point based on how this goes.

How Has This Been Tested?

The test page RadialGradientBrushPage exercises introduced APIs via markup, code-behind and x:Bind. It includes different usage models such as locally set brushes, shared resources and dynamically created brushes.

This change also includes basic automated tests for:

  • Verifying rendering in default and fallback modes via pixel sampling
  • Adding gradient stop to collection
  • Removing gradient stop from collection

which pass locally on desktop 1909 and 1903. I did not manually test other versions or devices.

Screenshots (if appropriate):

image

image

Initial commit of basic RadialGradientBrush implementation
@jesbis jesbis added feature proposal New feature proposal release note PR that we want to call out in the next release summary team-Rendering Issue for the Rendering team labels Jan 11, 2020
@msft-github-bot msft-github-bot added the needs-triage Issue needs to be triaged by the area owners label Jan 11, 2020
case winrt::BrushMappingMode::RelativeToBoundingBox:
compositionGradientBrush.MappingMode(winrt::Windows::UI::Composition::CompositionMappingMode::Relative);
break;
}
Copy link
Contributor

@ranjeshj ranjeshj Jan 12, 2020

Choose a reason for hiding this comment

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

Are these the only two modes? should we throw if a different value is provided ? #Closed

Copy link
Member Author

@jesbis jesbis Jan 12, 2020

Choose a reason for hiding this comment

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

If we get a different value and don't explicitly set any mapping mode then the brush will just use the composition layer's default (Relative) which seems like a reasonable behavior?

I used the existing BrushMappingMode enum which has only ever had two values for ~20 years and I don't expect it will be expanded.

Is it convention to guard against future potential additions to an enum and throw?

We could also make RelativeToBoundingBox the default in the switch statement in addition to the explicit case, but that'd yield the same behavior we get as-is with the composition default. #Closed

Copy link
Contributor

@ranjeshj ranjeshj Jan 13, 2020

Choose a reason for hiding this comment

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

I'm ok with falling back to a default behavior instead of throwing, but vote for making it explicit in the code with a default tag for the switch statement. #Closed

Copy link
Contributor

@StephenLPeters StephenLPeters Jan 13, 2020

Choose a reason for hiding this comment

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

+1
While it is not explicitly called out as a rule in the C++ guidelines, es79(https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es79-use-default-to-handle-common-cases-only) and Enum2's (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#enum2-use-enumerations-to-represent-sets-of-related-named-constants) enforcement rules imply that call switch statement should have a default case, even if it is a do nothing, or a throw. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Added default case and made the RelativeToBoundingBox case [[fallthrough]] - let me know if you'd prefer explicit duplication instead

- Disable feature by default in MUXControlsInnerLoop
- move compositionbrush cast checks into if statements
- use local variable to access x and y values of points to avoid extra property system access
- switch gradientstops iteration from index-based to range-based
@jesbis jesbis requested a review from ranjeshj January 12, 2020 01:23
@@ -198,6 +198,12 @@ bool SharedHelpers::IsIsLoadedAvailable()
return s_isAvailable;
}

bool SharedHelpers::IsCompositionRadialGradientBrushAvailable()
Copy link
Contributor

Choose a reason for hiding this comment

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

IsCompositionRadialGradientBrushAvailable [](start = 20, length = 41)

Is there an interface we can QI for instead? platform look ups are expensive and should be avoided if possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do we do that anywhere else?
I was trying to follow the other SharedHelpers checks which also use ApiInformation, and I think this lookup in windows.foundation.metadata should only do the lookup once on first use of the brush per app launch.

{
ColorMatchTestResult.Text = "Failed";
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, why is this code here rather than in the test? I think generally we would output the center and outer color value to something the test can read rather than outputing a passed or failed state. As written we would not get very much info from a log of a test failure.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was following the precedent of the acrylic tests which do something similar:

TestResult.Text = "AcrylicRendering: " + (result ? "Passed" : ("Failed " + resultString));

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm, @ranjeshj @kmahone thoughts? I think having the test page report info to the test is better, that way if the test fails we will see something like 0xFF0001 != 0xFF0000 instead of Failed != Passed

- add default case to mappingmode switch statement
- switch from event tokens to event_revoker
- add per-property change callback methods
- reorder .targets imports to ensure commenting is accurate
@jesbis
Copy link
Member Author

jesbis commented Jan 14, 2020

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Contributor

@ranjeshj ranjeshj left a comment

Choose a reason for hiding this comment

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

:shipit:

@jesbis jesbis merged commit 63d3920 into microsoft:master Jan 14, 2020
@jesbis jesbis deleted the radialgradientsquash branch January 14, 2020 20:31
@michael-hawker
Copy link
Collaborator

@jesbis having different property names will be a big migration pain. There are ton of resources available (and tools that export) that would be very hard to convert needing to do extra math to the XAML property values. Compat with previous versions was a MUST in the spec.

Since this is a similar base approach as we had in the Toolkit, you can see how we mapped the properties as defined in WPF to the brush interop for Win2D. I validated this provided a 99% match in output to WPF for relative mode absolute.

The relative mapping was the tricky bit as we have no info about the hosting control's coordinates. I didn't see how that was addressed in this PR or is that handled underneath in the composition layer now with the new RadialGradientBrush creation method?

@codendone codendone removed the needs-triage Issue needs to be triaged by the area owners label Jan 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature proposal New feature proposal release note PR that we want to call out in the next release summary team-Rendering Issue for the Rendering team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants