// 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/graphite_shared_context.h"

#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "components/crash/core/common/crash_key.h"
#include "gpu/command_buffer/common/shm_count.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/gpu/graphite/Context.h"
#include "third_party/skia/include/gpu/graphite/PrecompileContext.h"

namespace gpu {

namespace {
// This is emitted to UMA - values should not be reordered, only appended!
// LINT.IfChange(InsertRecordingStatusUma)
enum class InsertRecordingStatusUma {
  kSuccess,
  kInvalidRecording,
  kPromiseImageInstantiationFailed,
  kAddCommandsFailed,
  kAsyncShaderCompilesFailed,
  kOutOfOrderRecording,
  kMaxValue = kOutOfOrderRecording
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/gpu/enums.xml:GraphiteInsertRecordingStatus)

InsertRecordingStatusUma InsertRecordingStatusUma(
    skgpu::graphite::InsertStatus insert_status) {
  // InsertStatus almost behaves like an enum class, but not quite since it can
  // convert to both bool and integer types and can't be used in a switch.
  if (insert_status == skgpu::graphite::InsertStatus::kSuccess) {
    return InsertRecordingStatusUma::kSuccess;
  } else if (insert_status ==
             skgpu::graphite::InsertStatus::kInvalidRecording) {
    return InsertRecordingStatusUma::kInvalidRecording;
  } else if (insert_status ==
             skgpu::graphite::InsertStatus::kPromiseImageInstantiationFailed) {
    return InsertRecordingStatusUma::kPromiseImageInstantiationFailed;
  } else if (insert_status ==
             skgpu::graphite::InsertStatus::kAddCommandsFailed) {
    return InsertRecordingStatusUma::kAddCommandsFailed;
  } else if (insert_status ==
             skgpu::graphite::InsertStatus::kAsyncShaderCompilesFailed) {
    return InsertRecordingStatusUma::kAsyncShaderCompilesFailed;
  } else if (insert_status ==
             skgpu::graphite::InsertStatus::kOutOfOrderRecording) {
    return InsertRecordingStatusUma::kOutOfOrderRecording;
  }
  NOTREACHED();
}

struct FinishedContext {
  skgpu::graphite::GpuFinishedProc old_finished_proc;
  skgpu::graphite::GpuFinishedContext old_context;
  scoped_refptr<base::SingleThreadTaskRunner> task_runner;
};

std::pair<skgpu::graphite::GpuFinishedProc, skgpu::graphite::GpuFinishedContext>
CreateFinishedProcThreadSafe(
    skgpu::graphite::GpuFinishedProc finished_proc,
    skgpu::graphite::GpuFinishedContext finished_context,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  DCHECK(finished_proc);
  DCHECK(task_runner);

  // Ensure finishedProc is called on the original thread.
  auto* context = new FinishedContext{finished_proc, finished_context,
                                      std::move(task_runner)};

  auto thread_safe_finished_proc = [](void* ctx, skgpu::CallbackResult result) {
    auto context = base::WrapUnique(static_cast<FinishedContext*>(ctx));
    DCHECK(context->old_finished_proc);
    base::SingleThreadTaskRunner* task_runner = context->task_runner.get();
    if (task_runner && !task_runner->BelongsToCurrentThread()) {
      task_runner->PostTask(FROM_HERE,
                            base::BindOnce(context->old_finished_proc,
                                           context->old_context, result));
      return;
    }
    context->old_finished_proc(context->old_context, result);
  };

  return {thread_safe_finished_proc, context};
}

struct AsyncReadContext {
  GraphiteSharedContext::SkImageReadPixelsCallback old_callback;
  SkImage::ReadPixelsContext old_context;
  scoped_refptr<base::SingleThreadTaskRunner> task_runner;
};

void* CreateAsyncReadContextThreadSafe(
    GraphiteSharedContext::SkImageReadPixelsCallback old_callback,
    SkImage::ReadPixelsContext old_callbackContext,
    bool is_thread_safe) {
  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
      is_thread_safe && base::SingleThreadTaskRunner::HasCurrentDefault()
          ? base::SingleThreadTaskRunner::GetCurrentDefault()
          : nullptr;

  // Wrapped the old callback with a new thread safe callback.
  return new AsyncReadContext(std::move(old_callback), old_callbackContext,
                              std::move(task_runner));
}

static void ReadPixelsCallbackThreadSafe(
    void* ctx,
    std::unique_ptr<const SkSurface::AsyncReadResult> async_result) {
  auto context = base::WrapUnique(static_cast<AsyncReadContext*>(ctx));
  if (!context->old_callback) {
    return;
  }

  // Ensure callbacks are called on the original thread if only one
  // graphite::Context is created and is shared by multiple threads.
  base::SingleThreadTaskRunner* task_runner = context->task_runner.get();
  if (task_runner && !task_runner->BelongsToCurrentThread()) {
    task_runner->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(context->old_callback), context->old_context,
                       std::move(async_result)));
    return;
  }

