diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index 17436c1fdc..f7df07c785 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -1723,12 +1723,20 @@ bool Canvas::SupportsBlitToOnscreen() const { } bool Canvas::BlitToOnscreen(bool is_onscreen) { - auto command_buffer = renderer_.GetContext()->CreateCommandBuffer(); + std::shared_ptr command_buffer = + renderer_.GetContext()->CreateCommandBuffer(); command_buffer->SetLabel("EntityPass Root Command Buffer"); - auto offscreen_target = render_passes_.back() - .inline_pass_context->GetPassTarget() - .GetRenderTarget(); - if (SupportsBlitToOnscreen()) { + RenderTarget offscreen_target = render_passes_.back() + .inline_pass_context->GetPassTarget() + .GetRenderTarget(); + // If the src and destination format differ (due to wide gamut, alpha-less + // format, et cetera), then a draw must always be performed instead of a blit. + if (SupportsBlitToOnscreen() && + offscreen_target.GetRenderTargetTexture() + ->GetTextureDescriptor() + .format == render_target_.GetRenderTargetTexture() + ->GetTextureDescriptor() + .format) { auto blit_pass = command_buffer->CreateBlitPass(); blit_pass->AddCopy(offscreen_target.GetRenderTargetTexture(), render_target_.GetRenderTargetTexture()); diff --git a/engine/src/flutter/impeller/display_list/canvas_unittests.cc b/engine/src/flutter/impeller/display_list/canvas_unittests.cc index ab78138b54..70487a3132 100644 --- a/engine/src/flutter/impeller/display_list/canvas_unittests.cc +++ b/engine/src/flutter/impeller/display_list/canvas_unittests.cc @@ -24,11 +24,12 @@ namespace testing { std::unique_ptr CreateTestCanvas( ContentContext& context, std::optional cull_rect = std::nullopt, - bool requires_readback = false) { + bool requires_readback = false, + std::optional format = std::nullopt) { TextureDescriptor onscreen_desc; onscreen_desc.size = {100, 100}; onscreen_desc.format = - context.GetDeviceCapabilities().GetDefaultColorFormat(); + format.value_or(context.GetDeviceCapabilities().GetDefaultColorFormat()); onscreen_desc.usage = TextureUsage::kRenderTarget; onscreen_desc.storage_mode = StorageMode::kDevicePrivate; onscreen_desc.sample_count = SampleCount::kCount1; @@ -385,5 +386,18 @@ TEST_P(AiksTest, SupportsBlitToOnscreen) { } } +TEST_P(AiksTest, SupportsBlitToOnscreenWithDifferentFormat) { + if (GetBackend() == PlaygroundBackend::kOpenGLES) { + GTEST_SKIP() << "Not valid on GLES"; + } + // Create an onscreen format which is different than the offscreen format, + // then make the canvas perform a restore to verify a blit is not used. + ContentContext context(GetContext(), nullptr); + auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100), + /*requires_readback=*/true, + /*format=*/PixelFormat::kB8G8R8A8UNormIntSRGB); + canvas->EndReplay(); +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/metal/swapchain_transients_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/swapchain_transients_mtl.mm index 6527dd588e..2e52793298 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/swapchain_transients_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/swapchain_transients_mtl.mm @@ -52,7 +52,11 @@ std::shared_ptr SwapchainTransientsMTL::GetMSAATexture() { TextureDescriptor desc; desc.size = size_; desc.sample_count = SampleCount::kCount4; - desc.format = format_; + if (format_ == PixelFormat::kB10G10R10XR) { + desc.format = PixelFormat::kB10G10R10A10XR; + } else { + desc.format = format_; + } desc.storage_mode = StorageMode::kDeviceTransient; desc.usage = TextureUsage::kRenderTarget; desc.type = TextureType::kTexture2DMultisample; diff --git a/engine/src/flutter/impeller/renderer/backend/metal/swapchain_transients_mtl_unittests.mm b/engine/src/flutter/impeller/renderer/backend/metal/swapchain_transients_mtl_unittests.mm index 40a393084a..dea293c61c 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/swapchain_transients_mtl_unittests.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/swapchain_transients_mtl_unittests.mm @@ -73,6 +73,14 @@ TEST_P(SwapchainTransientsMTLTest, CanAllocateSwapchainTextures) { // Texture cache is invalidated when pixel format changes. transients->SetSizeAndFormat({2, 2}, PixelFormat::kB10G10R10A10XR); EXPECT_NE(resolve, transients->GetResolveTexture()); + + // Transients converts alpha-less format to have alpha for MSAA. + transients->SetSizeAndFormat({2, 2}, PixelFormat::kB10G10R10XR); + + EXPECT_EQ(transients->GetMSAATexture()->GetTextureDescriptor().format, + PixelFormat::kB10G10R10A10XR); + EXPECT_EQ(transients->GetResolveTexture()->GetTextureDescriptor().format, + PixelFormat::kB10G10R10XR); } } // namespace testing diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm index f31eda904b..9933f9a926 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm @@ -275,6 +275,9 @@ extern CFTimeInterval display_link_target; } else if (self.pixelFormat == MTLPixelFormatBGRA10_XR) { pixelFormat = kCVPixelFormatType_40ARGBLEWideGamut; bytesPerElement = 8; + } else if (self.pixelFormat == MTLPixelFormatBGR10_XR) { + pixelFormat = kCVPixelFormatType_30RGBLEPackedWideGamut; + bytesPerElement = 4; } else { FML_LOG(ERROR) << "Unsupported pixel format: " << self.pixelFormat; return nil; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm index 8000df2830..afe969db1c 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm @@ -51,9 +51,12 @@ FLUTTER_ASSERT_ARC CAMetalLayer* layer = (CAMetalLayer*)self.layer; #pragma clang diagnostic pop layer.pixelFormat = pixelFormat; - if (pixelFormat == MTLPixelFormatRGBA16Float || pixelFormat == MTLPixelFormatBGRA10_XR) { + if (pixelFormat == MTLPixelFormatRGBA16Float || pixelFormat == MTLPixelFormatBGRA10_XR || + pixelFormat == MTLPixelFormatBGR10_XR) { self->_colorSpaceRef = fml::CFRef(CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB)); layer.colorspace = self->_colorSpaceRef; + // Overlay layers always need an alpha channel. + layer.pixelFormat = MTLPixelFormatBGRA10_XR; } } return self; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm index dd37e8a553..b128b90b6c 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm @@ -78,16 +78,6 @@ FLUTTER_ASSERT_ARC return self; } -static void PrintWideGamutWarningOnce() { - static BOOL did_print = NO; - if (did_print) { - return; - } - FML_DLOG(WARNING) << "Rendering wide gamut colors is turned on but isn't " - "supported, downgrading the color gamut to sRGB."; - did_print = YES; -} - - (void)layoutSubviews { if ([self.layer isKindOfClass:[CAMetalLayer class]]) { // It is a known Apple bug that CAMetalLayer incorrectly reports its supported @@ -96,6 +86,7 @@ static void PrintWideGamutWarningOnce() { #pragma clang diagnostic ignored "-Wunguarded-availability-new" CAMetalLayer* layer = (CAMetalLayer*)self.layer; #pragma clang diagnostic pop + CGFloat screenScale = self.screen.scale; layer.allowsGroupOpacity = YES; layer.contentsScale = screenScale; @@ -104,9 +95,13 @@ static void PrintWideGamutWarningOnce() { if (_isWideGamutEnabled && self.isWideGamutSupported) { fml::CFRef srgb(CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB)); layer.colorspace = srgb; - layer.pixelFormat = MTLPixelFormatBGRA10_XR; - } else if (_isWideGamutEnabled && !self.isWideGamutSupported) { - PrintWideGamutWarningOnce(); + // If the flutter layer is opaque, then use an alpha-less format for the onscreen + // texture. This will reduce wide gamut memory usage by 50%, and Impeller will + // still correctly use alpha for MSAA textures and any offscreen save layer usage. + // For non-wide gamut formats there is no point in removing the alpha channel as + // the textures must align to 32 bits (32 -> 24 = 32) whereas wide gamut is (40 -> 32 = 32) + // instead of 64. + layer.pixelFormat = layer.opaque ? MTLPixelFormatBGR10_XR : MTLPixelFormatBGRA10_XR; } } diff --git a/engine/src/flutter/shell/platform/darwin/ios/ios_surface_metal_impeller.mm b/engine/src/flutter/shell/platform/darwin/ios/ios_surface_metal_impeller.mm index 8ec9bb8eef..f025db100d 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/ios_surface_metal_impeller.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/ios_surface_metal_impeller.mm @@ -4,6 +4,7 @@ #import "flutter/shell/platform/darwin/ios/ios_surface_metal_impeller.h" +#include "flutter/impeller/core/formats.h" #include "flutter/impeller/renderer/backend/metal/formats_mtl.h" #include "flutter/impeller/renderer/context.h" #include "flutter/shell/gpu/gpu_surface_metal_impeller.h" @@ -43,8 +44,12 @@ void IOSSurfaceMetalImpeller::UpdateStorageSizeIfNecessary() { // |IOSSurface| std::unique_ptr IOSSurfaceMetalImpeller::CreateGPUSurface() { - impeller_context_->UpdateOffscreenLayerPixelFormat( - impeller::FromMTLPixelFormat(layer_.pixelFormat)); + // Convert alpha-less onscreen format to alpha including format. + impeller::PixelFormat pixel_format = impeller::FromMTLPixelFormat(layer_.pixelFormat); + if (pixel_format == impeller::PixelFormat::kB10G10R10XR) { + pixel_format = impeller::PixelFormat::kB10G10R10A10XR; + } + impeller_context_->UpdateOffscreenLayerPixelFormat(pixel_format); return std::make_unique(this, // aiks_context_ // );