blob: 5c68add57c321ee3ad90a613c6a54809620232ce [file] [log] [blame]
// Copyright (c) 2011 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 "ui/gfx/compositor/compositor.h"
#include <GL/gl.h>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/transform.h"
#include "ui/gfx/gl/gl_bindings.h"
#include "ui/gfx/gl/gl_context.h"
#include "ui/gfx/gl/gl_implementation.h"
#include "ui/gfx/gl/gl_surface.h"
#include "ui/gfx/gl/gl_surface_glx.h"
namespace ui {
namespace glHidden {
class CompositorGL;
class TextureGL : public Texture {
public:
TextureGL(CompositorGL* compositor);
virtual ~TextureGL();
virtual void SetBitmap(const SkBitmap& bitmap,
const gfx::Point& origin,
const gfx::Size& overall_size) OVERRIDE;
// Draws the texture.
virtual void Draw(const ui::Transform& transform) OVERRIDE;
private:
unsigned int texture_id_;
gfx::Size size_;
CompositorGL* compositor_;
DISALLOW_COPY_AND_ASSIGN(TextureGL);
};
class CompositorGL : public Compositor {
public:
explicit CompositorGL(gfx::AcceleratedWidget widget);
virtual ~CompositorGL();
void MakeCurrent();
gfx::Size GetSize();
GLuint program();
GLuint a_pos_loc();
GLuint a_tex_loc();
GLuint u_tex_loc();
GLuint u_mat_loc();
private:
// Overridden from Compositor.
virtual Texture* CreateTexture() OVERRIDE;
virtual void NotifyStart() OVERRIDE;
virtual void NotifyEnd() OVERRIDE;
virtual void Blur(const gfx::Rect& bounds) OVERRIDE;
// Specific to CompositorGL.
bool InitShaders();
// The GL context used for compositing.
scoped_refptr<gfx::GLSurface> gl_surface_;
scoped_refptr<gfx::GLContext> gl_context_;
gfx::Size size_;
// Shader program, attributes and uniforms.
// TODO(wjmaclean): Make these static so they ca be shared in a single
// context.
GLuint program_;
GLuint a_pos_loc_;
GLuint a_tex_loc_;
GLuint u_tex_loc_;
GLuint u_mat_loc_;
// Keep track of whether compositing has started or not.
bool started_;
DISALLOW_COPY_AND_ASSIGN(CompositorGL);
};
TextureGL::TextureGL(CompositorGL* compositor) : texture_id_(0),
compositor_(compositor) {
}
TextureGL::~TextureGL() {
if (texture_id_) {
compositor_->MakeCurrent();
glDeleteTextures(1, &texture_id_);
}
}
void TextureGL::SetBitmap(const SkBitmap& bitmap,
const gfx::Point& origin,
const gfx::Size& overall_size) {
// Verify bitmap pixels are contiguous.
DCHECK_EQ(bitmap.rowBytes(),
SkBitmap::ComputeRowBytes(bitmap.config(), bitmap.width()));
SkAutoLockPixels lock (bitmap);
void* pixels = bitmap.getPixels();
if (!texture_id_) {
// Texture needs to be created. We assume the first call is for
// a full-sized canvas.
size_ = overall_size;
glGenTextures(1, &texture_id_);
glBindTexture(GL_TEXTURE_2D, texture_id_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
size_.width(), size_.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else if (size_ != overall_size) { // Size has changed.
size_ = overall_size;
glBindTexture(GL_TEXTURE_2D, texture_id_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
size_.width(), size_.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, pixels);
} else { // Uploading partial texture.
glBindTexture(GL_TEXTURE_2D, texture_id_);
glTexSubImage2D(GL_TEXTURE_2D, 0, origin.x(), origin.y(),
bitmap.width(), bitmap.height(),
GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
}
void TextureGL::Draw(const ui::Transform& transform) {
gfx::Size window_size = compositor_->GetSize();
ui::Transform t;
t.ConcatTranslate(1,1);
t.ConcatScale(size_.width()/2.0f, size_.height()/2.0f);
t.ConcatTranslate(0, -size_.height());
t.ConcatScale(1, -1);
t.ConcatTransform(transform); // Add view transform.
t.ConcatTranslate(0, -window_size.height());
t.ConcatScale(1, -1);
t.ConcatTranslate(-window_size.width()/2.0f, -window_size.height()/2.0f);
t.ConcatScale(2.0f/window_size.width(), 2.0f/window_size.height());
DCHECK(compositor_->program());
glUseProgram(compositor_->program());
glActiveTexture(GL_TEXTURE0);
glUniform1i(compositor_->u_tex_loc(), 0);
glBindTexture(GL_TEXTURE_2D, texture_id_);
GLfloat m[16];
const SkMatrix& matrix = t.matrix();
// Convert 3x3 view transform matrix (row major) into 4x4 GL matrix (column
// major). Assume 2-D rotations/translations restricted to XY plane.
m[ 0] = matrix[0];
m[ 1] = matrix[3];
m[ 2] = 0;
m[ 3] = matrix[6];
m[ 4] = matrix[1];
m[ 5] = matrix[4];
m[ 6] = 0;
m[ 7] = matrix[7];
m[ 8] = 0;
m[ 9] = 0;
m[10] = 1;
m[11] = 0;
m[12] = matrix[2];
m[13] = matrix[5];
m[14] = 0;
m[15] = matrix[8];
const GLfloat vertices[] = { -1., -1., +0., +0., +1.,
+1., -1., +0., +1., +1.,
+1., +1., +0., +1., +0.,
-1., +1., +0., +0., +0. };
glVertexAttribPointer(compositor_->a_pos_loc(), 3, GL_FLOAT,
GL_FALSE, 5 * sizeof(GLfloat), vertices);
glVertexAttribPointer(compositor_->a_tex_loc(), 2, GL_FLOAT,
GL_FALSE, 5 * sizeof(GLfloat), &vertices[3]);
glEnableVertexAttribArray(compositor_->a_pos_loc());
glEnableVertexAttribArray(compositor_->a_tex_loc());
glUniformMatrix4fv(compositor_->u_mat_loc(), 1, GL_FALSE, m);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
CompositorGL::CompositorGL(gfx::AcceleratedWidget widget)
: started_(false) {
gl_surface_ = gfx::GLSurface::CreateViewGLSurface(widget);
gl_context_ = gfx::GLContext::CreateGLContext(NULL, gl_surface_.get());
gl_context_->MakeCurrent(gl_surface_.get());
if (!InitShaders())
LOG(ERROR) << "Unable to initialize shaders (context = "
<< static_cast<void*>(gl_context_.get()) << ")" ;
glColorMask(true, true, true, true);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
CompositorGL::~CompositorGL() {
}
void CompositorGL::MakeCurrent() {
gl_context_->MakeCurrent(gl_surface_.get());
}
gfx::Size CompositorGL::GetSize() {
return gl_surface_->GetSize();
}
GLuint CompositorGL::program() {
return program_;
}
GLuint CompositorGL::a_pos_loc() {
return a_pos_loc_;
}
GLuint CompositorGL::a_tex_loc() {
return a_tex_loc_;
}
GLuint CompositorGL::u_tex_loc() {
return u_tex_loc_;
}
GLuint CompositorGL::u_mat_loc() {
return u_mat_loc_;
}
Texture* CompositorGL::CreateTexture() {
Texture* texture = new TextureGL(this);
return texture;
}
void CompositorGL::NotifyStart() {
started_ = true;
gl_context_->MakeCurrent(gl_surface_.get());
glViewport(0, 0,
gl_surface_->GetSize().width(), gl_surface_->GetSize().height());
// Clear to 'psychedelic' purple to make it easy to spot un-rendered regions.
glClearColor(223.0 / 255, 0, 1, 1);
glColorMask(true, true, true, true);
glClear(GL_COLOR_BUFFER_BIT);
// Disable alpha writes, since we're using blending anyways.
glColorMask(true, true, true, false);
}
void CompositorGL::NotifyEnd() {
DCHECK(started_);
gl_surface_->SwapBuffers();
started_ = false;
}
void CompositorGL::Blur(const gfx::Rect& bounds) {
NOTIMPLEMENTED();
}
namespace {
GLuint CompileShader(GLenum type, const GLchar* source) {
GLuint shader = glCreateShader(type);
if (!shader)
return 0;
glShaderSource(shader, 1, &source, 0);
glCompileShader(shader);
GLint compiled;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint info_len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
if (info_len > 0) {
char* info_log = reinterpret_cast<char*> (
malloc(sizeof(info_log[0]) * info_len));
glGetShaderInfoLog(shader, info_len, NULL, info_log);
LOG(ERROR) << "Compile error: " << info_log;
free(info_log);
return 0;
}
}
return shader;
}
} // namespace (anonymous)
bool CompositorGL::InitShaders() {
const GLchar* vertex_shader_source =
"attribute vec4 a_position;"
"attribute vec2 a_texCoord;"
"uniform mat4 u_matViewProjection;"
"varying vec2 v_texCoord;"
"void main()"
"{"
" gl_Position = u_matViewProjection * a_position;"
" v_texCoord = a_texCoord;"
"}";
GLuint vertex_shader = CompileShader(GL_VERTEX_SHADER, vertex_shader_source);
if (!vertex_shader)
return false;
const GLchar* frag_shader_source =
"#ifdef GL_ES\n"
"precision mediump float;\n"
"#endif\n"
"uniform sampler2D u_tex;"
"varying vec2 v_texCoord;"
"void main()"
"{"
" gl_FragColor = texture2D(u_tex, v_texCoord).zyxw;"
"}";
GLuint frag_shader = CompileShader(GL_FRAGMENT_SHADER, frag_shader_source);
if (!frag_shader)
return false;
program_ = glCreateProgram();
glAttachShader(program_, vertex_shader);
glAttachShader(program_, frag_shader);
glLinkProgram(program_);
if (glGetError() != GL_NO_ERROR)
return false;
// Store locations of program inputs.
a_pos_loc_ = glGetAttribLocation(program_, "a_position");
a_tex_loc_ = glGetAttribLocation(program_, "a_texCoord");
u_tex_loc_ = glGetUniformLocation(program_, "u_tex");
u_mat_loc_ = glGetUniformLocation(program_, "u_matViewProjection");
return true;
}
} // namespace
// static
Compositor* Compositor::Create(gfx::AcceleratedWidget widget) {
gfx::GLSurface::InitializeOneOff();
if (gfx::GetGLImplementation() != gfx::kGLImplementationNone)
return new glHidden::CompositorGL(widget);
return NULL;
}
} // namespace ui