Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / content / common / gpu / media / rendering_helper.cc
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/common/gpu/media/rendering_helper.h"
6
7 #include "base/bind.h"
8 #include "base/mac/scoped_nsautorelease_pool.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/strings/stringize_macros.h"
11 #include "base/synchronization/waitable_event.h"
12 #include "ui/gl/gl_context.h"
13 #include "ui/gl/gl_context_stub_with_extensions.h"
14 #include "ui/gl/gl_implementation.h"
15 #include "ui/gl/gl_surface.h"
16
17 #ifdef GL_VARIANT_GLX
18 typedef GLXWindow NativeWindowType;
19 struct XFreeDeleter {
20   void operator()(void* x) const { ::XFree(x); }
21 };
22 #else  // EGL
23 typedef EGLNativeWindowType NativeWindowType;
24 #endif
25
26 // Helper for Shader creation.
27 static void CreateShader(GLuint program,
28                          GLenum type,
29                          const char* source,
30                          int size) {
31   GLuint shader = glCreateShader(type);
32   glShaderSource(shader, 1, &source, &size);
33   glCompileShader(shader);
34   int result = GL_FALSE;
35   glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
36   if (!result) {
37     char log[4096];
38     glGetShaderInfoLog(shader, arraysize(log), NULL, log);
39     LOG(FATAL) << log;
40   }
41   glAttachShader(program, shader);
42   glDeleteShader(shader);
43   CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
44 }
45
46 namespace content {
47
48 RenderingHelperParams::RenderingHelperParams() {}
49
50 RenderingHelperParams::~RenderingHelperParams() {}
51
52 static const gfx::GLImplementation kGLImplementation =
53 #if defined(GL_VARIANT_GLX)
54     gfx::kGLImplementationDesktopGL;
55 #elif defined(GL_VARIANT_EGL)
56     gfx::kGLImplementationEGLGLES2;
57 #else
58     -1;
59 #error "Unknown GL implementation."
60 #endif
61
62 RenderingHelper::RenderingHelper() {
63   Clear();
64 }
65
66 RenderingHelper::~RenderingHelper() {
67   CHECK_EQ(window_dimensions_.size(), 0U) <<
68     "Must call UnInitialize before dtor.";
69   Clear();
70 }
71
72 void RenderingHelper::MakeCurrent(int window_id) {
73 #if GL_VARIANT_GLX
74   if (window_id < 0) {
75     CHECK(glXMakeContextCurrent(x_display_, GLX_NONE, GLX_NONE, NULL));
76   } else {
77     CHECK(glXMakeContextCurrent(
78         x_display_, x_windows_[window_id], x_windows_[window_id], gl_context_));
79   }
80 #else  // EGL
81   if (window_id < 0) {
82     CHECK(eglMakeCurrent(gl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
83                          EGL_NO_CONTEXT)) << eglGetError();
84   } else {
85     CHECK(eglMakeCurrent(gl_display_, gl_surfaces_[window_id],
86                          gl_surfaces_[window_id], gl_context_))
87         << eglGetError();
88   }
89 #endif
90 }
91
92 void RenderingHelper::Initialize(const RenderingHelperParams& params,
93                                  base::WaitableEvent* done) {
94   // Use window_dimensions_.size() != 0 as a proxy for the class having already
95   // been Initialize()'d, and UnInitialize() before continuing.
96   if (window_dimensions_.size()) {
97     base::WaitableEvent done(false, false);
98     UnInitialize(&done);
99     done.Wait();
100   }
101
102   gfx::InitializeStaticGLBindings(kGLImplementation);
103   scoped_refptr<gfx::GLContextStubWithExtensions> stub_context(
104       new gfx::GLContextStubWithExtensions());
105
106   CHECK_GT(params.window_dimensions.size(), 0U);
107   CHECK_EQ(params.frame_dimensions.size(), params.window_dimensions.size());
108   window_dimensions_ = params.window_dimensions;
109   frame_dimensions_ = params.frame_dimensions;
110   render_as_thumbnails_ = params.render_as_thumbnails;
111   message_loop_ = base::MessageLoop::current();
112   CHECK_GT(params.num_windows, 0);
113
114 #if GL_VARIANT_GLX
115   x_display_ = base::MessagePumpForUI::GetDefaultXDisplay();
116   CHECK(x_display_);
117   CHECK(glXQueryVersion(x_display_, NULL, NULL));
118   const int fbconfig_attr[] = {
119     GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
120     GLX_RENDER_TYPE, GLX_RGBA_BIT,
121     GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
122     GLX_BIND_TO_TEXTURE_RGB_EXT, GL_TRUE,
123     GLX_DOUBLEBUFFER, True,
124     GL_NONE,
125   };
126   int num_fbconfigs;
127   scoped_ptr<GLXFBConfig, XFreeDeleter> glx_fb_configs(
128       glXChooseFBConfig(x_display_, DefaultScreen(x_display_), fbconfig_attr,
129                         &num_fbconfigs));
130   CHECK(glx_fb_configs.get());
131   CHECK_GT(num_fbconfigs, 0);
132   x_visual_ = glXGetVisualFromFBConfig(x_display_, glx_fb_configs.get()[0]);
133   CHECK(x_visual_);
134   gl_context_ = glXCreateContext(x_display_, x_visual_, 0, true);
135   CHECK(gl_context_);
136   stub_context->AddExtensionsString(
137       reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)));
138   stub_context->SetGLVersionString(
139       reinterpret_cast<const char*>(glGetString(GL_VERSION)));
140
141 #else // EGL
142   EGLNativeDisplayType native_display;
143
144 #if defined(OS_WIN)
145   native_display = EGL_DEFAULT_DISPLAY;
146 #else
147   x_display_ = base::MessagePumpForUI::GetDefaultXDisplay();
148   CHECK(x_display_);
149   native_display = x_display_;
150 #endif
151
152   gl_display_ = eglGetDisplay(native_display);
153   CHECK(gl_display_);
154   CHECK(eglInitialize(gl_display_, NULL, NULL)) << glGetError();
155
156   static EGLint rgba8888[] = {
157     EGL_RED_SIZE, 8,
158     EGL_GREEN_SIZE, 8,
159     EGL_BLUE_SIZE, 8,
160     EGL_ALPHA_SIZE, 8,
161     EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
162     EGL_NONE,
163   };
164   EGLConfig egl_config;
165   int num_configs;
166   CHECK(eglChooseConfig(gl_display_, rgba8888, &egl_config, 1, &num_configs))
167       << eglGetError();
168   CHECK_GE(num_configs, 1);
169   static EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
170   gl_context_ = eglCreateContext(
171       gl_display_, egl_config, EGL_NO_CONTEXT, context_attribs);
172   CHECK_NE(gl_context_, EGL_NO_CONTEXT) << eglGetError();
173   stub_context->AddExtensionsString(
174       reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)));
175   stub_context->AddExtensionsString(
176       eglQueryString(gl_display_, EGL_EXTENSIONS));
177   stub_context->SetGLVersionString(
178       reinterpret_cast<const char*>(glGetString(GL_VERSION)));
179 #endif
180
181   // Per-window/surface X11 & EGL initialization.
182   for (int i = 0; i < params.num_windows; ++i) {
183     // Arrange X windows whimsically, with some padding.
184     int j = i % window_dimensions_.size();
185     int width = window_dimensions_[j].width();
186     int height = window_dimensions_[j].height();
187     CHECK_GT(width, 0);
188     CHECK_GT(height, 0);
189     int top_left_x = (width + 20) * (i % 4);
190     int top_left_y = (height + 12) * (i % 3);
191
192 #if defined(OS_WIN)
193     NativeWindowType window =
194         CreateWindowEx(0, L"Static", L"VideoDecodeAcceleratorTest",
195                        WS_OVERLAPPEDWINDOW | WS_VISIBLE, top_left_x,
196                        top_left_y, width, height, NULL, NULL, NULL,
197                        NULL);
198     CHECK(window != NULL);
199     windows_.push_back(window);
200 #else
201     int depth = DefaultDepth(x_display_, DefaultScreen(x_display_));
202
203 #if defined(GL_VARIANT_GLX)
204     CHECK_EQ(depth, x_visual_->depth);
205 #endif
206
207     XSetWindowAttributes window_attributes;
208     window_attributes.background_pixel =
209         BlackPixel(x_display_, DefaultScreen(x_display_));
210     window_attributes.override_redirect = true;
211
212     NativeWindowType window = XCreateWindow(
213         x_display_, DefaultRootWindow(x_display_),
214         top_left_x, top_left_y, width, height,
215         0 /* border width */,
216         depth, CopyFromParent /* class */, CopyFromParent /* visual */,
217         (CWBackPixel | CWOverrideRedirect), &window_attributes);
218     XStoreName(x_display_, window, "VideoDecodeAcceleratorTest");
219     XSelectInput(x_display_, window, ExposureMask);
220     XMapWindow(x_display_, window);
221     x_windows_.push_back(window);
222 #endif
223
224 #if GL_VARIANT_EGL
225     EGLSurface egl_surface =
226         eglCreateWindowSurface(gl_display_, egl_config, window, NULL);
227     gl_surfaces_.push_back(egl_surface);
228     CHECK_NE(egl_surface, EGL_NO_SURFACE);
229 #endif
230     MakeCurrent(i);
231   }
232
233   // Must be done after a context is made current.
234   gfx::InitializeDynamicGLBindings(kGLImplementation, stub_context.get());
235
236   if (render_as_thumbnails_) {
237     CHECK_EQ(window_dimensions_.size(), 1U);
238
239     GLint max_texture_size;
240     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
241     CHECK_GE(max_texture_size, params.thumbnails_page_size.width());
242     CHECK_GE(max_texture_size, params.thumbnails_page_size.height());
243
244     thumbnails_fbo_size_ = params.thumbnails_page_size;
245     thumbnail_size_ = params.thumbnail_size;
246
247     glGenFramebuffersEXT(1, &thumbnails_fbo_id_);
248     glGenTextures(1, &thumbnails_texture_id_);
249     glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
250     glTexImage2D(GL_TEXTURE_2D,
251                  0,
252                  GL_RGB,
253                  thumbnails_fbo_size_.width(),
254                  thumbnails_fbo_size_.height(),
255                  0,
256                  GL_RGB,
257                  GL_UNSIGNED_SHORT_5_6_5,
258                  NULL);
259     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
260     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
261     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
262     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
263
264     glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
265     glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
266                               GL_COLOR_ATTACHMENT0,
267                               GL_TEXTURE_2D,
268                               thumbnails_texture_id_,
269                               0);
270
271     GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
272     CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status;
273     glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
274     glClear(GL_COLOR_BUFFER_BIT);
275     glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
276   }
277
278   // These vertices and texture coords. map (0,0) in the texture to the
279   // bottom left of the viewport.  Since we get the video frames with the
280   // the top left at (0,0) we need to flip the texture y coordinate
281   // in the vertex shader for this to be rendered the right way up.
282   // In the case of thumbnail rendering we use the same vertex shader
283   // to render the FBO the screen, where we do not want this flipping.
284   static const float kVertices[] =
285       { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, };
286   static const float kTextureCoords[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
287   static const char kVertexShader[] = STRINGIZE(
288       varying vec2 interp_tc;
289       attribute vec4 in_pos;
290       attribute vec2 in_tc;
291       uniform bool tex_flip;
292       void main() {
293         if (tex_flip)
294           interp_tc = vec2(in_tc.x, 1.0 - in_tc.y);
295         else
296           interp_tc = in_tc;
297         gl_Position = in_pos;
298       });
299
300 #if GL_VARIANT_EGL
301   static const char kFragmentShader[] =
302       "#extension GL_OES_EGL_image_external : enable\n"
303       "precision mediump float;\n"
304       "varying vec2 interp_tc;\n"
305       "uniform sampler2D tex;\n"
306       "#ifdef GL_OES_EGL_image_external\n"
307       "uniform samplerExternalOES tex_external;\n"
308       "#endif\n"
309       "void main() {\n"
310       "  vec4 color = texture2D(tex, interp_tc);\n"
311       "#ifdef GL_OES_EGL_image_external\n"
312       "  color += texture2D(tex_external, interp_tc);\n"
313       "#endif\n"
314       "  gl_FragColor = color;\n"
315       "}\n";
316 #else
317   static const char kFragmentShader[] = STRINGIZE(
318       varying vec2 interp_tc;
319       uniform sampler2D tex;
320       void main() {
321         gl_FragColor = texture2D(tex, interp_tc);
322       });
323 #endif
324   program_ = glCreateProgram();
325   CreateShader(
326       program_, GL_VERTEX_SHADER, kVertexShader, arraysize(kVertexShader));
327   CreateShader(program_,
328                GL_FRAGMENT_SHADER,
329                kFragmentShader,
330                arraysize(kFragmentShader));
331   glLinkProgram(program_);
332   int result = GL_FALSE;
333   glGetProgramiv(program_, GL_LINK_STATUS, &result);
334   if (!result) {
335     char log[4096];
336     glGetShaderInfoLog(program_, arraysize(log), NULL, log);
337     LOG(FATAL) << log;
338   }
339   glUseProgram(program_);
340   glDeleteProgram(program_);
341
342   glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
343   glUniform1i(glGetUniformLocation(program_, "tex"), 0);
344   GLint tex_external = glGetUniformLocation(program_, "tex_external");
345   if (tex_external != -1) {
346     glUniform1i(tex_external, 1);
347   }
348   int pos_location = glGetAttribLocation(program_, "in_pos");
349   glEnableVertexAttribArray(pos_location);
350   glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
351   int tc_location = glGetAttribLocation(program_, "in_tc");
352   glEnableVertexAttribArray(tc_location);
353   glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords);
354   done->Signal();
355 }
356
357 void RenderingHelper::UnInitialize(base::WaitableEvent* done) {
358   CHECK_EQ(base::MessageLoop::current(), message_loop_);
359   if (render_as_thumbnails_) {
360     glDeleteTextures(1, &thumbnails_texture_id_);
361     glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_);
362   }
363 #if GL_VARIANT_GLX
364
365   glXDestroyContext(x_display_, gl_context_);
366 #else // EGL
367   MakeCurrent(-1);
368   CHECK(eglDestroyContext(gl_display_, gl_context_));
369   for (size_t i = 0; i < gl_surfaces_.size(); ++i)
370     CHECK(eglDestroySurface(gl_display_, gl_surfaces_[i]));
371   CHECK(eglTerminate(gl_display_));
372 #endif
373   gfx::ClearGLBindings();
374   Clear();
375   done->Signal();
376 }
377
378 void RenderingHelper::CreateTexture(int window_id,
379                                     uint32 texture_target,
380                                     uint32* texture_id,
381                                     base::WaitableEvent* done) {
382   if (base::MessageLoop::current() != message_loop_) {
383     message_loop_->PostTask(
384         FROM_HERE,
385         base::Bind(&RenderingHelper::CreateTexture, base::Unretained(this),
386                    window_id, texture_target, texture_id, done));
387     return;
388   }
389   MakeCurrent(window_id);
390   glGenTextures(1, texture_id);
391   glBindTexture(texture_target, *texture_id);
392   int dimensions_id = window_id % frame_dimensions_.size();
393   if (texture_target == GL_TEXTURE_2D) {
394     glTexImage2D(GL_TEXTURE_2D,
395                  0,
396                  GL_RGBA,
397                  frame_dimensions_[dimensions_id].width(),
398                  frame_dimensions_[dimensions_id].height(),
399                  0,
400                  GL_RGBA,
401                  GL_UNSIGNED_BYTE,
402                  NULL);
403   }
404   glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
405   glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
406   // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
407   glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
408   glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
409   CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
410   CHECK(texture_id_to_surface_index_.insert(
411       std::make_pair(*texture_id, window_id)).second);
412   done->Signal();
413 }
414
415 void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) {
416   CHECK_EQ(base::MessageLoop::current(), message_loop_);
417   size_t window_id = texture_id_to_surface_index_[texture_id];
418   MakeCurrent(window_id);
419
420   int dimensions_id = window_id % window_dimensions_.size();
421   int width = window_dimensions_[dimensions_id].width();
422   int height = window_dimensions_[dimensions_id].height();
423
424   if (render_as_thumbnails_) {
425     glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
426     const int thumbnails_in_row =
427         thumbnails_fbo_size_.width() / thumbnail_size_.width();
428     const int thumbnails_in_column =
429         thumbnails_fbo_size_.height() / thumbnail_size_.height();
430     const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column;
431     const int col = frame_count_ % thumbnails_in_row;
432     const int x = col * thumbnail_size_.width();
433     const int y = row * thumbnail_size_.height();
434
435     glViewport(x, y, thumbnail_size_.width(), thumbnail_size_.height());
436     glScissor(x, y, thumbnail_size_.width(), thumbnail_size_.height());
437     glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
438   } else {
439     glViewport(0, 0, width, height);
440     glScissor(0, 0, width, height);
441     glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1);
442   }
443
444   // Unbound texture samplers default to (0, 0, 0, 1).  Use this fact to switch
445   // between GL_TEXTURE_2D and GL_TEXTURE_EXTERNAL_OES as appopriate.
446   if (texture_target == GL_TEXTURE_2D) {
447     glActiveTexture(GL_TEXTURE0 + 0);
448     glBindTexture(GL_TEXTURE_2D, texture_id);
449     glActiveTexture(GL_TEXTURE0 + 1);
450     glBindTexture(texture_target, 0);
451   } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) {
452     glActiveTexture(GL_TEXTURE0 + 0);
453     glBindTexture(GL_TEXTURE_2D, 0);
454     glActiveTexture(GL_TEXTURE0 + 1);
455     glBindTexture(texture_target, texture_id);
456   }
457   glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
458   CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
459
460   ++frame_count_;
461
462   if (render_as_thumbnails_) {
463     // Copy from FBO to screen
464     glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1);
465     glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
466     glViewport(0, 0, width, height);
467     glScissor(0, 0, width, height);
468     glActiveTexture(GL_TEXTURE0 + 0);
469     glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
470     glActiveTexture(GL_TEXTURE0 + 1);
471     glBindTexture(texture_target, 0);
472     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
473   }
474
475 #if GL_VARIANT_GLX
476   glXSwapBuffers(x_display_, x_windows_[window_id]);
477 #else  // EGL
478   eglSwapBuffers(gl_display_, gl_surfaces_[window_id]);
479   CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS);
480 #endif
481 }
482
483 void RenderingHelper::DeleteTexture(uint32 texture_id) {
484   glDeleteTextures(1, &texture_id);
485   CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
486 }
487
488 void* RenderingHelper::GetGLDisplay() {
489 #if GL_VARIANT_GLX
490   return x_display_;
491 #else  // EGL
492   return gl_display_;
493 #endif
494 }
495
496 void RenderingHelper::Clear() {
497   window_dimensions_.clear();
498   frame_dimensions_.clear();
499   texture_id_to_surface_index_.clear();
500   message_loop_ = NULL;
501   gl_context_ = NULL;
502 #if GL_VARIANT_EGL
503   gl_display_ = EGL_NO_DISPLAY;
504   gl_surfaces_.clear();
505 #endif
506   render_as_thumbnails_ = false;
507   frame_count_ = 0;
508   thumbnails_fbo_id_ = 0;
509   thumbnails_texture_id_ = 0;
510
511 #if defined(OS_WIN)
512   for (size_t i = 0; i < windows_.size(); ++i) {
513     DestroyWindow(windows_[i]);
514   }
515   windows_.clear();
516 #else
517   // Destroy resources acquired in Initialize, in reverse-acquisition order.
518   for (size_t i = 0; i < x_windows_.size(); ++i) {
519     CHECK(XUnmapWindow(x_display_, x_windows_[i]));
520     CHECK(XDestroyWindow(x_display_, x_windows_[i]));
521   }
522   // Mimic newly created object.
523   x_display_ = NULL;
524   x_windows_.clear();
525 #endif
526 }
527
528 void RenderingHelper::GetThumbnailsAsRGB(std::vector<unsigned char>* rgb,
529                                          bool* alpha_solid,
530                                          base::WaitableEvent* done) {
531   CHECK(render_as_thumbnails_);
532
533   const size_t num_pixels = thumbnails_fbo_size_.GetArea();
534   std::vector<unsigned char> rgba;
535   rgba.resize(num_pixels * 4);
536   glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
537   glPixelStorei(GL_PACK_ALIGNMENT, 1);
538   // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support.
539   glReadPixels(0,
540                0,
541                thumbnails_fbo_size_.width(),
542                thumbnails_fbo_size_.height(),
543                GL_RGBA,
544                GL_UNSIGNED_BYTE,
545                &rgba[0]);
546   glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
547   rgb->resize(num_pixels * 3);
548   // Drop the alpha channel, but check as we go that it is all 0xff.
549   bool solid = true;
550   unsigned char* rgb_ptr = &((*rgb)[0]);
551   unsigned char* rgba_ptr = &rgba[0];
552   for (size_t i = 0; i < num_pixels; ++i) {
553     *rgb_ptr++ = *rgba_ptr++;
554     *rgb_ptr++ = *rgba_ptr++;
555     *rgb_ptr++ = *rgba_ptr++;
556     solid = solid && (*rgba_ptr == 0xff);
557     rgba_ptr++;
558   }
559   *alpha_solid = solid;
560
561   done->Signal();
562 }
563
564 }  // namespace content