// 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 "gpu/command_buffer/service/shared_image_interface_in_process.h"

#include <optional>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "gpu/command_buffer/service/command_buffer_task_executor.h"
#include "gpu/command_buffer/service/display_compositor_memory_and_task_controller_on_gpu.h"
#include "gpu/command_buffer/service/gr_shader_cache.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image_interface_in_process_base.h"
#include "gpu/command_buffer/service/single_task_sequence.h"
#include "gpu/config/gpu_driver_bug_workarounds.h"
#include "gpu/config/gpu_feature_info.h"
#include "gpu/config/gpu_preferences.h"
#include "gpu/ipc/common/gpu_client_ids.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/gfx/gpu_fence.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_implementation.h"

namespace gpu {

struct SharedImageInterfaceInProcess::SetUpOnGpuParams {
  const GpuPreferences gpu_preferences;
  const GpuDriverBugWorkarounds gpu_workarounds;
  const GpuFeatureInfo gpu_feature_info;
  const raw_ptr<gpu::SharedContextState> context_state;
  const raw_ptr<SharedImageManager> shared_image_manager;
  const bool is_for_display_compositor;

  SetUpOnGpuParams(const GpuPreferences& gpu_preferences,
                   const GpuDriverBugWorkarounds& gpu_workarounds,
                   const GpuFeatureInfo& gpu_feature_info,
                   gpu::SharedContextState* context_state,
                   SharedImageManager* shared_image_manager,
                   bool is_for_display_compositor)
      : gpu_preferences(gpu_preferences),
        gpu_workarounds(gpu_workarounds),
        gpu_feature_info(gpu_feature_info),
        context_state(context_state),
        shared_image_manager(shared_image_manager),
        is_for_display_compositor(is_for_display_compositor) {}

  ~SetUpOnGpuParams() = default;

  SetUpOnGpuParams(const SetUpOnGpuParams& other) = delete;
  SetUpOnGpuParams& operator=(const SetUpOnGpuParams& other) = delete;
};

scoped_refptr<SharedImageInterfaceInProcess>
SharedImageInterfaceInProcess::Create(
    std::unique_ptr<SingleTaskSequence> task_sequence,
    const GpuPreferences& gpu_preferences,
    const GpuDriverBugWorkarounds& gpu_workarounds,
    const GpuFeatureInfo& gpu_feature_info,
    gpu::SharedContextState* context_state,
    SharedImageManager* shared_image_manager,
    bool is_for_display_compositor,
    scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
    bool always_create_native_gmb_handles /*=false*/) {
  // ensure Initialize() is called before pointer returned to caller
  auto sii = base::WrapRefCounted(new SharedImageInterfaceInProcess{
      std::move(task_sequence), shared_image_manager,
      std::move(gpu_task_runner)});
  sii->Initialize(std::make_unique<SetUpOnGpuParams>(
      gpu_preferences, gpu_workarounds, gpu_feature_info, context_state,
      shared_image_manager, is_for_display_compositor));

  if (always_create_native_gmb_handles) {
    // Creation with this option can be done only on the GPU thread, since we
    // must construct the SharedImageFactory eagerly to ensure that it is
    // immediately available to create GMB handles on the IO thread.
    CHECK(sii->gpu_task_runner_->BelongsToCurrentThread());
    sii->GetSharedImageFactoryOnGpuThread();
    sii->always_create_native_gmb_handles_ = true;
  }
  return sii;
}

SharedImageInterfaceInProcess::SharedImageInterfaceInProcess(
    std::unique_ptr<SingleTaskSequence> task_sequence,
    SharedImageManager* shared_image_manager,
    scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner)
    : SharedImageInterfaceInProcessBase(
          CommandBufferNamespace::IN_PROCESS,
          DisplayCompositorMemoryAndTaskControllerOnGpu::NextCommandBufferId(),
          /*verify_creation_sync_token=*/false),
      task_sequence_(std::move(task_sequence)),
      gpu_task_runner_(std::move(gpu_task_runner)),
      shared_image_manager_(shared_image_manager) {
  CHECK(task_sequence_);
}

void SharedImageInterfaceInProcess::Initialize(
    std::unique_ptr<SetUpOnGpuParams> params) {
  if (gpu_task_runner_->BelongsToCurrentThread()) {
    SetUpOnGpu(std::move(params));
  } else {
    // Can't safely be called in constructor, because receiver must be
    // retained, but constructor has zero ref-count
    task_sequence_->ScheduleTask(
        base::BindOnce(&SharedImageInterfaceInProcess::SetUpOnGpu, this,
                       std::move(params)),
        /*sync_token_fences=*/{}, SyncToken());
  }
}

SharedImageInterfaceInProcess::~SharedImageInterfaceInProcess() {
  base::WaitableEvent completion(
      base::WaitableEvent::ResetPolicy::MANUAL,
      base::WaitableEvent::InitialState::NOT_SIGNALED);

  if (gpu_task_runner_->BelongsToCurrentThread()) {
    DestroyOnGpu(&completion);
  } else {
    // Unretained because called in destructor, where ref-count is always zero;
    // safe because destructor is blocked on `completion` until async task runs
    task_sequence_->ScheduleTask(
        base::BindOnce(&SharedImageInterfaceInProcess::DestroyOnGpu,
                       base::Unretained(this), &completion),
        /*sync_token_fences=*/{}, SyncToken());
  }

  completion.Wait();
}

void SharedImageInterfaceInProcess::SetUpOnGpu(
    std::unique_ptr<SetUpOnGpuParams> params) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
  context_state_ = params->context_state.get();

