| // 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 "base/compiler_specific.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "base/test/bind.h" | |
| #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/graphite_shared_context.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(); | |
| { | |
| SCOPED_TRACE("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))); | |
| } | |
| { | |
| SCOPED_TRACE("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))); | |
| } | |
| { | |
| SCOPED_TRACE("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))); | |
| } | |
| // Prep packed data for packing view formats and the mailbox. | |
| std::vector<GLuint> packed_data; | |
| packed_data.resize(sizeof(mailbox.name) / sizeof(uint32_t)); | |
| UNSAFE_TODO(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(); | |
| } | |
| { | |
| SCOPED_TRACE("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)); | |
| } | |
| { | |
| SCOPED_TRACE("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())); | |
| } | |
| for (int adjustment : {-1, -2}) { | |
| SCOPED_TRACE(base::StringPrintf( | |
| "Error case: packed data size incorrect. adjustment=%d", | |
| adjustment)); | |
| 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())); | |
| } | |
| for (int adjustment : {-1, 1}) { | |
| SCOPED_TRACE(base::StringPrintf( | |
| "Error case: view_format_count incorrect. adjustment=%d", | |
| adjustment)); | |
| 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())); | |
| } | |
| { | |
| SCOPED_TRACE( | |
| "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())); | |
| } | |
| { | |
| SCOPED_TRACE( | |
| "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))); | |
| } | |
| { | |
| SCOPED_TRACE( | |
| "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(); | |
| } | |
| // Invalid texture usage is handled by making an error Texture. It shouldn't | |
| // fail decoding, or even produce a Dawn validation error. (The WebGPU CTS makes | |
| // sure the Web API sees a validation error, but it comes from elsewhere.) | |
| TEST_P(WebGPUMailboxTextureTest, AssociateMailboxCmdInvalidUsage) { | |
| struct UsageAndInternalUsage { | |
| uint64_t usage = 0, internal_usage = 0; | |
| }; | |
| constexpr uint64_t kInvalidUsage = 0x4000'0000; | |
| for (auto usage : { | |
| UsageAndInternalUsage{}, | |
| UsageAndInternalUsage{.usage = kInvalidUsage}, | |
| UsageAndInternalUsage{.internal_usage = kInvalidUsage}, | |
| }) { | |
| SCOPED_TRACE(base::StringPrintf("usage=%d internal_usage=%d", usage.usage, | |
| usage.internal_usage)); | |
| // Create the shared image | |
| SharedImageInterface* sii = GetSharedImageInterface(); | |
| scoped_refptr<gpu::ClientSharedImage> shared_image = | |
| sii->CreateSharedImage({GetParam().format, | |
| {1, 1}, | |
| gfx::ColorSpace::CreateSRGB(), | |
| GetSharedImageUsage(AccessType::Write), | |
| "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, | |
| UsageAndInternalUsage usage) { | |
| SCOPED_TRACE(base::StringPrintf("usage=%d internal_usage=%d", | |
| usage.usage, usage.internal_usage)); | |
| const gpu::Mailbox& mailbox = shared_image->mailbox(); | |
| { | |
| AssociateMailboxCmdStorage cmd; | |
| // Always include CopyDst to avoid triggering "uncleared texture" | |
| // checks. | |
| cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration, | |
| reservation.id, reservation.generation, | |
| usage.usage | WGPUTextureUsage_CopyDst, | |
| usage.internal_usage | WGPUTextureUsage_CopyDst, | |
| webgpu::WEBGPU_MAILBOX_NONE, 0u, | |
| ComputeNumEntries(sizeof(mailbox.name)), | |
| reinterpret_cast<const GLuint*>(&mailbox.name)); | |
| // Note in addition to no command buffer error, there should also | |
| // be no Dawn validation error as the error is caught before that. | |
| // If there is one, the test will fail cleanup. | |
| EXPECT_EQ( | |
| error::kNoError, | |
| 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), usage)); | |
| 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(©_src, ©_dst, ©_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. | |
| wgpu::FutureWaitInfo wait_info{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); | |
| WaitForFutureCompletion(device_, wait_info); | |
| 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 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(©_src, ©_dst, ©_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(©_src, ©_dst, ©_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(©_src, ©_dst, ©_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. | |
| wgpu::FutureWaitInfo wait_info{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); | |
| WaitForFutureCompletion(device_, wait_info); | |
| 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) { | |
| UNSAFE_TODO(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(©_src, ©_dst, ©_size); | |
| commands = encoder2.Finish(); | |
| queue.Submit(1, &commands); | |
| WebGPUTextureScopedAccess::EndAccess(std::move(webgpu_scoped_access)); | |
| // Map the buffer. | |
| wgpu::FutureWaitInfo wait_info2{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); | |
| WaitForFutureCompletion(device_, wait_info2); | |
| } | |
| // 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(©_src, ©_dst, ©_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. | |
| wgpu::FutureWaitInfo wait_info{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); | |
| WaitForFutureCompletion(device_, wait_info); | |
| 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) { | |
| UNSAFE_TODO(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(©_src, ©_dst, ©_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. | |
| wgpu::FutureWaitInfo wait_info{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); | |
| WaitForFutureCompletion(device_, wait_info); | |
| 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) { | |
| UNSAFE_TODO(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))); | |
| } | |
| // Test that a SharedImage that is presented while still uninitialized stays | |
| // reported as uncleared if the Skia clear could not be inserted into the | |
| // Graphite context, so subsequent reads see lazy-cleared contents rather than | |
| // stale data. | |
| TEST_P(WebGPUMailboxTextureTest, | |
| PresentClearLeavesSharedImageUnclearedOnInsertFailure) { | |
| auto* graphite_shared_context = | |
| GetGpuServiceHolder()->GetGraphiteSharedContext(); | |
| SKIP_TEST_IF(!graphite_shared_context); | |
| // Create the shared image and fill it with a non-zero sentinel. | |
| SharedImageInterface* sii = GetSharedImageInterface(); | |
| scoped_refptr<gpu::ClientSharedImage> shared_image = | |
| sii->CreateSharedImage({GetParam().format, | |
| {1, 1}, | |
| gfx::ColorSpace::CreateSRGB(), | |
| GetSharedImageUsage(AccessType::ReadWrite), | |
| "TestLabel"}, | |
| kNullSurfaceHandle); | |
| InitializeTextureColor(device_, shared_image, {1.0, 0, 0, 1.0}); | |
| WaitForCompletion(device_); | |
| // Simulate a recoverable Graphite insertRecording() failure so any recorded | |
| // work is dropped without losing the Skia context. | |
| graphite_shared_context->set_simulated_insert_status( | |
| skgpu::graphite::InsertStatus::kPromiseImageInstantiationFailed); | |
| // Associate with DISCARD so the texture is treated as uninitialized, then | |
| // present without writing. The decoder will attempt to clear via Skia and | |
| // the recording insertion will be dropped. | |
| wgpu::TextureDescriptor desc = { | |
| .usage = wgpu::TextureUsage::RenderAttachment, | |
| }; | |
| std::unique_ptr<WebGPUTextureScopedAccess> webgpu_scoped_access = | |
| shared_image->BeginWebGPUTextureAccess(webgpu(), gpu::SyncToken(), | |
| device_, desc, /*usage=*/0, | |
| webgpu::WEBGPU_MAILBOX_DISCARD); | |
| webgpu_scoped_access->SetNeedsPresent(true); | |
| webgpu_impl()->SetLostContextCallback( | |
| base::MakeExpectedRunClosure(FROM_HERE)); | |
| EXPECT_WEBGPU_DEVICE_LOST(device_, WebGPUTextureScopedAccess::EndAccess( | |
| std::move(webgpu_scoped_access))); | |
| } | |
| 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); | |
| std::vector<wgpu::FeatureName> requiredFeatures; | |
| switch (info.backendType) { | |
| case wgpu::BackendType::D3D12: | |
| // On D3D12, SharedImage must be buffer-backed to get a wgpu::Buffer. | |
| use_shared_buffer_ = true; | |
| requiredFeatures.push_back( | |
| wgpu::FeatureName::SharedBufferMemoryD3D12Resource); | |
| break; | |
| case wgpu::BackendType::Metal: | |
| // On Metal, SharedImage is always texture-backed, but can still be | |
| // associated with a buffer (using buffer<->texture copies). | |
| use_shared_buffer_ = false; | |
| break; | |
| default: | |
| GTEST_SKIP(); | |
| } | |
| device_ = GetNewDevice(requiredFeatures); | |
| mock_buffer_map_callback = | |
| std::make_unique<testing::StrictMock<MockBufferMapCallback>>(); | |
| } | |
| scoped_refptr<gpu::ClientSharedImage> MakeSharedImage() { | |
| SharedImageInterface* sii = GetSharedImageInterface(); | |
| if (use_shared_buffer_) { | |
| return 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); | |
| } else { | |
| constexpr std::array<uint8_t, kBufferSize> kInitial{}; | |
| return sii->CreateSharedImage({viz::SinglePlaneFormat::kRGBA_8888, | |
| {1, 1}, | |
| gfx::ColorSpace(), | |
| kTopLeft_GrSurfaceOrigin, | |
| kUnknown_SkAlphaType, | |
| gpu::SHARED_IMAGE_USAGE_WEBGPU_READ | | |
| gpu::SHARED_IMAGE_USAGE_WEBGPU_WRITE, | |
| "TestLabel"}, | |
| kInitial); | |
| } | |
| } | |
| 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: | |
| static inline constexpr int kBufferSize = 4; | |
| const uint32_t kBufferData = 0x12345678; | |
| bool use_shared_buffer_; | |
| wgpu::Device device_; | |
| }; | |
| // 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, MakeSharedImage())); | |
| GetGpuServiceHolder() | |
| ->gpu_main_thread_task_runner() | |
| ->RunsTasksInCurrentSequence(); | |
| } | |
| // Test that AssociateMailboxForBuffer with a bad mailbox produces an error | |
| // buffer. | |
| TEST_F(WebGPUMailboxBufferTest, AssociateMailboxForBufferCmdBadMailbox) { | |
| webgpu::ReservedBuffer reservation = webgpu()->ReserveBuffer(device_.Get()); | |
| GetGpuServiceHolder()->ScheduleGpuMainTask(base::BindOnce( | |
| [](webgpu::WebGPUDecoder* decoder, webgpu::ReservedBuffer reservation, | |
| scoped_refptr<gpu::ClientSharedImage> shared_image) { | |
| 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, MakeSharedImage())); | |
| 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()); | |
| } | |
| // Calling AssociateMailboxForBuffer with an invalid usage (e.g. Uniform) | |
| // produces an error buffer. | |
| TEST_F(WebGPUMailboxBufferTest, AssociateMailboxForBufferCmdInvalidUsage) { | |
| 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(); | |
| AssociateMailboxForBufferCmdStorage cmd; | |
| cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration, | |
| reservation.id, reservation.generation, | |
| WGPUBufferUsage_Uniform, | |
| reinterpret_cast<const GLuint*>(&mailbox.name)); | |
| EXPECT_EQ(error::kNoError, | |
| ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(mailbox.name))); | |
| }, | |
| GetDecoder(), reservation, MakeSharedImage())); | |
| wgpu::Buffer buffer = wgpu::Buffer::Acquire(reservation.buffer); | |
| wgpu::CommandEncoder encoder = device_.CreateCommandEncoder(); | |
| encoder.ClearBuffer(buffer, 0, kBufferSize); | |
| EXPECT_WEBGPU_ERROR(device_, wgpu::ErrorType::Validation, | |
| wgpu::CommandBuffer commands = encoder.Finish()); | |
| } | |
| // Test that AssociateMailboxForBuffer without WEBGPU_READ isn't allowed. | |
| TEST_F(WebGPUMailboxBufferTest, AssociateMailboxForBufferCmdNoWebGPURead) { | |
| // Nothing to test with SHARED_BUFFER, because that requires WEBGPU_READ to | |
| // create the SI in the first place. | |
| SKIP_TEST_IF(use_shared_buffer_); | |
| constexpr std::array<uint8_t, kBufferSize> kInitial{}; | |
| SharedImageInterface* sii = GetSharedImageInterface(); | |
| auto shared_image = | |
| sii->CreateSharedImage({viz::SinglePlaneFormat::kRGBA_8888, | |
| {1, 1}, | |
| gfx::ColorSpace(), | |
| kTopLeft_GrSurfaceOrigin, | |
| kUnknown_SkAlphaType, | |
| gpu::SHARED_IMAGE_USAGE_WEBGPU_WRITE, | |
| "TestLabel"}, | |
| kInitial); | |
| 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(); | |
| AssociateMailboxForBufferCmdStorage cmd; | |
| cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration, | |
| reservation.id, reservation.generation, | |
| WGPUBufferUsage_CopyDst, | |
| reinterpret_cast<const GLuint*>(&mailbox.name)); | |
| EXPECT_EQ(error::kNoError, | |
| ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(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 AssociateMailboxForBuffer without WEBGPU_WRITE isn't allowed. | |
| TEST_F(WebGPUMailboxBufferTest, AssociateMailboxForBufferCmdNoWebGPUWrite) { | |
| // Nothing to test with SHARED_BUFFER, because that requires WEBGPU_WRITE to | |
| // create the SI in the first place. | |
| SKIP_TEST_IF(use_shared_buffer_); | |
| constexpr std::array<uint8_t, kBufferSize> kInitial{}; | |
| SharedImageInterface* sii = GetSharedImageInterface(); | |
| auto shared_image = | |
| sii->CreateSharedImage({viz::SinglePlaneFormat::kRGBA_8888, | |
| {1, 1}, | |
| gfx::ColorSpace(), | |
| kTopLeft_GrSurfaceOrigin, | |
| kUnknown_SkAlphaType, | |
| gpu::SHARED_IMAGE_USAGE_WEBGPU_READ, | |
| "TestLabel"}, | |
| kInitial); | |
| 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(); | |
| AssociateMailboxForBufferCmdStorage cmd; | |
| cmd.cmd.Init(reservation.deviceId, reservation.deviceGeneration, | |
| reservation.id, reservation.generation, | |
| WGPUBufferUsage_CopyDst, | |
| reinterpret_cast<const GLuint*>(&mailbox.name)); | |
| EXPECT_EQ(error::kNoError, | |
| ExecuteImmediateCmd(decoder, cmd.cmd, sizeof(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, MakeSharedImage())); | |
| 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()); | |
| auto shared_image = MakeSharedImage(); | |
| 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. | |
| wgpu::FutureWaitInfo wait_info{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); | |
| WaitForFutureCompletion(device_, wait_info); | |
| 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()); | |
| auto shared_image = MakeSharedImage(); | |
| 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 |