diff options
author | Torne (Richard Coles) <torne@google.com> | 2013-10-18 15:46:22 +0100 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2013-10-18 15:46:22 +0100 |
commit | 4e180b6a0b4720a9b8e9e959a882386f690f08ff (patch) | |
tree | 788435d09362885908ba5ba9ef868b852ca82c0b /content/common/gpu/media/rendering_helper.cc | |
parent | 1179b92b08db0c652a0cf003ab4d89b31ce3610f (diff) | |
download | chromium_org-4e180b6a0b4720a9b8e9e959a882386f690f08ff.tar.gz |
Merge from Chromium at DEPS revision 228962
This commit was generated by merge_to_master.py.
Change-Id: I23bd7d7766f213fd52f28ae5e1ecc6ae9df905ea
Diffstat (limited to 'content/common/gpu/media/rendering_helper.cc')
-rw-r--r-- | content/common/gpu/media/rendering_helper.cc | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/content/common/gpu/media/rendering_helper.cc b/content/common/gpu/media/rendering_helper.cc new file mode 100644 index 0000000000..b0f540ea86 --- /dev/null +++ b/content/common/gpu/media/rendering_helper.cc @@ -0,0 +1,572 @@ +// 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[] = STRINGIZE( + precision mediump float; + varying vec2 interp_tc; + uniform sampler2D tex; + void main() { + gl_FragColor = texture2D(tex, interp_tc); + }); +#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); + 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; + } + CHECK_EQ(static_cast<uint32>(GL_TEXTURE_2D), texture_target); + MakeCurrent(window_id); + glGenTextures(1, texture_id); + glBindTexture(GL_TEXTURE_2D, *texture_id); + int dimensions_id = window_id % frame_dimensions_.size(); + 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(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, 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_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); + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 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); + glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_); + 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]); + 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 |