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