OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/common/gpu/media/rendering_helper.h" | 5 #include "content/common/gpu/media/rendering_helper.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <numeric> | 8 #include <numeric> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
71 texture_id_(texture_id), | 71 texture_id_(texture_id), |
72 no_longer_needed_cb_(no_longer_needed_cb) { | 72 no_longer_needed_cb_(no_longer_needed_cb) { |
73 DCHECK(!no_longer_needed_cb_.is_null()); | 73 DCHECK(!no_longer_needed_cb_.is_null()); |
74 } | 74 } |
75 | 75 |
76 VideoFrameTexture::~VideoFrameTexture() { | 76 VideoFrameTexture::~VideoFrameTexture() { |
77 base::ResetAndReturn(&no_longer_needed_cb_).Run(); | 77 base::ResetAndReturn(&no_longer_needed_cb_).Run(); |
78 } | 78 } |
79 | 79 |
80 RenderingHelper::RenderedVideo::RenderedVideo() | 80 RenderingHelper::RenderedVideo::RenderedVideo() |
81 : last_frame_rendered(false), is_flushing(false), frames_to_drop(0) { | 81 : is_flushing(false), frames_to_drop(0) { |
82 } | 82 } |
83 | 83 |
84 RenderingHelper::RenderedVideo::~RenderedVideo() { | 84 RenderingHelper::RenderedVideo::~RenderedVideo() { |
85 } | 85 } |
86 | 86 |
87 // static | 87 // static |
88 bool RenderingHelper::InitializeOneOff() { | 88 bool RenderingHelper::InitializeOneOff() { |
89 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | 89 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); |
90 #if GL_VARIANT_GLX | 90 #if GL_VARIANT_GLX |
91 cmd_line->AppendSwitchASCII(switches::kUseGL, | 91 cmd_line->AppendSwitchASCII(switches::kUseGL, |
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
300 int pos_location = glGetAttribLocation(program_, "in_pos"); | 300 int pos_location = glGetAttribLocation(program_, "in_pos"); |
301 glEnableVertexAttribArray(pos_location); | 301 glEnableVertexAttribArray(pos_location); |
302 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices); | 302 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices); |
303 int tc_location = glGetAttribLocation(program_, "in_tc"); | 303 int tc_location = glGetAttribLocation(program_, "in_tc"); |
304 glEnableVertexAttribArray(tc_location); | 304 glEnableVertexAttribArray(tc_location); |
305 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords); | 305 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, kTextureCoords); |
306 | 306 |
307 if (frame_duration_ != base::TimeDelta()) | 307 if (frame_duration_ != base::TimeDelta()) |
308 WarmUpRendering(params.warm_up_iterations); | 308 WarmUpRendering(params.warm_up_iterations); |
309 | 309 |
310 done->Signal(); | 310 // It's safe to use Unretained here since |rendering_thread_| will be stopped |
| 311 // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is |
| 312 // a member of that class. (See video_decode_accelerator_unittest.cc.) |
| 313 gl_surface_->GetVSyncProvider()->GetVSyncParameters(base::Bind( |
| 314 &RenderingHelper::UpdateVSyncParameters, base::Unretained(this), done)); |
311 } | 315 } |
312 | 316 |
313 // The rendering for the first few frames is slow (e.g., 100ms on Peach Pit). | 317 // The rendering for the first few frames is slow (e.g., 100ms on Peach Pit). |
314 // This affects the numbers measured in the performance test. We try to render | 318 // This affects the numbers measured in the performance test. We try to render |
315 // several frames here to warm up the rendering. | 319 // several frames here to warm up the rendering. |
316 void RenderingHelper::WarmUpRendering(int warm_up_iterations) { | 320 void RenderingHelper::WarmUpRendering(int warm_up_iterations) { |
317 unsigned int texture_id; | 321 unsigned int texture_id; |
318 scoped_ptr<GLubyte[]> emptyData(new GLubyte[screen_size_.GetArea() * 2]); | 322 scoped_ptr<GLubyte[]> emptyData(new GLubyte[screen_size_.GetArea() * 2]); |
319 glGenTextures(1, &texture_id); | 323 glGenTextures(1, &texture_id); |
320 glBindTexture(GL_TEXTURE_2D, texture_id); | 324 glBindTexture(GL_TEXTURE_2D, texture_id); |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
418 ++frame_count_; | 422 ++frame_count_; |
419 } | 423 } |
420 | 424 |
421 void RenderingHelper::QueueVideoFrame( | 425 void RenderingHelper::QueueVideoFrame( |
422 size_t window_id, | 426 size_t window_id, |
423 scoped_refptr<VideoFrameTexture> video_frame) { | 427 scoped_refptr<VideoFrameTexture> video_frame) { |
424 CHECK_EQ(base::MessageLoop::current(), message_loop_); | 428 CHECK_EQ(base::MessageLoop::current(), message_loop_); |
425 RenderedVideo* video = &videos_[window_id]; | 429 RenderedVideo* video = &videos_[window_id]; |
426 DCHECK(!video->is_flushing); | 430 DCHECK(!video->is_flushing); |
427 | 431 |
428 // Start the rendering task when getting the first frame. | 432 video->pending_frames.push(video_frame); |
429 if (scheduled_render_time_.is_null() && | 433 |
430 (frame_duration_ != base::TimeDelta())) { | 434 if (video->frames_to_drop > 0 && video->pending_frames.size() > 1) { |
| 435 --video->frames_to_drop; |
| 436 video->pending_frames.pop(); |
| 437 } |
| 438 |
| 439 // Schedules the first RenderContent() if need. |
| 440 if (scheduled_render_time_.is_null()) { |
431 scheduled_render_time_ = base::TimeTicks::Now(); | 441 scheduled_render_time_ = base::TimeTicks::Now(); |
432 message_loop_->PostTask(FROM_HERE, render_task_.callback()); | 442 message_loop_->PostTask(FROM_HERE, render_task_.callback()); |
433 } | 443 } |
434 | |
435 if (video->frames_to_drop > 0) { | |
436 --video->frames_to_drop; | |
437 return; | |
438 } | |
439 | |
440 // Pop the last frame if it has been rendered. | |
441 if (video->last_frame_rendered) { | |
442 // When last_frame_rendered is true, we should have only one pending frame. | |
443 // Since we are going to have a new frame, we can release the pending one. | |
444 DCHECK(video->pending_frames.size() == 1); | |
445 video->pending_frames.pop(); | |
446 video->last_frame_rendered = false; | |
447 } | |
448 | |
449 video->pending_frames.push(video_frame); | |
450 } | 444 } |
451 | 445 |
452 void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) { | 446 void RenderingHelper::RenderTexture(uint32 texture_target, uint32 texture_id) { |
453 // The ExternalOES sampler is bound to GL_TEXTURE1 and the Texture2D sampler | 447 // The ExternalOES sampler is bound to GL_TEXTURE1 and the Texture2D sampler |
454 // is bound to GL_TEXTURE0. | 448 // is bound to GL_TEXTURE0. |
455 if (texture_target == GL_TEXTURE_2D) { | 449 if (texture_target == GL_TEXTURE_2D) { |
456 glActiveTexture(GL_TEXTURE0 + 0); | 450 glActiveTexture(GL_TEXTURE0 + 0); |
457 } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) { | 451 } else if (texture_target == GL_TEXTURE_EXTERNAL_OES) { |
458 glActiveTexture(GL_TEXTURE0 + 1); | 452 glActiveTexture(GL_TEXTURE0 + 1); |
459 } | 453 } |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
537 done->Signal(); | 531 done->Signal(); |
538 } | 532 } |
539 | 533 |
540 void RenderingHelper::Flush(size_t window_id) { | 534 void RenderingHelper::Flush(size_t window_id) { |
541 videos_[window_id].is_flushing = true; | 535 videos_[window_id].is_flushing = true; |
542 } | 536 } |
543 | 537 |
544 void RenderingHelper::RenderContent() { | 538 void RenderingHelper::RenderContent() { |
545 CHECK_EQ(base::MessageLoop::current(), message_loop_); | 539 CHECK_EQ(base::MessageLoop::current(), message_loop_); |
546 | 540 |
547 scheduled_render_time_ += frame_duration_; | 541 // Update the VSync params. |
548 base::TimeDelta delay = scheduled_render_time_ - base::TimeTicks::Now(); | 542 // |
549 message_loop_->PostDelayedTask( | 543 // It's safe to use Unretained here since |rendering_thread_| will be stopped |
550 FROM_HERE, render_task_.callback(), std::max(delay, base::TimeDelta())); | 544 // in VideoDecodeAcceleratorTest.TearDown(), while the |rendering_helper_| is |
| 545 // a member of that class. (See video_decode_accelerator_unittest.cc.) |
| 546 gl_surface_->GetVSyncProvider()->GetVSyncParameters( |
| 547 base::Bind(&RenderingHelper::UpdateVSyncParameters, |
| 548 base::Unretained(this), |
| 549 static_cast<base::WaitableEvent*>(NULL))); |
551 | 550 |
552 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1); | 551 glUniform1i(glGetUniformLocation(program_, "tex_flip"), 1); |
553 | 552 |
554 // Frames that will be returned to the client (via the no_longer_needed_cb) | 553 // Frames that will be returned to the client (via the no_longer_needed_cb) |
555 // after this vector falls out of scope at the end of this method. We need | 554 // after this vector falls out of scope at the end of this method. We need |
556 // to keep references to them until after SwapBuffers() call below. | 555 // to keep references to them until after SwapBuffers() call below. |
557 std::vector<scoped_refptr<VideoFrameTexture> > frames_to_be_returned; | 556 std::vector<scoped_refptr<VideoFrameTexture> > frames_to_be_returned; |
558 | 557 bool need_swap_buffer = false; |
559 if (render_as_thumbnails_) { | 558 if (render_as_thumbnails_) { |
560 // In render_as_thumbnails_ mode, we render the FBO content on the | 559 // In render_as_thumbnails_ mode, we render the FBO content on the |
561 // screen instead of the decoded textures. | 560 // screen instead of the decoded textures. |
562 GLSetViewPort(videos_[0].render_area); | 561 GLSetViewPort(videos_[0].render_area); |
563 RenderTexture(GL_TEXTURE_2D, thumbnails_texture_id_); | 562 RenderTexture(GL_TEXTURE_2D, thumbnails_texture_id_); |
| 563 need_swap_buffer = true; |
564 } else { | 564 } else { |
565 for (size_t i = 0; i < videos_.size(); ++i) { | 565 for (RenderedVideo& video : videos_) { |
566 RenderedVideo* video = &videos_[i]; | 566 if (video.pending_frames.empty()) |
567 if (video->pending_frames.empty()) | |
568 continue; | 567 continue; |
569 scoped_refptr<VideoFrameTexture> frame = video->pending_frames.front(); | 568 need_swap_buffer = true; |
570 GLSetViewPort(video->render_area); | 569 scoped_refptr<VideoFrameTexture> frame = video.pending_frames.front(); |
| 570 GLSetViewPort(video.render_area); |
571 RenderTexture(frame->texture_target(), frame->texture_id()); | 571 RenderTexture(frame->texture_target(), frame->texture_id()); |
572 | 572 |
573 if (video->last_frame_rendered) | 573 if (video.pending_frames.size() > 1 || video.is_flushing) { |
574 ++video->frames_to_drop; | 574 frames_to_be_returned.push_back(video.pending_frames.front()); |
575 | 575 video.pending_frames.pop(); |
576 if (video->pending_frames.size() > 1 || video->is_flushing) { | |
577 frames_to_be_returned.push_back(video->pending_frames.front()); | |
578 video->pending_frames.pop(); | |
579 video->last_frame_rendered = false; | |
580 } else { | 576 } else { |
581 video->last_frame_rendered = true; | 577 ++video.frames_to_drop; |
582 } | 578 } |
583 } | 579 } |
584 } | 580 } |
585 | 581 |
586 gl_surface_->SwapBuffers(); | 582 if (need_swap_buffer) |
| 583 gl_surface_->SwapBuffers(); |
| 584 |
| 585 ScheduleNextRenderContent(); |
587 } | 586 } |
588 | 587 |
589 // Helper function for the LayoutRenderingAreas(). The |lengths| are the | 588 // Helper function for the LayoutRenderingAreas(). The |lengths| are the |
590 // heights(widths) of the rows(columns). It scales the elements in | 589 // heights(widths) of the rows(columns). It scales the elements in |
591 // |lengths| proportionally so that the sum of them equal to |total_length|. | 590 // |lengths| proportionally so that the sum of them equal to |total_length|. |
592 // It also outputs the coordinates of the rows(columns) to |offsets|. | 591 // It also outputs the coordinates of the rows(columns) to |offsets|. |
593 static void ScaleAndCalculateOffsets(std::vector<int>* lengths, | 592 static void ScaleAndCalculateOffsets(std::vector<int>* lengths, |
594 std::vector<int>* offsets, | 593 std::vector<int>* offsets, |
595 int total_length) { | 594 int total_length) { |
596 int sum = std::accumulate(lengths->begin(), lengths->end(), 0); | 595 int sum = std::accumulate(lengths->begin(), lengths->end(), 0); |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
632 // Don't scale up the texture. | 631 // Don't scale up the texture. |
633 scale = std::min(1.0f, scale); | 632 scale = std::min(1.0f, scale); |
634 | 633 |
635 size_t w = scale * size.width(); | 634 size_t w = scale * size.width(); |
636 size_t h = scale * size.height(); | 635 size_t h = scale * size.height(); |
637 size_t x = offset_x[i % cols] + (widths[i % cols] - w) / 2; | 636 size_t x = offset_x[i % cols] + (widths[i % cols] - w) / 2; |
638 size_t y = offset_y[i / cols] + (heights[i / cols] - h) / 2; | 637 size_t y = offset_y[i / cols] + (heights[i / cols] - h) / 2; |
639 videos_[i].render_area = gfx::Rect(x, y, w, h); | 638 videos_[i].render_area = gfx::Rect(x, y, w, h); |
640 } | 639 } |
641 } | 640 } |
| 641 |
| 642 void RenderingHelper::UpdateVSyncParameters(base::WaitableEvent* done, |
| 643 const base::TimeTicks timebase, |
| 644 const base::TimeDelta interval) { |
| 645 vsync_timebase_ = timebase; |
| 646 vsync_interval_ = interval; |
| 647 |
| 648 if (done) |
| 649 done->Signal(); |
| 650 } |
| 651 |
| 652 void RenderingHelper::DropOneFrameForAllVideos() { |
| 653 for (RenderedVideo& video : videos_) { |
| 654 if (video.pending_frames.empty()) |
| 655 continue; |
| 656 |
| 657 if (video.pending_frames.size() > 1 || video.is_flushing) { |
| 658 video.pending_frames.pop(); |
| 659 } else { |
| 660 ++video.frames_to_drop; |
| 661 } |
| 662 } |
| 663 } |
| 664 |
| 665 void RenderingHelper::ScheduleNextRenderContent() { |
| 666 scheduled_render_time_ += frame_duration_; |
| 667 |
| 668 // Schedules the next RenderContent() at latest VSYNC before the |
| 669 // |scheduled_render_time_|. |
| 670 base::TimeTicks now = base::TimeTicks::Now(); |
| 671 base::TimeTicks target = |
| 672 std::max(now + vsync_interval_, scheduled_render_time_); |
| 673 |
| 674 int64 intervals = (target - vsync_timebase_) / vsync_interval_; |
| 675 target = vsync_timebase_ + intervals * vsync_interval_; |
| 676 |
| 677 // When the rendering falls behind, drops frames. |
| 678 while (scheduled_render_time_ < target) { |
| 679 scheduled_render_time_ += frame_duration_; |
| 680 DropOneFrameForAllVideos(); |
| 681 } |
| 682 |
| 683 message_loop_->PostDelayedTask( |
| 684 FROM_HERE, render_task_.callback(), target - now); |
| 685 } |
642 } // namespace content | 686 } // namespace content |
OLD | NEW |