blob: c18cffc5b9fb8b424a5f0049510d5af586328573 [file] [log] [blame]
// 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