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

#include <string.h>

#include <memory>

#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "media/base/android/sdk_media_codec_bridge.h"
#include "media/gpu/avda_shared_state.h"
#include "ui/gl/android/surface_texture.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/scoped_make_current.h"

namespace media {

AVDACodecImage::AVDACodecImage(
    const scoped_refptr<AVDASharedState>& shared_state,
    VideoCodecBridge* codec,
    const base::WeakPtr<gpu::gles2::GLES2Decoder>& decoder)
    : shared_state_(shared_state),
      codec_buffer_index_(kInvalidCodecBufferIndex),
      media_codec_(codec),
      decoder_(decoder),
      has_surface_texture_(false),
      texture_(0) {}

AVDACodecImage::~AVDACodecImage() {}

gfx::Size AVDACodecImage::GetSize() {
  return size_;
}

unsigned AVDACodecImage::GetInternalFormat() {
  return GL_RGBA;
}

bool AVDACodecImage::BindTexImage(unsigned target) {
  return false;
}

void AVDACodecImage::ReleaseTexImage(unsigned target) {}

bool AVDACodecImage::CopyTexImage(unsigned target) {
  if (!has_surface_texture_ || target != GL_TEXTURE_EXTERNAL_OES)
    return false;

  GLint bound_service_id = 0;
  glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &bound_service_id);
  // We insist that the currently bound texture is the right one.
  if (bound_service_id !=
      static_cast<GLint>(shared_state_->surface_texture_service_id())) {
    return false;
  }

  // Make sure that we have the right image in the front buffer.  Note that the
  // bound_service_id is guaranteed to be equal to the surface texture's client
  // texture id, so we can skip preserving it if the right context is current.
  UpdateSurfaceInternal(UpdateMode::RENDER_TO_FRONT_BUFFER,
                        kDontRestoreBindings);

  // By setting image state to UNBOUND instead of COPIED we ensure that
  // CopyTexImage() is called each time the surface texture is used for drawing.
  // It would be nice if we could do this via asking for the currently bound
  // Texture, but the active unit never seems to change.
  texture_->SetLevelImageState(GL_TEXTURE_EXTERNAL_OES, 0,
                               gpu::gles2::Texture::UNBOUND);

  return true;
}

bool AVDACodecImage::CopyTexSubImage(unsigned target,
                                     const gfx::Point& offset,
                                     const gfx::Rect& rect) {
  return false;
}

bool AVDACodecImage::ScheduleOverlayPlane(gfx::AcceleratedWidget widget,
                                          int z_order,
                                          gfx::OverlayTransform transform,
                                          const gfx::Rect& bounds_rect,
                                          const gfx::RectF& crop_rect) {
  // This should only be called when we're rendering to a SurfaceView.
  if (has_surface_texture_) {
    DVLOG(1) << "Invalid call to ScheduleOverlayPlane; this image is "
                "SurfaceTexture backed.";
    return false;
  }

  UpdateSurface(UpdateMode::RENDER_TO_FRONT_BUFFER);
  return true;
}

void AVDACodecImage::OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
                                  uint64_t process_tracing_id,
                                  const std::string& dump_name) {}

void AVDACodecImage::UpdateSurfaceTexture(RestoreBindingsMode mode) {
  DCHECK(has_surface_texture_);
  DCHECK_EQ(codec_buffer_index_, kUpdateOnly);
  codec_buffer_index_ = kRendered;

  // Swap the rendered image to the front.
  std::unique_ptr<ui::ScopedMakeCurrent> scoped_make_current =
      MakeCurrentIfNeeded();

  // If we changed contexts, then we always want to restore it, since the caller
  // doesn't know that we're switching contexts.
  if (scoped_make_current)
    mode = kDoRestoreBindings;

  // Save the current binding if requested.
  GLint bound_service_id = 0;
  if (mode == kDoRestoreBindings)
    glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &bound_service_id);

  shared_state_->UpdateTexImage();
  if (mode == kDoRestoreBindings)
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, bound_service_id);
}

void AVDACodecImage::UpdateSurface(UpdateMode update_mode) {
  UpdateSurfaceInternal(update_mode, kDoRestoreBindings);
}

void AVDACodecImage::CodecChanged(MediaCodecBridge* codec) {
  media_codec_ = codec;
  codec_buffer_index_ = kInvalidCodecBufferIndex;
}

