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 "base/bind.h" 8 #include "cc/output/gl_renderer.h" 9 #include "cc/resources/resource_provider.h" 10 #include "gpu/GLES2/gl2extchromium.h" 11 #include "media/base/video_frame.h" 12 #include "media/filters/skcanvas_video_renderer.h" 13 #include "third_party/khronos/GLES2/gl2.h" 14 #include "third_party/khronos/GLES2/gl2ext.h" 15 #include "ui/gfx/size_conversions.h" 16 17 const unsigned kYUVResourceFormat = GL_LUMINANCE; 18 const unsigned kRGBResourceFormat = GL_RGBA; 19 20 namespace cc { 21 22 VideoFrameExternalResources::VideoFrameExternalResources() : type(NONE) {} 23 24 VideoFrameExternalResources::~VideoFrameExternalResources() {} 25 26 VideoResourceUpdater::VideoResourceUpdater(ResourceProvider* resource_provider) 27 : resource_provider_(resource_provider) { 28 } 29 30 VideoResourceUpdater::~VideoResourceUpdater() { 31 while (!all_resources_.empty()) { 32 resource_provider_->DeleteResource(all_resources_.back()); 33 all_resources_.pop_back(); 34 } 35 } 36 37 void VideoResourceUpdater::DeleteResource(unsigned resource_id) { 38 resource_provider_->DeleteResource(resource_id); 39 all_resources_.erase(std::remove(all_resources_.begin(), 40 all_resources_.end(), 41 resource_id)); 42 } 43 44 VideoFrameExternalResources VideoResourceUpdater:: 45 CreateExternalResourcesFromVideoFrame( 46 const scoped_refptr<media::VideoFrame>& video_frame) { 47 if (!VerifyFrame(video_frame)) 48 return VideoFrameExternalResources(); 49 50 if (video_frame->format() == media::VideoFrame::NATIVE_TEXTURE) 51 return CreateForHardwarePlanes(video_frame); 52 else 53 return CreateForSoftwarePlanes(video_frame); 54 } 55 56 bool VideoResourceUpdater::VerifyFrame( 57 const scoped_refptr<media::VideoFrame>& video_frame) { 58 // If these fail, we'll have to add logic that handles offset bitmap/texture 59 // UVs. For now, just expect (0, 0) offset, since all our decoders so far 60 // don't offset. 61 DCHECK_EQ(video_frame->visible_rect().x(), 0); 62 DCHECK_EQ(video_frame->visible_rect().y(), 0); 63 64 switch (video_frame->format()) { 65 // Acceptable inputs. 66 case media::VideoFrame::YV12: 67 case media::VideoFrame::YV12A: 68 case media::VideoFrame::YV16: 69 case media::VideoFrame::NATIVE_TEXTURE: 70 #if defined(GOOGLE_TV) 71 case media::VideoFrame::HOLE: 72 #endif 73 return true; 74 75 // Unacceptable inputs. \(_o)/ 76 case media::VideoFrame::INVALID: 77 case media::VideoFrame::RGB32: 78 case media::VideoFrame::EMPTY: 79 case media::VideoFrame::I420: 80 break; 81 } 82 return false; 83 } 84 85 // For frames that we receive in software format, determine the dimensions of 86 // each plane in the frame. 87 static gfx::Size SoftwarePlaneDimension( 88 media::VideoFrame::Format input_frame_format, 89 gfx::Size coded_size, 90 GLenum output_resource_format, 91 int plane_index) { 92 if (output_resource_format == kYUVResourceFormat) { 93 if (plane_index == media::VideoFrame::kYPlane || 94 plane_index == media::VideoFrame::kAPlane) 95 return coded_size; 96 97 switch (input_frame_format) { 98 case media::VideoFrame::YV12: 99 case media::VideoFrame::YV12A: 100 return gfx::ToFlooredSize(gfx::ScaleSize(coded_size, 0.5f, 0.5f)); 101 case media::VideoFrame::YV16: 102 return gfx::ToFlooredSize(gfx::ScaleSize(coded_size, 0.5f, 1.f)); 103 104 case media::VideoFrame::INVALID: 105 case media::VideoFrame::RGB32: 106 case media::VideoFrame::EMPTY: 107 case media::VideoFrame::I420: 108 case media::VideoFrame::NATIVE_TEXTURE: 109 #if defined(GOOGLE_TV) 110 case media::VideoFrame::HOLE: 111 #endif 112 NOTREACHED(); 113 } 114 } 115 116 DCHECK_EQ(output_resource_format, static_cast<unsigned>(kRGBResourceFormat)); 117 return coded_size; 118 } 119 120 VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes( 121 const scoped_refptr<media::VideoFrame>& video_frame) { 122 media::VideoFrame::Format input_frame_format = video_frame->format(); 123 124 #if defined(GOOGLE_TV) 125 if (input_frame_format == media::VideoFrame::HOLE) { 126 VideoFrameExternalResources external_resources; 127 external_resources.type = VideoFrameExternalResources::HOLE; 128 return external_resources; 129 } 130 #endif 131 132 // Only YUV software video frames are supported. 133 DCHECK(input_frame_format == media::VideoFrame::YV12 || 134 input_frame_format == media::VideoFrame::YV12A || 135 input_frame_format == media::VideoFrame::YV16); 136 if (input_frame_format != media::VideoFrame::YV12 && 137 input_frame_format != media::VideoFrame::YV12A && 138 input_frame_format != media::VideoFrame::YV16) 139 return VideoFrameExternalResources(); 140 141 bool software_compositor = !resource_provider_->GraphicsContext3D(); 142 143 GLenum output_resource_format = kYUVResourceFormat; 144 size_t output_plane_count = 145 (input_frame_format == media::VideoFrame::YV12A) ? 4 : 3; 146 147 // TODO(skaslev): If we're in software compositing mode, we do the YUV -> RGB 148 // conversion here. That involves an extra copy of each frame to a bitmap. 149 // Obviously, this is suboptimal and should be addressed once ubercompositor 150 // starts shaping up. 151 if (software_compositor) { 152 output_resource_format = kRGBResourceFormat; 153 output_plane_count = 1; 154 } 155 156 int max_resource_size = resource_provider_->max_texture_size(); 157 gfx::Size coded_frame_size = video_frame->coded_size(); 158 159 std::vector<PlaneResource> plane_resources; 160 bool allocation_success = true; 161 162 for (size_t i = 0; i < output_plane_count; ++i) { 163 gfx::Size output_plane_resource_size = 164 SoftwarePlaneDimension(input_frame_format, 165 coded_frame_size, 166 output_resource_format, 167 i); 168 if (output_plane_resource_size.IsEmpty() || 169 output_plane_resource_size.width() > max_resource_size || 170 output_plane_resource_size.height() > max_resource_size) { 171 allocation_success = false; 172 break; 173 } 174 175 ResourceProvider::ResourceId resource_id = 0; 176 gpu::Mailbox mailbox; 177 178 // Try recycle a previously-allocated resource. 179 for (size_t i = 0; i < recycled_resources_.size(); ++i) { 180 if (recycled_resources_[i].resource_format == output_resource_format && 181 recycled_resources_[i].resource_size == output_plane_resource_size) { 182 resource_id = recycled_resources_[i].resource_id; 183 mailbox = recycled_resources_[i].mailbox; 184 recycled_resources_.erase(recycled_resources_.begin() + i); 185 break; 186 } 187 } 188 189 if (resource_id == 0) { 190 // TODO(danakj): Abstract out hw/sw resource create/delete from 191 // ResourceProvider and stop using ResourceProvider in this class. 192 resource_id = 193 resource_provider_->CreateResource(output_plane_resource_size, 194 output_resource_format, 195 ResourceProvider::TextureUsageAny); 196 197 DCHECK(mailbox.IsZero()); 198 199 if (!software_compositor) { 200 WebKit::WebGraphicsContext3D* context = 201 resource_provider_->GraphicsContext3D(); 202 DCHECK(context); 203 204 GLC(context, context->genMailboxCHROMIUM(mailbox.name)); 205 if (mailbox.IsZero()) { 206 resource_provider_->DeleteResource(resource_id); 207 resource_id = 0; 208 } else { 209 ResourceProvider::ScopedWriteLockGL lock( 210 resource_provider_, resource_id); 211 GLC(context, context->bindTexture(GL_TEXTURE_2D, lock.texture_id())); 212 GLC(context, context->produceTextureCHROMIUM(GL_TEXTURE_2D, 213 mailbox.name)); 214 GLC(context, context->bindTexture(GL_TEXTURE_2D, 0)); 215 } 216 } 217 218 if (resource_id) 219 all_resources_.push_back(resource_id); 220 } 221 222 if (resource_id == 0) { 223 allocation_success = false; 224 break; 225 } 226 227 DCHECK(software_compositor || !mailbox.IsZero()); 228 plane_resources.push_back(PlaneResource(resource_id, 229 output_plane_resource_size, 230 output_resource_format, 231 mailbox)); 232 } 233 234 if (!allocation_success) { 235 for (size_t i = 0; i < plane_resources.size(); ++i) 236 DeleteResource(plane_resources[i].resource_id); 237 return VideoFrameExternalResources(); 238 } 239 240 VideoFrameExternalResources external_resources; 241 242 if (software_compositor) { 243 DCHECK_EQ(plane_resources.size(), 1u); 244 DCHECK_EQ(plane_resources[0].resource_format, kRGBResourceFormat); 245 DCHECK(plane_resources[0].mailbox.IsZero()); 246 247 if (!video_renderer_) 248 video_renderer_.reset(new media::SkCanvasVideoRenderer); 249 250 { 251 ResourceProvider::ScopedWriteLockSoftware lock( 252 resource_provider_, plane_resources[0].resource_id); 253 video_renderer_->Paint(video_frame.get(), 254 lock.sk_canvas(), 255 video_frame->visible_rect(), 256 0xff); 257 } 258 259 // In software mode, the resource provider won't be lost. Soon this callback 260 // will be called directly from the resource provider, same as 3d 261 // compositing mode, so this raw unretained resource_provider will always 262 // be valid when the callback is fired. 263 RecycleResourceData recycle_data = { 264 plane_resources[0].resource_id, 265 plane_resources[0].resource_size, 266 plane_resources[0].resource_format, 267 gpu::Mailbox() 268 }; 269 TextureMailbox::ReleaseCallback callback_to_free_resource = 270 base::Bind(&RecycleResource, 271 AsWeakPtr(), 272 recycle_data); 273 external_resources.software_resources.push_back( 274 plane_resources[0].resource_id); 275 external_resources.software_release_callback = callback_to_free_resource; 276 277 external_resources.type = VideoFrameExternalResources::SOFTWARE_RESOURCE; 278 return external_resources; 279 } 280 281 for (size_t i = 0; i < plane_resources.size(); ++i) { 282 // Update each plane's resource id with its content. 283 DCHECK_EQ(plane_resources[i].resource_format, 284 static_cast<unsigned>(kYUVResourceFormat)); 285 286 const uint8_t* input_plane_pixels = video_frame->data(i); 287 288 gfx::Rect image_rect(0, 289 0, 290 video_frame->stride(i), 291 plane_resources[i].resource_size.height()); 292 gfx::Rect source_rect(plane_resources[i].resource_size); 293 resource_provider_->SetPixels(plane_resources[i].resource_id, 294 input_plane_pixels, 295 image_rect, 296 source_rect, 297 gfx::Vector2d()); 298 299 RecycleResourceData recycle_data = { 300 plane_resources[i].resource_id, 301 plane_resources[i].resource_size, 302 plane_resources[i].resource_format, 303 plane_resources[i].mailbox 304 }; 305 TextureMailbox::ReleaseCallback callback_to_free_resource = 306 base::Bind(&RecycleResource, 307 AsWeakPtr(), 308 recycle_data); 309 external_resources.mailboxes.push_back( 310 TextureMailbox(plane_resources[i].mailbox, 311 callback_to_free_resource)); 312 } 313 314 external_resources.type = VideoFrameExternalResources::YUV_RESOURCE; 315 return external_resources; 316 } 317 318 static void ReturnTexture( 319 scoped_refptr<media::VideoFrame::MailboxHolder> mailbox_holder, 320 unsigned sync_point, 321 bool lost_resource) { 322 mailbox_holder->Return(sync_point); 323 } 324 325 VideoFrameExternalResources VideoResourceUpdater::CreateForHardwarePlanes( 326 const scoped_refptr<media::VideoFrame>& video_frame) { 327 media::VideoFrame::Format frame_format = video_frame->format(); 328 329 DCHECK_EQ(frame_format, media::VideoFrame::NATIVE_TEXTURE); 330 if (frame_format != media::VideoFrame::NATIVE_TEXTURE) 331 return VideoFrameExternalResources(); 332 333 WebKit::WebGraphicsContext3D* context = 334 resource_provider_->GraphicsContext3D(); 335 if (!context) 336 return VideoFrameExternalResources(); 337 338 VideoFrameExternalResources external_resources; 339 switch (video_frame->texture_target()) { 340 case GL_TEXTURE_2D: 341 external_resources.type = VideoFrameExternalResources::RGB_RESOURCE; 342 break; 343 case GL_TEXTURE_EXTERNAL_OES: 344 external_resources.type = 345 VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE; 346 break; 347 case GL_TEXTURE_RECTANGLE_ARB: 348 external_resources.type = VideoFrameExternalResources::IO_SURFACE; 349 break; 350 default: 351 NOTREACHED(); 352 return VideoFrameExternalResources(); 353 } 354 355 scoped_refptr<media::VideoFrame::MailboxHolder> mailbox_holder = 356 video_frame->texture_mailbox(); 357 358 TextureMailbox::ReleaseCallback callback_to_return_resource = 359 base::Bind(&ReturnTexture, mailbox_holder); 360 361 external_resources.mailboxes.push_back( 362 TextureMailbox(mailbox_holder->mailbox(), 363 callback_to_return_resource, 364 video_frame->texture_target(), 365 mailbox_holder->sync_point())); 366 return external_resources; 367 } 368 369 // static 370 void VideoResourceUpdater::RecycleResource( 371 base::WeakPtr<VideoResourceUpdater> updater, 372 RecycleResourceData data, 373 unsigned sync_point, 374 bool lost_resource) { 375 if (!updater.get()) { 376 // Resource was already deleted. 377 return; 378 } 379 380 WebKit::WebGraphicsContext3D* context = 381 updater->resource_provider_->GraphicsContext3D(); 382 if (context && sync_point) 383 GLC(context, context->waitSyncPoint(sync_point)); 384 385 if (lost_resource) { 386 updater->DeleteResource(data.resource_id); 387 return; 388 } 389 390 // Drop recycled resources that are the wrong format. 391 while (!updater->recycled_resources_.empty() && 392 updater->recycled_resources_.back().resource_format != 393 data.resource_format) { 394 updater->DeleteResource(updater->recycled_resources_.back().resource_id); 395 updater->recycled_resources_.pop_back(); 396 } 397 398 PlaneResource recycled_resource(data.resource_id, 399 data.resource_size, 400 data.resource_format, 401 data.mailbox); 402 updater->recycled_resources_.push_back(recycled_resource); 403 } 404 405 } // namespace cc 406