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