// Copyright (c) 2012 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 <map> #if defined(OS_WIN) || defined(ARCH_CPU_ARMEL) #include "third_party/angle/include/EGL/egl.h" // Must precede ui/gl headers! #endif #include "base/bind.h" #include "base/mac/scoped_nsautorelease_pool.h" #include "base/message_loop.h" #include "base/stringize_macros.h" #include "base/synchronization/waitable_event.h" #if !defined(ARCH_CPU_ARMEL) #include "ui/gl/gl_bindings.h" #include "ui/gl/gl_context.h" #include "ui/gl/gl_implementation.h" #include "ui/gl/gl_surface.h" #else #include "third_party/angle/include/GLES2/gl2.h" #endif #if !defined(OS_WIN) && defined(ARCH_CPU_X86_FAMILY) #define GL_VARIANT_GLX 1 typedef GLXWindow NativeWindowType; typedef GLXContext NativeContextType; struct ScopedPtrXFree { void operator()(void* x) const { ::XFree(x); } }; #else #define GL_VARIANT_EGL 1 typedef EGLNativeWindowType NativeWindowType; typedef EGLContext NativeContextType; typedef EGLSurface NativeSurfaceType; #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 content { class RenderingHelperGL : public RenderingHelper { public: RenderingHelperGL(); virtual ~RenderingHelperGL(); // Implement RenderingHelper. virtual void Initialize(bool suppress_swap_to_display, int num_windows, int width, int height, base::WaitableEvent* done) OVERRIDE; virtual void UnInitialize(base::WaitableEvent* done) OVERRIDE; virtual void CreateTexture(int window_id, uint32 texture_target, uint32* texture_id, base::WaitableEvent* done) OVERRIDE; virtual void RenderTexture(uint32 texture_id) OVERRIDE; virtual void DeleteTexture(uint32 texture_id) OVERRIDE; virtual void* GetGLContext() OVERRIDE; virtual void* GetGLDisplay() OVERRIDE; private: void Clear(); // Make window_id's surface current w/ the GL context, or release the context // if |window_id < 0|. void MakeCurrent(int window_id); MessageLoop* message_loop_; int width_; int height_; bool suppress_swap_to_display_; NativeContextType gl_context_; std::map<uint32, int> texture_id_to_surface_index_; #if defined(GL_VARIANT_EGL) EGLDisplay gl_display_; std::vector<NativeSurfaceType> gl_surfaces_; #else XVisualInfo* x_visual_; #endif #if defined(OS_WIN) std::vector<HWND> windows_; #else Display* x_display_; std::vector<Window> x_windows_; #endif }; // static RenderingHelper* RenderingHelper::Create() { return new RenderingHelperGL; } // static void RenderingHelper::InitializePlatform() { #if defined(OS_WIN) gfx::InitializeGLBindings(gfx::kGLImplementationEGLGLES2); gfx::GLSurface::InitializeOneOff(); { // Hack to ensure that EGL extension function pointers are initialized. scoped_refptr<gfx::GLSurface> surface( gfx::GLSurface::CreateOffscreenGLSurface(false, gfx::Size(1, 1))); scoped_refptr<gfx::GLContext> context( gfx::GLContext::CreateGLContext(NULL, surface.get(), gfx::PreferIntegratedGpu)); context->MakeCurrent(surface.get()); } #endif // OS_WIN } RenderingHelperGL::RenderingHelperGL() { Clear(); } RenderingHelperGL::~RenderingHelperGL() { CHECK_EQ(width_, 0) << "Must call UnInitialize before dtor."; Clear(); } void RenderingHelperGL::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 RenderingHelperGL::Initialize(bool suppress_swap_to_display, int num_windows, int width, int height, base::WaitableEvent* done) { // Use width_ != 0 as a proxy for the class having already been // Initialize()'d, and UnInitialize() before continuing. if (width_) { base::WaitableEvent done(false, false); UnInitialize(&done); done.Wait(); } suppress_swap_to_display_ = suppress_swap_to_display; CHECK_GT(width, 0); CHECK_GT(height, 0); width_ = width; height_ = height; message_loop_ = MessageLoop::current(); CHECK_GT(num_windows, 0); #if GL_VARIANT_GLX x_display_ = base::MessagePumpForUI::GetDefaultXDisplay(); CHECK(x_display_); gfx::InitializeGLBindings(gfx::kGLImplementationDesktopGL); 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_); #else // EGL #if defined(OS_WIN) gl_display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); CHECK(gl_display_); CHECK(eglInitialize(gl_display_, NULL, NULL)) << glGetError(); #else x_display_ = base::MessagePumpForUI::GetDefaultXDisplay(); CHECK(x_display_); gl_display_ = eglGetDisplay(x_display_); CHECK(gl_display_); CHECK(eglInitialize(gl_display_, NULL, NULL)) << glGetError(); #endif 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(); #endif // Per-window/surface X11 & EGL initialization. for (int i = 0; i < num_windows; ++i) { // Arrange X windows whimsically, with some padding. 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 NativeSurfaceType 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); } 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, }; // On Windows the textures from Direct3D which renders them flipped. #if GL_VARIANT_GLX || defined(OS_WIN) static const char kVertexShader[] = STRINGIZE( varying vec2 interp_tc; attribute vec4 in_pos; attribute vec2 in_tc; void main() { interp_tc = vec2(in_tc.x, 1.0 - in_tc.y); gl_Position = in_pos; }); #else static const char kVertexShader[] = STRINGIZE( varying vec2 interp_tc; attribute vec4 in_pos; attribute vec2 in_tc; void main() { interp_tc = in_tc; gl_Position = in_pos; }); #endif #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 GLuint 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"), 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 RenderingHelperGL::UnInitialize(base::WaitableEvent* done) { CHECK_EQ(MessageLoop::current(), message_loop_); #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 Clear(); done->Signal(); } void RenderingHelperGL::CreateTexture(int window_id, uint32 texture_target, uint32* texture_id, base::WaitableEvent* done) { if (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); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, 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 RenderingHelperGL::RenderTexture(uint32 texture_id) { CHECK_EQ(MessageLoop::current(), message_loop_); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture_id); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); if (suppress_swap_to_display_) return; int window_id = texture_id_to_surface_index_[texture_id]; MakeCurrent(window_id); #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 RenderingHelperGL::DeleteTexture(uint32 texture_id) { glDeleteTextures(1, &texture_id); CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR); } void* RenderingHelperGL::GetGLContext() { return gl_context_; } void* RenderingHelperGL::GetGLDisplay() { #if GL_VARIANT_GLX return x_display_; #else // EGL return gl_display_; #endif } void RenderingHelperGL::Clear() { suppress_swap_to_display_ = false; width_ = 0; height_ = 0; 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 #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 } } // namespace content