1 // Copyright 2013 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.
5 #include "content/common/gpu/media/rendering_helper.h"
8 #include "base/mac/scoped_nsautorelease_pool.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/strings/stringize_macros.h"
11 #include "base/synchronization/waitable_event.h"
12 #include "ui/gl/gl_context.h"
13 #include "ui/gl/gl_context_stub_with_extensions.h"
14 #include "ui/gl/gl_implementation.h"
15 #include "ui/gl/gl_surface.h"
18 typedef GLXWindow NativeWindowType;
20 void operator()(void* x) const { ::XFree(x); }
23 typedef EGLNativeWindowType NativeWindowType;
26 // Helper for Shader creation.
27 static void CreateShader(GLuint program,
31 GLuint shader = glCreateShader(type);
32 glShaderSource(shader, 1, &source, &size);
33 glCompileShader(shader);
34 int result = GL_FALSE;
35 glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
38 glGetShaderInfoLog(shader, arraysize(log), NULL, log);
41 glAttachShader(program, shader);
42 glDeleteShader(shader);
43 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
48 RenderingHelperParams::RenderingHelperParams() {}
50 RenderingHelperParams::~RenderingHelperParams() {}
52 static const gfx::GLImplementation kGLImplementation =
53 #if defined(GL_VARIANT_GLX)
54 gfx::kGLImplementationDesktopGL;
55 #elif defined(GL_VARIANT_EGL)
56 gfx::kGLImplementationEGLGLES2;
59 #error "Unknown GL implementation."
62 RenderingHelper::RenderingHelper() {
66 RenderingHelper::~RenderingHelper() {
67 CHECK_EQ(window_dimensions_.size(), 0U) <<
68 "Must call UnInitialize before dtor.";
72 void RenderingHelper::MakeCurrent(int window_id) {
75 CHECK(glXMakeContextCurrent(x_display_, GLX_NONE, GLX_NONE, NULL));
77 CHECK(glXMakeContextCurrent(
78 x_display_, x_windows_[window_id], x_windows_[window_id], gl_context_));
82 CHECK(eglMakeCurrent(gl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
83 EGL_NO_CONTEXT)) << eglGetError();
85 CHECK(eglMakeCurrent(gl_display_, gl_surfaces_[window_id],
86 gl_surfaces_[window_id], gl_context_))
92 void RenderingHelper::Initialize(const RenderingHelperParams& params,
93 base::WaitableEvent* done) {
94 // Use window_dimensions_.size() != 0 as a proxy for the class having already
95 // been Initialize()'d, and UnInitialize() before continuing.
96 if (window_dimensions_.size()) {
97 base::WaitableEvent done(false, false);
102 gfx::InitializeStaticGLBindings(kGLImplementation);
103 scoped_refptr<gfx::GLContextStubWithExtensions> stub_context(
104 new gfx::GLContextStubWithExtensions());
106 CHECK_GT(params.window_dimensions.size(), 0U);
107 CHECK_EQ(params.frame_dimensions.size(), params.window_dimensions.size());
108 window_dimensions_ = params.window_dimensions;
109 frame_dimensions_ = params.frame_dimensions;
110 render_as_thumbnails_ = params.render_as_thumbnails;
111 message_loop_ = base::MessageLoop::current();
112 CHECK_GT(params.num_windows, 0);
115 x_display_ = base::MessagePumpForUI::GetDefaultXDisplay();
117 CHECK(glXQueryVersion(x_display_, NULL, NULL));
118 const int fbconfig_attr[] = {
119 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
120 GLX_RENDER_TYPE, GLX_RGBA_BIT,
121 GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
122 GLX_BIND_TO_TEXTURE_RGB_EXT, GL_TRUE,
123 GLX_DOUBLEBUFFER, True,
127 scoped_ptr<GLXFBConfig, XFreeDeleter> glx_fb_configs(
128 glXChooseFBConfig(x_display_, DefaultScreen(x_display_), fbconfig_attr,
130 CHECK(glx_fb_configs.get());
131 CHECK_GT(num_fbconfigs, 0);
132 x_visual_ = glXGetVisualFromFBConfig(x_display_, glx_fb_configs.get()[0]);
134 gl_context_ = glXCreateContext(x_display_, x_visual_, 0, true);
136 stub_context->AddExtensionsString(
137 reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)));
138 stub_context->SetGLVersionString(
139 reinterpret_cast<const char*>(glGetString(GL_VERSION)));
142 EGLNativeDisplayType native_display;
145 native_display = EGL_DEFAULT_DISPLAY;
147 x_display_ = base::MessagePumpForUI::GetDefaultXDisplay();
149 native_display = x_display_;
152 gl_display_ = eglGetDisplay(native_display);
154 CHECK(eglInitialize(gl_display_, NULL, NULL)) << glGetError();
156 static EGLint rgba8888[] = {
161 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
164 EGLConfig egl_config;
166 CHECK(eglChooseConfig(gl_display_, rgba8888, &egl_config, 1, &num_configs))
168 CHECK_GE(num_configs, 1);
169 static EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
170 gl_context_ = eglCreateContext(
171 gl_display_, egl_config, EGL_NO_CONTEXT, context_attribs);
172 CHECK_NE(gl_context_, EGL_NO_CONTEXT) << eglGetError();
173 stub_context->AddExtensionsString(
174 reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)));
175 stub_context->AddExtensionsString(
176 eglQueryString(gl_display_, EGL_EXTENSIONS));
177 stub_context->SetGLVersionString(
178 reinterpret_cast<const char*>(glGetString(GL_VERSION)));
181 // Per-window/surface X11 & EGL initialization.
182 for (int i = 0; i < params.num_windows; ++i) {
183 // Arrange X windows whimsically, with some padding.
184 int j = i % window_dimensions_.size();
185 int width = window_dimensions_[j].width();
186 int height = window_dimensions_[j].height();
189 int top_left_x = (width + 20) * (i % 4);
190 int top_left_y = (height + 12) * (i % 3);
193 NativeWindowType window =
194 CreateWindowEx(0, L"Static", L"VideoDecodeAcceleratorTest",
195 WS_OVERLAPPEDWINDOW | WS_VISIBLE, top_left_x,
196 top_left_y, width, height, NULL, NULL, NULL,
198 CHECK(window != NULL);
199 windows_.push_back(window);
201 int depth = DefaultDepth(x_display_, DefaultScreen(x_display_));
203 #if defined(GL_VARIANT_GLX)
204 CHECK_EQ(depth, x_visual_->depth);
207 XSetWindowAttributes window_attributes;
208 window_attributes.background_pixel =
209 BlackPixel(x_display_, DefaultScreen(x_display_));
210 window_attributes.override_redirect = true;
212 NativeWindowType window = XCreateWindow(
213 x_display_, DefaultRootWindow(x_display_),
214 top_left_x, top_left_y, width, height,
215 0 /* border width */,
216 depth, CopyFromParent /* class */, CopyFromParent /* visual */,
217 (CWBackPixel | CWOverrideRedirect), &window_attributes);
218 XStoreName(x_display_, window, "VideoDecodeAcceleratorTest");
219 XSelectInput(x_display_, window, ExposureMask);
220 XMapWindow(x_display_, window);
221 x_windows_.push_back(window);
225 EGLSurface egl_surface =
226 eglCreateWindowSurface(gl_display_, egl_config, window, NULL);
227 gl_surfaces_.push_back(egl_surface);
228 CHECK_NE(egl_surface, EGL_NO_SURFACE);
233 // Must be done after a context is made current.
234 gfx::InitializeDynamicGLBindings(kGLImplementation, stub_context.get());
236 if (render_as_thumbnails_) {
237 CHECK_EQ(window_dimensions_.size(), 1U);
239 GLint max_texture_size;
240 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
241 CHECK_GE(max_texture_size, params.thumbnails_page_size.width());
242 CHECK_GE(max_texture_size, params.thumbnails_page_size.height());
244 thumbnails_fbo_size_ = params.thumbnails_page_size;
245 thumbnail_size_ = params.thumbnail_size;
247 glGenFramebuffersEXT(1, &thumbnails_fbo_id_);
248 glGenTextures(1, &thumbnails_texture_id_);
249 glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
250 glTexImage2D(GL_TEXTURE_2D,
253 thumbnails_fbo_size_.width(),
254 thumbnails_fbo_size_.height(),
257 GL_UNSIGNED_SHORT_5_6_5,
259 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
260 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
261 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
262 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
264 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
265 glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
266 GL_COLOR_ATTACHMENT0,
268 thumbnails_texture_id_,
271 GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
272 CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status;
273 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
274 glClear(GL_COLOR_BUFFER_BIT);
275 glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
278 // These vertices and texture coords. map (0,0) in the texture to the
279 // bottom left of the viewport. Since we get the video frames with the
280 // the top left at (0,0) we need to flip the texture y coordinate
281 // in the vertex shader for this to be rendered the right way up.
282 // In the case of thumbnail rendering we use the same vertex shader
283 // to render the FBO the screen, where we do not want this flipping.
284 static const float kVertices[] =
285 { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, };
286 static const float kTextureCoords[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
287 static const char kVertexShader[] = STRINGIZE(
288 varying vec2 interp_tc;
289 attribute vec4 in_pos;
290 attribute vec2 in_tc;
291 uniform bool tex_flip;
294 interp_tc = vec2(in_tc.x, 1.0 - in_tc.y);
297 gl_Position = in_pos;
301 static const char kFragmentShader[] =
302 "#extension GL_OES_EGL_image_external : enable\n"
303 "precision mediump float;\n"
304 "varying vec2 interp_tc;\n"
305 "uniform sampler2D tex;\n"
306 "#ifdef GL_OES_EGL_image_external\n"
307 "uniform samplerExternalOES tex_external;\n"
310 " vec4 color = texture2D(tex, interp_tc);\n"
311 "#ifdef GL_OES_EGL_image_external\n"
312 " color += texture2D(tex_external, interp_tc);\n"
314 " gl_FragColor = color;\n"
317 static const char kFragmentShader[] = STRINGIZE(
318 varying vec2 interp_tc;
319 uniform sampler2D tex;
321 gl_FragColor = texture2D(tex, interp_tc);
324 program_ = glCreateProgram();
326 program_, GL_VERTEX_SHADER, kVertexShader, arraysize(kVertexShader));
327 CreateShader(program_,
330 arraysize(kFragmentShader));
331 glLinkProgram(program_);
332 int result = GL_FALSE;
333 glGetProgramiv(program_, GL_LINK_STATUS, &result);
336 glGetShaderInfoLog(program_, arraysize(log), NULL, log);
339 glUseProgram(program_);
340 glDeleteProgram(program_);
342 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
343 glUniform1i(glGetUniformLocation(program_, "tex"), 0);
344 GLint tex_external = glGetUniformLocation(program_, "tex_external");
345 if (tex_external != -1) {
346 glUniform1i(tex_external, 1);
348 int pos_location = glGetAttribLocation(program_, "in_pos");
349 glEnableVertexAttribArray(pos_location);
350 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
351 int tc_location = glGetAttribLocation(program_, "in_tc");
352 glEnableVertexAttribArray(tc_location);
353 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords);
357 void RenderingHelper::UnInitialize(base::WaitableEvent* done) {
358 CHECK_EQ(base::MessageLoop::current(), message_loop_);
359 if (render_as_thumbnails_) {
360 glDeleteTextures(1, &thumbnails_texture_id_);
361 glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_);
365 glXDestroyContext(x_display_, gl_context_);
368 CHECK(eglDestroyContext(gl_display_, gl_context_));
369 for (size_t i = 0; i < gl_surfaces_.size(); ++i)
370 CHECK(eglDestroySurface(gl_display_, gl_surfaces_[i]));
371 CHECK(eglTerminate(gl_display_));
373 gfx::ClearGLBindings();
378 void RenderingHelper::CreateTexture(int window_id,
379 uint32 texture_target,
381 base::WaitableEvent* done) {
382 if (base::MessageLoop::current() != message_loop_) {
383 message_loop_->PostTask(
385 base::Bind(&RenderingHelper::CreateTexture, base::Unretained(this),
386 window_id, texture_target, texture_id, done));
389 MakeCurrent(window_id);
390 glGenTextures(1, texture_id);
391 glBindTexture(texture_target, *texture_id);
392 int dimensions_id = window_id % frame_dimensions_.size();
393 if (texture_target == GL_TEXTURE_2D) {
394 glTexImage2D(GL_TEXTURE_2D,
397 frame_dimensions_[dimensions_id].width(),
398 frame_dimensions_[dimensions_id].height(),
404 glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
405 glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
406 // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
407 glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
408 glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
409 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
410 CHECK(texture_id_to_surface_index_.insert(
411 std::make_pair(*texture_id, window_id)).second);
415 void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) {
416 CHECK_EQ(base::MessageLoop::current(), message_loop_);
417 size_t window_id = texture_id_to_surface_index_[texture_id];
418 MakeCurrent(window_id);
420 int dimensions_id = window_id % window_dimensions_.size();
421 int width = window_dimensions_[dimensions_id].width();
422 int height = window_dimensions_[dimensions_id].height();
424 if (render_as_thumbnails_) {
425 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
426 const int thumbnails_in_row =
427 thumbnails_fbo_size_.width() / thumbnail_size_.width();
428 const int thumbnails_in_column =
429 thumbnails_fbo_size_.height() / thumbnail_size_.height();
430 const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column;
431 const int col = frame_count_ % thumbnails_in_row;
432 const int x = col * thumbnail_size_.width();
433 const int y = row * thumbnail_size_.height();
435 glViewport(x, y, thumbnail_size_.width(), thumbnail_size_.height());
436 glScissor(x, y, thumbnail_size_.width(), thumbnail_size_.height());
437 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
439 glViewport(0, 0, width, height);
440 glScissor(0, 0, width, height);
441 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1);
444 // Unbound texture samplers default to (0, 0, 0, 1). Use this fact to switch
445 // between GL_TEXTURE_2D and GL_TEXTURE_EXTERNAL_OES as appopriate.
446 if (texture_target == GL_TEXTURE_2D) {
447 glActiveTexture(GL_TEXTURE0 + 0);
448 glBindTexture(GL_TEXTURE_2D, texture_id);
449 glActiveTexture(GL_TEXTURE0 + 1);
450 glBindTexture(texture_target, 0);
451 } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) {
452 glActiveTexture(GL_TEXTURE0 + 0);
453 glBindTexture(GL_TEXTURE_2D, 0);
454 glActiveTexture(GL_TEXTURE0 + 1);
455 glBindTexture(texture_target, texture_id);
457 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
458 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
462 if (render_as_thumbnails_) {
463 // Copy from FBO to screen
464 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1);
465 glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
466 glViewport(0, 0, width, height);
467 glScissor(0, 0, width, height);
468 glActiveTexture(GL_TEXTURE0 + 0);
469 glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
470 glActiveTexture(GL_TEXTURE0 + 1);
471 glBindTexture(texture_target, 0);
472 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
476 glXSwapBuffers(x_display_, x_windows_[window_id]);
478 eglSwapBuffers(gl_display_, gl_surfaces_[window_id]);
479 CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS);
483 void RenderingHelper::DeleteTexture(uint32 texture_id) {
484 glDeleteTextures(1, &texture_id);
485 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
488 void* RenderingHelper::GetGLDisplay() {
496 void RenderingHelper::Clear() {
497 window_dimensions_.clear();
498 frame_dimensions_.clear();
499 texture_id_to_surface_index_.clear();
500 message_loop_ = NULL;
503 gl_display_ = EGL_NO_DISPLAY;
504 gl_surfaces_.clear();
506 render_as_thumbnails_ = false;
508 thumbnails_fbo_id_ = 0;
509 thumbnails_texture_id_ = 0;
512 for (size_t i = 0; i < windows_.size(); ++i) {
513 DestroyWindow(windows_[i]);
517 // Destroy resources acquired in Initialize, in reverse-acquisition order.
518 for (size_t i = 0; i < x_windows_.size(); ++i) {
519 CHECK(XUnmapWindow(x_display_, x_windows_[i]));
520 CHECK(XDestroyWindow(x_display_, x_windows_[i]));
522 // Mimic newly created object.
528 void RenderingHelper::GetThumbnailsAsRGB(std::vector<unsigned char>* rgb,
530 base::WaitableEvent* done) {
531 CHECK(render_as_thumbnails_);
533 const size_t num_pixels = thumbnails_fbo_size_.GetArea();
534 std::vector<unsigned char> rgba;
535 rgba.resize(num_pixels * 4);
536 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
537 glPixelStorei(GL_PACK_ALIGNMENT, 1);
538 // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support.
541 thumbnails_fbo_size_.width(),
542 thumbnails_fbo_size_.height(),
546 glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
547 rgb->resize(num_pixels * 3);
548 // Drop the alpha channel, but check as we go that it is all 0xff.
550 unsigned char* rgb_ptr = &((*rgb)[0]);
551 unsigned char* rgba_ptr = &rgba[0];
552 for (size_t i = 0; i < num_pixels; ++i) {
553 *rgb_ptr++ = *rgba_ptr++;
554 *rgb_ptr++ = *rgba_ptr++;
555 *rgb_ptr++ = *rgba_ptr++;
556 solid = solid && (*rgba_ptr == 0xff);
559 *alpha_solid = solid;
564 } // namespace content