blob: f2bae7ce0d521c174af13fde032127c86704b7fe [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#include "components/viz/test/test_gpu_service_holder.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/client/webgpu_implementation.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_utils.h"
#include "gpu/command_buffer/service/webgpu_decoder.h"
#include "gpu/command_buffer/tests/webgpu_test.h"
#include "gpu/config/gpu_test_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/color_space.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/init/gl_factory.h"
#define SKIP_TEST_IF(condition) \
if (condition) \
GTEST_SKIP() << #condition
namespace gpu {
namespace {
class MockBufferMapCallback {
public:
MOCK_METHOD(void, Call, (WGPUBufferMapAsyncStatus status, void* userdata));
};
std::unique_ptr<testing::StrictMock<MockBufferMapCallback>>
mock_buffer_map_callback;
void ToMockBufferMapCallback(WGPUBufferMapAsyncStatus status, void* userdata) {
mock_buffer_map_callback->Call(status, userdata);
}
class MockUncapturedErrorCallback {
public:
MOCK_METHOD3(Call,
void(WGPUErrorType type, const char* message, void* userdata));
};
std::unique_ptr<testing::StrictMock<MockUncapturedErrorCallback>>
mock_device_error_callback;
void ToMockUncapturedErrorCallback(WGPUErrorType type,
const char* message,
void* userdata) {
mock_device_error_callback->Call(type, message, userdata);
}
struct WebGPUMailboxTestParams : WebGPUTest::Options {
viz::SharedImageFormat format;
};
std::ostream& operator<<(std::ostream& os,
const WebGPUMailboxTestParams& options) {
DCHECK(options.format == viz::SinglePlaneFormat::kRGBA_8888 ||
options.format == viz::SinglePlaneFormat::kBGRA_8888 ||
options.format == viz::SinglePlaneFormat::kRGBA_F16);
os << options.format.ToTestParamString();
if (options.enable_unsafe_webgpu) {
os << "_UnsafeWebGPU";
}
if (options.force_fallback_adapter) {
os << "_FallbackAdapter";
}
return os;
}
uint32_t BytesPerTexel(viz::SharedImageFormat format) {
if ((format == viz::SinglePlaneFormat::kRGBA_8888) ||
(format == viz::SinglePlaneFormat::kBGRA_8888)) {
return 4;
}
if (format == viz::SinglePlaneFormat::kRGBA_F16) {
return 8;
}
NOTREACHED();
return 0;
}
} // namespace
class WebGPUMailboxTest
: public WebGPUTest,
public testing::WithParamInterface<WebGPUMailboxTestParams> {
public:
static std::vector<WebGPUMailboxTestParams> TestParams() {
WebGPUMailboxTestParams options = {};
WebGPUMailboxTestParams fallback_options = {};
fallback_options.force_fallback_adapter = true;
// Unsafe WebGPU currently required for SwiftShader fallback.
fallback_options.enable_unsafe_webgpu = true;
std::vector<WebGPUMailboxTestParams> params;
for (viz::SharedImageFormat format : {
// TODO(crbug.com/1241369): Handle additional formats.
#if !BUILDFLAG(IS_MAC)
viz::SinglePlaneFormat::kRGBA_8888,
#endif // !BUILDFLAG(IS_MAC)
viz::SinglePlaneFormat::kBGRA_8888,
viz::SinglePlaneFormat::kRGBA_F16,
}) {
WebGPUMailboxTestParams o = options;
o.format = format;
params.push_back(o);
o = fallback_options;
o.format = format;
params.push_back(o);
}
return params;
}
protected:
void SetUp() override {
SKIP_TEST_IF(!WebGPUSupported());
SKIP_TEST_IF(!WebGPUSharedImageSupported());
WebGPUTest::SetUp();
Initialize(GetParam());
device_ = GetNewDevice();
mock_buffer_map_callback =
std::make_unique<testing::StrictMock<MockBufferMapCallback>>();
mock_device_error_callback =
std::make_unique<testing::StrictMock<MockUncapturedErrorCallback>>();
}
void TearDown() override {
mock_buffer_map_callback = nullptr;
mock_device_error_callback = nullptr;
// Wait for all operations to catch any validation or device lost errors.
PollUntilIdle();
device_ = nullptr;
WebGPUTest::TearDown();
}
struct AssociateMailboxCmdStorage {
webgpu::cmds::AssociateMailboxImmediate cmd;
// Immediate data is copied into the space immediately following `cmd`.
// Allocate space to hold up to 1 mailbox and 2 view formats.
GLbyte data[GL_MAILBOX_SIZE_CHROMIUM];
std::array<WGPUTextureFormat, 2u> view_formats;
};
template <typename T>
static error::Error ExecuteCmd(webgpu::WebGPUDecoder* decoder, const T& cmd) {
static_assert(T::kArgFlags == cmd::kFixed,
"T::kArgFlags should equal cmd::kFixed");
int entries_processed = 0;
return decoder->DoCommands(1, (const void*)&cmd,
ComputeNumEntries(sizeof(cmd)),
&entries_processed);
}
template <typename T>
static error::Error ExecuteImmediateCmd(webgpu::WebGPUDecoder* decoder,
const T& cmd,
size_t data_size) {
static_assert(T::kArgFlags == cmd::kAtLeastN,
"T::kArgFlags should equal cmd::kAtLeastN");
int entries_processed = 0;
return decoder->DoCommands(1, (const void*)&cmd,
ComputeNumEntries(sizeof(cmd) + data_size),
&entries_processed);
}
void InitializeTextureColor(wgpu::Device device,
const Mailbox& mailbox,
wgpu::Color clearValue) {
gpu::webgpu::ReservedTexture reservation =
webgpu()->ReserveTexture(device.Get());
webgpu()->AssociateMailbox(
reservation.deviceId, reservation.deviceGeneration, reservation.id,
reservation.generation, WGPUTextureUsage_RenderAttachment,
webgpu::WEBGPU_MAILBOX_NONE, mailbox);
wgpu::Texture texture = wgpu::Texture::Acquire(reservation.texture);
// Clear the texture using a render pass.
wgpu::RenderPassColorAttachment color_desc = {};
color_desc.view = texture.CreateView();
color_desc.loadOp = wgpu::LoadOp::Clear;
color_desc.storeOp = wgpu::StoreOp::Store;
color_desc.clearValue = clearValue;
wgpu::RenderPassDescriptor render_pass_desc = {};
render_pass_desc.colorAttachmentCount = 1;
render_pass_desc.colorAttachments = &color_desc;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&render_pass_desc);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
wgpu::Queue queue = device.GetQueue();
queue.Submit(1, &commands);
webgpu()->DissociateMailbox(reservation.id, reservation.generation);
}
void UninitializeTexture(wgpu::Device device, wgpu::Texture texture) {
wgpu::RenderPassColorAttachment color_desc = {};
color_desc.view = texture.CreateView();
color_desc.loadOp = wgpu::LoadOp::Load;
color_desc.storeOp = wgpu::StoreOp::Discard;
wgpu::RenderPassDescriptor render_pass_desc = {};
render_pass_desc.colorAttachmentCount = 1;
render_pass_desc.colorAttachments = &color_desc;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&render_pass_desc);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
wgpu::Queue queue = device.GetQueue();
queue.Submit(1, &commands);
}
wgpu::Device device_;
};
TEST_P(WebGPUMailboxTest, AssociateMailboxCmd) {
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
webgpu::ReservedTexture reservation = webgpu()->ReserveTexture(device_.Get());
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder, webgpu::ReservedTexture reservation,
gpu::Mailbox mailbox) {
// Error case: device client id doesn't exist.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId + 1, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, 0u,
ComputeNumEntries(sizeof(mailbox.name)),
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(
error::kInvalidArguments,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name)));
}
// Error case: device generation is invalid.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration + 1,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, 0u,
ComputeNumEntries(sizeof(mailbox.name)),
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(
error::kInvalidArguments,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name)));
}
// Error case: texture ID invalid for the wire server.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id + 1, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, 0u,
ComputeNumEntries(sizeof(mailbox.name)),
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(
error::kInvalidArguments,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name)));
}
// Error case: invalid texture usage.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_Force32, webgpu::WEBGPU_MAILBOX_NONE,
0u, ComputeNumEntries(sizeof(mailbox.name)),
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(
error::kInvalidArguments,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name)));
}
// Prep packed data for packing view formats and the mailbox.
std::vector<GLuint> packed_data;
packed_data.resize(sizeof(mailbox.name) / sizeof(uint32_t));
memcpy(reinterpret_cast<char*>(packed_data.data()), &mailbox.name,
sizeof(mailbox.name));
uint32_t view_format_count = 0u;
if (GetParam().format == viz::SinglePlaneFormat::kRGBA_F16) {
} else if (GetParam().format == viz::SinglePlaneFormat::kRGBA_8888) {
view_format_count = 1u;
packed_data.push_back(
static_cast<uint32_t>(WGPUTextureFormat_RGBA8UnormSrgb));
} else if (GetParam().format == viz::SinglePlaneFormat::kBGRA_8888) {
view_format_count = 2u;
packed_data.push_back(
static_cast<uint32_t>(WGPUTextureFormat_BGRA8UnormSrgb));
packed_data.push_back(
static_cast<uint32_t>(WGPUTextureFormat_BGRA8Unorm));
} else {
NOTREACHED();
}
// Error case: packed data empty.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, 0u, 0u, packed_data.data());
EXPECT_EQ(error::kOutOfBounds,
ExecuteImmediateCmd(decoder, cmd.cmd, 0u));
}
// Error case: packed data smaller than mailbox.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, view_format_count,
ComputeNumEntries(sizeof(mailbox.name)) - 1u,
packed_data.data());
EXPECT_EQ(error::kOutOfBounds,
ExecuteImmediateCmd(decoder, cmd.cmd,
sizeof(uint32_t) * packed_data.size()));
}
// Error case: packed data size incorrect.
for (int adjustment : {-1, -2}) {
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, view_format_count,
packed_data.size() + adjustment, packed_data.data());
EXPECT_EQ(error::kOutOfBounds,
ExecuteImmediateCmd(decoder, cmd.cmd,
sizeof(uint32_t) * packed_data.size()));
}
// Error case: view_format_count incorrect.
for (int adjustment : {-1, 1}) {
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE,
view_format_count + adjustment, packed_data.size(),
packed_data.data());
EXPECT_EQ(error::kOutOfBounds,
ExecuteImmediateCmd(decoder, cmd.cmd,
sizeof(uint32_t) * packed_data.size()));
}
// Control case: test a successful call to AssociateMailbox.
// The control case is not put first because it modifies the internal
// state of the Dawn wire server and would make calls with the same
// texture ID and generation invalid.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, view_format_count,
packed_data.size(), packed_data.data());
EXPECT_EQ(error::kNoError,
ExecuteImmediateCmd(decoder, cmd.cmd,
sizeof(uint32_t) * packed_data.size()));
}
// Error case: associated to an already associated texture.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, 0u,
ComputeNumEntries(sizeof(mailbox.name)),
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(
error::kInvalidArguments,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name)));
}
// Dissociate the image from the control case to remove its reference.
{
webgpu::cmds::DissociateMailbox cmd;
cmd.Init(reservation.id, reservation.generation);
EXPECT_EQ(error::kNoError, ExecuteCmd(decoder, cmd));
}
},
GetDecoder(), reservation, mailbox));
GetGpuServiceHolder()
->gpu_main_thread_task_runner()
->RunsTasksInCurrentSequence();
}
// Test that AssociateMailbox with a bad mailbox produces an error texture.
TEST_P(WebGPUMailboxTest, AssociateMailboxCmdBadMailboxMakesErrorTexture) {
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
webgpu::ReservedTexture reservation = webgpu()->ReserveTexture(device_.Get());
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder, webgpu::ReservedTexture reservation,
gpu::Mailbox mailbox) {
// Error case: invalid mailbox
{
gpu::Mailbox bad_mailbox;
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, 0u,
ComputeNumEntries(sizeof(bad_mailbox.name)),
reinterpret_cast<const GLuint*>(&bad_mailbox.name));
EXPECT_EQ(
error::kNoError,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(bad_mailbox.name)));
}
},
GetDecoder(), reservation, mailbox));
wgpu::Texture texture = wgpu::Texture::Acquire(reservation.texture);
// Expect an error when creating a view since the texture is an error.
EXPECT_WEBGPU_ERROR(device_, WGPUErrorType_Validation, texture.CreateView());
}
TEST_P(WebGPUMailboxTest, DissociateMailboxCmd) {
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
webgpu::ReservedTexture reservation = webgpu()->ReserveTexture(device_.Get());
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder, webgpu::ReservedTexture reservation,
gpu::Mailbox mailbox) {
// Associate a mailbox so we can later dissociate it.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
webgpu::WEBGPU_MAILBOX_NONE, 0u,
ComputeNumEntries(sizeof(mailbox.name)),
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(decoder, cmd.cmd,
sizeof(mailbox.name)));
}
// Error case: wrong texture ID
{
webgpu::cmds::DissociateMailbox cmd;
cmd.Init(reservation.id + 1, reservation.generation);
EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(decoder, cmd));
}
// Error case: wrong texture generation
{
webgpu::cmds::DissociateMailbox cmd;
cmd.Init(reservation.id, reservation.generation + 1);
EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(decoder, cmd));
}
// Success case
{
webgpu::cmds::DissociateMailbox cmd;
cmd.Init(reservation.id, reservation.generation);
EXPECT_EQ(error::kNoError, ExecuteCmd(decoder, cmd));
}
// Error case: dissociate an already dissociated mailbox
{
webgpu::cmds::DissociateMailbox cmd;
cmd.Init(reservation.id, reservation.generation);
EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(decoder, cmd));
}
},
GetDecoder(), reservation, mailbox));
GetGpuServiceHolder()
->gpu_main_thread_task_runner()
->RunsTasksInCurrentSequence();
}
// Tests using Associate/DissociateMailbox to share an image with Dawn.
// For simplicity of the test the image is shared between a Dawn device and
// itself: we render to it using the Dawn device, then re-associate it to a
// Dawn texture and read back the values that were written.
TEST_P(WebGPUMailboxTest, WriteToMailboxThenReadFromIt) {
SKIP_TEST_IF(GetParam().format == viz::SinglePlaneFormat::kRGBA_F16);
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
SyncToken mailbox_produced_token = sii->GenVerifiedSyncToken();
webgpu()->WaitSyncTokenCHROMIUM(mailbox_produced_token.GetConstData());
// Part 1: Write to the texture using Dawn
InitializeTextureColor(device_, mailbox, {0.0, 0.0, 1.0, 1.0});
// Part 2: Read back the texture using Dawn
{
// Register the shared image as a Dawn texture in the wire.
gpu::webgpu::ReservedTexture reservation =
webgpu()->ReserveTexture(device_.Get());
webgpu()->AssociateMailbox(reservation.deviceId,
reservation.deviceGeneration, reservation.id,
reservation.generation, WGPUTextureUsage_CopySrc,
webgpu::WEBGPU_MAILBOX_NONE, mailbox);
wgpu::Texture texture = wgpu::Texture::Acquire(reservation.texture);
// Copy the texture in a mappable buffer.
wgpu::BufferDescriptor buffer_desc;
buffer_desc.size = 4;
buffer_desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
wgpu::Buffer readback_buffer = device_.CreateBuffer(&buffer_desc);
wgpu::ImageCopyTexture copy_src = {};
copy_src.texture = texture;
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::ImageCopyBuffer copy_dst = {};
copy_dst.buffer = readback_buffer;
copy_dst.layout.offset = 0;
copy_dst.layout.bytesPerRow = 256;
wgpu::Extent3D copy_size = {1, 1, 1};
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
encoder.CopyTextureToBuffer(&copy_src, &copy_dst, &copy_size);
wgpu::CommandBuffer commands = encoder.Finish();
wgpu::Queue queue = device_.GetQueue();
queue.Submit(1, &commands);
webgpu()->DissociateMailbox(reservation.id, reservation.generation);
// Map the buffer and assert the pixel is the correct value.
readback_buffer.MapAsync(wgpu::MapMode::Read, 0, 4, ToMockBufferMapCallback,
nullptr);
EXPECT_CALL(*mock_buffer_map_callback,
Call(WGPUBufferMapAsyncStatus_Success, nullptr))
.Times(1);
WaitForCompletion(device_);
const void* data = readback_buffer.GetConstMappedRange();
if (GetParam().format == viz::SinglePlaneFormat::kRGBA_8888) {
EXPECT_EQ(0xFFFF0000u, *static_cast<const uint32_t*>(data));
} else if (GetParam().format == viz::SinglePlaneFormat::kBGRA_8888) {
EXPECT_EQ(0xFF0000FFu, *static_cast<const uint32_t*>(data));
} else {
NOTREACHED();
}
}
}
// Test that an uninitialized shared image is lazily cleared by Dawn when it is
// read.
TEST_P(WebGPUMailboxTest, ReadUninitializedSharedImage) {
// Create the shared image.
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
SyncToken mailbox_produced_token = sii->GenVerifiedSyncToken();
webgpu()->WaitSyncTokenCHROMIUM(mailbox_produced_token.GetConstData());
// Set the texture contents to non-zero so we can test a lazy clear occurs.
InitializeTextureColor(device_, mailbox, {1.0, 0, 0, 1.0});
// Register the shared image as a Dawn texture in the wire.
gpu::webgpu::ReservedTexture reservation =
webgpu()->ReserveTexture(device_.Get());
// Associate the mailbox. Using WEBGPU_MAILBOX_DISCARD will set the contents
// to uncleared.
webgpu()->AssociateMailbox(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_CopySrc,
webgpu::WEBGPU_MAILBOX_DISCARD, mailbox);
wgpu::Texture texture = wgpu::Texture::Acquire(reservation.texture);
// Copy the texture in a mappable buffer.
wgpu::BufferDescriptor buffer_desc;
buffer_desc.size = BytesPerTexel(GetParam().format);
buffer_desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
wgpu::Buffer readback_buffer = device_.CreateBuffer(&buffer_desc);
wgpu::ImageCopyTexture copy_src = {};
copy_src.texture = texture;
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::ImageCopyBuffer copy_dst = {};
copy_dst.buffer = readback_buffer;
copy_dst.layout.offset = 0;
copy_dst.layout.bytesPerRow = 256;
wgpu::Extent3D copy_size = {1, 1, 1};
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
encoder.CopyTextureToBuffer(&copy_src, &copy_dst, &copy_size);
wgpu::CommandBuffer commands = encoder.Finish();
wgpu::Queue queue = device_.GetQueue();
queue.Submit(1, &commands);
webgpu()->DissociateMailbox(reservation.id, reservation.generation);
// Map the buffer and assert the pixel is the correct value.
readback_buffer.MapAsync(wgpu::MapMode::Read, 0, buffer_desc.size,
ToMockBufferMapCallback, nullptr);
EXPECT_CALL(*mock_buffer_map_callback,
Call(WGPUBufferMapAsyncStatus_Success, nullptr))
.Times(1);
WaitForCompletion(device_);
const uint8_t* data = static_cast<const uint8_t*>(
readback_buffer.GetConstMappedRange(0, buffer_desc.size));
// Contents should be black because the texture was lazily cleared.
for (uint32_t i = 0; i < buffer_desc.size; ++i) {
EXPECT_EQ(data[i], uint8_t(0));
}
}
// Test that an uninitialized shared image is lazily cleared by Dawn when it is
// read.
TEST_P(WebGPUMailboxTest, ReadWritableUninitializedSharedImage) {
// Create the shared image.
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
SyncToken mailbox_produced_token = sii->GenVerifiedSyncToken();
webgpu()->WaitSyncTokenCHROMIUM(mailbox_produced_token.GetConstData());
// Set the texture contents to non-zero so we can test a lazy clear occurs.
InitializeTextureColor(device_, mailbox, {1.0, 0, 0, 1.0});
// Register the shared image as a Dawn texture in the wire.
gpu::webgpu::ReservedTexture reservation =
webgpu()->ReserveTexture(device_.Get());
// Associate the mailbox. Using WEBGPU_MAILBOX_DISCARD will set the contents
// to uncleared.
webgpu()->AssociateMailbox(
reservation.deviceId, reservation.deviceGeneration, reservation.id,
reservation.generation,
WGPUTextureUsage_CopySrc | WGPUTextureUsage_RenderAttachment,
webgpu::WEBGPU_MAILBOX_DISCARD, mailbox);
wgpu::Texture texture = wgpu::Texture::Acquire(reservation.texture);
// Read the texture using a render pass. Load+Store the contents.
// Uninitialized contents should not be loaded.
wgpu::RenderPassColorAttachment color_desc = {};
color_desc.view = texture.CreateView();
color_desc.loadOp = wgpu::LoadOp::Load;
color_desc.storeOp = wgpu::StoreOp::Store;
wgpu::RenderPassDescriptor render_pass_desc = {};
render_pass_desc.colorAttachmentCount = 1;
render_pass_desc.colorAttachments = &color_desc;
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&render_pass_desc);
pass.End();
// Copy the texture in a mappable buffer.
wgpu::BufferDescriptor buffer_desc;
buffer_desc.size = BytesPerTexel(GetParam().format);
buffer_desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
wgpu::Buffer readback_buffer = device_.CreateBuffer(&buffer_desc);
wgpu::ImageCopyTexture copy_src = {};
copy_src.texture = texture;
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::ImageCopyBuffer copy_dst = {};
copy_dst.buffer = readback_buffer;
copy_dst.layout.offset = 0;
copy_dst.layout.bytesPerRow = 256;
wgpu::Extent3D copy_size = {1, 1, 1};
encoder.CopyTextureToBuffer(&copy_src, &copy_dst, &copy_size);
wgpu::CommandBuffer commands = encoder.Finish();
wgpu::Queue queue = device_.GetQueue();
queue.Submit(1, &commands);
webgpu()->DissociateMailbox(reservation.id, reservation.generation);
// Map the buffer and assert the pixel is the correct value.
readback_buffer.MapAsync(wgpu::MapMode::Read, 0, buffer_desc.size,
ToMockBufferMapCallback, nullptr);
EXPECT_CALL(*mock_buffer_map_callback,
Call(WGPUBufferMapAsyncStatus_Success, nullptr))
.Times(1);
WaitForCompletion(device_);
const uint8_t* data = static_cast<const uint8_t*>(
readback_buffer.GetConstMappedRange(0, buffer_desc.size));
// Contents should be black because the texture was lazily cleared.
for (uint32_t i = 0; i < buffer_desc.size; ++i) {
EXPECT_EQ(data[i], uint8_t(0));
}
}
// Tests that using a shared image aftr it is dissociated produces an error.
TEST_P(WebGPUMailboxTest, ErrorWhenUsingTextureAfterDissociate) {
// Create a the shared image
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
SyncToken mailbox_produced_token = sii->GenVerifiedSyncToken();
webgpu()->WaitSyncTokenCHROMIUM(mailbox_produced_token.GetConstData());
// Set callback to expect a validation error.
device_.SetUncapturedErrorCallback(ToMockUncapturedErrorCallback, nullptr);
// Associate and immediately dissociate the image.
gpu::webgpu::ReservedTexture reservation =
webgpu()->ReserveTexture(device_.Get());
wgpu::Texture texture = wgpu::Texture::Acquire(reservation.texture);
webgpu()->AssociateMailbox(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_CopySrc,
webgpu::WEBGPU_MAILBOX_NONE, mailbox);
webgpu()->DissociateMailbox(reservation.id, reservation.generation);
wgpu::TextureDescriptor dst_desc = {};
dst_desc.size = {1, 1};
dst_desc.usage = wgpu::TextureUsage::CopyDst;
DCHECK(GetParam().format == viz::SinglePlaneFormat::kRGBA_8888 ||
GetParam().format == viz::SinglePlaneFormat::kBGRA_8888 ||
GetParam().format == viz::SinglePlaneFormat::kRGBA_F16);
dst_desc.format = ToDawnFormat(GetParam().format);
wgpu::ImageCopyTexture src_image = {};
src_image.texture = texture;
wgpu::ImageCopyTexture dst_image = {};
dst_image.texture = device_.CreateTexture(&dst_desc);
wgpu::Extent3D extent = {1, 1};
// Try using the texture in a copy command; it should produce a validation
// error.
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
encoder.CopyTextureToTexture(&src_image, &dst_image, &extent);
wgpu::CommandBuffer commandBuffer = encoder.Finish();
// Wait so it's clear the validation error after this when we call Submit.
WaitForCompletion(device_);
device_.GetQueue().Submit(1, &commandBuffer);
EXPECT_CALL(*mock_device_error_callback,
Call(WGPUErrorType_Validation, testing::_, testing::_))
.Times(1);
WaitForCompletion(device_);
}
// This is a regression test for an issue when using multiple shared images
// where a `ScopedAccess` was destroyed after it's `SharedImageRepresentation`.
// The code was similar to the following.
//
// struct Pair {
// unique_ptr<Representation> representation;
// unique_ptr<Access> access;
// };
//
// base::flat_map<Key, Pair> map;
// map.erase(some_iterator);
//
// In the Pair destructor C++ guarantees that `access` is destroyed before
// `representation` but `erase` can move one element over another, causing
// the move-assignment operator to be called. In this case the defaulted
// move-assignment would first move `representation` then `access`. Causing
// incorrect member destruction order for the move-to object.
TEST_P(WebGPUMailboxTest, UseA_UseB_DestroyA_DestroyB) {
// Create a the shared images.
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox_a = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
Mailbox mailbox_b = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
// Associate both mailboxes
gpu::webgpu::ReservedTexture reservation_a =
webgpu()->ReserveTexture(device_.Get());
webgpu()->AssociateMailbox(
reservation_a.deviceId, reservation_a.deviceGeneration, reservation_a.id,
reservation_a.generation, WGPUTextureUsage_RenderAttachment,
webgpu::WEBGPU_MAILBOX_NONE, mailbox_a);
gpu::webgpu::ReservedTexture reservation_b =
webgpu()->ReserveTexture(device_.Get());
webgpu()->AssociateMailbox(
reservation_b.deviceId, reservation_b.deviceGeneration, reservation_b.id,
reservation_b.generation, WGPUTextureUsage_RenderAttachment,
webgpu::WEBGPU_MAILBOX_NONE, mailbox_b);
// Dissociate both mailboxes in the same order.
webgpu()->DissociateMailbox(reservation_a.id, reservation_a.generation);
webgpu()->DissociateMailbox(reservation_b.id, reservation_b.generation);
// Send all the previous commands to the WebGPU decoder.
webgpu()->FlushCommands();
}
// Regression test for a bug where the (id, generation) for associated shared
// images was stored globally instead of per-device. This meant that of two
// devices tried to create shared images with the same (id, generation) (which
// is possible because they can be on different Dawn wires) they would conflict.
TEST_P(WebGPUMailboxTest, AssociateOnTwoDevicesAtTheSameTime) {
// Create a the shared images.
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox_a = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
Mailbox mailbox_b = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
// Two WebGPU devices to associate the shared images to.
wgpu::Device device_a = GetNewDevice();
wgpu::Device device_b = GetNewDevice();
// Associate both mailboxes
gpu::webgpu::ReservedTexture reservation_a =
webgpu()->ReserveTexture(device_a.Get());
webgpu()->AssociateMailbox(
reservation_a.deviceId, reservation_a.deviceGeneration, reservation_a.id,
reservation_a.generation, WGPUTextureUsage_RenderAttachment,
webgpu::WEBGPU_MAILBOX_NONE, mailbox_a);
gpu::webgpu::ReservedTexture reservation_b =
webgpu()->ReserveTexture(device_b.Get());
webgpu()->AssociateMailbox(
reservation_b.deviceId, reservation_b.deviceGeneration, reservation_b.id,
reservation_b.generation, WGPUTextureUsage_RenderAttachment,
webgpu::WEBGPU_MAILBOX_NONE, mailbox_b);
// Dissociate both mailboxes in the same order.
webgpu()->DissociateMailbox(reservation_a.id, reservation_a.generation);
webgpu()->DissociateMailbox(reservation_b.id, reservation_b.generation);
// Send all the previous commands to the WebGPU decoder.
webgpu()->FlushCommands();
}
// Test that passing a descriptor to ReserveTexture produces a client-side
// WGPUTexture that correctly reflects said descriptor.
TEST_P(WebGPUMailboxTest, ReflectionOfDescriptor) {
// Check that reserving a texture with a full descriptor give the same data
// back through reflection.
wgpu::TextureDescriptor desc1 = {};
desc1.size = {1, 2, 3};
desc1.format = wgpu::TextureFormat::R32Float;
desc1.usage = wgpu::TextureUsage::CopyDst;
desc1.dimension = wgpu::TextureDimension::e2D;
desc1.sampleCount = 1;
desc1.mipLevelCount = 1;
gpu::webgpu::ReservedTexture reservation1 = webgpu()->ReserveTexture(
device_.Get(), reinterpret_cast<const WGPUTextureDescriptor*>(&desc1));
wgpu::Texture texture1 = wgpu::Texture::Acquire(reservation1.texture);
ASSERT_EQ(desc1.size.width, texture1.GetWidth());
ASSERT_EQ(desc1.size.height, texture1.GetHeight());
ASSERT_EQ(desc1.size.depthOrArrayLayers, texture1.GetDepthOrArrayLayers());
ASSERT_EQ(desc1.format, texture1.GetFormat());
ASSERT_EQ(desc1.usage, texture1.GetUsage());
ASSERT_EQ(desc1.dimension, texture1.GetDimension());
ASSERT_EQ(desc1.sampleCount, texture1.GetSampleCount());
ASSERT_EQ(desc1.mipLevelCount, texture1.GetMipLevelCount());
// Test with a different descriptor to check data is not hardcoded. Not that
// this is actually not a valid descriptor (diimension == 1D with height !=
// 1), but that it should still be reflected exactly.
wgpu::TextureDescriptor desc2 = {};
desc2.size = {4, 5, 6};
desc2.format = wgpu::TextureFormat::RGBA8Unorm;
desc2.usage = wgpu::TextureUsage::CopySrc;
desc2.dimension = wgpu::TextureDimension::e1D;
desc2.sampleCount = 4;
desc2.mipLevelCount = 3;
gpu::webgpu::ReservedTexture reservation2 = webgpu()->ReserveTexture(
device_.Get(), reinterpret_cast<const WGPUTextureDescriptor*>(&desc2));
wgpu::Texture texture2 = wgpu::Texture::Acquire(reservation2.texture);
ASSERT_EQ(desc2.size.width, texture2.GetWidth());
ASSERT_EQ(desc2.size.height, texture2.GetHeight());
ASSERT_EQ(desc2.size.depthOrArrayLayers, texture2.GetDepthOrArrayLayers());
ASSERT_EQ(desc2.format, texture2.GetFormat());
ASSERT_EQ(desc2.usage, texture2.GetUsage());
ASSERT_EQ(desc2.dimension, texture2.GetDimension());
ASSERT_EQ(desc2.sampleCount, texture2.GetSampleCount());
ASSERT_EQ(desc2.mipLevelCount, texture2.GetMipLevelCount());
// Associate mailboxes so that releasing the reserved wgpu::Textures does not
// fail. Note that these texture parameters do not match. It doesn't matter
// since the textures are not used in this test except for frontend
// reflection.
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox1 = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
Mailbox mailbox2 = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
webgpu()->AssociateMailbox(
reservation1.deviceId, reservation1.deviceGeneration, reservation1.id,
reservation1.generation, static_cast<WGPUTextureUsage>(desc1.usage),
webgpu::WEBGPU_MAILBOX_NONE, mailbox1);
webgpu()->AssociateMailbox(
reservation2.deviceId, reservation2.deviceGeneration, reservation2.id,
reservation2.generation, static_cast<WGPUTextureUsage>(desc2.usage),
webgpu::WEBGPU_MAILBOX_NONE, mailbox2);
}
// Test that if some other GL context is current when
// Associate/DissociateMailbox occurs, the operations do not fail. Some WebGPU
// shared image backings rely on GL and need to be responsible for making the
// context current.
TEST_P(WebGPUMailboxTest, AssociateDissociateMailboxWhenNotCurrent) {
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
Mailbox mailbox = sii->CreateSharedImage(
GetParam().format, {1, 1}, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SHARED_IMAGE_USAGE_WEBGPU,
"TestLabel", kNullSurfaceHandle);
scoped_refptr<gl::GLContext> gl_context1;
scoped_refptr<gl::GLContext> gl_context2;
scoped_refptr<gl::GLSurface> gl_surface1;
scoped_refptr<gl::GLSurface> gl_surface2;
// Create and make a new gl context current.
// Contexts must be created on the GPU thread, so this creates it on the GPU
// thread and sets a scoped_refptr on the main thread.
auto CreateAndMakeGLContextCurrent =
[&](scoped_refptr<gl::GLContext>* gl_context_out,
scoped_refptr<gl::GLSurface>* gl_surface_out) {
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](scoped_refptr<gl::GLContext>* gl_context_out,
scoped_refptr<gl::GLSurface>* gl_surface_out) {
auto gl_surface = gl::init::CreateOffscreenGLSurface(
gl::GetDefaultDisplay(), gfx::Size(4, 4));
auto gl_context = gl::init::CreateGLContext(
nullptr, gl_surface.get(), gl::GLContextAttribs());
EXPECT_TRUE(gl_context->MakeCurrent(gl_surface.get()))
<< "Failed to make GL context current";
*gl_context_out = std::move(gl_context);
*gl_surface_out = std::move(gl_surface);
},
gl_context_out, gl_surface_out));
GetGpuServiceHolder()
->gpu_main_thread_task_runner()
->RunsTasksInCurrentSequence();
};
webgpu::ReservedTexture reservation = webgpu()->ReserveTexture(device_.Get());
// Create a GL context and make it current.
CreateAndMakeGLContextCurrent(&gl_context1, &gl_surface1);
webgpu()->AssociateMailbox(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_RenderAttachment,
webgpu::WEBGPU_MAILBOX_NONE, mailbox);
wgpu::Texture texture = wgpu::Texture::Acquire(reservation.texture);
// Clear the texture using a render pass.
wgpu::RenderPassColorAttachment color_desc = {};
color_desc.view = texture.CreateView();
color_desc.loadOp = wgpu::LoadOp::Clear;
color_desc.storeOp = wgpu::StoreOp::Store;
color_desc.clearValue = {0.0, 1.0, 0.0, 1.0};
wgpu::RenderPassDescriptor render_pass_desc = {};
render_pass_desc.colorAttachmentCount = 1;
render_pass_desc.colorAttachments = &color_desc;
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&render_pass_desc);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
wgpu::Queue queue = device_.GetQueue();
queue.Submit(1, &commands);
WaitForCompletion(device_);
// Create another context and make it current.
// This is a distinct context to catch errors where Associate/Dissociate
// always use the current context, and in the test, these just so happen to be
// identical.
CreateAndMakeGLContextCurrent(&gl_context2, &gl_surface2);
webgpu()->DissociateMailbox(reservation.id, reservation.generation);
WaitForCompletion(device_);
// Delete the GL contexts on the GPU thread.
GetGpuServiceHolder()->ScheduleGpuMainTask(
base::BindOnce([](scoped_refptr<gl::GLContext> gl_context1,
scoped_refptr<gl::GLContext> gl_context2,
scoped_refptr<gl::GLSurface> gl_surface1,
scoped_refptr<gl::GLSurface> gl_surface2) {},
std::move(gl_context1), std::move(gl_context2),
std::move(gl_surface1), std::move(gl_surface2)));
}
INSTANTIATE_TEST_SUITE_P(,
WebGPUMailboxTest,
::testing::ValuesIn(WebGPUMailboxTest::TestParams()),
::testing::PrintToStringParamName());
} // namespace gpu