  create_factory_ = base::BindOnce(
      [](std::unique_ptr<SetUpOnGpuParams> params) {
        auto* memory_tracker = params->context_state
                                   ? params->context_state->memory_tracker()
                                   : nullptr;
        auto shared_image_factory = std::make_unique<SharedImageFactory>(
            params->gpu_preferences, params->gpu_workarounds,
            params->gpu_feature_info, params->context_state,
            params->shared_image_manager, memory_tracker,
            params->is_for_display_compositor);
        return shared_image_factory;
      },
      std::move(params));

  // Make the SharedImageInterface use the same sequence as the command buffer,
  // it's necessary for WebView because of the blocking behavior.
  // TODO(piman): see if it's worth using a different sequence for non-WebView.
  sync_point_client_state_ = task_sequence_->CreateSyncPointClientState(
      CommandBufferNamespace::IN_PROCESS, command_buffer_id());
}

void SharedImageInterfaceInProcess::DestroyOnGpu(
    base::WaitableEvent* completion) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
  bool have_context = MakeContextCurrentOnGpuThread();
  if (shared_image_factory_) {
    shared_image_factory_->DestroyAllSharedImages(have_context);
    shared_image_factory_ = nullptr;
  }

  sync_point_client_state_.Reset();

  context_state_ = nullptr;
  completion->Signal();
}

scoped_refptr<ClientSharedImage>
SharedImageInterfaceInProcess::CreateSharedImage(
    const SharedImageInfo& si_info,
    SurfaceHandle surface_handle,
    gfx::BufferUsage buffer_usage,
    std::optional<SharedImagePoolId> pool_id) {
  DCHECK(gpu::IsValidClientUsage(si_info.usage));

  if (always_create_native_gmb_handles_) {
#if BUILDFLAG(IS_ANDROID)
    // Creation of native buffer handles is not supported on Android (the
    // only way that a non-null GpuMemoryBufferHandle can be created on
    // Android is by importing an external AHB).
    return nullptr;
#else
    // The below method doesn't (yet?) take in pool IDs.
    CHECK(!pool_id);

    // The SIFactory may be null if it was not possible for it to be created in
    // the constructor (because the context was lost). In this case, there is
    // nothing possible to do (context loss is sticky and will eventually be
    // resolved by recreating the SII in one way or another).
    if (!shared_image_factory_) {
      return nullptr;
    }
    auto gmb_handle = shared_image_factory_->CreateNativeGpuMemoryBufferHandle(
        si_info.size, si_info.format, buffer_usage);
    if (gmb_handle.is_null()) {
      return nullptr;
    }
    return SharedImageInterfaceInProcessBase::CreateSharedImage(
        si_info, surface_handle, buffer_usage, std::move(gmb_handle));
#endif
  }

  return SharedImageInterfaceInProcessBase::CreateSharedImage(
      si_info, surface_handle, buffer_usage, pool_id);
}

SharedImageFactory*
SharedImageInterfaceInProcess::GetSharedImageFactoryOnGpuThread() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
  if (shared_image_factory_) {
    return shared_image_factory_.get();
  }

  // Some shared image backing factories will use GL in ctor, so we need GL even
  // if chrome is using non-GL backing.
  if (!MakeContextCurrentOnGpuThread(/*needs_gl=*/true)) {
    return nullptr;
  }

  shared_image_factory_ = std::move(create_factory_).Run();
  return shared_image_factory_.get();
}

bool SharedImageInterfaceInProcess::MakeContextCurrentOnGpuThread(
    bool needs_gl) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
  if (gl::GetGLImplementation() == gl::kGLImplementationDisabled) {
    return true;
  }

  if (!context_state_)
    return false;

  if (context_state_->context_lost())
    return false;

  // |shared_image_factory_| never writes to the surface, so skip unnecessary
  // MakeCurrent to improve performance. https://crbug.com/457431
  auto* context = context_state_->real_context();
  if (context->IsCurrent(nullptr))
    return !context_state_->CheckResetStatus(needs_gl);
  return context_state_->MakeCurrent(/*surface=*/nullptr, needs_gl);
}

void SharedImageInterfaceInProcess::MarkContextLostOnGpuThread() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
  if (context_state_) {
    context_state_->MarkContextLost();
  }
}

void SharedImageInterfaceInProcess::ScheduleGpuTask(
    base::OnceClosure task,
    std::vector<SyncToken> sync_token_fences,
    const SyncToken& release) {
  task_sequence_->ScheduleTask(std::move(task), std::move(sync_token_fences),
                               release);
}

}  // namespace gpu
