910e62b5创建于 1月15日历史提交
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/shared_image/dawn_copy_strategy.h"

#include <dawn/native/DawnNative.h>

#include "base/bits.h"
#include "base/notreached.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/command_buffer/service/shared_image/dawn_image_backing.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
#include "gpu/command_buffer/service/shared_image/wrapped_graphite_texture_backing.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "ui/gfx/geometry/skia_conversions.h"

namespace gpu {

namespace {

struct StagingBuffer {
  wgpu::Buffer buffer;
  gfx::Size plane_size;
  uint32_t bytes_per_row;
};

bool ComputeStagingBufferParams(const viz::SharedImageFormat& format,
                                const gfx::Size& size,
                                int plane_index,
                                uint32_t* bytes_per_row,
                                size_t* bytes_per_plane) {
  DCHECK(bytes_per_row);
  DCHECK(bytes_per_plane);

  std::optional<size_t> min_bytes_per_row(format.MaybeEstimatedPlaneSizeInBytes(
      plane_index, gfx::Size(size.width(), 1)));

  if (!min_bytes_per_row.has_value()) {
    return false;
  }

  // Align up to 256, required by WebGPU buffer->texture and texture->buffer
  // copies.
  base::CheckedNumeric<uint32_t> aligned_bytes_per_row =
      base::bits::AlignUp(*min_bytes_per_row, size_t{256});
  if (!aligned_bytes_per_row.AssignIfValid(bytes_per_row)) {
    return false;
  }
  if (*bytes_per_row < *min_bytes_per_row) {
    // Overflow in AlignUp.
    return false;
  }

  const gfx::Size plane_size = format.GetPlaneSize(plane_index, size);

  base::CheckedNumeric<size_t> aligned_bytes_per_plane = aligned_bytes_per_row;
  aligned_bytes_per_plane *= plane_size.height();

  return aligned_bytes_per_plane.AssignIfValid(bytes_per_plane);
}

// Allocate staging buffers. One staging buffer per plane.
bool AllocateStagingBuffers(wgpu::Device device,
                            const viz::SharedImageFormat& format,
                            const gfx::Size& size,
                            wgpu::BufferUsage usage,
                            bool map_at_creation,
                            std::vector<StagingBuffer>* buffers) {
  std::vector<StagingBuffer> staging_buffers;
  for (int plane_index = 0; plane_index < format.NumberOfPlanes();
       ++plane_index) {
    uint32_t bytes_per_row;
    size_t bytes_per_plane;
    if (!ComputeStagingBufferParams(format, size, plane_index, &bytes_per_row,
                                    &bytes_per_plane)) {
      return false;
    }

    // Create a staging buffer to hold pixel data which will be uploaded into
    // a texture.
    wgpu::BufferDescriptor buffer_desc = {
        .usage = usage,
        .size = bytes_per_plane,
        .mappedAtCreation = map_at_creation,
    };

    wgpu::Buffer buffer = device.CreateBuffer(&buffer_desc);

    const gfx::Size plane_size = format.GetPlaneSize(plane_index, size);

    staging_buffers.push_back({buffer, plane_size, bytes_per_row});
  }

  *buffers = std::move(staging_buffers);

  return true;
}

SkPixmap MappedStagingBufferToPixmap(const StagingBuffer& staging_buffer,
                                     const viz::SharedImageFormat& format,
                                     SkAlphaType alpha_type,
                                     const sk_sp<SkColorSpace>& color_space,
                                     int plane_index,
                                     bool writable) {
  const void* pixels_pointer =
      writable
          ? staging_buffer.buffer.GetMappedRange(0, wgpu::kWholeMapSize)
          : staging_buffer.buffer.GetConstMappedRange(0, wgpu::kWholeMapSize);

  DCHECK(pixels_pointer);

  auto info = SkImageInfo::Make(gfx::SizeToSkISize(staging_buffer.plane_size),
                                viz::ToClosestSkColorType(format, plane_index),
                                alpha_type, color_space);
  return SkPixmap(info, pixels_pointer, staging_buffer.bytes_per_row);
}

bool IsNonDawnGpuBacking(SharedImageBackingType type) {
  return type != SharedImageBackingType::kDawn &&
         type != SharedImageBackingType::kSharedMemory;
}

}  // namespace

DawnCopyStrategy::DawnCopyStrategy() = default;
DawnCopyStrategy::~DawnCopyStrategy() = default;

bool DawnCopyStrategy::CanCopy(SharedImageBacking* src_backing,
                               SharedImageBacking* dst_backing) {
  bool src_is_dawn = src_backing->GetType() == SharedImageBackingType::kDawn;
  bool dst_is_dawn = dst_backing->GetType() == SharedImageBackingType::kDawn;
  bool src_is_gpu = IsNonDawnGpuBacking(src_backing->GetType());
  bool dst_is_gpu = IsNonDawnGpuBacking(dst_backing->GetType());

  auto ret = (src_is_gpu && dst_is_dawn) || (src_is_dawn && dst_is_gpu);
  return ret;
}

bool DawnCopyStrategy::Copy(SharedImageBacking* src_backing,
                            SharedImageBacking* dst_backing) {
  if (IsNonDawnGpuBacking(src_backing->GetType()) &&
      dst_backing->GetType() == SharedImageBackingType::kDawn) {
    return CopyFromGpuBackingToDawn(
        src_backing, static_cast<DawnImageBacking*>(dst_backing));
  }

  CHECK(src_backing->GetType() == SharedImageBackingType::kDawn &&
        IsNonDawnGpuBacking(dst_backing->GetType()));
  return CopyFromDawnToGpuBacking(static_cast<DawnImageBacking*>(src_backing),
                                  dst_backing);
}

bool DawnCopyStrategy::CopyFromGpuBackingToDawn(SharedImageBacking* src,
                                                DawnImageBacking* dst) {
  wgpu::Device device = dst->device();
  wgpu::Texture texture = dst->GetTexture();
  if (!device || !texture) {
    LOG(ERROR) << "wgpu device or texture not present.";
    return false;
  }

  return CopyFromBackingToTexture(src, texture, device);
}

bool DawnCopyStrategy::CopyFromDawnToGpuBacking(DawnImageBacking* src,
                                                SharedImageBacking* dst) {
  wgpu::Device device = src->device();
  wgpu::Texture texture = src->GetTexture();
  if (!device || !texture) {
    LOG(ERROR) << "wgpu device or texture not present.";
    return false;
  }

  return CopyFromTextureToBacking(texture, dst, device);
}

// static
bool DawnCopyStrategy::CopyFromBackingToTexture(SharedImageBacking* src_backing,
                                                wgpu::Texture dst_texture,
                                                wgpu::Device device) {
  wgpu::DawnEncoderInternalUsageDescriptor internal_usage_desc;
  internal_usage_desc.useInternalUsages = true;
  wgpu::CommandEncoderDescriptor command_encoder_desc = {
      .nextInChain = &internal_usage_desc,
      .label = "DawnCopyStrategy::CopyFromBackingToTexture",
  };

  wgpu::CommandEncoder encoder =
      device.CreateCommandEncoder(&command_encoder_desc);

  const viz::SharedImageFormat format = src_backing->format();

  // Allocate staging buffers. One staging buffer per plane.
  std::vector<StagingBuffer> staging_buffers;
  if (!AllocateStagingBuffers(device, format, src_backing->size(),
                              wgpu::BufferUsage::CopySrc,
                              /*map_at_creation=*/true, &staging_buffers)) {
    LOG(ERROR) << "Failed to allocate staging buffers.";
    return false;
  }

  CHECK_EQ(static_cast<size_t>(format.NumberOfPlanes()),
           staging_buffers.size());

  std::vector<SkPixmap> staging_pixmaps;
  for (int plane_index = 0; plane_index < format.NumberOfPlanes();
       ++plane_index) {
    staging_pixmaps.push_back(MappedStagingBufferToPixmap(
        staging_buffers[plane_index], format, src_backing->alpha_type(),
        src_backing->color_space().ToSkColorSpace(), plane_index,
        /*writable=*/true));
  }

  // Read data from backing to the staging buffers
  if (!src_backing->ReadbackToMemory(staging_pixmaps)) {
    LOG(ERROR) << "Failed to read back from backing to memory.";
    return false;
  }

  // Copy the staging buffers to texture.
  for (int plane_index = 0; plane_index < format.NumberOfPlanes();
       ++plane_index) {
    const auto& staging_buffer_entry = staging_buffers[plane_index];
    wgpu::Buffer buffer = staging_buffer_entry.buffer;
    uint32_t bytes_per_row = staging_buffer_entry.bytes_per_row;
    const auto& plane_size = staging_buffer_entry.plane_size;

    // Unmap the buffer.
    buffer.Unmap();

    wgpu::TexelCopyBufferInfo buffer_copy = {
        .layout =
            {
                .bytesPerRow = bytes_per_row,
                .rowsPerImage = wgpu::kCopyStrideUndefined,
            },
        .buffer = buffer.Get(),
    };
    wgpu::TextureFormat wgpu_format = dst_texture.GetFormat();
    bool is_yuv_plane =
        (wgpu_format == wgpu::TextureFormat::R8BG8Biplanar420Unorm ||
         wgpu_format == wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm ||
         wgpu_format == wgpu::TextureFormat::R8BG8A8Triplanar420Unorm);
    // Get proper plane aspect for multiplanar textures.
    wgpu::TexelCopyTextureInfo texture_copy = {
        .texture = dst_texture,
        .aspect = ToDawnTextureAspect(is_yuv_plane, plane_index),
    };
    wgpu::Extent3D extent = {static_cast<uint32_t>(plane_size.width()),
                             static_cast<uint32_t>(plane_size.height()), 1};
    encoder.CopyBufferToTexture(&buffer_copy, &texture_copy, &extent);
  }

  wgpu::CommandBuffer commandBuffer = encoder.Finish();

  wgpu::Queue queue = device.GetQueue();
  queue.Submit(1, &commandBuffer);

  return true;
}

// static
bool DawnCopyStrategy::CopyFromTextureToBacking(wgpu::Texture src_texture,
                                                SharedImageBacking* dst_backing,
                                                wgpu::Device device) {
  wgpu::DawnEncoderInternalUsageDescriptor internal_usage_desc;
  internal_usage_desc.useInternalUsages = true;
  wgpu::CommandEncoderDescriptor command_encoder_desc = {
      .nextInChain = &internal_usage_desc,
      .label = "DawnCopyStrategy::CopyFromTextureToBacking",
  };

  wgpu::CommandEncoder encoder =
      device.CreateCommandEncoder(&command_encoder_desc);

  const viz::SharedImageFormat format = dst_backing->format();

  // Allocate staging buffers. One staging buffer per plane.
  std::vector<StagingBuffer> staging_buffers;
  if (!AllocateStagingBuffers(
          device, format, dst_backing->size(),
          wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead,
          /*map_at_creation=*/false, &staging_buffers)) {
    LOG(ERROR) << "Failed to allocate staging buffers.";
    return false;
  }

  CHECK_EQ(static_cast<size_t>(format.NumberOfPlanes()),
           staging_buffers.size());

  // Copy from texture to staging buffers.
  for (int plane_index = 0; plane_index < format.NumberOfPlanes();
       ++plane_index) {
    const auto& staging_buffer_entry = staging_buffers[plane_index];
    wgpu::Buffer buffer = staging_buffer_entry.buffer;
    uint32_t bytes_per_row = staging_buffer_entry.bytes_per_row;
    const auto& plane_size = staging_buffer_entry.plane_size;

    wgpu::TextureFormat wgpu_format = src_texture.GetFormat();
    bool is_yuv_plane =
        (wgpu_format == wgpu::TextureFormat::R8BG8Biplanar420Unorm ||
         wgpu_format == wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm ||
         wgpu_format == wgpu::TextureFormat::R8BG8A8Triplanar420Unorm);
    // Get proper plane aspect for multiplanar textures.
    wgpu::TexelCopyTextureInfo texture_copy = {
        .texture = src_texture,
        .aspect = ToDawnTextureAspect(is_yuv_plane, plane_index),
    };
    wgpu::TexelCopyBufferInfo buffer_copy = {
        .layout =
            {
                .bytesPerRow = bytes_per_row,
                .rowsPerImage = wgpu::kCopyStrideUndefined,
            },
        .buffer = buffer,
    };
    wgpu::Extent3D extent = {static_cast<uint32_t>(plane_size.width()),
                             static_cast<uint32_t>(plane_size.height()), 1};

    encoder.CopyTextureToBuffer(&texture_copy, &buffer_copy, &extent);
  }

  wgpu::CommandBuffer commandBuffer = encoder.Finish();

  wgpu::Queue queue = device.GetQueue();
  queue.Submit(1, &commandBuffer);

  // Map the staging buffer for read.
  std::vector<SkPixmap> staging_pixmaps;
  for (int plane_index = 0;
       plane_index < static_cast<int>(staging_buffers.size()); ++plane_index) {
    const auto& staging_buffer_entry = staging_buffers[plane_index];

    bool success = false;
    wgpu::FutureWaitInfo wait_info = {staging_buffer_entry.buffer.MapAsync(
        wgpu::MapMode::Read, 0, wgpu::kWholeMapSize,
        wgpu::CallbackMode::WaitAnyOnly,
        [](wgpu::MapAsyncStatus status, wgpu::StringView, bool* success) {
          *success = status == wgpu::MapAsyncStatus::Success;
        },
        &success)};

    if (device.GetAdapter().GetInstance().WaitAny(1, &wait_info, UINT64_MAX) !=
        wgpu::WaitStatus::Success) {
      LOG(ERROR) << "WaitAny failed while mapping staging buffer for read.";
      return false;
    }

    if (!wait_info.completed || !success) {
      LOG(ERROR) << "MapAsync did not yield a readable mapping.";
      return false;
    }

    staging_pixmaps.push_back(MappedStagingBufferToPixmap(
        staging_buffers[plane_index], format, dst_backing->alpha_type(),
        dst_backing->color_space().ToSkColorSpace(), plane_index,
        /*writable=*/false));
  }

  return dst_backing->UploadFromMemory(staging_pixmaps);
}

}  // namespace gpu