Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(57)

Unified Diff: media/video/gpu_memory_buffer_video_frame_pool.cc

Issue 1133563010: Add a GpuMemoryBuffer pool that creates hardware backed VideoFrames. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix a mem leak. Remove TODO in VideoRendererImpl. Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: media/video/gpu_memory_buffer_video_frame_pool.cc
diff --git a/media/video/gpu_memory_buffer_video_frame_pool.cc b/media/video/gpu_memory_buffer_video_frame_pool.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c60dd0c6eeb11506d62cf90d5db482d077d17c22
--- /dev/null
+++ b/media/video/gpu_memory_buffer_video_frame_pool.cc
@@ -0,0 +1,350 @@
+// Copyright (c) 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/video/gpu_memory_buffer_video_frame_pool.h"
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <list>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/containers/stack_container.h"
+#include "base/location.h"
+#include "base/memory/linked_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/trace_event/trace_event.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "media/renderers/gpu_video_accelerator_factories.h"
+
+namespace media {
+
+// Implementation of a pool of GpuMemoryBuffers used to back VideoFrames.
+class GpuMemoryBufferVideoFramePool::PoolImpl
+ : public base::RefCountedThreadSafe<
+ GpuMemoryBufferVideoFramePool::PoolImpl> {
+ public:
+ // 'task_runner' is associated to the thread where the context of
DaleCurtis 2015/05/14 00:58:58 Style is || instead of '' for designating variable
Daniele Castagna 2015/05/14 17:30:26 Done.
+ // GLES2Interface returned by 'gpu_factories' lives.
+ // 'gpu_factories' is an interface to GPU related operation and can be
+ // null.
+ PoolImpl(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories)
+ : task_runner_(task_runner), gpu_factories_(gpu_factories) {}
+
+ // Takes a software VideoFrame and returns a VideoFrame backed by native
+ // textures if possible.
+ // The data contained in video_frame is copied into the returned frame.
+ const scoped_refptr<VideoFrame> CreateHardwareFrame(
DaleCurtis 2015/05/14 00:58:57 Remove const
Daniele Castagna 2015/05/14 17:30:26 Done.
+ scoped_refptr<VideoFrame> video_frame);
DaleCurtis 2015/05/14 00:58:58 const&
Daniele Castagna 2015/05/14 17:30:26 Done.
+
+ private:
+ friend class base::RefCountedThreadSafe<
+ GpuMemoryBufferVideoFramePool::PoolImpl>;
+ ~PoolImpl();
+
+ // Resource to represent a plane.
+ struct PlaneResource {
+ gfx::Size size;
+ scoped_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer;
+ unsigned texture_id = 0u;
+ unsigned image_id = 0u;
+ gpu::Mailbox mailbox;
+ };
+
+ // All the resources needed to compose a frame.
+ struct FrameResources {
+ FrameResources(VideoFrame::Format format, const gfx::Size& size)
+ : format(format), size(size) {}
+ bool in_use = true;
+ VideoFrame::Format format;
+ gfx::Size size;
+ PlaneResource plane_resources[VideoFrame::kMaxPlanes];
+ };
+
+ // Return true if 'resources' can be used to represent a frame for
+ // specific 'format' and 'size'.
+ static bool IsFrameResourcesCompatible(const FrameResources* resources,
+ const gfx::Size& size,
+ VideoFrame::Format format) {
+ return size == resources->size && format == resources->format;
+ }
+
+ // Get the resources needed for a frame out of the pool, or create them if
+ // necessary.
+ // This also drops the LRU resources that can't be reuse for this frame.
+ FrameResources* GetOrCreateFrameResources(const gfx::Size& size,
+ VideoFrame::Format format);
+
+ // Callback called when a VideoFrame generated with GetFrameResources is no
+ // longer referenced.
+ // This could be called by any thread.
+ void MailboxHoldersReleased(FrameResources* frame_resources,
+ uint32 sync_point);
+
+ // Return frame resources to the pool. This has to be called on the thread
+ // where 'task_runner' is current.
+ void ReturnFrameResources(FrameResources* frame_resources);
+
+ // Delete resources. This has to be called on the thread where 'task_runner'
+ // is current.
+ static void DeleteFrameResources(
+ const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories,
+ FrameResources* frame_resources);
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories_;
+
+ // Pool of resources.
+ std::list<FrameResources*> resources_pool_;
+
+ unsigned texture_target_ = GL_TEXTURE_2D;
+ DISALLOW_COPY_AND_ASSIGN(PoolImpl);
+};
+
+namespace {
+
+// Copy a buffer info a GpuMemoryBuffer.
+// bytes_per_row is expected to be less or equal than the strides of the two
DaleCurtis 2015/05/14 00:58:57 bytes_per_row and all other variables in comments,
Daniele Castagna 2015/05/14 17:30:26 Done.
+// buffers.
+void CopyPlaneToGpuMemoryBuffer(int rows,
+ int bytes_per_row,
+ const uint8* source,
+ int source_stride,
+ gfx::GpuMemoryBuffer* buffer) {
+ TRACE_EVENT2("media", "CopyPlaneToGpuMemoryBuffer", "bytes_per_row",
+ bytes_per_row, "rows", rows);
+
+ DCHECK(buffer);
+ DCHECK(source);
+ void* data = nullptr;
+ CHECK(buffer->Map(&data));
+ uint8* mapped_buffer = static_cast<uint8*>(data);
+ int dest_stride = 0;
+ buffer->GetStride(&dest_stride);
+ DCHECK_NE(dest_stride, 0);
+ DCHECK_LE(bytes_per_row, std::abs(dest_stride));
+ DCHECK_LE(bytes_per_row, source_stride);
+ for (int row = 0; row < rows; ++row) {
+ memcpy(mapped_buffer + dest_stride * row, source + source_stride * row,
+ bytes_per_row);
+ }
+ buffer->Unmap();
+}
+
+} // unnamed namespace
+
+// Creates a VideoFrame backed by native textures starting from a software
DaleCurtis 2015/05/14 00:58:58 Again, strongly prefer if the comment was reflowed
Daniele Castagna 2015/05/14 17:30:26 Done.
+// VideoFrame.
+// The data contained in video_frame is copied into the returned VideoFrame.
+// This method first tries to reuse resources and drops the incompatible ones.
+// If resources are not available, they will be created.
+// The returned VideoFrame will call
+// GpuMemoryBufferVideoFramePool::PoolImpl::MailboxHoldersReleased once the
+// VideoFrame is not referenced anymore and the resources will be put back
+// in the pool.
+const scoped_refptr<VideoFrame>
+GpuMemoryBufferVideoFramePool::PoolImpl::CreateHardwareFrame(
DaleCurtis 2015/05/14 00:58:57 Fix const, const& stuff
Daniele Castagna 2015/05/14 17:30:26 Done.
+ scoped_refptr<VideoFrame> video_frame) {
+ if (!gpu_factories_)
+ return video_frame;
+
+ gpu::gles2::GLES2Interface* gles2 = gpu_factories_->GetGLES2Interface();
+ if (!gles2)
+ return video_frame;
+
+ VideoFrame::Format format = video_frame->format();
+ size_t planes = VideoFrame::NumPlanes(format);
+ DCHECK(video_frame->visible_rect().origin().IsOrigin());
+ gfx::Size size = video_frame->visible_rect().size();
+ gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes];
+ FrameResources* frame_resources = GetOrCreateFrameResources(size, format);
+
+ for (size_t i = 0; i < planes; ++i) {
+ PlaneResource& plane_resource = frame_resources->plane_resources[i];
+ CopyPlaneToGpuMemoryBuffer(VideoFrame::Rows(i, format, size.height()),
+ VideoFrame::RowBytes(i, format, size.width()),
+ video_frame->data(i), video_frame->stride(i),
+ plane_resource.gpu_memory_buffer.get());
+
+ // Bind the texture and create or rebind the image.
+ gles2->BindTexture(texture_target_, plane_resource.texture_id);
+ if (plane_resource.gpu_memory_buffer && !plane_resource.image_id) {
+ const size_t width = VideoFrame::Columns(i, format, size.width());
+ const size_t height = VideoFrame::Rows(i, format, size.height());
+ plane_resource.image_id = gles2->CreateImageCHROMIUM(
+ plane_resource.gpu_memory_buffer->AsClientBuffer(), width, height,
+ GL_R8_EXT);
+ } else {
+ gles2->ReleaseTexImage2DCHROMIUM(texture_target_,
+ plane_resource.image_id);
+ }
+ gles2->BindTexImage2DCHROMIUM(texture_target_, plane_resource.image_id);
+ mailbox_holders[i] =
+ gpu::MailboxHolder(plane_resource.mailbox, texture_target_, 0);
+ }
+
+ unsigned sync_point = gles2->InsertSyncPointCHROMIUM();
+ for (size_t i = 0; i < planes; ++i) {
+ mailbox_holders[i].sync_point = sync_point;
+ }
+
+ return VideoFrame::WrapYUV420NativeTextures(
+ mailbox_holders[VideoFrame::kYPlane],
+ mailbox_holders[VideoFrame::kUPlane],
+ mailbox_holders[VideoFrame::kVPlane],
+ base::Bind(&PoolImpl::MailboxHoldersReleased, this, frame_resources),
+ size, video_frame->visible_rect(), video_frame->natural_size(),
+ video_frame->timestamp(), video_frame->allow_overlay());
+}
+
+// Destroy all the resources posting one task per FrameResources
+// to the 'task_runner_'.
+GpuMemoryBufferVideoFramePool::PoolImpl::~PoolImpl() {
+ // Delete all the resources on the media thread.
+ while (!resources_pool_.empty()) {
+ FrameResources* frame_resources = resources_pool_.front();
+ resources_pool_.pop_front();
+ task_runner_->PostTask(
+ FROM_HERE, base::Bind(&PoolImpl::DeleteFrameResources, gpu_factories_,
+ base::Owned(frame_resources)));
+ }
+}
+
+// Tries to find the resources in the pool or create them.
+// Incompatible resources will be dropped.
+GpuMemoryBufferVideoFramePool::PoolImpl::FrameResources*
DaleCurtis 2015/05/14 00:58:58 Why not scoped_ptr?
Daniele Castagna 2015/05/14 17:30:26 As per reveman's suggestion we decided to leave Fr
DaleCurtis 2015/05/14 18:52:13 I'm not sure about that logic, but up to you :) So
Daniele Castagna 2015/05/14 19:24:16 I agree that it can be confusing, at least this me
DaleCurtis 2015/05/14 20:04:49 I was expected that resources_pool_ would be a Sco
+GpuMemoryBufferVideoFramePool::PoolImpl::GetOrCreateFrameResources(
+ const gfx::Size& size,
+ VideoFrame::Format format) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ auto it = resources_pool_.begin();
+ while (it != resources_pool_.end()) {
+ FrameResources* frame_resources = *it;
+ if (!frame_resources->in_use) {
+ if (IsFrameResourcesCompatible(frame_resources, size, format)) {
+ frame_resources->in_use = true;
+ return frame_resources;
+ } else {
+ resources_pool_.erase(it++);
+ DeleteFrameResources(gpu_factories_, frame_resources);
+ delete frame_resources;
+ }
+ } else {
+ it++;
+ }
+ }
+
+ // Create the resources.
+ gpu::gles2::GLES2Interface* gles2 = gpu_factories_->GetGLES2Interface();
+ DCHECK(gles2);
+ gles2->ActiveTexture(GL_TEXTURE0);
+ size_t planes = VideoFrame::NumPlanes(format);
+ FrameResources* frame_resources = new FrameResources(format, size);
+ resources_pool_.push_back(frame_resources);
+ for (size_t i = 0; i < planes; ++i) {
DaleCurtis 2015/05/14 00:58:57 Don't need i it looks like, so why not for (const
Daniele Castagna 2015/05/14 17:30:26 |planes| might be less than arraysize(frame_resour
+ PlaneResource& plane_resource = frame_resources->plane_resources[i];
+ const size_t width = VideoFrame::Columns(i, format, size.width());
+ const size_t height = VideoFrame::Rows(i, format, size.height());
+ const gfx::Size plane_size(width, height);
+ plane_resource.gpu_memory_buffer = gpu_factories_->AllocateGpuMemoryBuffer(
+ plane_size, gfx::GpuMemoryBuffer::R_8, gfx::GpuMemoryBuffer::MAP);
+
+ gles2->GenTextures(1, &plane_resource.texture_id);
+ gles2->BindTexture(texture_target_, plane_resource.texture_id);
+ gles2->TexParameteri(texture_target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gles2->TexParameteri(texture_target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gles2->TexParameteri(texture_target_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gles2->TexParameteri(texture_target_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ gles2->GenMailboxCHROMIUM(plane_resource.mailbox.name);
+ gles2->ProduceTextureCHROMIUM(texture_target_, plane_resource.mailbox.name);
+ }
+ return frame_resources;
+}
+
+// static
+void GpuMemoryBufferVideoFramePool::PoolImpl::DeleteFrameResources(
+ const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories,
+ FrameResources* frame_resources) {
+ // TODO(dcastagna): As soon as the context lost is dealt with in media,
DaleCurtis 2015/05/14 00:58:57 Seems pretty important to fix sooner than later :)
Daniele Castagna 2015/05/14 17:30:26 Absolutely. I talked with posciak@ about this and
DaleCurtis 2015/05/14 18:52:13 Depends on the failure mode, if it dangles use-aft
Daniele Castagna 2015/05/14 19:24:16 There is no use-after-frees and the resources are
+ // make sure that we won't execture this callback (use a weak pointer to
+ // the old context).
+ gpu::gles2::GLES2Interface* gles2 = gpu_factories->GetGLES2Interface();
+ if (!gles2)
+ return;
+
+ for (PlaneResource& plane_resource : frame_resources->plane_resources) {
+ if (plane_resource.image_id)
+ gles2->DestroyImageCHROMIUM(plane_resource.image_id);
+ if (plane_resource.texture_id)
+ gles2->DeleteTextures(1, &plane_resource.texture_id);
+ }
+}
+
+// Called when a VideoFrame is no longer references.
+void GpuMemoryBufferVideoFramePool::PoolImpl::MailboxHoldersReleased(
+ FrameResources* frame_resources,
+ uint32 sync_point) {
+ // Return the resource on the media thread.
+ task_runner_->PostTask(FROM_HERE, base::Bind(&PoolImpl::ReturnFrameResources,
+ this, frame_resources));
+}
+
+// Put back the resoruces in the pool.
+void GpuMemoryBufferVideoFramePool::PoolImpl::ReturnFrameResources(
+ FrameResources* frame_resources) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ auto it = std::find(resources_pool_.begin(), resources_pool_.end(),
DaleCurtis 2015/05/14 00:58:58 These will be released extremely frequently, so yo
Daniele Castagna 2015/05/14 17:30:27 I'll take a look at this before enabling the pool.
+ frame_resources);
+ DCHECK(it != resources_pool_.end());
DaleCurtis 2015/05/14 00:58:57 DCHECK_NE?
Daniele Castagna 2015/05/14 17:30:26 That doesn't work with iterators. :(
+ // We want the pool to behave in a FIFO way.
+ // This minimizes the chances of locking the buffer that might be
+ // still needed for drawing.
+ std::swap(*it, resources_pool_.back());
+ frame_resources->in_use = false;
+}
+
+GpuMemoryBufferVideoFramePool::GpuMemoryBufferVideoFramePool(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories)
+ : pool_impl_(new PoolImpl(task_runner, gpu_factories)) {
+}
+
+GpuMemoryBufferVideoFramePool::~GpuMemoryBufferVideoFramePool() {
+}
+
+scoped_refptr<VideoFrame>
+GpuMemoryBufferVideoFramePool::MaybeCreateHardwareFrame(
+ const scoped_refptr<VideoFrame>& video_frame) {
+ switch (video_frame->format()) {
+ // Supported cases.
+ case VideoFrame::YV12:
+ case VideoFrame::I420:
+ return pool_impl_->CreateHardwareFrame(video_frame);
+ // Unsupported cases.
+ case media::VideoFrame::YV12A:
+ case media::VideoFrame::YV16:
+ case media::VideoFrame::YV12J:
+ case media::VideoFrame::YV12HD:
+ case media::VideoFrame::YV24:
+#if defined(VIDEO_HOLE)
+ case media::VideoFrame::HOLE:
+#endif // defined(VIDEO_HOLE)
+ case media::VideoFrame::ARGB:
+ return video_frame;
DaleCurtis 2015/05/14 00:58:57 I'd just do nothing here and change the return at
Daniele Castagna 2015/05/14 17:30:26 Done.
+
+ // Unacceptable inputs.
+ case media::VideoFrame::NATIVE_TEXTURE:
+ case media::VideoFrame::UNKNOWN:
+ case media::VideoFrame::NV12:
+ NOTREACHED();
DaleCurtis 2015/05/14 00:58:58 Again remove this, these falls under the unsupport
Daniele Castagna 2015/05/14 17:30:26 Done.
+ }
+ NOTREACHED();
+ return scoped_refptr<VideoFrame>();
+}
+
+} // namespace media

Powered by Google App Engine
This is Rietveld 408576698