Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / content / common / gpu / media / rendering_helper.cc
blobb67106e5f3205861dcea7e141a288b3ee74771f8
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"
7 #include <algorithm>
8 #include <numeric>
9 #include <vector>
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/command_line.h"
14 #include "base/mac/scoped_nsautorelease_pool.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/run_loop.h"
17 #include "base/strings/stringize_macros.h"
18 #include "base/synchronization/waitable_event.h"
19 #include "base/time/time.h"
20 #include "ui/gl/gl_context.h"
21 #include "ui/gl/gl_implementation.h"
22 #include "ui/gl/gl_surface.h"
24 #if defined(OS_WIN)
25 #include <windows.h>
26 #endif
28 #if defined(USE_X11)
29 #include "ui/gfx/x/x11_types.h"
30 #endif
32 #if defined(ARCH_CPU_X86_FAMILY) && defined(USE_X11)
33 #include "ui/gl/gl_surface_glx.h"
34 #define GL_VARIANT_GLX 1
35 #else
36 #include "ui/gl/gl_surface_egl.h"
37 #define GL_VARIANT_EGL 1
38 #endif
40 #if defined(USE_OZONE)
41 #if defined(OS_CHROMEOS)
42 #include "ui/display/chromeos/display_configurator.h"
43 #include "ui/display/types/native_display_delegate.h"
44 #endif // defined(OS_CHROMEOS)
45 #include "ui/ozone/public/ozone_platform.h"
46 #include "ui/platform_window/platform_window.h"
47 #include "ui/platform_window/platform_window_delegate.h"
48 #endif // defined(USE_OZONE)
50 // Helper for Shader creation.
51 static void CreateShader(GLuint program,
52 GLenum type,
53 const char* source,
54 int size) {
55 GLuint shader = glCreateShader(type);
56 glShaderSource(shader, 1, &source, &size);
57 glCompileShader(shader);
58 int result = GL_FALSE;
59 glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
60 if (!result) {
61 char log[4096];
62 glGetShaderInfoLog(shader, arraysize(log), NULL, log);
63 LOG(FATAL) << log;
65 glAttachShader(program, shader);
66 glDeleteShader(shader);
67 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
70 namespace content {
71 namespace {
73 void WaitForSwapAck(const base::Closure& callback, gfx::SwapResult result) {
74 callback.Run();
77 } // namespace
79 #if defined(USE_OZONE)
81 class DisplayConfiguratorObserver : public ui::DisplayConfigurator::Observer {
82 public:
83 DisplayConfiguratorObserver(base::RunLoop* loop) : loop_(loop) {}
84 ~DisplayConfiguratorObserver() override {}
86 private:
87 // ui::DisplayConfigurator::Observer overrides:
88 void OnDisplayModeChanged(
89 const ui::DisplayConfigurator::DisplayStateList& outputs) override {
90 if (!loop_)
91 return;
92 loop_->Quit();
93 loop_ = nullptr;
95 void OnDisplayModeChangeFailed(
96 const ui::DisplayConfigurator::DisplayStateList& outputs,
97 ui::MultipleDisplayState failed_new_state) override {
98 LOG(FATAL) << "Could not configure display";
101 base::RunLoop* loop_;
103 DISALLOW_COPY_AND_ASSIGN(DisplayConfiguratorObserver);
106 class RenderingHelper::StubOzoneDelegate : public ui::PlatformWindowDelegate {
107 public:
108 StubOzoneDelegate() : accelerated_widget_(gfx::kNullAcceleratedWidget) {
109 platform_window_ = ui::OzonePlatform::GetInstance()->CreatePlatformWindow(
110 this, gfx::Rect());
112 ~StubOzoneDelegate() override {}
114 void OnBoundsChanged(const gfx::Rect& new_bounds) override {}
116 void OnDamageRect(const gfx::Rect& damaged_region) override {}
118 void DispatchEvent(ui::Event* event) override {}
120 void OnCloseRequest() override {}
121 void OnClosed() override {}
123 void OnWindowStateChanged(ui::PlatformWindowState new_state) override {}
125 void OnLostCapture() override {};
127 void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget,
128 float device_pixel_ratio) override {
129 accelerated_widget_ = widget;
132 void OnActivationChanged(bool active) override {};
134 gfx::AcceleratedWidget accelerated_widget() const {
135 return accelerated_widget_;
138 gfx::Size GetSize() { return platform_window_->GetBounds().size(); }
140 ui::PlatformWindow* platform_window() const { return platform_window_.get(); }
142 private:
143 scoped_ptr<ui::PlatformWindow> platform_window_;
144 gfx::AcceleratedWidget accelerated_widget_;
146 DISALLOW_COPY_AND_ASSIGN(StubOzoneDelegate);
149 #endif // defined(USE_OZONE)
151 RenderingHelperParams::RenderingHelperParams()
152 : rendering_fps(0), warm_up_iterations(0), render_as_thumbnails(false) {
155 RenderingHelperParams::~RenderingHelperParams() {}
157 VideoFrameTexture::VideoFrameTexture(uint32 texture_target,
158 uint32 texture_id,
159 const base::Closure& no_longer_needed_cb)
160 : texture_target_(texture_target),
161 texture_id_(texture_id),
162 no_longer_needed_cb_(no_longer_needed_cb) {
163 DCHECK(!no_longer_needed_cb_.is_null());
166 VideoFrameTexture::~VideoFrameTexture() {
167 base::ResetAndReturn(&no_longer_needed_cb_).Run();
170 RenderingHelper::RenderedVideo::RenderedVideo()
171 : is_flushing(false), frames_to_drop(0) {
174 RenderingHelper::RenderedVideo::~RenderedVideo() {
177 // static
178 void RenderingHelper::InitializeOneOff(base::WaitableEvent* done) {
179 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
180 #if GL_VARIANT_GLX
181 cmd_line->AppendSwitchASCII(switches::kUseGL,
182 gfx::kGLImplementationDesktopName);
183 #else
184 cmd_line->AppendSwitchASCII(switches::kUseGL, gfx::kGLImplementationEGLName);
185 #endif
187 if (!gfx::GLSurface::InitializeOneOff())
188 LOG(FATAL) << "Could not initialize GL";
189 done->Signal();
192 RenderingHelper::RenderingHelper() : ignore_vsync_(false) {
193 window_ = gfx::kNullAcceleratedWidget;
194 Clear();
197 RenderingHelper::~RenderingHelper() {
198 CHECK_EQ(videos_.size(), 0U) << "Must call UnInitialize before dtor.";
199 Clear();
202 void RenderingHelper::Setup() {
203 #if defined(OS_WIN)
204 window_ = CreateWindowEx(0,
205 L"Static",
206 L"VideoDecodeAcceleratorTest",
207 WS_OVERLAPPEDWINDOW | WS_VISIBLE,
210 GetSystemMetrics(SM_CXSCREEN),
211 GetSystemMetrics(SM_CYSCREEN),
212 NULL,
213 NULL,
214 NULL,
215 NULL);
216 #elif defined(USE_X11)
217 Display* display = gfx::GetXDisplay();
218 Screen* screen = DefaultScreenOfDisplay(display);
220 CHECK(display);
222 XSetWindowAttributes window_attributes;
223 memset(&window_attributes, 0, sizeof(window_attributes));
224 window_attributes.background_pixel =
225 BlackPixel(display, DefaultScreen(display));
226 window_attributes.override_redirect = true;
227 int depth = DefaultDepth(display, DefaultScreen(display));
229 window_ = XCreateWindow(display,
230 DefaultRootWindow(display),
233 XWidthOfScreen(screen),
234 XHeightOfScreen(screen),
235 0 /* border width */,
236 depth,
237 CopyFromParent /* class */,
238 CopyFromParent /* visual */,
239 (CWBackPixel | CWOverrideRedirect),
240 &window_attributes);
241 XStoreName(display, window_, "VideoDecodeAcceleratorTest");
242 XSelectInput(display, window_, ExposureMask);
243 XMapWindow(display, window_);
244 #elif defined(USE_OZONE)
245 base::MessageLoop::ScopedNestableTaskAllower nest_loop(
246 base::MessageLoop::current());
247 base::RunLoop wait_window_resize;
249 platform_window_delegate_.reset(new RenderingHelper::StubOzoneDelegate());
250 window_ = platform_window_delegate_->accelerated_widget();
251 gfx::Size window_size(800, 600);
252 // Ignore the vsync provider by default. On ChromeOS this will be set
253 // accordingly based on the display configuration.
254 ignore_vsync_ = true;
255 #if defined(OS_CHROMEOS)
256 // We hold onto the main loop here to wait for the DisplayController
257 // to give us the size of the display so we can create a window of
258 // the same size.
259 base::RunLoop wait_display_setup;
260 DisplayConfiguratorObserver display_setup_observer(&wait_display_setup);
261 display_configurator_.reset(new ui::DisplayConfigurator());
262 display_configurator_->SetDelegateForTesting(0);
263 display_configurator_->AddObserver(&display_setup_observer);
264 display_configurator_->Init(true);
265 display_configurator_->ForceInitialConfigure(0);
266 // Make sure all the display configuration is applied.
267 wait_display_setup.Run();
268 display_configurator_->RemoveObserver(&display_setup_observer);
270 gfx::Size framebuffer_size = display_configurator_->framebuffer_size();
271 if (!framebuffer_size.IsEmpty()) {
272 window_size = framebuffer_size;
273 ignore_vsync_ = false;
275 #endif
276 if (ignore_vsync_)
277 DVLOG(1) << "Ignoring vsync provider";
279 platform_window_delegate_->platform_window()->SetBounds(
280 gfx::Rect(window_size));
282 // On Ozone/DRI, platform windows are associated with the physical
283 // outputs. Association is achieved by matching the bounds of the
284 // window with the origin & modeset of the display output. Until a
285 // window is associated with a display output, we cannot get vsync
286 // events, because there is no hardware to get events from. Here we
287 // wait for the window to resized and therefore associated with
288 // display output to be sure that we will get such events.
289 wait_window_resize.RunUntilIdle();
290 #else
291 #error unknown platform
292 #endif
293 CHECK(window_ != gfx::kNullAcceleratedWidget);
296 void RenderingHelper::TearDown() {
297 #if defined(OS_WIN)
298 if (window_)
299 DestroyWindow(window_);
300 #elif defined(USE_X11)
301 // Destroy resources acquired in Initialize, in reverse-acquisition order.
302 if (window_) {
303 CHECK(XUnmapWindow(gfx::GetXDisplay(), window_));
304 CHECK(XDestroyWindow(gfx::GetXDisplay(), window_));
306 #elif defined(USE_OZONE)
307 platform_window_delegate_.reset();
308 #if defined(OS_CHROMEOS)
309 display_configurator_->PrepareForExit();
310 display_configurator_.reset();
311 #endif
312 #endif
313 window_ = gfx::kNullAcceleratedWidget;
316 void RenderingHelper::Initialize(const RenderingHelperParams& params,
317 base::WaitableEvent* done) {
318 // Use videos_.size() != 0 as a proxy for the class having already been
319 // Initialize()'d, and UnInitialize() before continuing.
320 if (videos_.size()) {
321 base::WaitableEvent done(false, false);
322 UnInitialize(&done);
323 done.Wait();
326 render_task_.Reset(
327 base::Bind(&RenderingHelper::RenderContent, base::Unretained(this)));
329 frame_duration_ = params.rendering_fps > 0
330 ? base::TimeDelta::FromSeconds(1) / params.rendering_fps
331 : base::TimeDelta();
333 render_as_thumbnails_ = params.render_as_thumbnails;
334 message_loop_ = base::MessageLoop::current();
336 gl_surface_ = gfx::GLSurface::CreateViewGLSurface(window_);
337 #if defined(USE_OZONE)
338 gl_surface_->Resize(platform_window_delegate_->GetSize());
339 #endif // defined(USE_OZONE)
340 screen_size_ = gl_surface_->GetSize();
342 gl_context_ = gfx::GLContext::CreateGLContext(
343 NULL, gl_surface_.get(), gfx::PreferIntegratedGpu);
344 CHECK(gl_context_->MakeCurrent(gl_surface_.get()));
346 CHECK_GT(params.window_sizes.size(), 0U);
347 videos_.resize(params.window_sizes.size());
348 LayoutRenderingAreas(params.window_sizes);
350 if (render_as_thumbnails_) {
351 CHECK_EQ(videos_.size(), 1U);
353 GLint max_texture_size;
354 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
355 CHECK_GE(max_texture_size, params.thumbnails_page_size.width());
356 CHECK_GE(max_texture_size, params.thumbnails_page_size.height());
358 thumbnails_fbo_size_ = params.thumbnails_page_size;
359 thumbnail_size_ = params.thumbnail_size;
361 glGenFramebuffersEXT(1, &thumbnails_fbo_id_);
362 glGenTextures(1, &thumbnails_texture_id_);
363 glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
364 glTexImage2D(GL_TEXTURE_2D,
366 GL_RGB,
367 thumbnails_fbo_size_.width(),
368 thumbnails_fbo_size_.height(),
370 GL_RGB,
371 GL_UNSIGNED_SHORT_5_6_5,
372 NULL);
373 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
374 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
375 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
376 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
377 glBindTexture(GL_TEXTURE_2D, 0);
379 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
380 glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
381 GL_COLOR_ATTACHMENT0,
382 GL_TEXTURE_2D,
383 thumbnails_texture_id_,
386 GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
387 CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status;
388 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
389 glClear(GL_COLOR_BUFFER_BIT);
390 glBindFramebufferEXT(GL_FRAMEBUFFER,
391 gl_surface_->GetBackingFrameBufferObject());
394 // These vertices and texture coords. map (0,0) in the texture to the
395 // bottom left of the viewport. Since we get the video frames with the
396 // the top left at (0,0) we need to flip the texture y coordinate
397 // in the vertex shader for this to be rendered the right way up.
398 // In the case of thumbnail rendering we use the same vertex shader
399 // to render the FBO the screen, where we do not want this flipping.
400 static const float kVertices[] =
401 { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, };
402 static const float kTextureCoords[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
403 static const char kVertexShader[] = STRINGIZE(
404 varying vec2 interp_tc;
405 attribute vec4 in_pos;
406 attribute vec2 in_tc;
407 uniform bool tex_flip;
408 void main() {
409 if (tex_flip)
410 interp_tc = vec2(in_tc.x, 1.0 - in_tc.y);
411 else
412 interp_tc = in_tc;
413 gl_Position = in_pos;
416 #if GL_VARIANT_EGL
417 static const char kFragmentShader[] =
418 "#extension GL_OES_EGL_image_external : enable\n"
419 "precision mediump float;\n"
420 "varying vec2 interp_tc;\n"
421 "uniform sampler2D tex;\n"
422 "#ifdef GL_OES_EGL_image_external\n"
423 "uniform samplerExternalOES tex_external;\n"
424 "#endif\n"
425 "void main() {\n"
426 " vec4 color = texture2D(tex, interp_tc);\n"
427 "#ifdef GL_OES_EGL_image_external\n"
428 " color += texture2D(tex_external, interp_tc);\n"
429 "#endif\n"
430 " gl_FragColor = color;\n"
431 "}\n";
432 #else
433 static const char kFragmentShader[] = STRINGIZE(
434 varying vec2 interp_tc;
435 uniform sampler2D tex;
436 void main() {
437 gl_FragColor = texture2D(tex, interp_tc);
439 #endif
440 program_ = glCreateProgram();
441 CreateShader(
442 program_, GL_VERTEX_SHADER, kVertexShader, arraysize(kVertexShader));
443 CreateShader(program_,
444 GL_FRAGMENT_SHADER,
445 kFragmentShader,
446 arraysize(kFragmentShader));
447 glLinkProgram(program_);
448 int result = GL_FALSE;
449 glGetProgramiv(program_, GL_LINK_STATUS, &result);
450 if (!result) {
451 char log[4096];
452 glGetShaderInfoLog(program_, arraysize(log), NULL, log);
453 LOG(FATAL) << log;
455 glUseProgram(program_);
456 glDeleteProgram(program_);
458 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
459 glUniform1i(glGetUniformLocation(program_, "tex"), 0);
460 GLint tex_external = glGetUniformLocation(program_, "tex_external");
461 if (tex_external != -1) {
462 glUniform1i(tex_external, 1);
464 int pos_location = glGetAttribLocation(program_, "in_pos");
465 glEnableVertexAttribArray(pos_location);
466 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
467 int tc_location = glGetAttribLocation(program_, "in_tc");
468 glEnableVertexAttribArray(tc_location);
469 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords);
471 if (frame_duration_ != base::TimeDelta()) {
472 int warm_up_iterations = params.warm_up_iterations;
473 #if defined(USE_OZONE)
474 // On Ozone the VSyncProvider can't provide a vsync interval until
475 // we render at least a frame, so we warm up with at least one
476 // frame.
477 // On top of this, we want to make sure all the buffers backing
478 // the GL surface are cleared, otherwise we can see the previous
479 // test's last frames, so we set warm up iterations to 2, to clear
480 // the front and back buffers.
481 warm_up_iterations = std::max(2, warm_up_iterations);
482 #endif
483 WarmUpRendering(warm_up_iterations);
486 // It's safe to use Unretained here since |rendering_thread_| will be stopped
487 // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is
488 // a member of that class. (See video_decode_accelerator_unittest.cc.)
489 gfx::VSyncProvider* vsync_provider = gl_surface_->GetVSyncProvider();
491 // VSync providers rely on the underlying CRTC to get the timing. In headless
492 // mode the surface isn't associated with a CRTC so the vsync provider can not
493 // get the timing, meaning it will not call UpdateVsyncParameters() ever.
494 if (!ignore_vsync_ && vsync_provider && frame_duration_ != base::TimeDelta())
495 vsync_provider->GetVSyncParameters(base::Bind(
496 &RenderingHelper::UpdateVSyncParameters, base::Unretained(this), done));
497 else
498 done->Signal();
501 // The rendering for the first few frames is slow (e.g., 100ms on Peach Pit).
502 // This affects the numbers measured in the performance test. We try to render
503 // several frames here to warm up the rendering.
504 void RenderingHelper::WarmUpRendering(int warm_up_iterations) {
505 unsigned int texture_id;
506 scoped_ptr<GLubyte[]> emptyData(new GLubyte[screen_size_.GetArea() * 2]());
507 glGenTextures(1, &texture_id);
508 glBindTexture(GL_TEXTURE_2D, texture_id);
509 glTexImage2D(GL_TEXTURE_2D,
511 GL_RGB,
512 screen_size_.width(),
513 screen_size_.height(),
515 GL_RGB,
516 GL_UNSIGNED_SHORT_5_6_5,
517 emptyData.get());
518 for (int i = 0; i < warm_up_iterations; ++i) {
519 RenderTexture(GL_TEXTURE_2D, texture_id);
521 // Need to allow nestable tasks since WarmUpRendering() is called from
522 // within another task on the renderer thread.
523 base::MessageLoop::ScopedNestableTaskAllower allow(
524 base::MessageLoop::current());
525 base::RunLoop wait_for_swap_ack;
526 gl_surface_->SwapBuffersAsync(
527 base::Bind(&WaitForSwapAck, wait_for_swap_ack.QuitClosure()));
528 wait_for_swap_ack.Run();
530 glDeleteTextures(1, &texture_id);
533 void RenderingHelper::UnInitialize(base::WaitableEvent* done) {
534 CHECK_EQ(base::MessageLoop::current(), message_loop_);
536 render_task_.Cancel();
538 if (render_as_thumbnails_) {
539 glDeleteTextures(1, &thumbnails_texture_id_);
540 glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_);
543 gl_surface_->Destroy();
544 gl_context_->ReleaseCurrent(gl_surface_.get());
545 gl_context_ = NULL;
546 gl_surface_ = NULL;
548 Clear();
549 done->Signal();
552 void RenderingHelper::CreateTexture(uint32 texture_target,
553 uint32* texture_id,
554 const gfx::Size& size,
555 base::WaitableEvent* done) {
556 if (base::MessageLoop::current() != message_loop_) {
557 message_loop_->PostTask(FROM_HERE,
558 base::Bind(&RenderingHelper::CreateTexture,
559 base::Unretained(this),
560 texture_target,
561 texture_id,
562 size,
563 done));
564 return;
566 glGenTextures(1, texture_id);
567 glBindTexture(texture_target, *texture_id);
568 if (texture_target == GL_TEXTURE_2D) {
569 glTexImage2D(GL_TEXTURE_2D,
571 GL_RGBA,
572 size.width(),
573 size.height(),
575 GL_RGBA,
576 GL_UNSIGNED_BYTE,
577 NULL);
579 glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
580 glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
581 // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
582 glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
583 glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
584 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
585 done->Signal();
588 // Helper function to set GL viewport.
589 static inline void GLSetViewPort(const gfx::Rect& area) {
590 glViewport(area.x(), area.y(), area.width(), area.height());
591 glScissor(area.x(), area.y(), area.width(), area.height());
594 void RenderingHelper::RenderThumbnail(uint32 texture_target,
595 uint32 texture_id) {
596 CHECK_EQ(base::MessageLoop::current(), message_loop_);
597 const int width = thumbnail_size_.width();
598 const int height = thumbnail_size_.height();
599 const int thumbnails_in_row = thumbnails_fbo_size_.width() / width;
600 const int thumbnails_in_column = thumbnails_fbo_size_.height() / height;
601 const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column;
602 const int col = frame_count_ % thumbnails_in_row;
604 gfx::Rect area(col * width, row * height, width, height);
606 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
607 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
608 GLSetViewPort(area);
609 RenderTexture(texture_target, texture_id);
610 glBindFramebufferEXT(GL_FRAMEBUFFER,
611 gl_surface_->GetBackingFrameBufferObject());
613 // Need to flush the GL commands before we return the tnumbnail texture to
614 // the decoder.
615 glFlush();
616 ++frame_count_;
619 void RenderingHelper::QueueVideoFrame(
620 size_t window_id,
621 scoped_refptr<VideoFrameTexture> video_frame) {
622 CHECK_EQ(base::MessageLoop::current(), message_loop_);
623 RenderedVideo* video = &videos_[window_id];
624 DCHECK(!video->is_flushing);
626 video->pending_frames.push(video_frame);
628 if (video->frames_to_drop > 0 && video->pending_frames.size() > 1) {
629 --video->frames_to_drop;
630 video->pending_frames.pop();
633 // Schedules the first RenderContent() if need.
634 if (scheduled_render_time_.is_null()) {
635 scheduled_render_time_ = base::TimeTicks::Now();
636 message_loop_->PostTask(FROM_HERE, render_task_.callback());
640 void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) {
641 // The ExternalOES sampler is bound to GL_TEXTURE1 and the Texture2D sampler
642 // is bound to GL_TEXTURE0.
643 if (texture_target == GL_TEXTURE_2D) {
644 glActiveTexture(GL_TEXTURE0 + 0);
645 } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) {
646 glActiveTexture(GL_TEXTURE0 + 1);
648 glBindTexture(texture_target, texture_id);
649 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
650 glBindTexture(texture_target, 0);
651 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
654 void RenderingHelper::DeleteTexture(uint32 texture_id) {
655 CHECK_EQ(base::MessageLoop::current(), message_loop_);
656 glDeleteTextures(1, &texture_id);
657 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
660 scoped_refptr<gfx::GLContext> RenderingHelper::GetGLContext() {
661 return gl_context_;
664 void* RenderingHelper::GetGLContextHandle() {
665 return gl_context_->GetHandle();
668 void* RenderingHelper::GetGLDisplay() {
669 return gl_surface_->GetDisplay();
672 void RenderingHelper::Clear() {
673 videos_.clear();
674 message_loop_ = NULL;
675 gl_context_ = NULL;
676 gl_surface_ = NULL;
678 render_as_thumbnails_ = false;
679 frame_count_ = 0;
680 thumbnails_fbo_id_ = 0;
681 thumbnails_texture_id_ = 0;
684 void RenderingHelper::GetThumbnailsAsRGB(std::vector<unsigned char>* rgb,
685 bool* alpha_solid,
686 base::WaitableEvent* done) {
687 CHECK(render_as_thumbnails_);
689 const size_t num_pixels = thumbnails_fbo_size_.GetArea();
690 std::vector<unsigned char> rgba;
691 rgba.resize(num_pixels * 4);
692 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
693 glPixelStorei(GL_PACK_ALIGNMENT, 1);
694 // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support.
695 glReadPixels(0,
697 thumbnails_fbo_size_.width(),
698 thumbnails_fbo_size_.height(),
699 GL_RGBA,
700 GL_UNSIGNED_BYTE,
701 &rgba[0]);
702 glBindFramebufferEXT(GL_FRAMEBUFFER,
703 gl_surface_->GetBackingFrameBufferObject());
704 rgb->resize(num_pixels * 3);
705 // Drop the alpha channel, but check as we go that it is all 0xff.
706 bool solid = true;
707 unsigned char* rgb_ptr = &((*rgb)[0]);
708 unsigned char* rgba_ptr = &rgba[0];
709 for (size_t i = 0; i < num_pixels; ++i) {
710 *rgb_ptr++ = *rgba_ptr++;
711 *rgb_ptr++ = *rgba_ptr++;
712 *rgb_ptr++ = *rgba_ptr++;
713 solid = solid && (*rgba_ptr == 0xff);
714 rgba_ptr++;
716 *alpha_solid = solid;
718 done->Signal();
721 void RenderingHelper::Flush(size_t window_id) {
722 videos_[window_id].is_flushing = true;
725 void RenderingHelper::RenderContent() {
726 CHECK_EQ(base::MessageLoop::current(), message_loop_);
728 // Update the VSync params.
730 // It's safe to use Unretained here since |rendering_thread_| will be stopped
731 // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is
732 // a member of that class. (See video_decode_accelerator_unittest.cc.)
733 gfx::VSyncProvider* vsync_provider = gl_surface_->GetVSyncProvider();
734 if (vsync_provider && !ignore_vsync_) {
735 vsync_provider->GetVSyncParameters(base::Bind(
736 &RenderingHelper::UpdateVSyncParameters, base::Unretained(this),
737 static_cast<base::WaitableEvent*>(NULL)));
740 int tex_flip = 1;
741 #if defined(USE_OZONE)
742 // Ozone surfaceless renders flipped from normal GL, so there's no need to
743 // do an extra flip.
744 tex_flip = 0;
745 #endif // defined(USE_OZONE)
746 glUniform1i(glGetUniformLocation(program_, "tex_flip"), tex_flip);
748 // Frames that will be returned to the client (via the no_longer_needed_cb)
749 // after this vector falls out of scope at the end of this method. We need
750 // to keep references to them until after SwapBuffers() call below.
751 std::vector<scoped_refptr<VideoFrameTexture> > frames_to_be_returned;
752 bool need_swap_buffer = false;
753 if (render_as_thumbnails_) {
754 // In render_as_thumbnails_ mode, we render the FBO content on the
755 // screen instead of the decoded textures.
756 GLSetViewPort(videos_[0].render_area);
757 RenderTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
758 need_swap_buffer = true;
759 } else {
760 for (RenderedVideo& video : videos_) {
761 if (video.pending_frames.empty())
762 continue;
763 need_swap_buffer = true;
764 scoped_refptr<VideoFrameTexture> frame = video.pending_frames.front();
765 GLSetViewPort(video.render_area);
766 RenderTexture(frame->texture_target(), frame->texture_id());
768 if (video.pending_frames.size() > 1 || video.is_flushing) {
769 frames_to_be_returned.push_back(video.pending_frames.front());
770 video.pending_frames.pop();
771 } else {
772 ++video.frames_to_drop;
777 base::Closure schedule_frame = base::Bind(
778 &RenderingHelper::ScheduleNextRenderContent, base::Unretained(this));
779 if (!need_swap_buffer ||
780 !gl_surface_->SwapBuffersAsync(
781 base::Bind(&WaitForSwapAck, schedule_frame)))
782 schedule_frame.Run();
785 // Helper function for the LayoutRenderingAreas(). The |lengths| are the
786 // heights(widths) of the rows(columns). It scales the elements in
787 // |lengths| proportionally so that the sum of them equal to |total_length|.
788 // It also outputs the coordinates of the rows(columns) to |offsets|.
789 static void ScaleAndCalculateOffsets(std::vector<int>* lengths,
790 std::vector<int>* offsets,
791 int total_length) {
792 int sum = std::accumulate(lengths->begin(), lengths->end(), 0);
793 for (size_t i = 0; i < lengths->size(); ++i) {
794 lengths->at(i) = lengths->at(i) * total_length / sum;
795 offsets->at(i) = (i == 0) ? 0 : offsets->at(i - 1) + lengths->at(i - 1);
799 void RenderingHelper::LayoutRenderingAreas(
800 const std::vector<gfx::Size>& window_sizes) {
801 // Find the number of colums and rows.
802 // The smallest n * n or n * (n + 1) > number of windows.
803 size_t cols = sqrt(videos_.size() - 1) + 1;
804 size_t rows = (videos_.size() + cols - 1) / cols;
806 // Find the widths and heights of the grid.
807 std::vector<int> widths(cols);
808 std::vector<int> heights(rows);
809 std::vector<int> offset_x(cols);
810 std::vector<int> offset_y(rows);
812 for (size_t i = 0; i < window_sizes.size(); ++i) {
813 const gfx::Size& size = window_sizes[i];
814 widths[i % cols] = std::max(widths[i % cols], size.width());
815 heights[i / cols] = std::max(heights[i / cols], size.height());
818 ScaleAndCalculateOffsets(&widths, &offset_x, screen_size_.width());
819 ScaleAndCalculateOffsets(&heights, &offset_y, screen_size_.height());
821 // Put each render_area_ in the center of each cell.
822 for (size_t i = 0; i < window_sizes.size(); ++i) {
823 const gfx::Size& size = window_sizes[i];
824 float scale =
825 std::min(static_cast<float>(widths[i % cols]) / size.width(),
826 static_cast<float>(heights[i / cols]) / size.height());
828 // Don't scale up the texture.
829 scale = std::min(1.0f, scale);
831 size_t w = scale * size.width();
832 size_t h = scale * size.height();
833 size_t x = offset_x[i % cols] + (widths[i % cols] - w) / 2;
834 size_t y = offset_y[i / cols] + (heights[i / cols] - h) / 2;
835 videos_[i].render_area = gfx::Rect(x, y, w, h);
839 void RenderingHelper::UpdateVSyncParameters(base::WaitableEvent* done,
840 const base::TimeTicks timebase,
841 const base::TimeDelta interval) {
842 vsync_timebase_ = timebase;
843 vsync_interval_ = interval;
845 if (done)
846 done->Signal();
849 void RenderingHelper::DropOneFrameForAllVideos() {
850 for (RenderedVideo& video : videos_) {
851 if (video.pending_frames.empty())
852 continue;
854 if (video.pending_frames.size() > 1 || video.is_flushing) {
855 video.pending_frames.pop();
856 } else {
857 ++video.frames_to_drop;
862 void RenderingHelper::ScheduleNextRenderContent() {
863 scheduled_render_time_ += frame_duration_;
864 base::TimeTicks now = base::TimeTicks::Now();
865 base::TimeTicks target;
867 if (vsync_interval_ != base::TimeDelta()) {
868 // Schedules the next RenderContent() at latest VSYNC before the
869 // |scheduled_render_time_|.
870 target = std::max(now + vsync_interval_, scheduled_render_time_);
872 int64 intervals = (target - vsync_timebase_) / vsync_interval_;
873 target = vsync_timebase_ + intervals * vsync_interval_;
874 } else {
875 target = std::max(now, scheduled_render_time_);
878 // When the rendering falls behind, drops frames.
879 while (scheduled_render_time_ < target) {
880 scheduled_render_time_ += frame_duration_;
881 DropOneFrameForAllVideos();
884 message_loop_->PostDelayedTask(
885 FROM_HERE, render_task_.callback(), target - now);
887 } // namespace content