Home | History | Annotate | Download | only in pepper
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/renderer/pepper/video_decoder_shim.h"
      6 
      7 #include <GLES2/gl2.h>
      8 #include <GLES2/gl2ext.h>
      9 #include <GLES2/gl2extchromium.h>
     10 
     11 #include "base/bind.h"
     12 #include "base/numerics/safe_conversions.h"
     13 #include "content/public/renderer/render_thread.h"
     14 #include "content/renderer/pepper/pepper_video_decoder_host.h"
     15 #include "content/renderer/render_thread_impl.h"
     16 #include "gpu/command_buffer/client/gles2_implementation.h"
     17 #include "media/base/decoder_buffer.h"
     18 #include "media/base/limits.h"
     19 #include "media/base/video_decoder.h"
     20 #include "media/filters/ffmpeg_video_decoder.h"
     21 #include "media/filters/vpx_video_decoder.h"
     22 #include "media/video/picture.h"
     23 #include "media/video/video_decode_accelerator.h"
     24 #include "ppapi/c/pp_errors.h"
     25 #include "third_party/libyuv/include/libyuv.h"
     26 #include "webkit/common/gpu/context_provider_web_context.h"
     27 
     28 namespace content {
     29 
     30 struct VideoDecoderShim::PendingDecode {
     31   PendingDecode(uint32_t decode_id,
     32                 const scoped_refptr<media::DecoderBuffer>& buffer);
     33   ~PendingDecode();
     34 
     35   const uint32_t decode_id;
     36   const scoped_refptr<media::DecoderBuffer> buffer;
     37 };
     38 
     39 VideoDecoderShim::PendingDecode::PendingDecode(
     40     uint32_t decode_id,
     41     const scoped_refptr<media::DecoderBuffer>& buffer)
     42     : decode_id(decode_id), buffer(buffer) {
     43 }
     44 
     45 VideoDecoderShim::PendingDecode::~PendingDecode() {
     46 }
     47 
     48 struct VideoDecoderShim::PendingFrame {
     49   explicit PendingFrame(uint32_t decode_id);
     50   PendingFrame(uint32_t decode_id, const gfx::Size& size);
     51   ~PendingFrame();
     52 
     53   const uint32_t decode_id;
     54   const gfx::Size size;
     55   std::vector<uint8_t> argb_pixels;
     56 
     57  private:
     58   // This could be expensive to copy, so guard against that.
     59   DISALLOW_COPY_AND_ASSIGN(PendingFrame);
     60 };
     61 
     62 VideoDecoderShim::PendingFrame::PendingFrame(uint32_t decode_id)
     63     : decode_id(decode_id) {
     64 }
     65 
     66 VideoDecoderShim::PendingFrame::PendingFrame(uint32_t decode_id,
     67                                              const gfx::Size& size)
     68     : decode_id(decode_id),
     69       size(size),
     70       argb_pixels(size.width() * size.height() * 4) {
     71 }
     72 
     73 VideoDecoderShim::PendingFrame::~PendingFrame() {
     74 }
     75 
     76 // DecoderImpl runs the underlying VideoDecoder on the media thread, receiving
     77 // calls from the VideoDecodeShim on the main thread and sending results back.
     78 // This class is constructed on the main thread, but used and destructed on the
     79 // media thread.
     80 class VideoDecoderShim::DecoderImpl {
     81  public:
     82   explicit DecoderImpl(const base::WeakPtr<VideoDecoderShim>& proxy);
     83   ~DecoderImpl();
     84 
     85   void Initialize(media::VideoDecoderConfig config);
     86   void Decode(uint32_t decode_id, scoped_refptr<media::DecoderBuffer> buffer);
     87   void Reset();
     88   void Stop();
     89 
     90  private:
     91   void OnPipelineStatus(media::PipelineStatus status);
     92   void DoDecode();
     93   void OnDecodeComplete(uint32_t decode_id, media::VideoDecoder::Status status);
     94   void OnOutputComplete(const scoped_refptr<media::VideoFrame>& frame);
     95   void OnResetComplete();
     96 
     97   // WeakPtr is bound to main_message_loop_. Use only in shim callbacks.
     98   base::WeakPtr<VideoDecoderShim> shim_;
     99   scoped_ptr<media::VideoDecoder> decoder_;
    100   scoped_refptr<base::MessageLoopProxy> main_message_loop_;
    101   // Queue of decodes waiting for the decoder.
    102   typedef std::queue<PendingDecode> PendingDecodeQueue;
    103   PendingDecodeQueue pending_decodes_;
    104   int max_decodes_at_decoder_;
    105   int num_decodes_at_decoder_;
    106   // VideoDecoder returns pictures without information about the decode buffer
    107   // that generated it. Save the decode_id from the last decode that completed,
    108   // which is close for most decoders, which only decode one buffer at a time.
    109   uint32_t decode_id_;
    110 };
    111 
    112 VideoDecoderShim::DecoderImpl::DecoderImpl(
    113     const base::WeakPtr<VideoDecoderShim>& proxy)
    114     : shim_(proxy),
    115       main_message_loop_(base::MessageLoopProxy::current()),
    116       max_decodes_at_decoder_(0),
    117       num_decodes_at_decoder_(0),
    118       decode_id_(0) {
    119 }
    120 
    121 VideoDecoderShim::DecoderImpl::~DecoderImpl() {
    122   DCHECK(pending_decodes_.empty());
    123 }
    124 
    125 void VideoDecoderShim::DecoderImpl::Initialize(
    126     media::VideoDecoderConfig config) {
    127   DCHECK(!decoder_);
    128   if (config.codec() == media::kCodecVP9) {
    129     decoder_.reset(
    130         new media::VpxVideoDecoder(base::MessageLoopProxy::current()));
    131   } else {
    132     scoped_ptr<media::FFmpegVideoDecoder> ffmpeg_video_decoder(
    133         new media::FFmpegVideoDecoder(base::MessageLoopProxy::current()));
    134     ffmpeg_video_decoder->set_decode_nalus(true);
    135     decoder_ = ffmpeg_video_decoder.Pass();
    136   }
    137   max_decodes_at_decoder_ = decoder_->GetMaxDecodeRequests();
    138   // We can use base::Unretained() safely in decoder callbacks because we call
    139   // VideoDecoder::Stop() before deletion. Stop() guarantees there will be no
    140   // outstanding callbacks after it returns.
    141   decoder_->Initialize(
    142       config,
    143       true /* low_delay */,
    144       base::Bind(&VideoDecoderShim::DecoderImpl::OnPipelineStatus,
    145                  base::Unretained(this)),
    146       base::Bind(&VideoDecoderShim::DecoderImpl::OnOutputComplete,
    147                  base::Unretained(this)));
    148 }
    149 
    150 void VideoDecoderShim::DecoderImpl::Decode(
    151     uint32_t decode_id,
    152     scoped_refptr<media::DecoderBuffer> buffer) {
    153   DCHECK(decoder_);
    154   pending_decodes_.push(PendingDecode(decode_id, buffer));
    155   DoDecode();
    156 }
    157 
    158 void VideoDecoderShim::DecoderImpl::Reset() {
    159   DCHECK(decoder_);
    160   // Abort all pending decodes.
    161   while (!pending_decodes_.empty()) {
    162     const PendingDecode& decode = pending_decodes_.front();
    163     scoped_ptr<PendingFrame> pending_frame(new PendingFrame(decode.decode_id));
    164     main_message_loop_->PostTask(FROM_HERE,
    165                                  base::Bind(&VideoDecoderShim::OnDecodeComplete,
    166                                             shim_,
    167                                             media::VideoDecoder::kAborted,
    168                                             decode.decode_id));
    169     pending_decodes_.pop();
    170   }
    171   decoder_->Reset(base::Bind(&VideoDecoderShim::DecoderImpl::OnResetComplete,
    172                              base::Unretained(this)));
    173 }
    174 
    175 void VideoDecoderShim::DecoderImpl::Stop() {
    176   DCHECK(decoder_);
    177   // Clear pending decodes now. We don't want OnDecodeComplete to call DoDecode
    178   // again.
    179   while (!pending_decodes_.empty())
    180     pending_decodes_.pop();
    181   decoder_->Stop();
    182   // This instance is deleted once we exit this scope.
    183 }
    184 
    185 void VideoDecoderShim::DecoderImpl::OnPipelineStatus(
    186     media::PipelineStatus status) {
    187   int32_t result;
    188   switch (status) {
    189     case media::PIPELINE_OK:
    190       result = PP_OK;
    191       break;
    192     case media::DECODER_ERROR_NOT_SUPPORTED:
    193       result = PP_ERROR_NOTSUPPORTED;
    194       break;
    195     default:
    196       result = PP_ERROR_FAILED;
    197       break;
    198   }
    199 
    200   // Calculate how many textures the shim should create.
    201   uint32_t shim_texture_pool_size =
    202       max_decodes_at_decoder_ + media::limits::kMaxVideoFrames;
    203   main_message_loop_->PostTask(
    204       FROM_HERE,
    205       base::Bind(&VideoDecoderShim::OnInitializeComplete,
    206                  shim_,
    207                  result,
    208                  shim_texture_pool_size));
    209 }
    210 
    211 void VideoDecoderShim::DecoderImpl::DoDecode() {
    212   while (!pending_decodes_.empty() &&
    213          num_decodes_at_decoder_ < max_decodes_at_decoder_) {
    214     num_decodes_at_decoder_++;
    215     const PendingDecode& decode = pending_decodes_.front();
    216     decoder_->Decode(
    217         decode.buffer,
    218         base::Bind(&VideoDecoderShim::DecoderImpl::OnDecodeComplete,
    219                    base::Unretained(this),
    220                    decode.decode_id));
    221     pending_decodes_.pop();
    222   }
    223 }
    224 
    225 void VideoDecoderShim::DecoderImpl::OnDecodeComplete(
    226     uint32_t decode_id,
    227     media::VideoDecoder::Status status) {
    228   num_decodes_at_decoder_--;
    229   decode_id_ = decode_id;
    230 
    231   int32_t result;
    232   switch (status) {
    233     case media::VideoDecoder::kOk:
    234     case media::VideoDecoder::kAborted:
    235       result = PP_OK;
    236       break;
    237     case media::VideoDecoder::kDecodeError:
    238       result = PP_ERROR_RESOURCE_FAILED;
    239       break;
    240     default:
    241       NOTREACHED();
    242       result = PP_ERROR_FAILED;
    243       break;
    244   }
    245 
    246   main_message_loop_->PostTask(
    247       FROM_HERE,
    248       base::Bind(
    249           &VideoDecoderShim::OnDecodeComplete, shim_, result, decode_id));
    250 
    251   DoDecode();
    252 }
    253 
    254 void VideoDecoderShim::DecoderImpl::OnOutputComplete(
    255     const scoped_refptr<media::VideoFrame>& frame) {
    256   scoped_ptr<PendingFrame> pending_frame;
    257   if (!frame->end_of_stream()) {
    258     pending_frame.reset(new PendingFrame(decode_id_, frame->coded_size()));
    259     // Convert the VideoFrame pixels to ABGR to match VideoDecodeAccelerator.
    260     libyuv::I420ToABGR(frame->data(media::VideoFrame::kYPlane),
    261                        frame->stride(media::VideoFrame::kYPlane),
    262                        frame->data(media::VideoFrame::kUPlane),
    263                        frame->stride(media::VideoFrame::kUPlane),
    264                        frame->data(media::VideoFrame::kVPlane),
    265                        frame->stride(media::VideoFrame::kVPlane),
    266                        &pending_frame->argb_pixels.front(),
    267                        frame->coded_size().width() * 4,
    268                        frame->coded_size().width(),
    269                        frame->coded_size().height());
    270   } else {
    271     pending_frame.reset(new PendingFrame(decode_id_));
    272   }
    273 
    274   main_message_loop_->PostTask(FROM_HERE,
    275                                base::Bind(&VideoDecoderShim::OnOutputComplete,
    276                                           shim_,
    277                                           base::Passed(&pending_frame)));
    278 }
    279 
    280 void VideoDecoderShim::DecoderImpl::OnResetComplete() {
    281   main_message_loop_->PostTask(
    282       FROM_HERE, base::Bind(&VideoDecoderShim::OnResetComplete, shim_));
    283 }
    284 
    285 VideoDecoderShim::VideoDecoderShim(PepperVideoDecoderHost* host)
    286     : state_(UNINITIALIZED),
    287       host_(host),
    288       media_message_loop_(
    289           RenderThreadImpl::current()->GetMediaThreadMessageLoopProxy()),
    290       context_provider_(
    291           RenderThreadImpl::current()->SharedMainThreadContextProvider()),
    292       texture_pool_size_(0),
    293       num_pending_decodes_(0),
    294       weak_ptr_factory_(this) {
    295   DCHECK(host_);
    296   DCHECK(media_message_loop_);
    297   DCHECK(context_provider_);
    298   decoder_impl_.reset(new DecoderImpl(weak_ptr_factory_.GetWeakPtr()));
    299 }
    300 
    301 VideoDecoderShim::~VideoDecoderShim() {
    302   DCHECK(RenderThreadImpl::current());
    303   // Delete any remaining textures.
    304   TextureIdMap::iterator it = texture_id_map_.begin();
    305   for (; it != texture_id_map_.end(); ++it)
    306     DeleteTexture(it->second);
    307   texture_id_map_.clear();
    308 
    309   FlushCommandBuffer();
    310 
    311   weak_ptr_factory_.InvalidateWeakPtrs();
    312   // No more callbacks from the delegate will be received now.
    313 
    314   // The callback now holds the only reference to the DecoderImpl, which will be
    315   // deleted when Stop completes.
    316   media_message_loop_->PostTask(
    317       FROM_HERE,
    318       base::Bind(&VideoDecoderShim::DecoderImpl::Stop,
    319                  base::Owned(decoder_impl_.release())));
    320 }
    321 
    322 bool VideoDecoderShim::Initialize(
    323     media::VideoCodecProfile profile,
    324     media::VideoDecodeAccelerator::Client* client) {
    325   DCHECK_EQ(client, host_);
    326   DCHECK(RenderThreadImpl::current());
    327   DCHECK_EQ(state_, UNINITIALIZED);
    328   media::VideoCodec codec = media::kUnknownVideoCodec;
    329   if (profile <= media::H264PROFILE_MAX)
    330     codec = media::kCodecH264;
    331   else if (profile <= media::VP8PROFILE_MAX)
    332     codec = media::kCodecVP8;
    333   else if (profile <= media::VP9PROFILE_MAX)
    334     codec = media::kCodecVP9;
    335   DCHECK_NE(codec, media::kUnknownVideoCodec);
    336 
    337   media::VideoDecoderConfig config(
    338       codec,
    339       profile,
    340       media::VideoFrame::YV12,
    341       gfx::Size(32, 24),  // Small sizes that won't fail.
    342       gfx::Rect(32, 24),
    343       gfx::Size(32, 24),
    344       NULL /* extra_data */,  // TODO(bbudge) Verify this isn't needed.
    345       0 /* extra_data_size */,
    346       false /* decryption */);
    347 
    348   media_message_loop_->PostTask(
    349       FROM_HERE,
    350       base::Bind(&VideoDecoderShim::DecoderImpl::Initialize,
    351                  base::Unretained(decoder_impl_.get()),
    352                  config));
    353   // Return success, even though we are asynchronous, to mimic
    354   // media::VideoDecodeAccelerator.
    355   return true;
    356 }
    357 
    358 void VideoDecoderShim::Decode(const media::BitstreamBuffer& bitstream_buffer) {
    359   DCHECK(RenderThreadImpl::current());
    360   DCHECK_EQ(state_, DECODING);
    361 
    362   // We need the address of the shared memory, so we can copy the buffer.
    363   const uint8_t* buffer = host_->DecodeIdToAddress(bitstream_buffer.id());
    364   DCHECK(buffer);
    365 
    366   media_message_loop_->PostTask(
    367       FROM_HERE,
    368       base::Bind(
    369           &VideoDecoderShim::DecoderImpl::Decode,
    370           base::Unretained(decoder_impl_.get()),
    371           bitstream_buffer.id(),
    372           media::DecoderBuffer::CopyFrom(buffer, bitstream_buffer.size())));
    373   num_pending_decodes_++;
    374 }
    375 
    376 void VideoDecoderShim::AssignPictureBuffers(
    377     const std::vector<media::PictureBuffer>& buffers) {
    378   DCHECK(RenderThreadImpl::current());
    379   DCHECK_EQ(state_, DECODING);
    380   if (buffers.empty()) {
    381     NOTREACHED();
    382     return;
    383   }
    384   DCHECK_EQ(buffers.size(), pending_texture_mailboxes_.size());
    385   GLuint num_textures = base::checked_cast<GLuint>(buffers.size());
    386   std::vector<uint32_t> local_texture_ids(num_textures);
    387   gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL();
    388   gles2->GenTextures(num_textures, &local_texture_ids.front());
    389   for (uint32_t i = 0; i < num_textures; i++) {
    390     gles2->ActiveTexture(GL_TEXTURE0);
    391     gles2->BindTexture(GL_TEXTURE_2D, local_texture_ids[i]);
    392     gles2->ConsumeTextureCHROMIUM(GL_TEXTURE_2D,
    393                                   pending_texture_mailboxes_[i].name);
    394     // Map the plugin texture id to the local texture id.
    395     uint32_t plugin_texture_id = buffers[i].texture_id();
    396     texture_id_map_[plugin_texture_id] = local_texture_ids[i];
    397     available_textures_.insert(plugin_texture_id);
    398   }
    399   pending_texture_mailboxes_.clear();
    400   SendPictures();
    401 }
    402 
    403 void VideoDecoderShim::ReusePictureBuffer(int32 picture_buffer_id) {
    404   DCHECK(RenderThreadImpl::current());
    405   uint32_t texture_id = static_cast<uint32_t>(picture_buffer_id);
    406   if (textures_to_dismiss_.find(texture_id) != textures_to_dismiss_.end()) {
    407     DismissTexture(texture_id);
    408   } else if (texture_id_map_.find(texture_id) != texture_id_map_.end()) {
    409     available_textures_.insert(texture_id);
    410     SendPictures();
    411   } else {
    412     NOTREACHED();
    413   }
    414 }
    415 
    416 void VideoDecoderShim::Flush() {
    417   DCHECK(RenderThreadImpl::current());
    418   DCHECK_EQ(state_, DECODING);
    419   state_ = FLUSHING;
    420 }
    421 
    422 void VideoDecoderShim::Reset() {
    423   DCHECK(RenderThreadImpl::current());
    424   DCHECK_EQ(state_, DECODING);
    425   state_ = RESETTING;
    426   media_message_loop_->PostTask(
    427       FROM_HERE,
    428       base::Bind(&VideoDecoderShim::DecoderImpl::Reset,
    429                  base::Unretained(decoder_impl_.get())));
    430 }
    431 
    432 void VideoDecoderShim::Destroy() {
    433   // This will be called, but our destructor does the actual work.
    434 }
    435 
    436 void VideoDecoderShim::OnInitializeComplete(int32_t result,
    437                                             uint32_t texture_pool_size) {
    438   DCHECK(RenderThreadImpl::current());
    439   DCHECK(host_);
    440 
    441   if (result == PP_OK) {
    442     state_ = DECODING;
    443     texture_pool_size_ = texture_pool_size;
    444   }
    445 
    446   host_->OnInitializeComplete(result);
    447 }
    448 
    449 void VideoDecoderShim::OnDecodeComplete(int32_t result, uint32_t decode_id) {
    450   DCHECK(RenderThreadImpl::current());
    451   DCHECK(host_);
    452 
    453   if (result == PP_ERROR_RESOURCE_FAILED) {
    454     host_->NotifyError(media::VideoDecodeAccelerator::PLATFORM_FAILURE);
    455     return;
    456   }
    457 
    458   num_pending_decodes_--;
    459   completed_decodes_.push(decode_id);
    460 
    461   // If frames are being queued because we're out of textures, don't notify
    462   // the host that decode has completed. This exerts "back pressure" to keep
    463   // the host from sending buffers that will cause pending_frames_ to grow.
    464   if (pending_frames_.empty())
    465     NotifyCompletedDecodes();
    466 }
    467 
    468 void VideoDecoderShim::OnOutputComplete(scoped_ptr<PendingFrame> frame) {
    469   DCHECK(RenderThreadImpl::current());
    470   DCHECK(host_);
    471 
    472   if (!frame->argb_pixels.empty()) {
    473     if (texture_size_ != frame->size) {
    474       // If the size has changed, all current textures must be dismissed. Add
    475       // all textures to |textures_to_dismiss_| and dismiss any that aren't in
    476       // use by the plugin. We will dismiss the rest as they are recycled.
    477       for (TextureIdMap::const_iterator it = texture_id_map_.begin();
    478            it != texture_id_map_.end();
    479            ++it) {
    480         textures_to_dismiss_.insert(it->second);
    481       }
    482       for (TextureIdSet::const_iterator it = available_textures_.begin();
    483            it != available_textures_.end();
    484            ++it) {
    485         DismissTexture(*it);
    486       }
    487       available_textures_.clear();
    488       FlushCommandBuffer();
    489 
    490       DCHECK(pending_texture_mailboxes_.empty());
    491       for (uint32_t i = 0; i < texture_pool_size_; i++)
    492         pending_texture_mailboxes_.push_back(gpu::Mailbox::Generate());
    493 
    494       host_->RequestTextures(texture_pool_size_,
    495                              frame->size,
    496                              GL_TEXTURE_2D,
    497                              pending_texture_mailboxes_);
    498       texture_size_ = frame->size;
    499     }
    500 
    501     pending_frames_.push(linked_ptr<PendingFrame>(frame.release()));
    502     SendPictures();
    503   }
    504 }
    505 
    506 void VideoDecoderShim::SendPictures() {
    507   DCHECK(RenderThreadImpl::current());
    508   DCHECK(host_);
    509   while (!pending_frames_.empty() && !available_textures_.empty()) {
    510     const linked_ptr<PendingFrame>& frame = pending_frames_.front();
    511 
    512     TextureIdSet::iterator it = available_textures_.begin();
    513     uint32_t texture_id = *it;
    514     available_textures_.erase(it);
    515 
    516     uint32_t local_texture_id = texture_id_map_[texture_id];
    517     gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL();
    518     gles2->ActiveTexture(GL_TEXTURE0);
    519     gles2->BindTexture(GL_TEXTURE_2D, local_texture_id);
    520     gles2->TexImage2D(GL_TEXTURE_2D,
    521                       0,
    522                       GL_RGBA,
    523                       texture_size_.width(),
    524                       texture_size_.height(),
    525                       0,
    526                       GL_RGBA,
    527                       GL_UNSIGNED_BYTE,
    528                       &frame->argb_pixels.front());
    529 
    530     host_->PictureReady(media::Picture(texture_id, frame->decode_id));
    531     pending_frames_.pop();
    532   }
    533 
    534   FlushCommandBuffer();
    535 
    536   if (pending_frames_.empty()) {
    537     // If frames aren't backing up, notify the host of any completed decodes so
    538     // it can send more buffers.
    539     NotifyCompletedDecodes();
    540 
    541     if (state_ == FLUSHING && !num_pending_decodes_) {
    542       state_ = DECODING;
    543       host_->NotifyFlushDone();
    544     }
    545   }
    546 }
    547 
    548 void VideoDecoderShim::OnResetComplete() {
    549   DCHECK(RenderThreadImpl::current());
    550   DCHECK(host_);
    551 
    552   while (!pending_frames_.empty())
    553     pending_frames_.pop();
    554   NotifyCompletedDecodes();
    555 
    556   // Dismiss any old textures now.
    557   while (!textures_to_dismiss_.empty())
    558     DismissTexture(*textures_to_dismiss_.begin());
    559 
    560   state_ = DECODING;
    561   host_->NotifyResetDone();
    562 }
    563 
    564 void VideoDecoderShim::NotifyCompletedDecodes() {
    565   while (!completed_decodes_.empty()) {
    566     host_->NotifyEndOfBitstreamBuffer(completed_decodes_.front());
    567     completed_decodes_.pop();
    568   }
    569 }
    570 
    571 void VideoDecoderShim::DismissTexture(uint32_t texture_id) {
    572   DCHECK(host_);
    573   textures_to_dismiss_.erase(texture_id);
    574   DCHECK(texture_id_map_.find(texture_id) != texture_id_map_.end());
    575   DeleteTexture(texture_id_map_[texture_id]);
    576   texture_id_map_.erase(texture_id);
    577   host_->DismissPictureBuffer(texture_id);
    578 }
    579 
    580 void VideoDecoderShim::DeleteTexture(uint32_t texture_id) {
    581   gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL();
    582   gles2->DeleteTextures(1, &texture_id);
    583 }
    584 
    585 void VideoDecoderShim::FlushCommandBuffer() {
    586   context_provider_->ContextGL()->Flush();
    587 }
    588 
    589 }  // namespace content
    590