1 // Copyright 2019 The Chromium 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 #include "third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler.h"
6
7 #include "base/memory/scoped_refptr.h"
8 #include "base/test/null_task_runner.h"
9 #include "base/test/task_environment.h"
10 #include "build/build_config.h"
11 #include "components/viz/common/resources/transferable_resource.h"
12 #include "components/viz/test/test_context_provider.h"
13 #include "gpu/command_buffer/client/webgpu_interface_stub.h"
14 #include "testing/gmock/include/gmock/gmock.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
17 #include "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h"
18 #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
19 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
20 #include "third_party/blink/renderer/platform/graphics/test/gpu_test_utils.h"
21
22 using testing::_;
23 using testing::Return;
24
25 namespace blink {
26
27 namespace {
GenTestSyncToken(GLbyte id)28 gpu::SyncToken GenTestSyncToken(GLbyte id) {
29 gpu::SyncToken token;
30 token.Set(gpu::CommandBufferNamespace::GPU_IO,
31 gpu::CommandBufferId::FromUnsafeValue(64), id);
32 return token;
33 }
34
CreateBitmap()35 scoped_refptr<StaticBitmapImage> CreateBitmap() {
36 auto mailbox = gpu::Mailbox::GenerateForSharedImage();
37 auto release_callback = viz::SingleReleaseCallback::Create(
38 base::BindOnce([](const gpu::SyncToken&, bool) {}));
39 return AcceleratedStaticBitmapImage::CreateFromCanvasMailbox(
40 mailbox, GenTestSyncToken(100), 0, SkImageInfo::MakeN32Premul(100, 100),
41 GL_TEXTURE_2D, true, SharedGpuContext::ContextProviderWrapper(),
42 base::PlatformThread::CurrentRef(),
43 base::MakeRefCounted<base::NullTaskRunner>(),
44 std::move(release_callback));
45 }
46
GPUUploadingPathSupported()47 bool GPUUploadingPathSupported() {
48 // In current state, only passthrough command buffer can work on this path.
49 // and Windows is the platform that is using passthrough command buffer by
50 // default.
51 // TODO(shaobo.yan@intel.com): Enable test on more platforms when they're ready.
52 #if defined(OS_WIN)
53 return true;
54 #else
55 return false;
56 #endif // defined(OS_WIN)
57 }
58
59 class MockWebGPUInterface : public gpu::webgpu::WebGPUInterfaceStub {
60 public:
61 MOCK_METHOD(gpu::webgpu::ReservedTexture,
62 ReserveTexture,
63 (uint64_t device_client_id));
64 MOCK_METHOD(void,
65 AssociateMailbox,
66 (GLuint64 device_client_id,
67 GLuint device_generation,
68 GLuint id,
69 GLuint generation,
70 GLuint usage,
71 const GLbyte* mailbox));
72 MOCK_METHOD(void,
73 DissociateMailbox,
74 (GLuint64 device_client_id,
75 GLuint texture_id,
76 GLuint texture_generation));
77 };
78
79 // The six reference pixels are: red, green, blue, white, black.
80 static const uint8_t rgba8[] = {
81 0xFF, 0x00, 0x00, 0xFF, // Red
82 0x00, 0xFF, 0x00, 0xFF, // Green
83 0x00, 0x00, 0xFF, 0xFF, // Blue
84 0x00, 0x00, 0x00, 0xFF, // White
85 0xFF, 0xFF, 0xFF, 0xFF, // Opaque Black
86 0xFF, 0xFF, 0xFF, 0x00, // Transparent Black
87 };
88
89 static const uint8_t bgra8[] = {
90 0x00, 0x00, 0xFF, 0xFF, // Red
91 0x00, 0xFF, 0x00, 0xFF, // Green
92 0xFF, 0x00, 0x00, 0xFF, // Blue
93 0x00, 0x00, 0x00, 0xFF, // White
94 0xFF, 0xFF, 0xFF, 0xFF, // Opaque Black
95 0xFF, 0xFF, 0xFF, 0x00, // Transparent Black
96 };
97
98 static const uint8_t rgb10a2[] = {
99 0xFF, 0x03, 0x00, 0xC0, // Red
100 0x00, 0xFC, 0x0F, 0xC0, // Green
101 0x00, 0x00, 0xF0, 0xFF, // Blue
102 0x00, 0x00, 0x00, 0xC0, // White
103 0xFF, 0xFF, 0xFF, 0xFF, // Opaque Black
104 0xFF, 0xFF, 0xFF, 0x3F, // Transparent Black
105 };
106
107 static const uint16_t f16[] = {
108 0x3C00, 0x0000, 0x0000, 0x3C00, // Red
109 0x0000, 0x3C00, 0x0000, 0x3C00, // Green
110 0x0000, 0x0000, 0x3C00, 0x3C00, // Blue
111 0x0000, 0x0000, 0x0000, 0x3C00, // White
112 0x3C00, 0x3C00, 0x3C00, 0x3C00, // Opaque Black
113 0x3C00, 0x3C00, 0x3C00, 0x0000, // Transparent Black
114 };
115
116 static const float f32[] = {
117 1.0f, 0.0f, 0.0f, 1.0f, // Red
118 0.0f, 1.0f, 0.0f, 1.0f, // Green
119 0.0f, 0.0f, 1.0f, 1.0f, // Blue
120 0.0f, 0.0f, 0.0f, 1.0f, // White
121 1.0f, 1.0f, 1.0f, 1.0f, // Opaque Black
122 1.0f, 1.0f, 1.0f, 0.0f, // Transparent Black
123 };
124
125 static const uint8_t rg8[] = {
126 0xFF, 0x00, // Red
127 0x00, 0xFF, // Green
128 0x00, 0x00, // No Blue
129 0x00, 0x00, // White
130 0xFF, 0xFF, // Opaque Black
131 0xFF, 0xFF, // Transparent Black
132 };
133
134 static const uint16_t rg16f[] = {
135 0x3C00, 0x0000, // Red
136 0x0000, 0x3C00, // Green
137 0x0000, 0x0000, // No Blue
138 0x0000, 0x0000, // White
139 0x3C00, 0x3C00, // Opaque Black
140 0x3C00, 0x3C00, // Transparent Black
141 };
142
GetDstContent(WGPUTextureFormat format)143 base::span<const uint8_t> GetDstContent(WGPUTextureFormat format) {
144 switch (format) {
145 case WGPUTextureFormat_RG8Unorm:
146 return base::span<const uint8_t>(rg8, sizeof(rg8));
147 case WGPUTextureFormat_RGBA8Unorm:
148 // We need to ensure no color space conversion happens
149 // during imageBitmap uploading.
150 case WGPUTextureFormat_RGBA8UnormSrgb:
151 return base::span<const uint8_t>(rgba8, sizeof(rgba8));
152 case WGPUTextureFormat_BGRA8Unorm:
153 case WGPUTextureFormat_BGRA8UnormSrgb:
154 return base::span<const uint8_t>(bgra8, sizeof(bgra8));
155 case WGPUTextureFormat_RGB10A2Unorm:
156 return base::span<const uint8_t>(rgb10a2, sizeof(rgb10a2));
157 case WGPUTextureFormat_RG16Float:
158 return base::span<const uint8_t>(reinterpret_cast<const uint8_t*>(rg16f),
159 sizeof(rg16f));
160 case WGPUTextureFormat_RGBA16Float:
161 return base::span<const uint8_t>(reinterpret_cast<const uint8_t*>(f16),
162 sizeof(f16));
163 case WGPUTextureFormat_RGBA32Float:
164 return base::span<const uint8_t>(reinterpret_cast<const uint8_t*>(f32),
165 sizeof(f32));
166 default:
167 NOTREACHED();
168 return {};
169 }
170 }
171
GetSrcPixelContent(SkColorType format)172 base::span<const uint8_t> GetSrcPixelContent(SkColorType format) {
173 switch (format) {
174 case SkColorType::kRGBA_8888_SkColorType:
175 return base::span<const uint8_t>(rgba8, sizeof(rgba8));
176 case SkColorType::kBGRA_8888_SkColorType:
177 return base::span<const uint8_t>(bgra8, sizeof(bgra8));
178 case SkColorType::kRGBA_F16_SkColorType:
179 return base::span<const uint8_t>(reinterpret_cast<const uint8_t*>(f16),
180 sizeof(f16));
181 default:
182 NOTREACHED();
183 return {};
184 }
185 }
186
187 } // anonymous namespace
188
189 class WebGPUImageBitmapHandlerTest : public testing::Test {
190 protected:
SetUp()191 void SetUp() override {}
192
VerifyCopyBytesForCanvasColorParams(uint64_t width,uint64_t height,SkImageInfo info,IntRect copy_rect,WGPUTextureFormat color_type)193 void VerifyCopyBytesForCanvasColorParams(uint64_t width,
194 uint64_t height,
195 SkImageInfo info,
196 IntRect copy_rect,
197 WGPUTextureFormat color_type) {
198 const uint64_t content_length = width * height * info.bytesPerPixel();
199 std::vector<uint8_t> contents(content_length, 0);
200 // Initialize contents.
201 for (size_t i = 0; i < content_length; ++i) {
202 contents[i] = i % std::numeric_limits<uint8_t>::max();
203 }
204
205 VerifyCopyBytes(width, height, info, copy_rect, color_type,
206 base::span<uint8_t>(contents.data(), content_length),
207 base::span<uint8_t>(contents.data(), content_length));
208 }
209
VerifyCopyBytes(uint64_t width,uint64_t height,SkImageInfo info,IntRect copy_rect,WGPUTextureFormat color_type,base::span<const uint8_t> contents,base::span<const uint8_t> expected_value)210 void VerifyCopyBytes(uint64_t width,
211 uint64_t height,
212 SkImageInfo info,
213 IntRect copy_rect,
214 WGPUTextureFormat color_type,
215 base::span<const uint8_t> contents,
216 base::span<const uint8_t> expected_value) {
217 uint64_t bytes_per_pixel = DawnTextureFormatBytesPerPixel(color_type);
218 ASSERT_EQ(contents.size(), width * height * info.bytesPerPixel());
219 sk_sp<SkData> image_pixels =
220 SkData::MakeWithCopy(contents.data(), contents.size());
221 scoped_refptr<StaticBitmapImage> image =
222 StaticBitmapImage::Create(std::move(image_pixels), info);
223
224 WebGPUImageUploadSizeInfo wgpu_info =
225 ComputeImageBitmapWebGPUUploadSizeInfo(copy_rect, color_type);
226
227 const uint64_t result_length = wgpu_info.size_in_bytes;
228 std::vector<uint8_t> results(result_length, 0);
229 bool success = CopyBytesFromImageBitmapForWebGPU(
230 image, base::span<uint8_t>(results.data(), result_length), copy_rect,
231 color_type);
232 ASSERT_EQ(success, true);
233
234 // Compare content and results
235 uint32_t bytes_per_row = wgpu_info.wgpu_bytes_per_row;
236 uint32_t content_row_index =
237 (copy_rect.Y() * width + copy_rect.X()) * bytes_per_pixel;
238 uint32_t result_row_index = 0;
239 for (int i = 0; i < copy_rect.Height(); ++i) {
240 EXPECT_EQ(0, memcmp(&expected_value[content_row_index],
241 &results[result_row_index],
242 copy_rect.Width() * bytes_per_pixel));
243 content_row_index += width * bytes_per_pixel;
244 result_row_index += bytes_per_row;
245 }
246 }
247 };
248
TEST_F(WebGPUImageBitmapHandlerTest,VerifyColorConvert)249 TEST_F(WebGPUImageBitmapHandlerTest, VerifyColorConvert) {
250 // All supported CanvasPixelFormat mapping to SkColorType
251 const SkColorType srcSkColorFormat[] = {
252 SkColorType::kRGBA_8888_SkColorType,
253 SkColorType::kBGRA_8888_SkColorType,
254 SkColorType::kRGBA_F16_SkColorType,
255 };
256
257 // Joint of SkColorType and WebGPU texture format
258 const WGPUTextureFormat kDstWebGPUTextureFormat[] = {
259 WGPUTextureFormat_RG16Float, WGPUTextureFormat_RGBA16Float,
260 WGPUTextureFormat_RGBA32Float,
261
262 WGPUTextureFormat_RGB10A2Unorm, WGPUTextureFormat_RG8Unorm,
263 WGPUTextureFormat_RGBA8Unorm, WGPUTextureFormat_BGRA8Unorm,
264 WGPUTextureFormat_RGBA8UnormSrgb, WGPUTextureFormat_BGRA8UnormSrgb,
265 };
266
267 const CanvasColorSpace kColorSpaces[] = {
268 CanvasColorSpace::kSRGB,
269 CanvasColorSpace::kRec2020,
270 CanvasColorSpace::kP3,
271 };
272
273 uint64_t kImageWidth = 3;
274 uint64_t kImageHeight = 2;
275
276 IntRect image_data_rect(0, 0, kImageWidth, kImageHeight);
277
278 for (SkColorType src_color_type : srcSkColorFormat) {
279 for (WGPUTextureFormat dst_color_type : kDstWebGPUTextureFormat) {
280 for (CanvasColorSpace color_space : kColorSpaces) {
281 SkImageInfo info =
282 SkImageInfo::Make(kImageWidth, kImageHeight, src_color_type,
283 SkAlphaType::kUnpremul_SkAlphaType,
284 CanvasColorSpaceToSkColorSpace(color_space));
285 VerifyCopyBytes(kImageWidth, kImageHeight, info, image_data_rect,
286 dst_color_type, GetSrcPixelContent(src_color_type),
287 GetDstContent(dst_color_type));
288 }
289 }
290 }
291 }
292
293 // Test calculate size
TEST_F(WebGPUImageBitmapHandlerTest,VerifyGetWGPUResourceInfo)294 TEST_F(WebGPUImageBitmapHandlerTest, VerifyGetWGPUResourceInfo) {
295 uint64_t kImageWidth = 63;
296 uint64_t kImageHeight = 1;
297
298 // Prebaked expected values.
299 uint32_t expected_bytes_per_row = 256;
300 uint64_t expected_size = 256;
301
302 IntRect test_rect(0, 0, kImageWidth, kImageHeight);
303 WebGPUImageUploadSizeInfo info = ComputeImageBitmapWebGPUUploadSizeInfo(
304 test_rect, WGPUTextureFormat_RGBA8Unorm);
305 ASSERT_EQ(expected_size, info.size_in_bytes);
306 ASSERT_EQ(expected_bytes_per_row, info.wgpu_bytes_per_row);
307 }
308
309 // Copy full image bitmap test
TEST_F(WebGPUImageBitmapHandlerTest,VerifyCopyBytesFromImageBitmapForWebGPU)310 TEST_F(WebGPUImageBitmapHandlerTest, VerifyCopyBytesFromImageBitmapForWebGPU) {
311 uint64_t kImageWidth = 4;
312 uint64_t kImageHeight = 2;
313 SkImageInfo info = SkImageInfo::Make(
314 kImageWidth, kImageHeight, SkColorType::kRGBA_8888_SkColorType,
315 SkAlphaType::kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB());
316
317 IntRect image_data_rect(0, 0, kImageWidth, kImageHeight);
318 VerifyCopyBytesForCanvasColorParams(kImageWidth, kImageHeight, info,
319 image_data_rect,
320 WGPUTextureFormat_RGBA8Unorm);
321 }
322
323 // Copy sub image bitmap test
TEST_F(WebGPUImageBitmapHandlerTest,VerifyCopyBytesFromSubImageBitmap)324 TEST_F(WebGPUImageBitmapHandlerTest, VerifyCopyBytesFromSubImageBitmap) {
325 uint64_t kImageWidth = 63;
326 uint64_t kImageHeight = 4;
327 SkImageInfo info = SkImageInfo::Make(
328 kImageWidth, kImageHeight, SkColorType::kRGBA_8888_SkColorType,
329 SkAlphaType::kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB());
330
331 IntRect image_data_rect(2, 2, 60, 2);
332 VerifyCopyBytesForCanvasColorParams(kImageWidth, kImageHeight, info,
333 image_data_rect,
334 WGPUTextureFormat_RGBA8Unorm);
335 }
336
337 // Copy image bitmap with premultiply alpha
TEST_F(WebGPUImageBitmapHandlerTest,VerifyCopyBytesWithPremultiplyAlpha)338 TEST_F(WebGPUImageBitmapHandlerTest, VerifyCopyBytesWithPremultiplyAlpha) {
339 uint64_t kImageWidth = 2;
340 uint64_t kImageHeight = 1;
341 SkImageInfo info = SkImageInfo::Make(
342 kImageWidth, kImageHeight, SkColorType::kRGBA_8888_SkColorType,
343 SkAlphaType::kPremul_SkAlphaType, SkColorSpace::MakeSRGB());
344
345 IntRect image_data_rect(0, 0, 2, 1);
346 VerifyCopyBytesForCanvasColorParams(kImageWidth, kImageHeight, info,
347 image_data_rect,
348 WGPUTextureFormat_RGBA8Unorm);
349 }
350
351 class DawnTextureFromImageBitmapTest : public testing::Test {
352 protected:
SetUp()353 void SetUp() override {
354 auto webgpu = std::make_unique<MockWebGPUInterface>();
355 webgpu_ = webgpu.get();
356 auto provider = std::make_unique<WebGraphicsContext3DProviderForTests>(
357 std::move(webgpu));
358
359 dawn_control_client_ =
360 base::MakeRefCounted<DawnControlClientHolder>(std::move(provider));
361
362 dawn_texture_provider_ = base::MakeRefCounted<DawnTextureFromImageBitmap>(
363 dawn_control_client_, 1 /* device_client_id */);
364
365 test_context_provider_ = viz::TestContextProvider::Create();
366 InitializeSharedGpuContext(test_context_provider_.get());
367 }
368
TearDown()369 void TearDown() override { SharedGpuContext::ResetForTesting(); }
370 MockWebGPUInterface* webgpu_;
371 scoped_refptr<DawnControlClientHolder> dawn_control_client_;
372 scoped_refptr<DawnTextureFromImageBitmap> dawn_texture_provider_;
373 scoped_refptr<viz::TestContextProvider> test_context_provider_;
374 base::test::TaskEnvironment task_environment_;
375 };
376
TEST_F(DawnTextureFromImageBitmapTest,VerifyAccessTexture)377 TEST_F(DawnTextureFromImageBitmapTest, VerifyAccessTexture) {
378 if (!GPUUploadingPathSupported()) {
379 LOG(ERROR) << "Test skipped because GPU uploading path not supported.";
380 return;
381 }
382 auto bitmap = CreateBitmap();
383
384 viz::TransferableResource resource;
385 gpu::webgpu::ReservedTexture reservation = {
386 reinterpret_cast<WGPUTexture>(&resource), 1, 1};
387
388 // Test that ProduceDawnTextureFromImageBitmap calls ReserveTexture and
389 // AssociateMailbox correctly.
390 const GLbyte* mailbox_bytes = nullptr;
391
392 EXPECT_CALL(*webgpu_, ReserveTexture(_)).WillOnce(Return(reservation));
393 EXPECT_CALL(*webgpu_, AssociateMailbox(
394 dawn_texture_provider_->GetDeviceClientIdForTest(),
395 _, reservation.id, reservation.generation,
396 WGPUTextureUsage_CopySrc, _))
397 .WillOnce(testing::SaveArg<5>(&mailbox_bytes));
398
399 WGPUTexture texture =
400 dawn_texture_provider_->ProduceDawnTextureFromImageBitmap(bitmap);
401
402 gpu::Mailbox mailbox = gpu::Mailbox::FromVolatile(
403 *reinterpret_cast<const volatile gpu::Mailbox*>(mailbox_bytes));
404
405 EXPECT_TRUE(mailbox == bitmap->GetMailboxHolder().mailbox);
406 EXPECT_NE(texture, nullptr);
407 EXPECT_EQ(dawn_texture_provider_->GetTextureIdForTest(), 1u);
408 EXPECT_EQ(dawn_texture_provider_->GetTextureGenerationForTest(), 1u);
409
410 // Test that FinishDawnTextureFromImageBitmapAccess calls DissociateMailbox
411 // correctly.
412 EXPECT_CALL(*webgpu_, DissociateMailbox(
413 dawn_texture_provider_->GetDeviceClientIdForTest(),
414 reservation.id, reservation.generation));
415
416 dawn_texture_provider_->FinishDawnTextureFromImageBitmapAccess();
417
418 EXPECT_EQ(dawn_texture_provider_->GetTextureIdForTest(), 0u);
419 }
420 } // namespace blink
421