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.h"
14 #include "ui/gl/gl_implementation.h"
15 #include "ui/gl/gl_surface.h"
18 typedef GLXWindow NativeWindowType;
19 struct ScopedPtrXFree {
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 // Lightweight GLContext stub implementation that returns a constructed
49 // extensions string. We use this to create a context that we can use to
50 // initialize GL extensions with, without actually creating a platform context.
51 class GLContextStubWithExtensions : public gfx::GLContextStub {
53 GLContextStubWithExtensions() {}
54 virtual std::string GetExtensions() OVERRIDE;
56 void AddExtensionsString(const char* extensions);
59 virtual ~GLContextStubWithExtensions() {}
62 std::string extensions_;
64 DISALLOW_COPY_AND_ASSIGN(GLContextStubWithExtensions);
67 void GLContextStubWithExtensions::AddExtensionsString(const char* extensions) {
68 if (extensions == NULL)
71 if (extensions_.size() != 0)
73 extensions_ += extensions;
76 std::string GLContextStubWithExtensions::GetExtensions() {
84 RenderingHelperParams::RenderingHelperParams() {}
86 RenderingHelperParams::~RenderingHelperParams() {}
88 static const gfx::GLImplementation kGLImplementation =
89 #if defined(GL_VARIANT_GLX)
90 gfx::kGLImplementationDesktopGL;
91 #elif defined(GL_VARIANT_EGL)
92 gfx::kGLImplementationEGLGLES2;
95 #error "Unknown GL implementation."
98 RenderingHelper::RenderingHelper() {
102 RenderingHelper::~RenderingHelper() {
103 CHECK_EQ(window_dimensions_.size(), 0U) <<
104 "Must call UnInitialize before dtor.";
108 void RenderingHelper::MakeCurrent(int window_id) {
111 CHECK(glXMakeContextCurrent(x_display_, GLX_NONE, GLX_NONE, NULL));
113 CHECK(glXMakeContextCurrent(
114 x_display_, x_windows_[window_id], x_windows_[window_id], gl_context_));
118 CHECK(eglMakeCurrent(gl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
119 EGL_NO_CONTEXT)) << eglGetError();
121 CHECK(eglMakeCurrent(gl_display_, gl_surfaces_[window_id],
122 gl_surfaces_[window_id], gl_context_))
128 void RenderingHelper::Initialize(const RenderingHelperParams& params,
129 base::WaitableEvent* done) {
130 // Use window_dimensions_.size() != 0 as a proxy for the class having already
131 // been Initialize()'d, and UnInitialize() before continuing.
132 if (window_dimensions_.size()) {
133 base::WaitableEvent done(false, false);
138 gfx::InitializeGLBindings(kGLImplementation);
139 scoped_refptr<GLContextStubWithExtensions> stub_context(
140 new GLContextStubWithExtensions());
142 CHECK_GT(params.window_dimensions.size(), 0U);
143 CHECK_EQ(params.frame_dimensions.size(), params.window_dimensions.size());
144 window_dimensions_ = params.window_dimensions;
145 frame_dimensions_ = params.frame_dimensions;
146 render_as_thumbnails_ = params.render_as_thumbnails;
147 message_loop_ = base::MessageLoop::current();
148 CHECK_GT(params.num_windows, 0);
151 x_display_ = base::MessagePumpForUI::GetDefaultXDisplay();
153 CHECK(glXQueryVersion(x_display_, NULL, NULL));
154 const int fbconfig_attr[] = {
155 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
156 GLX_RENDER_TYPE, GLX_RGBA_BIT,
157 GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
158 GLX_BIND_TO_TEXTURE_RGB_EXT, GL_TRUE,
159 GLX_DOUBLEBUFFER, True,
163 scoped_ptr_malloc<GLXFBConfig, ScopedPtrXFree> glx_fb_configs(
164 glXChooseFBConfig(x_display_, DefaultScreen(x_display_), fbconfig_attr,
166 CHECK(glx_fb_configs.get());
167 CHECK_GT(num_fbconfigs, 0);
168 x_visual_ = glXGetVisualFromFBConfig(x_display_, glx_fb_configs.get()[0]);
170 gl_context_ = glXCreateContext(x_display_, x_visual_, 0, true);
172 stub_context->AddExtensionsString(
173 reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)));
176 EGLNativeDisplayType native_display;
179 native_display = EGL_DEFAULT_DISPLAY;
181 x_display_ = base::MessagePumpForUI::GetDefaultXDisplay();
183 native_display = x_display_;
186 gl_display_ = eglGetDisplay(native_display);
188 CHECK(eglInitialize(gl_display_, NULL, NULL)) << glGetError();
190 static EGLint rgba8888[] = {
195 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
198 EGLConfig egl_config;
200 CHECK(eglChooseConfig(gl_display_, rgba8888, &egl_config, 1, &num_configs))
202 CHECK_GE(num_configs, 1);
203 static EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
204 gl_context_ = eglCreateContext(
205 gl_display_, egl_config, EGL_NO_CONTEXT, context_attribs);
206 CHECK_NE(gl_context_, EGL_NO_CONTEXT) << eglGetError();
207 stub_context->AddExtensionsString(
208 reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)));
209 stub_context->AddExtensionsString(
210 eglQueryString(gl_display_, EGL_EXTENSIONS));
213 // Per-window/surface X11 & EGL initialization.
214 for (int i = 0; i < params.num_windows; ++i) {
215 // Arrange X windows whimsically, with some padding.
216 int j = i % window_dimensions_.size();
217 int width = window_dimensions_[j].width();
218 int height = window_dimensions_[j].height();
221 int top_left_x = (width + 20) * (i % 4);
222 int top_left_y = (height + 12) * (i % 3);
225 NativeWindowType window =
226 CreateWindowEx(0, L"Static", L"VideoDecodeAcceleratorTest",
227 WS_OVERLAPPEDWINDOW | WS_VISIBLE, top_left_x,
228 top_left_y, width, height, NULL, NULL, NULL,
230 CHECK(window != NULL);
231 windows_.push_back(window);
233 int depth = DefaultDepth(x_display_, DefaultScreen(x_display_));
235 #if defined(GL_VARIANT_GLX)
236 CHECK_EQ(depth, x_visual_->depth);
239 XSetWindowAttributes window_attributes;
240 window_attributes.background_pixel =
241 BlackPixel(x_display_, DefaultScreen(x_display_));
242 window_attributes.override_redirect = true;
244 NativeWindowType window = XCreateWindow(
245 x_display_, DefaultRootWindow(x_display_),
246 top_left_x, top_left_y, width, height,
247 0 /* border width */,
248 depth, CopyFromParent /* class */, CopyFromParent /* visual */,
249 (CWBackPixel | CWOverrideRedirect), &window_attributes);
250 XStoreName(x_display_, window, "VideoDecodeAcceleratorTest");
251 XSelectInput(x_display_, window, ExposureMask);
252 XMapWindow(x_display_, window);
253 x_windows_.push_back(window);
257 EGLSurface egl_surface =
258 eglCreateWindowSurface(gl_display_, egl_config, window, NULL);
259 gl_surfaces_.push_back(egl_surface);
260 CHECK_NE(egl_surface, EGL_NO_SURFACE);
265 // Must be done after a context is made current.
266 gfx::InitializeGLExtensionBindings(kGLImplementation, stub_context.get());
268 if (render_as_thumbnails_) {
269 CHECK_EQ(window_dimensions_.size(), 1U);
271 GLint max_texture_size;
272 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
273 CHECK_GE(max_texture_size, params.thumbnails_page_size.width());
274 CHECK_GE(max_texture_size, params.thumbnails_page_size.height());
276 thumbnails_fbo_size_ = params.thumbnails_page_size;
277 thumbnail_size_ = params.thumbnail_size;
279 glGenFramebuffersEXT(1, &thumbnails_fbo_id_);
280 glGenTextures(1, &thumbnails_texture_id_);
281 glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
282 glTexImage2D(GL_TEXTURE_2D,
285 thumbnails_fbo_size_.width(),
286 thumbnails_fbo_size_.height(),
289 GL_UNSIGNED_SHORT_5_6_5,
291 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
292 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
293 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
294 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
296 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
297 glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
298 GL_COLOR_ATTACHMENT0,
300 thumbnails_texture_id_,
303 GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
304 CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status;
305 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
306 glClear(GL_COLOR_BUFFER_BIT);
307 glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
310 // These vertices and texture coords. map (0,0) in the texture to the
311 // bottom left of the viewport. Since we get the video frames with the
312 // the top left at (0,0) we need to flip the texture y coordinate
313 // in the vertex shader for this to be rendered the right way up.
314 // In the case of thumbnail rendering we use the same vertex shader
315 // to render the FBO the screen, where we do not want this flipping.
316 static const float kVertices[] =
317 { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, };
318 static const float kTextureCoords[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
319 static const char kVertexShader[] = STRINGIZE(
320 varying vec2 interp_tc;
321 attribute vec4 in_pos;
322 attribute vec2 in_tc;
323 uniform bool tex_flip;
326 interp_tc = vec2(in_tc.x, 1.0 - in_tc.y);
329 gl_Position = in_pos;
333 static const char kFragmentShader[] = STRINGIZE(
334 precision mediump float;
335 varying vec2 interp_tc;
336 uniform sampler2D tex;
338 gl_FragColor = texture2D(tex, interp_tc);
341 static const char kFragmentShader[] = STRINGIZE(
342 varying vec2 interp_tc;
343 uniform sampler2D tex;
345 gl_FragColor = texture2D(tex, interp_tc);
348 program_ = glCreateProgram();
350 program_, GL_VERTEX_SHADER, kVertexShader, arraysize(kVertexShader));
351 CreateShader(program_,
354 arraysize(kFragmentShader));
355 glLinkProgram(program_);
356 int result = GL_FALSE;
357 glGetProgramiv(program_, GL_LINK_STATUS, &result);
360 glGetShaderInfoLog(program_, arraysize(log), NULL, log);
363 glUseProgram(program_);
364 glDeleteProgram(program_);
366 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
367 glUniform1i(glGetUniformLocation(program_, "tex"), 0);
368 int pos_location = glGetAttribLocation(program_, "in_pos");
369 glEnableVertexAttribArray(pos_location);
370 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
371 int tc_location = glGetAttribLocation(program_, "in_tc");
372 glEnableVertexAttribArray(tc_location);
373 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords);
377 void RenderingHelper::UnInitialize(base::WaitableEvent* done) {
378 CHECK_EQ(base::MessageLoop::current(), message_loop_);
379 if (render_as_thumbnails_) {
380 glDeleteTextures(1, &thumbnails_texture_id_);
381 glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_);
385 glXDestroyContext(x_display_, gl_context_);
388 CHECK(eglDestroyContext(gl_display_, gl_context_));
389 for (size_t i = 0; i < gl_surfaces_.size(); ++i)
390 CHECK(eglDestroySurface(gl_display_, gl_surfaces_[i]));
391 CHECK(eglTerminate(gl_display_));
393 gfx::ClearGLBindings();
398 void RenderingHelper::CreateTexture(int window_id,
399 uint32 texture_target,
401 base::WaitableEvent* done) {
402 if (base::MessageLoop::current() != message_loop_) {
403 message_loop_->PostTask(
405 base::Bind(&RenderingHelper::CreateTexture, base::Unretained(this),
406 window_id, texture_target, texture_id, done));
409 CHECK_EQ(static_cast<uint32>(GL_TEXTURE_2D), texture_target);
410 MakeCurrent(window_id);
411 glGenTextures(1, texture_id);
412 glBindTexture(GL_TEXTURE_2D, *texture_id);
413 int dimensions_id = window_id % frame_dimensions_.size();
414 glTexImage2D(GL_TEXTURE_2D,
417 frame_dimensions_[dimensions_id].width(),
418 frame_dimensions_[dimensions_id].height(),
423 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
424 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
425 // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
426 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
427 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
428 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
429 CHECK(texture_id_to_surface_index_.insert(
430 std::make_pair(*texture_id, window_id)).second);
434 void RenderingHelper::RenderTexture(uint32 texture_id) {
435 CHECK_EQ(base::MessageLoop::current(), message_loop_);
436 size_t window_id = texture_id_to_surface_index_[texture_id];
437 MakeCurrent(window_id);
439 int dimensions_id = window_id % window_dimensions_.size();
440 int width = window_dimensions_[dimensions_id].width();
441 int height = window_dimensions_[dimensions_id].height();
443 if (render_as_thumbnails_) {
444 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
445 const int thumbnails_in_row =
446 thumbnails_fbo_size_.width() / thumbnail_size_.width();
447 const int thumbnails_in_column =
448 thumbnails_fbo_size_.height() / thumbnail_size_.height();
449 const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column;
450 const int col = frame_count_ % thumbnails_in_row;
451 const int x = col * thumbnail_size_.width();
452 const int y = row * thumbnail_size_.height();
454 glViewport(x, y, thumbnail_size_.width(), thumbnail_size_.height());
455 glScissor(x, y, thumbnail_size_.width(), thumbnail_size_.height());
456 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
458 glViewport(0, 0, width, height);
459 glScissor(0, 0, width, height);
460 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1);
463 glActiveTexture(GL_TEXTURE0);
464 glBindTexture(GL_TEXTURE_2D, texture_id);
465 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
466 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
470 if (render_as_thumbnails_) {
471 // Copy from FBO to screen
472 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1);
473 glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
474 glViewport(0, 0, width, height);
475 glScissor(0, 0, width, height);
476 glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
477 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
481 glXSwapBuffers(x_display_, x_windows_[window_id]);
483 eglSwapBuffers(gl_display_, gl_surfaces_[window_id]);
484 CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS);
488 void RenderingHelper::DeleteTexture(uint32 texture_id) {
489 glDeleteTextures(1, &texture_id);
490 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
493 void* RenderingHelper::GetGLContext() {
497 void* RenderingHelper::GetGLDisplay() {
505 void RenderingHelper::Clear() {
506 window_dimensions_.clear();
507 frame_dimensions_.clear();
508 texture_id_to_surface_index_.clear();
509 message_loop_ = NULL;
512 gl_display_ = EGL_NO_DISPLAY;
513 gl_surfaces_.clear();
515 render_as_thumbnails_ = false;
517 thumbnails_fbo_id_ = 0;
518 thumbnails_texture_id_ = 0;
521 for (size_t i = 0; i < windows_.size(); ++i) {
522 DestroyWindow(windows_[i]);
526 // Destroy resources acquired in Initialize, in reverse-acquisition order.
527 for (size_t i = 0; i < x_windows_.size(); ++i) {
528 CHECK(XUnmapWindow(x_display_, x_windows_[i]));
529 CHECK(XDestroyWindow(x_display_, x_windows_[i]));
531 // Mimic newly created object.
537 void RenderingHelper::GetThumbnailsAsRGB(std::vector<unsigned char>* rgb,
539 base::WaitableEvent* done) {
540 CHECK(render_as_thumbnails_);
542 const size_t num_pixels = thumbnails_fbo_size_.GetArea();
543 std::vector<unsigned char> rgba;
544 rgba.resize(num_pixels * 4);
545 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
546 glPixelStorei(GL_PACK_ALIGNMENT, 1);
547 // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support.
550 thumbnails_fbo_size_.width(),
551 thumbnails_fbo_size_.height(),
555 rgb->resize(num_pixels * 3);
556 // Drop the alpha channel, but check as we go that it is all 0xff.
558 unsigned char* rgb_ptr = &((*rgb)[0]);
559 unsigned char* rgba_ptr = &rgba[0];
560 for (size_t i = 0; i < num_pixels; ++i) {
561 *rgb_ptr++ = *rgba_ptr++;
562 *rgb_ptr++ = *rgba_ptr++;
563 *rgb_ptr++ = *rgba_ptr++;
564 solid = solid && (*rgba_ptr == 0xff);
567 *alpha_solid = solid;
572 } // namespace content