[Android WebView] Fix webview perf bot switchover to use org.chromium.webview_shell...
[chromium-blink-merge.git] / content / common / gpu / media / rendering_helper.cc
blob06252a5c71f81196955b38d1d61750855a51df43
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) override {
128 accelerated_widget_ = widget;
131 void OnActivationChanged(bool active) override {};
133 gfx::AcceleratedWidget accelerated_widget() const {
134 return accelerated_widget_;
137 gfx::Size GetSize() { return platform_window_->GetBounds().size(); }
139 ui::PlatformWindow* platform_window() const { return platform_window_.get(); }
141 private:
142 scoped_ptr<ui::PlatformWindow> platform_window_;
143 gfx::AcceleratedWidget accelerated_widget_;
145 DISALLOW_COPY_AND_ASSIGN(StubOzoneDelegate);
148 #endif // defined(USE_OZONE)
150 RenderingHelperParams::RenderingHelperParams()
151 : rendering_fps(0), warm_up_iterations(0), render_as_thumbnails(false) {
154 RenderingHelperParams::~RenderingHelperParams() {}
156 VideoFrameTexture::VideoFrameTexture(uint32 texture_target,
157 uint32 texture_id,
158 const base::Closure& no_longer_needed_cb)
159 : texture_target_(texture_target),
160 texture_id_(texture_id),
161 no_longer_needed_cb_(no_longer_needed_cb) {
162 DCHECK(!no_longer_needed_cb_.is_null());
165 VideoFrameTexture::~VideoFrameTexture() {
166 base::ResetAndReturn(&no_longer_needed_cb_).Run();
169 RenderingHelper::RenderedVideo::RenderedVideo()
170 : is_flushing(false), frames_to_drop(0) {
173 RenderingHelper::RenderedVideo::~RenderedVideo() {
176 // static
177 void RenderingHelper::InitializeOneOff(base::WaitableEvent* done) {
178 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
179 #if GL_VARIANT_GLX
180 cmd_line->AppendSwitchASCII(switches::kUseGL,
181 gfx::kGLImplementationDesktopName);
182 #else
183 cmd_line->AppendSwitchASCII(switches::kUseGL, gfx::kGLImplementationEGLName);
184 #endif
186 if (!gfx::GLSurface::InitializeOneOff())
187 LOG(FATAL) << "Could not initialize GL";
188 done->Signal();
191 RenderingHelper::RenderingHelper() : ignore_vsync_(false) {
192 window_ = gfx::kNullAcceleratedWidget;
193 Clear();
196 RenderingHelper::~RenderingHelper() {
197 CHECK_EQ(videos_.size(), 0U) << "Must call UnInitialize before dtor.";
198 Clear();
201 void RenderingHelper::Setup() {
202 #if defined(OS_WIN)
203 window_ = CreateWindowEx(0,
204 L"Static",
205 L"VideoDecodeAcceleratorTest",
206 WS_OVERLAPPEDWINDOW | WS_VISIBLE,
209 GetSystemMetrics(SM_CXSCREEN),
210 GetSystemMetrics(SM_CYSCREEN),
211 NULL,
212 NULL,
213 NULL,
214 NULL);
215 #elif defined(USE_X11)
216 Display* display = gfx::GetXDisplay();
217 Screen* screen = DefaultScreenOfDisplay(display);
219 CHECK(display);
221 XSetWindowAttributes window_attributes;
222 memset(&window_attributes, 0, sizeof(window_attributes));
223 window_attributes.background_pixel =
224 BlackPixel(display, DefaultScreen(display));
225 window_attributes.override_redirect = true;
226 int depth = DefaultDepth(display, DefaultScreen(display));
228 window_ = XCreateWindow(display,
229 DefaultRootWindow(display),
232 XWidthOfScreen(screen),
233 XHeightOfScreen(screen),
234 0 /* border width */,
235 depth,
236 CopyFromParent /* class */,
237 CopyFromParent /* visual */,
238 (CWBackPixel | CWOverrideRedirect),
239 &window_attributes);
240 XStoreName(display, window_, "VideoDecodeAcceleratorTest");
241 XSelectInput(display, window_, ExposureMask);
242 XMapWindow(display, window_);
243 #elif defined(USE_OZONE)
244 base::MessageLoop::ScopedNestableTaskAllower nest_loop(
245 base::MessageLoop::current());
246 base::RunLoop wait_window_resize;
248 platform_window_delegate_.reset(new RenderingHelper::StubOzoneDelegate());
249 window_ = platform_window_delegate_->accelerated_widget();
250 gfx::Size window_size(800, 600);
251 // Ignore the vsync provider by default. On ChromeOS this will be set
252 // accordingly based on the display configuration.
253 ignore_vsync_ = true;
254 #if defined(OS_CHROMEOS)
255 // We hold onto the main loop here to wait for the DisplayController
256 // to give us the size of the display so we can create a window of
257 // the same size.
258 base::RunLoop wait_display_setup;
259 DisplayConfiguratorObserver display_setup_observer(&wait_display_setup);
260 display_configurator_.reset(new ui::DisplayConfigurator());
261 display_configurator_->SetDelegateForTesting(0);
262 display_configurator_->AddObserver(&display_setup_observer);
263 display_configurator_->Init(true);
264 display_configurator_->ForceInitialConfigure(0);
265 // Make sure all the display configuration is applied.
266 wait_display_setup.Run();
267 display_configurator_->RemoveObserver(&display_setup_observer);
269 gfx::Size framebuffer_size = display_configurator_->framebuffer_size();
270 if (!framebuffer_size.IsEmpty()) {
271 window_size = framebuffer_size;
272 ignore_vsync_ = false;
274 #endif
275 if (ignore_vsync_)
276 DVLOG(1) << "Ignoring vsync provider";
278 platform_window_delegate_->platform_window()->SetBounds(
279 gfx::Rect(window_size));
281 // On Ozone/DRI, platform windows are associated with the physical
282 // outputs. Association is achieved by matching the bounds of the
283 // window with the origin & modeset of the display output. Until a
284 // window is associated with a display output, we cannot get vsync
285 // events, because there is no hardware to get events from. Here we
286 // wait for the window to resized and therefore associated with
287 // display output to be sure that we will get such events.
288 wait_window_resize.RunUntilIdle();
289 #else
290 #error unknown platform
291 #endif
292 CHECK(window_ != gfx::kNullAcceleratedWidget);
295 void RenderingHelper::TearDown() {
296 #if defined(OS_WIN)
297 if (window_)
298 DestroyWindow(window_);
299 #elif defined(USE_X11)
300 // Destroy resources acquired in Initialize, in reverse-acquisition order.
301 if (window_) {
302 CHECK(XUnmapWindow(gfx::GetXDisplay(), window_));
303 CHECK(XDestroyWindow(gfx::GetXDisplay(), window_));
305 #elif defined(USE_OZONE)
306 platform_window_delegate_.reset();
307 #if defined(OS_CHROMEOS)
308 display_configurator_->PrepareForExit();
309 display_configurator_.reset();
310 #endif
311 #endif
312 window_ = gfx::kNullAcceleratedWidget;
315 void RenderingHelper::Initialize(const RenderingHelperParams& params,
316 base::WaitableEvent* done) {
317 // Use videos_.size() != 0 as a proxy for the class having already been
318 // Initialize()'d, and UnInitialize() before continuing.
319 if (videos_.size()) {
320 base::WaitableEvent done(false, false);
321 UnInitialize(&done);
322 done.Wait();
325 render_task_.Reset(
326 base::Bind(&RenderingHelper::RenderContent, base::Unretained(this)));
328 frame_duration_ = params.rendering_fps > 0
329 ? base::TimeDelta::FromSeconds(1) / params.rendering_fps
330 : base::TimeDelta();
332 render_as_thumbnails_ = params.render_as_thumbnails;
333 message_loop_ = base::MessageLoop::current();
335 gl_surface_ = gfx::GLSurface::CreateViewGLSurface(window_);
336 #if defined(USE_OZONE)
337 gl_surface_->Resize(platform_window_delegate_->GetSize());
338 #endif // defined(USE_OZONE)
339 screen_size_ = gl_surface_->GetSize();
341 gl_context_ = gfx::GLContext::CreateGLContext(
342 NULL, gl_surface_.get(), gfx::PreferIntegratedGpu);
343 CHECK(gl_context_->MakeCurrent(gl_surface_.get()));
345 CHECK_GT(params.window_sizes.size(), 0U);
346 videos_.resize(params.window_sizes.size());
347 LayoutRenderingAreas(params.window_sizes);
349 if (render_as_thumbnails_) {
350 CHECK_EQ(videos_.size(), 1U);
352 GLint max_texture_size;
353 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
354 CHECK_GE(max_texture_size, params.thumbnails_page_size.width());
355 CHECK_GE(max_texture_size, params.thumbnails_page_size.height());
357 thumbnails_fbo_size_ = params.thumbnails_page_size;
358 thumbnail_size_ = params.thumbnail_size;
360 glGenFramebuffersEXT(1, &thumbnails_fbo_id_);
361 glGenTextures(1, &thumbnails_texture_id_);
362 glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
363 glTexImage2D(GL_TEXTURE_2D,
365 GL_RGB,
366 thumbnails_fbo_size_.width(),
367 thumbnails_fbo_size_.height(),
369 GL_RGB,
370 GL_UNSIGNED_SHORT_5_6_5,
371 NULL);
372 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
373 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
374 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
375 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
376 glBindTexture(GL_TEXTURE_2D, 0);
378 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
379 glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
380 GL_COLOR_ATTACHMENT0,
381 GL_TEXTURE_2D,
382 thumbnails_texture_id_,
385 GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
386 CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status;
387 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
388 glClear(GL_COLOR_BUFFER_BIT);
389 glBindFramebufferEXT(GL_FRAMEBUFFER,
390 gl_surface_->GetBackingFrameBufferObject());
393 // These vertices and texture coords. map (0,0) in the texture to the
394 // bottom left of the viewport. Since we get the video frames with the
395 // the top left at (0,0) we need to flip the texture y coordinate
396 // in the vertex shader for this to be rendered the right way up.
397 // In the case of thumbnail rendering we use the same vertex shader
398 // to render the FBO the screen, where we do not want this flipping.
399 static const float kVertices[] =
400 { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, };
401 static const float kTextureCoords[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
402 static const char kVertexShader[] = STRINGIZE(
403 varying vec2 interp_tc;
404 attribute vec4 in_pos;
405 attribute vec2 in_tc;
406 uniform bool tex_flip;
407 void main() {
408 if (tex_flip)
409 interp_tc = vec2(in_tc.x, 1.0 - in_tc.y);
410 else
411 interp_tc = in_tc;
412 gl_Position = in_pos;
415 #if GL_VARIANT_EGL
416 static const char kFragmentShader[] =
417 "#extension GL_OES_EGL_image_external : enable\n"
418 "precision mediump float;\n"
419 "varying vec2 interp_tc;\n"
420 "uniform sampler2D tex;\n"
421 "#ifdef GL_OES_EGL_image_external\n"
422 "uniform samplerExternalOES tex_external;\n"
423 "#endif\n"
424 "void main() {\n"
425 " vec4 color = texture2D(tex, interp_tc);\n"
426 "#ifdef GL_OES_EGL_image_external\n"
427 " color += texture2D(tex_external, interp_tc);\n"
428 "#endif\n"
429 " gl_FragColor = color;\n"
430 "}\n";
431 #else
432 static const char kFragmentShader[] = STRINGIZE(
433 varying vec2 interp_tc;
434 uniform sampler2D tex;
435 void main() {
436 gl_FragColor = texture2D(tex, interp_tc);
438 #endif
439 program_ = glCreateProgram();
440 CreateShader(
441 program_, GL_VERTEX_SHADER, kVertexShader, arraysize(kVertexShader));
442 CreateShader(program_,
443 GL_FRAGMENT_SHADER,
444 kFragmentShader,
445 arraysize(kFragmentShader));
446 glLinkProgram(program_);
447 int result = GL_FALSE;
448 glGetProgramiv(program_, GL_LINK_STATUS, &result);
449 if (!result) {
450 char log[4096];
451 glGetShaderInfoLog(program_, arraysize(log), NULL, log);
452 LOG(FATAL) << log;
454 glUseProgram(program_);
455 glDeleteProgram(program_);
457 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
458 glUniform1i(glGetUniformLocation(program_, "tex"), 0);
459 GLint tex_external = glGetUniformLocation(program_, "tex_external");
460 if (tex_external != -1) {
461 glUniform1i(tex_external, 1);
463 int pos_location = glGetAttribLocation(program_, "in_pos");
464 glEnableVertexAttribArray(pos_location);
465 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
466 int tc_location = glGetAttribLocation(program_, "in_tc");
467 glEnableVertexAttribArray(tc_location);
468 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords);
470 if (frame_duration_ != base::TimeDelta()) {
471 int warm_up_iterations = params.warm_up_iterations;
472 #if defined(USE_OZONE)
473 // On Ozone the VSyncProvider can't provide a vsync interval until
474 // we render at least a frame, so we warm up with at least one
475 // frame.
476 // On top of this, we want to make sure all the buffers backing
477 // the GL surface are cleared, otherwise we can see the previous
478 // test's last frames, so we set warm up iterations to 2, to clear
479 // the front and back buffers.
480 warm_up_iterations = std::max(2, warm_up_iterations);
481 #endif
482 WarmUpRendering(warm_up_iterations);
485 // It's safe to use Unretained here since |rendering_thread_| will be stopped
486 // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is
487 // a member of that class. (See video_decode_accelerator_unittest.cc.)
488 gfx::VSyncProvider* vsync_provider = gl_surface_->GetVSyncProvider();
490 // VSync providers rely on the underlying CRTC to get the timing. In headless
491 // mode the surface isn't associated with a CRTC so the vsync provider can not
492 // get the timing, meaning it will not call UpdateVsyncParameters() ever.
493 if (!ignore_vsync_ && vsync_provider && frame_duration_ != base::TimeDelta())
494 vsync_provider->GetVSyncParameters(base::Bind(
495 &RenderingHelper::UpdateVSyncParameters, base::Unretained(this), done));
496 else
497 done->Signal();
500 // The rendering for the first few frames is slow (e.g., 100ms on Peach Pit).
501 // This affects the numbers measured in the performance test. We try to render
502 // several frames here to warm up the rendering.
503 void RenderingHelper::WarmUpRendering(int warm_up_iterations) {
504 unsigned int texture_id;
505 scoped_ptr<GLubyte[]> emptyData(new GLubyte[screen_size_.GetArea() * 2]());
506 glGenTextures(1, &texture_id);
507 glBindTexture(GL_TEXTURE_2D, texture_id);
508 glTexImage2D(GL_TEXTURE_2D,
510 GL_RGB,
511 screen_size_.width(),
512 screen_size_.height(),
514 GL_RGB,
515 GL_UNSIGNED_SHORT_5_6_5,
516 emptyData.get());
517 for (int i = 0; i < warm_up_iterations; ++i) {
518 RenderTexture(GL_TEXTURE_2D, texture_id);
520 // Need to allow nestable tasks since WarmUpRendering() is called from
521 // within another task on the renderer thread.
522 base::MessageLoop::ScopedNestableTaskAllower allow(
523 base::MessageLoop::current());
524 base::RunLoop wait_for_swap_ack;
525 gl_surface_->SwapBuffersAsync(
526 base::Bind(&WaitForSwapAck, wait_for_swap_ack.QuitClosure()));
527 wait_for_swap_ack.Run();
529 glDeleteTextures(1, &texture_id);
532 void RenderingHelper::UnInitialize(base::WaitableEvent* done) {
533 CHECK_EQ(base::MessageLoop::current(), message_loop_);
535 render_task_.Cancel();
537 if (render_as_thumbnails_) {
538 glDeleteTextures(1, &thumbnails_texture_id_);
539 glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_);
542 gl_surface_->Destroy();
543 gl_context_->ReleaseCurrent(gl_surface_.get());
544 gl_context_ = NULL;
545 gl_surface_ = NULL;
547 Clear();
548 done->Signal();
551 void RenderingHelper::CreateTexture(uint32 texture_target,
552 uint32* texture_id,
553 const gfx::Size& size,
554 base::WaitableEvent* done) {
555 if (base::MessageLoop::current() != message_loop_) {
556 message_loop_->PostTask(FROM_HERE,
557 base::Bind(&RenderingHelper::CreateTexture,
558 base::Unretained(this),
559 texture_target,
560 texture_id,
561 size,
562 done));
563 return;
565 glGenTextures(1, texture_id);
566 glBindTexture(texture_target, *texture_id);
567 if (texture_target == GL_TEXTURE_2D) {
568 glTexImage2D(GL_TEXTURE_2D,
570 GL_RGBA,
571 size.width(),
572 size.height(),
574 GL_RGBA,
575 GL_UNSIGNED_BYTE,
576 NULL);
578 glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
579 glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
580 // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
581 glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
582 glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
583 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
584 done->Signal();
587 // Helper function to set GL viewport.
588 static inline void GLSetViewPort(const gfx::Rect& area) {
589 glViewport(area.x(), area.y(), area.width(), area.height());
590 glScissor(area.x(), area.y(), area.width(), area.height());
593 void RenderingHelper::RenderThumbnail(uint32 texture_target,
594 uint32 texture_id) {
595 CHECK_EQ(base::MessageLoop::current(), message_loop_);
596 const int width = thumbnail_size_.width();
597 const int height = thumbnail_size_.height();
598 const int thumbnails_in_row = thumbnails_fbo_size_.width() / width;
599 const int thumbnails_in_column = thumbnails_fbo_size_.height() / height;
600 const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column;
601 const int col = frame_count_ % thumbnails_in_row;
603 gfx::Rect area(col * width, row * height, width, height);
605 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
606 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
607 GLSetViewPort(area);
608 RenderTexture(texture_target, texture_id);
609 glBindFramebufferEXT(GL_FRAMEBUFFER,
610 gl_surface_->GetBackingFrameBufferObject());
612 // Need to flush the GL commands before we return the tnumbnail texture to
613 // the decoder.
614 glFlush();
615 ++frame_count_;
618 void RenderingHelper::QueueVideoFrame(
619 size_t window_id,
620 scoped_refptr<VideoFrameTexture> video_frame) {
621 CHECK_EQ(base::MessageLoop::current(), message_loop_);
622 RenderedVideo* video = &videos_[window_id];
623 DCHECK(!video->is_flushing);
625 video->pending_frames.push(video_frame);
627 if (video->frames_to_drop > 0 && video->pending_frames.size() > 1) {
628 --video->frames_to_drop;
629 video->pending_frames.pop();
632 // Schedules the first RenderContent() if need.
633 if (scheduled_render_time_.is_null()) {
634 scheduled_render_time_ = base::TimeTicks::Now();
635 message_loop_->PostTask(FROM_HERE, render_task_.callback());
639 void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) {
640 // The ExternalOES sampler is bound to GL_TEXTURE1 and the Texture2D sampler
641 // is bound to GL_TEXTURE0.
642 if (texture_target == GL_TEXTURE_2D) {
643 glActiveTexture(GL_TEXTURE0 + 0);
644 } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) {
645 glActiveTexture(GL_TEXTURE0 + 1);
647 glBindTexture(texture_target, texture_id);
648 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
649 glBindTexture(texture_target, 0);
650 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
653 void RenderingHelper::DeleteTexture(uint32 texture_id) {
654 CHECK_EQ(base::MessageLoop::current(), message_loop_);
655 glDeleteTextures(1, &texture_id);
656 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
659 scoped_refptr<gfx::GLContext> RenderingHelper::GetGLContext() {
660 return gl_context_;
663 void* RenderingHelper::GetGLContextHandle() {
664 return gl_context_->GetHandle();
667 void* RenderingHelper::GetGLDisplay() {
668 return gl_surface_->GetDisplay();
671 void RenderingHelper::Clear() {
672 videos_.clear();
673 message_loop_ = NULL;
674 gl_context_ = NULL;
675 gl_surface_ = NULL;
677 render_as_thumbnails_ = false;
678 frame_count_ = 0;
679 thumbnails_fbo_id_ = 0;
680 thumbnails_texture_id_ = 0;
683 void RenderingHelper::GetThumbnailsAsRGB(std::vector<unsigned char>* rgb,
684 bool* alpha_solid,
685 base::WaitableEvent* done) {
686 CHECK(render_as_thumbnails_);
688 const size_t num_pixels = thumbnails_fbo_size_.GetArea();
689 std::vector<unsigned char> rgba;
690 rgba.resize(num_pixels * 4);
691 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
692 glPixelStorei(GL_PACK_ALIGNMENT, 1);
693 // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support.
694 glReadPixels(0,
696 thumbnails_fbo_size_.width(),
697 thumbnails_fbo_size_.height(),
698 GL_RGBA,
699 GL_UNSIGNED_BYTE,
700 &rgba[0]);
701 glBindFramebufferEXT(GL_FRAMEBUFFER,
702 gl_surface_->GetBackingFrameBufferObject());
703 rgb->resize(num_pixels * 3);
704 // Drop the alpha channel, but check as we go that it is all 0xff.
705 bool solid = true;
706 unsigned char* rgb_ptr = &((*rgb)[0]);
707 unsigned char* rgba_ptr = &rgba[0];
708 for (size_t i = 0; i < num_pixels; ++i) {
709 *rgb_ptr++ = *rgba_ptr++;
710 *rgb_ptr++ = *rgba_ptr++;
711 *rgb_ptr++ = *rgba_ptr++;
712 solid = solid && (*rgba_ptr == 0xff);
713 rgba_ptr++;
715 *alpha_solid = solid;
717 done->Signal();
720 void RenderingHelper::Flush(size_t window_id) {
721 videos_[window_id].is_flushing = true;
724 void RenderingHelper::RenderContent() {
725 CHECK_EQ(base::MessageLoop::current(), message_loop_);
727 // Update the VSync params.
729 // It's safe to use Unretained here since |rendering_thread_| will be stopped
730 // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is
731 // a member of that class. (See video_decode_accelerator_unittest.cc.)
732 gfx::VSyncProvider* vsync_provider = gl_surface_->GetVSyncProvider();
733 if (vsync_provider && !ignore_vsync_) {
734 vsync_provider->GetVSyncParameters(base::Bind(
735 &RenderingHelper::UpdateVSyncParameters, base::Unretained(this),
736 static_cast<base::WaitableEvent*>(NULL)));
739 int tex_flip = 1;
740 #if defined(USE_OZONE)
741 // Ozone surfaceless renders flipped from normal GL, so there's no need to
742 // do an extra flip.
743 tex_flip = 0;
744 #endif // defined(USE_OZONE)
745 glUniform1i(glGetUniformLocation(program_, "tex_flip"), tex_flip);
747 // Frames that will be returned to the client (via the no_longer_needed_cb)
748 // after this vector falls out of scope at the end of this method. We need
749 // to keep references to them until after SwapBuffers() call below.
750 std::vector<scoped_refptr<VideoFrameTexture> > frames_to_be_returned;
751 bool need_swap_buffer = false;
752 if (render_as_thumbnails_) {
753 // In render_as_thumbnails_ mode, we render the FBO content on the
754 // screen instead of the decoded textures.
755 GLSetViewPort(videos_[0].render_area);
756 RenderTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
757 need_swap_buffer = true;
758 } else {
759 for (RenderedVideo& video : videos_) {
760 if (video.pending_frames.empty())
761 continue;
762 need_swap_buffer = true;
763 scoped_refptr<VideoFrameTexture> frame = video.pending_frames.front();
764 GLSetViewPort(video.render_area);
765 RenderTexture(frame->texture_target(), frame->texture_id());
767 if (video.pending_frames.size() > 1 || video.is_flushing) {
768 frames_to_be_returned.push_back(video.pending_frames.front());
769 video.pending_frames.pop();
770 } else {
771 ++video.frames_to_drop;
776 base::Closure schedule_frame = base::Bind(
777 &RenderingHelper::ScheduleNextRenderContent, base::Unretained(this));
778 if (!need_swap_buffer ||
779 !gl_surface_->SwapBuffersAsync(
780 base::Bind(&WaitForSwapAck, schedule_frame)))
781 schedule_frame.Run();
784 // Helper function for the LayoutRenderingAreas(). The |lengths| are the
785 // heights(widths) of the rows(columns). It scales the elements in
786 // |lengths| proportionally so that the sum of them equal to |total_length|.
787 // It also outputs the coordinates of the rows(columns) to |offsets|.
788 static void ScaleAndCalculateOffsets(std::vector<int>* lengths,
789 std::vector<int>* offsets,
790 int total_length) {
791 int sum = std::accumulate(lengths->begin(), lengths->end(), 0);
792 for (size_t i = 0; i < lengths->size(); ++i) {
793 lengths->at(i) = lengths->at(i) * total_length / sum;
794 offsets->at(i) = (i == 0) ? 0 : offsets->at(i - 1) + lengths->at(i - 1);
798 void RenderingHelper::LayoutRenderingAreas(
799 const std::vector<gfx::Size>& window_sizes) {
800 // Find the number of colums and rows.
801 // The smallest n * n or n * (n + 1) > number of windows.
802 size_t cols = sqrt(videos_.size() - 1) + 1;
803 size_t rows = (videos_.size() + cols - 1) / cols;
805 // Find the widths and heights of the grid.
806 std::vector<int> widths(cols);
807 std::vector<int> heights(rows);
808 std::vector<int> offset_x(cols);
809 std::vector<int> offset_y(rows);
811 for (size_t i = 0; i < window_sizes.size(); ++i) {
812 const gfx::Size& size = window_sizes[i];
813 widths[i % cols] = std::max(widths[i % cols], size.width());
814 heights[i / cols] = std::max(heights[i / cols], size.height());
817 ScaleAndCalculateOffsets(&widths, &offset_x, screen_size_.width());
818 ScaleAndCalculateOffsets(&heights, &offset_y, screen_size_.height());
820 // Put each render_area_ in the center of each cell.
821 for (size_t i = 0; i < window_sizes.size(); ++i) {
822 const gfx::Size& size = window_sizes[i];
823 float scale =
824 std::min(static_cast<float>(widths[i % cols]) / size.width(),
825 static_cast<float>(heights[i / cols]) / size.height());
827 // Don't scale up the texture.
828 scale = std::min(1.0f, scale);
830 size_t w = scale * size.width();
831 size_t h = scale * size.height();
832 size_t x = offset_x[i % cols] + (widths[i % cols] - w) / 2;
833 size_t y = offset_y[i / cols] + (heights[i / cols] - h) / 2;
834 videos_[i].render_area = gfx::Rect(x, y, w, h);
838 void RenderingHelper::UpdateVSyncParameters(base::WaitableEvent* done,
839 const base::TimeTicks timebase,
840 const base::TimeDelta interval) {
841 vsync_timebase_ = timebase;
842 vsync_interval_ = interval;
844 if (done)
845 done->Signal();
848 void RenderingHelper::DropOneFrameForAllVideos() {
849 for (RenderedVideo& video : videos_) {
850 if (video.pending_frames.empty())
851 continue;
853 if (video.pending_frames.size() > 1 || video.is_flushing) {
854 video.pending_frames.pop();
855 } else {
856 ++video.frames_to_drop;
861 void RenderingHelper::ScheduleNextRenderContent() {
862 scheduled_render_time_ += frame_duration_;
863 base::TimeTicks now = base::TimeTicks::Now();
864 base::TimeTicks target;
866 if (vsync_interval_ != base::TimeDelta()) {
867 // Schedules the next RenderContent() at latest VSYNC before the
868 // |scheduled_render_time_|.
869 target = std::max(now + vsync_interval_, scheduled_render_time_);
871 int64 intervals = (target - vsync_timebase_) / vsync_interval_;
872 target = vsync_timebase_ + intervals * vsync_interval_;
873 } else {
874 target = std::max(now, scheduled_render_time_);
877 // When the rendering falls behind, drops frames.
878 while (scheduled_render_time_ < target) {
879 scheduled_render_time_ += frame_duration_;
880 DropOneFrameForAllVideos();
883 message_loop_->PostDelayedTask(
884 FROM_HERE, render_task_.callback(), target - now);
886 } // namespace content