void AVDACodecImage::SetBufferMetadata(int buffer_index,
                                       bool has_surface_texture,
                                       const gfx::Size& size) {
  has_surface_texture_ = has_surface_texture;
  codec_buffer_index_ = buffer_index;
  size_ = size;
}

bool AVDACodecImage::SetSharedState(
    scoped_refptr<AVDASharedState> shared_state) {
  if (shared_state == shared_state_)
    return false;
  shared_state_ = shared_state;
  return true;
}

void AVDACodecImage::UpdateSurfaceInternal(
    UpdateMode update_mode,
    RestoreBindingsMode attached_bindings_mode) {
  if (!IsCodecBufferOutstanding())
    return;

  ReleaseOutputBuffer(update_mode);

  // SurfaceViews are updated implicitly, so no further steps are necessary.
  if (!has_surface_texture_) {
    DCHECK(update_mode != UpdateMode::RENDER_TO_BACK_BUFFER);
    return;
  }

  // If front buffer rendering hasn't been requested, exit early.
  if (update_mode != UpdateMode::RENDER_TO_FRONT_BUFFER)
    return;

  UpdateSurfaceTexture(attached_bindings_mode);
}

void AVDACodecImage::ReleaseOutputBuffer(UpdateMode update_mode) {
  DCHECK(IsCodecBufferOutstanding());

  // In case of discard, simply discard and clear our codec buffer index.
  if (update_mode == UpdateMode::DISCARD_CODEC_BUFFER) {
    if (codec_buffer_index_ != kUpdateOnly)
      media_codec_->ReleaseOutputBuffer(codec_buffer_index_, false);

    // Note: No need to wait for the frame to be available in the kUpdateOnly
    // case since it will be or has been waited on by another release call.
    codec_buffer_index_ = kInvalidCodecBufferIndex;
    return;
  }

  DCHECK(update_mode == UpdateMode::RENDER_TO_BACK_BUFFER ||
         update_mode == UpdateMode::RENDER_TO_FRONT_BUFFER);

  if (!has_surface_texture_) {
    DCHECK(update_mode == UpdateMode::RENDER_TO_FRONT_BUFFER);
    DCHECK_GE(codec_buffer_index_, 0);
    media_codec_->ReleaseOutputBuffer(codec_buffer_index_, true);
    codec_buffer_index_ = kRendered;
    return;
  }

  // If we've already released to the back buffer, there's nothing left to do,
  // but wait for the previously released buffer if necessary.
  if (codec_buffer_index_ != kUpdateOnly) {
    DCHECK(has_surface_texture_);
    DCHECK_GE(codec_buffer_index_, 0);
    shared_state_->RenderCodecBufferToSurfaceTexture(media_codec_,
                                                     codec_buffer_index_);
    codec_buffer_index_ = kUpdateOnly;
  }

  // Only wait for the SurfaceTexture update if we're rendering to the front.
  if (update_mode == UpdateMode::RENDER_TO_FRONT_BUFFER)
    shared_state_->WaitForFrameAvailable();
}

std::unique_ptr<ui::ScopedMakeCurrent> AVDACodecImage::MakeCurrentIfNeeded() {
  DCHECK(shared_state_->context());
  // Remember: virtual contexts return true if and only if their shared context
  // is current, regardless of which virtual context it is.
  return std::unique_ptr<ui::ScopedMakeCurrent>(
      shared_state_->context()->IsCurrent(nullptr)
          ? nullptr
          : new ui::ScopedMakeCurrent(shared_state_->context(),
                                      shared_state_->surface()));
}

void AVDACodecImage::GetTextureMatrix(float matrix[16]) {
  // Our current matrix may be stale.  Update it if possible.
  if (has_surface_texture_)
    UpdateSurface(UpdateMode::RENDER_TO_FRONT_BUFFER);
  shared_state_->GetTransformMatrix(matrix);
  YInvertMatrix(matrix);
}

bool AVDACodecImage::IsCodecBufferOutstanding() const {
  static_assert(kUpdateOnly < 0 && kUpdateOnly > kRendered &&
                    kRendered > kInvalidCodecBufferIndex,
                "Codec buffer index enum values are not ordered correctly.");
  return codec_buffer_index_ > kRendered && media_codec_;
}

}  // namespace media
