Index: cc/resources/video_resource_updater.cc |
diff --git a/cc/resources/video_resource_updater.cc b/cc/resources/video_resource_updater.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ca2d3bb9d7c211ef818b9bed2bb25e8613107212 |
--- /dev/null |
+++ b/cc/resources/video_resource_updater.cc |
@@ -0,0 +1,326 @@ |
+// Copyright 2013 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/resources/video_resource_updater.h" |
+ |
+#include "base/bind.h" |
+#include "cc/output/gl_renderer.h" |
+#include "cc/resources/resource_provider.h" |
+#include "gpu/GLES2/gl2extchromium.h" |
+#include "media/base/video_frame.h" |
+#include "media/filters/skcanvas_video_renderer.h" |
+#include "third_party/khronos/GLES2/gl2.h" |
+#include "third_party/khronos/GLES2/gl2ext.h" |
+#include "ui/gfx/size_conversions.h" |
+ |
+const unsigned kYUVResourceFormat = GL_LUMINANCE; |
+const unsigned kRGBResourceFormat = GL_RGBA; |
+ |
+namespace cc { |
+ |
+VideoFrameExternalResources::VideoFrameExternalResources() : type(NONE) {} |
+ |
+VideoFrameExternalResources::~VideoFrameExternalResources() {} |
+ |
+VideoResourceUpdater::VideoResourceUpdater(ResourceProvider* resource_provider) |
+ : resource_provider_(resource_provider) { |
+} |
+ |
+VideoResourceUpdater::~VideoResourceUpdater() {} |
+ |
+bool VideoResourceUpdater::VerifyFrame( |
+ const scoped_refptr<media::VideoFrame>& video_frame) { |
+ // If these fail, we'll have to add logic that handles offset bitmap/texture |
+ // UVs. For now, just expect (0, 0) offset, since all our decoders so far |
+ // don't offset. |
+ DCHECK_EQ(video_frame->visible_rect().x(), 0); |
+ DCHECK_EQ(video_frame->visible_rect().y(), 0); |
+ |
+ switch (video_frame->format()) { |
+ // Acceptable inputs. |
+ case media::VideoFrame::YV12: |
+ case media::VideoFrame::YV16: |
+ case media::VideoFrame::NATIVE_TEXTURE: |
+#if defined(GOOGLE_TV) |
+ case media::VideoFrame::HOLE: |
+#endif |
+ return true; |
+ |
+ // Unacceptable inputs. ¯\(°_o)/¯ |
+ case media::VideoFrame::INVALID: |
+ case media::VideoFrame::RGB32: |
+ case media::VideoFrame::EMPTY: |
+ case media::VideoFrame::I420: |
+ break; |
+ } |
+ return false; |
+} |
+ |
+// For frames that we receive in software format, determine the dimensions of |
+// each plane in the frame. |
+static gfx::Size SoftwarePlaneDimension( |
+ media::VideoFrame::Format input_frame_format, |
+ gfx::Size coded_size, |
+ GLenum output_resource_format, |
+ int plane_index) { |
+ if (output_resource_format == kYUVResourceFormat) { |
+ if (plane_index == media::VideoFrame::kYPlane) |
+ return coded_size; |
+ |
+ switch (input_frame_format) { |
+ case media::VideoFrame::YV12: |
+ return gfx::ToFlooredSize(gfx::ScaleSize(coded_size, 0.5f, 0.5f)); |
+ case media::VideoFrame::YV16: |
+ return gfx::ToFlooredSize(gfx::ScaleSize(coded_size, 0.5f, 1.f)); |
+ |
+ case media::VideoFrame::INVALID: |
+ case media::VideoFrame::RGB32: |
+ case media::VideoFrame::EMPTY: |
+ case media::VideoFrame::I420: |
+ case media::VideoFrame::NATIVE_TEXTURE: |
+#if defined(GOOGLE_TV) |
+ case media::VideoFrame::HOLE: |
+#endif |
+ NOTREACHED(); |
+ } |
+ } |
+ |
+ DCHECK_EQ(output_resource_format, static_cast<unsigned>(kRGBResourceFormat)); |
+ return coded_size; |
+} |
+ |
+static void ReleaseResource(ResourceProvider* resource_provider, |
+ ResourceProvider::ResourceId resource_id, |
+ unsigned sync_point) { |
+ resource_provider->DeleteResource(resource_id); |
+} |
+ |
+VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes( |
+ const scoped_refptr<media::VideoFrame>& video_frame) { |
+ if (!VerifyFrame(video_frame)) |
+ return VideoFrameExternalResources(); |
+ |
+ media::VideoFrame::Format input_frame_format = video_frame->format(); |
+ |
+#if defined(GOOGLE_TV) |
+ if (input_frame_format == media::VideoFrame::HOLE) { |
+ VideoFrameExternalResources external_resources; |
+ external_resources.type = VideoFrameExternalResources::HOLE; |
+ return external_resources; |
+ } |
+#endif |
+ |
+ // Only YUV software video frames are supported. |
+ DCHECK(input_frame_format == media::VideoFrame::YV12 || |
+ input_frame_format == media::VideoFrame::YV16); |
+ if (input_frame_format != media::VideoFrame::YV12 && |
+ input_frame_format != media::VideoFrame::YV16) |
+ return VideoFrameExternalResources(); |
+ |
+ bool software_compositor = !resource_provider_->GraphicsContext3D(); |
+ |
+ GLenum output_resource_format = kYUVResourceFormat; |
+ size_t output_plane_count = 3; |
+ |
+ // TODO(skaslev): If we're in software compositing mode, we do the YUV -> RGB |
+ // conversion here. That involves an extra copy of each frame to a bitmap. |
+ // Obviously, this is suboptimal and should be addressed once ubercompositor |
+ // starts shaping up. |
+ if (software_compositor) { |
+ output_resource_format = kRGBResourceFormat; |
+ output_plane_count = 1; |
+ } |
+ |
+ int max_resource_size = resource_provider_->max_texture_size(); |
+ gfx::Size coded_frame_size = video_frame->coded_size(); |
+ |
+ ResourceProvider::ResourceIdArray plane_resources; |
+ bool allocation_success = true; |
+ |
+ for (size_t i = 0; i < output_plane_count; ++i) { |
+ gfx::Size plane_size = |
+ SoftwarePlaneDimension(input_frame_format, |
+ coded_frame_size, |
+ output_resource_format, |
+ i); |
+ if (plane_size.IsEmpty() || |
+ plane_size.width() > max_resource_size || |
+ plane_size.height() > max_resource_size) { |
+ allocation_success = false; |
+ break; |
+ } |
+ |
+ // TODO(danakj): Could recycle resources that we previously allocated and |
+ // were returned to us. |
+ ResourceProvider::ResourceId resource_id = |
+ resource_provider_->CreateResource(plane_size, |
+ output_resource_format, |
+ ResourceProvider::TextureUsageAny); |
+ if (resource_id == 0) { |
+ allocation_success = false; |
+ break; |
+ } |
+ |
+ plane_resources.push_back(resource_id); |
+ } |
+ |
+ if (!allocation_success) { |
+ for (size_t i = 0; i < plane_resources.size(); ++i) |
+ resource_provider_->DeleteResource(plane_resources[i]); |
+ return VideoFrameExternalResources(); |
+ } |
+ |
+ VideoFrameExternalResources external_resources; |
+ |
+ if (software_compositor) { |
+ DCHECK_EQ(output_resource_format, kRGBResourceFormat); |
+ DCHECK_EQ(plane_resources.size(), 1u); |
+ |
+ if (!video_renderer_) |
+ video_renderer_.reset(new media::SkCanvasVideoRenderer); |
+ |
+ { |
+ ResourceProvider::ScopedWriteLockSoftware lock( |
+ resource_provider_, plane_resources[0]); |
+ video_renderer_->Paint(video_frame, |
+ lock.sk_canvas(), |
+ video_frame->visible_rect(), |
+ 0xff); |
+ } |
+ |
+ // In software mode, the resource provider won't be lost. Soon this callback |
+ // will be called directly from the resource provider, same as 3d |
+ // compositing mode, so this raw unretained resource_provider will always |
+ // be valid when the callback is fired. |
+ TextureMailbox::ReleaseCallback callback_to_free_resource = |
+ base::Bind(&ReleaseResource, |
+ base::Unretained(resource_provider_), |
+ plane_resources[0]); |
+ external_resources.software_resources.push_back(plane_resources[0]); |
+ external_resources.software_release_callback = callback_to_free_resource; |
+ |
+ external_resources.type = VideoFrameExternalResources::SOFTWARE_RESOURCE; |
+ return external_resources; |
+ } |
+ |
+ DCHECK_EQ(output_resource_format, |
+ static_cast<unsigned>(kYUVResourceFormat)); |
+ |
+ WebKit::WebGraphicsContext3D* context = |
+ resource_provider_->GraphicsContext3D(); |
+ DCHECK(context); |
+ |
+ for (size_t plane = 0; plane < plane_resources.size(); ++plane) { |
+ // Update each plane's resource id with its content. |
+ ResourceProvider::ResourceId output_plane_resource_id = |
+ plane_resources[plane]; |
+ gfx::Size plane_size = |
+ SoftwarePlaneDimension(input_frame_format, |
+ coded_frame_size, |
+ output_resource_format, |
+ plane); |
+ const uint8_t* input_plane_pixels = video_frame->data(plane); |
+ |
+ gfx::Rect image_rect( |
+ 0, 0, video_frame->stride(plane), plane_size.height()); |
+ gfx::Rect source_rect(plane_size); |
+ resource_provider_->SetPixels(output_plane_resource_id, |
+ input_plane_pixels, |
+ image_rect, |
+ source_rect, |
+ gfx::Vector2d()); |
+ |
+ gpu::Mailbox mailbox; |
+ { |
+ ResourceProvider::ScopedWriteLockGL lock( |
+ resource_provider_, output_plane_resource_id); |
+ |
+ GLC(context, context->genMailboxCHROMIUM(mailbox.name)); |
+ GLC(context, context->bindTexture(GL_TEXTURE_2D, lock.texture_id())); |
+ GLC(context, context->produceTextureCHROMIUM(GL_TEXTURE_2D, |
+ mailbox.name)); |
+ GLC(context, context->bindTexture(GL_TEXTURE_2D, 0)); |
+ } |
+ |
+ // This callback is called by the resource provider itself, so it's okay to |
+ // use an unretained raw pointer here. |
+ TextureMailbox::ReleaseCallback callback_to_free_resource = |
+ base::Bind(&ReleaseResource, |
+ base::Unretained(resource_provider_), |
+ output_plane_resource_id); |
+ external_resources.mailboxes.push_back( |
+ TextureMailbox(mailbox, callback_to_free_resource)); |
+ } |
+ |
+ external_resources.type = VideoFrameExternalResources::YUV_RESOURCE; |
+ return external_resources; |
+} |
+ |
+VideoFrameExternalResources VideoResourceUpdater::CreateForHardwarePlanes( |
+ const scoped_refptr<media::VideoFrame>& video_frame, |
+ const TextureMailbox::ReleaseCallback& release_callback) { |
+ if (!VerifyFrame(video_frame)) |
+ return VideoFrameExternalResources(); |
+ |
+ media::VideoFrame::Format frame_format = video_frame->format(); |
+ |
+ DCHECK_EQ(frame_format, media::VideoFrame::NATIVE_TEXTURE); |
+ if (frame_format != media::VideoFrame::NATIVE_TEXTURE) |
+ return VideoFrameExternalResources(); |
+ |
+ WebKit::WebGraphicsContext3D* context = |
+ resource_provider_->GraphicsContext3D(); |
+ if (!context) |
+ return VideoFrameExternalResources(); |
+ |
+ VideoFrameExternalResources external_resources; |
+ switch (video_frame->texture_target()) { |
+ case GL_TEXTURE_2D: |
+ external_resources.type = VideoFrameExternalResources::RGB_RESOURCE; |
+ break; |
+ case GL_TEXTURE_EXTERNAL_OES: |
+ external_resources.type = |
+ VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE; |
+ break; |
+ case GL_TEXTURE_RECTANGLE_ARB: |
+ external_resources.type = VideoFrameExternalResources::IO_SURFACE; |
+ break; |
+ default: |
+ NOTREACHED(); |
+ return VideoFrameExternalResources(); |
+ } |
+ |
+ gpu::Mailbox mailbox; |
+ GLC(context, context->genMailboxCHROMIUM(mailbox.name)); |
+ GLC(context, context->bindTexture(GL_TEXTURE_2D, video_frame->texture_id())); |
+ GLC(context, context->produceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name)); |
+ GLC(context, context->bindTexture(GL_TEXTURE_2D, 0)); |
+ |
+ TextureMailbox::ReleaseCallback callback_to_return_resource = |
+ base::Bind(&ReturnTexture, |
+ base::Unretained(resource_provider_), |
+ release_callback, |
+ video_frame->texture_id(), |
+ mailbox); |
+ external_resources.mailboxes.push_back( |
+ TextureMailbox(mailbox, callback_to_return_resource)); |
+ return external_resources; |
+} |
+ |
+// static |
+void VideoResourceUpdater::ReturnTexture( |
+ ResourceProvider* resource_provider, |
+ TextureMailbox::ReleaseCallback callback, |
+ unsigned texture_id, |
+ gpu::Mailbox mailbox, |
+ unsigned sync_point) { |
+ WebKit::WebGraphicsContext3D* context = |
+ resource_provider->GraphicsContext3D(); |
+ GLC(context, context->bindTexture(GL_TEXTURE_2D, texture_id)); |
+ GLC(context, context->consumeTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name)); |
+ GLC(context, context->bindTexture(GL_TEXTURE_2D, 0)); |
+ callback.Run(sync_point); |
+} |
+ |
+} // namespace cc |