Chromium Code Reviews| 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 |
| index 2d6e57df284a82b10756759a1c8db04cc9901202..74ef74e0cb530c9521de91065a912e643e021f91 100644 |
| --- a/media/video/gpu_memory_buffer_video_frame_pool.cc |
| +++ b/media/video/gpu_memory_buffer_video_frame_pool.cc |
| @@ -7,14 +7,15 @@ |
| #include <GLES2/gl2.h> |
| #include <GLES2/gl2ext.h> |
| +#include <algorithm> |
| #include <list> |
| #include <utility> |
| +#include "base/barrier_closure.h" |
| #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" |
| @@ -26,22 +27,31 @@ class GpuMemoryBufferVideoFramePool::PoolImpl |
| : public base::RefCountedThreadSafe< |
| GpuMemoryBufferVideoFramePool::PoolImpl> { |
| public: |
| - // |task_runner| is associated to the thread where the context of |
| - // GLES2Interface returned by |gpu_factories| lives. |
| + // |media_task_runner| is the media task runner associated with the |
| + // GL context provided by |gpu_factories| |
| + // |worker_task_runner| is a task runner used to asynchronously copy |
| + // video frame's planes. |
| // |gpu_factories| is an interface to GPU related operation and can be |
| - // null. |
| - PoolImpl(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
| + // null if a GL context is not available. |
| + PoolImpl(const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, |
| + const scoped_refptr<base::TaskRunner>& worker_task_runner, |
| const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories) |
| - : task_runner_(task_runner), |
| + : media_task_runner_(media_task_runner), |
| + worker_task_runner_(worker_task_runner), |
| gpu_factories_(gpu_factories), |
| - texture_target_(gpu_factories_ ? gpu_factories_->ImageTextureTarget() |
| - : GL_TEXTURE_2D) {} |
| + texture_target_(gpu_factories ? gpu_factories->ImageTextureTarget() |
| + : GL_TEXTURE_2D) { |
| + DCHECK(media_task_runner_); |
| + DCHECK(worker_task_runner_); |
| + } |
| - // 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. |
| - scoped_refptr<VideoFrame> CreateHardwareFrame( |
| - const scoped_refptr<VideoFrame>& video_frame); |
| + // Takes a software VideoFrame and calls |cb| with a VideoFrame backed by |
| + // native textures if possible. |
| + // The data contained in video_frame is copied into the returned frame |
| + // asynchronously posting tasks to |worker_task_runner_|, while |cb| will be |
| + // called on |media_task_runner_| once all the data has been copied. |
| + void CreateHardwareFrame(const scoped_refptr<VideoFrame>& video_frame, |
| + const FrameReadyCB& cb); |
| private: |
| friend class base::RefCountedThreadSafe< |
| @@ -67,6 +77,25 @@ class GpuMemoryBufferVideoFramePool::PoolImpl |
| PlaneResource plane_resources[VideoFrame::kMaxPlanes]; |
| }; |
| + // Copy |video_frame| data into |frame_resouces| |
| + // and calls |done| when done. |
| + void CopyVideoFrameToGpuMemoryBuffers( |
| + const scoped_refptr<VideoFrame>& video_frame, |
| + FrameResources* frame_resources, |
| + const FrameReadyCB& cb); |
|
DaleCurtis
2015/08/17 18:01:30
Please use something more descriptive than |cb|.
Daniele Castagna
2015/08/19 21:31:25
Done.
Change everywhere |cb| to |frame_ready_cb|
|
| + |
| + // Called when all the data has been copied. |
| + void OnCopiesDone(const scoped_refptr<VideoFrame>& video_frame, |
| + FrameResources* frame_resources, |
| + const FrameReadyCB& cb); |
| + |
| + // Prepares GL resources, mailboxes and calls |cb| with the new VideoFrame. |
| + // This has to be run on |media_task_runner_| where |cb| will also be run. |
| + void BindAndCreateMailboxesHardwareFrameResources( |
| + const scoped_refptr<VideoFrame>& video_frame, |
| + FrameResources* frame_resources, |
| + const FrameReadyCB& cb); |
| + |
| // Return true if |resources| can be used to represent a frame for |
| // specific |format| and |size|. |
| static bool AreFrameResourcesCompatible(const FrameResources* resources, |
| @@ -88,7 +117,7 @@ class GpuMemoryBufferVideoFramePool::PoolImpl |
| uint32 sync_point); |
| // Return frame resources to the pool. This has to be called on the thread |
| - // where |task_runner| is current. |
| + // where |media_task_runner_| is current. |
| void ReturnFrameResources(FrameResources* frame_resources); |
| // Delete resources. This has to be called on the thread where |task_runner| |
| @@ -97,7 +126,12 @@ class GpuMemoryBufferVideoFramePool::PoolImpl |
| const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories, |
| FrameResources* frame_resources); |
| - scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
| + // Task runner associated to the GL context provided by |gpu_factories_|. |
| + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_; |
| + // Task runner used to asynchronously copy planes. |
| + scoped_refptr<base::TaskRunner> worker_task_runner_; |
| + |
| + // Interface to GPU related operations. |
| scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories_; |
| // Pool of resources. |
| @@ -109,70 +143,155 @@ class GpuMemoryBufferVideoFramePool::PoolImpl |
| namespace { |
| -// Copy a buffer info a GpuMemoryBuffer. |
| -// |bytes_per_row| is expected to be less or equal than the strides of the two |
| -// 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); |
| +void CopyRowsToBuffer(int first_row, |
| + int rows, |
| + int bytes_per_row, |
| + const uint8* source, |
| + int source_stride, |
| + uint8* output, |
| + int dest_stride, |
| + base::Closure done) { |
|
DaleCurtis
2015/08/17 18:01:30
const&
Daniele Castagna
2015/08/19 21:31:25
Done.
|
| + TRACE_EVENT2("media", "CopyRowsToBuffer", "bytes_per_row", bytes_per_row, |
| + "rows", rows); |
| 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, |
| + for (int row = first_row; row < first_row + rows; ++row) { |
| + memcpy(output + dest_stride * row, source + source_stride * row, |
| bytes_per_row); |
| } |
| - buffer->Unmap(); |
| + done.Run(); |
| } |
| } // unnamed namespace |
| // Creates a VideoFrame backed by native textures starting from a software |
| // VideoFrame. |
| -// The data contained in video_frame is copied into the returned VideoFrame. |
| -scoped_refptr<VideoFrame> |
| -GpuMemoryBufferVideoFramePool::PoolImpl::CreateHardwareFrame( |
| - const scoped_refptr<VideoFrame>& video_frame) { |
| - if (!gpu_factories_) |
| - return video_frame; |
| - |
| - if (!gpu_factories_->IsTextureRGSupported()) |
| - return video_frame; |
| - |
| - gpu::gles2::GLES2Interface* gles2 = gpu_factories_->GetGLES2Interface(); |
| - if (!gles2) |
| - return video_frame; |
| +// The data contained in |video_frame| is copied into the VideoFrame passed to |
| +// |cb|. |
| +// This has to be called on the thread where |media_task_runner_| is current. |
| +void GpuMemoryBufferVideoFramePool::PoolImpl::CreateHardwareFrame( |
| + const scoped_refptr<VideoFrame>& video_frame, |
| + const FrameReadyCB& cb) { |
| + DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| + if (!gpu_factories_ || !gpu_factories_->IsTextureRGSupported()) { |
| + cb.Run(video_frame); |
| + return; |
| + } |
| + switch (video_frame->format()) { |
| + // Supported cases. |
| + case PIXEL_FORMAT_YV12: |
| + case PIXEL_FORMAT_I420: |
| + break; |
| + // Unsupported cases. |
| + case PIXEL_FORMAT_YV12A: |
| + case PIXEL_FORMAT_YV16: |
| + case PIXEL_FORMAT_YV24: |
| + case PIXEL_FORMAT_NV12: |
| + case PIXEL_FORMAT_ARGB: |
| + case PIXEL_FORMAT_XRGB: |
| + case PIXEL_FORMAT_UNKNOWN: |
| + cb.Run(video_frame); |
| + return; |
| + } |
| VideoPixelFormat 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]; |
| + const gfx::Size size = video_frame->visible_rect().size(); |
| // Acquire resources. Incompatible ones will be dropped from the pool. |
| FrameResources* frame_resources = GetOrCreateFrameResources(size, format); |
|
DaleCurtis
2015/08/17 18:01:30
How expensive is the create portion here? I.e. is
Daniele Castagna
2015/08/19 21:31:25
It should not be particularly expensive but it def
|
| + if (!frame_resources) { |
| + cb.Run(video_frame); |
| + return; |
| + } |
| + |
| + worker_task_runner_->PostTask( |
|
DaleCurtis
2015/08/17 18:01:30
WDYT about doing an inline copy (i.e. w/o post tas
Daniele Castagna
2015/08/19 21:31:25
Sounds like a good idea.
Is it OK if we do this af
|
| + FROM_HERE, base::Bind(&PoolImpl::CopyVideoFrameToGpuMemoryBuffers, this, |
| + video_frame, frame_resources, cb)); |
| +} |
| + |
| +void GpuMemoryBufferVideoFramePool::PoolImpl::OnCopiesDone( |
| + const scoped_refptr<VideoFrame>& video_frame, |
| + FrameResources* frame_resources, |
| + const FrameReadyCB& cb) { |
| + const VideoPixelFormat format = video_frame->format(); |
| + const size_t planes = VideoFrame::NumPlanes(format); |
| + for (size_t i = 0; i < planes; ++i) { |
| + frame_resources->plane_resources[i].gpu_memory_buffer->Unmap(); |
| + } |
| + |
| + media_task_runner_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&PoolImpl::BindAndCreateMailboxesHardwareFrameResources, this, |
| + video_frame, frame_resources, cb)); |
| +} |
| - // Set up the planes copying data into it and creating the mailboxes needed |
| - // to refer to the textures. |
| +// Copies |video_frame| into |frame_resources| asynchronously, posting n tasks |
| +// that will be synchronized by a barrier. |
| +// After the barrier is passed OnCopiesDone will be called. |
| +void GpuMemoryBufferVideoFramePool::PoolImpl::CopyVideoFrameToGpuMemoryBuffers( |
| + const scoped_refptr<VideoFrame>& video_frame, |
| + FrameResources* frame_resources, |
| + const FrameReadyCB& cb) { |
| + const size_t kBytesPerCopyTarget = 1 << 20; |
|
reveman
2015/08/17 16:38:29
Can you add a comment that makes it clear exactly
Daniele Castagna
2015/08/19 21:31:25
Done.
|
| + const VideoPixelFormat format = video_frame->format(); |
| + const size_t planes = VideoFrame::NumPlanes(format); |
| + gfx::Size size = video_frame->visible_rect().size(); |
| + size_t copies = 0; |
| for (size_t i = 0; i < planes; ++i) { |
| + int rows = VideoFrame::Rows(i, format, size.height()); |
| + int bytes = VideoFrame::RowBytes(i, format, size.width()); |
|
reveman
2015/08/17 16:38:29
nit: s/bytes/bytes_per_row/ or row_bytes
Daniele Castagna
2015/08/19 21:31:25
Done.
|
| + int rows_per_copy = std::max<size_t>(kBytesPerCopyTarget / bytes, 1); |
| + copies += rows / rows_per_copy; |
| + if (rows % rows_per_copy) |
| + ++copies; |
| + } |
| + |
| + base::Closure copies_done = base::Bind(&PoolImpl::OnCopiesDone, this, |
| + video_frame, frame_resources, cb); |
| + base::Closure barrier = base::BarrierClosure(copies, copies_done); |
| + |
| + for (size_t i = 0; i < planes; ++i) { |
| + int rows = VideoFrame::Rows(i, format, size.height()); |
| + int bytes = VideoFrame::RowBytes(i, format, size.width()); |
|
reveman
2015/08/17 16:38:29
nit: s/bytes/bytes_per_row/
Daniele Castagna
2015/08/19 21:31:25
Done.
|
| + int rows_per_copy = std::max<size_t>(kBytesPerCopyTarget / bytes, 1); |
| + |
| 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()); |
| + void* data = nullptr; |
| + CHECK(plane_resource.gpu_memory_buffer->Map(&data)); |
| + uint8* mapped_buffer = static_cast<uint8*>(data); |
| + int dest_stride = 0; |
| + plane_resource.gpu_memory_buffer->GetStride(&dest_stride); |
| + |
| + for (int row = 0; row < rows; row += rows_per_copy) { |
| + worker_task_runner_->PostTask( |
|
DaleCurtis
2015/08/17 18:01:30
Hmm, did you change the worker task runner to run
Daniele Castagna
2015/08/19 21:31:25
Correct, the worker task runner execute tasks in p
|
| + FROM_HERE, base::Bind(&CopyRowsToBuffer, row, |
| + std::min(rows_per_copy, rows - row), bytes, |
| + video_frame->data(i), video_frame->stride(i), |
| + mapped_buffer, dest_stride, barrier)); |
| + } |
| + } |
| +} |
| +void GpuMemoryBufferVideoFramePool::PoolImpl:: |
| + BindAndCreateMailboxesHardwareFrameResources( |
| + const scoped_refptr<VideoFrame>& video_frame, |
| + FrameResources* frame_resources, |
| + const FrameReadyCB& cb) { |
| + gpu::gles2::GLES2Interface* gles2 = gpu_factories_->GetGLES2Interface(); |
| + if (!gles2) { |
| + cb.Run(video_frame); |
| + return; |
| + } |
| + |
| + const VideoPixelFormat format = video_frame->format(); |
| + const size_t planes = VideoFrame::NumPlanes(format); |
| + const gfx::Size size = video_frame->visible_rect().size(); |
| + gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes]; |
| + // Set up the planes creating the mailboxes needed to refer to the textures. |
| + for (size_t i = 0; i < planes; ++i) { |
| + PlaneResource& plane_resource = frame_resources->plane_resources[i]; |
| // Bind the texture and create or rebind the image. |
| gles2->BindTexture(texture_target_, plane_resource.texture_id); |
| @@ -209,17 +328,17 @@ GpuMemoryBufferVideoFramePool::PoolImpl::CreateHardwareFrame( |
| video_frame->timestamp()); |
| if (video_frame->metadata()->IsTrue(VideoFrameMetadata::ALLOW_OVERLAY)) |
| frame->metadata()->SetBoolean(VideoFrameMetadata::ALLOW_OVERLAY, true); |
| - return frame; |
| + cb.Run(frame); |
| } |
| // Destroy all the resources posting one task per FrameResources |
| -// to the |task_runner_|. |
| +// to the |media_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( |
| + media_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&PoolImpl::DeleteFrameResources, gpu_factories_, |
| base::Owned(frame_resources))); |
| } |
| @@ -231,8 +350,6 @@ GpuMemoryBufferVideoFramePool::PoolImpl::FrameResources* |
| GpuMemoryBufferVideoFramePool::PoolImpl::GetOrCreateFrameResources( |
| const gfx::Size& size, |
| VideoPixelFormat format) { |
| - DCHECK(task_runner_->BelongsToCurrentThread()); |
| - |
| auto it = resources_pool_.begin(); |
| while (it != resources_pool_.end()) { |
| FrameResources* frame_resources = *it; |
| @@ -252,7 +369,8 @@ GpuMemoryBufferVideoFramePool::PoolImpl::GetOrCreateFrameResources( |
| // Create the resources. |
| gpu::gles2::GLES2Interface* gles2 = gpu_factories_->GetGLES2Interface(); |
| - DCHECK(gles2); |
| + if (!gles2) |
| + return nullptr; |
| gles2->ActiveTexture(GL_TEXTURE0); |
| size_t planes = VideoFrame::NumPlanes(format); |
| FrameResources* frame_resources = new FrameResources(format, size); |
| @@ -301,14 +419,14 @@ 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)); |
| + media_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(), |
| frame_resources); |
| @@ -321,33 +439,20 @@ void GpuMemoryBufferVideoFramePool::PoolImpl::ReturnFrameResources( |
| } |
| GpuMemoryBufferVideoFramePool::GpuMemoryBufferVideoFramePool( |
| - const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
| + const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, |
| + const scoped_refptr<base::TaskRunner>& worker_task_runner, |
| const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories) |
| - : pool_impl_(new PoolImpl(task_runner, gpu_factories)) { |
| -} |
| + : pool_impl_( |
| + new PoolImpl(media_task_runner, worker_task_runner, gpu_factories)) {} |
| GpuMemoryBufferVideoFramePool::~GpuMemoryBufferVideoFramePool() { |
| } |
| -scoped_refptr<VideoFrame> |
| -GpuMemoryBufferVideoFramePool::MaybeCreateHardwareFrame( |
| - const scoped_refptr<VideoFrame>& video_frame) { |
| - switch (video_frame->format()) { |
| - // Supported cases. |
| - case PIXEL_FORMAT_YV12: |
| - case PIXEL_FORMAT_I420: |
| - return pool_impl_->CreateHardwareFrame(video_frame); |
| - // Unsupported cases. |
| - case PIXEL_FORMAT_YV12A: |
| - case PIXEL_FORMAT_YV16: |
| - case PIXEL_FORMAT_YV24: |
| - case PIXEL_FORMAT_NV12: |
| - case PIXEL_FORMAT_ARGB: |
| - case PIXEL_FORMAT_XRGB: |
| - case PIXEL_FORMAT_UNKNOWN: |
| - break; |
| - } |
| - return video_frame; |
| +void GpuMemoryBufferVideoFramePool::MaybeCreateHardwareFrame( |
| + const scoped_refptr<VideoFrame>& video_frame, |
| + const FrameReadyCB& cb) { |
| + DCHECK(video_frame); |
| + pool_impl_->CreateHardwareFrame(video_frame, cb); |
| } |
| } // namespace media |