// Copyright 2015 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 "media/renderers/video_overlay_factory.h"

#include "base/single_thread_task_runner.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "media/base/video_frame.h"
#include "media/renderers/gpu_video_accelerator_factories.h"

namespace media {

class VideoOverlayFactory::Texture {
 public:
  explicit Texture(GpuVideoAcceleratorFactories* gpu_factories)
      : gpu_factories_(gpu_factories), image_id_(0), texture_id_(0) {
    DCHECK(gpu_factories_);
    DCHECK(gpu_factories_->GetTaskRunner()->BelongsToCurrentThread());

    std::unique_ptr<GpuVideoAcceleratorFactories::ScopedGLContextLock> lock(
        gpu_factories_->GetGLContextLock());
    if (lock) {
      gpu::gles2::GLES2Interface* gl = lock->ContextGL();
      image_id_ = gl->CreateGpuMemoryBufferImageCHROMIUM(
          1, 1, GL_RGBA, GL_READ_WRITE_CHROMIUM);
      if (image_id_) {
        gl->GenTextures(1, &texture_id_);
        gl->BindTexture(GL_TEXTURE_2D, texture_id_);
        gl->BindTexImage2DCHROMIUM(GL_TEXTURE_2D, image_id_);

        gl->GenMailboxCHROMIUM(mailbox_.name);
        gl->ProduceTextureDirectCHROMIUM(texture_id_, GL_TEXTURE_2D,
                                         mailbox_.name);

        const GLuint64 fence_sync = gl->InsertFenceSyncCHROMIUM();
        gl->ShallowFlushCHROMIUM();
        gl->GenSyncTokenCHROMIUM(fence_sync, sync_token_.GetData());
      }
    }
  }

  ~Texture() {
    DCHECK(gpu_factories_->GetTaskRunner()->BelongsToCurrentThread());

    if (image_id_) {
      std::unique_ptr<GpuVideoAcceleratorFactories::ScopedGLContextLock> lock(
          gpu_factories_->GetGLContextLock());
      if (lock) {
        gpu::gles2::GLES2Interface* gl = lock->ContextGL();
        gl->BindTexture(GL_TEXTURE_2D, texture_id_);
        gl->ReleaseTexImage2DCHROMIUM(GL_TEXTURE_2D, image_id_);
        gl->DeleteTextures(1, &texture_id_);
        gl->DestroyImageCHROMIUM(image_id_);
      }
    }
  }

  bool IsValid() const { return image_id_ != 0; }

 private:
  friend class VideoOverlayFactory;
  GpuVideoAcceleratorFactories* gpu_factories_;

  GLuint image_id_;
  GLuint texture_id_;
  gpu::Mailbox mailbox_;
  gpu::SyncToken sync_token_;
};

VideoOverlayFactory::VideoOverlayFactory(
    GpuVideoAcceleratorFactories* gpu_factories)
    : gpu_factories_(gpu_factories) {}

VideoOverlayFactory::~VideoOverlayFactory() {}

scoped_refptr<VideoFrame> VideoOverlayFactory::CreateFrame(
    const gfx::Size& size) {
  // Frame size empty => video has one dimension = 0.
  // Dimension 0 case triggers a DCHECK later on in TextureMailbox if we push
  // through the overlay path.
  Texture* texture = size.IsEmpty() ? nullptr : GetTexture();
  if (!texture) {
    DVLOG(1) << "Create black frame " << size.width() << "x" << size.height();
    return VideoFrame::CreateBlackFrame(gfx::Size(1, 1));
  }

  DCHECK(texture);
  DCHECK(texture->IsValid());
  DVLOG(2) << "Create video overlay frame: " << size.ToString();
  gpu::MailboxHolder holders[VideoFrame::kMaxPlanes] = {gpu::MailboxHolder(
      texture->mailbox_, texture->sync_token_, GL_TEXTURE_2D)};
  scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures(
      PIXEL_FORMAT_XRGB, holders, VideoFrame::ReleaseMailboxCB(),
      size,                // coded_size
      gfx::Rect(size),     // visible rect
      size,                // natural size
      base::TimeDelta());  // timestamp
  CHECK(frame);
  frame->metadata()->SetBoolean(VideoFrameMetadata::ALLOW_OVERLAY, true);
  // TODO(halliwell): this is to block idle suspend behaviour on Chromecast.
  // Find a better way to do this.
  frame->metadata()->SetBoolean(VideoFrameMetadata::DECODER_OWNS_FRAME, true);
  return frame;
}

VideoOverlayFactory::Texture* VideoOverlayFactory::GetTexture() {
  if (!gpu_factories_)
    return nullptr;

  // Lazily create overlay texture.
  if (!texture_)
    texture_.reset(new Texture(gpu_factories_));

  return texture_->IsValid() ? texture_.get() : nullptr;
}

}  // namespace media