  std::move(context->old_callback)
      .Run(context->old_context, std::move(async_result));
}

class AutoReset {
  STACK_ALLOCATED();

 public:
  explicit AutoReset(std::atomic<bool>& var) : var_(var) {
    var_.store(true, std::memory_order_relaxed);
  }
  ~AutoReset() { var_.store(false, std::memory_order_relaxed); }

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

 private:
  std::atomic<bool>& var_;
};

}  // namespace

// Helper class used by subclasses to acquire |lock_| if it exists.
// Recursive lock is permitted for locking will be skipped upon reentance.
class SCOPED_LOCKABLE GraphiteSharedContext::AutoLock {
  STACK_ALLOCATED();
 public:
  explicit AutoLock(const GraphiteSharedContext* context)
      EXCLUSIVE_LOCK_FUNCTION(context->lock_);

  AutoLock(AutoLock&) = delete;
  AutoLock& operator=(AutoLock&) = delete;

  ~AutoLock() UNLOCK_FUNCTION();

 private:
  std::optional<base::AutoLockMaybe> auto_lock_;
  const GraphiteSharedContext* context_;
};

// |context->locked_thread_id_| reflects the thread where |lock_| is acquired.
// It should only be changed from Invalid to Current, or Current to Invalid.
// It is accessed with `memory_order_relaxed` as writing to |locked_thread_id_|
// is guarded by |context->lock_|.
//
// The logic of detecting recursive lock with
// "current_thread_id == locked_thread_id_":
// - There is no concurrent write hazard for |locked_thread_id|.
// - If Thread1 holds |lock_| and it tries to re-acquire |lock_| after
//   re-entering GraphiteSharedContext, |locked_thread_id_| can be read back
//   safely now as no one can write to |locked_thread_id_| when |lock_| is held
//   by Thread1. It is determined a recursive lock if the current thread id is
//   |locked_thread_id_|. Locking should be skipped here to avoid a deadlock.
// - If Thread1 has held |lock_| and is writing to |locked_thread_id_| while
//   Thread2 is trying to read it, it means Thread1 changes the id between
//   kInvalidThreadId and Thread1::Id() so neither of them matches the current
//   Thread2 id. Thread2 can proceed to acquire the lock.
//

GraphiteSharedContext::AutoLock::AutoLock(const GraphiteSharedContext* context)
    : context_(context) {
  base::PlatformThreadId current_thread_id = base::PlatformThread::CurrentId();

  bool was_in_submit =
      context->locked_thread_in_submit_.load(std::memory_order_relaxed);
  bool was_in_insert = context->locked_thread_in_insert_recording_.load(
      std::memory_order_relaxed);

  if (!context->lock_ || current_thread_id == context->locked_thread_id_.load(
                                                  std::memory_order_relaxed)) {
    // Skip if is_thread_safe is disabled or it's a recursive lock.
  } else {
    base::TimeTicks start_time = base::TimeTicks::Now();
    auto_lock_.emplace(&context->lock_.value());
    base::TimeDelta wait_time = base::TimeTicks::Now() - start_time;

    if (base::ShouldRecordSubsampledMetric(0.01)) {
      UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
          "Gpu.GraphiteSharedContext.LockAcquireTimeUs", wait_time,
          base::Microseconds(1), base::Seconds(1), 50);
      if (was_in_submit) {
        UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
            "Gpu.GraphiteSharedContext.LockAcquireTimeUs.LockedThreadInSubmit",
            wait_time, base::Microseconds(1), base::Seconds(1), 50);
      }
      if (was_in_insert) {
        UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
            "Gpu.GraphiteSharedContext.LockAcquireTimeUs."
            "LockedThreadInInsertRecording",
            wait_time, base::Microseconds(1), base::Seconds(1), 50);
      }
    }

    // |locked_thread_id_| must be kInvalid after the lock is acquired.
    CHECK_EQ(context_->locked_thread_id_.load(std::memory_order_relaxed),
             base::kInvalidThreadId);
    context_->locked_thread_id_.store(current_thread_id,
                                      std::memory_order_relaxed);
  }
}

