// 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 "base/bind.h"
#include "base/logging.h"
#include "cc/layers/video_frame_provider_client_impl.h"
#include "cc/quads/io_surface_draw_quad.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_tracker.h"
#include "cc/trees/proxy.h"
#include "media/base/video_frame.h"

#if defined(VIDEO_HOLE)
#include "cc/quads/solid_color_draw_quad.h"
#endif  // defined(VIDEO_HOLE)

namespace cc {

// static
scoped_ptr<VideoLayerImpl> VideoLayerImpl::Create(
    LayerTreeImpl* tree_impl,
    int id,
    VideoFrameProvider* provider,
    media::VideoRotation video_rotation) {
  scoped_ptr<VideoLayerImpl> layer(
      new VideoLayerImpl(tree_impl, id, video_rotation));
  layer->SetProviderClientImpl(VideoFrameProviderClientImpl::Create(provider));
  DCHECK(tree_impl->proxy()->IsImplThread());
  DCHECK(tree_impl->proxy()->IsMainThreadBlocked());
  return layer.Pass();
}

VideoLayerImpl::VideoLayerImpl(LayerTreeImpl* tree_impl,
                               int id,
                               media::VideoRotation video_rotation)
    : LayerImpl(tree_impl, id), frame_(NULL), video_rotation_(video_rotation) {
}

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()->proxy()->IsImplThread());
    DCHECK(layer_tree_impl()->proxy()->IsMainThreadBlocked());
    provider_client_impl_->Stop();
  }
}

scoped_ptr<LayerImpl> VideoLayerImpl::CreateLayerImpl(
    LayerTreeImpl* tree_impl) {
  VideoLayerImpl* impl = new VideoLayerImpl(tree_impl, id(), video_rotation_);
  return scoped_ptr<LayerImpl>(impl);
}

void VideoLayerImpl::PushPropertiesTo(LayerImpl* layer) {
  LayerImpl::PushPropertiesTo(layer);

  VideoLayerImpl* other = static_cast<VideoLayerImpl*>(layer);
  other->SetProviderClientImpl(provider_client_impl_);
}

void VideoLayerImpl::DidBecomeActive() {
  provider_client_impl_->set_active_video_layer(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_.reset();

    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;
  }

  DCHECK_EQ(external_resources.mailboxes.size(),
            external_resources.release_callbacks.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]));
    frame_resources_.push_back(resource_id);
  }

  return true;
}

void VideoLayerImpl::AppendQuads(
    RenderPass* render_pass,
    const OcclusionTracker<LayerImpl>& occlusion_tracker,
    AppendQuadsData* append_quads_data) {
  DCHECK(frame_.get());

  gfx::Transform transform = draw_transform();
  gfx::Size rotated_size = content_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_content_rect(),
                            clip_rect(),
                            is_clipped(),
                            draw_opacity(),
                            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();

  gfx::Rect visible_quad_rect =
      occlusion_tracker.GetCurrentOcclusionForLayer(transform)
          .GetUnoccludedContentRect(quad_rect);
  if (visible_quad_rect.IsEmpty())
    return;

  // Pixels for macroblocked formats.
  const float tex_width_scale =
      static_cast<float>(visible_rect.width()) / coded_size.width();
  const float tex_height_scale =
      static_cast<float>(visible_rect.height()) / coded_size.height();
  const float tex_x_offset =
      static_cast<float>(visible_rect.x()) / coded_size.width();
  const float tex_y_offset =
      static_cast<float>(visible_rect.y()) / 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;
      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);
      break;
    }
    case VideoFrameExternalResources::YUV_RESOURCE: {
      DCHECK_GE(frame_resources_.size(), 3u);
      if (frame_resources_.size() < 3u)
        break;
      YUVVideoDrawQuad::ColorSpace color_space =
          frame_->format() == media::VideoFrame::YV12J
              ? YUVVideoDrawQuad::REC_601_JPEG
              : YUVVideoDrawQuad::REC_601;
      gfx::RectF tex_coord_rect(
          tex_x_offset, tex_y_offset, tex_width_scale, tex_height_scale);
      YUVVideoDrawQuad* yuv_video_quad =
          render_pass->CreateAndAppendDrawQuad<YUVVideoDrawQuad>();
      yuv_video_quad->SetNew(
          shared_quad_state,
          quad_rect,
          opaque_rect,
          visible_quad_rect,
          tex_coord_rect,
          frame_resources_[0],
          frame_resources_[1],
          frame_resources_[2],
          frame_resources_.size() > 3 ? frame_resources_[3] : 0,
          color_space);
      break;
    }
    case VideoFrameExternalResources::RGB_RESOURCE: {
      DCHECK_EQ(frame_resources_.size(), 1u);
      if (frame_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;
      TextureDrawQuad* texture_quad =
          render_pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
      texture_quad->SetNew(shared_quad_state,
                           quad_rect,
                           opaque_rect,
                           visible_quad_rect,
                           frame_resources_[0],
                           premultiplied_alpha,
                           uv_top_left,
                           uv_bottom_right,
                           SK_ColorTRANSPARENT,
                           opacity,
                           flipped);
      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],
          scale * provider_client_impl_->stream_texture_matrix());
      break;
    }
    case VideoFrameExternalResources::IO_SURFACE: {
      DCHECK_EQ(frame_resources_.size(), 1u);
      if (frame_resources_.size() < 1u)
        break;
      IOSurfaceDrawQuad* io_surface_quad =
          render_pass->CreateAndAppendDrawQuad<IOSurfaceDrawQuad>();
      io_surface_quad->SetNew(shared_quad_state,
                              quad_rect,
                              opaque_rect,
                              visible_quad_rect,
                              visible_rect.size(),
                              frame_resources_[0],
                              IOSurfaceDrawQuad::UNFLIPPED);
      break;
    }
#if defined(VIDEO_HOLE)
    // This block and other blocks wrapped around #if defined(GOOGLE_TV) is not
    // maintained by the general compositor team. Please contact the following
    // people instead:
    //
    // wonsik@chromium.org
    // ycheo@chromium.org
    case VideoFrameExternalResources::HOLE: {
      DCHECK_EQ(frame_resources_.size(), 0u);
      SolidColorDrawQuad* solid_color_draw_quad =
          render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();

      // Create a solid color quad with transparent black and force no
      // blending / no anti-aliasing.
      gfx::Rect opaque_rect = quad_rect;
      solid_color_draw_quad->SetAll(shared_quad_state,
                                    quad_rect,
                                    opaque_rect,
                                    visible_quad_rect,
                                    false,
                                    SK_ColorTRANSPARENT,
                                    true);
      break;
    }
#endif  // defined(VIDEO_HOLE)
    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(
          0, false, layer_tree_impl()->BlockingMainThreadTaskRunner());
    }

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

  provider_client_impl_->PutCurrentFrame(frame_);
  frame_ = NULL;

  provider_client_impl_->ReleaseLock();
}

void VideoLayerImpl::ReleaseResources() {
  updater_.reset();
}

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

void VideoLayerImpl::SetProviderClientImpl(
    scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl) {
  provider_client_impl_ = provider_client_impl;
}

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

}  // namespace cc