// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/common/gpu/media/rendering_helper.h" #include #include #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/mac/scoped_nsautorelease_pool.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/stringize_macros.h" #include "base/synchronization/waitable_event.h" #include "base/time/time.h" #include "ui/gl/gl_context.h" #include "ui/gl/gl_implementation.h" #include "ui/gl/gl_surface.h" #if defined(OS_WIN) #include #endif #if defined(USE_X11) #include "ui/gfx/x/x11_types.h" #endif #if defined(ARCH_CPU_X86_FAMILY) && defined(USE_X11) #include "ui/gl/gl_surface_glx.h" #define GL_VARIANT_GLX 1 #else #include "ui/gl/gl_surface_egl.h" #define GL_VARIANT_EGL 1 #endif #if defined(USE_OZONE) #if defined(OS_CHROMEOS) #include "ui/display/chromeos/display_configurator.h" #include "ui/display/types/native_display_delegate.h" #endif // defined(OS_CHROMEOS) #include "ui/ozone/public/ozone_platform.h" #include "ui/platform_window/platform_window.h" #include "ui/platform_window/platform_window_delegate.h" #endif // defined(USE_OZONE) // Helper for Shader creation. static void CreateShader(GLuint program, GLenum type, const char* source, int size) { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, &size); glCompileShader(shader); int result = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &result); if (!result) { char log[4096]; glGetShaderInfoLog(shader, arraysize(log), NULL, log); LOG(FATAL) << log; } glAttachShader(program, shader); glDeleteShader(shader); CHECK_EQ(static_cast(glGetError()), GL_NO_ERROR); } namespace content { #if defined(USE_OZONE) class DisplayConfiguratorObserver : public ui::DisplayConfigurator::Observer { public: DisplayConfiguratorObserver(base::RunLoop* loop) : loop_(loop) {} ~DisplayConfiguratorObserver() override {} private: // ui::DisplayConfigurator::Observer overrides: void OnDisplayModeChanged( const ui::DisplayConfigurator::DisplayStateList& outputs) override { if (!loop_) return; loop_->Quit(); loop_ = nullptr; } void OnDisplayModeChangeFailed( const ui::DisplayConfigurator::DisplayStateList& outputs, ui::MultipleDisplayState failed_new_state) override { LOG(FATAL) << "Could not configure display"; } base::RunLoop* loop_; DISALLOW_COPY_AND_ASSIGN(DisplayConfiguratorObserver); }; class RenderingHelper::StubOzoneDelegate : public ui::PlatformWindowDelegate { public: StubOzoneDelegate() : accelerated_widget_(gfx::kNullAcceleratedWidget) { platform_window_ = ui::OzonePlatform::GetInstance()->CreatePlatformWindow( this, gfx::Rect()); } ~StubOzoneDelegate() override {} void OnBoundsChanged(const gfx::Rect& new_bounds) override {} void OnDamageRect(const gfx::Rect& damaged_region) override {} void DispatchEvent(ui::Event* event) override {} void OnCloseRequest() override {} void OnClosed() override {} void OnWindowStateChanged(ui::PlatformWindowState new_state) override {} void OnLostCapture() override {}; void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override { accelerated_widget_ = widget; }; void OnActivationChanged(bool active) override {}; gfx::AcceleratedWidget accelerated_widget() const { return accelerated_widget_; } gfx::Size GetSize() { return platform_window_->GetBounds().size(); } ui::PlatformWindow* platform_window() const { return platform_window_.get(); } private: scoped_ptr platform_window_; gfx::AcceleratedWidget accelerated_widget_; DISALLOW_COPY_AND_ASSIGN(StubOzoneDelegate); }; #endif // defined(USE_OZONE) RenderingHelperParams::RenderingHelperParams() : rendering_fps(0), warm_up_iterations(0), render_as_thumbnails(false) { } RenderingHelperParams::~RenderingHelperParams() {} VideoFrameTexture::VideoFrameTexture(uint32 texture_target, uint32 texture_id, const base::Closure& no_longer_needed_cb) : texture_target_(texture_target), texture_id_(texture_id), no_longer_needed_cb_(no_longer_needed_cb) { DCHECK(!no_longer_needed_cb_.is_null()); } VideoFrameTexture::~VideoFrameTexture() { base::ResetAndReturn(&no_longer_needed_cb_).Run(); } RenderingHelper::RenderedVideo::RenderedVideo() : is_flushing(false), frames_to_drop(0) { } RenderingHelper::RenderedVideo::~RenderedVideo() { } // static void RenderingHelper::InitializeOneOff(base::WaitableEvent* done) { base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); #if GL_VARIANT_GLX cmd_line->AppendSwitchASCII(switches::kUseGL, gfx::kGLImplementationDesktopName); #else cmd_line->AppendSwitchASCII(switches::kUseGL, gfx::kGLImplementationEGLName); #endif if (!gfx::GLSurface::InitializeOneOff()) LOG(FATAL) << "Could not initialize GL"; done->Signal(); } RenderingHelper::RenderingHelper() : ignore_vsync_(false) { window_ = gfx::kNullAcceleratedWidget; Clear(); } RenderingHelper::~RenderingHelper() { CHECK_EQ(videos_.size(), 0U) << "Must call UnInitialize before dtor."; Clear(); } void RenderingHelper::Setup() { #if defined(OS_WIN) window_ = CreateWindowEx(0, L"Static", L"VideoDecodeAcceleratorTest", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, NULL, NULL); #elif defined(USE_X11) Display* display = gfx::GetXDisplay(); Screen* screen = DefaultScreenOfDisplay(display); CHECK(display); XSetWindowAttributes window_attributes; memset(&window_attributes, 0, sizeof(window_attributes)); window_attributes.background_pixel = BlackPixel(display, DefaultScreen(display)); window_attributes.override_redirect = true; int depth = DefaultDepth(display, DefaultScreen(display)); window_ = XCreateWindow(display, DefaultRootWindow(display), 0, 0, XWidthOfScreen(screen), XHeightOfScreen(screen), 0 /* border width */, depth, CopyFromParent /* class */, CopyFromParent /* visual */, (CWBackPixel | CWOverrideRedirect), &window_attributes); XStoreName(display, window_, "VideoDecodeAcceleratorTest"); XSelectInput(display, window_, ExposureMask); XMapWindow(display, window_); #elif defined(USE_OZONE) base::MessageLoop::ScopedNestableTaskAllower nest_loop( base::MessageLoop::current()); base::RunLoop wait_window_resize; platform_window_delegate_.reset(new RenderingHelper::StubOzoneDelegate()); window_ = platform_window_delegate_->accelerated_widget(); gfx::Size window_size(800, 600); // Ignore the vsync provider by default. On ChromeOS this will be set // accordingly based on the display configuration. ignore_vsync_ = true; #if defined(OS_CHROMEOS) // We hold onto the main loop here to wait for the DisplayController // to give us the size of the display so we can create a window of // the same size. base::RunLoop wait_display_setup; DisplayConfiguratorObserver display_setup_observer(&wait_display_setup); display_configurator_.reset(new ui::DisplayConfigurator()); display_configurator_->SetDelegateForTesting(0); display_configurator_->AddObserver(&display_setup_observer); display_configurator_->Init(true); display_configurator_->ForceInitialConfigure(0); // Make sure all the display configuration is applied. wait_display_setup.Run(); display_configurator_->RemoveObserver(&display_setup_observer); gfx::Size framebuffer_size = display_configurator_->framebuffer_size(); if (!framebuffer_size.IsEmpty()) { window_size = framebuffer_size; ignore_vsync_ = false; } #endif if (ignore_vsync_) DVLOG(1) << "Ignoring vsync provider"; platform_window_delegate_->platform_window()->SetBounds( gfx::Rect(window_size)); // On Ozone/DRI, platform windows are associated with the physical // outputs. Association is achieved by matching the bounds of the // window with the origin & modeset of the display output. Until a // window is associated with a display output, we cannot get vsync // events, because there is no hardware to get events from. Here we // wait for the window to resized and therefore associated with // display output to be sure that we will get such events. wait_window_resize.RunUntilIdle(); #else #error unknown platform #endif CHECK(window_ != gfx::kNullAcceleratedWidget); } void RenderingHelper::TearDown() { #if defined(OS_WIN) if (window_) DestroyWindow(window_); #elif defined(USE_X11) // Destroy resources acquired in Initialize, in reverse-acquisition order. if (window_) { CHECK(XUnmapWindow(gfx::GetXDisplay(), window_)); CHECK(XDestroyWindow(gfx::GetXDisplay(), window_)); } #elif defined(USE_OZONE) platform_window_delegate_.reset(); #if defined(OS_CHROMEOS) display_configurator_->PrepareForExit(); display_configurator_.reset(); #endif #endif window_ = gfx::kNullAcceleratedWidget; } void RenderingHelper::Initialize(const RenderingHelperParams& params, base::WaitableEvent* done) { // Use videos_.size() != 0 as a proxy for the class having already been // Initialize()'d, and UnInitialize() before continuing. if (videos_.size()) { base::WaitableEvent done(false, false); UnInitialize(&done); done.Wait(); } render_task_.Reset( base::Bind(&RenderingHelper::RenderContent, base::Unretained(this))); frame_duration_ = params.rendering_fps > 0 ? base::TimeDelta::FromSeconds(1) / params.rendering_fps : base::TimeDelta(); render_as_thumbnails_ = params.render_as_thumbnails; message_loop_ = base::MessageLoop::current(); gl_surface_ = gfx::GLSurface::CreateViewGLSurface(window_); #if defined(USE_OZONE) gl_surface_->Resize(platform_window_delegate_->GetSize()); #endif // defined(USE_OZONE) screen_size_ = gl_surface_->GetSize(); gl_context_ = gfx::GLContext::CreateGLContext( NULL, gl_surface_.get(), gfx::PreferIntegratedGpu); CHECK(gl_context_->MakeCurrent(gl_surface_.get())); CHECK_GT(params.window_sizes.size(), 0U); videos_.resize(params.window_sizes.size()); LayoutRenderingAreas(params.window_sizes); if (render_as_thumbnails_) { CHECK_EQ(videos_.size(), 1U); GLint max_texture_size; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); CHECK_GE(max_texture_size, params.thumbnails_page_size.width()); CHECK_GE(max_texture_size, params.thumbnails_page_size.height()); thumbnails_fbo_size_ = params.thumbnails_page_size; thumbnail_size_ = params.thumbnail_size; glGenFramebuffersEXT(1, &thumbnails_fbo_id_); glGenTextures(1, &thumbnails_texture_id_); glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, thumbnails_fbo_size_.width(), thumbnails_fbo_size_.height(), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_); glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, thumbnails_texture_id_, 0); GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER); CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status; glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glBindFramebufferEXT(GL_FRAMEBUFFER, gl_surface_->GetBackingFrameBufferObject()); } // These vertices and texture coords. map (0,0) in the texture to the // bottom left of the viewport. Since we get the video frames with the // the top left at (0,0) we need to flip the texture y coordinate // in the vertex shader for this to be rendered the right way up. // In the case of thumbnail rendering we use the same vertex shader // to render the FBO the screen, where we do not want this flipping. static const float kVertices[] = { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, }; static const float kTextureCoords[] = { 0, 1, 0, 0, 1, 1, 1, 0, }; static const char kVertexShader[] = STRINGIZE( varying vec2 interp_tc; attribute vec4 in_pos; attribute vec2 in_tc; uniform bool tex_flip; void main() { if (tex_flip) interp_tc = vec2(in_tc.x, 1.0 - in_tc.y); else interp_tc = in_tc; gl_Position = in_pos; }); #if GL_VARIANT_EGL static const char kFragmentShader[] = "#extension GL_OES_EGL_image_external : enable\n" "precision mediump float;\n" "varying vec2 interp_tc;\n" "uniform sampler2D tex;\n" "#ifdef GL_OES_EGL_image_external\n" "uniform samplerExternalOES tex_external;\n" "#endif\n" "void main() {\n" " vec4 color = texture2D(tex, interp_tc);\n" "#ifdef GL_OES_EGL_image_external\n" " color += texture2D(tex_external, interp_tc);\n" "#endif\n" " gl_FragColor = color;\n" "}\n"; #else static const char kFragmentShader[] = STRINGIZE( varying vec2 interp_tc; uniform sampler2D tex; void main() { gl_FragColor = texture2D(tex, interp_tc); }); #endif program_ = glCreateProgram(); CreateShader( program_, GL_VERTEX_SHADER, kVertexShader, arraysize(kVertexShader)); CreateShader(program_, GL_FRAGMENT_SHADER, kFragmentShader, arraysize(kFragmentShader)); glLinkProgram(program_); int result = GL_FALSE; glGetProgramiv(program_, GL_LINK_STATUS, &result); if (!result) { char log[4096]; glGetShaderInfoLog(program_, arraysize(log), NULL, log); LOG(FATAL) << log; } glUseProgram(program_); glDeleteProgram(program_); glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0); glUniform1i(glGetUniformLocation(program_, "tex"), 0); GLint tex_external = glGetUniformLocation(program_, "tex_external"); if (tex_external != -1) { glUniform1i(tex_external, 1); } int pos_location = glGetAttribLocation(program_, "in_pos"); glEnableVertexAttribArray(pos_location); glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices); int tc_location = glGetAttribLocation(program_, "in_tc"); glEnableVertexAttribArray(tc_location); glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords); if (frame_duration_ != base::TimeDelta()) { int warm_up_iterations = params.warm_up_iterations; #if defined(USE_OZONE) // On Ozone the VSyncProvider can't provide a vsync interval until // we render at least a frame, so we warm up with at least one // frame. // On top of this, we want to make sure all the buffers backing // the GL surface are cleared, otherwise we can see the previous // test's last frames, so we set warm up iterations to 2, to clear // the front and back buffers. warm_up_iterations = std::max(2, warm_up_iterations); #endif WarmUpRendering(warm_up_iterations); } // It's safe to use Unretained here since |rendering_thread_| will be stopped // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is // a member of that class. (See video_decode_accelerator_unittest.cc.) gfx::VSyncProvider* vsync_provider = gl_surface_->GetVSyncProvider(); // VSync providers rely on the underlying CRTC to get the timing. In headless // mode the surface isn't associated with a CRTC so the vsync provider can not // get the timing, meaning it will not call UpdateVsyncParameters() ever. if (!ignore_vsync_ && vsync_provider && frame_duration_ != base::TimeDelta()) vsync_provider->GetVSyncParameters(base::Bind( &RenderingHelper::UpdateVSyncParameters, base::Unretained(this), done)); else done->Signal(); } // The rendering for the first few frames is slow (e.g., 100ms on Peach Pit). // This affects the numbers measured in the performance test. We try to render // several frames here to warm up the rendering. void RenderingHelper::WarmUpRendering(int warm_up_iterations) { unsigned int texture_id; scoped_ptr emptyData(new GLubyte[screen_size_.GetArea() * 2]()); glGenTextures(1, &texture_id); glBindTexture(GL_TEXTURE_2D, texture_id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, screen_size_.width(), screen_size_.height(), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, emptyData.get()); for (int i = 0; i < warm_up_iterations; ++i) { RenderTexture(GL_TEXTURE_2D, texture_id); gl_surface_->SwapBuffers(); } glDeleteTextures(1, &texture_id); } void RenderingHelper::UnInitialize(base::WaitableEvent* done) { CHECK_EQ(base::MessageLoop::current(), message_loop_); render_task_.Cancel(); if (render_as_thumbnails_) { glDeleteTextures(1, &thumbnails_texture_id_); glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_); } gl_surface_->Destroy(); gl_context_->ReleaseCurrent(gl_surface_.get()); gl_context_ = NULL; gl_surface_ = NULL; Clear(); done->Signal(); } void RenderingHelper::CreateTexture(uint32 texture_target, uint32* texture_id, const gfx::Size& size, base::WaitableEvent* done) { if (base::MessageLoop::current() != message_loop_) { message_loop_->PostTask(FROM_HERE, base::Bind(&RenderingHelper::CreateTexture, base::Unretained(this), texture_target, texture_id, size, done)); return; } glGenTextures(1, texture_id); glBindTexture(texture_target, *texture_id); if (texture_target == GL_TEXTURE_2D) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); } glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures. glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); CHECK_EQ(static_cast(glGetError()), GL_NO_ERROR); done->Signal(); } // Helper function to set GL viewport. static inline void GLSetViewPort(const gfx::Rect& area) { glViewport(area.x(), area.y(), area.width(), area.height()); glScissor(area.x(), area.y(), area.width(), area.height()); } void RenderingHelper::RenderThumbnail(uint32 texture_target, uint32 texture_id) { CHECK_EQ(base::MessageLoop::current(), message_loop_); const int width = thumbnail_size_.width(); const int height = thumbnail_size_.height(); const int thumbnails_in_row = thumbnails_fbo_size_.width() / width; const int thumbnails_in_column = thumbnails_fbo_size_.height() / height; const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column; const int col = frame_count_ % thumbnails_in_row; gfx::Rect area(col * width, row * height, width, height); glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0); glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_); GLSetViewPort(area); RenderTexture(texture_target, texture_id); glBindFramebufferEXT(GL_FRAMEBUFFER, gl_surface_->GetBackingFrameBufferObject()); // Need to flush the GL commands before we return the tnumbnail texture to // the decoder. glFlush(); ++frame_count_; } void RenderingHelper::QueueVideoFrame( size_t window_id, scoped_refptr video_frame) { CHECK_EQ(base::MessageLoop::current(), message_loop_); RenderedVideo* video = &videos_[window_id]; DCHECK(!video->is_flushing); video->pending_frames.push(video_frame); if (video->frames_to_drop > 0 && video->pending_frames.size() > 1) { --video->frames_to_drop; video->pending_frames.pop(); } // Schedules the first RenderContent() if need. if (scheduled_render_time_.is_null()) { scheduled_render_time_ = base::TimeTicks::Now(); message_loop_->PostTask(FROM_HERE, render_task_.callback()); } } void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) { // The ExternalOES sampler is bound to GL_TEXTURE1 and the Texture2D sampler // is bound to GL_TEXTURE0. if (texture_target == GL_TEXTURE_2D) { glActiveTexture(GL_TEXTURE0 + 0); } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) { glActiveTexture(GL_TEXTURE0 + 1); } glBindTexture(texture_target, texture_id); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindTexture(texture_target, 0); CHECK_EQ(static_cast(glGetError()), GL_NO_ERROR); } void RenderingHelper::DeleteTexture(uint32 texture_id) { CHECK_EQ(base::MessageLoop::current(), message_loop_); glDeleteTextures(1, &texture_id); CHECK_EQ(static_cast(glGetError()), GL_NO_ERROR); } scoped_refptr RenderingHelper::GetGLContext() { return gl_context_; } void* RenderingHelper::GetGLContextHandle() { return gl_context_->GetHandle(); } void* RenderingHelper::GetGLDisplay() { return gl_surface_->GetDisplay(); } void RenderingHelper::Clear() { videos_.clear(); message_loop_ = NULL; gl_context_ = NULL; gl_surface_ = NULL; render_as_thumbnails_ = false; frame_count_ = 0; thumbnails_fbo_id_ = 0; thumbnails_texture_id_ = 0; } void RenderingHelper::GetThumbnailsAsRGB(std::vector* rgb, bool* alpha_solid, base::WaitableEvent* done) { CHECK(render_as_thumbnails_); const size_t num_pixels = thumbnails_fbo_size_.GetArea(); std::vector rgba; rgba.resize(num_pixels * 4); glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_); glPixelStorei(GL_PACK_ALIGNMENT, 1); // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support. glReadPixels(0, 0, thumbnails_fbo_size_.width(), thumbnails_fbo_size_.height(), GL_RGBA, GL_UNSIGNED_BYTE, &rgba[0]); glBindFramebufferEXT(GL_FRAMEBUFFER, gl_surface_->GetBackingFrameBufferObject()); rgb->resize(num_pixels * 3); // Drop the alpha channel, but check as we go that it is all 0xff. bool solid = true; unsigned char* rgb_ptr = &((*rgb)[0]); unsigned char* rgba_ptr = &rgba[0]; for (size_t i = 0; i < num_pixels; ++i) { *rgb_ptr++ = *rgba_ptr++; *rgb_ptr++ = *rgba_ptr++; *rgb_ptr++ = *rgba_ptr++; solid = solid && (*rgba_ptr == 0xff); rgba_ptr++; } *alpha_solid = solid; done->Signal(); } void RenderingHelper::Flush(size_t window_id) { videos_[window_id].is_flushing = true; } void RenderingHelper::RenderContent() { CHECK_EQ(base::MessageLoop::current(), message_loop_); // Update the VSync params. // // It's safe to use Unretained here since |rendering_thread_| will be stopped // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is // a member of that class. (See video_decode_accelerator_unittest.cc.) gfx::VSyncProvider* vsync_provider = gl_surface_->GetVSyncProvider(); if (vsync_provider && !ignore_vsync_) { vsync_provider->GetVSyncParameters(base::Bind( &RenderingHelper::UpdateVSyncParameters, base::Unretained(this), static_cast(NULL))); } int tex_flip = 1; #if defined(USE_OZONE) // Ozone surfaceless renders flipped from normal GL, so there's no need to // do an extra flip. tex_flip = 0; #endif // defined(USE_OZONE) glUniform1i(glGetUniformLocation(program_, "tex_flip"), tex_flip); // Frames that will be returned to the client (via the no_longer_needed_cb) // after this vector falls out of scope at the end of this method. We need // to keep references to them until after SwapBuffers() call below. std::vector > frames_to_be_returned; bool need_swap_buffer = false; if (render_as_thumbnails_) { // In render_as_thumbnails_ mode, we render the FBO content on the // screen instead of the decoded textures. GLSetViewPort(videos_[0].render_area); RenderTexture(GL_TEXTURE_2D, thumbnails_texture_id_); need_swap_buffer = true; } else { for (RenderedVideo& video : videos_) { if (video.pending_frames.empty()) continue; need_swap_buffer = true; scoped_refptr frame = video.pending_frames.front(); GLSetViewPort(video.render_area); RenderTexture(frame->texture_target(), frame->texture_id()); if (video.pending_frames.size() > 1 || video.is_flushing) { frames_to_be_returned.push_back(video.pending_frames.front()); video.pending_frames.pop(); } else { ++video.frames_to_drop; } } } if (need_swap_buffer) gl_surface_->SwapBuffers(); ScheduleNextRenderContent(); } // Helper function for the LayoutRenderingAreas(). The |lengths| are the // heights(widths) of the rows(columns). It scales the elements in // |lengths| proportionally so that the sum of them equal to |total_length|. // It also outputs the coordinates of the rows(columns) to |offsets|. static void ScaleAndCalculateOffsets(std::vector* lengths, std::vector* offsets, int total_length) { int sum = std::accumulate(lengths->begin(), lengths->end(), 0); for (size_t i = 0; i < lengths->size(); ++i) { lengths->at(i) = lengths->at(i) * total_length / sum; offsets->at(i) = (i == 0) ? 0 : offsets->at(i - 1) + lengths->at(i - 1); } } void RenderingHelper::LayoutRenderingAreas( const std::vector& window_sizes) { // Find the number of colums and rows. // The smallest n * n or n * (n + 1) > number of windows. size_t cols = sqrt(videos_.size() - 1) + 1; size_t rows = (videos_.size() + cols - 1) / cols; // Find the widths and heights of the grid. std::vector widths(cols); std::vector heights(rows); std::vector offset_x(cols); std::vector offset_y(rows); for (size_t i = 0; i < window_sizes.size(); ++i) { const gfx::Size& size = window_sizes[i]; widths[i % cols] = std::max(widths[i % cols], size.width()); heights[i / cols] = std::max(heights[i / cols], size.height()); } ScaleAndCalculateOffsets(&widths, &offset_x, screen_size_.width()); ScaleAndCalculateOffsets(&heights, &offset_y, screen_size_.height()); // Put each render_area_ in the center of each cell. for (size_t i = 0; i < window_sizes.size(); ++i) { const gfx::Size& size = window_sizes[i]; float scale = std::min(static_cast(widths[i % cols]) / size.width(), static_cast(heights[i / cols]) / size.height()); // Don't scale up the texture. scale = std::min(1.0f, scale); size_t w = scale * size.width(); size_t h = scale * size.height(); size_t x = offset_x[i % cols] + (widths[i % cols] - w) / 2; size_t y = offset_y[i / cols] + (heights[i / cols] - h) / 2; videos_[i].render_area = gfx::Rect(x, y, w, h); } } void RenderingHelper::UpdateVSyncParameters(base::WaitableEvent* done, const base::TimeTicks timebase, const base::TimeDelta interval) { vsync_timebase_ = timebase; vsync_interval_ = interval; if (done) done->Signal(); } void RenderingHelper::DropOneFrameForAllVideos() { for (RenderedVideo& video : videos_) { if (video.pending_frames.empty()) continue; if (video.pending_frames.size() > 1 || video.is_flushing) { video.pending_frames.pop(); } else { ++video.frames_to_drop; } } } void RenderingHelper::ScheduleNextRenderContent() { scheduled_render_time_ += frame_duration_; base::TimeTicks now = base::TimeTicks::Now(); base::TimeTicks target; if (vsync_interval_ != base::TimeDelta()) { // Schedules the next RenderContent() at latest VSYNC before the // |scheduled_render_time_|. target = std::max(now + vsync_interval_, scheduled_render_time_); int64 intervals = (target - vsync_timebase_) / vsync_interval_; target = vsync_timebase_ + intervals * vsync_interval_; } else { target = std::max(now, scheduled_render_time_); } // When the rendering falls behind, drops frames. while (scheduled_render_time_ < target) { scheduled_render_time_ += frame_duration_; DropOneFrameForAllVideos(); } message_loop_->PostDelayedTask( FROM_HERE, render_task_.callback(), target - now); } } // namespace content