GraphiteSharedContext::AutoLock::~AutoLock() {
  if (auto_lock_.has_value()) {
    CHECK_EQ(context_->locked_thread_id_.load(std::memory_order_relaxed),
             base::PlatformThread::CurrentId());
    context_->locked_thread_id_.store(base::kInvalidThreadId,
                                      std::memory_order_relaxed);
  }
}

GraphiteSharedContext::GraphiteSharedContext(
    std::unique_ptr<skgpu::graphite::Context> graphite_context,
    GpuProcessShmCount* use_shader_cache_shm_count,
    bool is_thread_safe,
    size_t max_pending_recordings,
    Delegate* delegate)
    : graphite_context_(std::move(graphite_context)),
      use_shader_cache_shm_count_(use_shader_cache_shm_count),
      max_pending_recordings_(max_pending_recordings),
      delegate_(delegate) {
  DCHECK(graphite_context_);
  if (is_thread_safe) {
    lock_.emplace();
  }
}

GraphiteSharedContext::~GraphiteSharedContext() = default;

bool GraphiteSharedContext::IsContextLost() const {
  if (delegate_) {
    return delegate_->IsContextLost();
  }
  return false;
}

skgpu::BackendApi GraphiteSharedContext::backend() const {
  AutoLock auto_lock(this);
  return graphite_context_->backend();
}

std::unique_ptr<skgpu::graphite::Recorder> GraphiteSharedContext::makeRecorder(
    const skgpu::graphite::RecorderOptions& options) {
  AutoLock auto_lock(this);
  return graphite_context_->makeRecorder(options);
}

std::unique_ptr<skgpu::graphite::PrecompileContext>
GraphiteSharedContext::makePrecompileContext() {
  AutoLock auto_lock(this);
  return graphite_context_->makePrecompileContext();
}

void GraphiteSharedContext::set_simulated_insert_status(
    skgpu::graphite::InsertStatus status) {
  AutoLock auto_lock(this);
  simulated_insert_status_ = status;
}

bool GraphiteSharedContext::insertRecording(
    const skgpu::graphite::InsertRecordingInfo& info) {
  AutoLock auto_lock(this);
  AutoReset auto_reset(locked_thread_in_insert_recording_);
  if (!InsertRecordingImpl(info)) {
    return false;
  }

  num_pending_recordings_++;

  // Force submitting if there are too many pending recordings.
  if (num_pending_recordings_ >= max_pending_recordings_) {
    SubmitAndFlushBackendImpl(skgpu::graphite::SyncToCpu::kNo);
  }

  return true;
}

bool GraphiteSharedContext::InsertRecordingImpl(
    const skgpu::graphite::InsertRecordingInfo& info) {
  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
      IsThreadSafe() && base::SingleThreadTaskRunner::HasCurrentDefault()
          ? base::SingleThreadTaskRunner::GetCurrentDefault()
          : nullptr;

  const skgpu::graphite::InsertRecordingInfo* info_ptr = &info;

  // Ensure fFinishedProc is called on the original thread if there is only one
  // graphite::Context.
  std::optional<skgpu::graphite::InsertRecordingInfo> info_copy;
  if (info.fFinishedProc && task_runner) {
    info_copy = *info_ptr;
    std::tie(info_copy->fFinishedProc, info_copy->fFinishedContext) =
        CreateFinishedProcThreadSafe(info.fFinishedProc, info.fFinishedContext,
                                     std::move(task_runner));
    info_ptr = &info_copy.value();
  }
  if (simulated_insert_status_ != skgpu::graphite::InsertStatus::kSuccess) {
    info_copy = *info_ptr;
    info_copy->fSimulatedStatus = simulated_insert_status_;
    info_ptr = &info_copy.value();
  }

  auto insert_status = graphite_context_->insertRecording(*info_ptr);

  const bool simulating_insert_failure =
      info_ptr->fSimulatedStatus != skgpu::graphite::InsertStatus::kSuccess;

  // Crash, log, or emit UMA only if we're not simulating a failure for testing.
  if (!simulating_insert_failure) {
    if (base::ShouldRecordSubsampledMetric(0.01)) {
      UMA_HISTOGRAM_ENUMERATION("GPU.Graphite.InsertRecordingStatus",
                                InsertRecordingStatusUma(insert_status));
    }
    if (insert_status != skgpu::graphite::InsertStatus::kSuccess) {
      // skgpu::graphite::InsertStatus almost behaves like an enum class, but
      // not quite - it can't be static_cast to an int.
      LOG(ERROR) << "Graphite insertRecording failed with status "
                 << static_cast<int>(InsertRecordingStatusUma(insert_status));
    }
  }

  // kAsyncShaderCompilesFailed and kOutOfOrderRecording are unrecoverable
  // failures because they cause future recordings to be rendered incorrectly.
  // TODO(433845560): Check the kAddCommandsFailed failures.
  if (insert_status ==
      skgpu::graphite::InsertStatus::kAsyncShaderCompilesFailed) {
    // For kAsyncShaderCompilesFailed, we should also clear the disk shader
    // cache in case the error was due to a corrupted cached shader blob.
    GpuProcessShmCount::ScopedIncrement use_shader_cache(
        use_shader_cache_shm_count_);
    static crash_reporter::CrashKeyString<4096> insert_error_key(
        "graphite-insert-error");
    insert_error_key.Set(insert_status.message());
    CHECK(simulating_insert_failure);
  } else if (insert_status ==
             skgpu::graphite::InsertStatus::kOutOfOrderRecording) {
    if (delegate_) {
      // TODO(crbug.com/478211694): assume the reason of out of order is OOM for
      // now.
      delegate_->MarkContextLost(error::kOutOfMemory);
    }
  }

  // All other failure modes are recoverable in the sense that future recordings
  // will be rendered correctly, so merely return a boolean here so that callers
  // can log the error.
  return insert_status == skgpu::graphite::InsertStatus::kSuccess;
}

