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

#include "content/renderer/media/android/stream_texture_wrapper_impl.h"

#include "base/callback.h"
#include "cc/layers/video_frame_provider.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "media/base/bind_to_current_loop.h"

using gpu::gles2::GLES2Interface;

static const uint32_t kGLTextureExternalOES = GL_TEXTURE_EXTERNAL_OES;

namespace {
// Non-member function to allow it to run even after this class is deleted.
void OnReleaseTexture(scoped_refptr<content::StreamTextureFactory> factories,
                      uint32_t texture_id,
                      const gpu::SyncToken& sync_token) {
  GLES2Interface* gl = factories->ContextGL();
  gl->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
  gl->DeleteTextures(1, &texture_id);
  // Flush to ensure that the stream texture gets deleted in a timely fashion.
  gl->ShallowFlushCHROMIUM();
}
}

namespace content {

StreamTextureWrapperImpl::StreamTextureWrapperImpl(
    bool enable_texture_copy,
    scoped_refptr<StreamTextureFactory> factory,
    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner)
    : enable_texture_copy_(enable_texture_copy),
      texture_id_(0),
      factory_(factory),
      main_task_runner_(main_task_runner),
      weak_factory_(this) {}

StreamTextureWrapperImpl::~StreamTextureWrapperImpl() {
  DCHECK(main_task_runner_->BelongsToCurrentThread());

  if (texture_id_) {
    GLES2Interface* gl = factory_->ContextGL();
    gl->DeleteTextures(1, &texture_id_);
    // Flush to ensure that the stream texture gets deleted in a timely fashion.
    gl->ShallowFlushCHROMIUM();
  }

  SetCurrentFrameInternal(nullptr);
}

media::ScopedStreamTextureWrapper StreamTextureWrapperImpl::Create(
    bool enable_texture_copy,
    scoped_refptr<StreamTextureFactory> factory,
    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner) {
  return media::ScopedStreamTextureWrapper(new StreamTextureWrapperImpl(
      enable_texture_copy, factory, main_task_runner));
}

scoped_refptr<media::VideoFrame> StreamTextureWrapperImpl::GetCurrentFrame() {
  base::AutoLock auto_lock(current_frame_lock_);
  return current_frame_;
}

void StreamTextureWrapperImpl::ReallocateVideoFrame(
    const gfx::Size& natural_size) {
  DVLOG(2) << __func__;
  DCHECK(main_task_runner_->BelongsToCurrentThread());

  GLES2Interface* gl = factory_->ContextGL();
  GLuint texture_target = kGLTextureExternalOES;
  GLuint texture_id_ref = gl->CreateAndConsumeTextureCHROMIUM(
      texture_target, texture_mailbox_.name);
  const GLuint64 fence_sync = gl->InsertFenceSyncCHROMIUM();
  gl->Flush();

  gpu::SyncToken texture_mailbox_sync_token;
  gl->GenUnverifiedSyncTokenCHROMIUM(fence_sync,
                                     texture_mailbox_sync_token.GetData());
  if (texture_mailbox_sync_token.namespace_id() ==
      gpu::CommandBufferNamespace::IN_PROCESS) {
    // TODO(boliu): Remove this once Android WebView switches to IPC-based
    // command buffer for video.
    GLbyte* sync_tokens[] = {texture_mailbox_sync_token.GetData()};
    gl->VerifySyncTokensCHROMIUM(sync_tokens, arraysize(sync_tokens));
  }

  gpu::MailboxHolder holders[media::VideoFrame::kMaxPlanes] = {
      gpu::MailboxHolder(texture_mailbox_, texture_mailbox_sync_token,
                         texture_target)};

  scoped_refptr<media::VideoFrame> new_frame =
      media::VideoFrame::WrapNativeTextures(
          media::PIXEL_FORMAT_ARGB, holders,
          media::BindToCurrentLoop(
              base::Bind(&OnReleaseTexture, factory_, texture_id_ref)),
          natural_size, gfx::Rect(natural_size), natural_size,
          base::TimeDelta());

  if (enable_texture_copy_) {
    new_frame->metadata()->SetBoolean(media::VideoFrameMetadata::COPY_REQUIRED,
                                      true);
  }

  SetCurrentFrameInternal(new_frame);
}

void StreamTextureWrapperImpl::ForwardStreamTextureForSurfaceRequest(
    const base::UnguessableToken& request_token) {
  stream_texture_proxy_->ForwardStreamTextureForSurfaceRequest(request_token);
}

void StreamTextureWrapperImpl::SetCurrentFrameInternal(
    const scoped_refptr<media::VideoFrame>& video_frame) {
  base::AutoLock auto_lock(current_frame_lock_);
  current_frame_ = video_frame;
}

void StreamTextureWrapperImpl::UpdateTextureSize(const gfx::Size& new_size) {
  DVLOG(2) << __func__;

  if (!main_task_runner_->BelongsToCurrentThread()) {
    main_task_runner_->PostTask(
        FROM_HERE, base::Bind(&StreamTextureWrapperImpl::UpdateTextureSize,
                              weak_factory_.GetWeakPtr(), new_size));
    return;
  }

  // InitializeOnMainThread() hasn't run, or failed.
  if (!stream_texture_proxy_)
    return;

  if (natural_size_ == new_size)
    return;

  natural_size_ = new_size;

  ReallocateVideoFrame(new_size);
  stream_texture_proxy_->SetStreamTextureSize(new_size);
}

void StreamTextureWrapperImpl::Initialize(
    const base::Closure& received_frame_cb,
    const gfx::Size& natural_size,
    scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner,
    const StreamTextureWrapperInitCB& init_cb) {
  DVLOG(2) << __func__;

  compositor_task_runner_ = compositor_task_runner;
  natural_size_ = natural_size;

  main_task_runner_->PostTask(
      FROM_HERE, base::Bind(&StreamTextureWrapperImpl::InitializeOnMainThread,
                            weak_factory_.GetWeakPtr(), received_frame_cb,
                            media::BindToCurrentLoop(init_cb)));
}

void StreamTextureWrapperImpl::InitializeOnMainThread(
    const base::Closure& received_frame_cb,
    const StreamTextureWrapperInitCB& init_cb) {
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  DVLOG(2) << __func__;

  stream_texture_proxy_ = factory_->CreateProxy(
      kGLTextureExternalOES, &texture_id_, &texture_mailbox_);
  if (!stream_texture_proxy_) {
    init_cb.Run(false);
    return;
  }

  ReallocateVideoFrame(natural_size_);

  stream_texture_proxy_->BindToTaskRunner(received_frame_cb,
                                          compositor_task_runner_);

  init_cb.Run(true);
}

void StreamTextureWrapperImpl::Destroy() {
  // Safely stop StreamTextureProxy from signaling the arrival of new frames.
  // |stream_texture_proxy_| will be cleared on the main task runner, and its
  // deleter will destroy the underlying object on the right task runner.
  if (stream_texture_proxy_)
    stream_texture_proxy_->ClearReceivedFrameCB();

  if (!main_task_runner_->BelongsToCurrentThread()) {
    // base::Unretained is safe here because this function is the only one that
    // can can call delete.
    main_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&StreamTextureWrapperImpl::Destroy, base::Unretained(this)));
    return;
  }

  delete this;
}

}  // namespace content
