Skip to content

Commit 6f8d037

Browse files
gaaclarkechinmaygarde
authored andcommitted
[Impeller] Made toByteData work on wide gamut images (always downscaling to sRGB). (flutter#39932)
[Impeller] Made toByteData work on wide gamut images (always downscaling to sRGB).
1 parent 64c257b commit 6f8d037

File tree

6 files changed

+278
-30
lines changed

6 files changed

+278
-30
lines changed

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
../../../flutter/impeller/renderer/pipeline_descriptor_unittests.cc
146146
../../../flutter/impeller/renderer/renderer_dart_unittests.cc
147147
../../../flutter/impeller/renderer/renderer_unittests.cc
148+
../../../flutter/impeller/renderer/testing
148149
../../../flutter/impeller/runtime_stage/runtime_stage_unittests.cc
149150
../../../flutter/impeller/scene/README.md
150151
../../../flutter/impeller/scene/importer/importer_unittests.cc

impeller/renderer/testing/mocks.h

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#pragma once
6+
7+
#include "gmock/gmock.h"
8+
#include "impeller/renderer/allocator.h"
9+
#include "impeller/renderer/command_buffer.h"
10+
#include "impeller/renderer/context.h"
11+
#include "impeller/renderer/render_target.h"
12+
#include "impeller/renderer/texture.h"
13+
14+
namespace impeller {
15+
namespace testing {
16+
17+
class MockDeviceBuffer : public DeviceBuffer {
18+
public:
19+
MockDeviceBuffer(const DeviceBufferDescriptor& desc) : DeviceBuffer(desc) {}
20+
MOCK_METHOD3(CopyHostBuffer,
21+
bool(const uint8_t* source, Range source_range, size_t offset));
22+
23+
MOCK_METHOD1(SetLabel, bool(const std::string& label));
24+
25+
MOCK_METHOD2(SetLabel, bool(const std::string& label, Range range));
26+
27+
MOCK_CONST_METHOD0(OnGetContents, uint8_t*());
28+
29+
MOCK_METHOD3(OnCopyHostBuffer,
30+
bool(const uint8_t* source, Range source_range, size_t offset));
31+
};
32+
33+
class MockAllocator : public Allocator {
34+
public:
35+
MOCK_CONST_METHOD0(GetMaxTextureSizeSupported, ISize());
36+
MOCK_METHOD1(
37+
OnCreateBuffer,
38+
std::shared_ptr<DeviceBuffer>(const DeviceBufferDescriptor& desc));
39+
MOCK_METHOD1(OnCreateTexture,
40+
std::shared_ptr<Texture>(const TextureDescriptor& desc));
41+
};
42+
43+
class MockBlitPass : public BlitPass {
44+
public:
45+
MOCK_CONST_METHOD0(IsValid, bool());
46+
MOCK_CONST_METHOD1(
47+
EncodeCommands,
48+
bool(const std::shared_ptr<Allocator>& transients_allocator));
49+
MOCK_METHOD1(OnSetLabel, void(std::string label));
50+
51+
MOCK_METHOD5(OnCopyTextureToTextureCommand,
52+
bool(std::shared_ptr<Texture> source,
53+
std::shared_ptr<Texture> destination,
54+
IRect source_region,
55+
IPoint destination_origin,
56+
std::string label));
57+
58+
MOCK_METHOD5(OnCopyTextureToBufferCommand,
59+
bool(std::shared_ptr<Texture> source,
60+
std::shared_ptr<DeviceBuffer> destination,
61+
IRect source_region,
62+
size_t destination_offset,
63+
std::string label));
64+
65+
MOCK_METHOD2(OnGenerateMipmapCommand,
66+
bool(std::shared_ptr<Texture> texture, std::string label));
67+
};
68+
69+
class MockCommandBuffer : public CommandBuffer {
70+
public:
71+
MockCommandBuffer(std::weak_ptr<const Context> context)
72+
: CommandBuffer(context) {}
73+
MOCK_CONST_METHOD0(IsValid, bool());
74+
MOCK_CONST_METHOD1(SetLabel, void(const std::string& label));
75+
MOCK_CONST_METHOD0(OnCreateBlitPass, std::shared_ptr<BlitPass>());
76+
MOCK_METHOD1(OnSubmitCommands, bool(CompletionCallback callback));
77+
MOCK_CONST_METHOD0(OnCreateComputePass, std::shared_ptr<ComputePass>());
78+
MOCK_METHOD1(OnCreateRenderPass,
79+
std::shared_ptr<RenderPass>(RenderTarget render_target));
80+
};
81+
82+
class MockImpellerContext : public Context {
83+
public:
84+
MOCK_CONST_METHOD0(IsValid, bool());
85+
86+
MOCK_CONST_METHOD0(GetResourceAllocator, std::shared_ptr<Allocator>());
87+
88+
MOCK_CONST_METHOD0(GetShaderLibrary, std::shared_ptr<ShaderLibrary>());
89+
90+
MOCK_CONST_METHOD0(GetSamplerLibrary, std::shared_ptr<SamplerLibrary>());
91+
92+
MOCK_CONST_METHOD0(GetPipelineLibrary, std::shared_ptr<PipelineLibrary>());
93+
94+
MOCK_CONST_METHOD0(CreateCommandBuffer, std::shared_ptr<CommandBuffer>());
95+
96+
MOCK_CONST_METHOD0(GetWorkQueue, std::shared_ptr<WorkQueue>());
97+
98+
MOCK_CONST_METHOD0(GetGPUTracer, std::shared_ptr<GPUTracer>());
99+
100+
MOCK_CONST_METHOD0(GetColorAttachmentPixelFormat, PixelFormat());
101+
102+
MOCK_CONST_METHOD0(GetDeviceCapabilities, const IDeviceCapabilities&());
103+
};
104+
105+
class MockTexture : public Texture {
106+
public:
107+
MockTexture(const TextureDescriptor& desc) : Texture(desc) {}
108+
MOCK_METHOD1(SetLabel, void(std::string_view label));
109+
MOCK_METHOD3(SetContents,
110+
bool(const uint8_t* contents, size_t length, size_t slice));
111+
MOCK_METHOD2(SetContents,
112+
bool(std::shared_ptr<const fml::Mapping> mapping, size_t slice));
113+
MOCK_CONST_METHOD0(IsValid, bool());
114+
MOCK_CONST_METHOD0(GetSize, ISize());
115+
MOCK_METHOD3(OnSetContents,
116+
bool(const uint8_t* contents, size_t length, size_t slice));
117+
MOCK_METHOD2(OnSetContents,
118+
bool(std::shared_ptr<const fml::Mapping> mapping, size_t slice));
119+
};
120+
121+
} // namespace testing
122+
} // namespace impeller

lib/ui/painting/image_encoding.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ void EncodeImageAndInvokeDataCallback(
173173
FML_DCHECK(image);
174174
#if IMPELLER_SUPPORTS_RENDERING
175175
if (is_impeller_enabled) {
176-
ConvertImageToRasterImpeller(image, encode_task, raster_task_runner,
177-
io_task_runner, is_gpu_disabled_sync_switch,
178-
impeller_context);
176+
ImageEncodingImpeller::ConvertImageToRaster(
177+
image, encode_task, raster_task_runner, io_task_runner,
178+
is_gpu_disabled_sync_switch, impeller_context);
179179
return;
180180
}
181181
#endif // IMPELLER_SUPPORTS_RENDERING

lib/ui/painting/image_encoding_impeller.cc

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include "flutter/lib/ui/painting/image_encoding_impeller.h"
6+
57
#include "flutter/lib/ui/painting/image.h"
68
#include "impeller/renderer/command_buffer.h"
79
#include "impeller/renderer/context.h"
@@ -15,12 +17,14 @@ std::optional<SkColorType> ToSkColorType(impeller::PixelFormat format) {
1517
switch (format) {
1618
case impeller::PixelFormat::kR8G8B8A8UNormInt:
1719
return SkColorType::kRGBA_8888_SkColorType;
20+
case impeller::PixelFormat::kR16G16B16A16Float:
21+
return SkColorType::kRGBA_F16_SkColorType;
1822
case impeller::PixelFormat::kB8G8R8A8UNormInt:
1923
return SkColorType::kBGRA_8888_SkColorType;
20-
break;
24+
case impeller::PixelFormat::kB10G10R10XR:
25+
return SkColorType::kBGR_101010x_XR_SkColorType;
2126
default:
2227
return std::nullopt;
23-
break;
2428
}
2529
}
2630

@@ -50,7 +54,23 @@ sk_sp<SkImage> ConvertBufferToSkImage(
5054
return raster_image;
5155
}
5256

53-
void ConvertDlImageImpellerToSkImage(
57+
void DoConvertImageToRasterImpeller(
58+
const sk_sp<DlImage>& dl_image,
59+
std::function<void(sk_sp<SkImage>)> encode_task,
60+
const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch,
61+
const std::shared_ptr<impeller::Context>& impeller_context) {
62+
is_gpu_disabled_sync_switch->Execute(
63+
fml::SyncSwitch::Handlers()
64+
.SetIfTrue([&encode_task] { encode_task(nullptr); })
65+
.SetIfFalse([&dl_image, &encode_task, &impeller_context] {
66+
ImageEncodingImpeller::ConvertDlImageToSkImage(
67+
dl_image, std::move(encode_task), impeller_context);
68+
}));
69+
}
70+
71+
} // namespace
72+
73+
void ImageEncodingImpeller::ConvertDlImageToSkImage(
5474
const sk_sp<DlImage>& dl_image,
5575
std::function<void(sk_sp<SkImage>)> encode_task,
5676
const std::shared_ptr<impeller::Context>& impeller_context) {
@@ -111,23 +131,7 @@ void ConvertDlImageImpellerToSkImage(
111131
}
112132
}
113133

114-
void DoConvertImageToRasterImpeller(
115-
const sk_sp<DlImage>& dl_image,
116-
std::function<void(sk_sp<SkImage>)> encode_task,
117-
const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch,
118-
const std::shared_ptr<impeller::Context>& impeller_context) {
119-
is_gpu_disabled_sync_switch->Execute(
120-
fml::SyncSwitch::Handlers()
121-
.SetIfTrue([&encode_task] { encode_task(nullptr); })
122-
.SetIfFalse([&dl_image, &encode_task, &impeller_context] {
123-
ConvertDlImageImpellerToSkImage(dl_image, std::move(encode_task),
124-
impeller_context);
125-
}));
126-
}
127-
128-
} // namespace
129-
130-
void ConvertImageToRasterImpeller(
134+
void ImageEncodingImpeller::ConvertImageToRaster(
131135
const sk_sp<DlImage>& dl_image,
132136
std::function<void(sk_sp<SkImage>)> encode_task,
133137
const fml::RefPtr<fml::TaskRunner>& raster_task_runner,

lib/ui/painting/image_encoding_impeller.h

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,28 @@ class Context;
1515

1616
namespace flutter {
1717

18-
void ConvertImageToRasterImpeller(
19-
const sk_sp<DlImage>& dl_image,
20-
std::function<void(sk_sp<SkImage>)> encode_task,
21-
const fml::RefPtr<fml::TaskRunner>& raster_task_runner,
22-
const fml::RefPtr<fml::TaskRunner>& io_task_runner,
23-
const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch,
24-
const std::shared_ptr<impeller::Context>& impeller_context);
18+
class ImageEncodingImpeller {
19+
public:
20+
/// Converts a DlImage to a SkImage.
21+
/// This should be called from the thread that corresponds to
22+
/// `dl_image->owning_context()` when gpu access is guaranteed.
23+
/// See also: `ConvertImageToRaster`.
24+
/// Visible for testing.
25+
static void ConvertDlImageToSkImage(
26+
const sk_sp<DlImage>& dl_image,
27+
std::function<void(sk_sp<SkImage>)> encode_task,
28+
const std::shared_ptr<impeller::Context>& impeller_context);
2529

30+
/// Converts a DlImage to a SkImage.
31+
/// `encode_task` is executed with the resulting `SkImage`.
32+
static void ConvertImageToRaster(
33+
const sk_sp<DlImage>& dl_image,
34+
std::function<void(sk_sp<SkImage>)> encode_task,
35+
const fml::RefPtr<fml::TaskRunner>& raster_task_runner,
36+
const fml::RefPtr<fml::TaskRunner>& io_task_runner,
37+
const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch,
38+
const std::shared_ptr<impeller::Context>& impeller_context);
39+
};
2640
} // namespace flutter
2741

2842
#endif // FLUTTER_LIB_UI_PAINTING_IMAGE_ENCODING_IMPELLER_H_

lib/ui/painting/image_encoding_unittests.cc

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
#include "flutter/shell/common/thread_host.h"
1414
#include "flutter/testing/testing.h"
1515
#include "gmock/gmock.h"
16+
#include "gtest/gtest.h"
17+
18+
#if IMPELLER_SUPPORTS_RENDERING
19+
#include "flutter/lib/ui/painting/image_encoding_impeller.h"
20+
#include "impeller/renderer/testing/mocks.h"
21+
#endif // IMPELLER_SUPPORTS_RENDERING
1622

1723
// CREATE_NATIVE_ENTRY is leaky by design
1824
// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
@@ -22,8 +28,19 @@ namespace testing {
2228

2329
namespace {
2430
fml::AutoResetWaitableEvent message_latch;
31+
32+
class MockDlImage : public DlImage {
33+
public:
34+
MOCK_CONST_METHOD0(skia_image, sk_sp<SkImage>());
35+
MOCK_CONST_METHOD0(impeller_texture, std::shared_ptr<impeller::Texture>());
36+
MOCK_CONST_METHOD0(isOpaque, bool());
37+
MOCK_CONST_METHOD0(isTextureBacked, bool());
38+
MOCK_CONST_METHOD0(dimensions, SkISize());
39+
MOCK_CONST_METHOD0(GetApproximateByteSize, size_t());
2540
};
2641

42+
} // namespace
43+
2744
class MockSyncSwitch {
2845
public:
2946
struct Handlers {
@@ -166,6 +183,96 @@ TEST_F(ShellTest, EncodeImageAccessesSyncSwitch) {
166183
DestroyShell(std::move(shell), task_runners);
167184
}
168185

186+
#if IMPELLER_SUPPORTS_RENDERING
187+
using ::impeller::testing::MockAllocator;
188+
using ::impeller::testing::MockBlitPass;
189+
using ::impeller::testing::MockCommandBuffer;
190+
using ::impeller::testing::MockDeviceBuffer;
191+
using ::impeller::testing::MockImpellerContext;
192+
using ::impeller::testing::MockTexture;
193+
using ::testing::_;
194+
using ::testing::DoAll;
195+
using ::testing::InvokeArgument;
196+
using ::testing::Return;
197+
198+
namespace {
199+
std::shared_ptr<impeller::Context> MakeConvertDlImageToSkImageContext(
200+
std::vector<uint8_t>& buffer) {
201+
auto context = std::make_shared<MockImpellerContext>();
202+
auto command_buffer = std::make_shared<MockCommandBuffer>(context);
203+
auto allocator = std::make_shared<MockAllocator>();
204+
auto blit_pass = std::make_shared<MockBlitPass>();
205+
impeller::DeviceBufferDescriptor device_buffer_desc;
206+
device_buffer_desc.size = buffer.size();
207+
auto device_buffer = std::make_shared<MockDeviceBuffer>(device_buffer_desc);
208+
EXPECT_CALL(*allocator, OnCreateBuffer).WillOnce(Return(device_buffer));
209+
EXPECT_CALL(*blit_pass, IsValid).WillRepeatedly(Return(true));
210+
EXPECT_CALL(*command_buffer, IsValid).WillRepeatedly(Return(true));
211+
EXPECT_CALL(*command_buffer, OnCreateBlitPass).WillOnce(Return(blit_pass));
212+
EXPECT_CALL(*command_buffer, OnSubmitCommands(_))
213+
.WillOnce(
214+
DoAll(InvokeArgument<0>(impeller::CommandBuffer::Status::kCompleted),
215+
Return(true)));
216+
EXPECT_CALL(*context, GetResourceAllocator).WillRepeatedly(Return(allocator));
217+
EXPECT_CALL(*context, CreateCommandBuffer).WillOnce(Return(command_buffer));
218+
EXPECT_CALL(*device_buffer, OnGetContents).WillOnce(Return(buffer.data()));
219+
return context;
220+
}
221+
} // namespace
222+
223+
TEST(ImageEncodingImpellerTest, ConvertDlImageToSkImage16Float) {
224+
sk_sp<MockDlImage> image(new MockDlImage());
225+
EXPECT_CALL(*image, dimensions)
226+
.WillRepeatedly(Return(SkISize::Make(100, 100)));
227+
impeller::TextureDescriptor desc;
228+
desc.format = impeller::PixelFormat::kR16G16B16A16Float;
229+
auto texture = std::make_shared<MockTexture>(desc);
230+
EXPECT_CALL(*image, impeller_texture).WillOnce(Return(texture));
231+
std::vector<uint8_t> buffer;
232+
buffer.reserve(100 * 100 * 8);
233+
auto context = MakeConvertDlImageToSkImageContext(buffer);
234+
bool did_call = false;
235+
ImageEncodingImpeller::ConvertDlImageToSkImage(
236+
image,
237+
[&did_call](const sk_sp<SkImage>& image) {
238+
did_call = true;
239+
ASSERT_TRUE(image);
240+
EXPECT_EQ(100, image->width());
241+
EXPECT_EQ(100, image->height());
242+
EXPECT_EQ(kRGBA_F16_SkColorType, image->colorType());
243+
EXPECT_EQ(nullptr, image->colorSpace());
244+
},
245+
context);
246+
EXPECT_TRUE(did_call);
247+
}
248+
249+
TEST(ImageEncodingImpellerTest, ConvertDlImageToSkImage10XR) {
250+
sk_sp<MockDlImage> image(new MockDlImage());
251+
EXPECT_CALL(*image, dimensions)
252+
.WillRepeatedly(Return(SkISize::Make(100, 100)));
253+
impeller::TextureDescriptor desc;
254+
desc.format = impeller::PixelFormat::kB10G10R10XR;
255+
auto texture = std::make_shared<MockTexture>(desc);
256+
EXPECT_CALL(*image, impeller_texture).WillOnce(Return(texture));
257+
std::vector<uint8_t> buffer;
258+
buffer.reserve(100 * 100 * 4);
259+
auto context = MakeConvertDlImageToSkImageContext(buffer);
260+
bool did_call = false;
261+
ImageEncodingImpeller::ConvertDlImageToSkImage(
262+
image,
263+
[&did_call](const sk_sp<SkImage>& image) {
264+
did_call = true;
265+
ASSERT_TRUE(image);
266+
EXPECT_EQ(100, image->width());
267+
EXPECT_EQ(100, image->height());
268+
EXPECT_EQ(kBGR_101010x_XR_SkColorType, image->colorType());
269+
EXPECT_EQ(nullptr, image->colorSpace());
270+
},
271+
context);
272+
EXPECT_TRUE(did_call);
273+
}
274+
#endif // IMPELLER_SUPPORTS_RENDERING
275+
169276
} // namespace testing
170277
} // namespace flutter
171278

0 commit comments

Comments
 (0)