// Copyright 2011 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 "cc/layers/video_layer_impl.h"

#include <stddef.h>

#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "cc/layers/video_frame_provider_client_impl.h"
#include "cc/quads/stream_video_draw_quad.h"
#include "cc/quads/texture_draw_quad.h"
#include "cc/quads/yuv_video_draw_quad.h"
#include "cc/resources/resource_provider.h"
#include "cc/resources/single_release_callback_impl.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/occlusion.h"
#include "cc/trees/task_runner_provider.h"
#include "media/base/video_frame.h"
#include "ui/gfx/color_space.h"

namespace cc {

// static
std::unique_ptr<VideoLayerImpl> VideoLayerImpl::Create(
    LayerTreeImpl* tree_impl,
    int id,
    VideoFrameProvider* provider,
    media::VideoRotation video_rotation) {
  DCHECK(tree_impl->task_runner_provider()->IsMainThreadBlocked());
  DCHECK(tree_impl->task_runner_provider()->IsImplThread());

  scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl =
      VideoFrameProviderClientImpl::Create(
          provider, tree_impl->GetVideoFrameControllerClient());

  return base::WrapUnique(new VideoLayerImpl(
      tree_impl, id, std::move(provider_client_impl), video_rotation));
}

VideoLayerImpl::VideoLayerImpl(
    LayerTreeImpl* tree_impl,
    int id,
    scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl,
    media::VideoRotation video_rotation)
    : LayerImpl(tree_impl, id),
      provider_client_impl_(std::move(provider_client_impl)),
      frame_(nullptr),
      video_rotation_(video_rotation) {
  set_may_contain_video(true);
}

VideoLayerImpl::~VideoLayerImpl() {
  if (!provider_client_impl_->Stopped()) {
    // In impl side painting, we may have a pending and active layer
    // associated with the video provider at the same time. Both have a ref
    // on the VideoFrameProviderClientImpl, but we stop when the first
    // LayerImpl (the one on the pending tree) is destroyed since we know
    // the main thread is blocked for this commit.
    DCHECK(layer_tree_impl()->task_runner_provider()->IsImplThread());
    DCHECK(layer_tree_impl()->task_runner_provider()->IsMainThreadBlocked());
    provider_client_impl_->Stop();
  }
}

std::unique_ptr<LayerImpl> VideoLayerImpl::CreateLayerImpl(
    LayerTreeImpl* tree_impl) {
  return base::WrapUnique(new VideoLayerImpl(
      tree_impl, id(), provider_client_impl_, video_rotation_));
}

void VideoLayerImpl::DidBecomeActive() {
  provider_client_impl_->SetActiveVideoLayer(this);
}

bool VideoLayerImpl::WillDraw(DrawMode draw_mode,
                              ResourceProvider* resource_provider) {
  if (draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE)
    return false;

  // Explicitly acquire and release the provider mutex so it can be held from
  // WillDraw to DidDraw. Since the compositor thread is in the middle of
  // drawing, the layer will not be destroyed before DidDraw is called.
  // Therefore, the only thing that will prevent this lock from being released
  // is the GPU process locking it. As the GPU process can't cause the
  // destruction of the provider (calling StopUsingProvider), holding this
  // lock should not cause a deadlock.
  frame_ = provider_client_impl_->AcquireLockAndCurrentFrame();

  if (!frame_.get()) {
    // Drop any resources used by the updater if there is no frame to display.
    updater_ = nullptr;

    provider_client_impl_->ReleaseLock();
    return false;
  }

  if (!LayerImpl::WillDraw(draw_mode, resource_provider))
    return false;

  if (!updater_) {
    updater_.reset(
        new VideoResourceUpdater(layer_tree_impl()->context_provider(),
                                 layer_tree_impl()->resource_provider()));
  }

  VideoFrameExternalResources external_resources =
      updater_->CreateExternalResourcesFromVideoFrame(frame_);
  frame_resource_type_ = external_resources.type;

  if (external_resources.type ==
      VideoFrameExternalResources::SOFTWARE_RESOURCE) {
    software_resources_ = external_resources.software_resources;
    software_release_callback_ =
        external_resources.software_release_callback;
    return true;
  }
  frame_resource_offset_ = external_resources.offset;
  frame_resource_multiplier_ = external_resources.multiplier;
  frame_bits_per_channel_ = external_resources.bits_per_channel;

  DCHECK_EQ(external_resources.mailboxes.size(),
            external_resources.release_callbacks.size());
  ResourceProvider::ResourceIdArray resource_ids;
  resource_ids.reserve(external_resources.mailboxes.size());
  for (size_t i = 0; i < external_resources.mailboxes.size(); ++i) {
    unsigned resource_id = resource_provider->CreateResourceFromTextureMailbox(
        external_resources.mailboxes[i],
        SingleReleaseCallbackImpl::Create(
            external_resources.release_callbacks[i]),
        external_resources.read_lock_fences_enabled);
    frame_resources_.push_back(FrameResource(
        resource_id, external_resources.mailboxes[i].size_in_pixels(),
        external_resources.mailboxes[i].is_overlay_candidate()));
    resource_ids.push_back(resource_id);
  }
  resource_provider->GenerateSyncTokenForResources(resource_ids);

  return true;
}

void VideoLayerImpl::AppendQuads(RenderPass* render_pass,
                                 AppendQuadsData* append_quads_data) {
  DCHECK(frame_.get());

  gfx::Transform transform = DrawTransform();
  gfx::Size rotated_size = bounds();

  switch (video_rotation_) {
    case media::VIDEO_ROTATION_90:
      rotated_size = gfx::Size(rotated_size.height(), rotated_size.width());
      transform.Rotate(90.0);
      transform.Translate(0.0, -rotated_size.height());
      break;
    case media::VIDEO_ROTATION_180:
      transform.Rotate(180.0);
      transform.Translate(-rotated_size.width(), -rotated_size.height());
      break;
    case media::VIDEO_ROTATION_270:
      rotated_size = gfx::Size(rotated_size.height(), rotated_size.width());
      transform.Rotate(270.0);
      transform.Translate(-rotated_size.width(), 0);
    case media::VIDEO_ROTATION_0:
      break;
  }

  SharedQuadState* shared_quad_state =
      render_pass->CreateAndAppendSharedQuadState();
  shared_quad_state->SetAll(transform, rotated_size, visible_layer_rect(),
                            clip_rect(), is_clipped(), draw_opacity(),
                            draw_blend_mode(), sorting_context_id());

  AppendDebugBorderQuad(
      render_pass, rotated_size, shared_quad_state, append_quads_data);

  gfx::Rect quad_rect(rotated_size);
  gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect());
  gfx::Rect visible_rect = frame_->visible_rect();
  gfx::Size coded_size = frame_->coded_size();

