| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #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/tests/webgpu_test.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/color_space.h" |
| |
| using namespace testing; |
| |
| namespace gpu { |
| namespace { |
| |
| class MockBufferMapReadCallback { |
| public: |
| MOCK_METHOD4(Call, |
| void(DawnBufferMapAsyncStatus status, |
| const uint32_t* ptr, |
| uint64_t data_length, |
| void* userdata)); |
| }; |
| |
| std::unique_ptr<StrictMock<MockBufferMapReadCallback>> |
| mock_buffer_map_read_callback; |
| void ToMockBufferMapReadCallback(DawnBufferMapAsyncStatus status, |
| const void* ptr, |
| uint64_t data_length, |
| void* userdata) { |
| // Assume the data is uint32_t |
| mock_buffer_map_read_callback->Call(status, static_cast<const uint32_t*>(ptr), |
| data_length, userdata); |
| } |
| |
| class MockUncapturedErrorCallback { |
| public: |
| MOCK_METHOD3(Call, |
| void(DawnErrorType type, const char* message, void* userdata)); |
| }; |
| |
| std::unique_ptr<StrictMock<MockUncapturedErrorCallback>> |
| mock_device_error_callback; |
| void ToMockUncapturedErrorCallback(DawnErrorType type, |
| const char* message, |
| void* userdata) { |
| mock_device_error_callback->Call(type, message, userdata); |
| } |
| |
| } // namespace |
| |
| class WebGPUMailboxTest : public WebGPUTest { |
| protected: |
| void SetUp() override { |
| WebGPUTest::SetUp(); |
| Initialize(WebGPUTest::Options()); |
| mock_buffer_map_read_callback = |
| std::make_unique<StrictMock<MockBufferMapReadCallback>>(); |
| mock_device_error_callback = |
| std::make_unique<StrictMock<MockUncapturedErrorCallback>>(); |
| } |
| |
| void TearDown() override { |
| mock_buffer_map_read_callback = nullptr; |
| mock_device_error_callback = nullptr; |
| WebGPUTest::TearDown(); |
| } |
| }; |
| |
| // 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_F(WebGPUMailboxTest, WriteToMailboxThenReadFromIt) { |
| if (!WebGPUSupported()) { |
| LOG(ERROR) << "Test skipped because WebGPU isn't supported"; |
| return; |
| } |
| if (!WebGPUSharedImageSupported()) { |
| LOG(ERROR) << "Test skipped because WebGPUSharedImage isn't supported"; |
| return; |
| } |
| |
| // Create a the shared image |
| SharedImageInterface* sii = GetSharedImageInterface(); |
| Mailbox mailbox = sii->CreateSharedImage( |
| viz::ResourceFormat::RGBA_8888, {1, 1}, gfx::ColorSpace::CreateSRGB(), |
| SHARED_IMAGE_USAGE_WEBGPU); |
| SyncToken mailbox_produced_token = sii->GenVerifiedSyncToken(); |
| webgpu()->WaitSyncTokenCHROMIUM(mailbox_produced_token.GetConstData()); |
| |
| dawn::Device device = dawn::Device::Acquire(webgpu()->GetDefaultDevice()); |
| |
| // Part 1: Write to the texture using Dawn |
| { |
| // Register the shared image as a Dawn texture in the wire. |
| gpu::webgpu::ReservedTexture reservation = |
| webgpu()->ReserveTexture(device.Get()); |
| |
| webgpu()->AssociateMailbox(0, 0, reservation.id, reservation.generation, |
| DAWN_TEXTURE_USAGE_OUTPUT_ATTACHMENT, |
| reinterpret_cast<GLbyte*>(&mailbox)); |
| dawn::Texture texture = dawn::Texture::Acquire(reservation.texture); |
| |
| // Clear the texture using a render pass. |
| dawn::RenderPassColorAttachmentDescriptor color_desc; |
| color_desc.attachment = texture.CreateView(); |
| color_desc.resolveTarget = nullptr; |
| color_desc.loadOp = dawn::LoadOp::Clear; |
| color_desc.storeOp = dawn::StoreOp::Store; |
| color_desc.clearColor = {0, 255, 0, 255}; |
| |
| dawn::RenderPassDescriptor render_pass_desc; |
| render_pass_desc.colorAttachmentCount = 1; |
| render_pass_desc.colorAttachments = &color_desc; |
| render_pass_desc.depthStencilAttachment = nullptr; |
| |
| dawn::CommandEncoder encoder = device.CreateCommandEncoder(); |
| dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&render_pass_desc); |
| pass.EndPass(); |
| dawn::CommandBuffer commands = encoder.Finish(); |
| |
| dawn::Queue queue = device.CreateQueue(); |
| queue.Submit(1, &commands); |
| |
| // Dissociate the mailbox, flushing previous commands first |
| webgpu()->FlushCommands(); |
| webgpu()->DissociateMailbox(reservation.id, reservation.generation); |
| } |
| |
| // Part 2: Read back the texture using Dawn |
| { |
| // Register the shared image as a Dawn texture in the wire. |
| gpu::webgpu::ReservedTexture reservation = |
| webgpu()->ReserveTexture(device.Get()); |
| |
| // Make sure previous Dawn wire commands are sent so that the texture IDs |
| // are validated correctly. |
| webgpu()->FlushCommands(); |
| |
| webgpu()->AssociateMailbox(0, 0, reservation.id, reservation.generation, |
| DAWN_TEXTURE_USAGE_COPY_SRC, |
| reinterpret_cast<GLbyte*>(&mailbox)); |
| dawn::Texture texture = dawn::Texture::Acquire(reservation.texture); |
| |
| // Copy the texture in a mappable buffer. |
| dawn::BufferDescriptor buffer_desc; |
| buffer_desc.size = 4; |
| buffer_desc.usage = dawn::BufferUsage::MapRead | dawn::BufferUsage::CopyDst; |
| dawn::Buffer readback_buffer = device.CreateBuffer(&buffer_desc); |
| |
| dawn::TextureCopyView copy_src; |
| copy_src.texture = texture; |
| copy_src.mipLevel = 0; |
| copy_src.arrayLayer = 0; |
| copy_src.origin = {0, 0, 0}; |
| |
| dawn::BufferCopyView copy_dst; |
| copy_dst.buffer = readback_buffer; |
| copy_dst.offset = 0; |
| copy_dst.rowPitch = 256; |
| copy_dst.imageHeight = 0; |
| |
| dawn::Extent3D copy_size = {1, 1, 1}; |
| |
| dawn::CommandEncoder encoder = device.CreateCommandEncoder(); |
| encoder.CopyTextureToBuffer(©_src, ©_dst, ©_size); |
| dawn::CommandBuffer commands = encoder.Finish(); |
| |
| dawn::Queue queue = device.CreateQueue(); |
| queue.Submit(1, &commands); |
| |
| // Dissociate the mailbox, flushing previous commands first |
| webgpu()->FlushCommands(); |
| webgpu()->DissociateMailbox(reservation.id, reservation.generation); |
| |
| // Map the buffer and assert the pixel is the correct value. |
| readback_buffer.MapReadAsync(ToMockBufferMapReadCallback, 0); |
| uint32_t buffer_contents = 0xFF00FF00; |
| EXPECT_CALL(*mock_buffer_map_read_callback, |
| Call(DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS, |
| Pointee(Eq(buffer_contents)), sizeof(uint32_t), 0)) |
| .Times(1); |
| |
| WaitForCompletion(device); |
| } |
| } |
| |
| // Tests that using a shared image aftr it is dissociated produces an error. |
| TEST_F(WebGPUMailboxTest, ErrorWhenUsingTextureAfterDissociate) { |
| if (!WebGPUSupported()) { |
| LOG(ERROR) << "Test skipped because WebGPU isn't supported"; |
| return; |
| } |
| if (!WebGPUSharedImageSupported()) { |
| LOG(ERROR) << "Test skipped because WebGPUSharedImage isn't supported"; |
| return; |
| } |
| |
| // Create a the shared image |
| SharedImageInterface* sii = GetSharedImageInterface(); |
| Mailbox mailbox = sii->CreateSharedImage( |
| viz::ResourceFormat::RGBA_8888, {1, 1}, gfx::ColorSpace::CreateSRGB(), |
| SHARED_IMAGE_USAGE_WEBGPU); |
| SyncToken mailbox_produced_token = sii->GenVerifiedSyncToken(); |
| webgpu()->WaitSyncTokenCHROMIUM(mailbox_produced_token.GetConstData()); |
| |
| // Create the device, and expect a validation error. |
| dawn::Device device = dawn::Device::Acquire(webgpu()->GetDefaultDevice()); |
| device.SetUncapturedErrorCallback(ToMockUncapturedErrorCallback, 0); |
| |
| // Associate and immediately dissociate the image. |
| gpu::webgpu::ReservedTexture reservation = |
| webgpu()->ReserveTexture(device.Get()); |
| dawn::Texture texture = dawn::Texture::Acquire(reservation.texture); |
| |
| webgpu()->AssociateMailbox(0, 0, reservation.id, reservation.generation, |
| DAWN_TEXTURE_USAGE_OUTPUT_ATTACHMENT, |
| reinterpret_cast<GLbyte*>(&mailbox)); |
| webgpu()->DissociateMailbox(reservation.id, reservation.generation); |
| |
| // Try using the texture, it should produce a validation error. |
| dawn::TextureView view = texture.CreateView(); |
| EXPECT_CALL(*mock_device_error_callback, |
| Call(DAWN_ERROR_TYPE_VALIDATION, _, _)) |
| .Times(1); |
| WaitForCompletion(device); |
| } |
| |
| } // namespace gpu |