Implement macOS wide gamut (Display P3) support#181769
Implement macOS wide gamut (Display P3) support#181769auto-submit[bot] merged 22 commits intoflutter:masterfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements wide gamut (Display P3) support for macOS, upgrades the surface pixel format on both iOS and macOS to 16-bit float RGBA, and fixes a blur filter clamping issue on macOS. The changes are comprehensive, touching integration tests, the engine, and the embedder API. The switch to RGBA16Float is well-justified and correctly implemented to prevent color clamping in multi-pass rendering. The addition of dynamic wide gamut switching on macOS based on display capabilities is a great feature. The new and updated tests, especially for the surface manager and dynamic switching, are thorough and provide good coverage for the new functionality. The code quality is high, and the changes are well-structured. I have one minor suggestion to improve a comment for future clarity.
engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm
Outdated
Show resolved
Hide resolved
gaaclarke
left a comment
There was a problem hiding this comment.
Can you please split this pr between MacOS support and switching iOS's pixel format to 16bit floats?
macOS will be easier to approve and accept. Switching iOS's pixel format is going to be much more involved since it will require doing some research about support of legacy devices, including performance. Honestly I'm not sure we have a the resources to do that sort of evaluation soon.
|
This pixel format change only applies to enabled wide gamut mode, not a general change. So, with turned off wide gamut (the default) nothing changes in pixel format. Or you mean changing to 16bit not acceptable even with WG mode? I think on a secondary plist option ex. All paths where 16bit mode set, guarded with isWideGamut |
Wide gamut is on by default on iOS. Switching the pixel format for iOS would require performance testing and compatibility research into the old devices we support.
For iOS, I'd like to keep it bgra10xr to avoid the work of trying to make sure it's an acceptable tradeoff. For macOS I expect float16 will be much easier to accommodate since performance is less of a concern on a desktop. We'd still need to do some digging to know where it is supported.
Every option we have in the plist incurs a cost beyond the initial implementation. As the engine evolves those options can easily atrophy or increase the cost of future changes. So, while I believe in our ability to implement something like that. I wouldn't want to sign up for it unless we had a big reason to incur the cost of maintaining it.
FWIW we looked briefly into wg on android and I think we'll have to use 8bit per channel but interpreted as DisplayP3 because the performance hit of using float16 on android devices was too much. |
|
Okay, I understand! I reverted iOS, so it uses 10bit. |
gaaclarke
left a comment
There was a problem hiding this comment.
Very impressive work! The big feedback is we need to keep our tests in kB10G10R10A10XR, ideally we add f16 tests instead of replacing them. The integration test changes will be easier to review if you refrain from refactoring them and instead just add the new test that you want. Refactoring can happen in a follow up PR. The changes to macOS look good to me and seem well tested. We'll have to get a review from someone more familiar with the macOS embedder though (pr always need 2 reviews anyways).
dev/integration_tests/wide_gamut_test/integration_test/app_test.dart
Outdated
Show resolved
Hide resolved
dev/integration_tests/wide_gamut_test/integration_test/app_test.dart
Outdated
Show resolved
Hide resolved
| group('end-to-end test', () { | ||
| testWidgets('look for display p3 deepest red', (WidgetTester tester) async { | ||
| app.run(app.Setup.image); | ||
| await tester.pumpAndSettle(const Duration(seconds: 2)); |
There was a problem hiding this comment.
Why are we removing the time on pump and settle?
There was a problem hiding this comment.
Because the test using image preloading now. Before, there was no preload and UI had to async wait for the test image to appear. Now load is awaited and the test is much faster this way.
engine/src/flutter/impeller/display_list/aiks_dl_atlas_unittests.cc
Outdated
Show resolved
Hide resolved
engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm
Outdated
Show resolved
Hide resolved
engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm
Show resolved
Hide resolved
| * | ||
| * Must be called on the platform thread. | ||
| */ | ||
| - (void)setEnableWideGamut:(BOOL)enableWideGamut; |
There was a problem hiding this comment.
I don't think we want this as part of the public interface. Our engine is the one that should be calling it. Let's move this to a category on the class instead. See FlutterEngine_Internal.h
There was a problem hiding this comment.
On iOS this is a static option - checked at app start - but on macOS wide gamut state is dynamically changing based on monitor capabilities, thats why this is in FlutterView header. There was no internal header so I created one.
| /dev/devicelab/bin/tasks/very_long_picture_scrolling_perf__e2e_summary.dart @flar @flutter/engine | ||
| /dev/devicelab/bin/tasks/web_size__compile_test.dart @yjbanov @flutter/web | ||
| /dev/devicelab/bin/tasks/wide_gamut_ios.dart @gaaclarke @flutter/engine | ||
| /dev/devicelab/bin/tasks/wide_gamut_macos.dart @gaaclarke @flutter/engine |
engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.mm
Outdated
Show resolved
Hide resolved
|
Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change). If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
Head's up there are some legitimate failures in the golden tests above: https://flutter-gold.skia.org/cl/github/181769 |
engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm
Outdated
Show resolved
Hide resolved
|
I'm wondering if setting layer |
|
I fully replaced 16bit to 10bit at macOS. The difference isn't noticeable to the naked eye. Color picker shows |
gaaclarke
left a comment
There was a problem hiding this comment.
This LGTM. Thanks again for another great PR. I'll give it an approval as soon as we can get it passing all the tests on CI. Looks like dev/integration_tests/wide_gamut_test/integration_test/app_test.dart isn't formatted correctly right now. Even though you might be thinking we didn't change anything for Linux, Linux is where we run the formatting tests.
| NSColor* borderColor, | ||
| const std::vector<FlutterRect>& paintRegion) { | ||
| const std::vector<FlutterRect>& paintRegion, | ||
| BOOL enableWideGamut) { |
There was a problem hiding this comment.
This parameter was added to this method, but it isn't used.
|
There's one thing that is bugging me - I think we should check the surfaces inside Is possible that these surfaces have been held by raster thread during wide gamut switch and we need them out of circulation. |
|
Thats reasonable. I added a guard to the commit method |
|
|
||
| // Release all unused back buffer surfaces and replace them with front surfaces. | ||
| [_backBufferCache returnSurfaces:_frontSurfaces]; | ||
| // Only return surfaces to the cache if they match the current wide gamut mode. |
There was a problem hiding this comment.
This is not what I had in mind. You don't need to touch _frontSurfaces, those are already cleaned setEnableWideGamut. It's the incoming surfaces (method argument) that I'm concerned with. You should check if they match current wide gamut, and if not, simply return early from the method, don't do anything with the surfaces. They will be released.
gaaclarke
left a comment
There was a problem hiding this comment.
LGTM! Thanks. I'll file a followup issue for making wide gamut the default. We'll have to audit our testing to make sure we are comfortable with that change. Having it be optional is good for now.
Roll Flutter from bf701fefec86 to f916dd6887bf (44 revisions) flutter/flutter@bf701fe...f916dd6 2026-02-05 [email protected] Implement macOS wide gamut (Display P3) support (flutter/flutter#181769) 2026-02-04 [email protected] Roll Skia from d23ecfbfdff9 to 8543ce512d5c (3 revisions) (flutter/flutter#181923) 2026-02-04 [email protected] Roll Dart SDK from 8001c99d952b to 8f778ffd318b (3 revisions) (flutter/flutter#181927) 2026-02-04 [email protected] Re-enable AddressSanitizer on the linux_unopt builder (flutter/flutter#181741) 2026-02-04 [email protected] Add exception to log message in ContentSizingFlag.java (flutter/flutter#181813) 2026-02-04 [email protected] Roll pub packages (flutter/flutter#181925) 2026-02-04 [email protected] [flutter_tools] Deprecate web hot reload flag (flutter/flutter#181884) 2026-02-04 [email protected] Marks platform_views_scroll_perf_impeller__timeline_summary unflaky (flutter/flutter#181649) 2026-02-04 [email protected] Roll Dart SDK from 204db085d970 to 8001c99d952b (1 revision) (flutter/flutter#181902) 2026-02-04 [email protected] Roll Skia from f37a22506eb4 to d23ecfbfdff9 (23 revisions) (flutter/flutter#181915) 2026-02-04 [email protected] In the Web codec tests, skip an undecodable image that is used to test a Skia error handling code path. (flutter/flutter#181870) 2026-02-04 [email protected] Roll Packages from 5b1bea8 to 3bddf2c (5 revisions) (flutter/flutter#181918) 2026-02-04 [email protected] Roll Fuchsia Linux SDK from UmQaaNuhkiuE8Dzug... to J2QdLcY2gyt4NP_xV... (flutter/flutter#181893) 2026-02-04 [email protected] Roll Dart SDK from 54322a0b1109 to 204db085d970 (3 revisions) (flutter/flutter#181890) 2026-02-04 [email protected] Cleanup cross imports (flutter/flutter#181807) 2026-02-04 [email protected] [Material] Remove Material import from backdrop_filter_test.dart widget tests (flutter/flutter#181386) 2026-02-04 [email protected] Move CheckedModeBanner tests to material and remove Material import from widgets banner_test (flutter/flutter#181261) 2026-02-04 [email protected] feat: Pass parameters from DropdownMenuFormField to DropDownMenu (flutter/flutter#181373) 2026-02-04 [email protected] Remove `Config complete` log when using `flutter build apk --config-only` (flutter/flutter#181864) 2026-02-04 [email protected] [Impeller] Fix flattening of very large zoomed curves with tiny stroke widths (flutter/flutter#181505) 2026-02-03 [email protected] Propagates Overlay's MediaQueryData to OverlayPortal child (flutter/flutter#181579) 2026-02-03 [email protected] Make sure that an AnimatedScale doesn't crash in 0x0 environment (flutter/flutter#181481) 2026-02-03 [email protected] Roll Dart SDK from 56294a92d5cc to 54322a0b1109 (1 revision) (flutter/flutter#181872) 2026-02-03 [email protected] Fix decorated box (flutter/flutter#179802) 2026-02-03 [email protected] Roll pub packages (flutter/flutter#181871) 2026-02-03 [email protected] Remove Material library dependency from expansible_test.dart (flutter/flutter#181657) 2026-02-03 [email protected] Organize and update fragment shader uniform tests. (flutter/flutter#181822) 2026-02-03 [email protected] fix(web_ui): handle non-invertible matrices in ImageFilter.matrix (flutter/flutter#181742) 2026-02-03 [email protected] Remove unnecessary Material import from cupertino/slider_test.dart (flutter/flutter#180957) 2026-02-03 [email protected] Remove the Flutter.xcframework as a swift dependency (flutter/flutter#181739) 2026-02-03 [email protected] feature: implementation of tooltips in the `_TestWindowingOwner` and minor bugfixes to the multiple windows example app (flutter/flutter#181510) 2026-02-03 [email protected] [Web] Fix flt-platform-view comment (flutter/flutter#181576) 2026-02-03 [email protected] Marks Linux_pixel_7pro android_verified_input_test to be unflaky (flutter/flutter#179120) 2026-02-03 [email protected] Unmark `hybrid_android_views_integration_test` as bringup (flutter/flutter#181628) 2026-02-03 [email protected] Remove material from sliver_tree_test.dart (flutter/flutter#181415) 2026-02-03 [email protected] Make `android_plugin_new_output_dir_test` only build release (flutter/flutter#181677) 2026-02-03 [email protected] Roll customer tests (flutter/flutter#181825) 2026-02-03 [email protected] Add Linux Foundation Health Score badge to README (flutter/flutter#175587) 2026-02-03 [email protected] Remove unused getters on AndroidProject class (flutter/flutter#181860) 2026-02-03 [email protected] Adds batch release doc for flutter/package (flutter/flutter#181676) 2026-02-03 [email protected] [ Tool ] Don't use `globals.platform` in `getFlutterRoot()` (flutter/flutter#181859) 2026-02-03 [email protected] Roll Packages from 837dbbd to 5b1bea8 (10 revisions) (flutter/flutter#181857) 2026-02-03 [email protected] Remove material from basic_test.dart (flutter/flutter#181444) 2026-02-03 [email protected] [ Tool ] Fix regression introduced in flutter/flutter#181421 (flutter/flutter#181826) If this roll has caused a breakage, revert this CL and stop the roller ...
Adds wide gamut color support to macOS (matching iOS), upgrades the surface pixel format from 10-bit BGRA10_XR to 16-bit float RGBA16Float on both iOS and macOS when enabled, and fixes Impeller's blur filter P3 clamping on macOS. **macOS Wide Gamut Support** - Added DoesHardwareSupportWideGamut() hardware capability check (MTLGPUFamilyApple2 or MTLGPUFamilyMac2) - Wide gamut enabled when both hardware supports it and FLTEnableWideGamut plist flag is YES - Dynamic wide gamut switching when windows move between P3 and sRGB displays - Added flutter/screenshot method channel on macOS for integration testing **RGBA16Float Surface Format (iOS + macOS)** - macOS IOSurface: kCVPixelFormatType_64RGBAHalf + MTLPixelFormatRGBA16Float - iOS CAMetalLayer: MTLPixelFormatRGBA16Float - Image decoder: always uses kRGBA_F16_SkColorType for all wide gamut images (previously only transparent images used 16-bit) **Fix Blur P3 Clamping on macOS** macOS uses the compositor/embedder path, not GPUSurfaceMetalImpeller, so UpdateOffscreenLayerPixelFormat was never called. Added the call in embedder.cc after wrapping the Metal resolve texture. **Why RGBA16Float over BGRA10_XR?** BGRA10_XR has only 10 bits per channel — values outside sRGB gamut get clamped in intermediate render targets (e.g. blur filters). RGBA16Float has 16 bits per channel with full floating-point range, preventing P3 color clamping in multi-pass rendering. **Tests** - 9 new iOS FlutterView unit tests verifying RGBA16Float pixel format and extended sRGB color space - Updated macOS FlutterSurfaceManagerTest for RGBA16Float, dynamic switching, color space, and pixel format verification - Updated image decoder and Impeller display list tests for kR16G16B16A16Float - 11 macOS/iOS integration tests: image, saveLayer, codecImage, none, blur, drawnImage, container, linearGradient, radialGradient, conicalGradient, sweepGradient ### Issues flutter#164557 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing.
Adds wide gamut color support to macOS (matching iOS), upgrades the surface pixel format from 10-bit BGRA10_XR to 16-bit float RGBA16Float on both iOS and macOS when enabled, and fixes Impeller's blur filter P3 clamping on macOS. **macOS Wide Gamut Support** - Added DoesHardwareSupportWideGamut() hardware capability check (MTLGPUFamilyApple2 or MTLGPUFamilyMac2) - Wide gamut enabled when both hardware supports it and FLTEnableWideGamut plist flag is YES - Dynamic wide gamut switching when windows move between P3 and sRGB displays - Added flutter/screenshot method channel on macOS for integration testing **RGBA16Float Surface Format (iOS + macOS)** - macOS IOSurface: kCVPixelFormatType_64RGBAHalf + MTLPixelFormatRGBA16Float - iOS CAMetalLayer: MTLPixelFormatRGBA16Float - Image decoder: always uses kRGBA_F16_SkColorType for all wide gamut images (previously only transparent images used 16-bit) **Fix Blur P3 Clamping on macOS** macOS uses the compositor/embedder path, not GPUSurfaceMetalImpeller, so UpdateOffscreenLayerPixelFormat was never called. Added the call in embedder.cc after wrapping the Metal resolve texture. **Why RGBA16Float over BGRA10_XR?** BGRA10_XR has only 10 bits per channel — values outside sRGB gamut get clamped in intermediate render targets (e.g. blur filters). RGBA16Float has 16 bits per channel with full floating-point range, preventing P3 color clamping in multi-pass rendering. **Tests** - 9 new iOS FlutterView unit tests verifying RGBA16Float pixel format and extended sRGB color space - Updated macOS FlutterSurfaceManagerTest for RGBA16Float, dynamic switching, color space, and pixel format verification - Updated image decoder and Impeller display list tests for kR16G16B16A16Float - 11 macOS/iOS integration tests: image, saveLayer, codecImage, none, blur, drawnImage, container, linearGradient, radialGradient, conicalGradient, sweepGradient ### Issues flutter#164557 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing.
Adds wide gamut color support to macOS (matching iOS), upgrades the surface pixel format from 10-bit BGRA10_XR to 16-bit float RGBA16Float on both iOS and macOS when enabled, and fixes Impeller's blur filter P3 clamping on macOS. **macOS Wide Gamut Support** - Added DoesHardwareSupportWideGamut() hardware capability check (MTLGPUFamilyApple2 or MTLGPUFamilyMac2) - Wide gamut enabled when both hardware supports it and FLTEnableWideGamut plist flag is YES - Dynamic wide gamut switching when windows move between P3 and sRGB displays - Added flutter/screenshot method channel on macOS for integration testing **RGBA16Float Surface Format (iOS + macOS)** - macOS IOSurface: kCVPixelFormatType_64RGBAHalf + MTLPixelFormatRGBA16Float - iOS CAMetalLayer: MTLPixelFormatRGBA16Float - Image decoder: always uses kRGBA_F16_SkColorType for all wide gamut images (previously only transparent images used 16-bit) **Fix Blur P3 Clamping on macOS** macOS uses the compositor/embedder path, not GPUSurfaceMetalImpeller, so UpdateOffscreenLayerPixelFormat was never called. Added the call in embedder.cc after wrapping the Metal resolve texture. **Why RGBA16Float over BGRA10_XR?** BGRA10_XR has only 10 bits per channel — values outside sRGB gamut get clamped in intermediate render targets (e.g. blur filters). RGBA16Float has 16 bits per channel with full floating-point range, preventing P3 color clamping in multi-pass rendering. **Tests** - 9 new iOS FlutterView unit tests verifying RGBA16Float pixel format and extended sRGB color space - Updated macOS FlutterSurfaceManagerTest for RGBA16Float, dynamic switching, color space, and pixel format verification - Updated image decoder and Impeller display list tests for kR16G16B16A16Float - 11 macOS/iOS integration tests: image, saveLayer, codecImage, none, blur, drawnImage, container, linearGradient, radialGradient, conicalGradient, sweepGradient ### Issues flutter#164557 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing.
Adds wide gamut color support to macOS (matching iOS), upgrades the surface pixel format from 10-bit BGRA10_XR to 16-bit float RGBA16Float on both iOS and macOS when enabled, and fixes Impeller's blur filter P3 clamping on macOS.
macOS Wide Gamut Support
RGBA16Float Surface Format (iOS + macOS)
Fix Blur P3 Clamping on macOS
macOS uses the compositor/embedder path, not GPUSurfaceMetalImpeller, so UpdateOffscreenLayerPixelFormat was never called. Added the call in embedder.cc after wrapping the Metal resolve texture.
Why RGBA16Float over BGRA10_XR?
BGRA10_XR has only 10 bits per channel — values outside sRGB gamut get clamped in intermediate render targets (e.g. blur filters). RGBA16Float has 16 bits per channel with full floating-point range, preventing P3 color clamping in multi-pass rendering.
Tests
Issues
#164557
Pre-launch Checklist
///).