OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "media/tools/player_x11/gl_video_renderer.h" |
| 6 |
| 7 #include <dlfcn.h> |
| 8 #include <X11/Xutil.h> |
| 9 #include <X11/extensions/Xrender.h> |
| 10 #include <X11/extensions/Xcomposite.h> |
| 11 |
| 12 #include "media/base/buffers.h" |
| 13 #include "media/base/yuv_convert.h" |
| 14 |
| 15 GlVideoRenderer* GlVideoRenderer::instance_ = NULL; |
| 16 |
| 17 GlVideoRenderer::GlVideoRenderer(Display* display, Window window) |
| 18 : display_(display), |
| 19 window_(window), |
| 20 new_frame_(false), |
| 21 gl_context_(NULL) { |
| 22 } |
| 23 |
| 24 GlVideoRenderer::~GlVideoRenderer() { |
| 25 } |
| 26 |
| 27 // static |
| 28 bool GlVideoRenderer::IsMediaFormatSupported( |
| 29 const media::MediaFormat& media_format) { |
| 30 int width = 0; |
| 31 int height = 0; |
| 32 return ParseMediaFormat(media_format, &width, &height); |
| 33 } |
| 34 |
| 35 void GlVideoRenderer::OnStop() { |
| 36 glXMakeCurrent(display_, 0, NULL); |
| 37 glXDestroyContext(display_, gl_context_); |
| 38 } |
| 39 |
| 40 static GLXContext InitGLContext(Display* display, Window window) { |
| 41 // Some versions of NVIDIA's GL libGL.so include a broken version of |
| 42 // dlopen/dlsym, and so linking it into chrome breaks it. So we dynamically |
| 43 // load it, and use glew to dynamically resolve symbols. |
| 44 // See http://code.google.com/p/chromium/issues/detail?id=16800 |
| 45 void* handle = dlopen("libGL.so.1", RTLD_LAZY | RTLD_GLOBAL); |
| 46 if (!handle) { |
| 47 LOG(ERROR) << "Could not find libGL.so.1"; |
| 48 return NULL; |
| 49 } |
| 50 if (glxewInit() != GLEW_OK) { |
| 51 LOG(ERROR) << "GLXEW failed initialization"; |
| 52 return NULL; |
| 53 } |
| 54 |
| 55 XWindowAttributes attributes; |
| 56 XGetWindowAttributes(display, window, &attributes); |
| 57 XVisualInfo visual_info_template; |
| 58 visual_info_template.visualid = XVisualIDFromVisual(attributes.visual); |
| 59 int visual_info_count = 0; |
| 60 XVisualInfo* visual_info_list = XGetVisualInfo(display, VisualIDMask, |
| 61 &visual_info_template, |
| 62 &visual_info_count); |
| 63 GLXContext context = NULL; |
| 64 for (int i = 0; i < visual_info_count && !context; ++i) { |
| 65 context = glXCreateContext(display, visual_info_list + i, 0, |
| 66 True /* Direct rendering */); |
| 67 } |
| 68 |
| 69 XFree(visual_info_list); |
| 70 if (!context) { |
| 71 return NULL; |
| 72 } |
| 73 |
| 74 if (!glXMakeCurrent(display, window, context)) { |
| 75 glXDestroyContext(display, context); |
| 76 return NULL; |
| 77 } |
| 78 |
| 79 if (glewInit() != GLEW_OK) { |
| 80 LOG(ERROR) << "GLEW failed initialization"; |
| 81 glXDestroyContext(display, context); |
| 82 return NULL; |
| 83 } |
| 84 |
| 85 if (!glewIsSupported("GL_VERSION_2_0")) { |
| 86 LOG(ERROR) << "GL implementation doesn't support GL version 2.0"; |
| 87 glXDestroyContext(display, context); |
| 88 return NULL; |
| 89 } |
| 90 |
| 91 return context; |
| 92 } |
| 93 |
| 94 // Matrix used for the YUV to RGB conversion. |
| 95 static const float kYUV2RGB[9] = { |
| 96 1.f, 0.f, 1.403f, |
| 97 1.f, -.344f, -.714f, |
| 98 1.f, 1.772f, 0.f, |
| 99 }; |
| 100 |
| 101 // Vertices for a full screen quad. |
| 102 static const float kVertices[8] = { |
| 103 -1.f, 1.f, |
| 104 -1.f, -1.f, |
| 105 1.f, 1.f, |
| 106 1.f, -1.f, |
| 107 }; |
| 108 |
| 109 // Texture Coordinates mapping the entire texture. |
| 110 static const float kTextureCoords[8] = { |
| 111 0, 0, |
| 112 0, 1, |
| 113 1, 0, |
| 114 1, 1, |
| 115 }; |
| 116 |
| 117 // Pass-through vertex shader. |
| 118 static const char kVertexShader[] = |
| 119 "varying vec2 interp_tc;\n" |
| 120 "\n" |
| 121 "attribute vec4 in_pos;\n" |
| 122 "attribute vec2 in_tc;\n" |
| 123 "\n" |
| 124 "void main() {\n" |
| 125 " interp_tc = in_tc;\n" |
| 126 " gl_Position = in_pos;\n" |
| 127 "}\n"; |
| 128 |
| 129 // YUV to RGB pixel shader. Loads a pixel from each plane and pass through the |
| 130 // matrix. |
| 131 static const char kFragmentShader[] = |
| 132 "varying vec2 interp_tc;\n" |
| 133 "\n" |
| 134 "uniform sampler2D y_tex;\n" |
| 135 "uniform sampler2D u_tex;\n" |
| 136 "uniform sampler2D v_tex;\n" |
| 137 "uniform mat3 yuv2rgb;\n" |
| 138 "\n" |
| 139 "void main() {\n" |
| 140 " float y = texture2D(y_tex, interp_tc).x;\n" |
| 141 " float u = texture2D(u_tex, interp_tc).r - .5;\n" |
| 142 " float v = texture2D(v_tex, interp_tc).r - .5;\n" |
| 143 " vec3 rgb = yuv2rgb * vec3(y, u, v);\n" |
| 144 " gl_FragColor = vec4(rgb, 1);\n" |
| 145 "}\n"; |
| 146 |
| 147 // Buffer size for compile errors. |
| 148 static const unsigned int kErrorSize = 4096; |
| 149 |
| 150 bool GlVideoRenderer::OnInitialize(media::VideoDecoder* decoder) { |
| 151 if (!ParseMediaFormat(decoder->media_format(), &width_, &height_)) |
| 152 return false; |
| 153 |
| 154 LOG(INFO) << "Initializing GL Renderer..."; |
| 155 |
| 156 // Resize the window to fit that of the video. |
| 157 XResizeWindow(display_, window_, width_, height_); |
| 158 |
| 159 gl_context_ = InitGLContext(display_, window_); |
| 160 if (!gl_context_) |
| 161 return false; |
| 162 |
| 163 glMatrixMode(GL_MODELVIEW); |
| 164 |
| 165 // Create 3 textures, one for each plane, and bind them to different |
| 166 // texture units. |
| 167 glGenTextures(media::VideoSurface::kNumYUVPlanes, textures_); |
| 168 glActiveTexture(GL_TEXTURE0); |
| 169 glBindTexture(GL_TEXTURE_2D, textures_[0]); |
| 170 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 171 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| 172 glEnable(GL_TEXTURE_2D); |
| 173 |
| 174 glActiveTexture(GL_TEXTURE1); |
| 175 glBindTexture(GL_TEXTURE_2D, textures_[1]); |
| 176 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 177 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| 178 glEnable(GL_TEXTURE_2D); |
| 179 |
| 180 glActiveTexture(GL_TEXTURE2); |
| 181 glBindTexture(GL_TEXTURE_2D, textures_[2]); |
| 182 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 183 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| 184 glEnable(GL_TEXTURE_2D); |
| 185 |
| 186 GLuint program_ = glCreateProgram(); |
| 187 |
| 188 // Create our YUV->RGB shader. |
| 189 GLuint vertex_shader_ = glCreateShader(GL_VERTEX_SHADER); |
| 190 const char* vs_source = kVertexShader; |
| 191 int vs_size = sizeof(kVertexShader); |
| 192 glShaderSource(vertex_shader_, 1, &vs_source, &vs_size); |
| 193 glCompileShader(vertex_shader_); |
| 194 int result = GL_FALSE; |
| 195 glGetShaderiv(vertex_shader_, GL_COMPILE_STATUS, &result); |
| 196 if (!result) { |
| 197 char log[kErrorSize]; |
| 198 int len; |
| 199 glGetShaderInfoLog(vertex_shader_, kErrorSize - 1, &len, log); |
| 200 log[kErrorSize - 1] = 0; |
| 201 LOG(FATAL) << log; |
| 202 } |
| 203 glAttachShader(program_, vertex_shader_); |
| 204 |
| 205 GLuint fragment_shader_ = glCreateShader(GL_FRAGMENT_SHADER); |
| 206 const char* ps_source = kFragmentShader; |
| 207 int ps_size = sizeof(kFragmentShader); |
| 208 glShaderSource(fragment_shader_, 1, &ps_source, &ps_size); |
| 209 glCompileShader(fragment_shader_); |
| 210 result = GL_FALSE; |
| 211 glGetShaderiv(fragment_shader_, GL_COMPILE_STATUS, &result); |
| 212 if (!result) { |
| 213 char log[kErrorSize]; |
| 214 int len; |
| 215 glGetShaderInfoLog(fragment_shader_, kErrorSize - 1, &len, log); |
| 216 log[kErrorSize - 1] = 0; |
| 217 LOG(FATAL) << log; |
| 218 } |
| 219 glAttachShader(program_, fragment_shader_); |
| 220 |
| 221 glLinkProgram(program_); |
| 222 result = GL_FALSE; |
| 223 glGetProgramiv(program_, GL_LINK_STATUS, &result); |
| 224 if (!result) { |
| 225 char log[kErrorSize]; |
| 226 int len; |
| 227 glGetProgramInfoLog(program_, kErrorSize - 1, &len, log); |
| 228 log[kErrorSize - 1] = 0; |
| 229 LOG(FATAL) << log; |
| 230 } |
| 231 glUseProgram(program_); |
| 232 |
| 233 // Bind parameters. |
| 234 glUniform1i(glGetUniformLocation(program_, "y_tex"), 0); |
| 235 glUniform1i(glGetUniformLocation(program_, "u_tex"), 1); |
| 236 glUniform1i(glGetUniformLocation(program_, "v_tex"), 2); |
| 237 int yuv2rgb_location = glGetUniformLocation(program_, "yuv2rgb"); |
| 238 glUniformMatrix3fv(yuv2rgb_location, 1, GL_TRUE, kYUV2RGB); |
| 239 |
| 240 int pos_location = glGetAttribLocation(program_, "in_pos"); |
| 241 glEnableVertexAttribArray(pos_location); |
| 242 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices); |
| 243 |
| 244 int tc_location = glGetAttribLocation(program_, "in_tc"); |
| 245 glEnableVertexAttribArray(tc_location); |
| 246 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, |
| 247 kTextureCoords); |
| 248 |
| 249 // We are getting called on a thread. Release the context so that it can be |
| 250 // made current on the main thread. |
| 251 glXMakeCurrent(display_, 0, NULL); |
| 252 |
| 253 // Save this instance. |
| 254 DCHECK(!instance_); |
| 255 instance_ = this; |
| 256 return true; |
| 257 } |
| 258 |
| 259 void GlVideoRenderer::OnFrameAvailable() { |
| 260 AutoLock auto_lock(lock_); |
| 261 new_frame_ = true; |
| 262 } |
| 263 |
| 264 void GlVideoRenderer::Paint() { |
| 265 // Use |new_frame_| to prevent overdraw since Paint() is called more |
| 266 // often than needed. It is OK to lock only this flag and we don't |
| 267 // want to lock the whole function because this method takes a long |
| 268 // time to complete. |
| 269 { |
| 270 AutoLock auto_lock(lock_); |
| 271 if (!new_frame_) |
| 272 return; |
| 273 new_frame_ = false; |
| 274 } |
| 275 |
| 276 scoped_refptr<media::VideoFrame> video_frame; |
| 277 GetCurrentFrame(&video_frame); |
| 278 |
| 279 if (!video_frame) |
| 280 return; |
| 281 |
| 282 // Convert YUV frame to RGB. |
| 283 media::VideoSurface frame_in; |
| 284 if (video_frame->Lock(&frame_in)) { |
| 285 DCHECK(frame_in.format == media::VideoSurface::YV12 || |
| 286 frame_in.format == media::VideoSurface::YV16); |
| 287 DCHECK(frame_in.strides[media::VideoSurface::kUPlane] == |
| 288 frame_in.strides[media::VideoSurface::kVPlane]); |
| 289 DCHECK(frame_in.planes == media::VideoSurface::kNumYUVPlanes); |
| 290 |
| 291 if (glXGetCurrentContext() != gl_context_ || |
| 292 glXGetCurrentDrawable() != window_) { |
| 293 glXMakeCurrent(display_, window_, gl_context_); |
| 294 } |
| 295 for (unsigned int i = 0; i < media::VideoSurface::kNumYUVPlanes; ++i) { |
| 296 unsigned int width = (i == media::VideoSurface::kYPlane) ? |
| 297 frame_in.width : frame_in.width / 2; |
| 298 unsigned int height = (i == media::VideoSurface::kYPlane || |
| 299 frame_in.format == media::VideoSurface::YV16) ? |
| 300 frame_in.height : frame_in.height / 2; |
| 301 glActiveTexture(GL_TEXTURE0 + i); |
| 302 glPixelStorei(GL_UNPACK_ROW_LENGTH, frame_in.strides[i]); |
| 303 glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, |
| 304 GL_LUMINANCE, GL_UNSIGNED_BYTE, frame_in.data[i]); |
| 305 } |
| 306 video_frame->Unlock(); |
| 307 } else { |
| 308 NOTREACHED(); |
| 309 } |
| 310 |
| 311 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| 312 glXSwapBuffers(display_, window_); |
| 313 } |
OLD | NEW |