From d8fc22e25cbdadd6adfcbe968a24fb7f9ee103e8 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:28:35 -0800 Subject: [PATCH] [Impeller] Adds support for r32float textures (#178418) fixes https://github.com/flutter/flutter/issues/178420 ## 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]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../integration_test/app_test.dart | 14 ++ .../high_bitrate_images/lib/main.dart | 57 +++++++- engine/src/flutter/impeller/core/formats.h | 4 + .../backend/gles/blit_command_gles.cc | 5 + .../renderer/backend/gles/texture_gles.cc | 7 + .../renderer/backend/metal/formats_mtl.h | 2 + .../renderer/backend/vulkan/formats_vk.h | 5 + engine/src/flutter/lib/gpu/formats.h | 3 + engine/src/flutter/lib/ui/painting.dart | 6 + .../flutter/lib/ui/painting/image_decoder.h | 1 + .../lib/ui/painting/image_decoder_impeller.cc | 55 ++++++- .../painting/image_decoder_no_gl_unittests.cc | 66 ++++++++- .../lib/ui/painting/image_decoder_skia.cc | 10 +- .../ui/painting/image_decoder_unittests.cc | 6 +- .../lib/ui/painting/image_descriptor.cc | 135 ++++++++++++++---- .../lib/ui/painting/image_descriptor.h | 36 +++-- engine/src/flutter/shell/common/rasterizer.cc | 1 + 17 files changed, 360 insertions(+), 53 deletions(-) diff --git a/dev/integration_tests/high_bitrate_images/integration_test/app_test.dart b/dev/integration_tests/high_bitrate_images/integration_test/app_test.dart index aa64c7ca17c..359c85545f0 100644 --- a/dev/integration_tests/high_bitrate_images/integration_test/app_test.dart +++ b/dev/integration_tests/high_bitrate_images/integration_test/app_test.dart @@ -73,6 +73,20 @@ void main() { group('end-to-end test', () { testWidgets('renders sdfs with rgba32f', (WidgetTester tester) async { + app.gTargetPixelFormat = ui.TargetPixelFormat.rgbaFloat32; + app.main(); + await tester.pumpAndSettle(const Duration(seconds: 2)); + await _getScreenshot(); + // TODO(gaaclarke): Turn this into a golden test. This turned out to be + // quite involved so it's deferred. + // expect( + // screenshot, + // matchesGoldenFile('high_bitrate_images.rbga32f'), + // ); + }); + + testWidgets('renders sdfs with r32f', (WidgetTester tester) async { + app.gTargetPixelFormat = ui.TargetPixelFormat.rFloat32; app.main(); await tester.pumpAndSettle(const Duration(seconds: 2)); await _getScreenshot(); diff --git a/dev/integration_tests/high_bitrate_images/lib/main.dart b/dev/integration_tests/high_bitrate_images/lib/main.dart index cfeb67c9ae3..67c6ea72da3 100644 --- a/dev/integration_tests/high_bitrate_images/lib/main.dart +++ b/dev/integration_tests/high_bitrate_images/lib/main.dart @@ -9,6 +9,8 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +ui.TargetPixelFormat gTargetPixelFormat = ui.TargetPixelFormat.rFloat32; + void main() { runApp(const MyApp()); } @@ -55,11 +57,22 @@ class _SdfCanvasState extends State { _shader = shader; }); }); - _loadSdfImage().then((ui.Image image) { - setState(() { - _sdfImage = image; - }); - }); + switch (gTargetPixelFormat) { + case ui.TargetPixelFormat.rgbaFloat32: + _loadRGBA32FloatSdfImage().then((ui.Image image) { + setState(() { + _sdfImage = image; + }); + }); + case ui.TargetPixelFormat.rFloat32: + _loadR32FloatSdfImage().then((ui.Image image) { + setState(() { + _sdfImage = image; + }); + }); + case ui.TargetPixelFormat.dontCare: + assert(false); + } } Future _loadShader() async { @@ -67,7 +80,39 @@ class _SdfCanvasState extends State { return program.fragmentShader(); } - Future _loadSdfImage() async { + Future _loadR32FloatSdfImage() async { + const int width = 1024; + const int height = 1024; + const double radius = width / 4.0; + final List floats = List.filled(width * height, 0.0); + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + double x = j.toDouble(); + double y = i.toDouble(); + x -= width / 2.0; + y -= height / 2.0; + final double length = sqrt(x * x + y * y) - radius; + final int idx = i * width + j; + floats[idx] = length - radius; + } + } + final Float32List floatList = Float32List.fromList(floats); + final Uint8List intList = Uint8List.view(floatList.buffer); + final Completer completer = Completer(); + ui.decodeImageFromPixels( + intList, + width, + height, + ui.PixelFormat.rFloat32, + targetFormat: ui.TargetPixelFormat.rFloat32, + (ui.Image image) { + completer.complete(image); + }, + ); + return completer.future; + } + + Future _loadRGBA32FloatSdfImage() async { const int width = 1024; const int height = 1024; const double radius = width / 4.0; diff --git a/engine/src/flutter/impeller/core/formats.h b/engine/src/flutter/impeller/core/formats.h index bbe0b87045d..911c76a8934 100644 --- a/engine/src/flutter/impeller/core/formats.h +++ b/engine/src/flutter/impeller/core/formats.h @@ -110,6 +110,7 @@ enum class PixelFormat : uint8_t { kB10G10R10XR, kB10G10R10XRSRGB, kB10G10R10A10XR, + kR32Float, // Depth and stencil formats. kS8UInt, kD24UnormS8Uint, @@ -171,6 +172,8 @@ constexpr const char* PixelFormatToString(PixelFormat format) { return "D24UnormS8Uint"; case PixelFormat::kD32FloatS8UInt: return "D32FloatS8UInt"; + case PixelFormat::kR32Float: + return "R32Float"; } FML_UNREACHABLE(); } @@ -479,6 +482,7 @@ constexpr size_t BytesPerPixelForPixelFormat(PixelFormat format) { case PixelFormat::kB8G8R8A8UNormIntSRGB: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10XR: + case PixelFormat::kR32Float: return 4u; case PixelFormat::kD24UnormS8Uint: return 4u; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc index 046a1394914..29cd893211c 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc @@ -178,6 +178,11 @@ struct TexImage2DData { external_format = GL_RGBA; type = GL_FLOAT; break; + case PixelFormat::kR32Float: + internal_format = GL_RED; + external_format = GL_RED; + type = GL_FLOAT; + break; case PixelFormat::kR16G16B16A16Float: internal_format = GL_RGBA; external_format = GL_RGBA; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc index 4fdad4743aa..26295fe0acb 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc @@ -40,6 +40,7 @@ static bool IsDepthStencilFormat(PixelFormat format) { case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return false; } FML_UNREACHABLE(); @@ -87,6 +88,11 @@ struct TexImage2DData { external_format = GL_RGBA; type = GL_UNSIGNED_BYTE; break; + case PixelFormat::kR32Float: + internal_format = GL_R32F; + external_format = GL_RGBA; + type = GL_FLOAT; + break; case PixelFormat::kR32G32B32A32Float: internal_format = GL_RGBA32F; external_format = GL_RGBA; @@ -401,6 +407,7 @@ static std::optional ToRenderBufferFormat(PixelFormat format) { case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return std::nullopt; } FML_UNREACHABLE(); diff --git a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h index 432a1cfa5c7..19ffc94d17f 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h +++ b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h @@ -107,6 +107,8 @@ constexpr MTLPixelFormat ToMTLPixelFormat(PixelFormat format) { return SafeMTLPixelFormatBGR10_XR(); case PixelFormat::kB10G10R10A10XR: return SafeMTLPixelFormatBGRA10_XR(); + case PixelFormat::kR32Float: + return MTLPixelFormatR32Float; } return MTLPixelFormatInvalid; }; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h index 2dbb21c5af0..020d5802773 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h @@ -175,6 +175,8 @@ constexpr vk::Format ToVKImageFormat(PixelFormat format) { return vk::Format::eR8Unorm; case PixelFormat::kR8G8UNormInt: return vk::Format::eR8G8Unorm; + case PixelFormat::kR32Float: + return vk::Format::eR32Sfloat; } FML_UNREACHABLE(); @@ -426,6 +428,7 @@ constexpr bool PixelFormatIsDepthStencil(PixelFormat format) { case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return false; case PixelFormat::kS8UInt: case PixelFormat::kD24UnormS8Uint: @@ -525,6 +528,7 @@ constexpr vk::ImageAspectFlags ToVKImageAspectFlags(PixelFormat format) { case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return vk::ImageAspectFlagBits::eColor; case PixelFormat::kS8UInt: return vk::ImageAspectFlagBits::eStencil; @@ -599,6 +603,7 @@ constexpr vk::ImageAspectFlags ToImageAspectFlags(PixelFormat format) { case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return vk::ImageAspectFlagBits::eColor; case PixelFormat::kS8UInt: return vk::ImageAspectFlagBits::eStencil; diff --git a/engine/src/flutter/lib/gpu/formats.h b/engine/src/flutter/lib/gpu/formats.h index 4c52fd57c7c..eef3d0fbc3f 100644 --- a/engine/src/flutter/lib/gpu/formats.h +++ b/engine/src/flutter/lib/gpu/formats.h @@ -104,6 +104,9 @@ constexpr FlutterGPUPixelFormat FromImpellerPixelFormat( switch (value) { case impeller::PixelFormat::kUnknown: return FlutterGPUPixelFormat::kUnknown; + case impeller::PixelFormat::kR32Float: + FML_DCHECK(false) << "k32Float not implemented."; + return FlutterGPUPixelFormat::kUnknown; case impeller::PixelFormat::kA8UNormInt: return FlutterGPUPixelFormat::kA8UNormInt; case impeller::PixelFormat::kR8UNormInt: diff --git a/engine/src/flutter/lib/ui/painting.dart b/engine/src/flutter/lib/ui/painting.dart index 88389964a90..c96e1f9a431 100644 --- a/engine/src/flutter/lib/ui/painting.dart +++ b/engine/src/flutter/lib/ui/painting.dart @@ -1909,6 +1909,9 @@ enum PixelFormat { /// component, followed by: green, blue and alpha. Premultiplied alpha isn't /// used, matching [ImageByteFormat.rawExtendedRgba128]. rgbaFloat32, + + /// Each pixel is 32 bits, the red channel is just one 32 bit float. + rFloat32, } /// The format of pixel data of the texture generated by @@ -1919,6 +1922,9 @@ enum TargetPixelFormat { /// Each pixel is 128 bits, where each color component is a 32 bit float. rgbaFloat32, + + /// Each pixel is 32 bits, the red channel is just one 32 bit float. + rFloat32, } /// Signature for [Image] lifecycle events. diff --git a/engine/src/flutter/lib/ui/painting/image_decoder.h b/engine/src/flutter/lib/ui/painting/image_decoder.h index 6b3bbbadd7a..4cfa99710b6 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder.h +++ b/engine/src/flutter/lib/ui/painting/image_decoder.h @@ -40,6 +40,7 @@ class ImageDecoder { /// Explicitly declare the target pixel is left for the engine to decide. kDontCare, kR32G32B32A32Float, + kR32Float, }; struct Options { diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc b/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc index 5bbad68446b..584dee2b802 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc +++ b/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc @@ -9,6 +9,7 @@ #include "flutter/fml/closure.h" #include "flutter/fml/make_copyable.h" +#include "flutter/fml/mapping.h" #include "flutter/fml/trace_event.h" #include "flutter/impeller/core/allocator.h" #include "flutter/impeller/display_list/dl_image_impeller.h" @@ -262,6 +263,26 @@ absl::StatusOr ResizeOnCpu( .image_info = decoded_image_info.value()}; } +bool IsZeroOpConversion(ImageDescriptor::PixelFormat input, + ImageDecoder::TargetPixelFormat output) { + switch (input) { + case ImageDescriptor::PixelFormat::kR32Float: + return output == ImageDecoder::TargetPixelFormat::kR32Float; + default: + return false; + } +} + +impeller::PixelFormat ToImpellerPixelFormat( + ImageDecoder::TargetPixelFormat format) { + switch (format) { + case ImageDecoder::TargetPixelFormat::kR32Float: + return impeller::PixelFormat::kR32Float; + default: + FML_DCHECK(false) << "Unsupported pixel format"; + return impeller::PixelFormat::kUnknown; + } +} } // namespace std::optional ImageDecoderImpeller::ToPixelFormat( @@ -321,12 +342,41 @@ ImageDecoderImpeller::DecompressTexture( return absl::InvalidArgumentError(decode_error); } - const SkISize source_size = descriptor->image_info().dimensions(); + const SkISize source_size = SkISize::Make(descriptor->image_info().width, + descriptor->image_info().height); const SkISize target_size = SkISize::Make(std::min(max_texture_size.width, static_cast(options.target_width)), std::min(max_texture_size.height, static_cast(options.target_height))); + + // Fast path for when the input requires no decompressing or conversion. + if (!descriptor->is_compressed() && source_size == target_size && + IsZeroOpConversion(descriptor->image_info().format, + options.target_format)) { + impeller::DeviceBufferDescriptor desc; + desc.storage_mode = impeller::StorageMode::kHostVisible; + desc.size = descriptor->data()->size(); + std::shared_ptr buffer = + allocator->CreateBuffer(desc); + if (!buffer) { + return absl::ResourceExhaustedError("Could not create buffer for image."); + } + sk_sp data = descriptor->data(); + memcpy(buffer->OnGetContents(), data->bytes(), data->size()); + buffer->Flush(); + + return ImageDecoderImpeller::DecompressResult{ + .device_buffer = std::move(buffer), + .image_info = + { + .size = + impeller::ISize(source_size.width(), source_size.height()), + .format = ToImpellerPixelFormat(options.target_format), + }, + }; + } + SkISize decode_size = source_size; if (descriptor->is_compressed()) { decode_size = descriptor->get_scaled_dimensions(std::max( @@ -334,7 +384,8 @@ ImageDecoderImpeller::DecompressTexture( static_cast(target_size.height()) / source_size.height())); } - const SkImageInfo& base_image_info = descriptor->image_info(); + const SkImageInfo base_image_info = + ImageDescriptor::ToSkImageInfo(descriptor->image_info()); const absl::StatusOr image_info = CreateImageInfo( base_image_info, decode_size, supports_wide_gamut, options.target_format); diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.cc b/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.cc index bf1dcaa9c2e..8e58a85ee21 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.cc +++ b/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.cc @@ -221,9 +221,12 @@ TEST(ImageDecoderNoGLTest, ImpellerRGBA32FDecode) { fml::MakeRefCounted(std::move(sk_data)); // 2. Create an ImageDescriptor using the private constructor. - SkImageInfo image_info = - SkImageInfo::Make(1, 1, kRGBA_F32_SkColorType, kPremul_SkAlphaType, - SkColorSpace::MakeSRGB()); + ImageDescriptor::ImageInfo image_info = { + .width = 1, + .height = 1, + .format = ImageDescriptor::PixelFormat::kRGBAFloat32, + .alpha_type = kUnpremul_SkAlphaType, + }; auto descriptor = fml::MakeRefCounted( immutable_buffer->data(), image_info, sizeof(pixel_data)); @@ -265,6 +268,63 @@ TEST(ImageDecoderNoGLTest, ImpellerRGBA32FDecode) { #endif // IMPELLER_SUPPORTS_RENDERING } +TEST(ImageDecoderNoGLTest, ImpellerR32FDecode) { +#if defined(OS_FUCHSIA) + GTEST_SKIP() << "Fuchsia can't load the test fixtures."; +#endif + +#if !IMPELLER_SUPPORTS_RENDERING + GTEST_SKIP() << "test only supported on impeller"; +#else + // 1. Create a 1x1 pixel with float RGBA values. + float pixel_data[] = {1.0f}; + sk_sp sk_data = SkData::MakeWithCopy(pixel_data, sizeof(pixel_data)); + auto immutable_buffer = + fml::MakeRefCounted(std::move(sk_data)); + + // 2. Create an ImageDescriptor using the private constructor. + ImageDescriptor::ImageInfo image_info = { + .width = 1, + .height = 1, + .format = ImageDescriptor::PixelFormat::kR32Float, + .alpha_type = kUnpremul_SkAlphaType, + }; + auto descriptor = fml::MakeRefCounted( + immutable_buffer->data(), image_info, sizeof(pixel_data)); + + // Set up Impeller capabilities and allocator. + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); + std::shared_ptr allocator = + std::make_shared(); + + // 3. Call ImageDecoderImpeller::DecompressTexture with this ImageDescriptor. + absl::StatusOr result = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), + /*options=*/ + {.target_width = 1, + .target_height = 1, + .target_format = ImageDecoder::TargetPixelFormat::kR32Float}, + /*max_texture_size=*/{1, 1}, + /*supports_wide_gamut=*/true, capabilities, allocator); + + // 4. Assert that wide_result->image_info.format is + // impeller::PixelFormat::kR32G32B32A32Float. + ASSERT_TRUE(result.ok()); + ASSERT_EQ(result->image_info.format, impeller::PixelFormat::kR32Float); + + // Optionally, verify the pixel data if needed. + const float* decompressed_pixel_ptr = + reinterpret_cast(result->device_buffer->OnGetContents()); + ASSERT_NE(decompressed_pixel_ptr, nullptr); + EXPECT_EQ(decompressed_pixel_ptr[0], 1.0f); + +#endif // IMPELLER_SUPPORTS_RENDERING +} + TEST(ImageDecoderNoGLTest, ImpellerUnmultipliedAlphaPng) { #if defined(OS_FUCHSIA) GTEST_SKIP() << "Fuchsia can't load the test fixtures."; diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_skia.cc b/engine/src/flutter/lib/ui/painting/image_decoder_skia.cc index dfdbc46e91f..54503592855 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder_skia.cc +++ b/engine/src/flutter/lib/ui/painting/image_decoder_skia.cc @@ -83,7 +83,8 @@ static sk_sp ImageFromDecompressedData( TRACE_EVENT0("flutter", __FUNCTION__); flow.Step(__FUNCTION__); auto image = SkImages::RasterFromData( - descriptor->image_info(), descriptor->data(), descriptor->row_bytes()); + ImageDescriptor::ToSkImageInfo(descriptor->image_info()), + descriptor->data(), descriptor->row_bytes()); if (!image) { FML_LOG(ERROR) << "Could not create image from decompressed bytes."; @@ -113,7 +114,9 @@ sk_sp ImageDecoderSkia::ImageFromCompressedData( return image ? image->makeRasterImage(nullptr) : nullptr; } - const SkISize source_dimensions = descriptor->image_info().dimensions(); + const SkImageInfo image_info = + ImageDescriptor::ToSkImageInfo(descriptor->image_info()); + const SkISize source_dimensions = image_info.dimensions(); const SkISize resized_dimensions = {static_cast(target_width), static_cast(target_height)}; @@ -126,8 +129,7 @@ sk_sp ImageDecoderSkia::ImageFromCompressedData( // If the codec supports efficient sub-pixel decoding, decoded at a resolution // close to the target resolution before resizing. if (decode_dimensions != source_dimensions) { - auto scaled_image_info = - descriptor->image_info().makeDimensions(decode_dimensions); + auto scaled_image_info = image_info.makeDimensions(decode_dimensions); SkBitmap scaled_bitmap; if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) { diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc b/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc index 18e45032e6a..464c180dc94 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc +++ b/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc @@ -459,7 +459,8 @@ TEST_F(ImageDecoderFixtureTest, ImpellerNullColorspace) { EXPECT_EQ(nullptr, image->colorSpace()); auto descriptor = fml::MakeRefCounted( - std::move(data), image->imageInfo(), 10 * 4); + std::move(data), ImageDescriptor::CreateImageInfo(image->imageInfo()), + 10 * 4); #if IMPELLER_SUPPORTS_RENDERING std::shared_ptr capabilities = @@ -492,7 +493,8 @@ TEST_F(ImageDecoderFixtureTest, ImpellerPixelConversion32F) { EXPECT_EQ(nullptr, image->colorSpace()); auto descriptor = fml::MakeRefCounted( - std::move(data), image->imageInfo(), 10 * 16); + std::move(data), ImageDescriptor::CreateImageInfo(image->imageInfo()), + 10 * 16); #if IMPELLER_SUPPORTS_RENDERING std::shared_ptr capabilities = diff --git a/engine/src/flutter/lib/ui/painting/image_descriptor.cc b/engine/src/flutter/lib/ui/painting/image_descriptor.cc index 8a7d2f4de32..30fdf02d0c3 100644 --- a/engine/src/flutter/lib/ui/painting/image_descriptor.cc +++ b/engine/src/flutter/lib/ui/painting/image_descriptor.cc @@ -17,24 +17,78 @@ namespace flutter { IMPLEMENT_WRAPPERTYPEINFO(ui, ImageDescriptor); -const SkImageInfo ImageDescriptor::CreateImageInfo() const { - FML_DCHECK(generator_); - return generator_->GetInfo(); +ImageDescriptor::ImageInfo ImageDescriptor::CreateImageInfo( + const SkImageInfo& sk_image_info) { + PixelFormat format; + switch (sk_image_info.colorType()) { + case kUnknown_SkColorType: + format = kUnknown; + break; + case kRGBA_8888_SkColorType: + format = kRGBA8888; + break; + case kBGRA_8888_SkColorType: + format = kBGRA8888; + break; + case kRGBA_F32_SkColorType: + format = kRGBAFloat32; + break; + case kGray_8_SkColorType: + format = kGray8; + break; + default: + FML_DCHECK(false) << "Unsupported pixel format: " + << sk_image_info.colorType(); + format = kRGBA8888; + } + return ImageInfo{ + .width = static_cast(sk_image_info.width()), + .height = static_cast(sk_image_info.height()), + .format = format, + .alpha_type = sk_image_info.alphaType(), + .color_space = sk_image_info.refColorSpace(), + }; +} + +SkImageInfo ImageDescriptor::ToSkImageInfo(const ImageInfo& image_info) { + SkColorType color_type = kUnknown_SkColorType; + switch (image_info.format) { + case PixelFormat::kUnknown: + color_type = kUnknown_SkColorType; + break; + case PixelFormat::kRGBA8888: + color_type = kRGBA_8888_SkColorType; + break; + case PixelFormat::kBGRA8888: + color_type = kBGRA_8888_SkColorType; + break; + case PixelFormat::kRGBAFloat32: + color_type = kRGBA_F32_SkColorType; + break; + case PixelFormat::kGray8: + color_type = kGray_8_SkColorType; + break; + case PixelFormat::kR32Float: + FML_DCHECK(false) << "not a supported skia format"; + break; + } + return SkImageInfo::Make(image_info.width, image_info.height, color_type, + image_info.alpha_type, image_info.color_space); } ImageDescriptor::ImageDescriptor(sk_sp buffer, - const SkImageInfo& image_info, + const ImageInfo& image_info, std::optional row_bytes) : buffer_(std::move(buffer)), - generator_(nullptr), image_info_(image_info), + generator_(nullptr), row_bytes_(row_bytes) {} ImageDescriptor::ImageDescriptor(sk_sp buffer, std::shared_ptr generator) : buffer_(std::move(buffer)), + image_info_(CreateImageInfo(generator->GetInfo())), generator_(std::move(generator)), - image_info_(CreateImageInfo()), row_bytes_(std::nullopt) {} Dart_Handle ImageDescriptor::initEncoded(Dart_Handle descriptor_handle, @@ -78,31 +132,45 @@ Dart_Handle ImageDescriptor::initEncoded(Dart_Handle descriptor_handle, return Dart_Null(); } +namespace { +// Must be kept in sync with painting.dart. +ImageDescriptor::PixelFormat toImageDescriptorPixelFormat(int val) { + switch (val) { + case 0: + return ImageDescriptor::PixelFormat::kRGBA8888; + case 1: + return ImageDescriptor::PixelFormat::kBGRA8888; + case 2: + return ImageDescriptor::PixelFormat::kRGBAFloat32; + case 3: + return ImageDescriptor::PixelFormat::kR32Float; + default: + FML_DCHECK(false) << "unrecognized format"; + return ImageDescriptor::PixelFormat::kRGBA8888; + } +} +} // namespace + void ImageDescriptor::initRaw(Dart_Handle descriptor_handle, const fml::RefPtr& data, int width, int height, int row_bytes, - PixelFormat pixel_format) { - SkColorType color_type = kUnknown_SkColorType; - SkAlphaType alpha_type = kPremul_SkAlphaType; - switch (pixel_format) { - case PixelFormat::kRGBA8888: - color_type = kRGBA_8888_SkColorType; - break; - case PixelFormat::kBGRA8888: - color_type = kBGRA_8888_SkColorType; - break; - case PixelFormat::kRGBAFloat32: - // `PixelFormat.rgbaFloat32` is documented to not use premultiplied alpha. - color_type = kRGBA_F32_SkColorType; - alpha_type = kUnpremul_SkAlphaType; - break; - } - FML_DCHECK(color_type != kUnknown_SkColorType); - auto image_info = SkImageInfo::Make(width, height, color_type, alpha_type); + int pixel_format) { + ImageDescriptor::PixelFormat image_descriptor_pixel_format = + toImageDescriptorPixelFormat(pixel_format); + const ImageInfo image_info = { + .width = static_cast(width), + .height = static_cast(height), + .format = image_descriptor_pixel_format, + .alpha_type = image_descriptor_pixel_format == PixelFormat::kRGBAFloat32 + ? kUnpremul_SkAlphaType + : kPremul_SkAlphaType, + .color_space = SkColorSpace::MakeSRGB(), + }; + auto descriptor = fml::MakeRefCounted( - data->data(), std::move(image_info), + data->data(), image_info, row_bytes == -1 ? std::nullopt : std::optional(row_bytes)); descriptor->AssociateWithDartWrapper(descriptor_handle); } @@ -115,6 +183,8 @@ ImageDecoder::TargetPixelFormat ToImageDecoderTargetPixelFormat(int32_t value) { return ImageDecoder::TargetPixelFormat::kDontCare; case 1: return ImageDecoder::TargetPixelFormat::kR32G32B32A32Float; + case 2: + return ImageDecoder::TargetPixelFormat::kR32Float; default: FML_DCHECK(false) << "Unknown pixel format."; return ImageDecoder::TargetPixelFormat::kUnknown; @@ -148,4 +218,19 @@ bool ImageDescriptor::get_pixels(const SkPixmap& pixmap) const { pixmap.rowBytes()); } +int ImageDescriptor::bytesPerPixel() const { + switch (image_info_.format) { + case kUnknown: + return 0; + case kGray8: + return 1; + case kRGBA8888: + case kBGRA8888: + case kR32Float: + return 4; + case kRGBAFloat32: + return 16; + } +} + } // namespace flutter diff --git a/engine/src/flutter/lib/ui/painting/image_descriptor.h b/engine/src/flutter/lib/ui/painting/image_descriptor.h index ec2e79e0d17..cc5358e9833 100644 --- a/engine/src/flutter/lib/ui/painting/image_descriptor.h +++ b/engine/src/flutter/lib/ui/painting/image_descriptor.h @@ -13,6 +13,7 @@ #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/image_generator_registry.h" #include "flutter/lib/ui/painting/immutable_buffer.h" +#include "third_party/skia/include/core/SkColorSpace.h" #include "third_party/skia/include/core/SkData.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkImageInfo.h" @@ -35,9 +36,21 @@ class ImageDescriptor : public RefCountedDartWrappable { // This must be kept in sync with the enum in painting.dart enum PixelFormat { + // Error pixel format for Skia compatibility. + kUnknown, kRGBA8888, kBGRA8888, kRGBAFloat32, + kR32Float, + kGray8, + }; + + struct ImageInfo { + const uint32_t width; + const uint32_t height; + const PixelFormat format; + const SkAlphaType alpha_type; + const sk_sp color_space; }; /// @brief Asynchronously initializes an ImageDescriptor for an encoded @@ -57,7 +70,7 @@ class ImageDescriptor : public RefCountedDartWrappable { int width, int height, int row_bytes, - PixelFormat pixel_format); + int pixel_format); /// @brief Associates a flutter::Codec object with the dart.ui Codec handle. void instantiateCodec(Dart_Handle codec, @@ -66,19 +79,19 @@ class ImageDescriptor : public RefCountedDartWrappable { int32_t destination_format); /// @brief The width of this image, EXIF oriented if applicable. - int width() const { return image_info_.width(); } + int width() const { return image_info_.width; } /// @brief The height of this image. EXIF oriented if applicable. - int height() const { return image_info_.height(); } + int height() const { return image_info_.height; } /// @brief The bytes per pixel of the image. - int bytesPerPixel() const { return image_info_.bytesPerPixel(); } + int bytesPerPixel() const; /// @brief The byte length of the first row of the image. /// Defaults to width() * 4. int row_bytes() const { return row_bytes_.value_or( - static_cast(image_info_.width() * image_info_.bytesPerPixel())); + static_cast(image_info_.width * bytesPerPixel())); } /// @brief Whether the given `target_width` or `target_height` differ from @@ -97,7 +110,7 @@ class ImageDescriptor : public RefCountedDartWrappable { bool is_compressed() const { return !!generator_; } /// @brief The orientation corrected image info for this image. - const SkImageInfo& image_info() const { return image_info_; } + const ImageInfo& image_info() const { return image_info_; } /// @brief Gets the scaled dimensions of this image, if backed by an /// `ImageGenerator` that can perform efficient subpixel scaling. @@ -106,7 +119,7 @@ class ImageDescriptor : public RefCountedDartWrappable { if (generator_) { return generator_->GetScaledDimensions(scale); } - return image_info_.dimensions(); + return SkISize::Make(image_info_.width, image_info_.height); } /// @brief Gets pixels for this image transformed based on the EXIF @@ -119,20 +132,21 @@ class ImageDescriptor : public RefCountedDartWrappable { ClearDartWrapper(); } + static ImageInfo CreateImageInfo(const SkImageInfo& sk_image_info); + static SkImageInfo ToSkImageInfo(const ImageInfo& image_info); + private: ImageDescriptor(sk_sp buffer, - const SkImageInfo& image_info, + const ImageInfo& image_info, std::optional row_bytes); ImageDescriptor(sk_sp buffer, std::shared_ptr generator); sk_sp buffer_; + const ImageInfo image_info_; std::shared_ptr generator_; - const SkImageInfo image_info_; std::optional row_bytes_; - const SkImageInfo CreateImageInfo() const; - DEFINE_WRAPPERTYPEINFO(); FML_FRIEND_MAKE_REF_COUNTED(ImageDescriptor); FML_DISALLOW_COPY_AND_ASSIGN(ImageDescriptor); diff --git a/engine/src/flutter/shell/common/rasterizer.cc b/engine/src/flutter/shell/common/rasterizer.cc index 50330d4eaa0..18b7c7dc868 100644 --- a/engine/src/flutter/shell/common/rasterizer.cc +++ b/engine/src/flutter/shell/common/rasterizer.cc @@ -903,6 +903,7 @@ Rasterizer::ScreenshotFormat ToScreenshotFormat(impeller::PixelFormat format) { case impeller::PixelFormat::kR32G32B32A32Float: case impeller::PixelFormat::kB10G10R10XR: case impeller::PixelFormat::kB10G10R10A10XR: + case impeller::PixelFormat::kR32Float: FML_DCHECK(false); return Rasterizer::ScreenshotFormat::kUnknown; case impeller::PixelFormat::kR8G8B8A8UNormInt: