diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 4c2ead5262dad..8dd864e9bb249 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -145,6 +145,7 @@ ../../../flutter/impeller/renderer/pipeline_descriptor_unittests.cc ../../../flutter/impeller/renderer/renderer_dart_unittests.cc ../../../flutter/impeller/renderer/renderer_unittests.cc +../../../flutter/impeller/renderer/testing ../../../flutter/impeller/runtime_stage/runtime_stage_unittests.cc ../../../flutter/impeller/scene/README.md ../../../flutter/impeller/scene/importer/importer_unittests.cc diff --git a/impeller/renderer/testing/mocks.h b/impeller/renderer/testing/mocks.h new file mode 100644 index 0000000000000..1f387ee58e05a --- /dev/null +++ b/impeller/renderer/testing/mocks.h @@ -0,0 +1,122 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "gmock/gmock.h" +#include "impeller/renderer/allocator.h" +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/render_target.h" +#include "impeller/renderer/texture.h" + +namespace impeller { +namespace testing { + +class MockDeviceBuffer : public DeviceBuffer { + public: + MockDeviceBuffer(const DeviceBufferDescriptor& desc) : DeviceBuffer(desc) {} + MOCK_METHOD3(CopyHostBuffer, + bool(const uint8_t* source, Range source_range, size_t offset)); + + MOCK_METHOD1(SetLabel, bool(const std::string& label)); + + MOCK_METHOD2(SetLabel, bool(const std::string& label, Range range)); + + MOCK_CONST_METHOD0(OnGetContents, uint8_t*()); + + MOCK_METHOD3(OnCopyHostBuffer, + bool(const uint8_t* source, Range source_range, size_t offset)); +}; + +class MockAllocator : public Allocator { + public: + MOCK_CONST_METHOD0(GetMaxTextureSizeSupported, ISize()); + MOCK_METHOD1( + OnCreateBuffer, + std::shared_ptr(const DeviceBufferDescriptor& desc)); + MOCK_METHOD1(OnCreateTexture, + std::shared_ptr(const TextureDescriptor& desc)); +}; + +class MockBlitPass : public BlitPass { + public: + MOCK_CONST_METHOD0(IsValid, bool()); + MOCK_CONST_METHOD1( + EncodeCommands, + bool(const std::shared_ptr& transients_allocator)); + MOCK_METHOD1(OnSetLabel, void(std::string label)); + + MOCK_METHOD5(OnCopyTextureToTextureCommand, + bool(std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + IPoint destination_origin, + std::string label)); + + MOCK_METHOD5(OnCopyTextureToBufferCommand, + bool(std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + size_t destination_offset, + std::string label)); + + MOCK_METHOD2(OnGenerateMipmapCommand, + bool(std::shared_ptr texture, std::string label)); +}; + +class MockCommandBuffer : public CommandBuffer { + public: + MockCommandBuffer(std::weak_ptr context) + : CommandBuffer(context) {} + MOCK_CONST_METHOD0(IsValid, bool()); + MOCK_CONST_METHOD1(SetLabel, void(const std::string& label)); + MOCK_CONST_METHOD0(OnCreateBlitPass, std::shared_ptr()); + MOCK_METHOD1(OnSubmitCommands, bool(CompletionCallback callback)); + MOCK_CONST_METHOD0(OnCreateComputePass, std::shared_ptr()); + MOCK_METHOD1(OnCreateRenderPass, + std::shared_ptr(RenderTarget render_target)); +}; + +class MockImpellerContext : public Context { + public: + MOCK_CONST_METHOD0(IsValid, bool()); + + MOCK_CONST_METHOD0(GetResourceAllocator, std::shared_ptr()); + + MOCK_CONST_METHOD0(GetShaderLibrary, std::shared_ptr()); + + MOCK_CONST_METHOD0(GetSamplerLibrary, std::shared_ptr()); + + MOCK_CONST_METHOD0(GetPipelineLibrary, std::shared_ptr()); + + MOCK_CONST_METHOD0(CreateCommandBuffer, std::shared_ptr()); + + MOCK_CONST_METHOD0(GetWorkQueue, std::shared_ptr()); + + MOCK_CONST_METHOD0(GetGPUTracer, std::shared_ptr()); + + MOCK_CONST_METHOD0(GetColorAttachmentPixelFormat, PixelFormat()); + + MOCK_CONST_METHOD0(GetDeviceCapabilities, const IDeviceCapabilities&()); +}; + +class MockTexture : public Texture { + public: + MockTexture(const TextureDescriptor& desc) : Texture(desc) {} + MOCK_METHOD1(SetLabel, void(std::string_view label)); + MOCK_METHOD3(SetContents, + bool(const uint8_t* contents, size_t length, size_t slice)); + MOCK_METHOD2(SetContents, + bool(std::shared_ptr mapping, size_t slice)); + MOCK_CONST_METHOD0(IsValid, bool()); + MOCK_CONST_METHOD0(GetSize, ISize()); + MOCK_METHOD3(OnSetContents, + bool(const uint8_t* contents, size_t length, size_t slice)); + MOCK_METHOD2(OnSetContents, + bool(std::shared_ptr mapping, size_t slice)); +}; + +} // namespace testing +} // namespace impeller diff --git a/lib/ui/painting/image_encoding.cc b/lib/ui/painting/image_encoding.cc index 578e9d6020c33..ea087a044c227 100644 --- a/lib/ui/painting/image_encoding.cc +++ b/lib/ui/painting/image_encoding.cc @@ -173,9 +173,9 @@ void EncodeImageAndInvokeDataCallback( FML_DCHECK(image); #if IMPELLER_SUPPORTS_RENDERING if (is_impeller_enabled) { - ConvertImageToRasterImpeller(image, encode_task, raster_task_runner, - io_task_runner, is_gpu_disabled_sync_switch, - impeller_context); + ImageEncodingImpeller::ConvertImageToRaster( + image, encode_task, raster_task_runner, io_task_runner, + is_gpu_disabled_sync_switch, impeller_context); return; } #endif // IMPELLER_SUPPORTS_RENDERING diff --git a/lib/ui/painting/image_encoding_impeller.cc b/lib/ui/painting/image_encoding_impeller.cc index bfb2365e428a4..ec429a2cda90f 100644 --- a/lib/ui/painting/image_encoding_impeller.cc +++ b/lib/ui/painting/image_encoding_impeller.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/lib/ui/painting/image_encoding_impeller.h" + #include "flutter/lib/ui/painting/image.h" #include "impeller/renderer/command_buffer.h" #include "impeller/renderer/context.h" @@ -15,12 +17,14 @@ std::optional ToSkColorType(impeller::PixelFormat format) { switch (format) { case impeller::PixelFormat::kR8G8B8A8UNormInt: return SkColorType::kRGBA_8888_SkColorType; + case impeller::PixelFormat::kR16G16B16A16Float: + return SkColorType::kRGBA_F16_SkColorType; case impeller::PixelFormat::kB8G8R8A8UNormInt: return SkColorType::kBGRA_8888_SkColorType; - break; + case impeller::PixelFormat::kB10G10R10XR: + return SkColorType::kBGR_101010x_XR_SkColorType; default: return std::nullopt; - break; } } @@ -50,7 +54,23 @@ sk_sp ConvertBufferToSkImage( return raster_image; } -void ConvertDlImageImpellerToSkImage( +void DoConvertImageToRasterImpeller( + const sk_sp& dl_image, + std::function)> encode_task, + const std::shared_ptr& is_gpu_disabled_sync_switch, + const std::shared_ptr& impeller_context) { + is_gpu_disabled_sync_switch->Execute( + fml::SyncSwitch::Handlers() + .SetIfTrue([&encode_task] { encode_task(nullptr); }) + .SetIfFalse([&dl_image, &encode_task, &impeller_context] { + ImageEncodingImpeller::ConvertDlImageToSkImage( + dl_image, std::move(encode_task), impeller_context); + })); +} + +} // namespace + +void ImageEncodingImpeller::ConvertDlImageToSkImage( const sk_sp& dl_image, std::function)> encode_task, const std::shared_ptr& impeller_context) { @@ -111,23 +131,7 @@ void ConvertDlImageImpellerToSkImage( } } -void DoConvertImageToRasterImpeller( - const sk_sp& dl_image, - std::function)> encode_task, - const std::shared_ptr& is_gpu_disabled_sync_switch, - const std::shared_ptr& impeller_context) { - is_gpu_disabled_sync_switch->Execute( - fml::SyncSwitch::Handlers() - .SetIfTrue([&encode_task] { encode_task(nullptr); }) - .SetIfFalse([&dl_image, &encode_task, &impeller_context] { - ConvertDlImageImpellerToSkImage(dl_image, std::move(encode_task), - impeller_context); - })); -} - -} // namespace - -void ConvertImageToRasterImpeller( +void ImageEncodingImpeller::ConvertImageToRaster( const sk_sp& dl_image, std::function)> encode_task, const fml::RefPtr& raster_task_runner, diff --git a/lib/ui/painting/image_encoding_impeller.h b/lib/ui/painting/image_encoding_impeller.h index 459ecf50909c4..fafe8fc9992b4 100644 --- a/lib/ui/painting/image_encoding_impeller.h +++ b/lib/ui/painting/image_encoding_impeller.h @@ -15,14 +15,28 @@ class Context; namespace flutter { -void ConvertImageToRasterImpeller( - const sk_sp& dl_image, - std::function)> encode_task, - const fml::RefPtr& raster_task_runner, - const fml::RefPtr& io_task_runner, - const std::shared_ptr& is_gpu_disabled_sync_switch, - const std::shared_ptr& impeller_context); +class ImageEncodingImpeller { + public: + /// Converts a DlImage to a SkImage. + /// This should be called from the thread that corresponds to + /// `dl_image->owning_context()` when gpu access is guaranteed. + /// See also: `ConvertImageToRaster`. + /// Visible for testing. + static void ConvertDlImageToSkImage( + const sk_sp& dl_image, + std::function)> encode_task, + const std::shared_ptr& impeller_context); + /// Converts a DlImage to a SkImage. + /// `encode_task` is executed with the resulting `SkImage`. + static void ConvertImageToRaster( + const sk_sp& dl_image, + std::function)> encode_task, + const fml::RefPtr& raster_task_runner, + const fml::RefPtr& io_task_runner, + const std::shared_ptr& is_gpu_disabled_sync_switch, + const std::shared_ptr& impeller_context); +}; } // namespace flutter #endif // FLUTTER_LIB_UI_PAINTING_IMAGE_ENCODING_IMPELLER_H_ diff --git a/lib/ui/painting/image_encoding_unittests.cc b/lib/ui/painting/image_encoding_unittests.cc index 1899054a94c1d..40e587c523c87 100644 --- a/lib/ui/painting/image_encoding_unittests.cc +++ b/lib/ui/painting/image_encoding_unittests.cc @@ -13,6 +13,12 @@ #include "flutter/shell/common/thread_host.h" #include "flutter/testing/testing.h" #include "gmock/gmock.h" +#include "gtest/gtest.h" + +#if IMPELLER_SUPPORTS_RENDERING +#include "flutter/lib/ui/painting/image_encoding_impeller.h" +#include "impeller/renderer/testing/mocks.h" +#endif // IMPELLER_SUPPORTS_RENDERING // CREATE_NATIVE_ENTRY is leaky by design // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape) @@ -22,8 +28,19 @@ namespace testing { namespace { fml::AutoResetWaitableEvent message_latch; + +class MockDlImage : public DlImage { + public: + MOCK_CONST_METHOD0(skia_image, sk_sp()); + MOCK_CONST_METHOD0(impeller_texture, std::shared_ptr()); + MOCK_CONST_METHOD0(isOpaque, bool()); + MOCK_CONST_METHOD0(isTextureBacked, bool()); + MOCK_CONST_METHOD0(dimensions, SkISize()); + MOCK_CONST_METHOD0(GetApproximateByteSize, size_t()); }; +} // namespace + class MockSyncSwitch { public: struct Handlers { @@ -166,6 +183,96 @@ TEST_F(ShellTest, EncodeImageAccessesSyncSwitch) { DestroyShell(std::move(shell), task_runners); } +#if IMPELLER_SUPPORTS_RENDERING +using ::impeller::testing::MockAllocator; +using ::impeller::testing::MockBlitPass; +using ::impeller::testing::MockCommandBuffer; +using ::impeller::testing::MockDeviceBuffer; +using ::impeller::testing::MockImpellerContext; +using ::impeller::testing::MockTexture; +using ::testing::_; +using ::testing::DoAll; +using ::testing::InvokeArgument; +using ::testing::Return; + +namespace { +std::shared_ptr MakeConvertDlImageToSkImageContext( + std::vector& buffer) { + auto context = std::make_shared(); + auto command_buffer = std::make_shared(context); + auto allocator = std::make_shared(); + auto blit_pass = std::make_shared(); + impeller::DeviceBufferDescriptor device_buffer_desc; + device_buffer_desc.size = buffer.size(); + auto device_buffer = std::make_shared(device_buffer_desc); + EXPECT_CALL(*allocator, OnCreateBuffer).WillOnce(Return(device_buffer)); + EXPECT_CALL(*blit_pass, IsValid).WillRepeatedly(Return(true)); + EXPECT_CALL(*command_buffer, IsValid).WillRepeatedly(Return(true)); + EXPECT_CALL(*command_buffer, OnCreateBlitPass).WillOnce(Return(blit_pass)); + EXPECT_CALL(*command_buffer, OnSubmitCommands(_)) + .WillOnce( + DoAll(InvokeArgument<0>(impeller::CommandBuffer::Status::kCompleted), + Return(true))); + EXPECT_CALL(*context, GetResourceAllocator).WillRepeatedly(Return(allocator)); + EXPECT_CALL(*context, CreateCommandBuffer).WillOnce(Return(command_buffer)); + EXPECT_CALL(*device_buffer, OnGetContents).WillOnce(Return(buffer.data())); + return context; +} +} // namespace + +TEST(ImageEncodingImpellerTest, ConvertDlImageToSkImage16Float) { + sk_sp image(new MockDlImage()); + EXPECT_CALL(*image, dimensions) + .WillRepeatedly(Return(SkISize::Make(100, 100))); + impeller::TextureDescriptor desc; + desc.format = impeller::PixelFormat::kR16G16B16A16Float; + auto texture = std::make_shared(desc); + EXPECT_CALL(*image, impeller_texture).WillOnce(Return(texture)); + std::vector buffer; + buffer.reserve(100 * 100 * 8); + auto context = MakeConvertDlImageToSkImageContext(buffer); + bool did_call = false; + ImageEncodingImpeller::ConvertDlImageToSkImage( + image, + [&did_call](const sk_sp& image) { + did_call = true; + ASSERT_TRUE(image); + EXPECT_EQ(100, image->width()); + EXPECT_EQ(100, image->height()); + EXPECT_EQ(kRGBA_F16_SkColorType, image->colorType()); + EXPECT_EQ(nullptr, image->colorSpace()); + }, + context); + EXPECT_TRUE(did_call); +} + +TEST(ImageEncodingImpellerTest, ConvertDlImageToSkImage10XR) { + sk_sp image(new MockDlImage()); + EXPECT_CALL(*image, dimensions) + .WillRepeatedly(Return(SkISize::Make(100, 100))); + impeller::TextureDescriptor desc; + desc.format = impeller::PixelFormat::kB10G10R10XR; + auto texture = std::make_shared(desc); + EXPECT_CALL(*image, impeller_texture).WillOnce(Return(texture)); + std::vector buffer; + buffer.reserve(100 * 100 * 4); + auto context = MakeConvertDlImageToSkImageContext(buffer); + bool did_call = false; + ImageEncodingImpeller::ConvertDlImageToSkImage( + image, + [&did_call](const sk_sp& image) { + did_call = true; + ASSERT_TRUE(image); + EXPECT_EQ(100, image->width()); + EXPECT_EQ(100, image->height()); + EXPECT_EQ(kBGR_101010x_XR_SkColorType, image->colorType()); + EXPECT_EQ(nullptr, image->colorSpace()); + }, + context); + EXPECT_TRUE(did_call); +} +#endif // IMPELLER_SUPPORTS_RENDERING + } // namespace testing } // namespace flutter