blob: c3c28a0d1c8bb75c0e4f3a5e84383a70be879953 [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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "build/build_config.h"
#include "components/viz/test/test_gpu_service_holder.h"
#include "gpu/command_buffer/client/client_shared_image.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/webgpu_decoder.h"
#include "gpu/command_buffer/tests/webgpu_test.h"
#include "gpu/config/gpu_finch_features.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_surface.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,
(wgpu::MapAsyncStatus status, wgpu::StringView message));
};
std::unique_ptr<testing::StrictMock<MockBufferMapCallback>>
mock_buffer_map_callback;
void ToMockBufferMapCallback(wgpu::MapAsyncStatus status,
wgpu::StringView message) {
mock_buffer_map_callback->Call(status, message);
}
struct WebGPUMailboxTextureTestParams : WebGPUTest::Options {
viz::SharedImageFormat format;
};
std::ostream& operator<<(std::ostream& os,
const WebGPUMailboxTextureTestParams& 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.use_skia_graphite) {
os << "_SkiaGraphite";
}
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();
}
wgpu::TextureFormat VizToWGPUFormat(const viz::SharedImageFormat& format) {
// This function provides the inverse mapping of `WGPUFormatToViz` (located in
// webgpu_swap_buffer_provider.cc).
if (format == viz::SinglePlaneFormat::kBGRA_8888) {
return wgpu::TextureFormat::BGRA8Unorm;
}
if (format == viz::SinglePlaneFormat::kRGBA_8888) {
return wgpu::TextureFormat::RGBA8Unorm;
}
if (format == viz::SinglePlaneFormat::kRGBA_F16) {
return wgpu::TextureFormat::RGBA16Float;
}
NOTREACHED() << "Unsupported format: " << format.ToString();
}
} // namespace
class WebGPUMailboxTestBase : public WebGPUTest {
protected:
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);
}
};
class WebGPUMailboxTextureTest
: public WebGPUMailboxTestBase,
public testing::WithParamInterface<WebGPUMailboxTextureTestParams> {
public:
static std::vector<WebGPUMailboxTextureTestParams> TestParams() {
WebGPUMailboxTextureTestParams options = {};
WebGPUMailboxTextureTestParams fallback_options = {};
fallback_options.force_fallback_adapter = true;
// Unsafe WebGPU currently required for SwiftShader fallback.
fallback_options.enable_unsafe_webgpu = true;
std::vector<WebGPUMailboxTextureTestParams> params;
for (viz::SharedImageFormat format : {
// TODO(crbug.com/40823053): Support "rgba8unorm" canvas context format on Mac
#if !BUILDFLAG(IS_MAC)
viz::SinglePlaneFormat::kRGBA_8888,
#endif // !BUILDFLAG(IS_MAC)
viz::SinglePlaneFormat::kBGRA_8888,
viz::SinglePlaneFormat::kRGBA_F16,
}) {
WebGPUMailboxTextureTestParams o = options;
o.format = format;
#if BUILDFLAG(IS_LINUX)
// Linux does not support creation of RGBA_F16 GpuMemoryBuffers, which
// causes SharedImage creation in these tests to fail.
if (o.format != viz::SinglePlaneFormat::kRGBA_F16) {
params.push_back(o);
}
#else
params.push_back(o);
#endif
// Test SwiftShader fallback both with and without SkiaGraphite
o = fallback_options;
o.format = format;
o.use_skia_graphite = false;
params.push_back(o);
// Note: Only windows & Mac have Graphite supported for now.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
o.use_skia_graphite = true;
params.push_back(o);
#endif
}
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>>();
}
void TearDown() override {
mock_buffer_map_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;
};
enum class AccessType { Read, Write, ReadWrite };
SharedImageUsageSet GetSharedImageUsage(AccessType access_type) {
SharedImageUsageSet webgpu_usage;
// With the fallback adapter, reading/writing from the SharedImage will
// occur via Skia.
SharedImageUsageSet fallback_usage;
switch (access_type) {
case AccessType::Read:
webgpu_usage = SHARED_IMAGE_USAGE_WEBGPU_READ;
fallback_usage = SHARED_IMAGE_USAGE_RASTER_READ;
break;
case AccessType::Write:
webgpu_usage = SHARED_IMAGE_USAGE_WEBGPU_WRITE;
fallback_usage = SHARED_IMAGE_USAGE_RASTER_WRITE;
break;
case AccessType::ReadWrite:
webgpu_usage =
SHARED_IMAGE_USAGE_WEBGPU_READ | SHARED_IMAGE_USAGE_WEBGPU_WRITE;
fallback_usage =
SHARED_IMAGE_USAGE_RASTER_READ | SHARED_IMAGE_USAGE_RASTER_WRITE;
break;
}
auto si_usage = webgpu_usage;
if (IsUsingFallbackAdapter()) {
si_usage |= fallback_usage;
}
return si_usage;
}
void InitializeTextureColor(
wgpu::Device device,
scoped_refptr<gpu::ClientSharedImage> shared_image,
wgpu::Color clearValue) {
wgpu::TextureDescriptor desc = {
.usage = wgpu::TextureUsage::RenderAttachment,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), GetSharedImageInterface()->GenVerifiedSyncToken(), device,
desc, /*usage=*/0, webgpu::WEBGPU_MAILBOX_NONE);
// Clear the texture using a render pass.
wgpu::RenderPassColorAttachment color_desc = {};
color_desc.view = webgpu_scoped_access->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);
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
}
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(WebGPUMailboxTextureTest, AssociateMailboxCmd) {
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::Read),
"TestLabel"},
kNullSurfaceHandle);
webgpu::ReservedTexture reservation = webgpu()->ReserveTexture(device_.Get());
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder, webgpu::ReservedTexture reservation,
scoped_refptr<gpu::ClientSharedImage> shared_image) {
const gpu::Mailbox& mailbox = shared_image->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, 0u,
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, 0u,
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, 0u,
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,
UINT64_MAX, 0u,
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 internal texture usage.
{
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding,
UINT64_MAX, 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, UINT64_MAX, 0u,
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, UINT64_MAX, 0u,
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, 0u,
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, 0u,
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, 0u,
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, 0u,
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, std::move(shared_image)));
GetGpuServiceHolder()
->gpu_main_thread_task_runner()
->RunsTasksInCurrentSequence();
}
// Test that AssociateMailbox with a bad mailbox produces an error texture.
TEST_P(WebGPUMailboxTextureTest,
AssociateMailboxCmdBadMailboxMakesErrorTexture) {
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::Read),
"TestLabel"},
kNullSurfaceHandle);
webgpu::ReservedTexture reservation = webgpu()->ReserveTexture(device_.Get());
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder, webgpu::ReservedTexture reservation,
scoped_refptr<gpu::ClientSharedImage> shared_image) {
// Error case: invalid mailbox
{
gpu::Mailbox bad_mailbox;
AssociateMailboxCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUTextureUsage_TextureBinding, 0u,
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, std::move(shared_image)));
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_, wgpu::ErrorType::Validation,
texture.CreateView());
}
TEST_P(WebGPUMailboxTextureTest, DissociateMailboxCmd) {
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::Read),
"TestLabel"},
kNullSurfaceHandle);
webgpu::ReservedTexture reservation = webgpu()->ReserveTexture(device_.Get());
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder, webgpu::ReservedTexture reservation,
scoped_refptr<gpu::ClientSharedImage> shared_image) {
const gpu::Mailbox& mailbox = shared_image->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, 0u,
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, std::move(shared_image)));
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(WebGPUMailboxTextureTest, WriteToMailboxThenReadFromIt) {
SKIP_TEST_IF(GetParam().format == viz::SinglePlaneFormat::kRGBA_F16);
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
// Part 1: Write to the texture using Dawn
InitializeTextureColor(device_, shared_image, {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.
wgpu::TextureDescriptor desc = {
.usage = wgpu::TextureUsage::CopySrc,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(webgpu(), gpu::SyncToken(),
device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
// 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::TexelCopyTextureInfo copy_src = {};
copy_src.texture = webgpu_scoped_access->texture();
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::TexelCopyBufferInfo 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);
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
// Map the buffer and assert the pixel is the correct value.
readback_buffer.MapAsync(wgpu::MapMode::Read, 0, 4,
wgpu::CallbackMode::AllowSpontaneous,
ToMockBufferMapCallback);
EXPECT_CALL(*mock_buffer_map_callback,
Call(wgpu::MapAsyncStatus::Success, testing::_))
.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 passing write usages when associating a mailbox fails if
// the SharedImage associated with the mailbox doesn't have WEBGPU_WRITE access.
TEST_P(WebGPUMailboxTextureTest,
PassWriteUsagesWhenAssociatingReadOnlyMailbox) {
// Create the shared image.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::Read),
"TestLabel"},
kNullSurfaceHandle);
wgpu::TextureDescriptor desc = {
.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
// 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::TexelCopyTextureInfo copy_src = {};
copy_src.texture = webgpu_scoped_access->texture();
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::TexelCopyBufferInfo 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);
EXPECT_WEBGPU_ERROR(device_, wgpu::ErrorType::Validation, encoder.Finish());
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
WaitForCompletion(device_);
}
// Test that passing internal write usages when associating a mailbox fails if
// the SharedImage associated with the mailbox doesn't have WEBGPU_WRITE access.
TEST_P(WebGPUMailboxTextureTest,
PassInternalWriteUsagesWhenAssociatingReadOnlyMailbox) {
// Create the shared image.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::Read),
"TestLabel"},
kNullSurfaceHandle);
wgpu::TextureDescriptor desc = {
.usage = wgpu::TextureUsage::CopySrc,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc,
WGPUTextureUsage_CopyDst, webgpu::WEBGPU_MAILBOX_NONE);
// 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::TexelCopyTextureInfo copy_src = {};
copy_src.texture = webgpu_scoped_access->texture();
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::TexelCopyBufferInfo 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);
EXPECT_WEBGPU_ERROR(device_, wgpu::ErrorType::Validation, encoder.Finish());
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
WaitForCompletion(device_);
}
// Test that passing WEBGPU_MAILBOX_DISCARD when associating a mailbox fails if
// the SharedImage associated with the mailbox doesn't have WEBGPU_WRITE access.
TEST_P(WebGPUMailboxTextureTest, PassDiscardWhenAssociatingReadOnlyMailbox) {
// Create the shared image.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::Read),
"TestLabel"},
kNullSurfaceHandle);
wgpu::TextureDescriptor desc = {
.usage = wgpu::TextureUsage::CopySrc,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_DISCARD);
// 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::TexelCopyTextureInfo copy_src = {};
copy_src.texture = webgpu_scoped_access->texture();
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::TexelCopyBufferInfo 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);
EXPECT_WEBGPU_ERROR(device_, wgpu::ErrorType::Validation, encoder.Finish());
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
WaitForCompletion(device_);
}
// Test that passing WEBGPU_MAILBOX_DISCARD when associating a mailbox fails if
// the client doesn't pass a usage supporting lazy clearing.
TEST_P(WebGPUMailboxTextureTest,
PassDiscardWhenAssociatingMailboxWithoutUsageSupportingClearing) {
// Create the shared image.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
wgpu::TextureDescriptor desc = {
.usage = wgpu::TextureUsage::CopySrc,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_DISCARD);
// 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::TexelCopyTextureInfo copy_src = {};
copy_src.texture = webgpu_scoped_access->texture();
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::TexelCopyBufferInfo 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);
EXPECT_WEBGPU_ERROR(device_, wgpu::ErrorType::Validation, encoder.Finish());
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
WaitForCompletion(device_);
}
// Test that an uninitialized writable shared image is lazily cleared by Dawn
// when it is accessed with an internal write usage supporting lazy clearing.
TEST_P(WebGPUMailboxTextureTest,
ReadWritableUninitializedSharedImageWhenAccessedWithInternalWriteUsage) {
// Create the shared image. Note that it is uncleared by default.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
wgpu::TextureDescriptor desc = {
.usage = wgpu::TextureUsage::CopySrc,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc,
WGPUTextureUsage_RenderAttachment, webgpu::WEBGPU_MAILBOX_NONE);
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
// 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::TexelCopyTextureInfo copy_src = {};
copy_src.texture = webgpu_scoped_access->texture();
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::TexelCopyBufferInfo 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);
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
// Map the buffer and assert the pixel is the correct value.
readback_buffer.MapAsync(wgpu::MapMode::Read, 0, buffer_desc.size,
wgpu::CallbackMode::AllowSpontaneous,
ToMockBufferMapCallback);
EXPECT_CALL(*mock_buffer_map_callback,
Call(wgpu::MapAsyncStatus::Success, testing::_))
.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));
}
// Associate the SharedImage with a new Dawn texture in a read-only access.
// The SharedImage should have been cleared at the end of the previous access,
// and hence this read access should succeed.
EXPECT_EQ(webgpu_scoped_access, nullptr);
webgpu_scoped_access = shared_image->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
copy_src.texture = webgpu_scoped_access->texture();
wgpu::Buffer readback_buffer2 = device_.CreateBuffer(&buffer_desc);
copy_dst.buffer = readback_buffer2;
wgpu::CommandEncoder encoder2 = device_.CreateCommandEncoder();
encoder2.CopyTextureToBuffer(&copy_src, &copy_dst, &copy_size);
commands = encoder2.Finish();
queue.Submit(1, &commands);
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
// Map the buffer.
readback_buffer2.MapAsync(wgpu::MapMode::Read, 0, buffer_desc.size,
wgpu::CallbackMode::AllowSpontaneous,
ToMockBufferMapCallback);
EXPECT_CALL(*mock_buffer_map_callback,
Call(wgpu::MapAsyncStatus::Success, testing::_))
.Times(1);
WaitForCompletion(device_);
}
// Test that an uninitialized writable shared image is lazily cleared by Dawn
// when it is read if a usage supporting lazy clearing is passed.
TEST_P(WebGPUMailboxTextureTest,
ReadWritableUninitializedSharedImageWithUsageSupportingLazyClearing) {
// Create the shared image.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
// Set the texture contents to non-zero so we can test a lazy clear occurs.
InitializeTextureColor(device_, shared_image, {1.0, 0, 0, 1.0});
// Register the shared image as a Dawn texture in the wire.
wgpu::TextureDescriptor desc = {
.usage =
wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(webgpu(), gpu::SyncToken(),
device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_DISCARD);
// Read the texture using a render pass. Load+Store the contents.
// Uninitialized contents should not be loaded.
wgpu::RenderPassColorAttachment color_desc = {};
color_desc.view = webgpu_scoped_access->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::TexelCopyTextureInfo copy_src = {};
copy_src.texture = webgpu_scoped_access->texture();
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::TexelCopyBufferInfo 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);
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
// Map the buffer and assert the pixel is the correct value.
readback_buffer.MapAsync(wgpu::MapMode::Read, 0, buffer_desc.size,
wgpu::CallbackMode::AllowSpontaneous,
ToMockBufferMapCallback);
EXPECT_CALL(*mock_buffer_map_callback,
Call(wgpu::MapAsyncStatus::Success, testing::_))
.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 writable shared image is lazily cleared by Dawn
// when it is read if an internal usage supporting lazy clearing is passed.
TEST_P(
WebGPUMailboxTextureTest,
ReadWritableUninitializedSharedImageWithInternalUsageSupportingLazyClearing) {
// Create the shared image.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
// Set the texture contents to non-zero so we can test a lazy clear occurs.
InitializeTextureColor(device_, shared_image, {1.0, 0, 0, 1.0});
// Register the shared image as a Dawn texture in the wire.
wgpu::TextureDescriptor desc = {
.usage = wgpu::TextureUsage::CopySrc,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), gpu::SyncToken(), device_, desc,
WGPUTextureUsage_RenderAttachment, webgpu::WEBGPU_MAILBOX_DISCARD);
// 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::TexelCopyTextureInfo copy_src = {};
copy_src.texture = webgpu_scoped_access->texture();
copy_src.mipLevel = 0;
copy_src.origin = {0, 0, 0};
wgpu::TexelCopyBufferInfo 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);
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
// Map the buffer and assert the pixel is the correct value.
readback_buffer.MapAsync(wgpu::MapMode::Read, 0, buffer_desc.size,
wgpu::CallbackMode::AllowSpontaneous,
ToMockBufferMapCallback);
EXPECT_CALL(*mock_buffer_map_callback,
Call(wgpu::MapAsyncStatus::Success, testing::_))
.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(WebGPUMailboxTextureTest, ErrorWhenUsingTextureAfterDissociate) {
// Create the shared image.
// NOTE: It's necessary to add WEBGPU_WRITE access as the created SharedImage
// will be uncleared and hence require lazy clearing on access.
// WebGPUDecoderImpl might also need to fall back to using Skia to read and
// write, making it necessary to add those usages as well.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
// NOTE: Accessing an uncleared Dawn texture requires passing a usage that
// supports lazy clearing (otherwise AssociateMailbox() will generate an
// error, which is not the error case that this test is looking to test).
wgpu::TextureDescriptor desc = {.usage =
wgpu::TextureUsage::CopySrc |
wgpu::TextureUsage::RenderAttachment};
// Associate and immediately dissociate the image.
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
wgpu::Texture texture = webgpu_scoped_access->texture();
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
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 = VizToWGPUFormat(GetParam().format);
wgpu::TexelCopyTextureInfo src_image = {};
src_image.texture = texture;
wgpu::TexelCopyTextureInfo 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_);
EXPECT_WEBGPU_ERROR(device_, wgpu::ErrorType::Validation,
device_.GetQueue().Submit(1, &commandBuffer));
}
// 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(WebGPUMailboxTextureTest, UseA_UseB_DestroyA_DestroyB) {
// Create a the shared images.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image_a =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
scoped_refptr<gpu::ClientSharedImage> shared_image_b =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
// Associate both mailboxes
wgpu::TextureDescriptor desc = {.usage =
wgpu::TextureUsage::RenderAttachment};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access_a =
shared_image_a->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access_b =
shared_image_b->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
// Dissociate both mailboxes in the same order.
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access_a));
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access_b));
// 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(WebGPUMailboxTextureTest, AssociateOnTwoDevicesAtTheSameTime) {
// Create a the shared images.
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image_a =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
scoped_refptr<gpu::ClientSharedImage> shared_image_b =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
// Two WebGPU devices to associate the shared images to.
wgpu::Device device_a = GetNewDevice();
wgpu::Device device_b = GetNewDevice();
// Associate both mailboxes
wgpu::TextureDescriptor desc = {.usage =
wgpu::TextureUsage::RenderAttachment};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access_a =
shared_image_a->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_a, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access_b =
shared_image_b->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_b, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
// Dissociate both mailboxes in the same order.
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access_a));
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access_b));
// 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(WebGPUMailboxTextureTest, ReflectionOfDescriptor) {
// 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();
scoped_refptr<gpu::ClientSharedImage> shared_image1 =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
scoped_refptr<gpu::ClientSharedImage> shared_image2 =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::Read),
"TestLabel"},
kNullSurfaceHandle);
// 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;
// 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;
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access1 =
shared_image1->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc1, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access2 =
shared_image2->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc2, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
wgpu::Texture texture1 = webgpu_scoped_access1->texture();
wgpu::Texture texture2 = webgpu_scoped_access2->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());
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());
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access1));
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access2));
}
// Test that passing a texture with invalid view formats to AssociateMailbox
// does not cause WebGPU validation errors on a later call to DissociateMailbox.
TEST_P(WebGPUMailboxTextureTest, AssociateInvalidViewFormats) {
wgpu::TextureDescriptor desc = {};
desc.size = {1, 1, 1};
desc.format = VizToWGPUFormat(GetParam().format);
desc.usage = wgpu::TextureUsage::RenderAttachment;
desc.dimension = wgpu::TextureDimension::e2D;
desc.sampleCount = 1;
desc.mipLevelCount = 1;
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"TestLabel"},
kNullSurfaceHandle);
wgpu::TextureFormat view_formats = wgpu::TextureFormat::R8Unorm;
desc.viewFormats = &view_formats;
desc.viewFormatCount = 1;
// AssociateMailbox may cause validation errors, given the invalid
// viewFormats, so wrap it in an error scope.
device_.PushErrorScope(wgpu::ErrorFilter::Validation);
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), sii->GenVerifiedSyncToken(), device_, desc, /*usage=*/0,
webgpu::WEBGPU_MAILBOX_NONE);
device_.PopErrorScope(
wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::PopErrorScopeStatus, wgpu::ErrorType, wgpu::StringView) {});
// DissociateMailbox should NOT cause validation errors.
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
}
// 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(WebGPUMailboxTextureTest, AssociateDissociateMailboxWhenNotCurrent) {
// Create the shared image
SharedImageInterface* sii = GetSharedImageInterface();
scoped_refptr<gpu::ClientSharedImage> shared_image =
sii->CreateSharedImage({GetParam().format,
{1, 1},
gfx::ColorSpace::CreateSRGB(),
GetSharedImageUsage(AccessType::ReadWrite),
"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();
};
// Create a GL context and make it current.
CreateAndMakeGLContextCurrent(&gl_context1, &gl_surface1);
wgpu::TextureDescriptor desc = {
.usage = wgpu::TextureUsage::RenderAttachment,
};
std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access =
shared_image->BeginWebGPUTextureAccess(
webgpu(), GetSharedImageInterface()->GenVerifiedSyncToken(), device_,
desc, /*usage=*/0, webgpu::WEBGPU_MAILBOX_NONE);
// Clear the texture using a render pass.
wgpu::RenderPassColorAttachment color_desc = {};
color_desc.view = webgpu_scoped_access->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);
WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access));
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(
,
WebGPUMailboxTextureTest,
::testing::ValuesIn(WebGPUMailboxTextureTest::TestParams()),
::testing::PrintToStringParamName());
class WebGPUMailboxBufferTest : public WebGPUMailboxTestBase {
public:
void SetUp() override {
SKIP_TEST_IF(!WebGPUSupported());
SKIP_TEST_IF(!WebGPUSharedImageSupported());
WebGPUTest::SetUp();
WebGPUTest::Options options = {};
options.enable_unsafe_webgpu = true;
Initialize(options);
wgpu::AdapterInfo info;
adapter_.GetInfo(&info);
// Buffer-backed SharedImages are only supported on D3D12 right now.
SKIP_TEST_IF(info.backendType != wgpu::BackendType::D3D12);
device_ = GetNewDevice(kRequiredFeatures);
mock_buffer_map_callback =
std::make_unique<testing::StrictMock<MockBufferMapCallback>>();
SharedImageInterface* sii = GetSharedImageInterface();
shared_image_ = sii->CreateSharedImage(
{viz::SharedImageFormat(),
{kBufferSize, 1},
gfx::ColorSpace(),
kTopLeft_GrSurfaceOrigin,
kUnknown_SkAlphaType,
gpu::SHARED_IMAGE_USAGE_WEBGPU_READ |
gpu::SHARED_IMAGE_USAGE_WEBGPU_WRITE |
gpu::SHARED_IMAGE_USAGE_WEBGPU_SHARED_BUFFER,
"TestLabel"},
kNullSurfaceHandle);
}
void TearDown() override {
mock_buffer_map_callback = nullptr;
// Wait for all operations to catch any validation or device lost errors.
PollUntilIdle();
device_ = nullptr;
WebGPUTest::TearDown();
}
struct AssociateMailboxForBufferCmdStorage {
webgpu::cmds::AssociateMailboxForBufferImmediate cmd;
// Immediate data is copied into the space immediately following `cmd`.
// Allocate space to hold 1 mailbox.
GLbyte data[GL_MAILBOX_SIZE_CHROMIUM];
};
protected:
std::vector<wgpu::FeatureName> kRequiredFeatures = {
wgpu::FeatureName::SharedBufferMemoryD3D12Resource};
const int kBufferSize = 4;
const uint32_t kBufferData = 0x12345678;
wgpu::Device device_;
scoped_refptr<gpu::ClientSharedImage> shared_image_;
};
// Test that AssociateMailboxForBuffer works as expected.
TEST_F(WebGPUMailboxBufferTest, AssociateMailboxForBufferCmd) {
webgpu::ReservedBuffer reservation = webgpu()->ReserveBuffer(device_.Get());
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder, webgpu::ReservedBuffer reservation,
scoped_refptr<gpu::ClientSharedImage> shared_image) {
const gpu::Mailbox& mailbox = shared_image->mailbox();
// Error case: device client id doesn't exist.
{
AssociateMailboxForBufferCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId + 1, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUBufferUsage_Storage,
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(
error::kInvalidArguments,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name)));
}
// Error case: device generation is invalid.
{
AssociateMailboxForBufferCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration + 1,
reservation.id, reservation.generation,
WGPUBufferUsage_Storage,
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(
error::kInvalidArguments,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name)));
}
// Error case: buffer ID invalid for the wire server.
{
AssociateMailboxForBufferCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id + 1, reservation.generation,
WGPUBufferUsage_Storage,
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(
error::kInvalidArguments,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name)));
}
// Error case: packed data empty.
{
AssociateMailboxForBufferCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUBufferUsage_Storage,
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(error::kOutOfBounds,
ExecuteImmediateCmd(decoder, cmd.cmd, 0u));
}
// Control case: test a successful call to AssociateMailboxForBuffer.
// 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
// buffer ID and generation invalid.
{
AssociateMailboxForBufferCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUBufferUsage_Storage,
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(decoder, cmd.cmd,
sizeof(mailbox.name)));
}
// Error case: associated to an already associated buffer.
{
AssociateMailboxForBufferCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUBufferUsage_Storage,
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(
error::kInvalidArguments,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name)));
}
// Dissociate the buffer from the control case to remove its reference.
{
webgpu::cmds::DissociateMailboxForBuffer cmd;
cmd.Init(reservation.id, reservation.generation);
EXPECT_EQ(error::kNoError, ExecuteCmd(decoder, cmd));
}
},
GetDecoder(), reservation, std::move(shared_image_)));
GetGpuServiceHolder()
->gpu_main_thread_task_runner()
->RunsTasksInCurrentSequence();
}
// Test that AssociateMailboxForBuffer with a bad mailbox produces an error
// buffer.
TEST_F(WebGPUMailboxBufferTest,
AssociateMailboxForBufferCmdBadMailboxMakesErrorBuffer) {
webgpu::ReservedBuffer reservation = webgpu()->ReserveBuffer(device_.Get());
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder, webgpu::ReservedBuffer reservation,
scoped_refptr<gpu::ClientSharedImage> shared_image) {
// Calling AssociateMailboxForBuffer with an invalid Mailbox should
// return an error buffer.
{
gpu::Mailbox bad_mailbox;
AssociateMailboxForBufferCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUBufferUsage_CopyDst,
reinterpret_cast<const GLuint*>(&bad_mailbox.name));
EXPECT_EQ(
error::kNoError,
ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(bad_mailbox.name)));
}
},
GetDecoder(), reservation, std::move(shared_image_)));
wgpu::Buffer buffer = wgpu::Buffer::Acquire(reservation.buffer);
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
encoder.ClearBuffer(buffer, 0, kBufferSize);
// Expect an error when finishing encoding because the buffer is an error.
EXPECT_WEBGPU_ERROR(device_, wgpu::ErrorType::Validation,
wgpu::CommandBuffer commands = encoder.Finish());
}
// Test that DissociateMailboxForBuffer works as expected.
TEST_F(WebGPUMailboxBufferTest, DissociateMailboxForBufferCmd) {
webgpu::ReservedBuffer reservation = webgpu()->ReserveBuffer(device_.Get());
GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder, webgpu::ReservedBuffer reservation,
scoped_refptr<gpu::ClientSharedImage> shared_image) {
const gpu::Mailbox& mailbox = shared_image->mailbox();
// Associate a mailbox so we can later dissociate it.
{
AssociateMailboxForBufferCmdStorage cmd;
cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
WGPUBufferUsage_Storage,
reinterpret_cast<const GLuint*>(&mailbox.name));
EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(decoder, cmd.cmd,
sizeof(mailbox.name)));
}
// Error case: wrong buffer ID
{
webgpu::cmds::DissociateMailboxForBuffer cmd;
cmd.Init(reservation.id + 1, reservation.generation);
EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(decoder, cmd));
}
// Error case: wrong buffer generation
{
webgpu::cmds::DissociateMailboxForBuffer cmd;
cmd.Init(reservation.id, reservation.generation + 1);
EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(decoder, cmd));
}
// Success case
{
webgpu::cmds::DissociateMailboxForBuffer cmd;
cmd.Init(reservation.id, reservation.generation);
EXPECT_EQ(error::kNoError, ExecuteCmd(decoder, cmd));
}
// Error case: dissociate an already dissociated mailbox
{
webgpu::cmds::DissociateMailboxForBuffer cmd;
cmd.Init(reservation.id, reservation.generation);
EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(decoder, cmd));
}
},
GetDecoder(), reservation, std::move(shared_image_)));
GetGpuServiceHolder()
->gpu_main_thread_task_runner()
->RunsTasksInCurrentSequence();
}
// Tests using Associate/DissociateMailbox to share a buffer with Dawn.
TEST_F(WebGPUMailboxBufferTest, WriteToMailboxThenReadFromIt) {
webgpu::ReservedBuffer reservation = webgpu()->ReserveBuffer(device_.Get());
SharedImageInterface* sii = GetSharedImageInterface();
SyncToken mailbox_produced_token = sii->GenVerifiedSyncToken();
webgpu()->WaitSyncTokenCHROMIUM(mailbox_produced_token.GetConstData());
webgpu()->AssociateMailboxForBuffer(
reservation.deviceId, reservation.deviceGeneration, reservation.id,
reservation.generation,
WGPUBufferUsage_Storage | WGPUBufferUsage_CopySrc |
WGPUBufferUsage_CopyDst,
shared_image_->mailbox());
wgpu::Buffer mailbox_buffer = wgpu::Buffer::Acquire(reservation.buffer);
// Create an buffer and write data to it.
wgpu::BufferDescriptor data_buffer_desc;
data_buffer_desc.size = kBufferSize;
data_buffer_desc.usage =
wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
wgpu::Buffer data_buffer = device_.CreateBuffer(&data_buffer_desc);
wgpu::Queue queue = device_.GetQueue();
queue.WriteBuffer(data_buffer, 0, &kBufferData, kBufferSize);
// Create a readback buffer to store result data.
wgpu::BufferDescriptor readback_buffer_desc;
readback_buffer_desc.size = kBufferSize;
readback_buffer_desc.usage =
wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
wgpu::Buffer readback_buffer = device_.CreateBuffer(&readback_buffer_desc);
// Copy data from the upload buffer to the mailbox buffer, then from the
// mailbox buffer to the readback buffer.
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
encoder.CopyBufferToBuffer(data_buffer, 0, mailbox_buffer, 0, kBufferSize);
encoder.CopyBufferToBuffer(mailbox_buffer, 0, readback_buffer, 0,
kBufferSize);
wgpu::CommandBuffer commands = encoder.Finish();
queue = device_.GetQueue();
queue.Submit(1, &commands);
webgpu()->DissociateMailboxForBuffer(reservation.id, reservation.generation);
// Map the readback buffer and check that it contains the correct value.
readback_buffer.MapAsync(wgpu::MapMode::Read, 0, 4,
wgpu::CallbackMode::AllowSpontaneous,
ToMockBufferMapCallback);
EXPECT_CALL(*mock_buffer_map_callback,
Call(wgpu::MapAsyncStatus::Success, testing::_))
.Times(1);
WaitForCompletion(device_);
const void* readback_data = readback_buffer.GetConstMappedRange();
EXPECT_EQ(kBufferData, *static_cast<const uint32_t*>(readback_data));
}
// Tests that using a mailbox buffer after it is dissociated produces an
// error.
TEST_F(WebGPUMailboxBufferTest, ErrorWhenUsingBufferAfterDissociate) {
webgpu::ReservedBuffer reservation = webgpu()->ReserveBuffer(device_.Get());
SharedImageInterface* sii = GetSharedImageInterface();
SyncToken mailbox_produced_token = sii->GenVerifiedSyncToken();
webgpu()->WaitSyncTokenCHROMIUM(mailbox_produced_token.GetConstData());
wgpu::Buffer mailbox_buffer = wgpu::Buffer::Acquire(reservation.buffer);
webgpu()->AssociateMailboxForBuffer(
reservation.deviceId, reservation.deviceGeneration, reservation.id,
reservation.generation,
WGPUBufferUsage_Storage | WGPUBufferUsage_CopySrc |
WGPUBufferUsage_CopyDst,
shared_image_->mailbox());
webgpu()->DissociateMailboxForBuffer(reservation.id, reservation.generation);
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
encoder.ClearBuffer(mailbox_buffer, 0, kBufferSize);
wgpu::CommandBuffer commands = encoder.Finish();
// Wait so it's clear the validation error after this when we call Submit.
WaitForCompletion(device_);
EXPECT_WEBGPU_ERROR(device_, wgpu::ErrorType::Validation,
device_.GetQueue().Submit(1, &commands));
WaitForCompletion(device_);
}
} // namespace gpu