Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / common / gpu / media / rendering_helper.cc
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.
4
5 #include "content/common/gpu/media/rendering_helper.h"
6
7 #include <algorithm>
8 #include <numeric>
9 #include <vector>
10
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/strings/stringize_macros.h"
17 #include "base/synchronization/waitable_event.h"
18 #include "base/time/time.h"
19 #include "ui/gl/gl_context.h"
20 #include "ui/gl/gl_implementation.h"
21 #include "ui/gl/gl_surface.h"
22 #include "ui/gl/gl_surface_egl.h"
23 #include "ui/gl/gl_surface_glx.h"
24
25 #if defined(OS_WIN)
26 #include <windows.h>
27 #endif
28
29 #if defined(USE_X11)
30 #include "ui/gfx/x/x11_types.h"
31 #endif
32
33 #if !defined(OS_WIN) && defined(ARCH_CPU_X86_FAMILY)
34 #define GL_VARIANT_GLX 1
35 #else
36 #define GL_VARIANT_EGL 1
37 #endif
38
39 // Helper for Shader creation.
40 static void CreateShader(GLuint program,
41                          GLenum type,
42                          const char* source,
43                          int size) {
44   GLuint shader = glCreateShader(type);
45   glShaderSource(shader, 1, &source, &size);
46   glCompileShader(shader);
47   int result = GL_FALSE;
48   glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
49   if (!result) {
50     char log[4096];
51     glGetShaderInfoLog(shader, arraysize(log), NULL, log);
52     LOG(FATAL) << log;
53   }
54   glAttachShader(program, shader);
55   glDeleteShader(shader);
56   CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
57 }
58
59 namespace content {
60
61 RenderingHelperParams::RenderingHelperParams()
62     : rendering_fps(0), warm_up_iterations(0), render_as_thumbnails(false) {
63 }
64
65 RenderingHelperParams::~RenderingHelperParams() {}
66
67 VideoFrameTexture::VideoFrameTexture(uint32 texture_target,
68                                      uint32 texture_id,
69                                      const base::Closure& no_longer_needed_cb)
70     : texture_target_(texture_target),
71       texture_id_(texture_id),
72       no_longer_needed_cb_(no_longer_needed_cb) {
73   DCHECK(!no_longer_needed_cb_.is_null());
74 }
75
76 VideoFrameTexture::~VideoFrameTexture() {
77   base::ResetAndReturn(&no_longer_needed_cb_).Run();
78 }
79
80 RenderingHelper::RenderedVideo::RenderedVideo()
81     : is_flushing(false), frames_to_drop(0) {
82 }
83
84 RenderingHelper::RenderedVideo::~RenderedVideo() {
85 }
86
87 // static
88 bool RenderingHelper::InitializeOneOff() {
89   base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
90 #if GL_VARIANT_GLX
91   cmd_line->AppendSwitchASCII(switches::kUseGL,
92                               gfx::kGLImplementationDesktopName);
93 #else
94   cmd_line->AppendSwitchASCII(switches::kUseGL, gfx::kGLImplementationEGLName);
95 #endif
96   return gfx::GLSurface::InitializeOneOff();
97 }
98
99 RenderingHelper::RenderingHelper() {
100   window_ = gfx::kNullAcceleratedWidget;
101   Clear();
102 }
103
104 RenderingHelper::~RenderingHelper() {
105   CHECK_EQ(videos_.size(), 0U) << "Must call UnInitialize before dtor.";
106   Clear();
107 }
108
109 void RenderingHelper::Initialize(const RenderingHelperParams& params,
110                                  base::WaitableEvent* done) {
111   // Use videos_.size() != 0 as a proxy for the class having already been
112   // Initialize()'d, and UnInitialize() before continuing.
113   if (videos_.size()) {
114     base::WaitableEvent done(false, false);
115     UnInitialize(&done);
116     done.Wait();
117   }
118
119   render_task_.Reset(
120       base::Bind(&RenderingHelper::RenderContent, base::Unretained(this)));
121
122   frame_duration_ = params.rendering_fps > 0
123                         ? base::TimeDelta::FromSeconds(1) / params.rendering_fps
124                         : base::TimeDelta();
125
126   render_as_thumbnails_ = params.render_as_thumbnails;
127   message_loop_ = base::MessageLoop::current();
128
129 #if defined(OS_WIN)
130   screen_size_ =
131       gfx::Size(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
132   window_ = CreateWindowEx(0,
133                            L"Static",
134                            L"VideoDecodeAcceleratorTest",
135                            WS_OVERLAPPEDWINDOW | WS_VISIBLE,
136                            0,
137                            0,
138                            screen_size_.width(),
139                            screen_size_.height(),
140                            NULL,
141                            NULL,
142                            NULL,
143                            NULL);
144 #elif defined(USE_X11)
145   Display* display = gfx::GetXDisplay();
146   Screen* screen = DefaultScreenOfDisplay(display);
147   screen_size_ = gfx::Size(XWidthOfScreen(screen), XHeightOfScreen(screen));
148
149   CHECK(display);
150
151   XSetWindowAttributes window_attributes;
152   memset(&window_attributes, 0, sizeof(window_attributes));
153   window_attributes.background_pixel =
154       BlackPixel(display, DefaultScreen(display));
155   window_attributes.override_redirect = true;
156   int depth = DefaultDepth(display, DefaultScreen(display));
157
158   window_ = XCreateWindow(display,
159                           DefaultRootWindow(display),
160                           0,
161                           0,
162                           screen_size_.width(),
163                           screen_size_.height(),
164                           0 /* border width */,
165                           depth,
166                           CopyFromParent /* class */,
167                           CopyFromParent /* visual */,
168                           (CWBackPixel | CWOverrideRedirect),
169                           &window_attributes);
170   XStoreName(display, window_, "VideoDecodeAcceleratorTest");
171   XSelectInput(display, window_, ExposureMask);
172   XMapWindow(display, window_);
173 #else
174 #error unknown platform
175 #endif
176   CHECK(window_ != gfx::kNullAcceleratedWidget);
177
178   gl_surface_ = gfx::GLSurface::CreateViewGLSurface(window_);
179   gl_context_ = gfx::GLContext::CreateGLContext(
180       NULL, gl_surface_.get(), gfx::PreferIntegratedGpu);
181   gl_context_->MakeCurrent(gl_surface_.get());
182
183   CHECK_GT(params.window_sizes.size(), 0U);
184   videos_.resize(params.window_sizes.size());
185   LayoutRenderingAreas(params.window_sizes);
186
187   if (render_as_thumbnails_) {
188     CHECK_EQ(videos_.size(), 1U);
189
190     GLint max_texture_size;
191     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
192     CHECK_GE(max_texture_size, params.thumbnails_page_size.width());
193     CHECK_GE(max_texture_size, params.thumbnails_page_size.height());
194
195     thumbnails_fbo_size_ = params.thumbnails_page_size;
196     thumbnail_size_ = params.thumbnail_size;
197
198     glGenFramebuffersEXT(1, &thumbnails_fbo_id_);
199     glGenTextures(1, &thumbnails_texture_id_);
200     glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
201     glTexImage2D(GL_TEXTURE_2D,
202                  0,
203                  GL_RGB,
204                  thumbnails_fbo_size_.width(),
205                  thumbnails_fbo_size_.height(),
206                  0,
207                  GL_RGB,
208                  GL_UNSIGNED_SHORT_5_6_5,
209                  NULL);
210     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
211     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
212     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
213     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
214     glBindTexture(GL_TEXTURE_2D, 0);
215
216     glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
217     glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
218                               GL_COLOR_ATTACHMENT0,
219                               GL_TEXTURE_2D,
220                               thumbnails_texture_id_,
221                               0);
222
223     GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
224     CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status;
225     glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
226     glClear(GL_COLOR_BUFFER_BIT);
227     glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
228   }
229
230   // These vertices and texture coords. map (0,0) in the texture to the
231   // bottom left of the viewport.  Since we get the video frames with the
232   // the top left at (0,0) we need to flip the texture y coordinate
233   // in the vertex shader for this to be rendered the right way up.
234   // In the case of thumbnail rendering we use the same vertex shader
235   // to render the FBO the screen, where we do not want this flipping.
236   static const float kVertices[] =
237       { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, };
238   static const float kTextureCoords[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
239   static const char kVertexShader[] = STRINGIZE(
240       varying vec2 interp_tc;
241       attribute vec4 in_pos;
242       attribute vec2 in_tc;
243       uniform bool tex_flip;
244       void main() {
245         if (tex_flip)
246           interp_tc = vec2(in_tc.x, 1.0 - in_tc.y);
247         else
248           interp_tc = in_tc;
249         gl_Position = in_pos;
250       });
251
252 #if GL_VARIANT_EGL
253   static const char kFragmentShader[] =
254       "#extension GL_OES_EGL_image_external : enable\n"
255       "precision mediump float;\n"
256       "varying vec2 interp_tc;\n"
257       "uniform sampler2D tex;\n"
258       "#ifdef GL_OES_EGL_image_external\n"
259       "uniform samplerExternalOES tex_external;\n"
260       "#endif\n"
261       "void main() {\n"
262       "  vec4 color = texture2D(tex, interp_tc);\n"
263       "#ifdef GL_OES_EGL_image_external\n"
264       "  color += texture2D(tex_external, interp_tc);\n"
265       "#endif\n"
266       "  gl_FragColor = color;\n"
267       "}\n";
268 #else
269   static const char kFragmentShader[] = STRINGIZE(
270       varying vec2 interp_tc;
271       uniform sampler2D tex;
272       void main() {
273         gl_FragColor = texture2D(tex, interp_tc);
274       });
275 #endif
276   program_ = glCreateProgram();
277   CreateShader(
278       program_, GL_VERTEX_SHADER, kVertexShader, arraysize(kVertexShader));
279   CreateShader(program_,
280                GL_FRAGMENT_SHADER,
281                kFragmentShader,
282                arraysize(kFragmentShader));
283   glLinkProgram(program_);
284   int result = GL_FALSE;
285   glGetProgramiv(program_, GL_LINK_STATUS, &result);
286   if (!result) {
287     char log[4096];
288     glGetShaderInfoLog(program_, arraysize(log), NULL, log);
289     LOG(FATAL) << log;
290   }
291   glUseProgram(program_);
292   glDeleteProgram(program_);
293
294   glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
295   glUniform1i(glGetUniformLocation(program_, "tex"), 0);
296   GLint tex_external = glGetUniformLocation(program_, "tex_external");
297   if (tex_external != -1) {
298     glUniform1i(tex_external, 1);
299   }
300   int pos_location = glGetAttribLocation(program_, "in_pos");
301   glEnableVertexAttribArray(pos_location);
302   glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
303   int tc_location = glGetAttribLocation(program_, "in_tc");
304   glEnableVertexAttribArray(tc_location);
305   glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords);
306
307   if (frame_duration_ != base::TimeDelta())
308     WarmUpRendering(params.warm_up_iterations);
309
310   // It's safe to use Unretained here since |rendering_thread_| will be stopped
311   // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is
312   // a member of that class. (See video_decode_accelerator_unittest.cc.)
313   gl_surface_->GetVSyncProvider()->GetVSyncParameters(base::Bind(
314       &RenderingHelper::UpdateVSyncParameters, base::Unretained(this), done));
315 }
316
317 // The rendering for the first few frames is slow (e.g., 100ms on Peach Pit).
318 // This affects the numbers measured in the performance test. We try to render
319 // several frames here to warm up the rendering.
320 void RenderingHelper::WarmUpRendering(int warm_up_iterations) {
321   unsigned int texture_id;
322   scoped_ptr<GLubyte[]> emptyData(new GLubyte[screen_size_.GetArea() * 2]);
323   glGenTextures(1, &texture_id);
324   glBindTexture(GL_TEXTURE_2D, texture_id);
325   glTexImage2D(GL_TEXTURE_2D,
326                0,
327                GL_RGB,
328                screen_size_.width(),
329                screen_size_.height(),
330                0,
331                GL_RGB,
332                GL_UNSIGNED_SHORT_5_6_5,
333                emptyData.get());
334   for (int i = 0; i < warm_up_iterations; ++i) {
335     RenderTexture(GL_TEXTURE_2D, texture_id);
336     gl_surface_->SwapBuffers();
337   }
338   glDeleteTextures(1, &texture_id);
339 }
340
341 void RenderingHelper::UnInitialize(base::WaitableEvent* done) {
342   CHECK_EQ(base::MessageLoop::current(), message_loop_);
343
344   render_task_.Cancel();
345
346   if (render_as_thumbnails_) {
347     glDeleteTextures(1, &thumbnails_texture_id_);
348     glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_);
349   }
350
351   gl_context_->ReleaseCurrent(gl_surface_.get());
352   gl_context_ = NULL;
353   gl_surface_ = NULL;
354
355   Clear();
356   done->Signal();
357 }
358
359 void RenderingHelper::CreateTexture(uint32 texture_target,
360                                     uint32* texture_id,
361                                     const gfx::Size& size,
362                                     base::WaitableEvent* done) {
363   if (base::MessageLoop::current() != message_loop_) {
364     message_loop_->PostTask(FROM_HERE,
365                             base::Bind(&RenderingHelper::CreateTexture,
366                                        base::Unretained(this),
367                                        texture_target,
368                                        texture_id,
369                                        size,
370                                        done));
371     return;
372   }
373   glGenTextures(1, texture_id);
374   glBindTexture(texture_target, *texture_id);
375   if (texture_target == GL_TEXTURE_2D) {
376     glTexImage2D(GL_TEXTURE_2D,
377                  0,
378                  GL_RGBA,
379                  size.width(),
380                  size.height(),
381                  0,
382                  GL_RGBA,
383                  GL_UNSIGNED_BYTE,
384                  NULL);
385   }
386   glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
387   glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
388   // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
389   glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
390   glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
391   CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
392   done->Signal();
393 }
394
395 // Helper function to set GL viewport.
396 static inline void GLSetViewPort(const gfx::Rect& area) {
397   glViewport(area.x(), area.y(), area.width(), area.height());
398   glScissor(area.x(), area.y(), area.width(), area.height());
399 }
400
401 void RenderingHelper::RenderThumbnail(uint32 texture_target,
402                                       uint32 texture_id) {
403   CHECK_EQ(base::MessageLoop::current(), message_loop_);
404   const int width = thumbnail_size_.width();
405   const int height = thumbnail_size_.height();
406   const int thumbnails_in_row = thumbnails_fbo_size_.width() / width;
407   const int thumbnails_in_column = thumbnails_fbo_size_.height() / height;
408   const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column;
409   const int col = frame_count_ % thumbnails_in_row;
410
411   gfx::Rect area(col * width, row * height, width, height);
412
413   glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
414   glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
415   GLSetViewPort(area);
416   RenderTexture(texture_target, texture_id);
417   glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
418
419   // Need to flush the GL commands before we return the tnumbnail texture to
420   // the decoder.
421   glFlush();
422   ++frame_count_;
423 }
424
425 void RenderingHelper::QueueVideoFrame(
426     size_t window_id,
427     scoped_refptr<VideoFrameTexture> video_frame) {
428   CHECK_EQ(base::MessageLoop::current(), message_loop_);
429   RenderedVideo* video = &videos_[window_id];
430   DCHECK(!video->is_flushing);
431
432   video->pending_frames.push(video_frame);
433
434   if (video->frames_to_drop > 0 && video->pending_frames.size() > 1) {
435     --video->frames_to_drop;
436     video->pending_frames.pop();
437   }
438
439   // Schedules the first RenderContent() if need.
440   if (scheduled_render_time_.is_null()) {
441     scheduled_render_time_ = base::TimeTicks::Now();
442     message_loop_->PostTask(FROM_HERE, render_task_.callback());
443   }
444 }
445
446 void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) {
447   // The ExternalOES sampler is bound to GL_TEXTURE1 and the Texture2D sampler
448   // is bound to GL_TEXTURE0.
449   if (texture_target == GL_TEXTURE_2D) {
450     glActiveTexture(GL_TEXTURE0 + 0);
451   } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) {
452     glActiveTexture(GL_TEXTURE0 + 1);
453   }
454   glBindTexture(texture_target, texture_id);
455   glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
456   glBindTexture(texture_target, 0);
457   CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
458 }
459
460 void RenderingHelper::DeleteTexture(uint32 texture_id) {
461   CHECK_EQ(base::MessageLoop::current(), message_loop_);
462   glDeleteTextures(1, &texture_id);
463   CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
464 }
465
466 void* RenderingHelper::GetGLContext() {
467   return gl_context_->GetHandle();
468 }
469
470 void* RenderingHelper::GetGLDisplay() {
471   return gl_surface_->GetDisplay();
472 }
473
474 void RenderingHelper::Clear() {
475   videos_.clear();
476   message_loop_ = NULL;
477   gl_context_ = NULL;
478   gl_surface_ = NULL;
479
480   render_as_thumbnails_ = false;
481   frame_count_ = 0;
482   thumbnails_fbo_id_ = 0;
483   thumbnails_texture_id_ = 0;
484
485 #if defined(OS_WIN)
486   if (window_)
487     DestroyWindow(window_);
488 #else
489   // Destroy resources acquired in Initialize, in reverse-acquisition order.
490   if (window_) {
491     CHECK(XUnmapWindow(gfx::GetXDisplay(), window_));
492     CHECK(XDestroyWindow(gfx::GetXDisplay(), window_));
493   }
494 #endif
495   window_ = gfx::kNullAcceleratedWidget;
496 }
497
498 void RenderingHelper::GetThumbnailsAsRGB(std::vector<unsigned char>* rgb,
499                                          bool* alpha_solid,
500                                          base::WaitableEvent* done) {
501   CHECK(render_as_thumbnails_);
502
503   const size_t num_pixels = thumbnails_fbo_size_.GetArea();
504   std::vector<unsigned char> rgba;
505   rgba.resize(num_pixels * 4);
506   glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
507   glPixelStorei(GL_PACK_ALIGNMENT, 1);
508   // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support.
509   glReadPixels(0,
510                0,
511                thumbnails_fbo_size_.width(),
512                thumbnails_fbo_size_.height(),
513                GL_RGBA,
514                GL_UNSIGNED_BYTE,
515                &rgba[0]);
516   glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
517   rgb->resize(num_pixels * 3);
518   // Drop the alpha channel, but check as we go that it is all 0xff.
519   bool solid = true;
520   unsigned char* rgb_ptr = &((*rgb)[0]);
521   unsigned char* rgba_ptr = &rgba[0];
522   for (size_t i = 0; i < num_pixels; ++i) {
523     *rgb_ptr++ = *rgba_ptr++;
524     *rgb_ptr++ = *rgba_ptr++;
525     *rgb_ptr++ = *rgba_ptr++;
526     solid = solid && (*rgba_ptr == 0xff);
527     rgba_ptr++;
528   }
529   *alpha_solid = solid;
530
531   done->Signal();
532 }
533
534 void RenderingHelper::Flush(size_t window_id) {
535   videos_[window_id].is_flushing = true;
536 }
537
538 void RenderingHelper::RenderContent() {
539   CHECK_EQ(base::MessageLoop::current(), message_loop_);
540
541   // Update the VSync params.
542   //
543   // It's safe to use Unretained here since |rendering_thread_| will be stopped
544   // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is
545   // a member of that class. (See video_decode_accelerator_unittest.cc.)
546   gl_surface_->GetVSyncProvider()->GetVSyncParameters(
547       base::Bind(&RenderingHelper::UpdateVSyncParameters,
548                  base::Unretained(this),
549                  static_cast<base::WaitableEvent*>(NULL)));
550
551   glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1);
552
553   // Frames that will be returned to the client (via the no_longer_needed_cb)
554   // after this vector falls out of scope at the end of this method. We need
555   // to keep references to them until after SwapBuffers() call below.
556   std::vector<scoped_refptr<VideoFrameTexture> > frames_to_be_returned;
557   bool need_swap_buffer = false;
558   if (render_as_thumbnails_) {
559     // In render_as_thumbnails_ mode, we render the FBO content on the
560     // screen instead of the decoded textures.
561     GLSetViewPort(videos_[0].render_area);
562     RenderTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
563     need_swap_buffer = true;
564   } else {
565     for (RenderedVideo& video : videos_) {
566       if (video.pending_frames.empty())
567         continue;
568       need_swap_buffer = true;
569       scoped_refptr<VideoFrameTexture> frame = video.pending_frames.front();
570       GLSetViewPort(video.render_area);
571       RenderTexture(frame->texture_target(), frame->texture_id());
572
573       if (video.pending_frames.size() > 1 || video.is_flushing) {
574         frames_to_be_returned.push_back(video.pending_frames.front());
575         video.pending_frames.pop();
576       } else {
577         ++video.frames_to_drop;
578       }
579     }
580   }
581
582   if (need_swap_buffer)
583     gl_surface_->SwapBuffers();
584
585   ScheduleNextRenderContent();
586 }
587
588 // Helper function for the LayoutRenderingAreas(). The |lengths| are the
589 // heights(widths) of the rows(columns). It scales the elements in
590 // |lengths| proportionally so that the sum of them equal to |total_length|.
591 // It also outputs the coordinates of the rows(columns) to |offsets|.
592 static void ScaleAndCalculateOffsets(std::vector<int>* lengths,
593                                      std::vector<int>* offsets,
594                                      int total_length) {
595   int sum = std::accumulate(lengths->begin(), lengths->end(), 0);
596   for (size_t i = 0; i < lengths->size(); ++i) {
597     lengths->at(i) = lengths->at(i) * total_length / sum;
598     offsets->at(i) = (i == 0) ? 0 : offsets->at(i - 1) + lengths->at(i - 1);
599   }
600 }
601
602 void RenderingHelper::LayoutRenderingAreas(
603     const std::vector<gfx::Size>& window_sizes) {
604   // Find the number of colums and rows.
605   // The smallest n * n or n * (n + 1) > number of windows.
606   size_t cols = sqrt(videos_.size() - 1) + 1;
607   size_t rows = (videos_.size() + cols - 1) / cols;
608
609   // Find the widths and heights of the grid.
610   std::vector<int> widths(cols);
611   std::vector<int> heights(rows);
612   std::vector<int> offset_x(cols);
613   std::vector<int> offset_y(rows);
614
615   for (size_t i = 0; i < window_sizes.size(); ++i) {
616     const gfx::Size& size = window_sizes[i];
617     widths[i % cols] = std::max(widths[i % cols], size.width());
618     heights[i / cols] = std::max(heights[i / cols], size.height());
619   }
620
621   ScaleAndCalculateOffsets(&widths, &offset_x, screen_size_.width());
622   ScaleAndCalculateOffsets(&heights, &offset_y, screen_size_.height());
623
624   // Put each render_area_ in the center of each cell.
625   for (size_t i = 0; i < window_sizes.size(); ++i) {
626     const gfx::Size& size = window_sizes[i];
627     float scale =
628         std::min(static_cast<float>(widths[i % cols]) / size.width(),
629                  static_cast<float>(heights[i / cols]) / size.height());
630
631     // Don't scale up the texture.
632     scale = std::min(1.0f, scale);
633
634     size_t w = scale * size.width();
635     size_t h = scale * size.height();
636     size_t x = offset_x[i % cols] + (widths[i % cols] - w) / 2;
637     size_t y = offset_y[i / cols] + (heights[i / cols] - h) / 2;
638     videos_[i].render_area = gfx::Rect(x, y, w, h);
639   }
640 }
641
642 void RenderingHelper::UpdateVSyncParameters(base::WaitableEvent* done,
643                                             const base::TimeTicks timebase,
644                                             const base::TimeDelta interval) {
645   vsync_timebase_ = timebase;
646   vsync_interval_ = interval;
647
648   if (done)
649     done->Signal();
650 }
651
652 void RenderingHelper::DropOneFrameForAllVideos() {
653   for (RenderedVideo& video : videos_) {
654     if (video.pending_frames.empty())
655       continue;
656
657     if (video.pending_frames.size() > 1 || video.is_flushing) {
658       video.pending_frames.pop();
659     } else {
660       ++video.frames_to_drop;
661     }
662   }
663 }
664
665 void RenderingHelper::ScheduleNextRenderContent() {
666   scheduled_render_time_ += frame_duration_;
667
668   // Schedules the next RenderContent() at latest VSYNC before the
669   // |scheduled_render_time_|.
670   base::TimeTicks now = base::TimeTicks::Now();
671   base::TimeTicks target =
672       std::max(now + vsync_interval_, scheduled_render_time_);
673
674   int64 intervals = (target - vsync_timebase_) / vsync_interval_;
675   target = vsync_timebase_ + intervals * vsync_interval_;
676
677   // When the rendering falls behind, drops frames.
678   while (scheduled_render_time_ < target) {
679     scheduled_render_time_ += frame_duration_;
680     DropOneFrameForAllVideos();
681   }
682
683   message_loop_->PostDelayedTask(
684       FROM_HERE, render_task_.callback(), target - now);
685 }
686 }  // namespace content