| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/common/gpu/media/rendering_helper.h" |
| |
| #include "base/bind.h" |
| #include "base/mac/scoped_nsautorelease_pool.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/stringize_macros.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "ui/gl/gl_context.h" |
| #include "ui/gl/gl_context_stub.h" |
| #include "ui/gl/gl_implementation.h" |
| #include "ui/gl/gl_surface.h" |
| |
| #ifdef GL_VARIANT_GLX |
| typedef GLXWindow NativeWindowType; |
| struct ScopedPtrXFree { |
| void operator()(void* x) const { ::XFree(x); } |
| }; |
| #else // EGL |
| typedef EGLNativeWindowType NativeWindowType; |
| #endif |
| |
| // Helper for Shader creation. |
| static void CreateShader(GLuint program, |
| GLenum type, |
| const char* source, |
| int size) { |
| GLuint shader = glCreateShader(type); |
| glShaderSource(shader, 1, &source, &size); |
| glCompileShader(shader); |
| int result = GL_FALSE; |
| glGetShaderiv(shader, GL_COMPILE_STATUS, &result); |
| if (!result) { |
| char log[4096]; |
| glGetShaderInfoLog(shader, arraysize(log), NULL, log); |
| LOG(FATAL) << log; |
| } |
| glAttachShader(program, shader); |
| glDeleteShader(shader); |
| CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); |
| } |
| |
| namespace { |
| |
| // Lightweight GLContext stub implementation that returns a constructed |
| // extensions string. We use this to create a context that we can use to |
| // initialize GL extensions with, without actually creating a platform context. |
| class GLContextStubWithExtensions : public gfx::GLContextStub { |
| public: |
| GLContextStubWithExtensions() {} |
| virtual std::string GetExtensions() OVERRIDE; |
| |
| void AddExtensionsString(const char* extensions); |
| |
| protected: |
| virtual ~GLContextStubWithExtensions() {} |
| |
| private: |
| std::string extensions_; |
| |
| DISALLOW_COPY_AND_ASSIGN(GLContextStubWithExtensions); |
| }; |
| |
| void GLContextStubWithExtensions::AddExtensionsString(const char* extensions) { |
| if (extensions == NULL) |
| return; |
| |
| if (extensions_.size() != 0) |
| extensions_ += ' '; |
| extensions_ += extensions; |
| } |
| |
| std::string GLContextStubWithExtensions::GetExtensions() { |
| return extensions_; |
| } |
| |
| } // anonymous |
| |
| namespace content { |
| |
| RenderingHelperParams::RenderingHelperParams() {} |
| |
| RenderingHelperParams::~RenderingHelperParams() {} |
| |
| static const gfx::GLImplementation kGLImplementation = |
| #if defined(GL_VARIANT_GLX) |
| gfx::kGLImplementationDesktopGL; |
| #elif defined(GL_VARIANT_EGL) |
| gfx::kGLImplementationEGLGLES2; |
| #else |
| -1; |
| #error "Unknown GL implementation." |
| #endif |
| |
| RenderingHelper::RenderingHelper() { |
| Clear(); |
| } |
| |
| RenderingHelper::~RenderingHelper() { |
| CHECK_EQ(window_dimensions_.size(), 0U) << |
| "Must call UnInitialize before dtor."; |
| Clear(); |
| } |
| |
| void RenderingHelper::MakeCurrent(int window_id) { |
| #if GL_VARIANT_GLX |
| if (window_id < 0) { |
| CHECK(glXMakeContextCurrent(x_display_, GLX_NONE, GLX_NONE, NULL)); |
| } else { |
| CHECK(glXMakeContextCurrent( |
| x_display_, x_windows_[window_id], x_windows_[window_id], gl_context_)); |
| } |
| #else // EGL |
| if (window_id < 0) { |
| CHECK(eglMakeCurrent(gl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, |
| EGL_NO_CONTEXT)) << eglGetError(); |
| } else { |
| CHECK(eglMakeCurrent(gl_display_, gl_surfaces_[window_id], |
| gl_surfaces_[window_id], gl_context_)) |
| << eglGetError(); |
| } |
| #endif |
| } |
| |
| void RenderingHelper::Initialize(const RenderingHelperParams& params, |
| base::WaitableEvent* done) { |
| // Use window_dimensions_.size() != 0 as a proxy for the class having already |
| // been Initialize()'d, and UnInitialize() before continuing. |
| if (window_dimensions_.size()) { |
| base::WaitableEvent done(false, false); |
| UnInitialize(&done); |
| done.Wait(); |
| } |
| |
| gfx::InitializeGLBindings(kGLImplementation); |
| scoped_refptr<GLContextStubWithExtensions> stub_context( |
| new GLContextStubWithExtensions()); |
| |
| CHECK_GT(params.window_dimensions.size(), 0U); |
| CHECK_EQ(params.frame_dimensions.size(), params.window_dimensions.size()); |
| window_dimensions_ = params.window_dimensions; |
| frame_dimensions_ = params.frame_dimensions; |
| render_as_thumbnails_ = params.render_as_thumbnails; |
| message_loop_ = base::MessageLoop::current(); |
| CHECK_GT(params.num_windows, 0); |
| |
| #if GL_VARIANT_GLX |
| x_display_ = base::MessagePumpForUI::GetDefaultXDisplay(); |
| CHECK(x_display_); |
| CHECK(glXQueryVersion(x_display_, NULL, NULL)); |
| const int fbconfig_attr[] = { |
| GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, |
| GLX_RENDER_TYPE, GLX_RGBA_BIT, |
| GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT, |
| GLX_BIND_TO_TEXTURE_RGB_EXT, GL_TRUE, |
| GLX_DOUBLEBUFFER, True, |
| GL_NONE, |
| }; |
| int num_fbconfigs; |
| scoped_ptr_malloc<GLXFBConfig, ScopedPtrXFree> glx_fb_configs( |
| glXChooseFBConfig(x_display_, DefaultScreen(x_display_), fbconfig_attr, |
| &num_fbconfigs)); |
| CHECK(glx_fb_configs.get()); |
| CHECK_GT(num_fbconfigs, 0); |
| x_visual_ = glXGetVisualFromFBConfig(x_display_, glx_fb_configs.get()[0]); |
| CHECK(x_visual_); |
| gl_context_ = glXCreateContext(x_display_, x_visual_, 0, true); |
| CHECK(gl_context_); |
| stub_context->AddExtensionsString( |
| reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))); |
| |
| #else // EGL |
| EGLNativeDisplayType native_display; |
| |
| #if defined(OS_WIN) |
| native_display = EGL_DEFAULT_DISPLAY; |
| #else |
| x_display_ = base::MessagePumpForUI::GetDefaultXDisplay(); |
| CHECK(x_display_); |
| native_display = x_display_; |
| #endif |
| |
| gl_display_ = eglGetDisplay(native_display); |
| CHECK(gl_display_); |
| CHECK(eglInitialize(gl_display_, NULL, NULL)) << glGetError(); |
| |
| static EGLint rgba8888[] = { |
| EGL_RED_SIZE, 8, |
| EGL_GREEN_SIZE, 8, |
| EGL_BLUE_SIZE, 8, |
| EGL_ALPHA_SIZE, 8, |
| EGL_SURFACE_TYPE, EGL_WINDOW_BIT, |
| EGL_NONE, |
| }; |
| EGLConfig egl_config; |
| int num_configs; |
| CHECK(eglChooseConfig(gl_display_, rgba8888, &egl_config, 1, &num_configs)) |
| << eglGetError(); |
| CHECK_GE(num_configs, 1); |
| static EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; |
| gl_context_ = eglCreateContext( |
| gl_display_, egl_config, EGL_NO_CONTEXT, context_attribs); |
| CHECK_NE(gl_context_, EGL_NO_CONTEXT) << eglGetError(); |
| stub_context->AddExtensionsString( |
| reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))); |
| stub_context->AddExtensionsString( |
| eglQueryString(gl_display_, EGL_EXTENSIONS)); |
| #endif |
| |
| // Per-window/surface X11 & EGL initialization. |
| for (int i = 0; i < params.num_windows; ++i) { |
| // Arrange X windows whimsically, with some padding. |
| int j = i % window_dimensions_.size(); |
| int width = window_dimensions_[j].width(); |
| int height = window_dimensions_[j].height(); |
| CHECK_GT(width, 0); |
| CHECK_GT(height, 0); |
| int top_left_x = (width + 20) * (i % 4); |
| int top_left_y = (height + 12) * (i % 3); |
| |
| #if defined(OS_WIN) |
| NativeWindowType window = |
| CreateWindowEx(0, L"Static", L"VideoDecodeAcceleratorTest", |
| WS_OVERLAPPEDWINDOW | WS_VISIBLE, top_left_x, |
| top_left_y, width, height, NULL, NULL, NULL, |
| NULL); |
| CHECK(window != NULL); |
| windows_.push_back(window); |
| #else |
| int depth = DefaultDepth(x_display_, DefaultScreen(x_display_)); |
| |
| #if defined(GL_VARIANT_GLX) |
| CHECK_EQ(depth, x_visual_->depth); |
| #endif |
| |
| XSetWindowAttributes window_attributes; |
| window_attributes.background_pixel = |
| BlackPixel(x_display_, DefaultScreen(x_display_)); |
| window_attributes.override_redirect = true; |
| |
| NativeWindowType window = XCreateWindow( |
| x_display_, DefaultRootWindow(x_display_), |
| top_left_x, top_left_y, width, height, |
| 0 /* border width */, |
| depth, CopyFromParent /* class */, CopyFromParent /* visual */, |
| (CWBackPixel | CWOverrideRedirect), &window_attributes); |
| XStoreName(x_display_, window, "VideoDecodeAcceleratorTest"); |
| XSelectInput(x_display_, window, ExposureMask); |
| XMapWindow(x_display_, window); |
| x_windows_.push_back(window); |
| #endif |
| |
| #if GL_VARIANT_EGL |
| EGLSurface egl_surface = |
| eglCreateWindowSurface(gl_display_, egl_config, window, NULL); |
| gl_surfaces_.push_back(egl_surface); |
| CHECK_NE(egl_surface, EGL_NO_SURFACE); |
| #endif |
| MakeCurrent(i); |
| } |
| |
| // Must be done after a context is made current. |
| gfx::InitializeGLExtensionBindings(kGLImplementation, stub_context.get()); |
| |
| if (render_as_thumbnails_) { |
| CHECK_EQ(window_dimensions_.size(), 1U); |
| |
| GLint max_texture_size; |
| glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); |
| CHECK_GE(max_texture_size, params.thumbnails_page_size.width()); |
| CHECK_GE(max_texture_size, params.thumbnails_page_size.height()); |
| |
| thumbnails_fbo_size_ = params.thumbnails_page_size; |
| thumbnail_size_ = params.thumbnail_size; |
| |
| glGenFramebuffersEXT(1, &thumbnails_fbo_id_); |
| glGenTextures(1, &thumbnails_texture_id_); |
| glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_); |
| glTexImage2D(GL_TEXTURE_2D, |
| 0, |
| GL_RGB, |
| thumbnails_fbo_size_.width(), |
| thumbnails_fbo_size_.height(), |
| 0, |
| GL_RGB, |
| GL_UNSIGNED_SHORT_5_6_5, |
| NULL); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| |
| glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_); |
| glFramebufferTexture2DEXT(GL_FRAMEBUFFER, |
| GL_COLOR_ATTACHMENT0, |
| GL_TEXTURE_2D, |
| thumbnails_texture_id_, |
| 0); |
| |
| GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER); |
| CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status; |
| glClearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| glBindFramebufferEXT(GL_FRAMEBUFFER, 0); |
| } |
| |
| // These vertices and texture coords. map (0,0) in the texture to the |
| // bottom left of the viewport. Since we get the video frames with the |
| // the top left at (0,0) we need to flip the texture y coordinate |
| // in the vertex shader for this to be rendered the right way up. |
| // In the case of thumbnail rendering we use the same vertex shader |
| // to render the FBO the screen, where we do not want this flipping. |
| static const float kVertices[] = |
| { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, }; |
| static const float kTextureCoords[] = { 0, 1, 0, 0, 1, 1, 1, 0, }; |
| static const char kVertexShader[] = STRINGIZE( |
| varying vec2 interp_tc; |
| attribute vec4 in_pos; |
| attribute vec2 in_tc; |
| uniform bool tex_flip; |
| void main() { |
| if (tex_flip) |
| interp_tc = vec2(in_tc.x, 1.0 - in_tc.y); |
| else |
| interp_tc = in_tc; |
| gl_Position = in_pos; |
| }); |
| |
| #if GL_VARIANT_EGL |
| static const char kFragmentShader[] = |
| "#extension GL_OES_EGL_image_external : enable\n" |
| "precision mediump float;\n" |
| "varying vec2 interp_tc;\n" |
| "uniform sampler2D tex;\n" |
| "#ifdef GL_OES_EGL_image_external\n" |
| "uniform samplerExternalOES tex_external;\n" |
| "#endif\n" |
| "void main() {\n" |
| " vec4 color = texture2D(tex, interp_tc);\n" |
| "#ifdef GL_OES_EGL_image_external\n" |
| " color += texture2D(tex_external, interp_tc);\n" |
| "#endif\n" |
| " gl_FragColor = color;\n" |
| "}\n"; |
| #else |
| static const char kFragmentShader[] = STRINGIZE( |
| varying vec2 interp_tc; |
| uniform sampler2D tex; |
| void main() { |
| gl_FragColor = texture2D(tex, interp_tc); |
| }); |
| #endif |
| program_ = glCreateProgram(); |
| CreateShader( |
| program_, GL_VERTEX_SHADER, kVertexShader, arraysize(kVertexShader)); |
| CreateShader(program_, |
| GL_FRAGMENT_SHADER, |
| kFragmentShader, |
| arraysize(kFragmentShader)); |
| glLinkProgram(program_); |
| int result = GL_FALSE; |
| glGetProgramiv(program_, GL_LINK_STATUS, &result); |
| if (!result) { |
| char log[4096]; |
| glGetShaderInfoLog(program_, arraysize(log), NULL, log); |
| LOG(FATAL) << log; |
| } |
| glUseProgram(program_); |
| glDeleteProgram(program_); |
| |
| glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0); |
| glUniform1i(glGetUniformLocation(program_, "tex"), 0); |
| GLint tex_external = glGetUniformLocation(program_, "tex_external"); |
| if (tex_external != -1) { |
| glUniform1i(tex_external, 1); |
| } |
| int pos_location = glGetAttribLocation(program_, "in_pos"); |
| glEnableVertexAttribArray(pos_location); |
| glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices); |
| int tc_location = glGetAttribLocation(program_, "in_tc"); |
| glEnableVertexAttribArray(tc_location); |
| glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords); |
| done->Signal(); |
| } |
| |
| void RenderingHelper::UnInitialize(base::WaitableEvent* done) { |
| CHECK_EQ(base::MessageLoop::current(), message_loop_); |
| if (render_as_thumbnails_) { |
| glDeleteTextures(1, &thumbnails_texture_id_); |
| glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_); |
| } |
| #if GL_VARIANT_GLX |
| |
| glXDestroyContext(x_display_, gl_context_); |
| #else // EGL |
| MakeCurrent(-1); |
| CHECK(eglDestroyContext(gl_display_, gl_context_)); |
| for (size_t i = 0; i < gl_surfaces_.size(); ++i) |
| CHECK(eglDestroySurface(gl_display_, gl_surfaces_[i])); |
| CHECK(eglTerminate(gl_display_)); |
| #endif |
| gfx::ClearGLBindings(); |
| Clear(); |
| done->Signal(); |
| } |
| |
| void RenderingHelper::CreateTexture(int window_id, |
| uint32 texture_target, |
| uint32* texture_id, |
| base::WaitableEvent* done) { |
| if (base::MessageLoop::current() != message_loop_) { |
| message_loop_->PostTask( |
| FROM_HERE, |
| base::Bind(&RenderingHelper::CreateTexture, base::Unretained(this), |
| window_id, texture_target, texture_id, done)); |
| return; |
| } |
| MakeCurrent(window_id); |
| glGenTextures(1, texture_id); |
| glBindTexture(texture_target, *texture_id); |
| int dimensions_id = window_id % frame_dimensions_.size(); |
| if (texture_target == GL_TEXTURE_2D) { |
| glTexImage2D(GL_TEXTURE_2D, |
| 0, |
| GL_RGBA, |
| frame_dimensions_[dimensions_id].width(), |
| frame_dimensions_[dimensions_id].height(), |
| 0, |
| GL_RGBA, |
| GL_UNSIGNED_BYTE, |
| NULL); |
| } |
| glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures. |
| glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); |
| CHECK(texture_id_to_surface_index_.insert( |
| std::make_pair(*texture_id, window_id)).second); |
| done->Signal(); |
| } |
| |
| void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) { |
| CHECK_EQ(base::MessageLoop::current(), message_loop_); |
| size_t window_id = texture_id_to_surface_index_[texture_id]; |
| MakeCurrent(window_id); |
| |
| int dimensions_id = window_id % window_dimensions_.size(); |
| int width = window_dimensions_[dimensions_id].width(); |
| int height = window_dimensions_[dimensions_id].height(); |
| |
| if (render_as_thumbnails_) { |
| glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_); |
| const int thumbnails_in_row = |
| thumbnails_fbo_size_.width() / thumbnail_size_.width(); |
| const int thumbnails_in_column = |
| thumbnails_fbo_size_.height() / thumbnail_size_.height(); |
| const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column; |
| const int col = frame_count_ % thumbnails_in_row; |
| const int x = col * thumbnail_size_.width(); |
| const int y = row * thumbnail_size_.height(); |
| |
| glViewport(x, y, thumbnail_size_.width(), thumbnail_size_.height()); |
| glScissor(x, y, thumbnail_size_.width(), thumbnail_size_.height()); |
| glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0); |
| } else { |
| glViewport(0, 0, width, height); |
| glScissor(0, 0, width, height); |
| glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1); |
| } |
| |
| // Unbound texture samplers default to (0, 0, 0, 1). Use this fact to switch |
| // between GL_TEXTURE_2D and GL_TEXTURE_EXTERNAL_OES as appopriate. |
| if (texture_target == GL_TEXTURE_2D) { |
| glActiveTexture(GL_TEXTURE0 + 0); |
| glBindTexture(GL_TEXTURE_2D, texture_id); |
| glActiveTexture(GL_TEXTURE0 + 1); |
| glBindTexture(texture_target, 0); |
| } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) { |
| glActiveTexture(GL_TEXTURE0 + 0); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| glActiveTexture(GL_TEXTURE0 + 1); |
| glBindTexture(texture_target, texture_id); |
| } |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); |
| |
| ++frame_count_; |
| |
| if (render_as_thumbnails_) { |
| // Copy from FBO to screen |
| glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1); |
| glBindFramebufferEXT(GL_FRAMEBUFFER, 0); |
| glViewport(0, 0, width, height); |
| glScissor(0, 0, width, height); |
| glActiveTexture(GL_TEXTURE0 + 0); |
| glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_); |
| glActiveTexture(GL_TEXTURE0 + 1); |
| glBindTexture(texture_target, 0); |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| } |
| |
| #if GL_VARIANT_GLX |
| glXSwapBuffers(x_display_, x_windows_[window_id]); |
| #else // EGL |
| eglSwapBuffers(gl_display_, gl_surfaces_[window_id]); |
| CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS); |
| #endif |
| } |
| |
| void RenderingHelper::DeleteTexture(uint32 texture_id) { |
| glDeleteTextures(1, &texture_id); |
| CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); |
| } |
| |
| void* RenderingHelper::GetGLContext() { |
| return gl_context_; |
| } |
| |
| void* RenderingHelper::GetGLDisplay() { |
| #if GL_VARIANT_GLX |
| return x_display_; |
| #else // EGL |
| return gl_display_; |
| #endif |
| } |
| |
| void RenderingHelper::Clear() { |
| window_dimensions_.clear(); |
| frame_dimensions_.clear(); |
| texture_id_to_surface_index_.clear(); |
| message_loop_ = NULL; |
| gl_context_ = NULL; |
| #if GL_VARIANT_EGL |
| gl_display_ = EGL_NO_DISPLAY; |
| gl_surfaces_.clear(); |
| #endif |
| render_as_thumbnails_ = false; |
| frame_count_ = 0; |
| thumbnails_fbo_id_ = 0; |
| thumbnails_texture_id_ = 0; |
| |
| #if defined(OS_WIN) |
| for (size_t i = 0; i < windows_.size(); ++i) { |
| DestroyWindow(windows_[i]); |
| } |
| windows_.clear(); |
| #else |
| // Destroy resources acquired in Initialize, in reverse-acquisition order. |
| for (size_t i = 0; i < x_windows_.size(); ++i) { |
| CHECK(XUnmapWindow(x_display_, x_windows_[i])); |
| CHECK(XDestroyWindow(x_display_, x_windows_[i])); |
| } |
| // Mimic newly created object. |
| x_display_ = NULL; |
| x_windows_.clear(); |
| #endif |
| } |
| |
| void RenderingHelper::GetThumbnailsAsRGB(std::vector<unsigned char>* rgb, |
| bool* alpha_solid, |
| base::WaitableEvent* done) { |
| CHECK(render_as_thumbnails_); |
| |
| const size_t num_pixels = thumbnails_fbo_size_.GetArea(); |
| std::vector<unsigned char> rgba; |
| rgba.resize(num_pixels * 4); |
| glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_); |
| glPixelStorei(GL_PACK_ALIGNMENT, 1); |
| // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support. |
| glReadPixels(0, |
| 0, |
| thumbnails_fbo_size_.width(), |
| thumbnails_fbo_size_.height(), |
| GL_RGBA, |
| GL_UNSIGNED_BYTE, |
| &rgba[0]); |
| glBindFramebufferEXT(GL_FRAMEBUFFER, 0); |
| rgb->resize(num_pixels * 3); |
| // Drop the alpha channel, but check as we go that it is all 0xff. |
| bool solid = true; |
| unsigned char* rgb_ptr = &((*rgb)[0]); |
| unsigned char* rgba_ptr = &rgba[0]; |
| for (size_t i = 0; i < num_pixels; ++i) { |
| *rgb_ptr++ = *rgba_ptr++; |
| *rgb_ptr++ = *rgba_ptr++; |
| *rgb_ptr++ = *rgba_ptr++; |
| solid = solid && (*rgba_ptr == 0xff); |
| rgba_ptr++; |
| } |
| *alpha_solid = solid; |
| |
| done->Signal(); |
| } |
| |
| } // namespace content |