// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/exo/buffer.h"

#include <stdint.h>

#include <algorithm>
#include <cstddef>
#include <string_view>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "build/build_config.h"
#include "components/exo/frame_sink_resource_manager.h"
#include "components/viz/common/gpu/context_lost_observer.h"
#include "components/viz/common/gpu/context_provider.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/common/resources/returned_resource.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/context_support.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/shared_image_capabilities.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "media/base/media_switches.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "ui/aura/env.h"
#include "ui/color/color_id.h"
#include "ui/compositor/compositor.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/gpu_fence_handle.h"
#include "ui/gfx/gpu_memory_buffer_handle.h"

#if BUILDFLAG(USE_ARC_PROTECTED_MEDIA)
#include "base/files/scoped_file.h"
#include "base/posix/eintr_wrapper.h"
#endif  // BUILDFLAG(USE_ARC_PROTECTED_MEDIA)

namespace exo {
namespace {

// When enabled, this feature allows ReleaseSharedImage() to EndExport and
// run callback immediately, skipping WaitSyncToken, and Begin/EndQuery
// operations. This kill switch is used to verify the assumption that these
// skipped operations are actually needed in this case.
BASE_FEATURE(kReleaseSharedImageImmediately, base::FEATURE_ENABLED_BY_DEFAULT);

// The amount of time before we wait for release queries using
// GetQueryObjectuivEXT(GL_QUERY_RESULT_EXT).
const int kWaitForReleaseDelayMs = 500;

constexpr char kBufferInUse[] = "BufferInUse";
const unsigned kDefaultQueryType = GL_COMMANDS_COMPLETED_CHROMIUM;
const bool kDefaultUseZeroCopy = true;
const bool kDefaultIsOverlayCandidate = false;
const bool kDefaultYInvert = false;
const viz::SharedImageFormat kDefaultFormat =
    viz::SinglePlaneFormat::kRGBA_8888;
const gfx::Size kDefaultSize = gfx::Size(0, 0);
const gfx::BufferUsage kDefaultBufferUsage = gfx::BufferUsage::GPU_READ;
// Default usage in order to create a mappable shared image and get a
// GpuMemoryBufferHandle from it.
const gpu::SharedImageUsageSet kDefaultMappableSIUsage =
    gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;

// Gets the color type of |format| for creating bitmap. If it returns
// SkColorType::kUnknown_SkColorType, it means with this format, this buffer
// contents should not be used to create bitmap.
SkColorType GetColorTypeForBitmapCreation(viz::SharedImageFormat format) {
  // Don't create bitmap for other formats.
  if (format == viz::SinglePlaneFormat::kRGBA_8888 ||
      format == viz::SinglePlaneFormat::kBGRA_8888) {
    return ToClosestSkColorType(format);
  }
  return SkColorType::kUnknown_SkColorType;
}

// Helper to create ClientSharedImage.
gpu::SharedImageInterface* GetSharedImageInterface() {
  ui::ContextFactory* context_factory =
      aura::Env::GetInstance()->context_factory();
  CHECK(context_factory);
  // Note : This can fail if GPU acceleration has been disabled.
  scoped_refptr<viz::RasterContextProvider> context_provider =
      context_factory->SharedMainThreadRasterContextProvider();
  if (!context_provider) {
    DLOG(ERROR) << "Failed to acquire a context provider";
    CHECK(context_provider);
    return nullptr;
  }
  return context_provider->SharedImageInterface();
}

perfetto::NamedTrack GetTrack(const void* buffer_id) {
  return perfetto::NamedTrack(kBufferInUse,
                              reinterpret_cast<uintptr_t>(buffer_id));
}

bool ValidateGpuMemoryBufferHandle(const gfx::GpuMemoryBufferHandle& handle,
                                   const viz::SharedImageFormat& format,
                                   const gfx::Size& size) {
  if (handle.type == gfx::SHARED_MEMORY_BUFFER) {
    const auto& region = handle.region();
    if (!region.IsValid()) {
      DLOG(ERROR) << "Invalid shared memory region.";
      return false;
    }
    auto required_size = format.MaybeEstimatedSizeInBytes(size);
    if (!required_size || region.GetSize() < *required_size) {
      DLOG(ERROR) << "Shared memory region is too small. Required: "
                  << (required_size ? *required_size : 0)
                  << ", actual: " << region.GetSize();
      return false;
    }
    if (handle.stride > 0 && size.height() > 0 &&
        static_cast<size_t>(handle.stride) *
                static_cast<size_t>(size.height()) >
            region.GetSize()) {
      DLOG(ERROR) << "Shared memory region is too small for stride. Required: "
                  << handle.stride * size.height()
                  << ", actual: " << region.GetSize();
      return false;
    }
  }
#if BUILDFLAG(IS_OZONE)
  if (handle.type == gfx::NATIVE_PIXMAP) {
    const auto& pixmap_handle = handle.native_pixmap_handle();
    if (pixmap_handle.planes.empty()) {
      DLOG(ERROR) << "Native pixmap handle has no planes.";
      return false;
    }
    for (const auto& plane : pixmap_handle.planes) {
      if (!plane.fd.is_valid()) {
        DLOG(ERROR) << "Invalid plane FD.";
        return false;
      }
    }
  }
#endif
  return true;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// Buffer::Texture

// Encapsulates the state and logic needed to bind a buffer to a SharedImage.
class Buffer::Texture : public viz::ContextLostObserver {
 public:
  static std::unique_ptr<Texture> Create(
      scoped_refptr<viz::RasterContextProvider> context_provider,
      const gfx::Size& size,
      gfx::ColorSpace color_space);
  static std::unique_ptr<Texture> Create(
      scoped_refptr<viz::RasterContextProvider> context_provider,
      gfx::GpuMemoryBufferHandle* gpu_memory_buffer_handle,
      const viz::SharedImageFormat format,
      const gfx::Size& size,
      gfx::ColorSpace color_space,
      unsigned query_type,
      base::TimeDelta wait_for_release_delay,
      bool is_overlay_candidate);

  Texture(const Texture&) = delete;
  Texture& operator=(const Texture&) = delete;

  ~Texture() override;

  // Overridden from viz::ContextLostObserver:
  void OnContextLost() override;

  // Returns true if the RasterInterface context has been lost.
  bool IsLost();

  // Allow texture to be reused after |sync_token| has passed and runs
  // |callback|.
  void Release(base::OnceClosure callback, viz::ReturnedResource resource);

  // Updates the contents referenced by |gpu_memory_buffer_handle_| returned by
  // mailbox().
  void UpdateSharedImage(std::unique_ptr<gfx::GpuFence> acquire_fence);

  // Releases the contents referenced by |mailbox_| after |sync_token| has
  // passed and runs |callback| when completed.
  void ReleaseSharedImage(base::OnceClosure callback,
                          viz::ReturnedResource resource);

  // Copy the contents of texture to |destination| and runs |callback| when
  // completed.
  void CopyTexImage(Texture* destination, base::OnceClosure callback);

  // Returns the ClientSharedImage for this texture.
  gpu::ClientSharedImage* shared_image() const { return shared_image_.get(); }

  // Returns sync token to wait before read.
  gpu::SyncToken sync_token() { return sync_token_; }

 private:
  Texture(scoped_refptr<viz::RasterContextProvider> context_provider,
          scoped_refptr<gpu::ClientSharedImage> shared_image,
          const gfx::Size& size);
  Texture(scoped_refptr<viz::RasterContextProvider> context_provider,
          scoped_refptr<gpu::ClientSharedImage> shared_image,
          gfx::GpuMemoryBufferHandle* gpu_memory_buffer_handle,
          const gfx::Size& size,
          unsigned query_type,
          base::TimeDelta wait_for_release_delay);

  void DestroyResources();
  static uintptr_t GetBufferIdHelper(gfx::GpuMemoryBufferHandle* handle);
  void ReleaseWhenQueryResultIsAvailable(base::OnceClosure callback);
  void Released();
  void ScheduleWaitForRelease(base::TimeDelta delay);
  void WaitForRelease();
  const void* GetBufferId() const;

  // Stores the address of the GpuMemoryBufferHandle as a stable identifier
  // for tracing. We store this as a uintptr_t rather than a raw_ptr to avoid
  // dangling pointer detection issues, as the owning ::Buffer can be
  // destroyed before the ::Buffer::Texture is destroyed (e.g. during
  // ReleaseSharedImage). This identifier is only used for tracing and is
  // never dereferenced.
  const uintptr_t buffer_id_;
  const gfx::Size size_;
  scoped_refptr<viz::RasterContextProvider> context_provider_;
  const unsigned query_type_;
  unsigned query_id_ = 0;
  scoped_refptr<gpu::ClientSharedImage> shared_image_;
  base::OnceClosure release_callback_;
  const base::TimeDelta wait_for_release_delay_;
  base::TimeTicks wait_for_release_time_;
  bool wait_for_release_pending_ = false;
  gpu::SyncToken sync_token_;
  base::WeakPtrFactory<Texture> weak_ptr_factory_{this};
};

// static
uintptr_t Buffer::Texture::GetBufferIdHelper(
    gfx::GpuMemoryBufferHandle* handle) {
  CHECK(handle);
  CHECK(!handle->is_null());
  return reinterpret_cast<uintptr_t>(handle);
}

// static
std::unique_ptr<Buffer::Texture> Buffer::Texture::Create(
    scoped_refptr<viz::RasterContextProvider> context_provider,
    const gfx::Size& size,
    gfx::ColorSpace color_space) {
  if (!context_provider) {
    return nullptr;
  }
  gpu::SharedImageInterface* sii = context_provider->SharedImageInterface();
  if (!sii) {
    return nullptr;
  }

  const gpu::SharedImageUsageSet usage = gpu::SHARED_IMAGE_USAGE_RASTER_READ |
                                         gpu::SHARED_IMAGE_USAGE_RASTER_WRITE |
                                         gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;

  auto shared_image =
      sii->CreateSharedImage({viz::SinglePlaneFormat::kRGBA_8888, size,
                              color_space, usage, gpu::kExoTextureLabelPrefix},
                             gpu::kNullSurfaceHandle);
  if (!shared_image) {
    DLOG(ERROR) << "Failed to create shared image for texture";
    return nullptr;
  }

  return base::WrapUnique(
      new Texture(std::move(context_provider), std::move(shared_image), size));
}

// static
std::unique_ptr<Buffer::Texture> Buffer::Texture::Create(
    scoped_refptr<viz::RasterContextProvider> context_provider,
    gfx::GpuMemoryBufferHandle* gpu_memory_buffer_handle,
    const viz::SharedImageFormat format,
    const gfx::Size& size,
    gfx::ColorSpace color_space,
    unsigned query_type,
    base::TimeDelta wait_for_release_delay,
    bool is_overlay_candidate) {
  if (!context_provider || !gpu_memory_buffer_handle ||
      gpu_memory_buffer_handle->is_null()) {
    return nullptr;
  }

  // Use the central helper!
  if (!ValidateGpuMemoryBufferHandle(*gpu_memory_buffer_handle, format, size)) {
    return nullptr;
  }

  gpu::SharedImageInterface* sii = context_provider->SharedImageInterface();
  if (!sii) {
    return nullptr;
  }

  gpu::SharedImageUsageSet usage = gpu::SHARED_IMAGE_USAGE_RASTER_READ |
                                   gpu::SHARED_IMAGE_USAGE_RASTER_WRITE |
                                   gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;

  if (is_overlay_candidate &&
      sii->GetCapabilities().supports_scanout_shared_images) {
    usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
  }

  auto shared_image = sii->CreateSharedImage(
      {format, size, color_space, usage, gpu::kExoTextureLabelPrefix},
      gpu_memory_buffer_handle->Clone());
  if (!shared_image) {
    DLOG(ERROR) << "Failed to create shared image from GMB handle";
    return nullptr;
  }

  return base::WrapUnique(new Texture(
      std::move(context_provider), std::move(shared_image),
      gpu_memory_buffer_handle, size, query_type, wait_for_release_delay));
}

Buffer::Texture::Texture(
    scoped_refptr<viz::RasterContextProvider> context_provider,
    scoped_refptr<gpu::ClientSharedImage> shared_image,
    const gfx::Size& size)
    : buffer_id_(0),
      size_(size),
      context_provider_(std::move(context_provider)),
      query_type_(GL_COMMANDS_COMPLETED_CHROMIUM),
      shared_image_(std::move(shared_image)) {
  CHECK(shared_image_);
  sync_token_ = shared_image_->creation_sync_token();

  // Provides a notification when |context_provider_| is lost.
  context_provider_->AddObserver(this);
}

Buffer::Texture::Texture(
    scoped_refptr<viz::RasterContextProvider> context_provider,
    scoped_refptr<gpu::ClientSharedImage> shared_image,
    gfx::GpuMemoryBufferHandle* gpu_memory_buffer_handle,
    const gfx::Size& size,
    unsigned query_type,
    base::TimeDelta wait_for_release_delay)
    : buffer_id_(GetBufferIdHelper(gpu_memory_buffer_handle)),
      size_(size),
      context_provider_(std::move(context_provider)),
      query_type_(query_type),
      shared_image_(std::move(shared_image)),
      wait_for_release_delay_(wait_for_release_delay) {
  CHECK(shared_image_);
  sync_token_ = shared_image_->creation_sync_token();
  if (query_type_ != 0) {
    context_provider_->RasterInterface()->GenQueriesEXT(1, &query_id_);
  }

  // Provides a notification when |context_provider_| is lost.
  context_provider_->AddObserver(this);
}

Buffer::Texture::~Texture() {
  DestroyResources();
  if (context_provider_) {
    context_provider_->RemoveObserver(this);
  }
}

void Buffer::Texture::OnContextLost() {
  DestroyResources();
  context_provider_->RemoveObserver(this);
  context_provider_.reset();
}

bool Buffer::Texture::IsLost() {
  if (context_provider_) {
    gpu::raster::RasterInterface* ri = context_provider_->RasterInterface();
    return ri->GetGraphicsResetStatusKHR() != GL_NO_ERROR;
  }
  return true;
}

void Buffer::Texture::Release(base::OnceClosure callback,
                              viz::ReturnedResource resource) {
  if (context_provider_) {
    gpu::SyncToken resource_sync_token = shared_image()->EndExport(
        std::move(resource.shared_image_export_result));
    // Only need to wait on the sync token if we don't have a release fence.
    if (resource_sync_token.HasData()) {
      sync_token_ = resource_sync_token;
    }
  }

  // Run callback as texture can be reused immediately after waiting for sync
  // token.
  std::move(callback).Run();
}

void Buffer::Texture::ReleaseSharedImage(base::OnceClosure callback,
                                         viz::ReturnedResource resource) {
  if (base::FeatureList::IsEnabled(kReleaseSharedImageImmediately)) {
    gpu::SyncToken resource_sync_token = shared_image()->EndExport(
        std::move(resource.shared_image_export_result));
    if (resource_sync_token.HasData()) {
      sync_token_ = resource_sync_token;
    }
    std::move(callback).Run();
    return;
  }

  if (context_provider_ && query_type_ != 0) {
    gpu::raster::RasterInterface* ri = context_provider_->RasterInterface();
    gpu::SyncToken resource_sync_token = shared_image()->EndExport(
        std::move(resource.shared_image_export_result));
    if (resource_sync_token.HasData()) {
      ri->WaitSyncTokenCHROMIUM(resource_sync_token.GetConstData());
      sync_token_ = resource_sync_token;
    }
    ri->BeginQueryEXT(query_type_, query_id_);
    ri->EndQueryEXT(query_type_);
    // Run callback when query result is available (i.e., when all operations
    // on the shared image have completed and it's ready to be reused) if sync
    // token has data and buffer has been used. If buffer was never used then
    // run the callback immediately.
    if (resource_sync_token.HasData()) {
      ReleaseWhenQueryResultIsAvailable(std::move(callback));
      return;
    }
  }
  std::move(callback).Run();
}

void Buffer::Texture::CopyTexImage(Texture* destination,
                                   base::OnceClosure callback) {
  if (context_provider_) {
    CHECK(shared_image_);
    gpu::SyncToken sync_token =
        shared_image_->BackingWasExternallyUpdated(sync_token_);

    gpu::raster::RasterInterface* ri = context_provider_->RasterInterface();
    std::unique_ptr<gpu::RasterScopedAccess> ri_src_access =
        shared_image_->BeginRasterAccess(ri, sync_token, /*readonly=*/true);
    std::unique_ptr<gpu::RasterScopedAccess> ri_dst_access =
        destination->shared_image_->BeginRasterAccess(
            ri, destination->sync_token_, /*readonly=*/false);

    DCHECK_NE(query_id_, 0u);
    ri->BeginQueryEXT(query_type_, query_id_);

    ri->CopySharedImage(shared_image_->mailbox(),
                        destination->shared_image_->mailbox(), 0, 0, 0, 0,
                        size_.width(), size_.height());
    ri->EndQueryEXT(query_type_);
    // Run callback when query result is available.
    ReleaseWhenQueryResultIsAvailable(std::move(callback));
    // Create and return a sync token that can be used to ensure that the
    // CopySharedImage call is processed before issuing any commands
    // that will read from the target texture on a different context.
    destination->sync_token_ =
        gpu::RasterScopedAccess::EndAccess(std::move(ri_dst_access));
    sync_token_ = gpu::RasterScopedAccess::EndAccess(std::move(ri_src_access));
  }
}

void Buffer::Texture::DestroyResources() {
  if (context_provider_) {
    if (query_id_) {
      gpu::raster::RasterInterface* ri = context_provider_->RasterInterface();
      ri->DeleteQueriesEXT(1, &query_id_);
      query_id_ = 0;
    }

    shared_image_->UpdateDestructionSyncToken(sync_token_);
  }
}

void Buffer::Texture::ReleaseWhenQueryResultIsAvailable(
    base::OnceClosure callback) {
  DCHECK(context_provider_);
  CHECK(release_callback_.is_null());
  release_callback_ = std::move(callback);
  wait_for_release_time_ = base::TimeTicks::Now() + wait_for_release_delay_;
  ScheduleWaitForRelease(wait_for_release_delay_);
  TRACE_EVENT_INSTANT("exo", "pending_query", GetTrack(GetBufferId()));
  context_provider_->ContextSupport()->SignalQuery(
      query_id_, base::BindOnce(&Buffer::Texture::Released,
                                weak_ptr_factory_.GetWeakPtr()));
}

void Buffer::Texture::Released() {
  if (!release_callback_.is_null()) {
    std::move(release_callback_).Run();
  }
}

void Buffer::Texture::ScheduleWaitForRelease(base::TimeDelta delay) {
  if (wait_for_release_pending_) {
    return;
  }

  wait_for_release_pending_ = true;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&Buffer::Texture::WaitForRelease,
                     weak_ptr_factory_.GetWeakPtr()),
      delay);
}

void Buffer::Texture::WaitForRelease() {
  DCHECK(wait_for_release_pending_);
  wait_for_release_pending_ = false;

  if (release_callback_.is_null()) {
    return;
  }

  base::TimeTicks current_time = base::TimeTicks::Now();
  if (current_time < wait_for_release_time_) {
    ScheduleWaitForRelease(wait_for_release_time_ - current_time);
    return;
  }

  base::OnceClosure callback = std::move(release_callback_);

  if (context_provider_) {
    TRACE_EVENT0("exo", "Buffer::Texture::WaitForQueryResult");

    // We need to wait for the result to be available. Getting the result of
    // the query implies waiting for it to become available. The actual result
    // is unimportant and also not well defined.
    unsigned result = 0;
    gpu::raster::RasterInterface* ri = context_provider_->RasterInterface();
    ri->GetQueryObjectuivEXT(query_id_, GL_QUERY_RESULT_EXT, &result);
  }

  std::move(callback).Run();
}

const void* Buffer::Texture::GetBufferId() const {
  return reinterpret_cast<const void*>(buffer_id_);
}

////////////////////////////////////////////////////////////////////////////////
// Buffer, public:

Buffer::Buffer()
    : Buffer(gfx::GpuMemoryBufferHandle(),
             kDefaultFormat,
             kDefaultSize,
             kDefaultBufferUsage,
             kDefaultQueryType,
             kDefaultUseZeroCopy,
             kDefaultIsOverlayCandidate,
             kDefaultYInvert) {}

Buffer::Buffer(gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle,
               viz::SharedImageFormat format,
               gfx::Size size,
               gfx::BufferUsage buffer_usage,
               unsigned query_type,
               bool use_zero_copy,
               bool is_overlay_candidate,
               bool y_invert)
    : gpu_memory_buffer_handle_(std::move(gpu_memory_buffer_handle)),
      format_(format),
      size_(size),
      buffer_usage_(buffer_usage),
      query_type_(query_type),
      use_zero_copy_(use_zero_copy),
      is_overlay_candidate_(is_overlay_candidate),
      y_invert_(y_invert),
      wait_for_release_delay_(base::Milliseconds(kWaitForReleaseDelayMs)) {
  CHECK(use_zero_copy_ || query_type_ != 0);
}

Buffer::~Buffer() = default;

// static
std::unique_ptr<Buffer> Buffer::CreateBufferFromGMBHandle(
    gfx::GpuMemoryBufferHandle buffer_handle,
    const gfx::Size& buffer_size,
    viz::SharedImageFormat format,
    gfx::BufferUsage buffer_usage,
    unsigned query_type,
    bool use_zero_copy,
    bool is_overlay_candidate,
    bool y_invert) {
  // If format is true multiplanar format, we prefer external sampler on
  // ChromeOS.
  if (format.is_multi_plane()) {
    format.SetPrefersExternalSampler();
  }
  return base::WrapUnique(
      new Buffer(std::move(buffer_handle), format, buffer_size, buffer_usage,
                 query_type, use_zero_copy, is_overlay_candidate, y_invert));
}

// static
std::unique_ptr<Buffer> Buffer::CreateBuffer(
    gfx::Size buffer_size,
    viz::SharedImageFormat format,
    gfx::BufferUsage buffer_usage,
    std::string_view debug_label,
    gpu::SurfaceHandle surface_handle,
    base::WaitableEvent* shutdown_event,
    bool is_overlay_candidate) {
  // If format is true multiplanar format, we prefer external sampler on
  // ChromeOS.
  if (format.is_multi_plane()) {
    format.SetPrefersExternalSampler();
  }
  scoped_refptr<gpu::ClientSharedImage> shared_image;
  auto* sii = GetSharedImageInterface();
  if (sii) {
    // Note that we are creating this mappable shared image only to get a
    // GMBHandle from it and use below to create ::Buffer.
    // TODO(vikassoni) : Once MappableSI is fully launched
    // and we remove legacy code paths, refactor ::Buffer and
    // ::Buffer::Texture to use this MappableSI created below directly in
    // ::Buffer::Texture instead of creating new SI in it.
    // ::Buffer will keep a GMB handle as well as MappableSI when handles
    // comes externally via ::CreateBufferFromGMBHandle whereas only
    // MappableSI for ::CreateBuffer calls. ::Buffer also needs to handle
    // context loss since its using a SI.
    // Currently creating ::Buffer from MappableSI below and then using that
    // ::Buffer to create ::Buffer::Texture does not work well as the ::Buffer
    // does not implement ContextLostObserver like ::Buffer::Texture. Even if
    // ::Buffer does implement ContextLostObserver and destroys the MappableSI
    // correctly, it still needs to recreate it when contexts are recreated.
    shared_image = sii->CreateSharedImage(
        {format, buffer_size, gfx::ColorSpace(), kDefaultMappableSIUsage,
         "ExoBufferCreateBuffer"},
        surface_handle, buffer_usage);
  }
  if (!shared_image) {
    LOG(ERROR) << "Failed to create a mappable shared image.";
    return nullptr;
  }
  std::unique_ptr<Buffer> buffer = base::WrapUnique(
      new Buffer(shared_image->CloneGpuMemoryBufferHandle(), format,
                 buffer_size, buffer_usage, kDefaultQueryType,
                 kDefaultUseZeroCopy, is_overlay_candidate, kDefaultYInvert));

  return buffer;
}

std::optional<viz::TransferableResource> Buffer::ProduceTransferableResource(
    FrameSinkResourceManager* resource_manager,
    bool secure_output_only,
    gfx::ColorSpace color_space,
    ProtectedNativePixmapQueryDelegate* protected_native_pixmap_query) {
  TRACE_EVENT1("exo", "Buffer::ProduceTransferableResource", "buffer_id",
               GetBufferId());
  CHECK(attach_count_);

  // If textures are lost, destroy them to ensure that we create new ones
  // below.
  if (contents_texture_ && contents_texture_->IsLost()) {
    contents_texture_.reset();
  }
  if (texture_ && texture_->IsLost()) {
    texture_.reset();
  }

  ui::ContextFactory* context_factory =
      aura::Env::GetInstance()->context_factory();
  // Note: This can fail if GPU acceleration has been disabled.
  scoped_refptr<viz::RasterContextProvider> context_provider =
      context_factory->SharedMainThreadRasterContextProvider();
  if (!context_provider) {
    DLOG(WARNING) << "Failed to acquire a context provider";
    return std::nullopt;
  }

  // Invalid color spaces cause issues when used by the buffer. In these cases
  // revert to using SRGB as before.
  gfx::ColorSpace valid_color_space =
      color_space.IsValid() ? color_space : gfx::ColorSpace::CreateSRGB();

  // Create a new image texture for |gpu_memory_buffer_handle_| if one doesn't
  // already exist. The contents of this buffer are copied to |texture| using a
  // call to CopyTexImage.
  if (!contents_texture_) {
    contents_texture_ =
        Texture::Create(context_provider, &gpu_memory_buffer_handle_, format_,
                        size_, valid_color_space, query_type_,
                        wait_for_release_delay_, is_overlay_candidate_);
    if (!contents_texture_) {
      DLOG(WARNING) << "Failed to create contents texture from client handle";
      return std::nullopt;
    }
  }
  Texture* contents_texture = contents_texture_.get();

  if (release_contents_callback_.IsCancelled()) {
    TRACE_EVENT_BEGIN("exo", kBufferInUse, GetTrack(GetBufferId()), "buffer_id",
                      GetBufferId());
  }

  // Cancel pending contents release callback.
  release_contents_callback_.Reset(
      base::BindOnce(&Buffer::ReleaseContents, base::Unretained(this)));

#if BUILDFLAG(USE_ARC_PROTECTED_MEDIA)
  // Check if this buffer needs HW protection. This can only happen if we
  // require a secure output.
  if (secure_output_only &&
      protected_buffer_state_ == ProtectedBufferState::UNKNOWN &&
      gpu_memory_buffer_handle_.type == gfx::NATIVE_PIXMAP &&
      protected_native_pixmap_query) {
    if (!gpu_memory_buffer_handle_.native_pixmap_handle().planes.empty()) {
      base::ScopedFD pixmap_handle(
          HANDLE_EINTR(dup(gpu_memory_buffer_handle_.native_pixmap_handle()
                               .planes[0]
                               .fd.get())));
      if (pixmap_handle.is_valid()) {
        protected_buffer_state_ = ProtectedBufferState::QUERYING;
        protected_native_pixmap_query->IsProtectedNativePixmapHandle(
            std::move(pixmap_handle),
            base::BindOnce(&Buffer::OnIsProtectedNativePixmapHandle,
                           AsWeakPtr()));
      }
    }
  }
#endif  // BUILDFLAG(USE_ARC_PROTECTED_MEDIA)

  // Zero-copy means using the contents texture directly.
  if (use_zero_copy_) {
    // This binds the latest contents of this buffer to |contents_texture|.
    auto resource = viz::TransferableResource::Make(
        contents_texture_->shared_image(),
        viz::TransferableResource::ResourceSource::kExoBuffer,
        contents_texture->sync_token());

    // The contents texture will be released when no longer used by the
    // compositor.
    resource.id = resource_manager->AllocateResourceId();
    resource.synchronization_type =
        viz::TransferableResource::SynchronizationType::kGpuCommandsCompleted;
    resource_manager->SetResourceReleaseCallback(
        resource.id,
        base::BindOnce(&Buffer::Texture::ReleaseSharedImage,
                       base::Unretained(contents_texture),
                       base::BindOnce(&Buffer::ReleaseContentsTexture,
                                      AsWeakPtr(), std::move(contents_texture_),
                                      release_contents_callback_.callback())));
    return resource;
  }

  // Create a mailbox texture that we copy the buffer contents to.
  if (!texture_) {
    texture_ = Texture::Create(context_provider, GetSize(), valid_color_space);
    if (!texture_) {
      DLOG(WARNING) << "Failed to create copy texture";
      return std::nullopt;
    }
  }
  Texture* texture = texture_.get();

  // Copy the contents of |contents_texture| to |texture| and produce a
  // texture mailbox from the result in |texture|. The contents texture will
  // be released when copy has completed.
  contents_texture->CopyTexImage(
      texture, base::BindOnce(&Buffer::ReleaseContentsTexture, AsWeakPtr(),
                              std::move(contents_texture_),
                              release_contents_callback_.callback()));

  auto resource = viz::TransferableResource::Make(
      texture->shared_image(),
      viz::TransferableResource::ResourceSource::kExoBuffer,
      texture_->sync_token());

  // The mailbox texture will be released when no longer used by the
  // compositor.
  resource.id = resource_manager->AllocateResourceId();
  resource_manager->SetResourceReleaseCallback(
      resource.id,
      base::BindOnce(&Buffer::Texture::Release, base::Unretained(texture),
                     base::BindOnce(&Buffer::ReleaseTexture, AsWeakPtr(),
                                    std::move(texture_))));
  return resource;
}

void Buffer::SkipLegacyRelease() {
  legacy_release_skippable_ = true;
}

void Buffer::OnAttach() {
  DLOG_IF(WARNING, attach_count_ && !legacy_release_skippable_)
      << "Reattaching a buffer that is already attached to another surface.";
  TRACE_EVENT2("exo", "Buffer::OnAttach", "buffer_id", GetBufferId(), "count",
               attach_count_);
  ++attach_count_;
}

void Buffer::OnDetach() {
  CHECK_GT(attach_count_, 0u);
  TRACE_EVENT2("exo", "Buffer::OnAttach", "buffer_id", GetBufferId(), "count",
               attach_count_);
  --attach_count_;

  // Release buffer if no longer attached to a surface and content has been
  // released.
  if (!attach_count_ && release_contents_callback_.IsCancelled()) {
    Release();
  }
}

gfx::Size Buffer::GetSize() const {
  return size_;
}

viz::SharedImageFormat Buffer::GetFormat() const {
  return format_;
}

// TODO(vikassoni): Note that once MappableSI is fully landed, direct use of
// GMBs will go away and clients will end up using either GMBHandle or Mappable
// shared image. Below method will be updated accordingly.
const void* Buffer::GetBufferId() const {
  return static_cast<const void*>(&gpu_memory_buffer_handle_);
}

SkColor4f Buffer::GetColor() const {
  return SkColors::kBlack;
}

#if BUILDFLAG(USE_ARC_PROTECTED_MEDIA)
bool Buffer::NeedsHardwareProtection() {
  // We don't indicate protection is needed in the UNKNOWN state because we have
  // not seen a pixmap yet that could be protected.
  return protected_buffer_state_ == ProtectedBufferState::PROTECTED ||
         protected_buffer_state_ == ProtectedBufferState::QUERYING;
}
#endif  // BUILDFLAG(USE_ARC_PROTECTED_MEDIA)

////////////////////////////////////////////////////////////////////////////////
// Buffer, private:

void Buffer::Release() {
  TRACE_EVENT_END("exo", /* kBufferInUse */ GetTrack(GetBufferId()));

  // Run release callback to notify the client that buffer has been released.
  if (!release_callback_.is_null() && !legacy_release_skippable_) {
    release_callback_.Run();
  }
}

void Buffer::ReleaseTexture(std::unique_ptr<Texture> texture) {
  texture_ = std::move(texture);
}

void Buffer::ReleaseContentsTexture(std::unique_ptr<Texture> texture,
                                    base::OnceClosure callback) {
  contents_texture_ = std::move(texture);
  if (callback) {
    std::move(callback).Run();
  }
}

void Buffer::ReleaseContents() {
  TRACE_EVENT1("exo", "Buffer::ReleaseContents", "buffer_id", GetBufferId());

  // Cancel callback to indicate that buffer has been released.
  release_contents_callback_.Cancel();

  if (attach_count_) {
    TRACE_EVENT_INSTANT("exo", "attached", GetTrack(GetBufferId()));
  } else {
    // Release buffer if not attached to surface.
    Release();
  }
}

SkBitmap Buffer::CreateBitmap() {
  SkBitmap bitmap;
  SkColorType color_type = GetColorTypeForBitmapCreation(GetFormat());
  if (color_type == SkColorType::kUnknown_SkColorType) {
    return bitmap;
  }

  auto* sii = GetSharedImageInterface();
  if (gpu_memory_buffer_handle_.is_null() || !sii) {
    return bitmap;
  }

  if (!ValidateGpuMemoryBufferHandle(gpu_memory_buffer_handle_, format_,
                                     size_)) {
    return bitmap;
  }

  // We only need to create this shared image in order to Map the
  // |gpu_memory_buffer_handle_| to cpu visible memory.
  auto shared_image =
      sii->CreateSharedImage({format_, size_, gfx::ColorSpace(),
                              kDefaultMappableSIUsage, "ExoBufferCreateBitmap"},
                             gpu::kNullSurfaceHandle, buffer_usage_,
                             gpu_memory_buffer_handle_.Clone());
  if (!shared_image) {
    DLOG(ERROR) << "Failed to create SharedImage for mapping.";
    return bitmap;
  }

  auto mapping = shared_image->Map();
  if (!mapping) {
    DLOG(ERROR) << "Failed to map MappableSI.";
    return bitmap;
  }

  gfx::Size size = GetSize();
  SkImageInfo image_info = SkImageInfo::Make(size.width(), size.height(),
                                             color_type, kPremul_SkAlphaType);

  bitmap.allocPixels(image_info);
  bitmap.writePixels(mapping->GetSkPixmapForPlane(0, image_info));
  bitmap.setImmutable();
  mapping.reset();

  return bitmap;
}

#if BUILDFLAG(USE_ARC_PROTECTED_MEDIA)
void Buffer::OnIsProtectedNativePixmapHandle(bool is_protected) {
  protected_buffer_state_ = is_protected ? ProtectedBufferState::PROTECTED
                                         : ProtectedBufferState::UNPROTECTED;
}
#endif  // BUILDFLAG(USE_ARC_PROTECTED_MEDIA)

base::WeakPtr<Buffer> Buffer::AsWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

SolidColorBuffer::SolidColorBuffer(const SkColor4f& color,
                                   const gfx::Size& size)
    : color_(color), size_(size) {
  SkipLegacyRelease();
}

SolidColorBuffer::~SolidColorBuffer() = default;

std::optional<viz::TransferableResource>
SolidColorBuffer::ProduceTransferableResource(
    FrameSinkResourceManager* resource_manager,
    bool secure_output_only,
    gfx::ColorSpace color_space,
    ProtectedNativePixmapQueryDelegate* protected_native_pixmap_query) {
  return std::nullopt;
}

SkColor4f SolidColorBuffer::GetColor() const {
  return color_;
}

gfx::Size SolidColorBuffer::GetSize() const {
  return size_;
}

base::WeakPtr<Buffer> SolidColorBuffer::AsWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

}  // namespace exo
