Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / common / gpu / media / rendering_helper.cc
blob2ead51e4bba4ead664767b6fd791fcfa205df584
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/common/gpu/media/rendering_helper.h"
7 #include <algorithm>
8 #include <numeric>
9 #include <vector>
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/command_line.h"
14 #include "base/mac/scoped_nsautorelease_pool.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/run_loop.h"
17 #include "base/strings/stringize_macros.h"
18 #include "base/synchronization/waitable_event.h"
19 #include "base/time/time.h"
20 #include "ui/gl/gl_context.h"
21 #include "ui/gl/gl_implementation.h"
22 #include "ui/gl/gl_surface.h"
24 #if defined(OS_WIN)
25 #include <windows.h>
26 #endif
28 #if defined(USE_X11)
29 #include "ui/gfx/x/x11_types.h"
30 #endif
32 #if defined(ARCH_CPU_X86_FAMILY) && defined(USE_X11)
33 #include "ui/gl/gl_surface_glx.h"
34 #define GL_VARIANT_GLX 1
35 #else
36 #include "ui/gl/gl_surface_egl.h"
37 #define GL_VARIANT_EGL 1
38 #endif
40 #if defined(USE_OZONE)
41 #if defined(OS_CHROMEOS)
42 #include "ui/display/chromeos/display_configurator.h"
43 #include "ui/display/types/native_display_delegate.h"
44 #endif // defined(OS_CHROMEOS)
45 #include "ui/ozone/public/ozone_platform.h"
46 #include "ui/platform_window/platform_window.h"
47 #include "ui/platform_window/platform_window_delegate.h"
48 #endif // defined(USE_OZONE)
50 // Helper for Shader creation.
51 static void CreateShader(GLuint program,
52 GLenum type,
53 const char* source,
54 int size) {
55 GLuint shader = glCreateShader(type);
56 glShaderSource(shader, 1, &source, &size);
57 glCompileShader(shader);
58 int result = GL_FALSE;
59 glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
60 if (!result) {
61 char log[4096];
62 glGetShaderInfoLog(shader, arraysize(log), NULL, log);
63 LOG(FATAL) << log;
65 glAttachShader(program, shader);
66 glDeleteShader(shader);
67 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
70 namespace content {
71 namespace {
73 void WaitForSwapAck(const base::Closure& callback, gfx::SwapResult result) {
74 callback.Run();
77 } // namespace
79 #if defined(USE_OZONE)
81 class DisplayConfiguratorObserver : public ui::DisplayConfigurator::Observer {
82 public:
83 DisplayConfiguratorObserver(base::RunLoop* loop) : loop_(loop) {}
84 ~DisplayConfiguratorObserver() override {}
86 private:
87 // ui::DisplayConfigurator::Observer overrides:
88 void OnDisplayModeChanged(
89 const ui::DisplayConfigurator::DisplayStateList& outputs) override {
90 if (!loop_)
91 return;
92 loop_->Quit();
93 loop_ = nullptr;
95 void OnDisplayModeChangeFailed(
96 const ui::DisplayConfigurator::DisplayStateList& outputs,
97 ui::MultipleDisplayState failed_new_state) override {
98 LOG(FATAL) << "Could not configure display";
101 base::RunLoop* loop_;
103 DISALLOW_COPY_AND_ASSIGN(DisplayConfiguratorObserver);
106 class RenderingHelper::StubOzoneDelegate : public ui::PlatformWindowDelegate {
107 public:
108 StubOzoneDelegate() : accelerated_widget_(gfx::kNullAcceleratedWidget) {
109 platform_window_ = ui::OzonePlatform::GetInstance()->CreatePlatformWindow(
110 this, gfx::Rect());
112 ~StubOzoneDelegate() override {}
114 void OnBoundsChanged(const gfx::Rect& new_bounds) override {}
116 void OnDamageRect(const gfx::Rect& damaged_region) override {}
118 void DispatchEvent(ui::Event* event) override {}
120 void OnCloseRequest() override {}
121 void OnClosed() override {}
123 void OnWindowStateChanged(ui::PlatformWindowState new_state) override {}
125 void OnLostCapture() override {};
127 void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget,
128 float device_pixel_ratio) override {
129 accelerated_widget_ = widget;
132 void OnActivationChanged(bool active) override {};
134 gfx::AcceleratedWidget accelerated_widget() const {
135 return accelerated_widget_;
138 gfx::Size GetSize() { return platform_window_->GetBounds().size(); }
140 ui::PlatformWindow* platform_window() const { return platform_window_.get(); }
142 private:
143 scoped_ptr<ui::PlatformWindow> platform_window_;
144 gfx::AcceleratedWidget accelerated_widget_;
146 DISALLOW_COPY_AND_ASSIGN(StubOzoneDelegate);
149 #endif // defined(USE_OZONE)
151 RenderingHelperParams::RenderingHelperParams()
152 : rendering_fps(0), warm_up_iterations(0), render_as_thumbnails(false) {
155 RenderingHelperParams::~RenderingHelperParams() {}
157 VideoFrameTexture::VideoFrameTexture(uint32 texture_target,
158 uint32 texture_id,
159 const base::Closure& no_longer_needed_cb)
160 : texture_target_(texture_target),
161 texture_id_(texture_id),
162 no_longer_needed_cb_(no_longer_needed_cb) {
163 DCHECK(!no_longer_needed_cb_.is_null());
166 VideoFrameTexture::~VideoFrameTexture() {
167 base::ResetAndReturn(&no_longer_needed_cb_).Run();
170 RenderingHelper::RenderedVideo::RenderedVideo()
171 : is_flushing(false), frames_to_drop(0) {
174 RenderingHelper::RenderedVideo::~RenderedVideo() {
177 // static
178 void RenderingHelper::InitializeOneOff(base::WaitableEvent* done) {
179 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
180 #if GL_VARIANT_GLX
181 cmd_line->AppendSwitchASCII(switches::kUseGL,
182 gfx::kGLImplementationDesktopName);
183 #else
184 cmd_line->AppendSwitchASCII(switches::kUseGL, gfx::kGLImplementationEGLName);
185 #endif
187 if (!gfx::GLSurface::InitializeOneOff())
188 LOG(FATAL) << "Could not initialize GL";
189 done->Signal();
192 RenderingHelper::RenderingHelper() : ignore_vsync_(false) {
193 window_ = gfx::kNullAcceleratedWidget;
194 Clear();
197 RenderingHelper::~RenderingHelper() {
198 CHECK_EQ(videos_.size(), 0U) << "Must call UnInitialize before dtor.";
199 Clear();
202 void RenderingHelper::Setup() {
203 #if defined(OS_WIN)
204 window_ = CreateWindowEx(0,
205 L"Static",
206 L"VideoDecodeAcceleratorTest",
207 WS_OVERLAPPEDWINDOW | WS_VISIBLE,
210 GetSystemMetrics(SM_CXSCREEN),
211 GetSystemMetrics(SM_CYSCREEN),
212 NULL,
213 NULL,
214 NULL,
215 NULL);
216 #elif defined(USE_X11)
217 Display* display = gfx::GetXDisplay();
218 Screen* screen = DefaultScreenOfDisplay(display);
220 CHECK(display);
222 XSetWindowAttributes window_attributes;
223 memset(&window_attributes, 0, sizeof(window_attributes));
224 window_attributes.background_pixel =
225 BlackPixel(display, DefaultScreen(display));
226 window_attributes.override_redirect = true;
227 int depth = DefaultDepth(display, DefaultScreen(display));
229 window_ = XCreateWindow(display,
230 DefaultRootWindow(display),
233 XWidthOfScreen(screen),
234 XHeightOfScreen(screen),
235 0 /* border width */,
236 depth,
237 CopyFromParent /* class */,
238 CopyFromParent /* visual */,
239 (CWBackPixel | CWOverrideRedirect),
240 &window_attributes);
241 XStoreName(display, window_, "VideoDecodeAcceleratorTest");
242 XSelectInput(display, window_, ExposureMask);
243 XMapWindow(display, window_);
244 #elif defined(USE_OZONE)
245 base::MessageLoop::ScopedNestableTaskAllower nest_loop(
246 base::MessageLoop::current());
247 base::RunLoop wait_window_resize;
249 platform_window_delegate_.reset(new RenderingHelper::StubOzoneDelegate());
250 window_ = platform_window_delegate_->accelerated_widget();
251 gfx::Size window_size(800, 600);
252 // Ignore the vsync provider by default. On ChromeOS this will be set
253 // accordingly based on the display configuration.
254 ignore_vsync_ = true;
255 #if defined(OS_CHROMEOS)
256 // We hold onto the main loop here to wait for the DisplayController
257 // to give us the size of the display so we can create a window of
258 // the same size.
259 base::RunLoop wait_display_setup;
260 DisplayConfiguratorObserver display_setup_observer(&wait_display_setup);
261 display_configurator_.reset(new ui::DisplayConfigurator());
262 display_configurator_->SetDelegateForTesting(0);
263 display_configurator_->AddObserver(&display_setup_observer);
264 display_configurator_->Init(true);
265 display_configurator_->ForceInitialConfigure(0);
266 // Make sure all the display configuration is applied.
267 wait_display_setup.Run();
268 display_configurator_->RemoveObserver(&display_setup_observer);
270 gfx::Size framebuffer_size = display_configurator_->framebuffer_size();
271 if (!framebuffer_size.IsEmpty()) {
272 window_size = framebuffer_size;
273 ignore_vsync_ = false;
275 #endif
276 if (ignore_vsync_)
277 DVLOG(1) << "Ignoring vsync provider";
279 platform_window_delegate_->platform_window()->SetBounds(
280 gfx::Rect(window_size));
282 // On Ozone/DRI, platform windows are associated with the physical
283 // outputs. Association is achieved by matching the bounds of the
284 // window with the origin & modeset of the display output. Until a
285 // window is associated with a display output, we cannot get vsync
286 // events, because there is no hardware to get events from. Here we
287 // wait for the window to resized and therefore associated with
288 // display output to be sure that we will get such events.
289 wait_window_resize.RunUntilIdle();
290 #else
291 #error unknown platform
292 #endif
293 CHECK(window_ != gfx::kNullAcceleratedWidget);
296 void RenderingHelper::TearDown() {
297 #if defined(OS_WIN)
298 if (window_)
299 DestroyWindow(window_);
300 #elif defined(USE_X11)
301 // Destroy resources acquired in Initialize, in reverse-acquisition order.
302 if (window_) {
303 CHECK(XUnmapWindow(gfx::GetXDisplay(), window_));
304 CHECK(XDestroyWindow(gfx::GetXDisplay(), window_));
306 #elif defined(USE_OZONE)
307 platform_window_delegate_.reset();
308 #if defined(OS_CHROMEOS)
309 display_configurator_->PrepareForExit();
310 display_configurator_.reset();
311 #endif
312 #endif
313 window_ = gfx::kNullAcceleratedWidget;
316 void RenderingHelper::Initialize(const RenderingHelperParams& params,
317 base::WaitableEvent* done) {
318 // Use videos_.size() != 0 as a proxy for the class having already been
319 // Initialize()'d, and UnInitialize() before continuing.
320 if (videos_.size()) {
321 base::WaitableEvent done(false, false);
322 UnInitialize(&done);
323 done.Wait();
326 render_task_.Reset(
327 base::Bind(&RenderingHelper::RenderContent, base::Unretained(this)));
329 frame_duration_ = params.rendering_fps > 0
330 ? base::TimeDelta::FromSeconds(1) / params.rendering_fps
331 : base::TimeDelta();
333 render_as_thumbnails_ = params.render_as_thumbnails;
334 message_loop_ = base::MessageLoop::current();
336 gl_surface_ = gfx::GLSurface::CreateViewGLSurface(window_);
337 #if defined(USE_OZONE)
338 gl_surface_->Resize(platform_window_delegate_->GetSize());
339 #endif // defined(USE_OZONE)
340 screen_size_ = gl_surface_->GetSize();
342 gl_context_ = gfx::GLContext::CreateGLContext(
343 NULL, gl_surface_.get(), gfx::PreferIntegratedGpu);
344 CHECK(gl_context_->MakeCurrent(gl_surface_.get()));
346 CHECK_GT(params.window_sizes.size(), 0U);
347 videos_.resize(params.window_sizes.size());
348 LayoutRenderingAreas(params.window_sizes);
350 if (render_as_thumbnails_) {
351 CHECK_EQ(videos_.size(), 1U);
353 GLint max_texture_size;
354 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
355 CHECK_GE(max_texture_size, params.thumbnails_page_size.width());
356 CHECK_GE(max_texture_size, params.thumbnails_page_size.height());
358 thumbnails_fbo_size_ = params.thumbnails_page_size;
359 thumbnail_size_ = params.thumbnail_size;
361 glGenFramebuffersEXT(1, &thumbnails_fbo_id_);
362 glGenTextures(1, &thumbnails_texture_id_);
363 glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
364 glTexImage2D(GL_TEXTURE_2D,
366 GL_RGB,
367 thumbnails_fbo_size_.width(),
368 thumbnails_fbo_size_.height(),
370 GL_RGB,
371 GL_UNSIGNED_SHORT_5_6_5,
372 NULL);
373 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
374 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
375 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
376 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
377 glBindTexture(GL_TEXTURE_2D, 0);
379 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
380 glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
381 GL_COLOR_ATTACHMENT0,
382 GL_TEXTURE_2D,
383 thumbnails_texture_id_,
386 GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
387 CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status;
388 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
389 glClear(GL_COLOR_BUFFER_BIT);
390 glBindFramebufferEXT(GL_FRAMEBUFFER,
391 gl_surface_->GetBackingFrameBufferObject());
394 // These vertices and texture coords. map (0,0) in the texture to the
395 // bottom left of the viewport. Since we get the video frames with the
396 // the top left at (0,0) we need to flip the texture y coordinate
397 // in the vertex shader for this to be rendered the right way up.
398 // In the case of thumbnail rendering we use the same vertex shader
399 // to render the FBO the screen, where we do not want this flipping.
400 static const float kVertices[] =
401 { -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, };
402 static const float kTextureCoords[] = { 0, 1, 0, 0, 1, 1, 1, 0, };
403 static const char kVertexShader[] = STRINGIZE(
404 varying vec2 interp_tc;
405 attribute vec4 in_pos;
406 attribute vec2 in_tc;
407 uniform bool tex_flip;
408 void main() {
409 if (tex_flip)
410 interp_tc = vec2(in_tc.x, 1.0 - in_tc.y);
411 else
412 interp_tc = in_tc;
413 gl_Position = in_pos;
416 #if GL_VARIANT_EGL
417 static const char kFragmentShader[] =
418 "#extension GL_OES_EGL_image_external : enable\n"
419 "precision mediump float;\n"
420 "varying vec2 interp_tc;\n"
421 "uniform sampler2D tex;\n"
422 "#ifdef GL_OES_EGL_image_external\n"
423 "uniform samplerExternalOES tex_external;\n"
424 "#endif\n"
425 "void main() {\n"
426 " vec4 color = texture2D(tex, interp_tc);\n"
427 "#ifdef GL_OES_EGL_image_external\n"
428 " color += texture2D(tex_external, interp_tc);\n"
429 "#endif\n"
430 " gl_FragColor = color;\n"
431 "}\n";
432 #else
433 static const char kFragmentShader[] = STRINGIZE(
434 varying vec2 interp_tc;
435 uniform sampler2D tex;
436 void main() {
437 gl_FragColor = texture2D(tex, interp_tc);
439 #endif
440 program_ = glCreateProgram();
441 CreateShader(
442 program_, GL_VERTEX_SHADER, kVertexShader, arraysize(kVertexShader));
443 CreateShader(program_,
444 GL_FRAGMENT_SHADER,
445 kFragmentShader,
446 arraysize(kFragmentShader));
447 glLinkProgram(program_);
448 int result = GL_FALSE;
449 glGetProgramiv(program_, GL_LINK_STATUS, &result);
450 if (!result) {
451 char log[4096];
452 glGetShaderInfoLog(program_, arraysize(log), NULL, log);
453 LOG(FATAL) << log;
455 glUseProgram(program_);
456 glDeleteProgram(program_);
458 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
459 glUniform1i(glGetUniformLocation(program_, "tex"), 0);
460 GLint tex_external = glGetUniformLocation(program_, "tex_external");
461 if (tex_external != -1) {
462 glUniform1i(tex_external, 1);
464 int pos_location = glGetAttribLocation(program_, "in_pos");
465 glEnableVertexAttribArray(pos_location);
466 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
467 int tc_location = glGetAttribLocation(program_, "in_tc");
468 glEnableVertexAttribArray(tc_location);
469 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords);
471 if (frame_duration_ != base::TimeDelta()) {
472 int warm_up_iterations = params.warm_up_iterations;
473 #if defined(USE_OZONE)
474 // On Ozone the VSyncProvider can't provide a vsync interval until
475 // we render at least a frame, so we warm up with at least one
476 // frame.
477 // On top of this, we want to make sure all the buffers backing
478 // the GL surface are cleared, otherwise we can see the previous
479 // test's last frames, so we set warm up iterations to 2, to clear
480 // the front and back buffers.
481 warm_up_iterations = std::max(2, warm_up_iterations);
482 #endif
483 WarmUpRendering(warm_up_iterations);
486 // It's safe to use Unretained here since |rendering_thread_| will be stopped
487 // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is
488 // a member of that class. (See video_decode_accelerator_unittest.cc.)
489 gfx::VSyncProvider* vsync_provider = gl_surface_->GetVSyncProvider();
491 // VSync providers rely on the underlying CRTC to get the timing. In headless
492 // mode the surface isn't associated with a CRTC so the vsync provider can not
493 // get the timing, meaning it will not call UpdateVsyncParameters() ever.
494 if (!ignore_vsync_ && vsync_provider && frame_duration_ != base::TimeDelta())
495 vsync_provider->GetVSyncParameters(base::Bind(
496 &RenderingHelper::UpdateVSyncParameters, base::Unretained(this), done));
497 else
498 done->Signal();
501 // The rendering for the first few frames is slow (e.g., 100ms on Peach Pit).
502 // This affects the numbers measured in the performance test. We try to render
503 // several frames here to warm up the rendering.
504 void RenderingHelper::WarmUpRendering(int warm_up_iterations) {
505 unsigned int texture_id;
506 scoped_ptr<GLubyte[]> emptyData(new GLubyte[screen_size_.GetArea() * 2]());
507 glGenTextures(1, &texture_id);
508 glBindTexture(GL_TEXTURE_2D, texture_id);
509 glTexImage2D(GL_TEXTURE_2D,
511 GL_RGB,
512 screen_size_.width(),
513 screen_size_.height(),
515 GL_RGB,
516 GL_UNSIGNED_SHORT_5_6_5,
517 emptyData.get());
518 for (int i = 0; i < warm_up_iterations; ++i) {
519 RenderTexture(GL_TEXTURE_2D, texture_id);
521 // Need to allow nestable tasks since WarmUpRendering() is called from
522 // within another task on the renderer thread.
523 base::MessageLoop::ScopedNestableTaskAllower allow(
524 base::MessageLoop::current());
525 base::RunLoop wait_for_swap_ack;
526 gl_surface_->SwapBuffersAsync(
527 base::Bind(&WaitForSwapAck, wait_for_swap_ack.QuitClosure()));
528 wait_for_swap_ack.Run();
530 glDeleteTextures(1, &texture_id);
533 void RenderingHelper::UnInitialize(base::WaitableEvent* done) {
534 CHECK_EQ(base::MessageLoop::current(), message_loop_);
536 render_task_.Cancel();
538 if (render_as_thumbnails_) {
539 glDeleteTextures(1, &thumbnails_texture_id_);
540 glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_);
543 gl_context_->ReleaseCurrent(gl_surface_.get());
544 gl_context_ = NULL;
545 gl_surface_ = NULL;
547 Clear();
548 done->Signal();
551 void RenderingHelper::CreateTexture(uint32 texture_target,
552 uint32* texture_id,
553 const gfx::Size& size,
554 base::WaitableEvent* done) {
555 if (base::MessageLoop::current() != message_loop_) {
556 message_loop_->PostTask(FROM_HERE,
557 base::Bind(&RenderingHelper::CreateTexture,
558 base::Unretained(this),
559 texture_target,
560 texture_id,
561 size,
562 done));
563 return;
565 glGenTextures(1, texture_id);
566 glBindTexture(texture_target, *texture_id);
567 if (texture_target == GL_TEXTURE_2D) {
568 glTexImage2D(GL_TEXTURE_2D,
570 GL_RGBA,
571 size.width(),
572 size.height(),
574 GL_RGBA,
575 GL_UNSIGNED_BYTE,
576 NULL);
578 glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
579 glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
580 // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
581 glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
582 glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
583 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
584 done->Signal();
587 // Helper function to set GL viewport.
588 static inline void GLSetViewPort(const gfx::Rect& area) {
589 glViewport(area.x(), area.y(), area.width(), area.height());
590 glScissor(area.x(), area.y(), area.width(), area.height());
593 void RenderingHelper::RenderThumbnail(uint32 texture_target,
594 uint32 texture_id) {
595 CHECK_EQ(base::MessageLoop::current(), message_loop_);
596 const int width = thumbnail_size_.width();
597 const int height = thumbnail_size_.height();
598 const int thumbnails_in_row = thumbnails_fbo_size_.width() / width;
599 const int thumbnails_in_column = thumbnails_fbo_size_.height() / height;
600 const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column;
601 const int col = frame_count_ % thumbnails_in_row;
603 gfx::Rect area(col * width, row * height, width, height);
605 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
606 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
607 GLSetViewPort(area);
608 RenderTexture(texture_target, texture_id);
609 glBindFramebufferEXT(GL_FRAMEBUFFER,
610 gl_surface_->GetBackingFrameBufferObject());
612 // Need to flush the GL commands before we return the tnumbnail texture to
613 // the decoder.
614 glFlush();
615 ++frame_count_;
618 void RenderingHelper::QueueVideoFrame(
619 size_t window_id,
620 scoped_refptr<VideoFrameTexture> video_frame) {
621 CHECK_EQ(base::MessageLoop::current(), message_loop_);
622 RenderedVideo* video = &videos_[window_id];
623 DCHECK(!video->is_flushing);
625 video->pending_frames.push(video_frame);
627 if (video->frames_to_drop > 0 && video->pending_frames.size() > 1) {
628 --video->frames_to_drop;
629 video->pending_frames.pop();
632 // Schedules the first RenderContent() if need.
633 if (scheduled_render_time_.is_null()) {
634 scheduled_render_time_ = base::TimeTicks::Now();
635 message_loop_->PostTask(FROM_HERE, render_task_.callback());
639 void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) {
640 // The ExternalOES sampler is bound to GL_TEXTURE1 and the Texture2D sampler
641 // is bound to GL_TEXTURE0.
642 if (texture_target == GL_TEXTURE_2D) {
643 glActiveTexture(GL_TEXTURE0 + 0);
644 } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) {
645 glActiveTexture(GL_TEXTURE0 + 1);
647 glBindTexture(texture_target, texture_id);
648 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
649 glBindTexture(texture_target, 0);
650 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
653 void RenderingHelper::DeleteTexture(uint32 texture_id) {
654 CHECK_EQ(base::MessageLoop::current(), message_loop_);
655 glDeleteTextures(1, &texture_id);
656 CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
659 scoped_refptr<gfx::GLContext> RenderingHelper::GetGLContext() {
660 return gl_context_;
663 void* RenderingHelper::GetGLContextHandle() {
664 return gl_context_->GetHandle();
667 void* RenderingHelper::GetGLDisplay() {
668 return gl_surface_->GetDisplay();
671 void RenderingHelper::Clear() {
672 videos_.clear();
673 message_loop_ = NULL;
674 gl_context_ = NULL;
675 gl_surface_ = NULL;
677 render_as_thumbnails_ = false;
678 frame_count_ = 0;
679 thumbnails_fbo_id_ = 0;
680 thumbnails_texture_id_ = 0;
683 void RenderingHelper::GetThumbnailsAsRGB(std::vector<unsigned char>* rgb,
684 bool* alpha_solid,
685 base::WaitableEvent* done) {
686 CHECK(render_as_thumbnails_);
688 const size_t num_pixels = thumbnails_fbo_size_.GetArea();
689 std::vector<unsigned char> rgba;
690 rgba.resize(num_pixels * 4);
691 glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
692 glPixelStorei(GL_PACK_ALIGNMENT, 1);
693 // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support.
694 glReadPixels(0,
696 thumbnails_fbo_size_.width(),
697 thumbnails_fbo_size_.height(),
698 GL_RGBA,
699 GL_UNSIGNED_BYTE,
700 &rgba[0]);
701 glBindFramebufferEXT(GL_FRAMEBUFFER,
702 gl_surface_->GetBackingFrameBufferObject());
703 rgb->resize(num_pixels * 3);
704 // Drop the alpha channel, but check as we go that it is all 0xff.
705 bool solid = true;
706 unsigned char* rgb_ptr = &((*rgb)[0]);
707 unsigned char* rgba_ptr = &rgba[0];
708 for (size_t i = 0; i < num_pixels; ++i) {
709 *rgb_ptr++ = *rgba_ptr++;
710 *rgb_ptr++ = *rgba_ptr++;
711 *rgb_ptr++ = *rgba_ptr++;
712 solid = solid && (*rgba_ptr == 0xff);
713 rgba_ptr++;
715 *alpha_solid = solid;
717 done->Signal();
720 void RenderingHelper::Flush(size_t window_id) {
721 videos_[window_id].is_flushing = true;
724 void RenderingHelper::RenderContent() {
725 CHECK_EQ(base::MessageLoop::current(), message_loop_);
727 // Update the VSync params.
729 // It's safe to use Unretained here since |rendering_thread_| will be stopped
730 // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is
731 // a member of that class. (See video_decode_accelerator_unittest.cc.)
732 gfx::VSyncProvider* vsync_provider = gl_surface_->GetVSyncProvider();
733 if (vsync_provider && !ignore_vsync_) {
734 vsync_provider->GetVSyncParameters(base::Bind(
735 &RenderingHelper::UpdateVSyncParameters, base::Unretained(this),
736 static_cast<base::WaitableEvent*>(NULL)));
739 int tex_flip = 1;
740 #if defined(USE_OZONE)
741 // Ozone surfaceless renders flipped from normal GL, so there's no need to
742 // do an extra flip.
743 tex_flip = 0;
744 #endif // defined(USE_OZONE)
745 glUniform1i(glGetUniformLocation(program_, "tex_flip"), tex_flip);
747 // Frames that will be returned to the client (via the no_longer_needed_cb)
748 // after this vector falls out of scope at the end of this method. We need
749 // to keep references to them until after SwapBuffers() call below.
750 std::vector<scoped_refptr<VideoFrameTexture> > frames_to_be_returned;
751 bool need_swap_buffer = false;
752 if (render_as_thumbnails_) {
753 // In render_as_thumbnails_ mode, we render the FBO content on the
754 // screen instead of the decoded textures.
755 GLSetViewPort(videos_[0].render_area);
756 RenderTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
757 need_swap_buffer = true;
758 } else {
759 for (RenderedVideo& video : videos_) {
760 if (video.pending_frames.empty())
761 continue;
762 need_swap_buffer = true;
763 scoped_refptr<VideoFrameTexture> frame = video.pending_frames.front();
764 GLSetViewPort(video.render_area);
765 RenderTexture(frame->texture_target(), frame->texture_id());
767 if (video.pending_frames.size() > 1 || video.is_flushing) {
768 frames_to_be_returned.push_back(video.pending_frames.front());
769 video.pending_frames.pop();
770 } else {
771 ++video.frames_to_drop;
776 base::Closure schedule_frame = base::Bind(
777 &RenderingHelper::ScheduleNextRenderContent, base::Unretained(this));
778 if (!need_swap_buffer ||
779 !gl_surface_->SwapBuffersAsync(
780 base::Bind(&WaitForSwapAck, schedule_frame)))
781 schedule_frame.Run();
784 // Helper function for the LayoutRenderingAreas(). The |lengths| are the
785 // heights(widths) of the rows(columns). It scales the elements in
786 // |lengths| proportionally so that the sum of them equal to |total_length|.
787 // It also outputs the coordinates of the rows(columns) to |offsets|.
788 static void ScaleAndCalculateOffsets(std::vector<int>* lengths,
789 std::vector<int>* offsets,
790 int total_length) {
791 int sum = std::accumulate(lengths->begin(), lengths->end(), 0);
792 for (size_t i = 0; i < lengths->size(); ++i) {
793 lengths->at(i) = lengths->at(i) * total_length / sum;
794 offsets->at(i) = (i == 0) ? 0 : offsets->at(i - 1) + lengths->at(i - 1);
798 void RenderingHelper::LayoutRenderingAreas(
799 const std::vector<gfx::Size>& window_sizes) {
800 // Find the number of colums and rows.
801 // The smallest n * n or n * (n + 1) > number of windows.
802 size_t cols = sqrt(videos_.size() - 1) + 1;
803 size_t rows = (videos_.size() + cols - 1) / cols;
805 // Find the widths and heights of the grid.
806 std::vector<int> widths(cols);
807 std::vector<int> heights(rows);
808 std::vector<int> offset_x(cols);
809 std::vector<int> offset_y(rows);
811 for (size_t i = 0; i < window_sizes.size(); ++i) {
812 const gfx::Size& size = window_sizes[i];
813 widths[i % cols] = std::max(widths[i % cols], size.width());
814 heights[i / cols] = std::max(heights[i / cols], size.height());
817 ScaleAndCalculateOffsets(&widths, &offset_x, screen_size_.width());
818 ScaleAndCalculateOffsets(&heights, &offset_y, screen_size_.height());
820 // Put each render_area_ in the center of each cell.
821 for (size_t i = 0; i < window_sizes.size(); ++i) {
822 const gfx::Size& size = window_sizes[i];
823 float scale =
824 std::min(static_cast<float>(widths[i % cols]) / size.width(),
825 static_cast<float>(heights[i / cols]) / size.height());
827 // Don't scale up the texture.
828 scale = std::min(1.0f, scale);
830 size_t w = scale * size.width();
831 size_t h = scale * size.height();
832 size_t x = offset_x[i % cols] + (widths[i % cols] - w) / 2;
833 size_t y = offset_y[i / cols] + (heights[i / cols] - h) / 2;
834 videos_[i].render_area = gfx::Rect(x, y, w, h);
838 void RenderingHelper::UpdateVSyncParameters(base::WaitableEvent* done,
839 const base::TimeTicks timebase,
840 const base::TimeDelta interval) {
841 vsync_timebase_ = timebase;
842 vsync_interval_ = interval;
844 if (done)
845 done->Signal();
848 void RenderingHelper::DropOneFrameForAllVideos() {
849 for (RenderedVideo& video : videos_) {
850 if (video.pending_frames.empty())
851 continue;
853 if (video.pending_frames.size() > 1 || video.is_flushing) {
854 video.pending_frames.pop();
855 } else {
856 ++video.frames_to_drop;
861 void RenderingHelper::ScheduleNextRenderContent() {
862 scheduled_render_time_ += frame_duration_;
863 base::TimeTicks now = base::TimeTicks::Now();
864 base::TimeTicks target;
866 if (vsync_interval_ != base::TimeDelta()) {
867 // Schedules the next RenderContent() at latest VSYNC before the
868 // |scheduled_render_time_|.
869 target = std::max(now + vsync_interval_, scheduled_render_time_);
871 int64 intervals = (target - vsync_timebase_) / vsync_interval_;
872 target = vsync_timebase_ + intervals * vsync_interval_;
873 } else {
874 target = std::max(now, scheduled_render_time_);
877 // When the rendering falls behind, drops frames.
878 while (scheduled_render_time_ < target) {
879 scheduled_render_time_ += frame_duration_;
880 DropOneFrameForAllVideos();
883 message_loop_->PostDelayedTask(
884 FROM_HERE, render_task_.callback(), target - now);
886 } // namespace content