// 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 "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();

        {
          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(&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.
    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 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.
  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(&copy_src, &copy_dst, &copy_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(&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.
  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(&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.
  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)));
}

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.
  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());

  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