  Occlusion occlusion_in_video_space =
      draw_properties()
          .occlusion_in_content_space.GetOcclusionWithGivenDrawTransform(
              transform);
  gfx::Rect visible_quad_rect =
      occlusion_in_video_space.GetUnoccludedContentRect(quad_rect);
  if (visible_quad_rect.IsEmpty())
    return;

  // Pixels for macroblocked formats. To prevent sampling outside the visible
  // rect, stretch the video if needed.
  gfx::Rect visible_sample_rect = frame_->visible_rect();
  if (visible_rect.width() < coded_size.width() && visible_rect.width() > 1)
    visible_sample_rect.set_width(visible_rect.width() - 1);
  if (visible_rect.height() < coded_size.height() && visible_rect.height() > 1)
    visible_sample_rect.set_height(visible_rect.height() - 1);
  const float tex_width_scale =
      static_cast<float>(visible_sample_rect.width()) / coded_size.width();
  const float tex_height_scale =
      static_cast<float>(visible_sample_rect.height()) / coded_size.height();

  switch (frame_resource_type_) {
    // TODO(danakj): Remove this, hide it in the hardware path.
    case VideoFrameExternalResources::SOFTWARE_RESOURCE: {
      DCHECK_EQ(frame_resources_.size(), 0u);
      DCHECK_EQ(software_resources_.size(), 1u);
      if (software_resources_.size() < 1u)
        break;
      bool premultiplied_alpha = true;
      gfx::PointF uv_top_left(0.f, 0.f);
      gfx::PointF uv_bottom_right(tex_width_scale, tex_height_scale);
      float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f};
      bool flipped = false;
      bool nearest_neighbor = false;
      TextureDrawQuad* texture_quad =
          render_pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
      texture_quad->SetNew(shared_quad_state, quad_rect, opaque_rect,
                           visible_quad_rect, software_resources_[0],
                           premultiplied_alpha, uv_top_left, uv_bottom_right,
                           SK_ColorTRANSPARENT, opacity, flipped,
                           nearest_neighbor, false);
      ValidateQuadResources(texture_quad);
      break;
    }
    case VideoFrameExternalResources::YUV_RESOURCE: {
      YUVVideoDrawQuad::ColorSpace color_space = YUVVideoDrawQuad::REC_601;
      int videoframe_color_space;
      if (frame_->metadata()->GetInteger(media::VideoFrameMetadata::COLOR_SPACE,
                                         &videoframe_color_space)) {
        if (videoframe_color_space == media::COLOR_SPACE_JPEG) {
          color_space = YUVVideoDrawQuad::JPEG;
        } else if (videoframe_color_space == media::COLOR_SPACE_HD_REC709) {
          color_space = YUVVideoDrawQuad::REC_709;
        }
      }

      const gfx::Size ya_tex_size = coded_size;
      gfx::Size uv_tex_size = media::VideoFrame::PlaneSize(
          frame_->format(), media::VideoFrame::kUPlane, coded_size);

      if (frame_->HasTextures()) {
        if (frame_->format() == media::PIXEL_FORMAT_NV12) {
          DCHECK_EQ(2u, frame_resources_.size());
        } else {
          DCHECK_EQ(media::PIXEL_FORMAT_I420, frame_->format());
          DCHECK_EQ(3u,
                    frame_resources_.size());  // Alpha is not supported yet.
        }
      } else {
        DCHECK(uv_tex_size ==
               media::VideoFrame::PlaneSize(
                   frame_->format(), media::VideoFrame::kVPlane, coded_size));
        DCHECK_GE(frame_resources_.size(), 3u);
        DCHECK(frame_resources_.size() <= 3 ||
               ya_tex_size == media::VideoFrame::PlaneSize(
                                  frame_->format(), media::VideoFrame::kAPlane,
                                  coded_size));
      }

      // Compute the UV sub-sampling factor based on the ratio between
      // |ya_tex_size| and |uv_tex_size|.
      float uv_subsampling_factor_x =
          static_cast<float>(ya_tex_size.width()) / uv_tex_size.width();
      float uv_subsampling_factor_y =
          static_cast<float>(ya_tex_size.height()) / uv_tex_size.height();
      gfx::RectF ya_tex_coord_rect(visible_sample_rect);
      gfx::RectF uv_tex_coord_rect(
          visible_sample_rect.x() / uv_subsampling_factor_x,
          visible_sample_rect.y() / uv_subsampling_factor_y,
          visible_sample_rect.width() / uv_subsampling_factor_x,
          visible_sample_rect.height() / uv_subsampling_factor_y);

      YUVVideoDrawQuad* yuv_video_quad =
          render_pass->CreateAndAppendDrawQuad<YUVVideoDrawQuad>();
      yuv_video_quad->SetNew(
          shared_quad_state, quad_rect, opaque_rect, visible_quad_rect,
          ya_tex_coord_rect, uv_tex_coord_rect, ya_tex_size, uv_tex_size,
          frame_resources_[0].id, frame_resources_[1].id,
          frame_resources_.size() > 2 ? frame_resources_[2].id
                                      : frame_resources_[1].id,
          frame_resources_.size() > 3 ? frame_resources_[3].id : 0, color_space,
          frame_->ColorSpace(), frame_resource_offset_,
          frame_resource_multiplier_, frame_bits_per_channel_);
      ValidateQuadResources(yuv_video_quad);
      break;
    }
    case VideoFrameExternalResources::RGBA_RESOURCE:
    case VideoFrameExternalResources::RGBA_PREMULTIPLIED_RESOURCE:
    case VideoFrameExternalResources::RGB_RESOURCE: {
      DCHECK_EQ(frame_resources_.size(), 1u);
      if (frame_resources_.size() < 1u)
        break;
      bool premultiplied_alpha =
          frame_resource_type_ ==
          VideoFrameExternalResources::RGBA_PREMULTIPLIED_RESOURCE;
      gfx::PointF uv_top_left(0.f, 0.f);
      gfx::PointF uv_bottom_right(tex_width_scale, tex_height_scale);
      float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f};
      bool flipped = false;
      bool nearest_neighbor = false;
      TextureDrawQuad* texture_quad =
          render_pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
      texture_quad->SetNew(shared_quad_state, quad_rect, opaque_rect,
                           visible_quad_rect, frame_resources_[0].id,
                           premultiplied_alpha, uv_top_left, uv_bottom_right,
                           SK_ColorTRANSPARENT, opacity, flipped,
                           nearest_neighbor, false);
      ValidateQuadResources(texture_quad);
      break;
    }
    case VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE: {
      DCHECK_EQ(frame_resources_.size(), 1u);
      if (frame_resources_.size() < 1u)
        break;
      gfx::Transform scale;
      scale.Scale(tex_width_scale, tex_height_scale);
      StreamVideoDrawQuad* stream_video_quad =
          render_pass->CreateAndAppendDrawQuad<StreamVideoDrawQuad>();
      stream_video_quad->SetNew(shared_quad_state, quad_rect, opaque_rect,
                                visible_quad_rect, frame_resources_[0].id,
                                frame_resources_[0].size_in_pixels, scale);
      ValidateQuadResources(stream_video_quad);
      break;
    }
    case VideoFrameExternalResources::NONE:
      NOTIMPLEMENTED();
      break;
  }
}

