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.
5 #include "content/common/gpu/media/rendering_helper.h"
10 #include "base/mac/scoped_nsautorelease_pool.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/stringize_macros.h"
13 #include "base/synchronization/waitable_event.h"
14 #include "ui/gl/gl_bindings.h"
15 #include "ui/gl/gl_context.h"
16 #include "ui/gl/gl_context_stub.h"
17 #include "ui/gl/gl_implementation.h"
18 #include "ui/gl/gl_surface.h"
20 #if !defined(OS_WIN) && defined(ARCH_CPU_X86_FAMILY)
21 #define GL_VARIANT_GLX 1
22 typedef GLXWindow NativeWindowType
;
23 typedef GLXContext NativeContextType
;
24 struct ScopedPtrXFree
{
25 void operator()(void* x
) const { ::XFree(x
); }
28 #define GL_VARIANT_EGL 1
29 typedef EGLNativeWindowType NativeWindowType
;
30 typedef EGLContext NativeContextType
;
31 typedef EGLSurface NativeSurfaceType
;
34 // Helper for Shader creation.
35 static void CreateShader(GLuint program
,
39 GLuint shader
= glCreateShader(type
);
40 glShaderSource(shader
, 1, &source
, &size
);
41 glCompileShader(shader
);
42 int result
= GL_FALSE
;
43 glGetShaderiv(shader
, GL_COMPILE_STATUS
, &result
);
46 glGetShaderInfoLog(shader
, arraysize(log
), NULL
, log
);
49 glAttachShader(program
, shader
);
50 glDeleteShader(shader
);
51 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR
);
56 // Lightweight GLContext stub implementation that returns a constructed
57 // extensions string. We use this to create a context that we can use to
58 // initialize GL extensions with, without actually creating a platform context.
59 class GLContextStubWithExtensions
: public gfx::GLContextStub
{
61 GLContextStubWithExtensions() {}
62 virtual std::string
GetExtensions() OVERRIDE
;
64 void AddExtensionsString(const char* extensions
);
67 virtual ~GLContextStubWithExtensions() {}
70 std::string extensions_
;
72 DISALLOW_COPY_AND_ASSIGN(GLContextStubWithExtensions
);
75 void GLContextStubWithExtensions::AddExtensionsString(const char* extensions
) {
76 if (extensions
== NULL
)
79 if (extensions_
.size() != 0)
81 extensions_
+= extensions
;
84 std::string
GLContextStubWithExtensions::GetExtensions() {
92 RenderingHelperParams::RenderingHelperParams() {}
94 RenderingHelperParams::~RenderingHelperParams() {}
96 class RenderingHelperGL
: public RenderingHelper
{
99 virtual ~RenderingHelperGL();
101 // Implement RenderingHelper.
102 virtual void Initialize(const RenderingHelperParams
& params
,
103 base::WaitableEvent
* done
) OVERRIDE
;
104 virtual void UnInitialize(base::WaitableEvent
* done
) OVERRIDE
;
105 virtual void CreateTexture(int window_id
,
106 uint32 texture_target
,
108 base::WaitableEvent
* done
) OVERRIDE
;
109 virtual void RenderTexture(uint32 texture_id
) OVERRIDE
;
110 virtual void DeleteTexture(uint32 texture_id
) OVERRIDE
;
111 virtual void* GetGLContext() OVERRIDE
;
112 virtual void* GetGLDisplay() OVERRIDE
;
113 virtual void GetThumbnailsAsRGB(std::vector
<unsigned char>* rgb
,
115 base::WaitableEvent
* done
) OVERRIDE
;
117 static const gfx::GLImplementation kGLImplementation
;
122 // Make window_id's surface current w/ the GL context, or release the context
123 // if |window_id < 0|.
124 void MakeCurrent(int window_id
);
127 base::MessageLoop
* message_loop_
;
128 std::vector
<gfx::Size
> window_dimensions_
;
129 std::vector
<gfx::Size
> frame_dimensions_
;
131 NativeContextType gl_context_
;
132 std::map
<uint32
, int> texture_id_to_surface_index_
;
134 #if defined(GL_VARIANT_EGL)
135 EGLDisplay gl_display_
;
136 std::vector
<NativeSurfaceType
> gl_surfaces_
;
138 XVisualInfo
* x_visual_
;
142 std::vector
<HWND
> windows_
;
145 std::vector
<Window
> x_windows_
;
148 bool render_as_thumbnails_
;
150 GLuint thumbnails_fbo_id_
;
151 GLuint thumbnails_texture_id_
;
152 gfx::Size thumbnails_fbo_size_
;
153 gfx::Size thumbnail_size_
;
158 const gfx::GLImplementation
RenderingHelperGL::kGLImplementation
=
159 #if defined(GL_VARIANT_GLX)
160 gfx::kGLImplementationDesktopGL
;
161 #elif defined(GL_VARIANT_EGL)
162 gfx::kGLImplementationEGLGLES2
;
165 #error "Unknown GL implementation."
169 RenderingHelper
* RenderingHelper::Create() {
170 return new RenderingHelperGL
;
173 RenderingHelperGL::RenderingHelperGL() {
177 RenderingHelperGL::~RenderingHelperGL() {
178 CHECK_EQ(window_dimensions_
.size(), 0U) <<
179 "Must call UnInitialize before dtor.";
183 void RenderingHelperGL::MakeCurrent(int window_id
) {
186 CHECK(glXMakeContextCurrent(x_display_
, GLX_NONE
, GLX_NONE
, NULL
));
188 CHECK(glXMakeContextCurrent(
189 x_display_
, x_windows_
[window_id
], x_windows_
[window_id
], gl_context_
));
193 CHECK(eglMakeCurrent(gl_display_
, EGL_NO_SURFACE
, EGL_NO_SURFACE
,
194 EGL_NO_CONTEXT
)) << eglGetError();
196 CHECK(eglMakeCurrent(gl_display_
, gl_surfaces_
[window_id
],
197 gl_surfaces_
[window_id
], gl_context_
))
203 void RenderingHelperGL::Initialize(const RenderingHelperParams
& params
,
204 base::WaitableEvent
* done
) {
205 // Use window_dimensions_.size() != 0 as a proxy for the class having already
206 // been Initialize()'d, and UnInitialize() before continuing.
207 if (window_dimensions_
.size()) {
208 base::WaitableEvent
done(false, false);
213 gfx::InitializeGLBindings(RenderingHelperGL::kGLImplementation
);
214 scoped_refptr
<GLContextStubWithExtensions
> stub_context(
215 new GLContextStubWithExtensions());
217 CHECK_GT(params
.window_dimensions
.size(), 0U);
218 CHECK_EQ(params
.frame_dimensions
.size(), params
.window_dimensions
.size());
219 window_dimensions_
= params
.window_dimensions
;
220 frame_dimensions_
= params
.frame_dimensions
;
221 render_as_thumbnails_
= params
.render_as_thumbnails
;
222 message_loop_
= base::MessageLoop::current();
223 CHECK_GT(params
.num_windows
, 0);
226 x_display_
= base::MessagePumpForUI::GetDefaultXDisplay();
228 CHECK(glXQueryVersion(x_display_
, NULL
, NULL
));
229 const int fbconfig_attr
[] = {
230 GLX_DRAWABLE_TYPE
, GLX_WINDOW_BIT
,
231 GLX_RENDER_TYPE
, GLX_RGBA_BIT
,
232 GLX_BIND_TO_TEXTURE_TARGETS_EXT
, GLX_TEXTURE_2D_BIT_EXT
,
233 GLX_BIND_TO_TEXTURE_RGB_EXT
, GL_TRUE
,
234 GLX_DOUBLEBUFFER
, True
,
238 scoped_ptr_malloc
<GLXFBConfig
, ScopedPtrXFree
> glx_fb_configs(
239 glXChooseFBConfig(x_display_
, DefaultScreen(x_display_
), fbconfig_attr
,
241 CHECK(glx_fb_configs
.get());
242 CHECK_GT(num_fbconfigs
, 0);
243 x_visual_
= glXGetVisualFromFBConfig(x_display_
, glx_fb_configs
.get()[0]);
245 gl_context_
= glXCreateContext(x_display_
, x_visual_
, 0, true);
247 stub_context
->AddExtensionsString(
248 reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS
)));
251 EGLNativeDisplayType native_display
;
254 native_display
= EGL_DEFAULT_DISPLAY
;
256 x_display_
= base::MessagePumpForUI::GetDefaultXDisplay();
258 native_display
= x_display_
;
261 gl_display_
= eglGetDisplay(native_display
);
263 CHECK(eglInitialize(gl_display_
, NULL
, NULL
)) << glGetError();
265 static EGLint rgba8888
[] = {
270 EGL_SURFACE_TYPE
, EGL_WINDOW_BIT
,
273 EGLConfig egl_config
;
275 CHECK(eglChooseConfig(gl_display_
, rgba8888
, &egl_config
, 1, &num_configs
))
277 CHECK_GE(num_configs
, 1);
278 static EGLint context_attribs
[] = {EGL_CONTEXT_CLIENT_VERSION
, 2, EGL_NONE
};
279 gl_context_
= eglCreateContext(
280 gl_display_
, egl_config
, EGL_NO_CONTEXT
, context_attribs
);
281 CHECK_NE(gl_context_
, EGL_NO_CONTEXT
) << eglGetError();
282 stub_context
->AddExtensionsString(
283 reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS
)));
284 stub_context
->AddExtensionsString(
285 eglQueryString(gl_display_
, EGL_EXTENSIONS
));
288 // Per-window/surface X11 & EGL initialization.
289 for (int i
= 0; i
< params
.num_windows
; ++i
) {
290 // Arrange X windows whimsically, with some padding.
291 int j
= i
% window_dimensions_
.size();
292 int width
= window_dimensions_
[j
].width();
293 int height
= window_dimensions_
[j
].height();
296 int top_left_x
= (width
+ 20) * (i
% 4);
297 int top_left_y
= (height
+ 12) * (i
% 3);
300 NativeWindowType window
=
301 CreateWindowEx(0, L
"Static", L
"VideoDecodeAcceleratorTest",
302 WS_OVERLAPPEDWINDOW
| WS_VISIBLE
, top_left_x
,
303 top_left_y
, width
, height
, NULL
, NULL
, NULL
,
305 CHECK(window
!= NULL
);
306 windows_
.push_back(window
);
308 int depth
= DefaultDepth(x_display_
, DefaultScreen(x_display_
));
310 #if defined(GL_VARIANT_GLX)
311 CHECK_EQ(depth
, x_visual_
->depth
);
314 XSetWindowAttributes window_attributes
;
315 window_attributes
.background_pixel
=
316 BlackPixel(x_display_
, DefaultScreen(x_display_
));
317 window_attributes
.override_redirect
= true;
319 NativeWindowType window
= XCreateWindow(
320 x_display_
, DefaultRootWindow(x_display_
),
321 top_left_x
, top_left_y
, width
, height
,
322 0 /* border width */,
323 depth
, CopyFromParent
/* class */, CopyFromParent
/* visual */,
324 (CWBackPixel
| CWOverrideRedirect
), &window_attributes
);
325 XStoreName(x_display_
, window
, "VideoDecodeAcceleratorTest");
326 XSelectInput(x_display_
, window
, ExposureMask
);
327 XMapWindow(x_display_
, window
);
328 x_windows_
.push_back(window
);
332 NativeSurfaceType egl_surface
=
333 eglCreateWindowSurface(gl_display_
, egl_config
, window
, NULL
);
334 gl_surfaces_
.push_back(egl_surface
);
335 CHECK_NE(egl_surface
, EGL_NO_SURFACE
);
340 // Must be done after a context is made current.
341 gfx::InitializeGLExtensionBindings(kGLImplementation
, stub_context
.get());
343 if (render_as_thumbnails_
) {
344 CHECK_EQ(window_dimensions_
.size(), 1U);
346 GLint max_texture_size
;
347 glGetIntegerv(GL_MAX_TEXTURE_SIZE
, &max_texture_size
);
348 CHECK_GE(max_texture_size
, params
.thumbnails_page_size
.width());
349 CHECK_GE(max_texture_size
, params
.thumbnails_page_size
.height());
351 thumbnails_fbo_size_
= params
.thumbnails_page_size
;
352 thumbnail_size_
= params
.thumbnail_size
;
354 glGenFramebuffersEXT(1, &thumbnails_fbo_id_
);
355 glGenTextures(1, &thumbnails_texture_id_
);
356 glBindTexture(GL_TEXTURE_2D
, thumbnails_texture_id_
);
357 glTexImage2D(GL_TEXTURE_2D
,
360 thumbnails_fbo_size_
.width(),
361 thumbnails_fbo_size_
.height(),
364 GL_UNSIGNED_SHORT_5_6_5
,
366 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_CLAMP_TO_EDGE
);
367 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_CLAMP_TO_EDGE
);
368 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
);
369 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
);
371 glBindFramebufferEXT(GL_FRAMEBUFFER
, thumbnails_fbo_id_
);
372 glFramebufferTexture2DEXT(GL_FRAMEBUFFER
,
373 GL_COLOR_ATTACHMENT0
,
375 thumbnails_texture_id_
,
378 GLenum fb_status
= glCheckFramebufferStatusEXT(GL_FRAMEBUFFER
);
379 CHECK(fb_status
== GL_FRAMEBUFFER_COMPLETE
) << fb_status
;
380 glClearColor(0.0f
, 0.0f
, 0.0f
, 1.0f
);
381 glClear(GL_COLOR_BUFFER_BIT
);
382 glBindFramebufferEXT(GL_FRAMEBUFFER
, 0);
385 // These vertices and texture coords. map (0,0) in the texture to the
386 // bottom left of the viewport. Since we get the video frames with the
387 // the top left at (0,0) we need to flip the texture y coordinate
388 // in the vertex shader for this to be rendered the right way up.
389 // In the case of thumbnail rendering we use the same vertex shader
390 // to render the FBO the screen, where we do not want this flipping.
391 static const float kVertices
[] =
392 { -1.f
, 1.f
, -1.f
, -1.f
, 1.f
, 1.f
, 1.f
, -1.f
, };
393 static const float kTextureCoords
[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
394 static const char kVertexShader
[] = STRINGIZE(
395 varying vec2 interp_tc
;
396 attribute vec4 in_pos
;
397 attribute vec2 in_tc
;
398 uniform
bool tex_flip
;
401 interp_tc
= vec2(in_tc
.x
, 1.0 - in_tc
.y
);
404 gl_Position
= in_pos
;
408 static const char kFragmentShader
[] = STRINGIZE(
409 precision mediump
float;
410 varying vec2 interp_tc
;
411 uniform sampler2D tex
;
413 gl_FragColor
= texture2D(tex
, interp_tc
);
416 static const char kFragmentShader
[] = STRINGIZE(
417 varying vec2 interp_tc
;
418 uniform sampler2D tex
;
420 gl_FragColor
= texture2D(tex
, interp_tc
);
423 program_
= glCreateProgram();
425 program_
, GL_VERTEX_SHADER
, kVertexShader
, arraysize(kVertexShader
));
426 CreateShader(program_
,
429 arraysize(kFragmentShader
));
430 glLinkProgram(program_
);
431 int result
= GL_FALSE
;
432 glGetProgramiv(program_
, GL_LINK_STATUS
, &result
);
435 glGetShaderInfoLog(program_
, arraysize(log
), NULL
, log
);
438 glUseProgram(program_
);
439 glDeleteProgram(program_
);
441 glUniform1i(glGetUniformLocation(program_
, "tex_flip"), 0);
442 glUniform1i(glGetUniformLocation(program_
, "tex"), 0);
443 int pos_location
= glGetAttribLocation(program_
, "in_pos");
444 glEnableVertexAttribArray(pos_location
);
445 glVertexAttribPointer(pos_location
, 2, GL_FLOAT
, GL_FALSE
, 0, kVertices
);
446 int tc_location
= glGetAttribLocation(program_
, "in_tc");
447 glEnableVertexAttribArray(tc_location
);
448 glVertexAttribPointer(tc_location
, 2, GL_FLOAT
, GL_FALSE
, 0, kTextureCoords
);
452 void RenderingHelperGL::UnInitialize(base::WaitableEvent
* done
) {
453 CHECK_EQ(base::MessageLoop::current(), message_loop_
);
454 if (render_as_thumbnails_
) {
455 glDeleteTextures(1, &thumbnails_texture_id_
);
456 glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_
);
460 glXDestroyContext(x_display_
, gl_context_
);
463 CHECK(eglDestroyContext(gl_display_
, gl_context_
));
464 for (size_t i
= 0; i
< gl_surfaces_
.size(); ++i
)
465 CHECK(eglDestroySurface(gl_display_
, gl_surfaces_
[i
]));
466 CHECK(eglTerminate(gl_display_
));
468 gfx::ClearGLBindings();
473 void RenderingHelperGL::CreateTexture(int window_id
,
474 uint32 texture_target
,
476 base::WaitableEvent
* done
) {
477 if (base::MessageLoop::current() != message_loop_
) {
478 message_loop_
->PostTask(
480 base::Bind(&RenderingHelper::CreateTexture
, base::Unretained(this),
481 window_id
, texture_target
, texture_id
, done
));
484 CHECK_EQ(static_cast<uint32
>(GL_TEXTURE_2D
), texture_target
);
485 MakeCurrent(window_id
);
486 glGenTextures(1, texture_id
);
487 glBindTexture(GL_TEXTURE_2D
, *texture_id
);
488 int dimensions_id
= window_id
% frame_dimensions_
.size();
489 glTexImage2D(GL_TEXTURE_2D
,
492 frame_dimensions_
[dimensions_id
].width(),
493 frame_dimensions_
[dimensions_id
].height(),
498 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
);
499 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
);
500 // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
501 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_CLAMP_TO_EDGE
);
502 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_CLAMP_TO_EDGE
);
503 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR
);
504 CHECK(texture_id_to_surface_index_
.insert(
505 std::make_pair(*texture_id
, window_id
)).second
);
509 void RenderingHelperGL::RenderTexture(uint32 texture_id
) {
510 CHECK_EQ(base::MessageLoop::current(), message_loop_
);
511 size_t window_id
= texture_id_to_surface_index_
[texture_id
];
512 MakeCurrent(window_id
);
514 int dimensions_id
= window_id
% window_dimensions_
.size();
515 int width
= window_dimensions_
[dimensions_id
].width();
516 int height
= window_dimensions_
[dimensions_id
].height();
518 if (render_as_thumbnails_
) {
519 glBindFramebufferEXT(GL_FRAMEBUFFER
, thumbnails_fbo_id_
);
520 const int thumbnails_in_row
=
521 thumbnails_fbo_size_
.width() / thumbnail_size_
.width();
522 const int thumbnails_in_column
=
523 thumbnails_fbo_size_
.height() / thumbnail_size_
.height();
524 const int row
= (frame_count_
/ thumbnails_in_row
) % thumbnails_in_column
;
525 const int col
= frame_count_
% thumbnails_in_row
;
526 const int x
= col
* thumbnail_size_
.width();
527 const int y
= row
* thumbnail_size_
.height();
529 glViewport(x
, y
, thumbnail_size_
.width(), thumbnail_size_
.height());
530 glScissor(x
, y
, thumbnail_size_
.width(), thumbnail_size_
.height());
531 glUniform1i(glGetUniformLocation(program_
, "tex_flip"), 0);
533 glViewport(0, 0, width
, height
);
534 glScissor(0, 0, width
, height
);
535 glUniform1i(glGetUniformLocation(program_
, "tex_flip"), 1);
538 glActiveTexture(GL_TEXTURE0
);
539 glBindTexture(GL_TEXTURE_2D
, texture_id
);
540 glDrawArrays(GL_TRIANGLE_STRIP
, 0, 4);
541 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR
);
545 if (render_as_thumbnails_
) {
546 // Copy from FBO to screen
547 glUniform1i(glGetUniformLocation(program_
, "tex_flip"), 1);
548 glBindFramebufferEXT(GL_FRAMEBUFFER
, 0);
549 glViewport(0, 0, width
, height
);
550 glScissor(0, 0, width
, height
);
551 glBindTexture(GL_TEXTURE_2D
, thumbnails_texture_id_
);
552 glDrawArrays(GL_TRIANGLE_STRIP
, 0, 4);
556 glXSwapBuffers(x_display_
, x_windows_
[window_id
]);
558 eglSwapBuffers(gl_display_
, gl_surfaces_
[window_id
]);
559 CHECK_EQ(static_cast<int>(eglGetError()), EGL_SUCCESS
);
563 void RenderingHelperGL::DeleteTexture(uint32 texture_id
) {
564 glDeleteTextures(1, &texture_id
);
565 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR
);
568 void* RenderingHelperGL::GetGLContext() {
572 void* RenderingHelperGL::GetGLDisplay() {
580 void RenderingHelperGL::Clear() {
581 window_dimensions_
.clear();
582 frame_dimensions_
.clear();
583 texture_id_to_surface_index_
.clear();
584 message_loop_
= NULL
;
587 gl_display_
= EGL_NO_DISPLAY
;
588 gl_surfaces_
.clear();
590 render_as_thumbnails_
= false;
592 thumbnails_fbo_id_
= 0;
593 thumbnails_texture_id_
= 0;
596 for (size_t i
= 0; i
< windows_
.size(); ++i
) {
597 DestroyWindow(windows_
[i
]);
601 // Destroy resources acquired in Initialize, in reverse-acquisition order.
602 for (size_t i
= 0; i
< x_windows_
.size(); ++i
) {
603 CHECK(XUnmapWindow(x_display_
, x_windows_
[i
]));
604 CHECK(XDestroyWindow(x_display_
, x_windows_
[i
]));
606 // Mimic newly created object.
612 void RenderingHelperGL::GetThumbnailsAsRGB(std::vector
<unsigned char>* rgb
,
614 base::WaitableEvent
* done
) {
615 CHECK(render_as_thumbnails_
);
617 const size_t num_pixels
= thumbnails_fbo_size_
.GetArea();
618 std::vector
<unsigned char> rgba
;
619 rgba
.resize(num_pixels
* 4);
620 glBindFramebufferEXT(GL_FRAMEBUFFER
, thumbnails_fbo_id_
);
621 glPixelStorei(GL_PACK_ALIGNMENT
, 1);
622 // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support.
625 thumbnails_fbo_size_
.width(),
626 thumbnails_fbo_size_
.height(),
630 rgb
->resize(num_pixels
* 3);
631 // Drop the alpha channel, but check as we go that it is all 0xff.
633 unsigned char* rgb_ptr
= &((*rgb
)[0]);
634 unsigned char* rgba_ptr
= &rgba
[0];
635 for (size_t i
= 0; i
< num_pixels
; ++i
) {
636 *rgb_ptr
++ = *rgba_ptr
++;
637 *rgb_ptr
++ = *rgba_ptr
++;
638 *rgb_ptr
++ = *rgba_ptr
++;
639 solid
= solid
&& (*rgba_ptr
== 0xff);
642 *alpha_solid
= solid
;
647 } // namespace content