void GraphiteSharedContext::submit(skgpu::graphite::SubmitInfo submit_info) {
  AutoLock auto_lock(this);
  AutoReset auto_reset(locked_thread_in_submit_);
  CHECK(SubmitImpl(submit_info));
}

bool GraphiteSharedContext::SubmitImpl(
    const skgpu::graphite::SubmitInfo& submit_info) {
  num_pending_recordings_ = 0;

  if (submit_info.fSync == skgpu::graphite::SyncToCpu::kNo &&
      !submit_info.fFinishedProc && !graphite_context_->hasPendingGPUWork()) {
    // Skip submitting if there is no pending GPU work and no finish proc. If a
    // finish proc is provided, we must call submit() even without new work so
    // that it can be triggered when all previously submitted work completes.
    return true;
  }

  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
      IsThreadSafe() && base::SingleThreadTaskRunner::HasCurrentDefault()
          ? base::SingleThreadTaskRunner::GetCurrentDefault()
          : nullptr;
  bool success = false;

  const bool shoud_record_metric = base::ShouldRecordSubsampledMetric(0.01);
  base::TimeTicks start_time;
  if (shoud_record_metric) {
    start_time = base::TimeTicks::Now();
  }

  // Ensure fFinishedProc is called on the original thread if there is only one
  // graphite::Context.
  if (submit_info.fFinishedProc && task_runner) {
    auto wrapped_submit_info = submit_info;
    std::tie(wrapped_submit_info.fFinishedProc,
             wrapped_submit_info.fFinishedContext) =
        CreateFinishedProcThreadSafe(submit_info.fFinishedProc,
                                     submit_info.fFinishedContext,
                                     std::move(task_runner));
    success = graphite_context_->submit(wrapped_submit_info);
  } else {
    success = graphite_context_->submit(submit_info);
  }

  if (shoud_record_metric) {
    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
        "GPU.Graphite.SubmitDurationUs", base::TimeTicks::Now() - start_time,
        base::Microseconds(1), base::Seconds(1), 50);
  }

  return success;
}

void GraphiteSharedContext::submitAndFlushBackend(
    skgpu::graphite::SubmitInfo submit_info) {
  AutoLock auto_lock(this);
  AutoReset auto_reset(locked_thread_in_submit_);
  SubmitAndFlushBackendImpl(submit_info);
}

void GraphiteSharedContext::SubmitAndFlushBackendImpl(
    const skgpu::graphite::SubmitInfo& submit_info) {
  // Capture this before SubmitImpl() runs, since submitting clears the
  // context's pending GPU work.
  bool had_pending_gpu_work = graphite_context_->hasPendingGPUWork();

  CHECK(SubmitImpl(submit_info));

  if (delegate_ && had_pending_gpu_work) {
    delegate_->FlushBackend();
  }
}

