// 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(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(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; }