| // Copyright 2018 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 "ui/gl/gl_surface_presentation_helper.h" |
| |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "ui/gfx/vsync_provider.h" |
| #include "ui/gl/gl_context.h" |
| #include "ui/gl/gl_fence.h" |
| #include "ui/gl/gpu_timing.h" |
| |
| namespace gl { |
| |
| GLSurfacePresentationHelper::ScopedSwapBuffers::ScopedSwapBuffers( |
| GLSurfacePresentationHelper* helper, |
| const GLSurface::PresentationCallback& callback) |
| : helper_(helper) { |
| if (helper_) |
| helper_->PreSwapBuffers(callback); |
| } |
| |
| GLSurfacePresentationHelper::ScopedSwapBuffers::~ScopedSwapBuffers() { |
| if (helper_) |
| helper_->PostSwapBuffers(result_); |
| } |
| |
| GLSurfacePresentationHelper::Frame::Frame(Frame&& other) = default; |
| |
| GLSurfacePresentationHelper::Frame::Frame( |
| std::unique_ptr<GPUTimer>&& timer, |
| const GLSurface::PresentationCallback& callback) |
| : timer(std::move(timer)), callback(callback) {} |
| |
| GLSurfacePresentationHelper::Frame::Frame( |
| std::unique_ptr<GLFence>&& fence, |
| const GLSurface::PresentationCallback& callback) |
| : fence(std::move(fence)), callback(callback) {} |
| |
| GLSurfacePresentationHelper::Frame::Frame( |
| const GLSurface::PresentationCallback& callback) |
| : callback(callback) {} |
| |
| GLSurfacePresentationHelper::Frame::~Frame() = default; |
| |
| GLSurfacePresentationHelper::Frame& GLSurfacePresentationHelper::Frame:: |
| operator=(Frame&& other) = default; |
| |
| bool GLSurfacePresentationHelper::Frame::StillPending() const { |
| DCHECK(timer || fence); |
| return timer ? !timer->IsAvailable() : !fence->HasCompleted(); |
| } |
| |
| base::TimeTicks GLSurfacePresentationHelper::Frame::GetTimestamp() const { |
| DCHECK(!StillPending()); |
| if (timer) { |
| int64_t start = 0; |
| int64_t end = 0; |
| timer->GetStartEndTimestamps(&start, &end); |
| return base::TimeTicks() + base::TimeDelta::FromMicroseconds(start); |
| } |
| return base::TimeTicks::Now(); |
| } |
| |
| void GLSurfacePresentationHelper::Frame::Destroy(bool has_context) { |
| if (timer) { |
| timer->Destroy(has_context); |
| } else if (fence) { |
| if (has_context) |
| fence = nullptr; |
| else |
| fence->Invalidate(); |
| } |
| callback.Run(gfx::PresentationFeedback::Failure()); |
| } |
| |
| GLSurfacePresentationHelper::GLSurfacePresentationHelper( |
| gfx::VSyncProvider* vsync_provider) |
| : vsync_provider_(vsync_provider), weak_ptr_factory_(this) {} |
| |
| GLSurfacePresentationHelper::GLSurfacePresentationHelper( |
| base::TimeTicks timebase, |
| base::TimeDelta interval) |
| : vsync_provider_(nullptr), |
| vsync_timebase_(timebase), |
| vsync_interval_(interval), |
| weak_ptr_factory_(this) {} |
| |
| GLSurfacePresentationHelper::~GLSurfacePresentationHelper() { |
| // Discard pending frames and run presentation callback with empty |
| // PresentationFeedback. |
| bool has_context = gl_context_ && gl_context_->IsCurrent(surface_); |
| for (auto& frame : pending_frames_) { |
| frame.Destroy(has_context); |
| } |
| pending_frames_.clear(); |
| } |
| |
| void GLSurfacePresentationHelper::OnMakeCurrent(GLContext* context, |
| GLSurface* surface) { |
| DCHECK(context); |
| DCHECK(surface); |
| DCHECK(surface == surface_ || !surface_); |
| if (context == gl_context_) |
| return; |
| |
| surface_ = surface; |
| // If context is changed, we assume SwapBuffers issued for previous context |
| // will be discarded. |
| if (gpu_timing_client_) |
| gpu_timing_client_ = nullptr; |
| for (auto& frame : pending_frames_) { |
| frame.Destroy(); |
| } |
| pending_frames_.clear(); |
| |
| gl_context_ = context; |
| gpu_timing_client_ = context->CreateGPUTimingClient(); |
| if (!gpu_timing_client_->IsAvailable()) |
| gpu_timing_client_ = nullptr; |
| } |
| |
| void GLSurfacePresentationHelper::PreSwapBuffers( |
| const GLSurface::PresentationCallback& callback) { |
| if (gpu_timing_client_) { |
| std::unique_ptr<GPUTimer> timer; |
| timer = gpu_timing_client_->CreateGPUTimer(false /* prefer_elapsed_time */); |
| timer->QueryTimeStamp(); |
| pending_frames_.push_back(Frame(std::move(timer), callback)); |
| } else if (GLFence::IsSupported()) { |
| auto fence = GLFence::Create(); |
| pending_frames_.push_back(Frame(std::move(fence), callback)); |
| } else { |
| pending_frames_.push_back(Frame(callback)); |
| } |
| } |
| |
| void GLSurfacePresentationHelper::PostSwapBuffers(gfx::SwapResult result) { |
| DCHECK(!pending_frames_.empty()); |
| auto& frame = pending_frames_.back(); |
| frame.result = result; |
| ScheduleCheckPendingFrames(false /* align_with_next_vsync */); |
| } |
| |
| void GLSurfacePresentationHelper::CheckPendingFrames() { |
| DCHECK(gl_context_ || pending_frames_.empty()); |
| |
| if (vsync_provider_ && |
| vsync_provider_->SupportGetVSyncParametersIfAvailable()) { |
| if (!vsync_provider_->GetVSyncParametersIfAvailable(&vsync_timebase_, |
| &vsync_interval_)) { |
| vsync_timebase_ = base::TimeTicks(); |
| vsync_interval_ = base::TimeDelta(); |
| LOG(ERROR) << "GetVSyncParametersIfAvailable() failed!"; |
| } |
| } |
| |
| if (pending_frames_.empty()) |
| return; |
| |
| if (!gl_context_->MakeCurrent(surface_)) { |
| gl_context_ = nullptr; |
| gpu_timing_client_ = nullptr; |
| for (auto& frame : pending_frames_) |
| frame.Destroy(); |
| pending_frames_.clear(); |
| return; |
| } |
| |
| bool need_update_vsync = false; |
| bool disjoint_occurred = |
| gpu_timing_client_ && gpu_timing_client_->CheckAndResetTimerErrors(); |
| if (disjoint_occurred || (!gpu_timing_client_ && !GLFence::IsSupported())) { |
| // If GPUTimer and GLFence are not avaliable or disjoint occurred, we will |
| // compute the next VSync's timestamp and use it to run presentation |
| // callback. |
| uint32_t flags = 0; |
| auto timestamp = base::TimeTicks::Now(); |
| if (!vsync_interval_.is_zero()) { |
| timestamp = timestamp.SnappedToNextTick(vsync_timebase_, vsync_interval_); |
| flags = gfx::PresentationFeedback::kVSync; |
| } |
| gfx::PresentationFeedback feedback(timestamp, vsync_interval_, flags); |
| for (auto& frame : pending_frames_) { |
| if (frame.timer) |
| frame.timer->Destroy(true /* has_context */); |
| if (frame.result == gfx::SwapResult::SWAP_ACK) |
| frame.callback.Run(feedback); |
| else |
| frame.callback.Run(gfx::PresentationFeedback::Failure()); |
| } |
| pending_frames_.clear(); |
| // We want to update VSync, if we can not get VSync parameters |
| // synchronously. Otherwise we will update the VSync parameters with the |
| // next SwapBuffers. |
| if (vsync_provider_ && |
| !vsync_provider_->SupportGetVSyncParametersIfAvailable()) { |
| need_update_vsync = true; |
| } |
| } |
| |
| const bool fixed_vsync = !vsync_provider_; |
| const bool hw_clock = vsync_provider_ && vsync_provider_->IsHWClock(); |
| |
| while (!pending_frames_.empty()) { |
| auto& frame = pending_frames_.front(); |
| // Helper lambda for running the presentation callback and releasing the |
| // frame. |
| auto frame_presentation_callback = |
| [this, &frame](const gfx::PresentationFeedback& feedback) { |
| if (frame.timer) |
| frame.timer->Destroy(true /* has_context */); |
| frame.callback.Run(feedback); |
| pending_frames_.pop_front(); |
| }; |
| |
| if (frame.result != gfx::SwapResult::SWAP_ACK) { |
| frame_presentation_callback(gfx::PresentationFeedback::Failure()); |
| continue; |
| } |
| |
| if (frame.StillPending()) |
| break; |
| |
| auto timestamp = frame.GetTimestamp(); |
| |
| if (vsync_interval_.is_zero() || fixed_vsync) { |
| // If VSync parameters are fixed or not avaliable, we just run |
| // presentation callbacks with timestamp from GPUTimers. |
| frame_presentation_callback( |
| gfx::PresentationFeedback(timestamp, vsync_interval_, 0 /* flags */)); |
| } else if (timestamp < vsync_timebase_) { |
| // We got a VSync whose timestamp is after GPU finished renderering this |
| // back buffer. |
| uint32_t flags = gfx::PresentationFeedback::kVSync | |
| gfx::PresentationFeedback::kHWCompletion; |
| auto delta = vsync_timebase_ - timestamp; |
| if (delta < vsync_interval_) { |
| // The |vsync_timebase_| is the closest VSync's timestamp after the GPU |
| // finished renderering. |
| timestamp = vsync_timebase_; |
| if (hw_clock) |
| flags |= gfx::PresentationFeedback::kHWClock; |
| } else { |
| // The |vsync_timebase_| isn't the closest VSync's timestamp after the |
| // GPU finished renderering. We have to compute the closest VSync's |
| // timestmp. |
| timestamp = |
| timestamp.SnappedToNextTick(vsync_timebase_, vsync_interval_); |
| } |
| frame_presentation_callback( |
| gfx::PresentationFeedback(timestamp, vsync_interval_, flags)); |
| } else { |
| // The |vsync_timebase_| is earlier than |timestamp|, we will compute the |
| // next vSync's timestamp and use it to run callback. |
| uint32_t flags = 0; |
| if (!vsync_interval_.is_zero()) { |
| timestamp = |
| timestamp.SnappedToNextTick(vsync_timebase_, vsync_interval_); |
| flags = gfx::PresentationFeedback::kVSync; |
| } |
| frame_presentation_callback( |
| gfx::PresentationFeedback(timestamp, vsync_interval_, flags)); |
| } |
| } |
| |
| if (pending_frames_.empty() && !need_update_vsync) |
| return; |
| ScheduleCheckPendingFrames(true /* align_with_next_vsync */); |
| } |
| |
| void GLSurfacePresentationHelper::CheckPendingFramesCallback() { |
| DCHECK(check_pending_frame_scheduled_); |
| check_pending_frame_scheduled_ = false; |
| CheckPendingFrames(); |
| } |
| |
| void GLSurfacePresentationHelper::UpdateVSyncCallback( |
| const base::TimeTicks timebase, |
| const base::TimeDelta interval) { |
| DCHECK(check_pending_frame_scheduled_); |
| check_pending_frame_scheduled_ = false; |
| vsync_timebase_ = timebase; |
| vsync_interval_ = interval; |
| CheckPendingFrames(); |
| } |
| |
| void GLSurfacePresentationHelper::ScheduleCheckPendingFrames( |
| bool align_with_next_vsync) { |
| if (check_pending_frame_scheduled_) |
| return; |
| check_pending_frame_scheduled_ = true; |
| |
| if (!align_with_next_vsync) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&GLSurfacePresentationHelper::CheckPendingFramesCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| if (vsync_provider_ && |
| !vsync_provider_->SupportGetVSyncParametersIfAvailable()) { |
| // In this case, the |vsync_provider_| will call the callback when the next |
| // VSync is received. |
| vsync_provider_->GetVSyncParameters( |
| base::BindRepeating(&GLSurfacePresentationHelper::UpdateVSyncCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| // If the |vsync_provider_| can not notify us for the next VSync |
| // asynchronically, we have to compute the next VSync time and post a delayed |
| // task so we can check the VSync later. |
| base::TimeDelta interval = vsync_interval_.is_zero() |
| ? base::TimeDelta::FromSeconds(1) / 60 |
| : vsync_interval_; |
| auto now = base::TimeTicks::Now(); |
| auto next_vsync = now.SnappedToNextTick(vsync_timebase_, interval); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&GLSurfacePresentationHelper::CheckPendingFramesCallback, |
| weak_ptr_factory_.GetWeakPtr()), |
| next_vsync - now); |
| } |
| |
| } // namespace gl |