bool GraphiteSharedContext::hasUnfinishedGpuWork() const {
  AutoLock auto_lock(this);
  return graphite_context_->hasUnfinishedGpuWork();
}

void GraphiteSharedContext::asyncRescaleAndReadPixels(
    const SkImage* src,
    const SkImageInfo& dstImageInfo,
    const SkIRect& srcRect,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  return graphite_context_->asyncRescaleAndReadPixels(
      src, dstImageInfo, srcRect, rescaleGamma, rescaleMode,
      &ReadPixelsCallbackThreadSafe, new_callbackContext);
}

void GraphiteSharedContext::asyncRescaleAndReadPixels(
    const SkSurface* src,
    const SkImageInfo& dstImageInfo,
    const SkIRect& srcRect,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  return graphite_context_->asyncRescaleAndReadPixels(
      src, dstImageInfo, srcRect, rescaleGamma, rescaleMode,
      &ReadPixelsCallbackThreadSafe, new_callbackContext);
}

bool GraphiteSharedContext::asyncRescaleAndReadPixelsAndSubmit(
    const SkImage* src,
    const SkImageInfo& dstImageInfo,
    const SkIRect& srcRect,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  graphite_context_->asyncRescaleAndReadPixels(
      src, dstImageInfo, srcRect, rescaleGamma, rescaleMode,
      &ReadPixelsCallbackThreadSafe, new_callbackContext);

  return SubmitImpl(skgpu::graphite::SyncToCpu::kYes);
}

bool GraphiteSharedContext::asyncRescaleAndReadPixelsAndSubmit(
    const SkSurface* src,
    const SkImageInfo& dstImageInfo,
    const SkIRect& srcRect,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  graphite_context_->asyncRescaleAndReadPixels(
      src, dstImageInfo, srcRect, rescaleGamma, rescaleMode,
      &ReadPixelsCallbackThreadSafe, new_callbackContext);

  return SubmitImpl(skgpu::graphite::SyncToCpu::kYes);
}

void GraphiteSharedContext::asyncRescaleAndReadPixelsYUV420(
    const SkImage* src,
    SkYUVColorSpace yuvColorSpace,
    sk_sp<SkColorSpace> dstColorSpace,
    const SkIRect& srcRect,
    const SkISize& dstSize,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  return graphite_context_->asyncRescaleAndReadPixelsYUV420(
      src, yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma,
      rescaleMode, &ReadPixelsCallbackThreadSafe, new_callbackContext);
}

void GraphiteSharedContext::asyncRescaleAndReadPixelsYUV420(
    const SkSurface* src,
    SkYUVColorSpace yuvColorSpace,
    sk_sp<SkColorSpace> dstColorSpace,
    const SkIRect& srcRect,
    const SkISize& dstSize,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  return graphite_context_->asyncRescaleAndReadPixelsYUV420(
      src, yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma,
      rescaleMode, &ReadPixelsCallbackThreadSafe, new_callbackContext);
}

bool GraphiteSharedContext::asyncRescaleAndReadPixelsYUV420AndSubmit(
    const SkImage* src,
    SkYUVColorSpace yuvColorSpace,
    sk_sp<SkColorSpace> dstColorSpace,
    const SkIRect& srcRect,
    const SkISize& dstSize,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  graphite_context_->asyncRescaleAndReadPixelsYUV420(
      src, yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma,
      rescaleMode, &ReadPixelsCallbackThreadSafe, new_callbackContext);

  return SubmitImpl(skgpu::graphite::SyncToCpu::kYes);
}

bool GraphiteSharedContext::asyncRescaleAndReadPixelsYUV420AndSubmit(
    const SkSurface* src,
    SkYUVColorSpace yuvColorSpace,
    sk_sp<SkColorSpace> dstColorSpace,
    const SkIRect& srcRect,
    const SkISize& dstSize,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  graphite_context_->asyncRescaleAndReadPixelsYUV420(
      src, yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma,
      rescaleMode, &ReadPixelsCallbackThreadSafe, new_callbackContext);

  return SubmitImpl(skgpu::graphite::SyncToCpu::kYes);
}

void GraphiteSharedContext::asyncRescaleAndReadPixelsYUVA420(
    const SkImage* src,
    SkYUVColorSpace yuvColorSpace,
    sk_sp<SkColorSpace> dstColorSpace,
    const SkIRect& srcRect,
    const SkISize& dstSize,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  return graphite_context_->asyncRescaleAndReadPixelsYUVA420(
      src, yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma,
      rescaleMode, &ReadPixelsCallbackThreadSafe, new_callbackContext);
}

