1 // Copyright (c) 2012 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.
5 #include "media/blink/skcanvas_video_renderer.h"
7 #include "gpu/GLES2/gl2extchromium.h"
8 #include "gpu/command_buffer/client/gles2_interface.h"
9 #include "gpu/command_buffer/common/mailbox_holder.h"
10 #include "media/base/video_frame.h"
11 #include "media/base/yuv_convert.h"
12 #include "skia/ext/refptr.h"
13 #include "third_party/libyuv/include/libyuv.h"
14 #include "third_party/skia/include/core/SkCanvas.h"
15 #include "third_party/skia/include/core/SkImage.h"
16 #include "third_party/skia/include/core/SkImageGenerator.h"
17 #include "third_party/skia/include/gpu/GrContext.h"
18 #include "third_party/skia/include/gpu/GrPaint.h"
19 #include "third_party/skia/include/gpu/GrTexture.h"
20 #include "third_party/skia/include/gpu/GrTextureProvider.h"
22 // Skia internal format depends on a platform. On Android it is ABGR, on others
24 #if SK_B32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_R32_SHIFT == 16 && \
26 #define LIBYUV_I420_TO_ARGB libyuv::I420ToARGB
27 #define LIBYUV_I422_TO_ARGB libyuv::I422ToARGB
28 #elif SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && \
30 #define LIBYUV_I420_TO_ARGB libyuv::I420ToABGR
31 #define LIBYUV_I422_TO_ARGB libyuv::I422ToABGR
33 #error Unexpected Skia ARGB_8888 layout!
40 // This class keeps the last image drawn.
41 // We delete the temporary resource if it is not used for 3 seconds.
42 const int kTemporaryResourceDeletionDelay
= 3; // Seconds;
44 bool CheckColorSpace(const VideoFrame
* video_frame
, ColorSpace color_space
) {
46 return video_frame
->metadata()->GetInteger(
47 VideoFrameMetadata::COLOR_SPACE
, &result
) &&
48 result
== color_space
;
51 class SyncPointClientImpl
: public VideoFrame::SyncPointClient
{
53 explicit SyncPointClientImpl(gpu::gles2::GLES2Interface
* gl
) : gl_(gl
) {}
54 ~SyncPointClientImpl() override
{}
55 uint32
InsertSyncPoint() override
{ return gl_
->InsertSyncPointCHROMIUM(); }
56 void WaitSyncPoint(uint32 sync_point
) override
{
57 gl_
->WaitSyncPointCHROMIUM(sync_point
);
61 gpu::gles2::GLES2Interface
* gl_
;
63 DISALLOW_IMPLICIT_CONSTRUCTORS(SyncPointClientImpl
);
66 skia::RefPtr
<SkImage
> NewSkImageFromVideoFrameYUVTextures(
67 const VideoFrame
* video_frame
,
68 const Context3D
& context_3d
) {
69 // Support only TEXTURE_YUV_420.
70 DCHECK(video_frame
->HasTextures());
71 DCHECK_EQ(media::PIXEL_FORMAT_I420
, video_frame
->format());
72 DCHECK_EQ(3u, media::VideoFrame::NumPlanes(video_frame
->format()));
74 gpu::gles2::GLES2Interface
* gl
= context_3d
.gl
;
76 gfx::Size ya_tex_size
= video_frame
->coded_size();
77 gfx::Size
uv_tex_size((ya_tex_size
.width() + 1) / 2,
78 (ya_tex_size
.height() + 1) / 2);
80 unsigned source_textures
[3] = {0};
81 for (size_t i
= 0; i
< media::VideoFrame::NumPlanes(video_frame
->format());
83 // Get the texture from the mailbox and wrap it in a GrTexture.
84 const gpu::MailboxHolder
& mailbox_holder
= video_frame
->mailbox_holder(i
);
85 DCHECK(mailbox_holder
.texture_target
== GL_TEXTURE_2D
||
86 mailbox_holder
.texture_target
== GL_TEXTURE_EXTERNAL_OES
||
87 mailbox_holder
.texture_target
== GL_TEXTURE_RECTANGLE_ARB
);
88 gl
->WaitSyncPointCHROMIUM(mailbox_holder
.sync_point
);
89 source_textures
[i
] = gl
->CreateAndConsumeTextureCHROMIUM(
90 mailbox_holder
.texture_target
, mailbox_holder
.mailbox
.name
);
92 // TODO(dcastagna): avoid this copy once Skia supports native textures
93 // with a texture target different than TEXTURE_2D.
95 if (mailbox_holder
.texture_target
!= GL_TEXTURE_2D
) {
96 unsigned texture_copy
= 0;
97 gl
->GenTextures(1, &texture_copy
);
99 gl
->BindTexture(GL_TEXTURE_2D
, texture_copy
);
100 gl
->CopyTextureCHROMIUM(GL_TEXTURE_2D
, source_textures
[i
], texture_copy
,
101 GL_RGB
, GL_UNSIGNED_BYTE
, false, true, false);
103 gl
->DeleteTextures(1, &source_textures
[i
]);
104 source_textures
[i
] = texture_copy
;
107 GrBackendObject handles
[3] = {
108 source_textures
[0], source_textures
[1], source_textures
[2]};
110 SkISize yuvSizes
[] = {
111 {ya_tex_size
.width(), ya_tex_size
.height()},
112 {uv_tex_size
.width(), uv_tex_size
.height()},
113 {uv_tex_size
.width(), uv_tex_size
.height()},
116 SkYUVColorSpace color_space
= kRec601_SkYUVColorSpace
;
117 if (CheckColorSpace(video_frame
, media::COLOR_SPACE_JPEG
))
118 color_space
= kJPEG_SkYUVColorSpace
;
119 else if (CheckColorSpace(video_frame
, media::COLOR_SPACE_HD_REC709
))
120 color_space
= kRec709_SkYUVColorSpace
;
122 SkImage
* img
= SkImage::NewFromYUVTexturesCopy(context_3d
.gr_context
,
123 color_space
, handles
, yuvSizes
,
124 kTopLeft_GrSurfaceOrigin
);
126 gl
->DeleteTextures(3, source_textures
);
127 return skia::AdoptRef(img
);
130 bool ShouldCacheVideoFrameSkImage(const VideoFrame
* video_frame
) {
131 return !video_frame
->HasTextures() ||
132 media::VideoFrame::NumPlanes(video_frame
->format()) != 1 ||
133 video_frame
->mailbox_holder(0).texture_target
!= GL_TEXTURE_2D
;
136 // Creates a SkImage from a |video_frame| backed by native resources.
137 // The SkImage will take ownership of the underlying resource.
138 skia::RefPtr
<SkImage
> NewSkImageFromVideoFrameNative(
139 VideoFrame
* video_frame
,
140 const Context3D
& context_3d
) {
141 DCHECK_EQ(PIXEL_FORMAT_ARGB
, video_frame
->format());
143 const gpu::MailboxHolder
& mailbox_holder
= video_frame
->mailbox_holder(0);
144 DCHECK(mailbox_holder
.texture_target
== GL_TEXTURE_2D
||
145 mailbox_holder
.texture_target
== GL_TEXTURE_RECTANGLE_ARB
||
146 mailbox_holder
.texture_target
== GL_TEXTURE_EXTERNAL_OES
)
147 << mailbox_holder
.texture_target
;
149 gpu::gles2::GLES2Interface
* gl
= context_3d
.gl
;
150 unsigned source_texture
= 0;
151 if (mailbox_holder
.texture_target
!= GL_TEXTURE_2D
) {
152 // TODO(dcastagna): At the moment Skia doesn't support targets different
153 // than GL_TEXTURE_2D. Avoid this copy once
154 // https://code.google.com/p/skia/issues/detail?id=3868 is addressed.
155 gl
->GenTextures(1, &source_texture
);
156 DCHECK(source_texture
);
157 gl
->BindTexture(GL_TEXTURE_2D
, source_texture
);
158 SkCanvasVideoRenderer::CopyVideoFrameSingleTextureToGLTexture(
159 gl
, video_frame
, source_texture
, GL_RGBA
, GL_UNSIGNED_BYTE
, true,
162 gl
->WaitSyncPointCHROMIUM(mailbox_holder
.sync_point
);
163 source_texture
= gl
->CreateAndConsumeTextureCHROMIUM(
164 mailbox_holder
.texture_target
, mailbox_holder
.mailbox
.name
);
166 GrBackendTextureDesc desc
;
167 desc
.fFlags
= kRenderTarget_GrBackendTextureFlag
;
168 desc
.fOrigin
= kTopLeft_GrSurfaceOrigin
;
169 desc
.fWidth
= video_frame
->coded_size().width();
170 desc
.fHeight
= video_frame
->coded_size().height();
171 desc
.fConfig
= kRGBA_8888_GrPixelConfig
;
172 desc
.fTextureHandle
= source_texture
;
173 return skia::AdoptRef(
174 SkImage::NewFromAdoptedTexture(context_3d
.gr_context
, desc
));
177 } // anonymous namespace
179 // Generates an RGB image from a VideoFrame. Convert YUV to RGB plain on GPU.
180 class VideoImageGenerator
: public SkImageGenerator
{
182 VideoImageGenerator(const scoped_refptr
<VideoFrame
>& frame
)
184 SkImageInfo::MakeN32Premul(frame
->visible_rect().width(),
185 frame
->visible_rect().height())),
187 DCHECK(!frame_
->HasTextures());
189 ~VideoImageGenerator() override
{}
192 bool onGetPixels(const SkImageInfo
& info
,
196 int* ctable_count
) override
{
197 // If skia couldn't do the YUV conversion on GPU, we will on CPU.
198 SkCanvasVideoRenderer::ConvertVideoFrameToRGBPixels(frame_
.get(), pixels
,
203 bool onGetYUV8Planes(SkISize sizes
[3],
206 SkYUVColorSpace
* color_space
) override
{
207 if (!media::IsYuvPlanar(frame_
->format()) ||
208 // TODO(rileya): Skia currently doesn't support YUVA conversion. Remove
209 // this case once it does. As-is we will fall back on the pure-software
210 // path in this case.
211 frame_
->format() == PIXEL_FORMAT_YV12A
) {
216 if (CheckColorSpace(frame_
.get(), COLOR_SPACE_JPEG
))
217 *color_space
= kJPEG_SkYUVColorSpace
;
218 else if (CheckColorSpace(frame_
.get(), COLOR_SPACE_HD_REC709
))
219 *color_space
= kRec709_SkYUVColorSpace
;
221 *color_space
= kRec601_SkYUVColorSpace
;
224 for (int plane
= VideoFrame::kYPlane
; plane
<= VideoFrame::kVPlane
;
227 const gfx::Size size
=
228 VideoFrame::PlaneSize(frame_
->format(), plane
,
229 gfx::Size(frame_
->visible_rect().width(),
230 frame_
->visible_rect().height()));
231 sizes
[plane
].set(size
.width(), size
.height());
233 if (row_bytes
&& planes
) {
236 (frame_
->format() == media::PIXEL_FORMAT_YV16
) ? 0 : 1;
237 if (plane
== VideoFrame::kYPlane
) {
238 offset
= (frame_
->stride(VideoFrame::kYPlane
) *
239 frame_
->visible_rect().y()) +
240 frame_
->visible_rect().x();
242 offset
= (frame_
->stride(VideoFrame::kUPlane
) *
243 (frame_
->visible_rect().y() >> y_shift
)) +
244 (frame_
->visible_rect().x() >> 1);
247 // Copy the frame to the supplied memory.
248 // TODO: Find a way (API change?) to avoid this copy.
249 char* out_line
= static_cast<char*>(planes
[plane
]);
250 int out_line_stride
= row_bytes
[plane
];
251 uint8
* in_line
= frame_
->data(plane
) + offset
;
252 int in_line_stride
= frame_
->stride(plane
);
253 int plane_height
= sizes
[plane
].height();
254 if (in_line_stride
== out_line_stride
) {
255 memcpy(out_line
, in_line
, plane_height
* in_line_stride
);
257 // Different line padding so need to copy one line at a time.
258 int bytes_to_copy_per_line
= out_line_stride
< in_line_stride
261 for (int line_no
= 0; line_no
< plane_height
; line_no
++) {
262 memcpy(out_line
, in_line
, bytes_to_copy_per_line
);
263 in_line
+= in_line_stride
;
264 out_line
+= out_line_stride
;
273 scoped_refptr
<VideoFrame
> frame_
;
275 DISALLOW_IMPLICIT_CONSTRUCTORS(VideoImageGenerator
);
278 SkCanvasVideoRenderer::SkCanvasVideoRenderer()
279 : last_image_deleting_timer_(
281 base::TimeDelta::FromSeconds(kTemporaryResourceDeletionDelay
),
283 &SkCanvasVideoRenderer::ResetCache
) {}
285 SkCanvasVideoRenderer::~SkCanvasVideoRenderer() {
289 void SkCanvasVideoRenderer::Paint(const scoped_refptr
<VideoFrame
>& video_frame
,
291 const gfx::RectF
& dest_rect
,
293 SkXfermode::Mode mode
,
294 VideoRotation video_rotation
,
295 const Context3D
& context_3d
) {
301 dest
.set(dest_rect
.x(), dest_rect
.y(), dest_rect
.right(), dest_rect
.bottom());
304 paint
.setAlpha(alpha
);
306 // Paint black rectangle if there isn't a frame available or the
307 // frame has an unexpected format.
308 if (!video_frame
.get() || video_frame
->natural_size().IsEmpty() ||
309 !(media::IsYuvPlanar(video_frame
->format()) ||
310 video_frame
->HasTextures())) {
311 canvas
->drawRect(dest
, paint
);
316 gpu::gles2::GLES2Interface
* gl
= context_3d
.gl
;
318 if (!last_image_
|| video_frame
->timestamp() != last_timestamp_
) {
320 // Generate a new image.
321 // Note: Skia will hold onto |video_frame| via |video_generator| only when
322 // |video_frame| is software.
323 // Holding |video_frame| longer than this call when using GPUVideoDecoder
324 // could cause problems since the pool of VideoFrames has a fixed size.
325 if (video_frame
->HasTextures()) {
326 DCHECK(context_3d
.gr_context
);
328 if (media::VideoFrame::NumPlanes(video_frame
->format()) == 1) {
330 NewSkImageFromVideoFrameNative(video_frame
.get(), context_3d
);
333 NewSkImageFromVideoFrameYUVTextures(video_frame
.get(), context_3d
);
336 auto video_generator
= new VideoImageGenerator(video_frame
);
337 last_image_
= skia::AdoptRef(SkImage::NewFromGenerator(video_generator
));
339 last_timestamp_
= video_frame
->timestamp();
341 last_image_deleting_timer_
.Reset();
343 paint
.setXfermodeMode(mode
);
344 paint
.setFilterQuality(kLow_SkFilterQuality
);
346 const bool need_transform
=
347 video_rotation
!= VIDEO_ROTATION_0
||
348 dest_rect
.size() != video_frame
->visible_rect().size() ||
349 !dest_rect
.origin().IsOrigin();
350 if (need_transform
) {
353 SkFloatToScalar(dest_rect
.x() + (dest_rect
.width() * 0.5f
)),
354 SkFloatToScalar(dest_rect
.y() + (dest_rect
.height() * 0.5f
)));
355 SkScalar angle
= SkFloatToScalar(0.0f
);
356 switch (video_rotation
) {
357 case VIDEO_ROTATION_0
:
359 case VIDEO_ROTATION_90
:
360 angle
= SkFloatToScalar(90.0f
);
362 case VIDEO_ROTATION_180
:
363 angle
= SkFloatToScalar(180.0f
);
365 case VIDEO_ROTATION_270
:
366 angle
= SkFloatToScalar(270.0f
);
369 canvas
->rotate(angle
);
371 gfx::SizeF rotated_dest_size
= dest_rect
.size();
372 if (video_rotation
== VIDEO_ROTATION_90
||
373 video_rotation
== VIDEO_ROTATION_270
) {
375 gfx::SizeF(rotated_dest_size
.height(), rotated_dest_size
.width());
378 SkFloatToScalar(rotated_dest_size
.width() / last_image_
->width()),
379 SkFloatToScalar(rotated_dest_size
.height() / last_image_
->height()));
380 canvas
->translate(-SkFloatToScalar(last_image_
->width() * 0.5f
),
381 -SkFloatToScalar(last_image_
->height() * 0.5f
));
383 canvas
->drawImage(last_image_
.get(), 0, 0, &paint
);
387 // Make sure to flush so we can remove the videoframe from the generator.
390 if (!ShouldCacheVideoFrameSkImage(video_frame
.get()))
393 if (video_frame
->HasTextures()) {
395 SyncPointClientImpl
client(gl
);
396 video_frame
->UpdateReleaseSyncPoint(&client
);
400 void SkCanvasVideoRenderer::Copy(const scoped_refptr
<VideoFrame
>& video_frame
,
402 const Context3D
& context_3d
) {
403 Paint(video_frame
, canvas
, video_frame
->visible_rect(), 0xff,
404 SkXfermode::kSrc_Mode
, media::VIDEO_ROTATION_0
, context_3d
);
408 void SkCanvasVideoRenderer::ConvertVideoFrameToRGBPixels(
409 const VideoFrame
* video_frame
,
412 if (!video_frame
->IsMappable()) {
413 NOTREACHED() << "Cannot extract pixels from non-CPU frame formats.";
416 if (!media::IsYuvPlanar(video_frame
->format())) {
417 NOTREACHED() << "Non YUV formats are not supported";
421 DCHECK_EQ(video_frame
->stride(VideoFrame::kUPlane
),
422 video_frame
->stride(VideoFrame::kVPlane
));
425 (video_frame
->format() == media::PIXEL_FORMAT_YV16
) ? 0 : 1;
426 // Use the "left" and "top" of the destination rect to locate the offset
427 // in Y, U and V planes.
428 const size_t y_offset
= (video_frame
->stride(VideoFrame::kYPlane
) *
429 video_frame
->visible_rect().y()) +
430 video_frame
->visible_rect().x();
431 // For format YV12, there is one U, V value per 2x2 block.
432 // For format YV16, there is one U, V value per 2x1 block.
433 const size_t uv_offset
= (video_frame
->stride(VideoFrame::kUPlane
) *
434 (video_frame
->visible_rect().y() >> y_shift
)) +
435 (video_frame
->visible_rect().x() >> 1);
437 switch (video_frame
->format()) {
438 case PIXEL_FORMAT_YV12
:
439 case PIXEL_FORMAT_I420
:
440 if (CheckColorSpace(video_frame
, COLOR_SPACE_JPEG
)) {
442 video_frame
->data(VideoFrame::kYPlane
) + y_offset
,
443 video_frame
->data(VideoFrame::kUPlane
) + uv_offset
,
444 video_frame
->data(VideoFrame::kVPlane
) + uv_offset
,
445 static_cast<uint8
*>(rgb_pixels
),
446 video_frame
->visible_rect().width(),
447 video_frame
->visible_rect().height(),
448 video_frame
->stride(VideoFrame::kYPlane
),
449 video_frame
->stride(VideoFrame::kUPlane
),
452 } else if (CheckColorSpace(video_frame
, COLOR_SPACE_HD_REC709
)) {
453 ConvertYUVToRGB32(video_frame
->data(VideoFrame::kYPlane
) + y_offset
,
454 video_frame
->data(VideoFrame::kUPlane
) + uv_offset
,
455 video_frame
->data(VideoFrame::kVPlane
) + uv_offset
,
456 static_cast<uint8
*>(rgb_pixels
),
457 video_frame
->visible_rect().width(),
458 video_frame
->visible_rect().height(),
459 video_frame
->stride(VideoFrame::kYPlane
),
460 video_frame
->stride(VideoFrame::kUPlane
), row_bytes
,
464 video_frame
->data(VideoFrame::kYPlane
) + y_offset
,
465 video_frame
->stride(VideoFrame::kYPlane
),
466 video_frame
->data(VideoFrame::kUPlane
) + uv_offset
,
467 video_frame
->stride(VideoFrame::kUPlane
),
468 video_frame
->data(VideoFrame::kVPlane
) + uv_offset
,
469 video_frame
->stride(VideoFrame::kVPlane
),
470 static_cast<uint8
*>(rgb_pixels
),
472 video_frame
->visible_rect().width(),
473 video_frame
->visible_rect().height());
476 case PIXEL_FORMAT_YV16
:
478 video_frame
->data(VideoFrame::kYPlane
) + y_offset
,
479 video_frame
->stride(VideoFrame::kYPlane
),
480 video_frame
->data(VideoFrame::kUPlane
) + uv_offset
,
481 video_frame
->stride(VideoFrame::kUPlane
),
482 video_frame
->data(VideoFrame::kVPlane
) + uv_offset
,
483 video_frame
->stride(VideoFrame::kVPlane
),
484 static_cast<uint8
*>(rgb_pixels
),
486 video_frame
->visible_rect().width(),
487 video_frame
->visible_rect().height());
490 case PIXEL_FORMAT_YV12A
:
491 // Since libyuv doesn't support YUVA, fallback to media, which is not ARM
493 // TODO(fbarchard, mtomasz): Use libyuv, then copy the alpha channel.
495 video_frame
->data(VideoFrame::kYPlane
) + y_offset
,
496 video_frame
->data(VideoFrame::kUPlane
) + uv_offset
,
497 video_frame
->data(VideoFrame::kVPlane
) + uv_offset
,
498 video_frame
->data(VideoFrame::kAPlane
),
499 static_cast<uint8
*>(rgb_pixels
),
500 video_frame
->visible_rect().width(),
501 video_frame
->visible_rect().height(),
502 video_frame
->stride(VideoFrame::kYPlane
),
503 video_frame
->stride(VideoFrame::kUPlane
),
504 video_frame
->stride(VideoFrame::kAPlane
),
509 case PIXEL_FORMAT_YV24
:
511 video_frame
->data(VideoFrame::kYPlane
) + y_offset
,
512 video_frame
->stride(VideoFrame::kYPlane
),
513 video_frame
->data(VideoFrame::kUPlane
) + uv_offset
,
514 video_frame
->stride(VideoFrame::kUPlane
),
515 video_frame
->data(VideoFrame::kVPlane
) + uv_offset
,
516 video_frame
->stride(VideoFrame::kVPlane
),
517 static_cast<uint8
*>(rgb_pixels
),
519 video_frame
->visible_rect().width(),
520 video_frame
->visible_rect().height());
521 #if SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && \
523 libyuv::ARGBToABGR(static_cast<uint8
*>(rgb_pixels
),
525 static_cast<uint8
*>(rgb_pixels
),
527 video_frame
->visible_rect().width(),
528 video_frame
->visible_rect().height());
531 #if defined(OS_MACOSX) || defined(OS_CHROMEOS)
532 case PIXEL_FORMAT_NV12
:
534 case PIXEL_FORMAT_ARGB
:
535 case PIXEL_FORMAT_XRGB
:
536 case PIXEL_FORMAT_UNKNOWN
:
542 void SkCanvasVideoRenderer::CopyVideoFrameSingleTextureToGLTexture(
543 gpu::gles2::GLES2Interface
* gl
,
544 VideoFrame
* video_frame
,
545 unsigned int texture
,
546 unsigned int internal_format
,
548 bool premultiply_alpha
,
551 DCHECK(video_frame
->HasTextures());
552 DCHECK_EQ(1u, VideoFrame::NumPlanes(video_frame
->format()));
554 const gpu::MailboxHolder
& mailbox_holder
= video_frame
->mailbox_holder(0);
555 DCHECK(mailbox_holder
.texture_target
== GL_TEXTURE_2D
||
556 mailbox_holder
.texture_target
== GL_TEXTURE_RECTANGLE_ARB
||
557 mailbox_holder
.texture_target
== GL_TEXTURE_EXTERNAL_OES
)
558 << mailbox_holder
.texture_target
;
560 gl
->WaitSyncPointCHROMIUM(mailbox_holder
.sync_point
);
561 uint32 source_texture
= gl
->CreateAndConsumeTextureCHROMIUM(
562 mailbox_holder
.texture_target
, mailbox_holder
.mailbox
.name
);
564 // The video is stored in a unmultiplied format, so premultiply
566 // Application itself needs to take care of setting the right |flip_y|
567 // value down to get the expected result.
568 // "flip_y == true" means to reverse the video orientation while
569 // "flip_y == false" means to keep the intrinsic orientation.
570 gl
->CopyTextureCHROMIUM(GL_TEXTURE_2D
, source_texture
, texture
,
571 internal_format
, type
,
572 flip_y
, premultiply_alpha
, false);
574 gl
->DeleteTextures(1, &source_texture
);
577 SyncPointClientImpl
client(gl
);
578 video_frame
->UpdateReleaseSyncPoint(&client
);
581 void SkCanvasVideoRenderer::ResetCache() {
582 // Clear cached values.
583 last_image_
= nullptr;
584 last_timestamp_
= kNoTimestamp();