1// Copyright (c) 2012 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 <X11/Xutil.h>
8
9#include "base/bind.h"
10#include "base/message_loop/message_loop.h"
11#include "media/base/buffers.h"
12#include "media/base/video_frame.h"
13#include "media/base/yuv_convert.h"
14#include "ui/gl/gl_surface.h"
15
16enum { kNumYUVPlanes = 3 };
17
18static GLXContext InitGLContext(Display* display, Window window) {
19  // Some versions of NVIDIA's GL libGL.so include a broken version of
20  // dlopen/dlsym, and so linking it into chrome breaks it. So we dynamically
21  // load it, and use glew to dynamically resolve symbols.
22  // See http://code.google.com/p/chromium/issues/detail?id=16800
23  if (!gfx::GLSurface::InitializeOneOff()) {
24    LOG(ERROR) << "GLSurface::InitializeOneOff failed";
25    return NULL;
26  }
27
28  XWindowAttributes attributes;
29  XGetWindowAttributes(display, window, &attributes);
30  XVisualInfo visual_info_template;
31  visual_info_template.visualid = XVisualIDFromVisual(attributes.visual);
32  int visual_info_count = 0;
33  XVisualInfo* visual_info_list = XGetVisualInfo(display, VisualIDMask,
34                                                 &visual_info_template,
35                                                 &visual_info_count);
36  GLXContext context = NULL;
37  for (int i = 0; i < visual_info_count && !context; ++i) {
38    context = glXCreateContext(display, visual_info_list + i, 0,
39                               True /* Direct rendering */);
40  }
41
42  XFree(visual_info_list);
43  if (!context) {
44    return NULL;
45  }
46
47  if (!glXMakeCurrent(display, window, context)) {
48    glXDestroyContext(display, context);
49    return NULL;
50  }
51
52  return context;
53}
54
55// Matrix used for the YUV to RGB conversion.
56static const float kYUV2RGB[9] = {
57  1.f, 0.f, 1.403f,
58  1.f, -.344f, -.714f,
59  1.f, 1.772f, 0.f,
60};
61
62// Vertices for a full screen quad.
63static const float kVertices[8] = {
64  -1.f, 1.f,
65  -1.f, -1.f,
66  1.f, 1.f,
67  1.f, -1.f,
68};
69
70// Pass-through vertex shader.
71static const char kVertexShader[] =
72    "varying vec2 interp_tc;\n"
73    "\n"
74    "attribute vec4 in_pos;\n"
75    "attribute vec2 in_tc;\n"
76    "\n"
77    "void main() {\n"
78    "  interp_tc = in_tc;\n"
79    "  gl_Position = in_pos;\n"
80    "}\n";
81
82// YUV to RGB pixel shader. Loads a pixel from each plane and pass through the
83// matrix.
84static const char kFragmentShader[] =
85    "varying vec2 interp_tc;\n"
86    "\n"
87    "uniform sampler2D y_tex;\n"
88    "uniform sampler2D u_tex;\n"
89    "uniform sampler2D v_tex;\n"
90    "uniform mat3 yuv2rgb;\n"
91    "\n"
92    "void main() {\n"
93    "  float y = texture2D(y_tex, interp_tc).x;\n"
94    "  float u = texture2D(u_tex, interp_tc).r - .5;\n"
95    "  float v = texture2D(v_tex, interp_tc).r - .5;\n"
96    "  vec3 rgb = yuv2rgb * vec3(y, u, v);\n"
97    "  gl_FragColor = vec4(rgb, 1);\n"
98    "}\n";
99
100// Buffer size for compile errors.
101static const unsigned int kErrorSize = 4096;
102
103GlVideoRenderer::GlVideoRenderer(Display* display, Window window)
104    : display_(display),
105      window_(window),
106      gl_context_(NULL) {
107}
108
109GlVideoRenderer::~GlVideoRenderer() {
110  glXMakeCurrent(display_, 0, NULL);
111  glXDestroyContext(display_, gl_context_);
112}
113
114void GlVideoRenderer::Paint(
115    const scoped_refptr<media::VideoFrame>& video_frame) {
116  if (!gl_context_)
117    Initialize(video_frame->coded_size(), video_frame->visible_rect());
118
119  // Convert YUV frame to RGB.
120  DCHECK(video_frame->format() == media::VideoFrame::YV12 ||
121         video_frame->format() == media::VideoFrame::I420 ||
122         video_frame->format() == media::VideoFrame::YV16);
123  DCHECK(video_frame->stride(media::VideoFrame::kUPlane) ==
124         video_frame->stride(media::VideoFrame::kVPlane));
125
126  if (glXGetCurrentContext() != gl_context_ ||
127      glXGetCurrentDrawable() != window_) {
128    glXMakeCurrent(display_, window_, gl_context_);
129  }
130  for (unsigned int i = 0; i < kNumYUVPlanes; ++i) {
131    unsigned int width = video_frame->stride(i);
132    unsigned int height = video_frame->rows(i);
133    glActiveTexture(GL_TEXTURE0 + i);
134    glPixelStorei(GL_UNPACK_ROW_LENGTH, video_frame->stride(i));
135    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0,
136                 GL_LUMINANCE, GL_UNSIGNED_BYTE, video_frame->data(i));
137  }
138
139  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
140  glXSwapBuffers(display_, window_);
141}
142
143void GlVideoRenderer::Initialize(gfx::Size coded_size, gfx::Rect visible_rect) {
144  CHECK(!gl_context_);
145  VLOG(0) << "Initializing GL Renderer...";
146
147  // Resize the window to fit that of the video.
148  XResizeWindow(display_, window_, visible_rect.width(), visible_rect.height());
149
150  gl_context_ = InitGLContext(display_, window_);
151  CHECK(gl_context_) << "Failed to initialize GL context";
152
153  // Create 3 textures, one for each plane, and bind them to different
154  // texture units.
155  glGenTextures(3, textures_);
156  glActiveTexture(GL_TEXTURE0);
157  glBindTexture(GL_TEXTURE_2D, textures_[0]);
158  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
159  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
160  glEnable(GL_TEXTURE_2D);
161
162  glActiveTexture(GL_TEXTURE1);
163  glBindTexture(GL_TEXTURE_2D, textures_[1]);
164  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
165  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
166  glEnable(GL_TEXTURE_2D);
167
168  glActiveTexture(GL_TEXTURE2);
169  glBindTexture(GL_TEXTURE_2D, textures_[2]);
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  GLuint program = glCreateProgram();
175
176  // Create our YUV->RGB shader.
177  GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
178  const char* vs_source = kVertexShader;
179  int vs_size = sizeof(kVertexShader);
180  glShaderSource(vertex_shader, 1, &vs_source, &vs_size);
181  glCompileShader(vertex_shader);
182  int result = GL_FALSE;
183  glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &result);
184  if (!result) {
185    char log[kErrorSize];
186    int len = 0;
187    glGetShaderInfoLog(vertex_shader, kErrorSize - 1, &len, log);
188    log[kErrorSize - 1] = 0;
189    LOG(FATAL) << log;
190  }
191  glAttachShader(program, vertex_shader);
192  glDeleteShader(vertex_shader);
193
194  GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
195  const char* ps_source = kFragmentShader;
196  int ps_size = sizeof(kFragmentShader);
197  glShaderSource(fragment_shader, 1, &ps_source, &ps_size);
198  glCompileShader(fragment_shader);
199  result = GL_FALSE;
200  glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &result);
201  if (!result) {
202    char log[kErrorSize];
203    int len = 0;
204    glGetShaderInfoLog(fragment_shader, kErrorSize - 1, &len, log);
205    log[kErrorSize - 1] = 0;
206    LOG(FATAL) << log;
207  }
208  glAttachShader(program, fragment_shader);
209  glDeleteShader(fragment_shader);
210
211  glLinkProgram(program);
212  result = GL_FALSE;
213  glGetProgramiv(program, GL_LINK_STATUS, &result);
214  if (!result) {
215    char log[kErrorSize];
216    int len = 0;
217    glGetProgramInfoLog(program, kErrorSize - 1, &len, log);
218    log[kErrorSize - 1] = 0;
219    LOG(FATAL) << log;
220  }
221  glUseProgram(program);
222  glDeleteProgram(program);
223
224  // Bind parameters.
225  glUniform1i(glGetUniformLocation(program, "y_tex"), 0);
226  glUniform1i(glGetUniformLocation(program, "u_tex"), 1);
227  glUniform1i(glGetUniformLocation(program, "v_tex"), 2);
228  int yuv2rgb_location = glGetUniformLocation(program, "yuv2rgb");
229  glUniformMatrix3fv(yuv2rgb_location, 1, GL_TRUE, kYUV2RGB);
230
231  int pos_location = glGetAttribLocation(program, "in_pos");
232  glEnableVertexAttribArray(pos_location);
233  glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
234
235  int tc_location = glGetAttribLocation(program, "in_tc");
236  glEnableVertexAttribArray(tc_location);
237  float verts[8];
238  float x0 = static_cast<float>(visible_rect.x()) / coded_size.width();
239  float y0 = static_cast<float>(visible_rect.y()) / coded_size.height();
240  float x1 = static_cast<float>(visible_rect.right()) / coded_size.width();
241  float y1 = static_cast<float>(visible_rect.bottom()) / coded_size.height();
242  verts[0] = x0; verts[1] = y0;
243  verts[2] = x0; verts[3] = y1;
244  verts[4] = x1; verts[5] = y0;
245  verts[6] = x1; verts[7] = y1;
246  glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, verts);
247
248  // We are getting called on a thread. Release the context so that it can be
249  // made current on the main thread.
250  glXMakeCurrent(display_, 0, NULL);
251}
252