OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 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 <GLES2/gl2.h> | |
6 #include <GLES2/gl2ext.h> | |
7 #include <GLES2/gl2extchromium.h> | |
8 | |
9 #include "content/renderer/pepper/video_decoder_proxy.h" | |
10 | |
11 #include "base/bind.h" | |
12 #include "content/public/renderer/render_thread.h" | |
13 #include "content/renderer/pepper/pepper_video_decoder_host.h" | |
14 #include "content/renderer/render_thread_impl.h" | |
15 #include "gpu/command_buffer/client/gles2_implementation.h" | |
16 #include "media/base/decoder_buffer.h" | |
17 #include "media/filters/ffmpeg_video_decoder.h" | |
18 #include "media/video/picture.h" | |
19 #include "media/video/video_decode_accelerator.h" | |
20 #include "ppapi/c/pp_errors.h" | |
21 #include "third_party/libyuv/include/libyuv.h" | |
22 #include "webkit/common/gpu/context_provider_web_context.h" | |
23 | |
24 namespace content { | |
25 | |
26 VideoDecoderProxy::PendingDecode::PendingDecode( | |
27 uint32_t decode_id, | |
28 const scoped_refptr<media::DecoderBuffer>& buffer) | |
29 : decode_id(decode_id), buffer(buffer) { | |
30 } | |
31 | |
32 VideoDecoderProxy::PendingDecode::~PendingDecode() { | |
33 } | |
34 | |
35 VideoDecoderProxy::PendingFrame::PendingFrame(uint32_t decode_id, | |
36 const gfx::Size& size) | |
37 : decode_id(decode_id), | |
38 size(size), | |
39 pixels(size.width() * size.height() * 4) { | |
40 } | |
41 | |
42 VideoDecoderProxy::PendingFrame::~PendingFrame() { | |
43 } | |
44 | |
45 VideoDecoderProxy::Delegate::Delegate( | |
46 const base::WeakPtr<VideoDecoderProxy>& proxy) | |
47 : proxy_(proxy), main_message_loop_(base::MessageLoopProxy::current()) { | |
48 } | |
49 | |
50 VideoDecoderProxy::Delegate::~Delegate() { | |
51 DCHECK(pending_decodes_.empty()); | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
what guarantees this?
I think you might be relying
bbudge
2014/06/06 02:03:45
Done.
| |
52 } | |
53 | |
54 void VideoDecoderProxy::Delegate::Initialize( | |
55 scoped_ptr<media::VideoDecoder> decoder, | |
56 media::VideoDecoderConfig config) { | |
57 DCHECK(!decoder_); | |
58 decoder_.reset(decoder.release()); | |
dmichael (off chromium)
2014/06/05 23:00:43
tiny nit: I think I would write this as decoder_ =
bbudge
2014/06/06 02:03:45
This is no longer an argument.
| |
59 decoder_->Initialize( | |
60 config, | |
61 true /* low_delay */, | |
62 base::Bind(&VideoDecoderProxy::Delegate::OnPipelineStatus, | |
63 base::Unretained(this))); | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
I guess the only reason this isn't a UAF waiting t
bbudge
2014/06/06 02:03:45
Done.
| |
64 } | |
65 | |
66 void VideoDecoderProxy::Delegate::OnPipelineStatus( | |
67 media::PipelineStatus status) { | |
68 main_message_loop_->PostTask( | |
69 FROM_HERE, | |
70 base::Bind(&VideoDecoderProxy::OnPipelineStatus, proxy_, status)); | |
71 } | |
72 | |
73 void VideoDecoderProxy::Delegate::ReceiveBuffer( | |
74 uint32_t decode_id, | |
75 scoped_refptr<media::DecoderBuffer> buffer) { | |
76 bool decoder_busy = !pending_decodes_.empty(); | |
77 pending_decodes_.push(PendingDecode(decode_id, buffer)); | |
78 if (!decoder_busy) | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
FWIW can drop decoder_busy if you do
if (pending_d
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
AFAICT a client that never calls Reset() can never
bbudge
2014/06/06 02:03:44
I changed this and now call VD::GetMaxDecodeReques
bbudge
2014/06/06 02:03:45
It happens at startup and on reset. In the steady
| |
79 Decode(); | |
80 } | |
81 | |
82 void VideoDecoderProxy::Delegate::Decode() { | |
83 DCHECK(!pending_decodes_.empty()); | |
84 PendingDecode& next_decode = pending_decodes_.front(); | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
const& is less surprising
bbudge
2014/06/06 02:03:45
Done.
| |
85 decoder_->Decode(next_decode.buffer, | |
86 base::Bind(&VideoDecoderProxy::Delegate::ConvertFrame, | |
87 base::Unretained(this), | |
88 next_decode.decode_id)); | |
89 pending_decodes_.pop(); | |
90 } | |
91 | |
92 void VideoDecoderProxy::Delegate::ConvertFrame( | |
93 uint32_t decode_id, | |
94 media::VideoDecoder::Status status, | |
95 const scoped_refptr<media::VideoFrame>& frame) { | |
96 scoped_ptr<PendingFrame> pending_frame( | |
97 new PendingFrame(decode_id, gfx::Size())); | |
98 if (frame) { | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
You're using frame!=NULL as a proxy for status==kO
bbudge
2014/06/06 02:03:45
So:
if (status == kOk && frame && !frame->end_of_s
| |
99 pending_frame->size = frame->coded_size(); | |
100 pending_frame->pixels.resize(frame->coded_size().width() * | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
It's strange that you do the w*h*4 calc here _and_
bbudge
2014/06/06 02:03:45
Added a ctor to create an empty frame, and defer c
| |
101 frame->coded_size().height() * 4); | |
102 // Convert the decoded frame to ARGB pixels. | |
103 libyuv::I420ToARGB(frame->data(media::VideoFrame::kYPlane), | |
104 frame->stride(media::VideoFrame::kYPlane), | |
105 frame->data(media::VideoFrame::kUPlane), | |
106 frame->stride(media::VideoFrame::kUPlane), | |
107 frame->data(media::VideoFrame::kVPlane), | |
108 frame->stride(media::VideoFrame::kVPlane), | |
109 &pending_frame->pixels.front(), | |
110 frame->coded_size().width() * 4, | |
111 frame->coded_size().width(), | |
112 frame->coded_size().height()); | |
113 } | |
114 | |
115 main_message_loop_->PostTask(FROM_HERE, | |
116 base::Bind(&VideoDecoderProxy::ReceiveFrame, | |
117 proxy_, | |
118 status, | |
119 base::Passed(&pending_frame))); | |
120 | |
121 if (!pending_decodes_.empty()) | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
per comment at l.78, can this arise in the absence
bbudge
2014/06/06 02:03:45
Stop() and Reset() now clear pending frames first,
| |
122 Decode(); | |
123 } | |
124 | |
125 void VideoDecoderProxy::Delegate::Reset() { | |
126 decoder_->Reset(base::Bind(&VideoDecoderProxy::Delegate::OnResetComplete, | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
set some state so that decodes that arrive from no
bbudge
2014/06/06 02:03:44
No decodes can arrive until Reset completes becaus
| |
127 base::Unretained(this))); | |
128 } | |
129 | |
130 void VideoDecoderProxy::Delegate::OnResetComplete() { | |
131 // Cancel all remaining decodes, and notify the host so it can free the shm | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
is it obvious why this shouldn't have been done in
bbudge
2014/06/06 02:03:44
No, it should be done in Reset() as you point out.
| |
132 // buffers. We'll clear pending frames on the main thread. | |
133 while (!pending_decodes_.empty()) { | |
134 PendingDecode& next_decode = pending_decodes_.front(); | |
135 scoped_ptr<PendingFrame> pending_frame( | |
136 new PendingFrame(next_decode.decode_id, gfx::Size())); | |
137 main_message_loop_->PostTask(FROM_HERE, | |
138 base::Bind(&VideoDecoderProxy::ReceiveFrame, | |
139 proxy_, | |
140 media::VideoDecoder::kAborted, | |
141 base::Passed(&pending_frame))); | |
dmichael (off chromium)
2014/06/05 23:00:43
Any reason you can't pass a null pointer instead o
bbudge
2014/06/06 02:03:44
The frame has the decode_id, which is used to iden
| |
142 pending_decodes_.pop(); | |
143 } | |
144 main_message_loop_->PostTask( | |
145 FROM_HERE, base::Bind(&VideoDecoderProxy::OnResetComplete, proxy_)); | |
146 } | |
147 | |
148 void VideoDecoderProxy::Delegate::Destroy() { | |
149 DCHECK(decoder_); | |
150 decoder_->Stop(); | |
dmichael (off chromium)
2014/06/05 23:00:43
Would it be safe to instead put this in the destru
bbudge
2014/06/06 02:03:45
Stop can cause callbacks to run, and I'd rather no
| |
151 // By now, our owning VideoDecoderProxy has invalidated our weak_ptr to it. | |
152 } | |
153 | |
154 VideoDecoderProxy::VideoDecoderProxy(PepperVideoDecoderHost* host) | |
155 : state_(UNINITIALIZED), | |
156 host_(host), | |
157 media_message_loop_( | |
158 RenderThreadImpl::current()->GetMediaThreadMessageLoopProxy()), | |
159 context_provider_( | |
160 RenderThreadImpl::current()->SharedMainThreadContextProvider()), | |
161 num_pending_decodes_(0), | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
the logic around this member requires that Decode(
bbudge
2014/06/06 02:03:44
Done.
| |
162 weak_ptr_factory_(this) { | |
163 DCHECK(host_); | |
164 DCHECK(media_message_loop_); | |
165 DCHECK(context_provider_); | |
166 delegate_.reset(new Delegate(weak_ptr_factory_.GetWeakPtr())); | |
167 } | |
168 | |
169 VideoDecoderProxy::~VideoDecoderProxy() { | |
170 DCHECK(RenderThreadImpl::current()); | |
171 DCHECK(!host_); | |
172 // Delete any remaining video frames. | |
173 while (!pending_frames_.empty()) { | |
174 delete pending_frames_.front(); | |
175 pending_frames_.pop(); | |
176 } | |
177 // Delete any remaining textures. | |
178 TextureIdMap::iterator it = texture_id_map_.begin(); | |
179 for (; it != texture_id_map_.end(); ++it) | |
180 DeleteTexture(it->second); | |
181 | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
clear the map once done?
bbudge
2014/06/06 02:03:44
It will be destroyed once we exit the body.
| |
182 FlushCommandBuffer(); | |
183 } | |
184 | |
185 void VideoDecoderProxy::Initialize(media::VideoCodecProfile profile) { | |
186 DCHECK(RenderThreadImpl::current()); | |
187 DCHECK_EQ(state_, UNINITIALIZED); | |
188 media::VideoCodec codec = media::kUnknownVideoCodec; | |
189 if (profile <= media::H264PROFILE_MAX) | |
190 codec = media::kCodecH264; | |
191 else if (profile <= media::VP8PROFILE_MAX) | |
192 codec = media::kCodecVP8; | |
193 DCHECK_NE(codec, media::kUnknownVideoCodec); | |
194 | |
195 media::VideoDecoderConfig config( | |
196 codec, | |
197 profile, | |
198 media::VideoFrame::YV12, | |
199 gfx::Size(32, 24), // Small sizes that won't fail. | |
200 gfx::Rect(32, 24), | |
201 gfx::Size(32, 24), | |
202 NULL /* extra_data */, | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
durr, does this really work?
e.g. does the h264 te
bbudge
2014/06/06 02:03:45
I don't know. I'll have to try it. I have no idea
| |
203 0 /* extra_data_size */, | |
204 false /* decryption */); | |
205 | |
206 scoped_ptr<media::VideoDecoder> decoder( | |
207 new media::FFmpegVideoDecoder(media_message_loop_)); | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
Why not VpxVideoDecoder (for VP9) or one of the de
bbudge
2014/06/06 02:03:45
You're right. I moved VideoDecoder construction in
| |
208 | |
209 media_message_loop_->PostTask( | |
210 FROM_HERE, | |
211 base::Bind(&VideoDecoderProxy::Delegate::Initialize, | |
212 base::Unretained(delegate_.get()), | |
213 base::Passed(&decoder), | |
214 config)); | |
215 state_ = DECODING; | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
shouldn't this transition only happen in the OK ca
bbudge
2014/06/06 02:03:45
Yep.
| |
216 } | |
217 | |
218 void VideoDecoderProxy::Decode(uint32_t decode_id, | |
219 const uint8_t* buffer, | |
220 uint32_t size) { | |
221 DCHECK(RenderThreadImpl::current()); | |
222 DCHECK_EQ(state_, DECODING); | |
223 | |
224 num_pending_decodes_++; | |
225 | |
226 media_message_loop_->PostTask( | |
227 FROM_HERE, | |
228 base::Bind(&VideoDecoderProxy::Delegate::ReceiveBuffer, | |
229 base::Unretained(delegate_.get()), | |
230 decode_id, | |
231 media::DecoderBuffer::CopyFrom(buffer, size))); | |
232 } | |
233 | |
234 void VideoDecoderProxy::AssignTextures( | |
235 const std::vector<uint32_t>& texture_ids) { | |
236 DCHECK(RenderThreadImpl::current()); | |
237 DCHECK_EQ(state_, DECODING); | |
238 DCHECK(texture_ids.size()); | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
DCHECK(texture_id_map_.empty());
?
dmichael (off chromium)
2014/06/05 23:00:44
With a "!", of course :)
bbudge
2014/06/06 02:03:45
I don't think so. We AssignTextures when the video
| |
239 DCHECK_EQ(texture_ids.size(), pending_texture_mailboxes_.size()); | |
240 uint32_t num_textures = static_cast<GLuint>(texture_ids.size()); | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
checked_cast to avoid silent overflow
bbudge
2014/06/06 02:03:45
Done.
| |
241 std::vector<uint32_t> local_texture_ids(num_textures); | |
242 gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL(); | |
243 gles2->GenTextures(num_textures, &local_texture_ids.front()); | |
244 for (uint32_t i = 0; i < num_textures; i++) { | |
245 gles2->ActiveTexture(GL_TEXTURE0); | |
246 gles2->BindTexture(GL_TEXTURE_2D, local_texture_ids[i]); | |
247 gles2->ConsumeTextureCHROMIUM(GL_TEXTURE_2D, | |
248 pending_texture_mailboxes_[i].name); | |
249 // Map the plugin texture id to the local texture id. | |
250 texture_id_map_.insert( | |
251 std::make_pair(texture_ids[i], local_texture_ids[i])); | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
since you're ignoring return value you may as well
bbudge
2014/06/06 02:03:44
Done.
| |
252 } | |
253 pending_texture_mailboxes_.clear(); | |
254 available_textures_.insert( | |
255 available_textures_.end(), texture_ids.begin(), texture_ids.end()); | |
256 SendPictures(); | |
257 } | |
258 | |
259 void VideoDecoderProxy::RecycleTexture(uint32_t texture_id) { | |
260 DCHECK(RenderThreadImpl::current()); | |
261 if (textures_to_dismiss_.find(texture_id) != textures_to_dismiss_.end()) { | |
262 DismissTexture(texture_id); | |
263 } else if (texture_id_map_.find(texture_id) != texture_id_map_.end()) { | |
264 available_textures_.push_back(texture_id); | |
265 SendPictures(); | |
266 } else { | |
267 NOTREACHED(); | |
268 } | |
269 } | |
270 | |
271 void VideoDecoderProxy::Flush() { | |
272 DCHECK(RenderThreadImpl::current()); | |
273 DCHECK_EQ(state_, DECODING); | |
274 state_ = FLUSHING; | |
275 } | |
276 | |
277 void VideoDecoderProxy::Reset() { | |
278 DCHECK(RenderThreadImpl::current()); | |
279 DCHECK_EQ(state_, DECODING); | |
280 state_ = RESETTING; | |
281 media_message_loop_->PostTask(FROM_HERE, | |
282 base::Bind(&VideoDecoderProxy::Delegate::Reset, | |
283 base::Unretained(delegate_.get()))); | |
284 } | |
285 | |
286 void VideoDecoderProxy::Destroy() { | |
287 DCHECK(RenderThreadImpl::current()); | |
288 DCHECK(host_); | |
289 host_ = NULL; | |
290 // Cut the delegate loose. | |
291 weak_ptr_factory_.InvalidateWeakPtrs(); | |
292 media_message_loop_->PostTask( | |
293 FROM_HERE, | |
294 base::Bind(&VideoDecoderProxy::Delegate::Destroy, | |
295 base::Owned(delegate_.release()))); | |
296 | |
297 delete this; | |
dmichael (off chromium)
2014/06/05 23:00:43
Can we just do all this in a destructor? (aside fr
bbudge
2014/06/06 02:03:44
That's too late. There might be pending callbacks
| |
298 } | |
299 | |
300 void VideoDecoderProxy::OnPipelineStatus(media::PipelineStatus status) { | |
301 DCHECK(RenderThreadImpl::current()); | |
302 DCHECK(host_); | |
303 | |
304 int32_t result; | |
305 switch (status) { | |
306 case media::PIPELINE_OK: | |
307 result = PP_OK; | |
308 break; | |
309 case media::DECODER_ERROR_NOT_SUPPORTED: | |
310 result = PP_ERROR_NOTSUPPORTED; | |
311 break; | |
312 default: | |
313 result = PP_ERROR_FAILED; | |
314 break; | |
315 } | |
316 host_->OnInitializeComplete(result); | |
317 } | |
318 | |
319 void VideoDecoderProxy::ReceiveFrame(media::VideoDecoder::Status status, | |
320 scoped_ptr<PendingFrame> frame) { | |
321 DCHECK(RenderThreadImpl::current()); | |
322 DCHECK(host_); | |
323 | |
324 num_pending_decodes_--; | |
325 | |
326 if (frame->pixels.size()) { | |
327 if (texture_size_ != frame->size) { | |
328 // If the size has changed, all current textures must be dismissed. Add | |
329 // all textures to |textures_to_dismiss_| and dismiss any that aren't in | |
330 // use by the plugin. We dismiss the rest as they are recycled. | |
331 for (TextureIdMap::const_iterator it = texture_id_map_.begin(); | |
332 it != texture_id_map_.end(); | |
333 ++it) { | |
334 textures_to_dismiss_.insert(it->second); | |
335 } | |
336 for (std::vector<uint32_t>::const_iterator it = | |
337 available_textures_.begin(); | |
338 it != available_textures_.end(); | |
339 ++it) { | |
340 DismissTexture(*it); | |
341 } | |
342 available_textures_.clear(); | |
343 FlushCommandBuffer(); | |
344 | |
345 DCHECK(pending_texture_mailboxes_.empty()); | |
346 const uint32_t num_textures = 8; | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
o rly 8?
bbudge
2014/06/06 02:03:44
Yeah, this is just a guess. It seems like 1 might
| |
347 for (uint32_t i = 0; i < num_textures; i++) | |
348 pending_texture_mailboxes_.push_back(gpu::Mailbox::Generate()); | |
349 | |
350 host_->RequestTextures( | |
351 num_textures, frame->size, GL_TEXTURE_2D, pending_texture_mailboxes_); | |
352 texture_size_ = frame->size; | |
353 } | |
354 | |
355 pending_frames_.push(frame.release()); | |
356 SendPictures(); | |
357 } else { | |
358 host_->NotifyEndOfBitstreamBuffer(frame->decode_id); | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
In case of error this is wrong.
bbudge
2014/06/06 02:03:44
I don't see why. With VideoDecodeAccelerator we no
| |
359 } | |
360 | |
361 switch (status) { | |
362 case media::VideoDecoder::kOk: | |
363 case media::VideoDecoder::kAborted: | |
364 // This is not necessarily an error. | |
365 case media::VideoDecoder::kNotEnoughData: | |
366 break; | |
367 case media::VideoDecoder::kDecodeError: | |
368 case media::VideoDecoder::kDecryptError: | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
_decrypt_ error should be unreachable, right?
bbudge
2014/06/06 02:03:44
Done.
| |
369 host_->NotifyError(PP_ERROR_RESOURCE_FAILED); | |
370 break; | |
371 // No default case, to catch unhandled status values. | |
372 } | |
373 } | |
374 | |
375 void VideoDecoderProxy::SendPictures() { | |
376 DCHECK(RenderThreadImpl::current()); | |
377 DCHECK(host_); | |
378 | |
379 while (!pending_frames_.empty() && !available_textures_.empty()) { | |
380 scoped_ptr<PendingFrame> frame(pending_frames_.front()); | |
381 pending_frames_.pop(); | |
382 | |
383 uint32_t texture_id = available_textures_.back(); | |
384 available_textures_.pop_back(); | |
385 | |
386 uint32_t local_texture_id = texture_id_map_[texture_id]; | |
387 gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL(); | |
388 gles2->ActiveTexture(GL_TEXTURE0); | |
389 gles2->BindTexture(GL_TEXTURE_2D, local_texture_id); | |
390 gles2->TexImage2D(GL_TEXTURE_2D, | |
391 0, | |
392 GL_RGBA, | |
393 texture_size_.width(), | |
394 texture_size_.height(), | |
395 0, | |
396 GL_RGBA, | |
397 GL_UNSIGNED_BYTE, | |
398 &frame->pixels.front()); | |
399 | |
400 host_->NotifyEndOfBitstreamBuffer(frame->decode_id); | |
401 host_->PictureReady(media::Picture(texture_id, frame->decode_id)); | |
402 } | |
403 | |
404 FlushCommandBuffer(); | |
405 | |
406 if (state_ == FLUSHING && !num_pending_decodes_ && pending_frames_.empty()) { | |
407 state_ = DECODING; | |
408 host_->NotifyFlushDone(); | |
409 } | |
410 } | |
411 | |
412 void VideoDecoderProxy::OnResetComplete() { | |
413 DCHECK(RenderThreadImpl::current()); | |
414 DCHECK(host_); | |
415 | |
416 while (!pending_frames_.empty()) { | |
417 scoped_ptr<PendingFrame> frame(pending_frames_.front()); | |
418 host_->NotifyEndOfBitstreamBuffer(frame->decode_id); | |
Ami GONE FROM CHROMIUM
2014/06/05 00:06:24
Not sure this is necessary, FWIW.
bbudge
2014/06/06 02:03:44
It makes it a little simpler in the host, since I
| |
419 pending_frames_.pop(); | |
420 } | |
421 | |
422 state_ = DECODING; | |
423 host_->NotifyResetDone(); | |
424 } | |
425 | |
426 void VideoDecoderProxy::DismissTexture(uint32_t texture_id) { | |
427 DCHECK(host_); | |
428 textures_to_dismiss_.erase(texture_id); | |
429 DCHECK(texture_id_map_.find(texture_id) != texture_id_map_.end()); | |
430 DeleteTexture(texture_id_map_[texture_id]); | |
431 texture_id_map_.erase(texture_id); | |
432 host_->DismissPictureBuffer(texture_id); | |
433 } | |
434 | |
435 void VideoDecoderProxy::DeleteTexture(uint32_t texture_id) { | |
436 gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL(); | |
437 gles2->DeleteTextures(1, &texture_id); | |
438 } | |
439 | |
440 void VideoDecoderProxy::FlushCommandBuffer() { | |
441 DCHECK(RenderThreadImpl::current()); | |
442 context_provider_->ContextGL()->Flush(); | |
443 } | |
444 | |
445 } // namespace content | |
OLD | NEW |