blob: d0578adc47f5af36320cc2392cb67b44e496f02f [file] [log] [blame]
// Copyright (c) 2010 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 "chrome/gpu/gpu_video_layer_glx.h"
#include "app/gfx/gl/gl_bindings.h"
#include "chrome/common/gpu_messages.h"
#include "chrome/gpu/gpu_thread.h"
#include "chrome/gpu/gpu_view_x.h"
// Handy constants for addressing YV12 data.
static const int kYUVPlanes = 3;
static const int kYPlane = 0;
static const int kUPlane = 1;
static const int kVPlane = 2;
// Buffer size for shader compile errors.
static const unsigned int kErrorSize = 4096;
// Matrix used for the YUV to RGB conversion.
static const float kYUV2RGB[9] = {
1.f, 0.f, 1.403f,
1.f, -.344f, -.714f,
1.f, 1.772f, 0.f,
};
// Texture coordinates mapping the entire texture.
static const float kTextureCoords[8] = {
0, 0,
0, 1,
1, 0,
1, 1,
};
#define I915_WORKAROUND
// Pass-through vertex shader.
static const char kVertexShader[] =
"varying vec2 interp_tc;\n"
"\n"
"attribute vec4 in_pos;\n"
"attribute vec2 in_tc;\n"
"\n"
"void main() {\n"
#if defined(I915_WORKAROUND)
" gl_TexCoord[0].st = in_tc;\n"
#else
" interp_tc = in_tc;\n"
#endif
" gl_Position = in_pos;\n"
"}\n";
// YUV to RGB pixel shader. Loads a pixel from each plane and pass through the
// matrix.
static const char kFragmentShader[] =
"varying vec2 interp_tc;\n"
"\n"
"uniform sampler2D y_tex;\n"
"uniform sampler2D u_tex;\n"
"uniform sampler2D v_tex;\n"
"uniform mat3 yuv2rgb;\n"
"\n"
"void main() {\n"
#if defined(I915_WORKAROUND)
" float y = texture2D(y_tex, gl_TexCoord[0].st).x;\n"
" float u = texture2D(u_tex, gl_TexCoord[0].st).r - .5;\n"
" float v = texture2D(v_tex, gl_TexCoord[0].st).r - .5;\n"
" float r = y + v * 1.403;\n"
" float g = y - u * 0.344 - v * 0.714;\n"
" float b = y + u * 1.772;\n"
" gl_FragColor = vec4(r, g, b, 1);\n"
#else
" float y = texture2D(y_tex, interp_tc).x;\n"
" float u = texture2D(u_tex, interp_tc).r - .5;\n"
" float v = texture2D(v_tex, interp_tc).r - .5;\n"
" vec3 rgb = yuv2rgb * vec3(y, u, v);\n"
" gl_FragColor = vec4(rgb, 1);\n"
#endif
"}\n";
// Assume that somewhere along the line, someone will do width * height * 4
// with signed numbers. If the maximum value is 2**31, then 2**31 / 4 =
// 2**29 and floor(sqrt(2**29)) = 23170.
// Max height and width for layers
static const int kMaxVideoLayerSize = 23170;
GpuVideoLayerGLX::GpuVideoLayerGLX(GpuViewX* view,
GpuThread* gpu_thread,
int32 routing_id,
const gfx::Size& size)
: view_(view),
gpu_thread_(gpu_thread),
routing_id_(routing_id),
native_size_(size),
program_(0) {
memset(textures_, 0, sizeof(textures_));
// Load identity vertices.
gfx::Rect identity(0, 0, 1, 1);
CalculateVertices(identity.size(), identity, target_vertices_);
gpu_thread_->AddRoute(routing_id_, this);
view_->BindContext(); // Must do this before issuing OpenGl.
// TODO(apatrick): These functions are not available in GLES2.
// glMatrixMode(GL_MODELVIEW);
// Create 3 textures, one for each plane, and bind them to different
// texture units.
glGenTextures(kYUVPlanes, textures_);
glBindTexture(GL_TEXTURE_2D, textures_[kYPlane]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, textures_[kUPlane]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, textures_[kVPlane]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Create our YUV->RGB shader.
program_ = glCreateProgram();
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
const char* vs_source = kVertexShader;
int vs_size = sizeof(kVertexShader);
glShaderSource(vertex_shader, 1, &vs_source, &vs_size);
glCompileShader(vertex_shader);
int result = GL_FALSE;
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &result);
if (!result) {
char log[kErrorSize];
int len;
glGetShaderInfoLog(vertex_shader, kErrorSize - 1, &len, log);
log[kErrorSize - 1] = 0;
LOG(FATAL) << log;
}
glAttachShader(program_, vertex_shader);
glDeleteShader(vertex_shader);
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
const char* ps_source = kFragmentShader;
int ps_size = sizeof(kFragmentShader);
glShaderSource(fragment_shader, 1, &ps_source, &ps_size);
glCompileShader(fragment_shader);
result = GL_FALSE;
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &result);
if (!result) {
char log[kErrorSize];
int len;
glGetShaderInfoLog(fragment_shader, kErrorSize - 1, &len, log);
log[kErrorSize - 1] = 0;
LOG(FATAL) << log;
}
glAttachShader(program_, fragment_shader);
glDeleteShader(fragment_shader);
glLinkProgram(program_);
result = GL_FALSE;
glGetProgramiv(program_, GL_LINK_STATUS, &result);
if (!result) {
char log[kErrorSize];
int len;
glGetProgramInfoLog(program_, kErrorSize - 1, &len, log);
log[kErrorSize - 1] = 0;
LOG(FATAL) << log;
}
}
GpuVideoLayerGLX::~GpuVideoLayerGLX() {
// TODO(scherkus): this seems like a bad idea.. we might be better off with
// separate Initialize()/Teardown() calls instead.
view_->BindContext();
if (program_) {
glDeleteProgram(program_);
}
gpu_thread_->RemoveRoute(routing_id_);
}
void GpuVideoLayerGLX::Render(const gfx::Size& viewport_size) {
// Nothing to do if we're not visible or have no YUV data.
if (target_rect_.IsEmpty()) {
return;
}
// Calculate the position of our quad.
CalculateVertices(viewport_size, target_rect_, target_vertices_);
// Bind Y, U and V textures to texture units.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures_[kYPlane]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textures_[kUPlane]);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, textures_[kVPlane]);
// Bind vertex/fragment shader program.
glUseProgram(program_);
// Bind parameters.
glUniform1i(glGetUniformLocation(program_, "y_tex"), 0);
glUniform1i(glGetUniformLocation(program_, "u_tex"), 1);
glUniform1i(glGetUniformLocation(program_, "v_tex"), 2);
#if !defined(I915_WORKAROUND)
int yuv2rgb_location = glGetUniformLocation(program_, "yuv2rgb");
glUniformMatrix3fv(yuv2rgb_location, 1, GL_TRUE, kYUV2RGB);
#endif
// TODO(scherkus): instead of calculating and loading a geometry each time,
// we should store a constant geometry in a VBO and use a vertex shader.
int pos_location = glGetAttribLocation(program_, "in_pos");
glEnableVertexAttribArray(pos_location);
glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0,
target_vertices_);
int tc_location = glGetAttribLocation(program_, "in_tc");
glEnableVertexAttribArray(tc_location);
glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0,
kTextureCoords);
// Render!
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// Reset back to original state.
glDisableVertexAttribArray(pos_location);
glDisableVertexAttribArray(tc_location);
glActiveTexture(GL_TEXTURE0);
glUseProgram(0);
}
void GpuVideoLayerGLX::OnMessageReceived(const IPC::Message& msg) {
IPC_BEGIN_MESSAGE_MAP(GpuVideoLayerGLX, msg)
IPC_MESSAGE_HANDLER(GpuMsg_PaintToVideoLayer, OnPaintToVideoLayer)
IPC_END_MESSAGE_MAP_EX()
}
void GpuVideoLayerGLX::OnChannelConnected(int32 peer_pid) {
}
void GpuVideoLayerGLX::OnChannelError() {
// FIXME(brettw) does this mean we aren't getting any more messages and we
// should delete outselves?
NOTIMPLEMENTED();
}
void GpuVideoLayerGLX::OnPaintToVideoLayer(base::ProcessId source_process_id,
TransportDIB::Id id,
const gfx::Rect& bitmap_rect) {
// TODO(scherkus): |native_size_| is set in constructor, so perhaps this check
// should be a DCHECK().
const int width = native_size_.width();
const int height = native_size_.height();
const int stride = width;
if (width <= 0 || width > kMaxVideoLayerSize ||
height <= 0 || height > kMaxVideoLayerSize)
return;
TransportDIB* dib = TransportDIB::Map(id);
if (!dib)
return;
// Everything looks good, update our target position and size.
target_rect_ = bitmap_rect;
// Perform colour space conversion.
uint8* planes[kYUVPlanes];
planes[kYPlane] = reinterpret_cast<uint8*>(dib->memory());
planes[kUPlane] = planes[kYPlane] + width * height;
planes[kVPlane] = planes[kUPlane] + ((width * height) >> 2);
view_->BindContext(); // Must do this before issuing OpenGl.
// Assume YV12 format.
for (int i = 0; i < kYUVPlanes; ++i) {
int plane_width = (i == kYPlane ? width : width / 2);
int plane_height = (i == kYPlane ? height : height / 2);
int plane_stride = (i == kYPlane ? stride : stride / 2);
// Ensure that we will not read outside the shared mem region.
if (planes[i] >= planes[kYPlane] &&
(dib->size() - (planes[kYPlane] - planes[i])) >=
static_cast<unsigned int>(plane_width * plane_height)) {
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, textures_[i]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, plane_stride);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, plane_width, plane_height, 0,
GL_LUMINANCE, GL_UNSIGNED_BYTE, planes[i]);
}
}
// Reset back to original state.
glActiveTexture(GL_TEXTURE0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glFlush();
// TODO(scherkus): we may not need to ACK video layer updates at all.
gpu_thread_->Send(new GpuHostMsg_PaintToVideoLayer_ACK(routing_id_));
}
// static
void GpuVideoLayerGLX::CalculateVertices(const gfx::Size& world,
const gfx::Rect& object,
float* vertices) {
// Don't forget GL has a flipped Y-axis!
float width = world.width();
float height = world.height();
// Top left.
vertices[0] = 2.0f * (object.x() / width) - 1.0f;
vertices[1] = -2.0f * (object.y() / height) + 1.0f;
// Bottom left.
vertices[2] = 2.0f * (object.x() / width) - 1.0f;
vertices[3] = -2.0f * (object.bottom() / height) + 1.0f;
// Top right.
vertices[4] = 2.0f * (object.right() / width) - 1.0f;
vertices[5] = -2.0f * (object.y() / height) + 1.0f;
// Bottom right.
vertices[6] = 2.0f * (object.right() / width) - 1.0f;
vertices[7] = -2.0f * (object.bottom() / height) + 1.0f;
}