void GraphiteSharedContext::asyncRescaleAndReadPixelsYUVA420(
    const SkSurface* src,
    SkYUVColorSpace yuvColorSpace,
    sk_sp<SkColorSpace> dstColorSpace,
    const SkIRect& srcRect,
    const SkISize& dstSize,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  return graphite_context_->asyncRescaleAndReadPixelsYUVA420(
      src, yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma,
      rescaleMode, &ReadPixelsCallbackThreadSafe, new_callbackContext);
}

bool GraphiteSharedContext::asyncRescaleAndReadPixelsYUVA420AndSubmit(
    const SkImage* src,
    SkYUVColorSpace yuvColorSpace,
    sk_sp<SkColorSpace> dstColorSpace,
    const SkIRect& srcRect,
    const SkISize& dstSize,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  graphite_context_->asyncRescaleAndReadPixelsYUVA420(
      src, yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma,
      rescaleMode, &ReadPixelsCallbackThreadSafe, new_callbackContext);

  return SubmitImpl(skgpu::graphite::SyncToCpu::kYes);
}

bool GraphiteSharedContext::asyncRescaleAndReadPixelsYUVA420AndSubmit(
    const SkSurface* src,
    SkYUVColorSpace yuvColorSpace,
    sk_sp<SkColorSpace> dstColorSpace,
    const SkIRect& srcRect,
    const SkISize& dstSize,
    SkImage::RescaleGamma rescaleGamma,
    SkImage::RescaleMode rescaleMode,
    SkImageReadPixelsCallback callback,
    SkImage::ReadPixelsContext callbackContext) {
  AutoLock auto_lock(this);
  auto* new_callbackContext = CreateAsyncReadContextThreadSafe(
      std::move(callback), callbackContext, IsThreadSafe());

  graphite_context_->asyncRescaleAndReadPixelsYUVA420(
      src, yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma,
      rescaleMode, &ReadPixelsCallbackThreadSafe, new_callbackContext);

  return SubmitImpl(skgpu::graphite::SyncToCpu::kYes);
}

void GraphiteSharedContext::checkAsyncWorkCompletion() {
  AutoLock auto_lock(this);
  return graphite_context_->checkAsyncWorkCompletion();
}

void GraphiteSharedContext::deleteBackendTexture(
    const skgpu::graphite::BackendTexture& texture) {
  AutoLock auto_lock(this);
  return graphite_context_->deleteBackendTexture(texture);
}

void GraphiteSharedContext::freeGpuResources() {
  AutoLock auto_lock(this);
  return graphite_context_->freeGpuResources();
}

void GraphiteSharedContext::performDeferredCleanup(
    std::chrono::milliseconds msNotUsed) {
  AutoLock auto_lock(this);
  return graphite_context_->performDeferredCleanup(msNotUsed);
}

size_t GraphiteSharedContext::currentBudgetedBytes() const {
  AutoLock auto_lock(this);
  return graphite_context_->currentBudgetedBytes();
}

size_t GraphiteSharedContext::currentPurgeableBytes() const {
  AutoLock auto_lock(this);
  return graphite_context_->currentPurgeableBytes();
}

size_t GraphiteSharedContext::maxBudgetedBytes() const {
  AutoLock auto_lock(this);
  return graphite_context_->maxBudgetedBytes();
}

void GraphiteSharedContext::setMaxBudgetedBytes(size_t bytes) {
  AutoLock auto_lock(this);
  return graphite_context_->setMaxBudgetedBytes(bytes);
}

void GraphiteSharedContext::dumpMemoryStatistics(
    SkTraceMemoryDump* traceMemoryDump) const {
  AutoLock auto_lock(this);
  return graphite_context_->dumpMemoryStatistics(traceMemoryDump);
}

bool GraphiteSharedContext::isDeviceLost() const {
  AutoLock auto_lock(this);
  return graphite_context_->isDeviceLost();
}

int GraphiteSharedContext::maxTextureSize() const {
  return graphite_context_->maxTextureSize();
}

bool GraphiteSharedContext::supportsProtectedContent() const {
  return graphite_context_->supportsProtectedContent();
}

skgpu::GpuStatsFlags GraphiteSharedContext::supportedGpuStats() const {
  return graphite_context_->supportedGpuStats();
}

}  // namespace gpu
