// Copyright 2017 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/webxr/mailbox_to_surface_bridge_impl.h"

#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/gpu/context_provider.h"
#include "content/public/browser/android/compositor.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.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/gles2_interface.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "gpu/ipc/common/gpu_surface_tracker.h"
#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/gpu_memory_buffer_handle.h"
#include "ui/gl/android/surface_texture.h"

namespace webxr {

MailboxToSurfaceBridgeImpl::MailboxToSurfaceBridgeImpl() {
  DVLOG(1) << __func__;
}

MailboxToSurfaceBridgeImpl::~MailboxToSurfaceBridgeImpl() {
  DVLOG(1) << __func__;
}

bool MailboxToSurfaceBridgeImpl::IsConnected() {
  return context_provider_ && gl_ && context_support_;
}

void MailboxToSurfaceBridgeImpl::OnContextAvailableOnUiThread(
    scoped_refptr<viz::ContextProvider> provider) {
  DVLOG(1) << __func__;
  // Must save a reference to the viz::ContextProvider to keep it alive,
  // otherwise the GL context created from it becomes invalid on its
  // destruction.
  context_provider_ = std::move(provider);

  DCHECK(on_context_bound_);
  gl_thread_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &MailboxToSurfaceBridgeImpl::BindContextProviderToCurrentThread,
          base::Unretained(this)));
}

void MailboxToSurfaceBridgeImpl::BindContextProviderToCurrentThread() {
  auto result = context_provider_->BindToCurrentSequence();
  if (result != gpu::ContextResult::kSuccess) {
    DLOG(ERROR) << "Failed to init viz::ContextProvider";
    return;
  }

  gl_ = context_provider_->ContextGL();
  context_support_ = context_provider_->ContextSupport();

  if (!gl_) {
    DLOG(ERROR) << "Did not get a GL context";
    return;
  }
  if (!context_support_) {
    DLOG(ERROR) << "Did not get a ContextSupport";
    return;
  }

  DVLOG(1) << __func__ << ": Context ready";
  if (on_context_bound_) {
    std::move(on_context_bound_).Run();
  }
}

void MailboxToSurfaceBridgeImpl::CreateAndBindContextProvider(
    base::OnceClosure on_bound_callback) {
  gl_thread_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
  on_context_bound_ = std::move(on_bound_callback);

  // The callback to run in this thread. It is necessary to keep |surface| alive
  // until the context becomes available. So pass it on to the callback, so that
  // it stays alive, and is destroyed on the same thread once done.
  auto callback =
      base::BindOnce(&MailboxToSurfaceBridgeImpl::OnContextAvailableOnUiThread,
                     weak_ptr_factory_.GetWeakPtr());

  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(
                     [](content::Compositor::ContextProviderCallback callback) {
                       content::Compositor::CreateContextProvider(
                           std::move(callback));
                     },
                     std::move(callback)));
}

void MailboxToSurfaceBridgeImpl::GenSyncToken(gpu::SyncToken* out_sync_token) {
  TRACE_EVENT0("gpu", "GenSyncToken");
  DCHECK(IsConnected());
  gl_->GenSyncTokenCHROMIUM(out_sync_token->GetData());
}

void MailboxToSurfaceBridgeImpl::VerifySyncToken(
    gpu::SyncToken& out_sync_token) {
  TRACE_EVENT0("gpu", "VerifySyncToken");
  DCHECK(IsConnected());

  auto* sync_token_data = out_sync_token.GetData();
  gl_->VerifySyncTokensCHROMIUM(&sync_token_data, 1);
}

void MailboxToSurfaceBridgeImpl::WaitSyncToken(
    const gpu::SyncToken& sync_token) {
  TRACE_EVENT0("gpu", "WaitSyncToken");
  DCHECK(IsConnected());
  gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
}

void MailboxToSurfaceBridgeImpl::WaitForClientGpuFence(
    gfx::GpuFence& gpu_fence) {
  TRACE_EVENT0("gpu", "WaitForClientGpuFence");
  DCHECK(IsConnected());
  GLuint id = gl_->CreateClientGpuFenceCHROMIUM(gpu_fence.AsClientGpuFence());
  gl_->WaitGpuFenceCHROMIUM(id);
  gl_->DestroyGpuFenceCHROMIUM(id);
}

void MailboxToSurfaceBridgeImpl::CreateGpuFence(
    base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback) {
  TRACE_EVENT0("gpu", "CreateGpuFence");
  DCHECK(IsConnected());

  GLuint id = gl_->CreateGpuFenceCHROMIUM();
  context_support_->GetGpuFence(id, std::move(callback));
  gl_->DestroyGpuFenceCHROMIUM(id);
}

scoped_refptr<gpu::ClientSharedImage>
MailboxToSurfaceBridgeImpl::CreateSharedImage(
    gfx::GpuMemoryBufferHandle buffer_handle,
    viz::SharedImageFormat format,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    GrSurfaceOrigin surface_origin,
    gpu::SharedImageUsageSet usage,
    gpu::SyncToken& sync_token) {
  TRACE_EVENT0("gpu", "CreateSharedImage");
  DCHECK(IsConnected());

  auto* sii = context_provider_->SharedImageInterface();
  DCHECK(sii);

  CHECK_EQ(format, viz::SinglePlaneFormat::kRGBA_8888);
  auto client_shared_image = sii->CreateSharedImage(
      {format, size, color_space, surface_origin, kPremul_SkAlphaType, usage,
       "WebXrMailboxToSurfaceBridge"},
      std::move(buffer_handle));
  CHECK(client_shared_image);
  sync_token = client_shared_image->creation_sync_token();
  sii->VerifySyncToken(sync_token);
  DCHECK(client_shared_image->GetTextureTarget() == GL_TEXTURE_2D);
  return client_shared_image;
}

void MailboxToSurfaceBridgeImpl::DestroySharedImage(
    const gpu::SyncToken& sync_token,
    scoped_refptr<gpu::ClientSharedImage> shared_image) {
  TRACE_EVENT0("gpu", "CreateSharedImage");
  DCHECK(IsConnected());
  DCHECK(shared_image);

  shared_image->UpdateDestructionSyncToken(sync_token);
}

viz::ContextProvider* MailboxToSurfaceBridgeImpl::GetContextProvider() {
  return context_provider_.get();
}

std::unique_ptr<device::MailboxToSurfaceBridge>
MailboxToSurfaceBridgeFactoryImpl::Create() const {
  return std::make_unique<MailboxToSurfaceBridgeImpl>();
}

}  // namespace webxr
