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. 40static 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 59namespace content { 60 61RenderingHelperParams::RenderingHelperParams() {} 62 63RenderingHelperParams::~RenderingHelperParams() {} 64 65VideoFrameTexture::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 74VideoFrameTexture::~VideoFrameTexture() { 75 base::ResetAndReturn(&no_longer_needed_cb_).Run(); 76} 77 78RenderingHelper::RenderedVideo::RenderedVideo() 79 : last_frame_rendered(false), is_flushing(false), frames_to_drop(0) { 80} 81 82RenderingHelper::RenderedVideo::~RenderedVideo() { 83} 84 85// static 86bool 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 97RenderingHelper::RenderingHelper() { 98 window_ = gfx::kNullAcceleratedWidget; 99 Clear(); 100} 101 102RenderingHelper::~RenderingHelper() { 103 CHECK_EQ(videos_.size(), 0U) << "Must call UnInitialize before dtor."; 104 Clear(); 105} 106 107void 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 308void 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 326void 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. 363static 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 368void 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 392void 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 423void 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 437void 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 443void* RenderingHelper::GetGLContext() { 444 return gl_context_->GetHandle(); 445} 446 447void* RenderingHelper::GetGLDisplay() { 448 return gl_surface_->GetDisplay(); 449} 450 451void 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 475void 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 511void RenderingHelper::Flush(size_t window_id) { 512 videos_[window_id].is_flushing = true; 513} 514 515void 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|. 564static 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 574void 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 614