| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 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 "cc/resources/video_resource_updater.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/trace_event/trace_event.h" | |
| 11 #include "cc/base/util.h" | |
| 12 #include "cc/output/gl_renderer.h" | |
| 13 #include "cc/resources/resource_provider.h" | |
| 14 #include "gpu/GLES2/gl2extchromium.h" | |
| 15 #include "gpu/command_buffer/client/gles2_interface.h" | |
| 16 #include "media/base/video_frame.h" | |
| 17 #include "media/blink/skcanvas_video_renderer.h" | |
| 18 #include "third_party/khronos/GLES2/gl2.h" | |
| 19 #include "third_party/khronos/GLES2/gl2ext.h" | |
| 20 #include "ui/gfx/geometry/size_conversions.h" | |
| 21 | |
| 22 namespace cc { | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 const ResourceFormat kRGBResourceFormat = RGBA_8888; | |
| 27 | |
| 28 class SyncPointClientImpl : public media::VideoFrame::SyncPointClient { | |
| 29 public: | |
| 30 explicit SyncPointClientImpl(gpu::gles2::GLES2Interface* gl) : gl_(gl) {} | |
| 31 ~SyncPointClientImpl() override {} | |
| 32 uint32 InsertSyncPoint() override { | |
| 33 return GLC(gl_, gl_->InsertSyncPointCHROMIUM()); | |
| 34 } | |
| 35 void WaitSyncPoint(uint32 sync_point) override { | |
| 36 GLC(gl_, gl_->WaitSyncPointCHROMIUM(sync_point)); | |
| 37 } | |
| 38 | |
| 39 private: | |
| 40 gpu::gles2::GLES2Interface* gl_; | |
| 41 }; | |
| 42 | |
| 43 } // namespace | |
| 44 | |
| 45 VideoResourceUpdater::PlaneResource::PlaneResource( | |
| 46 unsigned int resource_id, | |
| 47 const gfx::Size& resource_size, | |
| 48 ResourceFormat resource_format, | |
| 49 gpu::Mailbox mailbox) | |
| 50 : resource_id(resource_id), | |
| 51 resource_size(resource_size), | |
| 52 resource_format(resource_format), | |
| 53 mailbox(mailbox), | |
| 54 ref_count(0), | |
| 55 frame_ptr(nullptr), | |
| 56 plane_index(0) { | |
| 57 } | |
| 58 | |
| 59 bool VideoResourceUpdater::PlaneResourceMatchesUniqueID( | |
| 60 const PlaneResource& plane_resource, | |
| 61 const media::VideoFrame* video_frame, | |
| 62 int plane_index) { | |
| 63 return plane_resource.frame_ptr == video_frame && | |
| 64 plane_resource.plane_index == plane_index && | |
| 65 plane_resource.timestamp == video_frame->timestamp(); | |
| 66 } | |
| 67 | |
| 68 void VideoResourceUpdater::SetPlaneResourceUniqueId( | |
| 69 const media::VideoFrame* video_frame, | |
| 70 int plane_index, | |
| 71 PlaneResource* plane_resource) { | |
| 72 plane_resource->frame_ptr = video_frame; | |
| 73 plane_resource->plane_index = plane_index; | |
| 74 plane_resource->timestamp = video_frame->timestamp(); | |
| 75 } | |
| 76 | |
| 77 VideoFrameExternalResources::VideoFrameExternalResources() : type(NONE) {} | |
| 78 | |
| 79 VideoFrameExternalResources::~VideoFrameExternalResources() {} | |
| 80 | |
| 81 VideoResourceUpdater::VideoResourceUpdater(ContextProvider* context_provider, | |
| 82 ResourceProvider* resource_provider) | |
| 83 : context_provider_(context_provider), | |
| 84 resource_provider_(resource_provider) { | |
| 85 } | |
| 86 | |
| 87 VideoResourceUpdater::~VideoResourceUpdater() { | |
| 88 for (const PlaneResource& plane_resource : all_resources_) | |
| 89 resource_provider_->DeleteResource(plane_resource.resource_id); | |
| 90 } | |
| 91 | |
| 92 VideoResourceUpdater::ResourceList::iterator | |
| 93 VideoResourceUpdater::AllocateResource(const gfx::Size& plane_size, | |
| 94 ResourceFormat format, | |
| 95 bool has_mailbox) { | |
| 96 // TODO(danakj): Abstract out hw/sw resource create/delete from | |
| 97 // ResourceProvider and stop using ResourceProvider in this class. | |
| 98 const ResourceProvider::ResourceId resource_id = | |
| 99 resource_provider_->CreateResource( | |
| 100 plane_size, GL_CLAMP_TO_EDGE, | |
| 101 ResourceProvider::TEXTURE_HINT_IMMUTABLE, format); | |
| 102 if (resource_id == 0) | |
| 103 return all_resources_.end(); | |
| 104 | |
| 105 gpu::Mailbox mailbox; | |
| 106 if (has_mailbox) { | |
| 107 DCHECK(context_provider_); | |
| 108 | |
| 109 gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); | |
| 110 | |
| 111 GLC(gl, gl->GenMailboxCHROMIUM(mailbox.name)); | |
| 112 ResourceProvider::ScopedWriteLockGL lock(resource_provider_, resource_id); | |
| 113 GLC(gl, gl->ProduceTextureDirectCHROMIUM(lock.texture_id(), GL_TEXTURE_2D, | |
| 114 mailbox.name)); | |
| 115 } | |
| 116 all_resources_.push_front( | |
| 117 PlaneResource(resource_id, plane_size, format, mailbox)); | |
| 118 return all_resources_.begin(); | |
| 119 } | |
| 120 | |
| 121 void VideoResourceUpdater::DeleteResource(ResourceList::iterator resource_it) { | |
| 122 DCHECK_EQ(resource_it->ref_count, 0); | |
| 123 resource_provider_->DeleteResource(resource_it->resource_id); | |
| 124 all_resources_.erase(resource_it); | |
| 125 } | |
| 126 | |
| 127 VideoFrameExternalResources VideoResourceUpdater:: | |
| 128 CreateExternalResourcesFromVideoFrame( | |
| 129 const scoped_refptr<media::VideoFrame>& video_frame) { | |
| 130 if (!VerifyFrame(video_frame)) | |
| 131 return VideoFrameExternalResources(); | |
| 132 | |
| 133 if (video_frame->format() == media::VideoFrame::NATIVE_TEXTURE) | |
| 134 return CreateForHardwarePlanes(video_frame); | |
| 135 else | |
| 136 return CreateForSoftwarePlanes(video_frame); | |
| 137 } | |
| 138 | |
| 139 bool VideoResourceUpdater::VerifyFrame( | |
| 140 const scoped_refptr<media::VideoFrame>& video_frame) { | |
| 141 switch (video_frame->format()) { | |
| 142 // Acceptable inputs. | |
| 143 case media::VideoFrame::YV12: | |
| 144 case media::VideoFrame::I420: | |
| 145 case media::VideoFrame::YV12A: | |
| 146 case media::VideoFrame::YV16: | |
| 147 case media::VideoFrame::YV12J: | |
| 148 case media::VideoFrame::YV12HD: | |
| 149 case media::VideoFrame::YV24: | |
| 150 case media::VideoFrame::NATIVE_TEXTURE: | |
| 151 #if defined(VIDEO_HOLE) | |
| 152 case media::VideoFrame::HOLE: | |
| 153 #endif // defined(VIDEO_HOLE) | |
| 154 case media::VideoFrame::ARGB: | |
| 155 return true; | |
| 156 | |
| 157 // Unacceptable inputs. ¯\(°_o)/¯ | |
| 158 case media::VideoFrame::UNKNOWN: | |
| 159 case media::VideoFrame::NV12: | |
| 160 break; | |
| 161 } | |
| 162 return false; | |
| 163 } | |
| 164 | |
| 165 // For frames that we receive in software format, determine the dimensions of | |
| 166 // each plane in the frame. | |
| 167 static gfx::Size SoftwarePlaneDimension( | |
| 168 const scoped_refptr<media::VideoFrame>& input_frame, | |
| 169 bool software_compositor, | |
| 170 size_t plane_index) { | |
| 171 if (!software_compositor) { | |
| 172 return media::VideoFrame::PlaneSize( | |
| 173 input_frame->format(), plane_index, input_frame->coded_size()); | |
| 174 } | |
| 175 return input_frame->coded_size(); | |
| 176 } | |
| 177 | |
| 178 VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes( | |
| 179 const scoped_refptr<media::VideoFrame>& video_frame) { | |
| 180 TRACE_EVENT0("cc", "VideoResourceUpdater::CreateForSoftwarePlanes"); | |
| 181 media::VideoFrame::Format input_frame_format = video_frame->format(); | |
| 182 | |
| 183 #if defined(VIDEO_HOLE) | |
| 184 if (input_frame_format == media::VideoFrame::HOLE) { | |
| 185 VideoFrameExternalResources external_resources; | |
| 186 external_resources.type = VideoFrameExternalResources::HOLE; | |
| 187 return external_resources; | |
| 188 } | |
| 189 #endif // defined(VIDEO_HOLE) | |
| 190 | |
| 191 // Only YUV software video frames are supported. | |
| 192 if (input_frame_format != media::VideoFrame::YV12 && | |
| 193 input_frame_format != media::VideoFrame::I420 && | |
| 194 input_frame_format != media::VideoFrame::YV12A && | |
| 195 input_frame_format != media::VideoFrame::YV12J && | |
| 196 input_frame_format != media::VideoFrame::YV12HD && | |
| 197 input_frame_format != media::VideoFrame::YV16 && | |
| 198 input_frame_format != media::VideoFrame::YV24) { | |
| 199 NOTREACHED() << input_frame_format; | |
| 200 return VideoFrameExternalResources(); | |
| 201 } | |
| 202 | |
| 203 bool software_compositor = context_provider_ == NULL; | |
| 204 | |
| 205 ResourceFormat output_resource_format = | |
| 206 resource_provider_->yuv_resource_format(); | |
| 207 size_t output_plane_count = media::VideoFrame::NumPlanes(input_frame_format); | |
| 208 | |
| 209 // TODO(skaslev): If we're in software compositing mode, we do the YUV -> RGB | |
| 210 // conversion here. That involves an extra copy of each frame to a bitmap. | |
| 211 // Obviously, this is suboptimal and should be addressed once ubercompositor | |
| 212 // starts shaping up. | |
| 213 if (software_compositor) { | |
| 214 output_resource_format = kRGBResourceFormat; | |
| 215 output_plane_count = 1; | |
| 216 } | |
| 217 | |
| 218 // Drop recycled resources that are the wrong format. | |
| 219 for (auto it = all_resources_.begin(); it != all_resources_.end();) { | |
| 220 if (it->ref_count == 0 && it->resource_format != output_resource_format) | |
| 221 DeleteResource(it++); | |
| 222 else | |
| 223 ++it; | |
| 224 } | |
| 225 | |
| 226 const int max_resource_size = resource_provider_->max_texture_size(); | |
| 227 std::vector<ResourceList::iterator> plane_resources; | |
| 228 for (size_t i = 0; i < output_plane_count; ++i) { | |
| 229 gfx::Size output_plane_resource_size = | |
| 230 SoftwarePlaneDimension(video_frame, software_compositor, i); | |
| 231 if (output_plane_resource_size.IsEmpty() || | |
| 232 output_plane_resource_size.width() > max_resource_size || | |
| 233 output_plane_resource_size.height() > max_resource_size) { | |
| 234 break; | |
| 235 } | |
| 236 | |
| 237 // Try recycle a previously-allocated resource. | |
| 238 ResourceList::iterator resource_it = all_resources_.end(); | |
| 239 for (auto it = all_resources_.begin(); it != all_resources_.end(); ++it) { | |
| 240 if (it->resource_size == output_plane_resource_size && | |
| 241 it->resource_format == output_resource_format) { | |
| 242 if (PlaneResourceMatchesUniqueID(*it, video_frame.get(), i)) { | |
| 243 // Bingo, we found a resource that already contains the data we are | |
| 244 // planning to put in it. It's safe to reuse it even if | |
| 245 // resource_provider_ holds some references to it, because those | |
| 246 // references are read-only. | |
| 247 resource_it = it; | |
| 248 break; | |
| 249 } | |
| 250 | |
| 251 // This extra check is needed because resources backed by SharedMemory | |
| 252 // are not ref-counted, unlike mailboxes. Full discussion in | |
| 253 // codereview.chromium.org/145273021. | |
| 254 const bool in_use = | |
| 255 software_compositor && | |
| 256 resource_provider_->InUseByConsumer(it->resource_id); | |
| 257 if (it->ref_count == 0 && !in_use) { | |
| 258 // We found a resource with the correct size that we can overwrite. | |
| 259 resource_it = it; | |
| 260 } | |
| 261 } | |
| 262 } | |
| 263 | |
| 264 // Check if we need to allocate a new resource. | |
| 265 if (resource_it == all_resources_.end()) { | |
| 266 resource_it = | |
| 267 AllocateResource(output_plane_resource_size, output_resource_format, | |
| 268 !software_compositor); | |
| 269 } | |
| 270 if (resource_it == all_resources_.end()) | |
| 271 break; | |
| 272 | |
| 273 ++resource_it->ref_count; | |
| 274 plane_resources.push_back(resource_it); | |
| 275 } | |
| 276 | |
| 277 if (plane_resources.size() != output_plane_count) { | |
| 278 // Allocation failed, nothing will be returned so restore reference counts. | |
| 279 for (ResourceList::iterator resource_it : plane_resources) | |
| 280 --resource_it->ref_count; | |
| 281 return VideoFrameExternalResources(); | |
| 282 } | |
| 283 | |
| 284 VideoFrameExternalResources external_resources; | |
| 285 | |
| 286 if (software_compositor) { | |
| 287 DCHECK_EQ(plane_resources.size(), 1u); | |
| 288 PlaneResource& plane_resource = *plane_resources[0]; | |
| 289 DCHECK_EQ(plane_resource.resource_format, kRGBResourceFormat); | |
| 290 DCHECK(plane_resource.mailbox.IsZero()); | |
| 291 | |
| 292 if (!PlaneResourceMatchesUniqueID(plane_resource, video_frame.get(), 0)) { | |
| 293 // We need to transfer data from |video_frame| to the plane resource. | |
| 294 if (!video_renderer_) | |
| 295 video_renderer_.reset(new media::SkCanvasVideoRenderer); | |
| 296 | |
| 297 ResourceProvider::ScopedWriteLockSoftware lock( | |
| 298 resource_provider_, plane_resource.resource_id); | |
| 299 SkCanvas canvas(lock.sk_bitmap()); | |
| 300 // This is software path, so canvas and video_frame are always backed | |
| 301 // by software. | |
| 302 video_renderer_->Copy(video_frame, &canvas, media::Context3D()); | |
| 303 SetPlaneResourceUniqueId(video_frame.get(), 0, &plane_resource); | |
| 304 } | |
| 305 | |
| 306 external_resources.software_resources.push_back(plane_resource.resource_id); | |
| 307 external_resources.software_release_callback = | |
| 308 base::Bind(&RecycleResource, AsWeakPtr(), plane_resource.resource_id); | |
| 309 external_resources.type = VideoFrameExternalResources::SOFTWARE_RESOURCE; | |
| 310 return external_resources; | |
| 311 } | |
| 312 | |
| 313 for (size_t i = 0; i < plane_resources.size(); ++i) { | |
| 314 PlaneResource& plane_resource = *plane_resources[i]; | |
| 315 // Update each plane's resource id with its content. | |
| 316 DCHECK_EQ(plane_resource.resource_format, | |
| 317 resource_provider_->yuv_resource_format()); | |
| 318 | |
| 319 if (!PlaneResourceMatchesUniqueID(plane_resource, video_frame.get(), i)) { | |
| 320 // We need to transfer data from |video_frame| to the plane resource. | |
| 321 // TODO(reveman): Can use GpuMemoryBuffers here to improve performance. | |
| 322 | |
| 323 // The |resource_size_pixels| is the size of the resource we want to | |
| 324 // upload to. | |
| 325 gfx::Size resource_size_pixels = plane_resource.resource_size; | |
| 326 // The |video_stride_pixels| is the width of the video frame we are | |
| 327 // uploading (including non-frame data to fill in the stride). | |
| 328 size_t video_stride_pixels = video_frame->stride(i); | |
| 329 | |
| 330 size_t bytes_per_pixel = BitsPerPixel(plane_resource.resource_format) / 8; | |
| 331 // Use 4-byte row alignment (OpenGL default) for upload performance. | |
| 332 // Assuming that GL_UNPACK_ALIGNMENT has not changed from default. | |
| 333 size_t upload_image_stride = | |
| 334 RoundUp<size_t>(bytes_per_pixel * resource_size_pixels.width(), 4u); | |
| 335 | |
| 336 const uint8_t* pixels; | |
| 337 if (upload_image_stride == video_stride_pixels * bytes_per_pixel) { | |
| 338 pixels = video_frame->data(i); | |
| 339 } else { | |
| 340 // Avoid malloc for each frame/plane if possible. | |
| 341 size_t needed_size = | |
| 342 upload_image_stride * resource_size_pixels.height(); | |
| 343 if (upload_pixels_.size() < needed_size) | |
| 344 upload_pixels_.resize(needed_size); | |
| 345 for (int row = 0; row < resource_size_pixels.height(); ++row) { | |
| 346 uint8_t* dst = &upload_pixels_[upload_image_stride * row]; | |
| 347 const uint8_t* src = video_frame->data(i) + | |
| 348 bytes_per_pixel * video_stride_pixels * row; | |
| 349 memcpy(dst, src, resource_size_pixels.width() * bytes_per_pixel); | |
| 350 } | |
| 351 pixels = &upload_pixels_[0]; | |
| 352 } | |
| 353 | |
| 354 resource_provider_->CopyToResource(plane_resource.resource_id, pixels, | |
| 355 resource_size_pixels); | |
| 356 SetPlaneResourceUniqueId(video_frame.get(), i, &plane_resource); | |
| 357 } | |
| 358 | |
| 359 external_resources.mailboxes.push_back( | |
| 360 TextureMailbox(plane_resource.mailbox, GL_TEXTURE_2D, 0)); | |
| 361 external_resources.release_callbacks.push_back( | |
| 362 base::Bind(&RecycleResource, AsWeakPtr(), plane_resource.resource_id)); | |
| 363 } | |
| 364 | |
| 365 external_resources.type = VideoFrameExternalResources::YUV_RESOURCE; | |
| 366 return external_resources; | |
| 367 } | |
| 368 | |
| 369 // static | |
| 370 void VideoResourceUpdater::ReturnTexture( | |
| 371 base::WeakPtr<VideoResourceUpdater> updater, | |
| 372 const scoped_refptr<media::VideoFrame>& video_frame, | |
| 373 uint32 sync_point, | |
| 374 bool lost_resource, | |
| 375 BlockingTaskRunner* main_thread_task_runner) { | |
| 376 // TODO(dshwang) this case should be forwarded to the decoder as lost | |
| 377 // resource. | |
| 378 if (lost_resource || !updater.get()) | |
| 379 return; | |
| 380 // VideoFrame::UpdateReleaseSyncPoint() creates new sync point using the same | |
| 381 // GL context which created the given |sync_point|, so discard the | |
| 382 // |sync_point|. | |
| 383 SyncPointClientImpl client(updater->context_provider_->ContextGL()); | |
| 384 video_frame->UpdateReleaseSyncPoint(&client); | |
| 385 } | |
| 386 | |
| 387 VideoFrameExternalResources VideoResourceUpdater::CreateForHardwarePlanes( | |
| 388 const scoped_refptr<media::VideoFrame>& video_frame) { | |
| 389 TRACE_EVENT0("cc", "VideoResourceUpdater::CreateForHardwarePlanes"); | |
| 390 media::VideoFrame::Format frame_format = video_frame->format(); | |
| 391 | |
| 392 DCHECK_EQ(frame_format, media::VideoFrame::NATIVE_TEXTURE); | |
| 393 if (frame_format != media::VideoFrame::NATIVE_TEXTURE) | |
| 394 return VideoFrameExternalResources(); | |
| 395 | |
| 396 if (!context_provider_) | |
| 397 return VideoFrameExternalResources(); | |
| 398 | |
| 399 const gpu::MailboxHolder* mailbox_holder = video_frame->mailbox_holder(); | |
| 400 VideoFrameExternalResources external_resources; | |
| 401 switch (mailbox_holder->texture_target) { | |
| 402 case GL_TEXTURE_2D: | |
| 403 external_resources.type = VideoFrameExternalResources::RGB_RESOURCE; | |
| 404 break; | |
| 405 case GL_TEXTURE_EXTERNAL_OES: | |
| 406 external_resources.type = | |
| 407 VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE; | |
| 408 break; | |
| 409 case GL_TEXTURE_RECTANGLE_ARB: | |
| 410 external_resources.type = VideoFrameExternalResources::IO_SURFACE; | |
| 411 break; | |
| 412 default: | |
| 413 NOTREACHED(); | |
| 414 return VideoFrameExternalResources(); | |
| 415 } | |
| 416 | |
| 417 external_resources.mailboxes.push_back( | |
| 418 TextureMailbox(mailbox_holder->mailbox, | |
| 419 mailbox_holder->texture_target, | |
| 420 mailbox_holder->sync_point)); | |
| 421 external_resources.mailboxes.back().set_allow_overlay( | |
| 422 video_frame->allow_overlay()); | |
| 423 external_resources.release_callbacks.push_back( | |
| 424 base::Bind(&ReturnTexture, AsWeakPtr(), video_frame)); | |
| 425 return external_resources; | |
| 426 } | |
| 427 | |
| 428 // static | |
| 429 void VideoResourceUpdater::RecycleResource( | |
| 430 base::WeakPtr<VideoResourceUpdater> updater, | |
| 431 ResourceProvider::ResourceId resource_id, | |
| 432 uint32 sync_point, | |
| 433 bool lost_resource, | |
| 434 BlockingTaskRunner* main_thread_task_runner) { | |
| 435 if (!updater.get()) { | |
| 436 // Resource was already deleted. | |
| 437 return; | |
| 438 } | |
| 439 | |
| 440 const ResourceList::iterator resource_it = std::find_if( | |
| 441 updater->all_resources_.begin(), updater->all_resources_.end(), | |
| 442 [resource_id](const PlaneResource& plane_resource) { | |
| 443 return plane_resource.resource_id == resource_id; | |
| 444 }); | |
| 445 if (resource_it == updater->all_resources_.end()) | |
| 446 return; | |
| 447 | |
| 448 ContextProvider* context_provider = updater->context_provider_; | |
| 449 if (context_provider && sync_point) { | |
| 450 GLC(context_provider->ContextGL(), | |
| 451 context_provider->ContextGL()->WaitSyncPointCHROMIUM(sync_point)); | |
| 452 } | |
| 453 | |
| 454 if (lost_resource) { | |
| 455 resource_it->ref_count = 0; | |
| 456 updater->DeleteResource(resource_it); | |
| 457 return; | |
| 458 } | |
| 459 | |
| 460 --resource_it->ref_count; | |
| 461 DCHECK_GE(resource_it->ref_count, 0); | |
| 462 } | |
| 463 | |
| 464 } // namespace cc | |
| OLD | NEW |