void VideoLayerImpl::DidDraw(ResourceProvider* resource_provider) {
  LayerImpl::DidDraw(resource_provider);

  DCHECK(frame_.get());

  if (frame_resource_type_ ==
      VideoFrameExternalResources::SOFTWARE_RESOURCE) {
    for (size_t i = 0; i < software_resources_.size(); ++i) {
      software_release_callback_.Run(gpu::SyncToken(), false,
                                     layer_tree_impl()
                                         ->task_runner_provider()
                                         ->blocking_main_thread_task_runner());
    }

    software_resources_.clear();
    software_release_callback_.Reset();
  } else {
    for (size_t i = 0; i < frame_resources_.size(); ++i)
      resource_provider->DeleteResource(frame_resources_[i].id);
    frame_resources_.clear();
  }

  provider_client_impl_->PutCurrentFrame();
  frame_ = nullptr;

  provider_client_impl_->ReleaseLock();
}

SimpleEnclosedRegion VideoLayerImpl::VisibleOpaqueRegion() const {
  // If we don't have a frame yet, then we don't have an opaque region.
  if (!provider_client_impl_->HasCurrentFrame())
    return SimpleEnclosedRegion();
  return LayerImpl::VisibleOpaqueRegion();
}

void VideoLayerImpl::ReleaseResources() {
  updater_ = nullptr;
}

void VideoLayerImpl::SetNeedsRedraw() {
  SetUpdateRect(gfx::UnionRects(update_rect(), gfx::Rect(bounds())));
  layer_tree_impl()->SetNeedsRedraw();
}

const char* VideoLayerImpl::LayerTypeAsString() const {
  return "cc::VideoLayerImpl";
}

}  // namespace cc
