|  | // Copyright (c) 2012 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/renderers/paint_canvas_video_renderer.h" | 
|  |  | 
|  | #include <GLES3/gl3.h> | 
|  | #include <limits> | 
|  |  | 
|  | #include "base/barrier_closure.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/numerics/checked_math.h" | 
|  | #include "base/synchronization/waitable_event.h" | 
|  | #include "base/system/sys_info.h" | 
|  | #include "base/task/thread_pool.h" | 
|  | #include "base/threading/thread_restrictions.h" | 
|  | #include "build/build_config.h" | 
|  | #include "cc/paint/paint_canvas.h" | 
|  | #include "cc/paint/paint_flags.h" | 
|  | #include "cc/paint/paint_image_builder.h" | 
|  | #include "components/viz/common/gpu/raster_context_provider.h" | 
|  | #include "components/viz/common/resources/resource_format.h" | 
|  | #include "gpu/GLES2/gl2extchromium.h" | 
|  | #include "gpu/command_buffer/client/context_support.h" | 
|  | #include "gpu/command_buffer/client/gles2_interface.h" | 
|  | #include "gpu/command_buffer/client/raster_interface.h" | 
|  | #include "gpu/command_buffer/client/shared_image_interface.h" | 
|  | #include "gpu/command_buffer/common/capabilities.h" | 
|  | #include "gpu/command_buffer/common/mailbox_holder.h" | 
|  | #include "gpu/command_buffer/common/shared_image_usage.h" | 
|  | #include "media/base/data_buffer.h" | 
|  | #include "media/base/video_frame.h" | 
|  | #include "media/base/wait_and_replace_sync_token_client.h" | 
|  | #include "third_party/libyuv/include/libyuv.h" | 
|  | #include "third_party/skia/include/core/SkImage.h" | 
|  | #include "third_party/skia/include/core/SkImageGenerator.h" | 
|  | #include "third_party/skia/include/core/SkImageInfo.h" | 
|  | #include "third_party/skia/include/gpu/GrBackendSurface.h" | 
|  | #include "third_party/skia/include/gpu/GrDirectContext.h" | 
|  | #include "third_party/skia/include/gpu/gl/GrGLTypes.h" | 
|  | #include "ui/gfx/geometry/rect_f.h" | 
|  | #include "ui/gfx/geometry/skia_conversions.h" | 
|  |  | 
|  | // Skia internal format depends on a platform. On Android it is ABGR, on others | 
|  | // it's ARGB. YUV_MATRIX(), YUV_ORDER() conditionally remap YUV to YVU for ABGR. | 
|  | #if SK_B32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_R32_SHIFT == 16 && \ | 
|  | SK_A32_SHIFT == 24 | 
|  | #define OUTPUT_ARGB 1 | 
|  | #define LIBYUV_ABGR_TO_ARGB libyuv::ABGRToARGB | 
|  | #define YUV_MATRIX(matrix) matrix | 
|  | #define YUV_ORDER(y, y_stride, u, u_stride, v, v_stride) \ | 
|  | (y), (y_stride), (u), (u_stride), (v), (v_stride) | 
|  | #define GBR_TO_RGB_ORDER(y, y_stride, u, u_stride, v, v_stride) \ | 
|  | (v), (v_stride), (y), (y_stride), (u), (u_stride) | 
|  | #define LIBYUV_NV12_TO_ARGB_MATRIX libyuv::NV12ToARGBMatrix | 
|  | #elif SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && \ | 
|  | SK_A32_SHIFT == 24 | 
|  | #define OUTPUT_ARGB 0 | 
|  | #define LIBYUV_ABGR_TO_ARGB libyuv::ARGBToABGR | 
|  | #define YUV_MATRIX(matrix) matrix##VU | 
|  | #define YUV_ORDER(y, y_stride, u, u_stride, v, v_stride) \ | 
|  | (y), (y_stride), (v), (v_stride), (u), (u_stride) | 
|  | #define GBR_TO_RGB_ORDER(y, y_stride, u, u_stride, v, v_stride) \ | 
|  | (u), (u_stride), (y), (y_stride), (v), (v_stride) | 
|  | #define LIBYUV_NV12_TO_ARGB_MATRIX libyuv::NV21ToARGBMatrix | 
|  | #else | 
|  | #error Unexpected Skia ARGB_8888 layout! | 
|  | #endif | 
|  |  | 
|  | namespace media { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // This class keeps the last image drawn. | 
|  | // We delete the temporary resource if it is not used for 3 seconds. | 
|  | const int kTemporaryResourceDeletionDelay = 3;  // Seconds; | 
|  |  | 
|  | // Helper class that begins/ends access to a mailbox within a scope. The mailbox | 
|  | // must have been imported into |texture|. | 
|  | class ScopedSharedImageAccess { | 
|  | public: | 
|  | ScopedSharedImageAccess( | 
|  | gpu::gles2::GLES2Interface* gl, | 
|  | GLuint texture, | 
|  | const gpu::Mailbox& mailbox, | 
|  | GLenum access = GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM) | 
|  | : gl(gl), | 
|  | ri(nullptr), | 
|  | texture(texture), | 
|  | is_shared_image(mailbox.IsSharedImage()) { | 
|  | if (is_shared_image) | 
|  | gl->BeginSharedImageAccessDirectCHROMIUM(texture, access); | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/1023270): Remove this ctor once we're no longer relying on | 
|  | // texture ids for Mailbox access as that is only supported on | 
|  | // RasterImplementationGLES. | 
|  | ScopedSharedImageAccess( | 
|  | gpu::raster::RasterInterface* ri, | 
|  | GLuint texture, | 
|  | const gpu::Mailbox& mailbox, | 
|  | GLenum access = GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM) | 
|  | : gl(nullptr), | 
|  | ri(ri), | 
|  | texture(texture), | 
|  | is_shared_image(mailbox.IsSharedImage()) { | 
|  | if (is_shared_image) | 
|  | ri->BeginSharedImageAccessDirectCHROMIUM(texture, access); | 
|  | } | 
|  |  | 
|  | ~ScopedSharedImageAccess() { | 
|  | if (is_shared_image) { | 
|  | if (gl) | 
|  | gl->EndSharedImageAccessDirectCHROMIUM(texture); | 
|  | else | 
|  | ri->EndSharedImageAccessDirectCHROMIUM(texture); | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | gpu::gles2::GLES2Interface* gl; | 
|  | gpu::raster::RasterInterface* ri; | 
|  | GLuint texture; | 
|  | bool is_shared_image; | 
|  | }; | 
|  |  | 
|  | const gpu::MailboxHolder& GetVideoFrameMailboxHolder(VideoFrame* video_frame) { | 
|  | DCHECK(video_frame->HasTextures()); | 
|  | DCHECK_EQ(video_frame->NumTextures(), 1u); | 
|  |  | 
|  | DCHECK(PIXEL_FORMAT_ARGB == video_frame->format() || | 
|  | PIXEL_FORMAT_XRGB == video_frame->format() || | 
|  | PIXEL_FORMAT_RGB24 == video_frame->format() || | 
|  | PIXEL_FORMAT_ABGR == video_frame->format() || | 
|  | PIXEL_FORMAT_XBGR == video_frame->format() || | 
|  | PIXEL_FORMAT_XB30 == video_frame->format() || | 
|  | PIXEL_FORMAT_XR30 == video_frame->format() || | 
|  | PIXEL_FORMAT_NV12 == video_frame->format()) | 
|  | << "Format: " << VideoPixelFormatToString(video_frame->format()); | 
|  |  | 
|  | const gpu::MailboxHolder& mailbox_holder = video_frame->mailbox_holder(0); | 
|  | DCHECK(mailbox_holder.texture_target == GL_TEXTURE_2D || | 
|  | mailbox_holder.texture_target == GL_TEXTURE_RECTANGLE_ARB || | 
|  | mailbox_holder.texture_target == GL_TEXTURE_EXTERNAL_OES) | 
|  | << mailbox_holder.texture_target; | 
|  | return mailbox_holder; | 
|  | } | 
|  |  | 
|  | // Wraps a GL RGBA texture into a SkImage. | 
|  | sk_sp<SkImage> WrapGLTexture( | 
|  | GLenum target, | 
|  | GLuint texture_id, | 
|  | const gfx::Size& size, | 
|  | viz::RasterContextProvider* raster_context_provider, | 
|  | bool texture_origin_is_top_left) { | 
|  | GrGLTextureInfo texture_info; | 
|  | texture_info.fID = texture_id; | 
|  | texture_info.fTarget = target; | 
|  | // TODO(bsalomon): GrGLTextureInfo::fFormat and SkColorType passed to | 
|  | // SkImage factory should reflect video_frame->format(). Update once | 
|  | // Skia supports GL_RGB. skbug.com/7533 | 
|  | texture_info.fFormat = GL_RGBA8_OES; | 
|  | GrBackendTexture backend_texture(size.width(), size.height(), | 
|  | GrMipMapped::kNo, texture_info); | 
|  | return SkImage::MakeFromAdoptedTexture( | 
|  | raster_context_provider->GrContext(), backend_texture, | 
|  | texture_origin_is_top_left ? kTopLeft_GrSurfaceOrigin | 
|  | : kBottomLeft_GrSurfaceOrigin, | 
|  | kRGBA_8888_SkColorType, kPremul_SkAlphaType); | 
|  | } | 
|  |  | 
|  | void CopyMailboxToTexture(gpu::gles2::GLES2Interface* gl, | 
|  | const gfx::Size& coded_size, | 
|  | const gfx::Rect& visible_rect, | 
|  | const gpu::Mailbox& source_mailbox, | 
|  | const gpu::SyncToken& source_sync_token, | 
|  | unsigned int target, | 
|  | unsigned int texture, | 
|  | unsigned int internal_format, | 
|  | unsigned int format, | 
|  | unsigned int type, | 
|  | int level, | 
|  | bool premultiply_alpha, | 
|  | bool flip_y) { | 
|  | gl->WaitSyncTokenCHROMIUM(source_sync_token.GetConstData()); | 
|  | GLuint source_texture = | 
|  | source_mailbox.IsSharedImage() | 
|  | ? gl->CreateAndTexStorage2DSharedImageCHROMIUM(source_mailbox.name) | 
|  | : gl->CreateAndConsumeTextureCHROMIUM(source_mailbox.name); | 
|  | { | 
|  | ScopedSharedImageAccess access(gl, source_texture, source_mailbox); | 
|  | // The video is stored in a unmultiplied format, so premultiply if | 
|  | // necessary. Application itself needs to take care of setting the right | 
|  | // |flip_y| value down to get the expected result. "flip_y == true" means to | 
|  | // reverse the video orientation while "flip_y == false" means to keep the | 
|  | // intrinsic orientation. | 
|  | if (visible_rect != gfx::Rect(coded_size)) { | 
|  | // Must reallocate the destination texture and copy only a sub-portion. | 
|  |  | 
|  | // There should always be enough data in the source texture to | 
|  | // cover this copy. | 
|  | DCHECK_LE(visible_rect.width(), coded_size.width()); | 
|  | DCHECK_LE(visible_rect.height(), coded_size.height()); | 
|  |  | 
|  | gl->BindTexture(target, texture); | 
|  | gl->TexImage2D(target, level, internal_format, visible_rect.width(), | 
|  | visible_rect.height(), 0, format, type, nullptr); | 
|  | gl->CopySubTextureCHROMIUM(source_texture, 0, target, texture, level, 0, | 
|  | 0, visible_rect.x(), visible_rect.y(), | 
|  | visible_rect.width(), visible_rect.height(), | 
|  | flip_y, premultiply_alpha, false); | 
|  |  | 
|  | } else { | 
|  | gl->CopyTextureCHROMIUM(source_texture, 0, target, texture, level, | 
|  | internal_format, type, flip_y, premultiply_alpha, | 
|  | false); | 
|  | } | 
|  | } | 
|  | gl->DeleteTextures(1, &source_texture); | 
|  | } | 
|  |  | 
|  | // Update |video_frame|'s release sync token to reflect the work done in |ri|, | 
|  | // and ensure that |video_frame| be kept remain alive until |ri|'s commands have | 
|  | // been completed. This is implemented for both gpu::gles2::GLES2Interface and | 
|  | // gpu::raster::RasterInterface. This function is critical to ensure that | 
|  | // |video_frame|'s resources not be returned until they are no longer in use. | 
|  | // https://crbug.com/819914 (software video decode frame corruption) | 
|  | // https://crbug.com/1237100 (camera capture reuse corruption) | 
|  | void SynchronizeVideoFrameRead(scoped_refptr<VideoFrame> video_frame, | 
|  | gpu::raster::RasterInterface* ri, | 
|  | gpu::ContextSupport* context_support) { | 
|  | WaitAndReplaceSyncTokenClient client(ri); | 
|  | video_frame->UpdateReleaseSyncToken(&client); | 
|  | if (!video_frame->metadata().read_lock_fences_enabled) | 
|  | return; | 
|  |  | 
|  | unsigned query_id = 0; | 
|  | ri->GenQueriesEXT(1, &query_id); | 
|  | DCHECK(query_id); | 
|  | ri->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, query_id); | 
|  | ri->EndQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM); | 
|  |  | 
|  | // |on_query_done_cb| will keep |video_frame| alive. | 
|  | auto on_query_done_cb = base::BindOnce( | 
|  | [](scoped_refptr<VideoFrame> video_frame, | 
|  | gpu::raster::RasterInterface* ri, | 
|  | unsigned query_id) { ri->DeleteQueriesEXT(1, &query_id); }, | 
|  | video_frame, ri, query_id); | 
|  | context_support->SignalQuery(query_id, std::move(on_query_done_cb)); | 
|  | } | 
|  |  | 
|  | void SynchronizeVideoFrameRead(scoped_refptr<VideoFrame> video_frame, | 
|  | gpu::gles2::GLES2Interface* gl, | 
|  | gpu::ContextSupport* context_support) { | 
|  | WaitAndReplaceSyncTokenClient client(gl); | 
|  | video_frame->UpdateReleaseSyncToken(&client); | 
|  | if (!video_frame->metadata().read_lock_fences_enabled) | 
|  | return; | 
|  |  | 
|  | unsigned query_id = 0; | 
|  | gl->GenQueriesEXT(1, &query_id); | 
|  | DCHECK(query_id); | 
|  | gl->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, query_id); | 
|  | gl->EndQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM); | 
|  |  | 
|  | // |on_query_done_cb| will keep |video_frame| alive. | 
|  | auto on_query_done_cb = | 
|  | base::BindOnce([](scoped_refptr<VideoFrame> video_frame) {}, video_frame); | 
|  | context_support->SignalQuery(query_id, std::move(on_query_done_cb)); | 
|  |  | 
|  | // Delete the query immediately. This will cause |on_query_done_cb| to be | 
|  | // issued prematurely. The alternative, deleting |query_id| within | 
|  | // |on_query_done_cb|, can cause a crash because |gl| may not still exist | 
|  | // at the time of the callback. This is incorrect behavior. | 
|  | // https://crbug.com/1243763 | 
|  | gl->DeleteQueriesEXT(1, &query_id); | 
|  | } | 
|  |  | 
|  | // TODO(thomasanderson): Remove these and use std::gcd and std::lcm once we're | 
|  | // building with C++17. | 
|  | size_t GCD(size_t a, size_t b) { | 
|  | return a == 0 ? b : GCD(b % a, a); | 
|  | } | 
|  | size_t LCM(size_t a, size_t b) { | 
|  | return a / GCD(a, b) * b; | 
|  | } | 
|  |  | 
|  | const libyuv::YuvConstants* GetYuvContantsForColorSpace(SkYUVColorSpace cs) { | 
|  | switch (cs) { | 
|  | case kJPEG_Full_SkYUVColorSpace: | 
|  | return &YUV_MATRIX(libyuv::kYuvJPEGConstants); | 
|  | case kRec601_Limited_SkYUVColorSpace: | 
|  | return &YUV_MATRIX(libyuv::kYuvI601Constants); | 
|  | case kRec709_Full_SkYUVColorSpace: | 
|  | return &YUV_MATRIX(libyuv::kYuvF709Constants); | 
|  | case kRec709_Limited_SkYUVColorSpace: | 
|  | return &YUV_MATRIX(libyuv::kYuvH709Constants); | 
|  | case kBT2020_8bit_Full_SkYUVColorSpace: | 
|  | case kBT2020_10bit_Full_SkYUVColorSpace: | 
|  | case kBT2020_12bit_Full_SkYUVColorSpace: | 
|  | return &YUV_MATRIX(libyuv::kYuvV2020Constants); | 
|  | case kBT2020_8bit_Limited_SkYUVColorSpace: | 
|  | case kBT2020_10bit_Limited_SkYUVColorSpace: | 
|  | case kBT2020_12bit_Limited_SkYUVColorSpace: | 
|  | return &YUV_MATRIX(libyuv::kYuv2020Constants); | 
|  | case kIdentity_SkYUVColorSpace: | 
|  | NOTREACHED(); | 
|  | return nullptr; | 
|  | }; | 
|  | } | 
|  |  | 
|  | void ConvertVideoFrameToRGBPixelsTask(const VideoFrame* video_frame, | 
|  | void* rgb_pixels, | 
|  | size_t row_bytes, | 
|  | bool premultiply_alpha, | 
|  | size_t task_index, | 
|  | size_t n_tasks, | 
|  | base::RepeatingClosure* done) { | 
|  | const VideoPixelFormat format = video_frame->format(); | 
|  | const int width = video_frame->visible_rect().width(); | 
|  | const int height = video_frame->visible_rect().height(); | 
|  |  | 
|  | size_t rows_per_chunk = 1; | 
|  | for (size_t plane = 0; plane < VideoFrame::kMaxPlanes; ++plane) { | 
|  | if (VideoFrame::IsValidPlane(format, plane)) { | 
|  | rows_per_chunk = | 
|  | LCM(rows_per_chunk, VideoFrame::SampleSize(format, plane).height()); | 
|  | } | 
|  | } | 
|  |  | 
|  | base::CheckedNumeric<size_t> chunks = height / rows_per_chunk; | 
|  | const size_t chunk_start = (chunks * task_index / n_tasks).ValueOrDie(); | 
|  | const size_t chunk_end = (chunks * (task_index + 1) / n_tasks).ValueOrDie(); | 
|  |  | 
|  | // Indivisible heights must process any remaining rows in the last task. | 
|  | size_t rows = (chunk_end - chunk_start) * rows_per_chunk; | 
|  | if (task_index + 1 == n_tasks) | 
|  | rows += height % rows_per_chunk; | 
|  |  | 
|  | struct { | 
|  | int stride; | 
|  | const uint8_t* data; | 
|  | } plane_meta[VideoFrame::kMaxPlanes]; | 
|  |  | 
|  | for (size_t plane = 0; plane < VideoFrame::kMaxPlanes; ++plane) { | 
|  | if (VideoFrame::IsValidPlane(format, plane)) { | 
|  | plane_meta[plane] = { | 
|  | // Note: Unlike |data|, stride does not need to be adjusted by the | 
|  | // visible rect and sample size. Adding the full frame stride to a | 
|  | // pixel on row N and column M will wrap to column M on row N + 1. | 
|  | .stride = video_frame->stride(plane), | 
|  |  | 
|  | .data = video_frame->visible_data(plane) + | 
|  | video_frame->stride(plane) * (chunk_start * rows_per_chunk) / | 
|  | VideoFrame::SampleSize(format, plane).height()}; | 
|  | } else { | 
|  | plane_meta[plane] = {.stride = 0, .data = nullptr}; | 
|  | } | 
|  | } | 
|  |  | 
|  | uint8_t* pixels = static_cast<uint8_t*>(rgb_pixels) + | 
|  | row_bytes * chunk_start * rows_per_chunk; | 
|  |  | 
|  | if (format == PIXEL_FORMAT_ARGB || format == PIXEL_FORMAT_XRGB || | 
|  | format == PIXEL_FORMAT_ABGR || format == PIXEL_FORMAT_XBGR) { | 
|  | DCHECK_LE(width, static_cast<int>(row_bytes)); | 
|  | const uint8_t* data = plane_meta[VideoFrame::kARGBPlane].data; | 
|  |  | 
|  | if ((OUTPUT_ARGB && | 
|  | (format == PIXEL_FORMAT_ARGB || format == PIXEL_FORMAT_XRGB)) || | 
|  | (!OUTPUT_ARGB && | 
|  | (format == PIXEL_FORMAT_ABGR || format == PIXEL_FORMAT_XBGR))) { | 
|  | for (size_t i = 0; i < rows; i++) { | 
|  | memcpy(pixels, data, width * 4); | 
|  | pixels += row_bytes; | 
|  | data += plane_meta[VideoFrame::kARGBPlane].stride; | 
|  | } | 
|  | } else { | 
|  | LIBYUV_ABGR_TO_ARGB(plane_meta[VideoFrame::kARGBPlane].data, | 
|  | plane_meta[VideoFrame::kARGBPlane].stride, pixels, | 
|  | row_bytes, width, rows); | 
|  | } | 
|  | done->Run(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/828599): This should default to BT.709 color space. | 
|  | auto yuv_cs = kRec601_SkYUVColorSpace; | 
|  | video_frame->ColorSpace().ToSkYUVColorSpace(video_frame->BitDepth(), &yuv_cs); | 
|  | const libyuv::YuvConstants* matrix = GetYuvContantsForColorSpace(yuv_cs); | 
|  |  | 
|  | if (!video_frame->data(VideoFrame::kUPlane) && | 
|  | !video_frame->data(VideoFrame::kVPlane)) { | 
|  | DCHECK_EQ(format, PIXEL_FORMAT_I420); | 
|  | // For monochrome content ARGB and ABGR are interchangeable. | 
|  | libyuv::I400ToARGBMatrix(plane_meta[VideoFrame::kYPlane].data, | 
|  | plane_meta[VideoFrame::kYPlane].stride, pixels, | 
|  | row_bytes, matrix, width, rows); | 
|  | done->Run(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto convert_yuv = [&](const libyuv::YuvConstants* matrix, auto&& func) { | 
|  | func(YUV_ORDER(plane_meta[VideoFrame::kYPlane].data, | 
|  | plane_meta[VideoFrame::kYPlane].stride, | 
|  | plane_meta[VideoFrame::kUPlane].data, | 
|  | plane_meta[VideoFrame::kUPlane].stride, | 
|  | plane_meta[VideoFrame::kVPlane].data, | 
|  | plane_meta[VideoFrame::kVPlane].stride), | 
|  | pixels, row_bytes, matrix, width, rows); | 
|  | }; | 
|  |  | 
|  | auto convert_yuv16 = [&](const libyuv::YuvConstants* matrix, auto&& func) { | 
|  | func(YUV_ORDER(reinterpret_cast<const uint16_t*>( | 
|  | plane_meta[VideoFrame::kYPlane].data), | 
|  | plane_meta[VideoFrame::kYPlane].stride / 2, | 
|  | reinterpret_cast<const uint16_t*>( | 
|  | plane_meta[VideoFrame::kUPlane].data), | 
|  | plane_meta[VideoFrame::kUPlane].stride / 2, | 
|  | reinterpret_cast<const uint16_t*>( | 
|  | plane_meta[VideoFrame::kVPlane].data), | 
|  | plane_meta[VideoFrame::kVPlane].stride / 2), | 
|  | pixels, row_bytes, matrix, width, rows); | 
|  | }; | 
|  |  | 
|  | switch (format) { | 
|  | case PIXEL_FORMAT_YV12: | 
|  | case PIXEL_FORMAT_I420: | 
|  | convert_yuv(matrix, libyuv::I420ToARGBMatrix); | 
|  | break; | 
|  | case PIXEL_FORMAT_I422: | 
|  | convert_yuv(matrix, libyuv::I422ToARGBMatrix); | 
|  | break; | 
|  | case PIXEL_FORMAT_I444: | 
|  | // Special case for 4:4:4 RGB encoded videos. | 
|  | if (video_frame->ColorSpace().GetMatrixID() == | 
|  | gfx::ColorSpace::MatrixID::GBR) { | 
|  | libyuv::MergeARGBPlane( | 
|  | GBR_TO_RGB_ORDER(plane_meta[VideoFrame::kYPlane].data, | 
|  | plane_meta[VideoFrame::kYPlane].stride, | 
|  | plane_meta[VideoFrame::kUPlane].data, | 
|  | plane_meta[VideoFrame::kUPlane].stride, | 
|  | plane_meta[VideoFrame::kVPlane].data, | 
|  | plane_meta[VideoFrame::kVPlane].stride), | 
|  | plane_meta[VideoFrame::kAPlane].data, | 
|  | plane_meta[VideoFrame::kAPlane].stride, pixels, row_bytes, width, | 
|  | rows); | 
|  | } else { | 
|  | convert_yuv(matrix, libyuv::I444ToARGBMatrix); | 
|  | } | 
|  | break; | 
|  | case PIXEL_FORMAT_YUV420P10: | 
|  | convert_yuv16(matrix, libyuv::I010ToARGBMatrix); | 
|  | break; | 
|  | case PIXEL_FORMAT_YUV422P10: | 
|  | convert_yuv16(matrix, libyuv::I210ToARGBMatrix); | 
|  | break; | 
|  |  | 
|  | case PIXEL_FORMAT_I420A: | 
|  | libyuv::I420AlphaToARGBMatrix( | 
|  | YUV_ORDER(plane_meta[VideoFrame::kYPlane].data, | 
|  | plane_meta[VideoFrame::kYPlane].stride, | 
|  | plane_meta[VideoFrame::kUPlane].data, | 
|  | plane_meta[VideoFrame::kUPlane].stride, | 
|  | plane_meta[VideoFrame::kVPlane].data, | 
|  | plane_meta[VideoFrame::kVPlane].stride), | 
|  | plane_meta[VideoFrame::kAPlane].data, | 
|  | plane_meta[VideoFrame::kAPlane].stride, pixels, row_bytes, matrix, | 
|  | width, rows, premultiply_alpha); | 
|  | break; | 
|  |  | 
|  | case PIXEL_FORMAT_NV12: | 
|  | LIBYUV_NV12_TO_ARGB_MATRIX(plane_meta[VideoFrame::kYPlane].data, | 
|  | plane_meta[VideoFrame::kYPlane].stride, | 
|  | plane_meta[VideoFrame::kUVPlane].data, | 
|  | plane_meta[VideoFrame::kUVPlane].stride, | 
|  | pixels, row_bytes, matrix, width, rows); | 
|  | break; | 
|  |  | 
|  | case PIXEL_FORMAT_YUV420P9: | 
|  | case PIXEL_FORMAT_YUV422P9: | 
|  | case PIXEL_FORMAT_YUV444P9: | 
|  | case PIXEL_FORMAT_YUV444P10: | 
|  | case PIXEL_FORMAT_YUV420P12: | 
|  | case PIXEL_FORMAT_YUV422P12: | 
|  | case PIXEL_FORMAT_YUV444P12: | 
|  | case PIXEL_FORMAT_Y16: | 
|  | NOTREACHED() | 
|  | << "These cases should be handled in ConvertVideoFrameToRGBPixels"; | 
|  | break; | 
|  |  | 
|  | case PIXEL_FORMAT_UYVY: | 
|  | case PIXEL_FORMAT_NV21: | 
|  | case PIXEL_FORMAT_YUY2: | 
|  | case PIXEL_FORMAT_ARGB: | 
|  | case PIXEL_FORMAT_BGRA: | 
|  | case PIXEL_FORMAT_XRGB: | 
|  | case PIXEL_FORMAT_RGB24: | 
|  | case PIXEL_FORMAT_MJPEG: | 
|  | case PIXEL_FORMAT_ABGR: | 
|  | case PIXEL_FORMAT_XBGR: | 
|  | case PIXEL_FORMAT_P016LE: | 
|  | case PIXEL_FORMAT_XR30: | 
|  | case PIXEL_FORMAT_XB30: | 
|  | case PIXEL_FORMAT_RGBAF16: | 
|  | case PIXEL_FORMAT_UNKNOWN: | 
|  | NOTREACHED() << "Only YUV formats and Y16 are supported, got: " | 
|  | << media::VideoPixelFormatToString(format); | 
|  | } | 
|  | done->Run(); | 
|  | } | 
|  |  | 
|  | // Valid gl texture internal format that can try to use direct uploading path. | 
|  | bool ValidFormatForDirectUploading( | 
|  | viz::RasterContextProvider* raster_context_provider, | 
|  | GrGLenum format, | 
|  | unsigned int type) { | 
|  | const gpu::Capabilities& context_caps = | 
|  | raster_context_provider->ContextCapabilities(); | 
|  | switch (format) { | 
|  | case GL_RGBA: | 
|  | return type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_SHORT_4_4_4_4; | 
|  | case GL_RGB: | 
|  | return type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_SHORT_5_6_5; | 
|  | // WebGL2 supported sized formats | 
|  | case GL_RGBA8: | 
|  | case GL_RGB565: | 
|  | case GL_RGBA16F: | 
|  | case GL_RGB8: | 
|  | case GL_RGB10_A2: | 
|  | case GL_RGBA4: | 
|  | return context_caps.major_version >= 3; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::tuple<SkYUVAInfo::PlaneConfig, SkYUVAInfo::Subsampling> | 
|  | VideoPixelFormatAsSkYUVAInfoValues(VideoPixelFormat format) { | 
|  | // The 9, 10, and 12 bit formats could be added here if GetYUVAPlanes() were | 
|  | // updated to convert data to unorm16/float16. Similarly, alpha planes and | 
|  | // formats with interleaved planes (e.g. NV12) could  be supported if that | 
|  | // function were updated to not assume 3 separate Y, U, and V planes. Also, | 
|  | // GpuImageDecodeCache would need be able to handle plane configurations | 
|  | // other than 3 separate y, u, and v planes (crbug.com/910276). | 
|  | switch (format) { | 
|  | case PIXEL_FORMAT_I420: | 
|  | return {SkYUVAInfo::PlaneConfig::kY_U_V, SkYUVAInfo::Subsampling::k420}; | 
|  | case PIXEL_FORMAT_I422: | 
|  | return {SkYUVAInfo::PlaneConfig::kY_U_V, SkYUVAInfo::Subsampling::k422}; | 
|  | case PIXEL_FORMAT_I444: | 
|  | return {SkYUVAInfo::PlaneConfig::kY_U_V, SkYUVAInfo::Subsampling::k444}; | 
|  | default: | 
|  | return {SkYUVAInfo::PlaneConfig::kUnknown, | 
|  | SkYUVAInfo::Subsampling::kUnknown}; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // anonymous namespace | 
|  |  | 
|  | // Generates an RGB image from a VideoFrame. Convert YUV to RGB plain on GPU. | 
|  | class VideoImageGenerator : public cc::PaintImageGenerator { | 
|  | public: | 
|  | VideoImageGenerator(scoped_refptr<VideoFrame> frame) | 
|  | : cc::PaintImageGenerator( | 
|  | SkImageInfo::MakeN32Premul(frame->visible_rect().width(), | 
|  | frame->visible_rect().height(), | 
|  | frame->ColorSpace().ToSkColorSpace())), | 
|  | frame_(std::move(frame)) { | 
|  | DCHECK(!frame_->HasTextures()); | 
|  | } | 
|  | ~VideoImageGenerator() override = default; | 
|  |  | 
|  | sk_sp<SkData> GetEncodedData() const override { return nullptr; } | 
|  |  | 
|  | bool GetPixels(const SkImageInfo& info, | 
|  | void* pixels, | 
|  | size_t row_bytes, | 
|  | size_t frame_index, | 
|  | cc::PaintImage::GeneratorClientId client_id, | 
|  | uint32_t lazy_pixel_ref) override { | 
|  | DCHECK_EQ(frame_index, 0u); | 
|  |  | 
|  | // If skia couldn't do the YUV conversion on GPU, we will on CPU. | 
|  | PaintCanvasVideoRenderer::ConvertVideoFrameToRGBPixels(frame_.get(), pixels, | 
|  | row_bytes); | 
|  |  | 
|  | if (!SkColorSpace::Equals(GetSkImageInfo().colorSpace(), | 
|  | info.colorSpace())) { | 
|  | SkPixmap src(GetSkImageInfo(), pixels, row_bytes); | 
|  | if (!src.readPixels(info, pixels, row_bytes)) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QueryYUVA(const SkYUVAPixmapInfo::SupportedDataTypes&, | 
|  | SkYUVAPixmapInfo* info) const override { | 
|  | SkYUVAInfo::PlaneConfig plane_config; | 
|  | SkYUVAInfo::Subsampling subsampling; | 
|  | std::tie(plane_config, subsampling) = | 
|  | VideoPixelFormatAsSkYUVAInfoValues(frame_->format()); | 
|  | if (plane_config == SkYUVAInfo::PlaneConfig::kUnknown) | 
|  | return false; | 
|  |  | 
|  | // Don't use the YUV conversion path for multi-plane RGB frames. | 
|  | if (frame_->format() == PIXEL_FORMAT_I444 && | 
|  | frame_->ColorSpace().GetMatrixID() == gfx::ColorSpace::MatrixID::GBR) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!info) | 
|  | return true; | 
|  |  | 
|  | SkYUVColorSpace yuv_color_space; | 
|  | if (!frame_->ColorSpace().ToSkYUVColorSpace(frame_->BitDepth(), | 
|  | &yuv_color_space)) { | 
|  | // TODO(crbug.com/828599): This should default to BT.709 color space. | 
|  | yuv_color_space = kRec601_SkYUVColorSpace; | 
|  | } | 
|  |  | 
|  | // We use the Y plane size because it may get rounded up to an even size. | 
|  | // Our implementation of GetYUVAPlanes expects this. | 
|  | auto y_size = VideoFrame::PlaneSizeInSamples( | 
|  | frame_->format(), VideoFrame::kYPlane, frame_->visible_rect().size()); | 
|  | auto yuva_info = SkYUVAInfo({y_size.width(), y_size.height()}, plane_config, | 
|  | subsampling, yuv_color_space); | 
|  | *info = SkYUVAPixmapInfo(yuva_info, SkYUVAPixmapInfo::DataType::kUnorm8, | 
|  | /*rowBytes=*/nullptr); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool GetYUVAPlanes(const SkYUVAPixmaps& pixmaps, | 
|  | size_t frame_index, | 
|  | uint32_t lazy_pixel_ref) override { | 
|  | DCHECK_EQ(frame_index, 0u); | 
|  | DCHECK_EQ(pixmaps.numPlanes(), 3); | 
|  |  | 
|  | #if DCHECK_IS_ON() | 
|  | SkYUVAInfo::PlaneConfig plane_config; | 
|  | SkYUVAInfo::Subsampling subsampling; | 
|  | std::tie(plane_config, subsampling) = | 
|  | VideoPixelFormatAsSkYUVAInfoValues(frame_->format()); | 
|  | DCHECK_EQ(plane_config, pixmaps.yuvaInfo().planeConfig()); | 
|  | DCHECK_EQ(subsampling, pixmaps.yuvaInfo().subsampling()); | 
|  | #endif | 
|  |  | 
|  | for (int plane = VideoFrame::kYPlane; plane <= VideoFrame::kVPlane; | 
|  | ++plane) { | 
|  | const auto plane_size = VideoFrame::PlaneSizeInSamples( | 
|  | frame_->format(), plane, frame_->visible_rect().size()); | 
|  | if (plane_size.width() != pixmaps.plane(plane).width() || | 
|  | plane_size.height() != pixmaps.plane(plane).height()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const auto& out_plane = pixmaps.plane(plane); | 
|  |  | 
|  | // Copy the frame to the supplied memory. It'd be nice to avoid this copy, | 
|  | // but the memory is externally owned so we can't w/o an API change. | 
|  | libyuv::CopyPlane(frame_->visible_data(plane), frame_->stride(plane), | 
|  | reinterpret_cast<uint8_t*>(out_plane.writable_addr()), | 
|  | out_plane.rowBytes(), plane_size.width(), | 
|  | plane_size.height()); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | scoped_refptr<VideoFrame> frame_; | 
|  |  | 
|  | DISALLOW_IMPLICIT_CONSTRUCTORS(VideoImageGenerator); | 
|  | }; | 
|  |  | 
|  | class VideoTextureBacking : public cc::TextureBacking { | 
|  | public: | 
|  | explicit VideoTextureBacking( | 
|  | sk_sp<SkImage> sk_image, | 
|  | const gpu::Mailbox& mailbox, | 
|  | bool wraps_video_frame_texture, | 
|  | scoped_refptr<viz::RasterContextProvider> raster_context_provider) | 
|  | : sk_image_(std::move(sk_image)), | 
|  | sk_image_info_(sk_image_->imageInfo()), | 
|  | mailbox_(mailbox), | 
|  | wraps_video_frame_texture_(wraps_video_frame_texture) { | 
|  | raster_context_provider_ = std::move(raster_context_provider); | 
|  | } | 
|  |  | 
|  | explicit VideoTextureBacking( | 
|  | const gpu::Mailbox& mailbox, | 
|  | const SkImageInfo& info, | 
|  | bool wraps_video_frame_texture, | 
|  | scoped_refptr<viz::RasterContextProvider> raster_context_provider) | 
|  | : sk_image_info_(info), | 
|  | mailbox_(mailbox), | 
|  | wraps_video_frame_texture_(wraps_video_frame_texture) { | 
|  | raster_context_provider_ = std::move(raster_context_provider); | 
|  | } | 
|  |  | 
|  | ~VideoTextureBacking() override { | 
|  | auto* ri = raster_context_provider_->RasterInterface(); | 
|  | if (!wraps_video_frame_texture_) { | 
|  | gpu::SyncToken sync_token; | 
|  | ri->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); | 
|  | auto* sii = raster_context_provider_->SharedImageInterface(); | 
|  | sii->DestroySharedImage(sync_token, mailbox_); | 
|  | } | 
|  | } | 
|  |  | 
|  | const SkImageInfo& GetSkImageInfo() override { return sk_image_info_; } | 
|  | gpu::Mailbox GetMailbox() const override { return mailbox_; } | 
|  | sk_sp<SkImage> GetAcceleratedSkImage() override { return sk_image_; } | 
|  | bool wraps_video_frame_texture() const { return wraps_video_frame_texture_; } | 
|  | const scoped_refptr<viz::RasterContextProvider>& raster_context_provider() | 
|  | const { | 
|  | return raster_context_provider_; | 
|  | } | 
|  |  | 
|  | // Used only for recycling this TextureBacking - where we need to keep the | 
|  | // texture/mailbox alive, but replace the SkImage. | 
|  | void ReplaceAcceleratedSkImage(sk_sp<SkImage> sk_image) { | 
|  | sk_image_ = sk_image; | 
|  | sk_image_info_ = sk_image->imageInfo(); | 
|  | } | 
|  |  | 
|  | sk_sp<SkImage> GetSkImageViaReadback() override { | 
|  | if (sk_image_) | 
|  | return sk_image_->makeNonTextureImage(); | 
|  |  | 
|  | sk_sp<SkData> image_pixels = | 
|  | SkData::MakeUninitialized(sk_image_info_.computeMinByteSize()); | 
|  | uint8_t* writable_pixels = | 
|  | static_cast<uint8_t*>(image_pixels->writable_data()); | 
|  | gpu::raster::RasterInterface* ri = | 
|  | raster_context_provider_->RasterInterface(); | 
|  | ri->ReadbackImagePixels(mailbox_, sk_image_info_, | 
|  | sk_image_info_.minRowBytes(), 0, 0, | 
|  | writable_pixels); | 
|  | return SkImage::MakeRasterData(sk_image_info_, std::move(image_pixels), | 
|  | sk_image_info_.minRowBytes()); | 
|  | } | 
|  |  | 
|  | bool readPixels(const SkImageInfo& dst_info, | 
|  | void* dst_pixels, | 
|  | size_t dst_row_bytes, | 
|  | int src_x, | 
|  | int src_y) override { | 
|  | if (sk_image_) { | 
|  | return sk_image_->readPixels(dst_info, dst_pixels, dst_row_bytes, src_x, | 
|  | src_y); | 
|  | } | 
|  | gpu::raster::RasterInterface* ri = | 
|  | raster_context_provider_->RasterInterface(); | 
|  | ri->ReadbackImagePixels(mailbox_, dst_info, dst_info.minRowBytes(), src_x, | 
|  | src_y, dst_pixels); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void FlushPendingSkiaOps() override { | 
|  | if (!raster_context_provider_ || !sk_image_) | 
|  | return; | 
|  | sk_image_->flushAndSubmit(raster_context_provider_->GrContext()); | 
|  | } | 
|  |  | 
|  | private: | 
|  | sk_sp<SkImage> sk_image_; | 
|  | SkImageInfo sk_image_info_; | 
|  | scoped_refptr<viz::RasterContextProvider> raster_context_provider_; | 
|  |  | 
|  | // This can be either the source VideoFrame's texture (if | 
|  | // |wraps_video_frame_texture_| is true) or a newly allocated shared image | 
|  | // (if |wraps_video_frame_texture_| is false) if a copy or conversion was | 
|  | // necessary. | 
|  | const gpu::Mailbox mailbox_; | 
|  |  | 
|  | // Whether |mailbox_| directly points to a texture of the VideoFrame | 
|  | // (if true), or to an allocated shared image (if false). | 
|  | const bool wraps_video_frame_texture_; | 
|  | }; | 
|  |  | 
|  | PaintCanvasVideoRenderer::PaintCanvasVideoRenderer() | 
|  | : cache_deleting_timer_(FROM_HERE, | 
|  | base::Seconds(kTemporaryResourceDeletionDelay), | 
|  | this, | 
|  | &PaintCanvasVideoRenderer::ResetCache), | 
|  | renderer_stable_id_(cc::PaintImage::GetNextId()) {} | 
|  |  | 
|  | PaintCanvasVideoRenderer::~PaintCanvasVideoRenderer() = default; | 
|  |  | 
|  | void PaintCanvasVideoRenderer::Paint( | 
|  | scoped_refptr<VideoFrame> video_frame, | 
|  | cc::PaintCanvas* canvas, | 
|  | const gfx::RectF& dest_rect, | 
|  | cc::PaintFlags& flags, | 
|  | VideoTransformation video_transformation, | 
|  | viz::RasterContextProvider* raster_context_provider) { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | if (flags.getAlpha() == 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (video_frame && video_frame->HasTextures()) { | 
|  | if (!raster_context_provider) { | 
|  | DLOG(ERROR) | 
|  | << "Can't render textured frames w/o viz::RasterContextProvider"; | 
|  | return;  // Unable to get/create a shared main thread context. | 
|  | } | 
|  | if (!raster_context_provider->GrContext() && | 
|  | !raster_context_provider->ContextCapabilities().supports_oop_raster) { | 
|  | DLOG(ERROR) | 
|  | << "Can't render textured frames w/o valid GrContext or OOP raster."; | 
|  | return;  // The context has been lost. | 
|  | } | 
|  | } | 
|  |  | 
|  | SkRect dest; | 
|  | dest.setLTRB(dest_rect.x(), dest_rect.y(), dest_rect.right(), | 
|  | dest_rect.bottom()); | 
|  |  | 
|  | // Paint black rectangle if there isn't a frame available or the | 
|  | // frame has an unexpected format. | 
|  | if (!video_frame.get() || video_frame->natural_size().IsEmpty() || | 
|  | !(media::IsYuvPlanar(video_frame->format()) || | 
|  | video_frame->format() == PIXEL_FORMAT_Y16 || | 
|  | video_frame->format() == PIXEL_FORMAT_ARGB || | 
|  | video_frame->format() == PIXEL_FORMAT_XRGB || | 
|  | video_frame->format() == PIXEL_FORMAT_ABGR || | 
|  | video_frame->format() == PIXEL_FORMAT_XBGR || | 
|  | video_frame->HasTextures())) { | 
|  | cc::PaintFlags black_with_alpha_flags; | 
|  | black_with_alpha_flags.setAlpha(flags.getAlpha()); | 
|  | canvas->drawRect(dest, black_with_alpha_flags); | 
|  | canvas->flush(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Don't allow wrapping the VideoFrame texture, as we want to be able to cache | 
|  | // the PaintImage, to avoid redundant readbacks if the canvas is software. | 
|  | if (!UpdateLastImage(video_frame, raster_context_provider, | 
|  | false /* allow_wrap_texture */)) { | 
|  | return; | 
|  | } | 
|  | DCHECK(cache_); | 
|  | cc::PaintImage image = cache_->paint_image; | 
|  | DCHECK(image); | 
|  |  | 
|  | absl::optional<ScopedSharedImageAccess> source_access; | 
|  | if (video_frame->HasTextures() && cache_->source_texture) { | 
|  | DCHECK(cache_->texture_backing); | 
|  | source_access.emplace(raster_context_provider->RasterInterface(), | 
|  | cache_->source_texture, | 
|  | cache_->texture_backing->GetMailbox()); | 
|  | } | 
|  |  | 
|  | cc::PaintFlags video_flags; | 
|  | video_flags.setAlpha(flags.getAlpha()); | 
|  | video_flags.setBlendMode(flags.getBlendMode()); | 
|  |  | 
|  | const bool need_rotation = video_transformation.rotation != VIDEO_ROTATION_0; | 
|  | const bool need_scaling = | 
|  | dest_rect.size() != gfx::SizeF(video_frame->visible_rect().size()); | 
|  | const bool need_translation = !dest_rect.origin().IsOrigin(); | 
|  | const bool needs_mirror = video_transformation.mirrored; | 
|  | bool need_transform = | 
|  | need_rotation || need_scaling || need_translation || needs_mirror; | 
|  | if (need_transform) { | 
|  | canvas->save(); | 
|  | canvas->translate( | 
|  | SkFloatToScalar(dest_rect.x() + (dest_rect.width() * 0.5f)), | 
|  | SkFloatToScalar(dest_rect.y() + (dest_rect.height() * 0.5f))); | 
|  | SkScalar angle = SkFloatToScalar(0.0f); | 
|  | switch (video_transformation.rotation) { | 
|  | case VIDEO_ROTATION_0: | 
|  | break; | 
|  | case VIDEO_ROTATION_90: | 
|  | angle = SkFloatToScalar(90.0f); | 
|  | break; | 
|  | case VIDEO_ROTATION_180: | 
|  | angle = SkFloatToScalar(180.0f); | 
|  | break; | 
|  | case VIDEO_ROTATION_270: | 
|  | angle = SkFloatToScalar(270.0f); | 
|  | break; | 
|  | } | 
|  | canvas->rotate(angle); | 
|  |  | 
|  | gfx::SizeF rotated_dest_size = dest_rect.size(); | 
|  |  | 
|  | const bool has_flipped_size = | 
|  | video_transformation.rotation == VIDEO_ROTATION_90 || | 
|  | video_transformation.rotation == VIDEO_ROTATION_270; | 
|  | if (has_flipped_size) { | 
|  | rotated_dest_size = | 
|  | gfx::SizeF(rotated_dest_size.height(), rotated_dest_size.width()); | 
|  | } | 
|  | auto sx = SkFloatToScalar(rotated_dest_size.width() / | 
|  | video_frame->visible_rect().width()); | 
|  | auto sy = SkFloatToScalar(rotated_dest_size.height() / | 
|  | video_frame->visible_rect().height()); | 
|  | if (needs_mirror) { | 
|  | if (has_flipped_size) | 
|  | sy *= -1; | 
|  | else | 
|  | sx *= -1; | 
|  | } | 
|  | canvas->scale(sx, sy); | 
|  | canvas->translate( | 
|  | -SkFloatToScalar(video_frame->visible_rect().width() * 0.5f), | 
|  | -SkFloatToScalar(video_frame->visible_rect().height() * 0.5f)); | 
|  | } | 
|  |  | 
|  | SkImageInfo info; | 
|  | size_t row_bytes; | 
|  | SkIPoint origin; | 
|  | void* pixels = nullptr; | 
|  | // This if is a special handling of video for SkiaPaintcanvas backend, where | 
|  | // the video does not need any transform and it is enough to draw the frame | 
|  | // directly into the skia canvas | 
|  | if (!need_transform && video_frame->IsMappable() && | 
|  | flags.getAlpha() == SK_AlphaOPAQUE && | 
|  | flags.getBlendMode() == SkBlendMode::kSrc && | 
|  | flags.getFilterQuality() == cc::PaintFlags::FilterQuality::kLow && | 
|  | (pixels = canvas->accessTopLayerPixels(&info, &row_bytes, &origin)) && | 
|  | info.colorType() == kBGRA_8888_SkColorType) { | 
|  | const size_t offset = info.computeOffset(origin.x(), origin.y(), row_bytes); | 
|  | void* const pixels_offset = reinterpret_cast<char*>(pixels) + offset; | 
|  | ConvertVideoFrameToRGBPixels(video_frame.get(), pixels_offset, row_bytes); | 
|  | } else if (video_frame->HasTextures()) { | 
|  | DCHECK_EQ(video_frame->coded_size(), | 
|  | gfx::Size(image.width(), image.height())); | 
|  | canvas->drawImageRect(image, gfx::RectToSkRect(video_frame->visible_rect()), | 
|  | SkRect::MakeWH(video_frame->visible_rect().width(), | 
|  | video_frame->visible_rect().height()), | 
|  | cc::PaintFlags::FilterQualityToSkSamplingOptions( | 
|  | flags.getFilterQuality()), | 
|  | &video_flags, SkCanvas::kStrict_SrcRectConstraint); | 
|  | } else { | 
|  | DCHECK_EQ(video_frame->visible_rect().size(), | 
|  | gfx::Size(image.width(), image.height())); | 
|  | canvas->drawImage(image, 0, 0, | 
|  | cc::PaintFlags::FilterQualityToSkSamplingOptions( | 
|  | flags.getFilterQuality()), | 
|  | &video_flags); | 
|  | } | 
|  |  | 
|  | if (need_transform) | 
|  | canvas->restore(); | 
|  | // Make sure to flush so we can remove the videoframe from the generator. | 
|  | canvas->flush(); | 
|  |  | 
|  | if (video_frame->HasTextures()) { | 
|  | source_access.reset(); | 
|  | // Synchronize |video_frame| with the read operations in UpdateLastImage(), | 
|  | // which are triggered by canvas->flush(). | 
|  | SynchronizeVideoFrameRead(std::move(video_frame), | 
|  | raster_context_provider->RasterInterface(), | 
|  | raster_context_provider->ContextSupport()); | 
|  | } | 
|  | // Because we are not retaining a reference to the VideoFrame, it would be | 
|  | // invalid for the texture_backing to directly wrap its texture(s), as they | 
|  | // will be recycled. | 
|  | DCHECK(!CacheBackingWrapsTexture()); | 
|  | } | 
|  |  | 
|  | void PaintCanvasVideoRenderer::Copy( | 
|  | scoped_refptr<VideoFrame> video_frame, | 
|  | cc::PaintCanvas* canvas, | 
|  | viz::RasterContextProvider* raster_context_provider) { | 
|  | cc::PaintFlags flags; | 
|  | flags.setBlendMode(SkBlendMode::kSrc); | 
|  | flags.setFilterQuality(cc::PaintFlags::FilterQuality::kLow); | 
|  |  | 
|  | auto dest_rect = gfx::RectF(gfx::SizeF(video_frame->visible_rect().size())); | 
|  | Paint(std::move(video_frame), canvas, dest_rect, flags, | 
|  | media::kNoTransformation, raster_context_provider); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // libyuv doesn't support all 9-, 10- nor 12-bit pixel formats yet. This | 
|  | // function creates a regular 8-bit video frame which we can give to libyuv. | 
|  | scoped_refptr<VideoFrame> DownShiftHighbitVideoFrame( | 
|  | const VideoFrame* video_frame) { | 
|  | VideoPixelFormat format; | 
|  | switch (video_frame->format()) { | 
|  | case PIXEL_FORMAT_YUV420P12: | 
|  | case PIXEL_FORMAT_YUV420P10: | 
|  | case PIXEL_FORMAT_YUV420P9: | 
|  | format = PIXEL_FORMAT_I420; | 
|  | break; | 
|  |  | 
|  | case PIXEL_FORMAT_YUV422P12: | 
|  | case PIXEL_FORMAT_YUV422P10: | 
|  | case PIXEL_FORMAT_YUV422P9: | 
|  | format = PIXEL_FORMAT_I422; | 
|  | break; | 
|  |  | 
|  | case PIXEL_FORMAT_YUV444P12: | 
|  | case PIXEL_FORMAT_YUV444P10: | 
|  | case PIXEL_FORMAT_YUV444P9: | 
|  | format = PIXEL_FORMAT_I444; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return nullptr; | 
|  | } | 
|  | const int scale = 1 << (24 - video_frame->BitDepth()); | 
|  | scoped_refptr<VideoFrame> ret = VideoFrame::CreateFrame( | 
|  | format, video_frame->coded_size(), video_frame->visible_rect(), | 
|  | video_frame->natural_size(), video_frame->timestamp()); | 
|  |  | 
|  | ret->set_color_space(video_frame->ColorSpace()); | 
|  | // Copy all metadata. | 
|  | // (May be enough to copy color space) | 
|  | ret->metadata().MergeMetadataFrom(video_frame->metadata()); | 
|  |  | 
|  | for (int plane = VideoFrame::kYPlane; plane <= VideoFrame::kVPlane; ++plane) { | 
|  | int width = VideoFrame::Columns(plane, video_frame->format(), | 
|  | video_frame->visible_rect().width()); | 
|  | int height = VideoFrame::Rows(plane, video_frame->format(), | 
|  | video_frame->visible_rect().height()); | 
|  | const uint16_t* src = | 
|  | reinterpret_cast<const uint16_t*>(video_frame->visible_data(plane)); | 
|  | uint8_t* dst = ret->visible_data(plane); | 
|  | if (!src) { | 
|  | // An AV1 monochrome (grayscale) frame has no U and V planes. Set all U | 
|  | // and V samples to the neutral value (128). | 
|  | DCHECK_NE(plane, VideoFrame::kYPlane); | 
|  | memset(dst, 128, height * ret->stride(plane)); | 
|  | continue; | 
|  | } | 
|  | libyuv::Convert16To8Plane(src, video_frame->stride(plane) / 2, dst, | 
|  | ret->stride(plane), scale, width, height); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | // Converts 16-bit data to |out| buffer of specified GL |type|. | 
|  | // When the |format| is RGBA, the converted value is fed as luminance. | 
|  | void FlipAndConvertY16(const VideoFrame* video_frame, | 
|  | uint8_t* out, | 
|  | unsigned format, | 
|  | unsigned type, | 
|  | bool flip_y, | 
|  | size_t output_row_bytes) { | 
|  | const uint8_t* row_head = video_frame->visible_data(0); | 
|  | const size_t stride = video_frame->stride(0); | 
|  | const int height = video_frame->visible_rect().height(); | 
|  | for (int i = 0; i < height; ++i, row_head += stride) { | 
|  | uint8_t* out_row_head = flip_y ? out + output_row_bytes * (height - i - 1) | 
|  | : out + output_row_bytes * i; | 
|  | const uint16_t* row = reinterpret_cast<const uint16_t*>(row_head); | 
|  | const uint16_t* row_end = row + video_frame->visible_rect().width(); | 
|  | if (type == GL_FLOAT) { | 
|  | float* out_row = reinterpret_cast<float*>(out_row_head); | 
|  | if (format == GL_RGBA) { | 
|  | while (row < row_end) { | 
|  | float gray_value = *row++ / 65535.f; | 
|  | *out_row++ = gray_value; | 
|  | *out_row++ = gray_value; | 
|  | *out_row++ = gray_value; | 
|  | *out_row++ = 1.0f; | 
|  | } | 
|  | continue; | 
|  | } else if (format == GL_RED) { | 
|  | while (row < row_end) | 
|  | *out_row++ = *row++ / 65535.f; | 
|  | continue; | 
|  | } | 
|  | // For other formats, hit NOTREACHED below. | 
|  | } else if (type == GL_UNSIGNED_BYTE) { | 
|  | // We take the upper 8 bits of 16-bit data and convert it as luminance to | 
|  | // ARGB.  We loose the precision here, but it is important not to render | 
|  | // Y16 as RG_88.  To get the full precision use float textures with WebGL1 | 
|  | // and e.g. R16UI or R32F textures with WebGL2. | 
|  | DCHECK_EQ(static_cast<unsigned>(GL_RGBA), format); | 
|  | uint32_t* rgba = reinterpret_cast<uint32_t*>(out_row_head); | 
|  | while (row < row_end) { | 
|  | uint32_t gray_value = *row++ >> 8; | 
|  | *rgba++ = SkColorSetRGB(gray_value, gray_value, gray_value); | 
|  | } | 
|  | continue; | 
|  | } | 
|  | NOTREACHED() << "Unsupported Y16 conversion for format: 0x" << std::hex | 
|  | << format << " and type: 0x" << std::hex << type; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Common functionality of PaintCanvasVideoRenderer's TexImage2D and | 
|  | // TexSubImage2D. Allocates a buffer required for conversion and converts | 
|  | // |frame| content to desired |format|. Returns true if calling glTex(Sub)Image | 
|  | // is supported for provided |frame| format and parameters. | 
|  | bool TexImageHelper(VideoFrame* frame, | 
|  | unsigned format, | 
|  | unsigned type, | 
|  | bool flip_y, | 
|  | scoped_refptr<DataBuffer>* temp_buffer) { | 
|  | unsigned output_bytes_per_pixel = 0; | 
|  | switch (frame->format()) { | 
|  | case PIXEL_FORMAT_Y16: | 
|  | // Converting single component unsigned short here to FLOAT luminance. | 
|  | switch (format) { | 
|  | case GL_RGBA: | 
|  | if (type == GL_FLOAT) { | 
|  | output_bytes_per_pixel = 4 * sizeof(GLfloat); | 
|  | break; | 
|  | } | 
|  | return false; | 
|  | case GL_RED: | 
|  | if (type == GL_FLOAT) { | 
|  | output_bytes_per_pixel = sizeof(GLfloat); | 
|  | break; | 
|  | } | 
|  | return false; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  |  | 
|  | size_t output_row_bytes = | 
|  | frame->visible_rect().width() * output_bytes_per_pixel; | 
|  | *temp_buffer = | 
|  | new DataBuffer(output_row_bytes * frame->visible_rect().height()); | 
|  | FlipAndConvertY16(frame, (*temp_buffer)->writable_data(), format, type, | 
|  | flip_y, output_row_bytes); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Upload the |frame| data to temporary texture of |temp_format|, | 
|  | // |temp_internalformat| and |temp_type| and then copy intermediate texture | 
|  | // subimage to destination |texture|. The destination |texture| is bound to the | 
|  | // |target| before the call. | 
|  | void TextureSubImageUsingIntermediate(unsigned target, | 
|  | unsigned texture, | 
|  | gpu::gles2::GLES2Interface* gl, | 
|  | VideoFrame* frame, | 
|  | int temp_internalformat, | 
|  | unsigned temp_format, | 
|  | unsigned temp_type, | 
|  | int level, | 
|  | int xoffset, | 
|  | int yoffset, | 
|  | bool flip_y, | 
|  | bool premultiply_alpha) { | 
|  | unsigned temp_texture = 0; | 
|  | gl->GenTextures(1, &temp_texture); | 
|  | gl->BindTexture(target, temp_texture); | 
|  | gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | 
|  | gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 
|  | gl->TexImage2D(target, 0, temp_internalformat, frame->visible_rect().width(), | 
|  | frame->visible_rect().height(), 0, temp_format, temp_type, | 
|  | frame->visible_data(0)); | 
|  | gl->BindTexture(target, texture); | 
|  | gl->CopySubTextureCHROMIUM(temp_texture, 0, target, texture, level, 0, 0, | 
|  | xoffset, yoffset, frame->visible_rect().width(), | 
|  | frame->visible_rect().height(), flip_y, | 
|  | premultiply_alpha, false); | 
|  | gl->DeleteTextures(1, &temp_texture); | 
|  | } | 
|  |  | 
|  | }  // anonymous namespace | 
|  |  | 
|  | // static | 
|  | void PaintCanvasVideoRenderer::ConvertVideoFrameToRGBPixels( | 
|  | const VideoFrame* video_frame, | 
|  | void* rgb_pixels, | 
|  | size_t row_bytes, | 
|  | bool premultiply_alpha) { | 
|  | if (!video_frame->IsMappable()) { | 
|  | NOTREACHED() << "Cannot extract pixels from non-CPU frame formats."; | 
|  | return; | 
|  | } | 
|  |  | 
|  | scoped_refptr<VideoFrame> temporary_frame; | 
|  | // TODO(thomasanderson): Parallelize converting these formats. | 
|  | switch (video_frame->format()) { | 
|  | case PIXEL_FORMAT_YUV420P9: | 
|  | case PIXEL_FORMAT_YUV422P9: | 
|  | case PIXEL_FORMAT_YUV444P9: | 
|  | case PIXEL_FORMAT_YUV444P10: | 
|  | case PIXEL_FORMAT_YUV420P12: | 
|  | case PIXEL_FORMAT_YUV422P12: | 
|  | case PIXEL_FORMAT_YUV444P12: | 
|  | temporary_frame = DownShiftHighbitVideoFrame(video_frame); | 
|  | video_frame = temporary_frame.get(); | 
|  | break; | 
|  | case PIXEL_FORMAT_YUV420P10: | 
|  | // In AV1, a monochrome (grayscale) frame is represented as a YUV 4:2:0 | 
|  | // frame with no U and V planes. Since there are no 10-bit versions of | 
|  | // libyuv::I400ToARGB() and libyuv::J400ToARGB(), convert the frame to an | 
|  | // 8-bit YUV 4:2:0 frame with U and V planes. | 
|  | if (!video_frame->data(VideoFrame::kUPlane) && | 
|  | !video_frame->data(VideoFrame::kVPlane)) { | 
|  | temporary_frame = DownShiftHighbitVideoFrame(video_frame); | 
|  | video_frame = temporary_frame.get(); | 
|  | } | 
|  | break; | 
|  | case PIXEL_FORMAT_Y16: | 
|  | // Since it is grayscale conversion, we disregard | 
|  | // SK_PMCOLOR_BYTE_ORDER and always use GL_RGBA. | 
|  | FlipAndConvertY16(video_frame, static_cast<uint8_t*>(rgb_pixels), GL_RGBA, | 
|  | GL_UNSIGNED_BYTE, false /*flip_y*/, row_bytes); | 
|  | return; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | constexpr size_t kTaskBytes = 1024 * 1024;  // 1 MiB | 
|  | const size_t n_tasks = std::min<size_t>( | 
|  | std::max<size_t>( | 
|  | 1, VideoFrame::AllocationSize(video_frame->format(), | 
|  | video_frame->visible_rect().size()) / | 
|  | kTaskBytes), | 
|  | base::SysInfo::NumberOfProcessors()); | 
|  | base::WaitableEvent event; | 
|  | base::RepeatingClosure barrier = base::BarrierClosure( | 
|  | n_tasks, | 
|  | base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&event))); | 
|  |  | 
|  | for (size_t i = 1; i < n_tasks; ++i) { | 
|  | base::ThreadPool::PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(ConvertVideoFrameToRGBPixelsTask, | 
|  | base::Unretained(video_frame), rgb_pixels, row_bytes, | 
|  | premultiply_alpha, i, n_tasks, &barrier)); | 
|  | } | 
|  | ConvertVideoFrameToRGBPixelsTask(video_frame, rgb_pixels, row_bytes, | 
|  | premultiply_alpha, 0, n_tasks, &barrier); | 
|  | { | 
|  | base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait; | 
|  | event.Wait(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool PaintCanvasVideoRenderer::CopyVideoFrameTexturesToGLTexture( | 
|  | viz::RasterContextProvider* raster_context_provider, | 
|  | gpu::gles2::GLES2Interface* destination_gl, | 
|  | scoped_refptr<VideoFrame> video_frame, | 
|  | unsigned int target, | 
|  | unsigned int texture, | 
|  | unsigned int internal_format, | 
|  | unsigned int format, | 
|  | unsigned int type, | 
|  | int level, | 
|  | bool premultiply_alpha, | 
|  | bool flip_y) { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | DCHECK(video_frame); | 
|  | DCHECK(video_frame->HasTextures()); | 
|  |  | 
|  | if (video_frame->NumTextures() > 1 || | 
|  | video_frame->metadata().read_lock_fences_enabled) { | 
|  | DCHECK(video_frame->metadata().texture_origin_is_top_left); | 
|  | if (!raster_context_provider) | 
|  | return false; | 
|  | GrDirectContext* gr_context = raster_context_provider->GrContext(); | 
|  | if (!gr_context && | 
|  | !raster_context_provider->ContextCapabilities().supports_oop_raster) | 
|  | return false; | 
|  | // Since skia always produces premultiply alpha outputs, | 
|  | // trying direct uploading path when video format is opaque or premultiply | 
|  | // alpha been requested. And dst texture mipLevel must be 0. | 
|  | // TODO(crbug.com/1155003): Figure out whether premultiply options here are | 
|  | // accurate. | 
|  | if ((media::IsOpaque(video_frame->format()) || premultiply_alpha) && | 
|  | level == 0 && video_frame->NumTextures() > 1) { | 
|  | if (UploadVideoFrameToGLTexture(raster_context_provider, destination_gl, | 
|  | video_frame.get(), target, texture, | 
|  | internal_format, format, type, flip_y)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!UpdateLastImage(video_frame, raster_context_provider, | 
|  | true /* allow_wrap_texture */)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | DCHECK(cache_); | 
|  | DCHECK(cache_->texture_backing); | 
|  | gpu::raster::RasterInterface* canvas_ri = | 
|  | raster_context_provider->RasterInterface(); | 
|  |  | 
|  | gpu::SyncToken sync_token; | 
|  | // Wait for mailbox creation on canvas context before consuming it and | 
|  | // copying from it on the consumer context. | 
|  | canvas_ri->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); | 
|  |  | 
|  | CopyMailboxToTexture( | 
|  | destination_gl, cache_->coded_size, cache_->visible_rect, | 
|  | cache_->texture_backing->GetMailbox(), sync_token, target, texture, | 
|  | internal_format, format, type, level, premultiply_alpha, flip_y); | 
|  |  | 
|  | // Wait for destination context to consume mailbox before deleting it in | 
|  | // canvas context. | 
|  | gpu::SyncToken dest_sync_token; | 
|  | destination_gl->GenUnverifiedSyncTokenCHROMIUM(dest_sync_token.GetData()); | 
|  | canvas_ri->WaitSyncTokenCHROMIUM(dest_sync_token.GetConstData()); | 
|  |  | 
|  | // Because we are not retaining a reference to the VideoFrame, it would be | 
|  | // invalid to keep |cache_| around if it directly wraps |video_frame|. | 
|  | if (cache_->texture_backing->wraps_video_frame_texture()) { | 
|  | cache_.reset(); | 
|  | // Ensure that |video_frame| not be destroyed until the above | 
|  | // CopyMailboxToTexture completes. | 
|  | SynchronizeVideoFrameRead(std::move(video_frame), destination_gl, | 
|  | raster_context_provider->ContextSupport()); | 
|  | } | 
|  | } else { | 
|  | // Correct Y-flip. flip_y should take precedent when | 
|  | // texture_origin_is_top_left is true, and invert the setting when | 
|  | // texture_origin_is_top_left is false. | 
|  | if (!video_frame->metadata().texture_origin_is_top_left) | 
|  | flip_y = !flip_y; | 
|  |  | 
|  | const gpu::MailboxHolder& mailbox_holder = | 
|  | GetVideoFrameMailboxHolder(video_frame.get()); | 
|  | CopyMailboxToTexture( | 
|  | destination_gl, video_frame->coded_size(), video_frame->visible_rect(), | 
|  | mailbox_holder.mailbox, mailbox_holder.sync_token, target, texture, | 
|  | internal_format, format, type, level, premultiply_alpha, flip_y); | 
|  | destination_gl->ShallowFlushCHROMIUM(); | 
|  |  | 
|  | SynchronizeVideoFrameRead(std::move(video_frame), destination_gl, | 
|  | raster_context_provider->ContextSupport()); | 
|  | } | 
|  | DCHECK(!CacheBackingWrapsTexture()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool PaintCanvasVideoRenderer::UploadVideoFrameToGLTexture( | 
|  | viz::RasterContextProvider* raster_context_provider, | 
|  | gpu::gles2::GLES2Interface* destination_gl, | 
|  | scoped_refptr<VideoFrame> video_frame, | 
|  | unsigned int target, | 
|  | unsigned int texture, | 
|  | unsigned int internal_format, | 
|  | unsigned int format, | 
|  | unsigned int type, | 
|  | bool flip_y) { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | DCHECK(video_frame); | 
|  | // Support uploading for NV12 and I420 video frame only. | 
|  | if (!VideoFrameYUVConverter::IsVideoFrameFormatSupported(*video_frame)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/1108154): Support more texture target, e.g. | 
|  | // 2d array, 3d etc. | 
|  | if (target != GL_TEXTURE_2D) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!ValidFormatForDirectUploading(raster_context_provider, | 
|  | static_cast<GLenum>(internal_format), | 
|  | type)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // TODO(nazabris): Support OOP-R code path here that does not have GrContext. | 
|  | if (!raster_context_provider || !raster_context_provider->GrContext()) | 
|  | return false; | 
|  |  | 
|  | if (raster_context_provider->ContextCapabilities().disable_legacy_mailbox) | 
|  | return false; | 
|  |  | 
|  | DCHECK(video_frame->metadata().texture_origin_is_top_left); | 
|  |  | 
|  | // Trigger resource allocation for dst texture to back SkSurface. | 
|  | // Dst texture size should equal to video frame visible rect. | 
|  | destination_gl->BindTexture(target, texture); | 
|  | destination_gl->TexImage2D( | 
|  | target, 0, internal_format, video_frame->visible_rect().width(), | 
|  | video_frame->visible_rect().height(), 0, format, type, nullptr); | 
|  |  | 
|  | gpu::MailboxHolder mailbox_holder; | 
|  | mailbox_holder.texture_target = target; | 
|  | destination_gl->ProduceTextureDirectCHROMIUM(texture, | 
|  | mailbox_holder.mailbox.name); | 
|  |  | 
|  | destination_gl->GenUnverifiedSyncTokenCHROMIUM( | 
|  | mailbox_holder.sync_token.GetData()); | 
|  |  | 
|  | if (!VideoFrameYUVConverter::ConvertYUVVideoFrameToDstTextureNoCaching( | 
|  | video_frame.get(), raster_context_provider, mailbox_holder, | 
|  | internal_format, type, flip_y, true /* use visible_rect */)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | gpu::raster::RasterInterface* source_ri = | 
|  | raster_context_provider->RasterInterface(); | 
|  | // Wait for mailbox creation on canvas context before consuming it. | 
|  | source_ri->GenUnverifiedSyncTokenCHROMIUM( | 
|  | mailbox_holder.sync_token.GetData()); | 
|  |  | 
|  | destination_gl->WaitSyncTokenCHROMIUM( | 
|  | mailbox_holder.sync_token.GetConstData()); | 
|  |  | 
|  | if (video_frame->HasTextures()) { | 
|  | SynchronizeVideoFrameRead(std::move(video_frame), source_ri, | 
|  | raster_context_provider->ContextSupport()); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool PaintCanvasVideoRenderer::PrepareVideoFrameForWebGL( | 
|  | viz::RasterContextProvider* raster_context_provider, | 
|  | gpu::gles2::GLES2Interface* destination_gl, | 
|  | scoped_refptr<VideoFrame> video_frame, | 
|  | unsigned int target, | 
|  | unsigned int texture) { | 
|  | // TODO(776222): This static function uses no common functionality in | 
|  | // PaintCanvasVideoRenderer, and should be removed from this class. | 
|  |  | 
|  | DCHECK(video_frame); | 
|  | DCHECK(video_frame->HasTextures()); | 
|  | if (video_frame->NumTextures() == 1) { | 
|  | if (target == GL_TEXTURE_EXTERNAL_OES) { | 
|  | // We don't support Android now. | 
|  | // TODO(crbug.com/776222): support Android. | 
|  | return false; | 
|  | } | 
|  | // We don't support sharing single video frame texture now. | 
|  | // TODO(crbug.com/776222): deal with single video frame texture. | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // TODO(nazabris): Support OOP-R code path here that does not have GrContext. | 
|  | if (!raster_context_provider || !raster_context_provider->GrContext()) | 
|  | return false; | 
|  |  | 
|  | DCHECK(video_frame->metadata().texture_origin_is_top_left); | 
|  |  | 
|  | // Take webgl video texture as 2D texture. Setting it as external render | 
|  | // target backend for skia. | 
|  | destination_gl->BindTexture(target, texture); | 
|  | destination_gl->TexImage2D(target, 0, GL_RGBA, | 
|  | video_frame->coded_size().width(), | 
|  | video_frame->coded_size().height(), 0, GL_RGBA, | 
|  | GL_UNSIGNED_BYTE, nullptr); | 
|  |  | 
|  | gpu::raster::RasterInterface* source_ri = | 
|  | raster_context_provider->RasterInterface(); | 
|  | gpu::MailboxHolder mailbox_holder; | 
|  | mailbox_holder.texture_target = target; | 
|  | destination_gl->ProduceTextureDirectCHROMIUM(texture, | 
|  | mailbox_holder.mailbox.name); | 
|  |  | 
|  | destination_gl->GenUnverifiedSyncTokenCHROMIUM( | 
|  | mailbox_holder.sync_token.GetData()); | 
|  |  | 
|  | // Generate a new image. | 
|  | if (video_frame->HasTextures()) { | 
|  | if (video_frame->NumTextures() > 1) { | 
|  | VideoFrameYUVConverter::ConvertYUVVideoFrameNoCaching( | 
|  | video_frame.get(), raster_context_provider, mailbox_holder); | 
|  | } else { | 
|  | // We don't support Android now. | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Wait for mailbox creation on canvas context before consuming it and | 
|  | // copying from it on the consumer context. | 
|  | source_ri->GenUnverifiedSyncTokenCHROMIUM( | 
|  | mailbox_holder.sync_token.GetData()); | 
|  |  | 
|  | destination_gl->WaitSyncTokenCHROMIUM( | 
|  | mailbox_holder.sync_token.GetConstData()); | 
|  |  | 
|  | WaitAndReplaceSyncTokenClient client(source_ri); | 
|  | video_frame->UpdateReleaseSyncToken(&client); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool PaintCanvasVideoRenderer::CopyVideoFrameYUVDataToGLTexture( | 
|  | viz::RasterContextProvider* raster_context_provider, | 
|  | gpu::gles2::GLES2Interface* destination_gl, | 
|  | scoped_refptr<VideoFrame> video_frame, | 
|  | unsigned int target, | 
|  | unsigned int texture, | 
|  | unsigned int internal_format, | 
|  | unsigned int format, | 
|  | unsigned int type, | 
|  | int level, | 
|  | bool premultiply_alpha, | 
|  | bool flip_y) { | 
|  | if (!raster_context_provider) | 
|  | return false; | 
|  | #if defined(OS_ANDROID) | 
|  | // TODO(crbug.com/1181993): These formats don't work with the passthrough | 
|  | // command decoder on Android for some reason. | 
|  | const auto format_enum = static_cast<GLenum>(internal_format); | 
|  | if (format_enum == GL_RGB10_A2 || format_enum == GL_RGB565 || | 
|  | format_enum == GL_RGB5_A1 || format_enum == GL_RGBA4) { | 
|  | return false; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | if (!video_frame->IsMappable()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!VideoFrameYUVConverter::IsVideoFrameFormatSupported(*video_frame)) { | 
|  | return false; | 
|  | } | 
|  | // Could handle NV12 here as well. See NewSkImageFromVideoFrameYUV. | 
|  |  | 
|  | DCHECK(video_frame->metadata().texture_origin_is_top_left); | 
|  |  | 
|  | // Since skia always produces premultiply alpha outputs, | 
|  | // trying direct uploading path when video format is opaque or premultiply | 
|  | // alpha been requested. And dst texture mipLevel must be 0. | 
|  | // TODO(crbug.com/1155003): Figure out whether premultiply options here are | 
|  | // accurate. | 
|  | if ((media::IsOpaque(video_frame->format()) || premultiply_alpha) && | 
|  | level == 0) { | 
|  | if (UploadVideoFrameToGLTexture(raster_context_provider, destination_gl, | 
|  | video_frame, target, texture, | 
|  | internal_format, format, type, flip_y)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | auto* sii = raster_context_provider->SharedImageInterface(); | 
|  | gpu::raster::RasterInterface* source_ri = | 
|  | raster_context_provider->RasterInterface(); | 
|  |  | 
|  | // We need a shared image to receive the intermediate RGB result. Try to reuse | 
|  | // one if compatible, otherwise create a new one. | 
|  | gpu::SyncToken token; | 
|  | if (!yuv_cache_.mailbox.IsZero() && | 
|  | yuv_cache_.size == video_frame->coded_size() && | 
|  | yuv_cache_.raster_context_provider == raster_context_provider) { | 
|  | token = yuv_cache_.sync_token; | 
|  | } else { | 
|  | yuv_cache_.Reset(); | 
|  | yuv_cache_.raster_context_provider = raster_context_provider; | 
|  | yuv_cache_.size = video_frame->coded_size(); | 
|  |  | 
|  | uint32_t usage = gpu::SHARED_IMAGE_USAGE_GLES2; | 
|  | if (raster_context_provider->ContextCapabilities().supports_oop_raster) { | 
|  | usage |= gpu::SHARED_IMAGE_USAGE_RASTER | | 
|  | gpu::SHARED_IMAGE_USAGE_OOP_RASTERIZATION; | 
|  | } | 
|  |  | 
|  | yuv_cache_.mailbox = sii->CreateSharedImage( | 
|  | viz::ResourceFormat::RGBA_8888, video_frame->coded_size(), | 
|  | gfx::ColorSpace(), kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, | 
|  | gpu::kNullSurfaceHandle); | 
|  | token = sii->GenUnverifiedSyncToken(); | 
|  | } | 
|  |  | 
|  | // On the source Raster context, do the YUV->RGB conversion. | 
|  | gpu::MailboxHolder dest_holder; | 
|  | dest_holder.mailbox = yuv_cache_.mailbox; | 
|  | dest_holder.texture_target = GL_TEXTURE_2D; | 
|  | dest_holder.sync_token = token; | 
|  | yuv_cache_.yuv_converter.ConvertYUVVideoFrame( | 
|  | video_frame.get(), raster_context_provider, dest_holder); | 
|  |  | 
|  | gpu::SyncToken post_conversion_sync_token; | 
|  | source_ri->GenUnverifiedSyncTokenCHROMIUM( | 
|  | post_conversion_sync_token.GetData()); | 
|  |  | 
|  | // On the destination GL context, do a copy (with cropping) into the | 
|  | // destination texture. | 
|  | CopyMailboxToTexture( | 
|  | destination_gl, video_frame->coded_size(), video_frame->visible_rect(), | 
|  | yuv_cache_.mailbox, post_conversion_sync_token, target, texture, | 
|  | internal_format, format, type, level, premultiply_alpha, flip_y); | 
|  | destination_gl->GenUnverifiedSyncTokenCHROMIUM( | 
|  | yuv_cache_.sync_token.GetData()); | 
|  |  | 
|  | // video_frame->UpdateReleaseSyncToken is not necessary since the video frame | 
|  | // data we used was CPU-side (IsMappable) to begin with. If there were any | 
|  | // textures, we didn't use them. | 
|  |  | 
|  | // The temporary SkImages should be automatically cleaned up here. | 
|  |  | 
|  | // Kick off a timer to release the cache. | 
|  | cache_deleting_timer_.Reset(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool PaintCanvasVideoRenderer::TexImage2D( | 
|  | unsigned target, | 
|  | unsigned texture, | 
|  | gpu::gles2::GLES2Interface* gl, | 
|  | const gpu::Capabilities& gpu_capabilities, | 
|  | VideoFrame* frame, | 
|  | int level, | 
|  | int internalformat, | 
|  | unsigned format, | 
|  | unsigned type, | 
|  | bool flip_y, | 
|  | bool premultiply_alpha) { | 
|  | DCHECK(frame); | 
|  | DCHECK(!frame->HasTextures()); | 
|  | DCHECK(frame->metadata().texture_origin_is_top_left); | 
|  |  | 
|  | // Note: CopyTextureCHROMIUM uses mediump for color computation. Don't use | 
|  | // it if the precision would lead to data loss when converting 16-bit | 
|  | // normalized to float. medium_float.precision > 15 means that the approach | 
|  | // below is not used on Android, where the extension EXT_texture_norm16 is | 
|  | // not widely supported. It is used on Windows, Linux and OSX. | 
|  | // Android support is not required for now because Tango depth camera already | 
|  | // provides floating point data (projected point cloud). See crbug.com/674440. | 
|  | if (gpu_capabilities.texture_norm16 && | 
|  | gpu_capabilities.fragment_shader_precisions.medium_float.precision > 15 && | 
|  | target == GL_TEXTURE_2D && | 
|  | (type == GL_FLOAT || type == GL_UNSIGNED_BYTE)) { | 
|  | // TODO(aleksandar.stojiljkovic): Extend the approach to TexSubImage2D | 
|  | // implementation and other types. See https://crbug.com/624436. | 
|  |  | 
|  | // Allocate the destination texture. | 
|  | gl->TexImage2D(target, level, internalformat, frame->visible_rect().width(), | 
|  | frame->visible_rect().height(), 0, format, type, nullptr); | 
|  | // We use sized internal format GL_R16_EXT instead of unsized GL_RED. | 
|  | // See angleproject:1952 | 
|  | TextureSubImageUsingIntermediate(target, texture, gl, frame, GL_R16_EXT, | 
|  | GL_RED, GL_UNSIGNED_SHORT, level, 0, 0, | 
|  | flip_y, premultiply_alpha); | 
|  | return true; | 
|  | } | 
|  | scoped_refptr<DataBuffer> temp_buffer; | 
|  | if (!TexImageHelper(frame, format, type, flip_y, &temp_buffer)) | 
|  | return false; | 
|  |  | 
|  | gl->TexImage2D(target, level, internalformat, frame->visible_rect().width(), | 
|  | frame->visible_rect().height(), 0, format, type, | 
|  | temp_buffer->data()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool PaintCanvasVideoRenderer::TexSubImage2D(unsigned target, | 
|  | gpu::gles2::GLES2Interface* gl, | 
|  | VideoFrame* frame, | 
|  | int level, | 
|  | unsigned format, | 
|  | unsigned type, | 
|  | int xoffset, | 
|  | int yoffset, | 
|  | bool flip_y, | 
|  | bool premultiply_alpha) { | 
|  | DCHECK(frame); | 
|  | DCHECK(!frame->HasTextures()); | 
|  | DCHECK(frame->metadata().texture_origin_is_top_left); | 
|  |  | 
|  | scoped_refptr<DataBuffer> temp_buffer; | 
|  | if (!TexImageHelper(frame, format, type, flip_y, &temp_buffer)) | 
|  | return false; | 
|  |  | 
|  | gl->TexSubImage2D( | 
|  | target, level, xoffset, yoffset, frame->visible_rect().width(), | 
|  | frame->visible_rect().height(), format, type, temp_buffer->data()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void PaintCanvasVideoRenderer::ResetCache() { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | cache_.reset(); | 
|  | yuv_cache_.Reset(); | 
|  | } | 
|  |  | 
|  | PaintCanvasVideoRenderer::Cache::Cache(int frame_id) : frame_id(frame_id) {} | 
|  |  | 
|  | PaintCanvasVideoRenderer::Cache::~Cache() = default; | 
|  |  | 
|  | bool PaintCanvasVideoRenderer::Cache::Recycle() { | 
|  | DCHECK(!texture_backing->wraps_video_frame_texture()); | 
|  |  | 
|  | paint_image = cc::PaintImage(); | 
|  | if (!texture_backing->unique()) | 
|  | return false; | 
|  |  | 
|  | // Flush any pending GPU work using this texture. | 
|  | texture_backing->FlushPendingSkiaOps(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool PaintCanvasVideoRenderer::UpdateLastImage( | 
|  | scoped_refptr<VideoFrame> video_frame, | 
|  | viz::RasterContextProvider* raster_context_provider, | 
|  | bool allow_wrap_texture) { | 
|  | DCHECK(!CacheBackingWrapsTexture()); | 
|  |  | 
|  | // Check for a cache hit. | 
|  | if (cache_ && video_frame->unique_id() == cache_->frame_id && | 
|  | cache_->paint_image) { | 
|  | cache_deleting_timer_.Reset(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | auto paint_image_builder = | 
|  | cc::PaintImageBuilder::WithDefault() | 
|  | .set_id(renderer_stable_id_) | 
|  | .set_animation_type(cc::PaintImage::AnimationType::VIDEO) | 
|  | .set_completion_state(cc::PaintImage::CompletionState::DONE); | 
|  |  | 
|  | // Generate a new image. | 
|  | // Note: Skia will hold onto |video_frame| via |video_generator| only when | 
|  | // |video_frame| is software. | 
|  | // Holding |video_frame| longer than this call when using GPUVideoDecoder | 
|  | // could cause problems since the pool of VideoFrames has a fixed size. | 
|  | if (video_frame->HasTextures()) { | 
|  | DCHECK(raster_context_provider); | 
|  | bool supports_oop_raster = | 
|  | raster_context_provider->ContextCapabilities().supports_oop_raster; | 
|  | DCHECK(supports_oop_raster || raster_context_provider->GrContext()); | 
|  | auto* ri = raster_context_provider->RasterInterface(); | 
|  | DCHECK(ri); | 
|  | bool wraps_video_frame_texture = false; | 
|  | gpu::Mailbox mailbox; | 
|  |  | 
|  | if (allow_wrap_texture && video_frame->NumTextures() == 1) { | 
|  | cache_.emplace(video_frame->unique_id()); | 
|  | const gpu::MailboxHolder& holder = | 
|  | GetVideoFrameMailboxHolder(video_frame.get()); | 
|  | mailbox = holder.mailbox; | 
|  | ri->WaitSyncTokenCHROMIUM(holder.sync_token.GetConstData()); | 
|  | wraps_video_frame_texture = true; | 
|  | } else { | 
|  | // Create or reuse a texture backing for the cached copy. | 
|  | if (cache_ && cache_->texture_backing && | 
|  | cache_->texture_backing->raster_context_provider() == | 
|  | raster_context_provider && | 
|  | cache_->coded_size == video_frame->coded_size() && | 
|  | // We always convert to top left origin when copying, so if the | 
|  | // cache was previously for a wrapped texture it can't be used. | 
|  | cache_->texture_origin_is_top_left && cache_->Recycle()) { | 
|  | // We can reuse the shared image from the previous cache. | 
|  | cache_->frame_id = video_frame->unique_id(); | 
|  | mailbox = cache_->texture_backing->GetMailbox(); | 
|  | } else { | 
|  | cache_.emplace(video_frame->unique_id()); | 
|  | auto* sii = raster_context_provider->SharedImageInterface(); | 
|  | // TODO(nazabris): Sort out what to do when GLES2 is needed but the | 
|  | // cached shared image is created without it. | 
|  | uint32_t flags = | 
|  | gpu::SHARED_IMAGE_USAGE_GLES2 | gpu::SHARED_IMAGE_USAGE_RASTER; | 
|  | if (supports_oop_raster) { | 
|  | flags |= gpu::SHARED_IMAGE_USAGE_OOP_RASTERIZATION; | 
|  | } | 
|  | mailbox = sii->CreateSharedImage( | 
|  | viz::ResourceFormat::RGBA_8888, video_frame->coded_size(), | 
|  | gfx::ColorSpace(), kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, | 
|  | flags, gpu::kNullSurfaceHandle); | 
|  | ri->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData()); | 
|  | } | 
|  |  | 
|  | // Copy into the texture backing of the cached copy. | 
|  | if (video_frame->NumTextures() == 1) { | 
|  | const gpu::MailboxHolder& frame_mailbox_holder = | 
|  | GetVideoFrameMailboxHolder(video_frame.get()); | 
|  | ri->WaitSyncTokenCHROMIUM( | 
|  | frame_mailbox_holder.sync_token.GetConstData()); | 
|  | ri->CopySubTexture(frame_mailbox_holder.mailbox, mailbox, GL_TEXTURE_2D, | 
|  | 0, 0, 0, 0, video_frame->coded_size().width(), | 
|  | video_frame->coded_size().height(), | 
|  | !video_frame->metadata().texture_origin_is_top_left, | 
|  | GL_FALSE); | 
|  | } else { | 
|  | DCHECK(video_frame->metadata().texture_origin_is_top_left); | 
|  | gpu::MailboxHolder dest_holder{mailbox, gpu::SyncToken(), | 
|  | GL_TEXTURE_2D}; | 
|  | VideoFrameYUVConverter::ConvertYUVVideoFrameNoCaching( | 
|  | video_frame.get(), raster_context_provider, dest_holder); | 
|  | } | 
|  | if (!supports_oop_raster) | 
|  | raster_context_provider->GrContext()->flushAndSubmit(); | 
|  |  | 
|  | // Ensure that |video_frame| not be deleted until the above copy is | 
|  | // completed. | 
|  | SynchronizeVideoFrameRead(video_frame, ri, | 
|  | raster_context_provider->ContextSupport()); | 
|  | } | 
|  |  | 
|  | cache_->coded_size = video_frame->coded_size(); | 
|  | cache_->visible_rect = video_frame->visible_rect(); | 
|  | cache_->texture_origin_is_top_left = | 
|  | wraps_video_frame_texture | 
|  | ? video_frame->metadata().texture_origin_is_top_left | 
|  | : true; | 
|  |  | 
|  | // In OOPR mode, we can keep the entire TextureBacking. In non-OOPR, | 
|  | // we can recycle the mailbox/texture, but have to replace the SkImage. | 
|  | if (!supports_oop_raster) { | 
|  | cache_->source_texture = ri->CreateAndConsumeForGpuRaster(mailbox); | 
|  |  | 
|  | // TODO(nazabris): Handle scoped access correctly. This follows the | 
|  | // current pattern but is most likely bugged. Access should last for | 
|  | // the lifetime of the SkImage. | 
|  | ScopedSharedImageAccess access(ri, cache_->source_texture, mailbox); | 
|  | auto source_image = WrapGLTexture( | 
|  | wraps_video_frame_texture | 
|  | ? video_frame->mailbox_holder(0).texture_target | 
|  | : GL_TEXTURE_2D, | 
|  | cache_->source_texture, video_frame->coded_size(), | 
|  | raster_context_provider, cache_->texture_origin_is_top_left); | 
|  | if (!source_image) { | 
|  | // Couldn't create the SkImage. | 
|  | cache_.reset(); | 
|  | return false; | 
|  | } | 
|  | if (!cache_->texture_backing) { | 
|  | cache_->texture_backing = sk_make_sp<VideoTextureBacking>( | 
|  | std::move(source_image), mailbox, wraps_video_frame_texture, | 
|  | raster_context_provider); | 
|  | } else { | 
|  | cache_->texture_backing->ReplaceAcceleratedSkImage( | 
|  | std::move(source_image)); | 
|  | } | 
|  | } else if (!cache_->texture_backing) { | 
|  | SkImageInfo sk_image_info = | 
|  | SkImageInfo::Make(gfx::SizeToSkISize(cache_->coded_size), | 
|  | kRGBA_8888_SkColorType, kPremul_SkAlphaType); | 
|  | cache_->texture_backing = sk_make_sp<VideoTextureBacking>( | 
|  | mailbox, sk_image_info, wraps_video_frame_texture, | 
|  | raster_context_provider); | 
|  | } | 
|  | paint_image_builder.set_texture_backing(cache_->texture_backing, | 
|  | cc::PaintImage::GetNextContentId()); | 
|  | } else { | 
|  | cache_.emplace(video_frame->unique_id()); | 
|  | paint_image_builder.set_paint_image_generator( | 
|  | sk_make_sp<VideoImageGenerator>(video_frame)); | 
|  | } | 
|  |  | 
|  | cache_->paint_image = paint_image_builder.TakePaintImage(); | 
|  | if (!cache_->paint_image) { | 
|  | // Couldn't create the PaintImage. | 
|  | cache_.reset(); | 
|  | return false; | 
|  | } | 
|  | cache_deleting_timer_.Reset(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | PaintCanvasVideoRenderer::YUVTextureCache::YUVTextureCache() = default; | 
|  | PaintCanvasVideoRenderer::YUVTextureCache::~YUVTextureCache() { | 
|  | Reset(); | 
|  | } | 
|  |  | 
|  | void PaintCanvasVideoRenderer::YUVTextureCache::Reset() { | 
|  | if (mailbox.IsZero()) | 
|  | return; | 
|  | DCHECK(raster_context_provider); | 
|  |  | 
|  | gpu::raster::RasterInterface* ri = raster_context_provider->RasterInterface(); | 
|  | ri->WaitSyncTokenCHROMIUM(sync_token.GetConstData()); | 
|  | ri->OrderingBarrierCHROMIUM(); | 
|  |  | 
|  | auto* sii = raster_context_provider->SharedImageInterface(); | 
|  | sii->DestroySharedImage(sync_token, mailbox); | 
|  | mailbox.SetZero(); | 
|  |  | 
|  | yuv_converter.ReleaseCachedData(); | 
|  |  | 
|  | // Kick off the GL work up to the OrderingBarrierCHROMIUM above as well as the | 
|  | // SharedImageInterface work, to ensure the shared image memory is released in | 
|  | // a timely fashion. | 
|  | raster_context_provider->ContextSupport()->FlushPendingWork(); | 
|  | raster_context_provider.reset(); | 
|  | } | 
|  |  | 
|  | gfx::Size PaintCanvasVideoRenderer::LastImageDimensionsForTesting() { | 
|  | DCHECK(cache_); | 
|  | DCHECK(cache_->paint_image); | 
|  | return gfx::Size(cache_->paint_image.width(), cache_->paint_image.height()); | 
|  | } | 
|  |  | 
|  | bool PaintCanvasVideoRenderer::CacheBackingWrapsTexture() const { | 
|  | return cache_ && cache_->texture_backing && | 
|  | cache_->texture_backing->wraps_video_frame_texture(); | 
|  | } | 
|  |  | 
|  | }  // namespace media |