Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "media/video/gpu_memory_buffer_video_frame_pool.h" | |
| 6 | |
| 7 #include <GLES2/gl2.h> | |
| 8 #include <GLES2/gl2ext.h> | |
| 9 | |
| 10 #include <list> | |
| 11 #include <utility> | |
| 12 | |
| 13 #include "base/bind.h" | |
| 14 #include "base/containers/stack_container.h" | |
| 15 #include "base/location.h" | |
| 16 #include "base/memory/linked_ptr.h" | |
| 17 #include "base/single_thread_task_runner.h" | |
| 18 #include "base/trace_event/trace_event.h" | |
| 19 #include "gpu/command_buffer/client/gles2_interface.h" | |
| 20 #include "media/renderers/gpu_video_accelerator_factories.h" | |
| 21 | |
| 22 namespace media { | |
| 23 | |
| 24 // Implementation of a pool of GpuMemoryBuffers used to back VideoFrames. | |
| 25 class GpuMemoryBufferVideoFramePool::PoolImpl | |
| 26 : public base::RefCountedThreadSafe< | |
| 27 GpuMemoryBufferVideoFramePool::PoolImpl> { | |
| 28 public: | |
| 29 // |task_runner| is associated to the thread where the context of | |
| 30 // GLES2Interface returned by |gpu_factories| lives. | |
| 31 // |gpu_factories| is an interface to GPU related operation and can be | |
| 32 // null. | |
| 33 PoolImpl(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, | |
| 34 const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories) | |
| 35 : task_runner_(task_runner), gpu_factories_(gpu_factories) {} | |
| 36 | |
| 37 // Takes a software VideoFrame and returns a VideoFrame backed by native | |
| 38 // textures if possible. | |
| 39 // The data contained in video_frame is copied into the returned frame. | |
| 40 scoped_refptr<VideoFrame> CreateHardwareFrame( | |
| 41 const scoped_refptr<VideoFrame>& video_frame); | |
| 42 | |
| 43 private: | |
| 44 friend class base::RefCountedThreadSafe< | |
| 45 GpuMemoryBufferVideoFramePool::PoolImpl>; | |
| 46 ~PoolImpl(); | |
| 47 | |
| 48 // Resource to represent a plane. | |
| 49 struct PlaneResource { | |
| 50 gfx::Size size; | |
| 51 scoped_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer; | |
| 52 unsigned texture_id = 0u; | |
| 53 unsigned image_id = 0u; | |
| 54 gpu::Mailbox mailbox; | |
| 55 }; | |
| 56 | |
| 57 // All the resources needed to compose a frame. | |
| 58 struct FrameResources { | |
| 59 FrameResources(VideoFrame::Format format, const gfx::Size& size) | |
| 60 : format(format), size(size) {} | |
| 61 bool in_use = true; | |
| 62 VideoFrame::Format format; | |
| 63 gfx::Size size; | |
| 64 PlaneResource plane_resources[VideoFrame::kMaxPlanes]; | |
| 65 }; | |
| 66 | |
| 67 // Return true if |resources| can be used to represent a frame for | |
| 68 // specific |format| and |size|. | |
| 69 static bool IsFrameResourcesCompatible(const FrameResources* resources, | |
|
DaleCurtis
2015/05/14 18:52:13
AreFrame...?
Daniele Castagna
2015/05/14 19:24:17
Done.
| |
| 70 const gfx::Size& size, | |
| 71 VideoFrame::Format format) { | |
| 72 return size == resources->size && format == resources->format; | |
| 73 } | |
| 74 | |
| 75 // Get the resources needed for a frame out of the pool, or create them if | |
| 76 // necessary. | |
| 77 // This also drops the LRU resources that can't be reuse for this frame. | |
| 78 FrameResources* GetOrCreateFrameResources(const gfx::Size& size, | |
| 79 VideoFrame::Format format); | |
| 80 | |
| 81 // Callback called when a VideoFrame generated with GetFrameResources is no | |
| 82 // longer referenced. | |
| 83 // This could be called by any thread. | |
| 84 void MailboxHoldersReleased(FrameResources* frame_resources, | |
| 85 uint32 sync_point); | |
| 86 | |
| 87 // Return frame resources to the pool. This has to be called on the thread | |
| 88 // where |task_runner| is current. | |
| 89 void ReturnFrameResources(FrameResources* frame_resources); | |
| 90 | |
| 91 // Delete resources. This has to be called on the thread where |task_runner| | |
| 92 // is current. | |
| 93 static void DeleteFrameResources( | |
| 94 const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories, | |
| 95 FrameResources* frame_resources); | |
| 96 | |
| 97 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; | |
| 98 scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories_; | |
| 99 | |
| 100 // Pool of resources. | |
| 101 std::list<FrameResources*> resources_pool_; | |
| 102 | |
| 103 unsigned texture_target_ = GL_TEXTURE_2D; | |
| 104 DISALLOW_COPY_AND_ASSIGN(PoolImpl); | |
| 105 }; | |
| 106 | |
| 107 namespace { | |
| 108 | |
| 109 // Copy a buffer info a GpuMemoryBuffer. | |
| 110 // |bytes_per_row| is expected to be less or equal than the strides of the two | |
| 111 // buffers. | |
| 112 void CopyPlaneToGpuMemoryBuffer(int rows, | |
| 113 int bytes_per_row, | |
| 114 const uint8* source, | |
| 115 int source_stride, | |
| 116 gfx::GpuMemoryBuffer* buffer) { | |
| 117 TRACE_EVENT2("media", "CopyPlaneToGpuMemoryBuffer", "bytes_per_row", | |
| 118 bytes_per_row, "rows", rows); | |
| 119 | |
| 120 DCHECK(buffer); | |
| 121 DCHECK(source); | |
| 122 void* data = nullptr; | |
| 123 CHECK(buffer->Map(&data)); | |
| 124 uint8* mapped_buffer = static_cast<uint8*>(data); | |
| 125 int dest_stride = 0; | |
| 126 buffer->GetStride(&dest_stride); | |
| 127 DCHECK_NE(dest_stride, 0); | |
| 128 DCHECK_LE(bytes_per_row, std::abs(dest_stride)); | |
| 129 DCHECK_LE(bytes_per_row, source_stride); | |
| 130 for (int row = 0; row < rows; ++row) { | |
| 131 memcpy(mapped_buffer + dest_stride * row, source + source_stride * row, | |
| 132 bytes_per_row); | |
| 133 } | |
| 134 buffer->Unmap(); | |
| 135 } | |
| 136 | |
| 137 } // unnamed namespace | |
| 138 | |
| 139 // Creates a VideoFrame backed by native textures starting from a software | |
| 140 // VideoFrame. | |
| 141 // The data contained in video_frame is copied into the returned VideoFrame. | |
| 142 scoped_refptr<VideoFrame> | |
| 143 GpuMemoryBufferVideoFramePool::PoolImpl::CreateHardwareFrame( | |
| 144 const scoped_refptr<VideoFrame>& video_frame) { | |
| 145 if (!gpu_factories_) | |
| 146 return video_frame; | |
| 147 | |
| 148 gpu::gles2::GLES2Interface* gles2 = gpu_factories_->GetGLES2Interface(); | |
| 149 if (!gles2) | |
| 150 return video_frame; | |
| 151 | |
| 152 VideoFrame::Format format = video_frame->format(); | |
| 153 size_t planes = VideoFrame::NumPlanes(format); | |
| 154 DCHECK(video_frame->visible_rect().origin().IsOrigin()); | |
| 155 gfx::Size size = video_frame->visible_rect().size(); | |
| 156 gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes]; | |
| 157 | |
| 158 // Acquire resources. Incompatible ones will be dropped from the pool. | |
| 159 FrameResources* frame_resources = GetOrCreateFrameResources(size, format); | |
| 160 | |
| 161 // Set up the planes copying data into it and creating the mailboxes needed | |
| 162 // to refer to the textures. | |
| 163 for (size_t i = 0; i < planes; ++i) { | |
| 164 PlaneResource& plane_resource = frame_resources->plane_resources[i]; | |
| 165 CopyPlaneToGpuMemoryBuffer(VideoFrame::Rows(i, format, size.height()), | |
| 166 VideoFrame::RowBytes(i, format, size.width()), | |
| 167 video_frame->data(i), video_frame->stride(i), | |
| 168 plane_resource.gpu_memory_buffer.get()); | |
| 169 | |
| 170 // Bind the texture and create or rebind the image. | |
| 171 gles2->BindTexture(texture_target_, plane_resource.texture_id); | |
| 172 | |
| 173 if (plane_resource.gpu_memory_buffer && !plane_resource.image_id) { | |
| 174 const size_t width = VideoFrame::Columns(i, format, size.width()); | |
| 175 const size_t height = VideoFrame::Rows(i, format, size.height()); | |
| 176 plane_resource.image_id = gles2->CreateImageCHROMIUM( | |
| 177 plane_resource.gpu_memory_buffer->AsClientBuffer(), width, height, | |
| 178 GL_R8_EXT); | |
| 179 } else { | |
| 180 gles2->ReleaseTexImage2DCHROMIUM(texture_target_, | |
| 181 plane_resource.image_id); | |
| 182 } | |
| 183 gles2->BindTexImage2DCHROMIUM(texture_target_, plane_resource.image_id); | |
| 184 mailbox_holders[i] = | |
| 185 gpu::MailboxHolder(plane_resource.mailbox, texture_target_, 0); | |
| 186 } | |
| 187 | |
| 188 // Insert a sync_point, this is needed to make sure that the textures the | |
| 189 // mailboxes refer to will be used only after all the previous commands posted | |
| 190 // in the command buffer have been processed. | |
| 191 unsigned sync_point = gles2->InsertSyncPointCHROMIUM(); | |
| 192 for (size_t i = 0; i < planes; ++i) { | |
| 193 mailbox_holders[i].sync_point = sync_point; | |
| 194 } | |
| 195 | |
| 196 // Create the VideoFrame backed by native textures. | |
| 197 return VideoFrame::WrapYUV420NativeTextures( | |
| 198 mailbox_holders[VideoFrame::kYPlane], | |
| 199 mailbox_holders[VideoFrame::kUPlane], | |
| 200 mailbox_holders[VideoFrame::kVPlane], | |
| 201 base::Bind(&PoolImpl::MailboxHoldersReleased, this, frame_resources), | |
| 202 size, video_frame->visible_rect(), video_frame->natural_size(), | |
| 203 video_frame->timestamp(), video_frame->allow_overlay()); | |
| 204 } | |
| 205 | |
| 206 // Destroy all the resources posting one task per FrameResources | |
| 207 // to the |task_runner_|. | |
| 208 GpuMemoryBufferVideoFramePool::PoolImpl::~PoolImpl() { | |
| 209 // Delete all the resources on the media thread. | |
| 210 while (!resources_pool_.empty()) { | |
| 211 FrameResources* frame_resources = resources_pool_.front(); | |
| 212 resources_pool_.pop_front(); | |
| 213 task_runner_->PostTask( | |
| 214 FROM_HERE, base::Bind(&PoolImpl::DeleteFrameResources, gpu_factories_, | |
| 215 base::Owned(frame_resources))); | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 // Tries to find the resources in the pool or create them. | |
| 220 // Incompatible resources will be dropped. | |
| 221 GpuMemoryBufferVideoFramePool::PoolImpl::FrameResources* | |
| 222 GpuMemoryBufferVideoFramePool::PoolImpl::GetOrCreateFrameResources( | |
| 223 const gfx::Size& size, | |
| 224 VideoFrame::Format format) { | |
| 225 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 226 | |
| 227 auto it = resources_pool_.begin(); | |
| 228 while (it != resources_pool_.end()) { | |
| 229 FrameResources* frame_resources = *it; | |
| 230 if (!frame_resources->in_use) { | |
| 231 if (IsFrameResourcesCompatible(frame_resources, size, format)) { | |
| 232 frame_resources->in_use = true; | |
| 233 return frame_resources; | |
| 234 } else { | |
| 235 resources_pool_.erase(it++); | |
| 236 DeleteFrameResources(gpu_factories_, frame_resources); | |
| 237 delete frame_resources; | |
| 238 } | |
| 239 } else { | |
| 240 it++; | |
| 241 } | |
| 242 } | |
| 243 | |
| 244 // Create the resources. | |
| 245 gpu::gles2::GLES2Interface* gles2 = gpu_factories_->GetGLES2Interface(); | |
| 246 DCHECK(gles2); | |
| 247 gles2->ActiveTexture(GL_TEXTURE0); | |
| 248 size_t planes = VideoFrame::NumPlanes(format); | |
| 249 FrameResources* frame_resources = new FrameResources(format, size); | |
| 250 resources_pool_.push_back(frame_resources); | |
| 251 for (size_t i = 0; i < planes; ++i) { | |
| 252 PlaneResource& plane_resource = frame_resources->plane_resources[i]; | |
| 253 const size_t width = VideoFrame::Columns(i, format, size.width()); | |
| 254 const size_t height = VideoFrame::Rows(i, format, size.height()); | |
| 255 const gfx::Size plane_size(width, height); | |
| 256 plane_resource.gpu_memory_buffer = gpu_factories_->AllocateGpuMemoryBuffer( | |
| 257 plane_size, gfx::GpuMemoryBuffer::R_8, gfx::GpuMemoryBuffer::MAP); | |
| 258 | |
| 259 gles2->GenTextures(1, &plane_resource.texture_id); | |
| 260 gles2->BindTexture(texture_target_, plane_resource.texture_id); | |
| 261 gles2->TexParameteri(texture_target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| 262 gles2->TexParameteri(texture_target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
| 263 gles2->TexParameteri(texture_target_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| 264 gles2->TexParameteri(texture_target_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| 265 gles2->GenMailboxCHROMIUM(plane_resource.mailbox.name); | |
| 266 gles2->ProduceTextureCHROMIUM(texture_target_, plane_resource.mailbox.name); | |
| 267 } | |
| 268 return frame_resources; | |
| 269 } | |
| 270 | |
| 271 // static | |
| 272 void GpuMemoryBufferVideoFramePool::PoolImpl::DeleteFrameResources( | |
| 273 const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories, | |
| 274 FrameResources* frame_resources) { | |
| 275 // TODO(dcastagna): As soon as the context lost is dealt with in media, | |
| 276 // make sure that we won't execture this callback (use a weak pointer to | |
| 277 // the old context). | |
| 278 gpu::gles2::GLES2Interface* gles2 = gpu_factories->GetGLES2Interface(); | |
| 279 if (!gles2) | |
| 280 return; | |
| 281 | |
| 282 for (PlaneResource& plane_resource : frame_resources->plane_resources) { | |
| 283 if (plane_resource.image_id) | |
| 284 gles2->DestroyImageCHROMIUM(plane_resource.image_id); | |
| 285 if (plane_resource.texture_id) | |
| 286 gles2->DeleteTextures(1, &plane_resource.texture_id); | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 // Called when a VideoFrame is no longer references. | |
| 291 void GpuMemoryBufferVideoFramePool::PoolImpl::MailboxHoldersReleased( | |
| 292 FrameResources* frame_resources, | |
| 293 uint32 sync_point) { | |
| 294 // Return the resource on the media thread. | |
| 295 task_runner_->PostTask(FROM_HERE, base::Bind(&PoolImpl::ReturnFrameResources, | |
| 296 this, frame_resources)); | |
| 297 } | |
| 298 | |
| 299 // Put back the resoruces in the pool. | |
| 300 void GpuMemoryBufferVideoFramePool::PoolImpl::ReturnFrameResources( | |
| 301 FrameResources* frame_resources) { | |
| 302 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 303 | |
| 304 auto it = std::find(resources_pool_.begin(), resources_pool_.end(), | |
| 305 frame_resources); | |
| 306 DCHECK(it != resources_pool_.end()); | |
| 307 // We want the pool to behave in a FIFO way. | |
| 308 // This minimizes the chances of locking the buffer that might be | |
| 309 // still needed for drawing. | |
| 310 std::swap(*it, resources_pool_.back()); | |
| 311 frame_resources->in_use = false; | |
| 312 } | |
| 313 | |
| 314 GpuMemoryBufferVideoFramePool::GpuMemoryBufferVideoFramePool( | |
| 315 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, | |
| 316 const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories) | |
| 317 : pool_impl_(new PoolImpl(task_runner, gpu_factories)) { | |
| 318 } | |
| 319 | |
| 320 GpuMemoryBufferVideoFramePool::~GpuMemoryBufferVideoFramePool() { | |
| 321 } | |
| 322 | |
| 323 scoped_refptr<VideoFrame> | |
| 324 GpuMemoryBufferVideoFramePool::MaybeCreateHardwareFrame( | |
| 325 const scoped_refptr<VideoFrame>& video_frame) { | |
| 326 switch (video_frame->format()) { | |
| 327 // Supported cases. | |
| 328 case VideoFrame::YV12: | |
| 329 case VideoFrame::I420: | |
| 330 return pool_impl_->CreateHardwareFrame(video_frame); | |
| 331 // Unsupported cases. | |
| 332 case media::VideoFrame::YV12A: | |
| 333 case media::VideoFrame::YV16: | |
| 334 case media::VideoFrame::YV12J: | |
| 335 case media::VideoFrame::YV12HD: | |
| 336 case media::VideoFrame::YV24: | |
| 337 #if defined(VIDEO_HOLE) | |
| 338 case media::VideoFrame::HOLE: | |
| 339 #endif // defined(VIDEO_HOLE) | |
| 340 case media::VideoFrame::ARGB: | |
| 341 case media::VideoFrame::NATIVE_TEXTURE: | |
| 342 case media::VideoFrame::UNKNOWN: | |
| 343 case media::VideoFrame::NV12: | |
| 344 break; | |
| 345 } | |
| 346 return video_frame; | |
| 347 } | |
| 348 | |
| 349 } // namespace media | |
| OLD | NEW |