blob: f7e6ae113b2676b12dbadb1e59a8f91066e0eb7b [file] [log] [blame]
// 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 <utility>
#include "base/bind.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "ui/gfx/vsync_provider.h"
#include "ui/gl/egl_timestamps.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,
GLSurface::PresentationCallback callback)
: ScopedSwapBuffers(helper, std::move(callback), -1) {}
GLSurfacePresentationHelper::ScopedSwapBuffers::ScopedSwapBuffers(
GLSurfacePresentationHelper* helper,
GLSurface::PresentationCallback callback,
int frame_id)
: helper_(helper) {
if (helper_)
helper_->PreSwapBuffers(std::move(callback), frame_id);
}
GLSurfacePresentationHelper::ScopedSwapBuffers::~ScopedSwapBuffers() {
if (helper_)
helper_->PostSwapBuffers(result_);
}
GLSurfacePresentationHelper::Frame::Frame(Frame&& other) = default;
GLSurfacePresentationHelper::Frame::Frame(
int frame_id,
GLSurface::PresentationCallback callback)
: frame_id(frame_id), callback(std::move(callback)) {}
GLSurfacePresentationHelper::Frame::Frame(
std::unique_ptr<GPUTimer>&& timer,
GLSurface::PresentationCallback callback)
: timer(std::move(timer)), callback(std::move(callback)) {}
GLSurfacePresentationHelper::Frame::Frame(
std::unique_ptr<GLFence>&& fence,
GLSurface::PresentationCallback callback)
: fence(std::move(fence)), callback(std::move(callback)) {}
GLSurfacePresentationHelper::Frame::Frame(
GLSurface::PresentationCallback callback)
: callback(std::move(callback)) {}
GLSurfacePresentationHelper::Frame::~Frame() = default;
GLSurfacePresentationHelper::Frame& GLSurfacePresentationHelper::Frame::
operator=(Frame&& other) = default;
bool GLSurfacePresentationHelper::GetFrameTimestampInfoIfAvailable(
const Frame& frame,
base::TimeTicks* timestamp,
base::TimeDelta* interval,
uint32_t* flags) {
DCHECK(frame.timer || frame.fence || egl_timestamp_client_);
if (egl_timestamp_client_) {
bool result = egl_timestamp_client_->GetFrameTimestampInfoIfAvailable(
timestamp, interval, flags, frame.frame_id);
// Workaround null timestamp by setting it to TimeTicks::Now() snapped to
// the next vsync interval. See
// https://bugs.chromium.org/p/chromium/issues/detail?id=966638 for more
// details.
if (result && timestamp->is_null()) {
*timestamp = base::TimeTicks::Now();
*interval = vsync_interval_;
*flags = 0;
if (!vsync_interval_.is_zero()) {
*timestamp =
timestamp->SnappedToNextTick(vsync_timebase_, vsync_interval_);
*flags = gfx::PresentationFeedback::kVSync;
}
}
return result;
} else if (frame.timer) {
if (!frame.timer->IsAvailable())
return false;
int64_t start = 0;
int64_t end = 0;
frame.timer->GetStartEndTimestamps(&start, &end);
*timestamp = base::TimeTicks() + base::TimeDelta::FromMicroseconds(start);
} else {
if (!frame.fence->HasCompleted())
return false;
*timestamp = base::TimeTicks::Now();
}
// Below logic is used to calculate final values of timestamp, interval and
// flags when using timer/fence to report the timestamps.
const bool fixed_vsync = !vsync_provider_;
const bool hw_clock = vsync_provider_ && vsync_provider_->IsHWClock();
*interval = vsync_interval_;
*flags = 0;
if (vsync_interval_.is_zero() || fixed_vsync) {
// If VSync parameters are fixed or not available, we just run
// presentation callbacks with timestamp from GPUTimers.
return true;
} else if (*timestamp < vsync_timebase_) {
// We got a VSync whose timestamp is after GPU finished rendering this
// back buffer.
*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 rendering.
*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 rendering. We have to compute the closest VSync's
// timestmp.
*timestamp =
timestamp->SnappedToNextTick(vsync_timebase_, vsync_interval_);
}
} else {
// The |vsync_timebase_| is earlier than |timestamp|, we will compute the
// next vSync's timestamp and use it to run callback.
if (!vsync_interval_.is_zero()) {
*timestamp =
timestamp->SnappedToNextTick(vsync_timebase_, vsync_interval_);
*flags = gfx::PresentationFeedback::kVSync;
}
}
return true;
}
void GLSurfacePresentationHelper::Frame::Destroy(bool has_context) {
if (timer) {
timer->Destroy(has_context);
} else if (fence) {
if (has_context)
fence = nullptr;
else
fence->Invalidate();
}
std::move(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;
// Get an egl timestamp client.
egl_timestamp_client_ = surface_->GetEGLTimestampClient();
// If there is an egl timestamp client, check if egl timestamps are supported
// or not. If supported, then return as there is no need to use gpu timestamp
// client or fence.
if (egl_timestamp_client_) {
if (egl_timestamp_client_->IsEGLTimestampSupported())
return;
else
egl_timestamp_client_ = nullptr;
}
gpu_timing_client_ = context->CreateGPUTimingClient();
if (!gpu_timing_client_->IsAvailable())
gpu_timing_client_ = nullptr;
// https://crbug.com/854298 : disable GLFence on Android as they seem to cause
// issues on some devices.
#if !defined(OS_ANDROID)
gl_fence_supported_ = GLFence::IsSupported();
#endif
}
void GLSurfacePresentationHelper::PreSwapBuffers(
GLSurface::PresentationCallback callback,
int frame_id) {
if (egl_timestamp_client_) {
pending_frames_.emplace_back(frame_id, std::move(callback));
} else 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), std::move(callback)));
} else if (gl_fence_supported_) {
auto fence = GLFence::Create();
pending_frames_.push_back(Frame(std::move(fence), std::move(callback)));
} else {
pending_frames_.push_back(Frame(std::move(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();
static unsigned int count = 0;
++count;
// GetVSyncParametersIfAvailable() could be called and failed frequently,
// so we have to limit the LOG to avoid flooding the log.
LOG_IF(ERROR, count < 20 || !(count & 0xff))
<< "GetVSyncParametersIfAvailable() failed for " << count
<< " times!";
}
}
if (pending_frames_.empty())
return;
if (!gl_context_->MakeCurrent(surface_)) {
gl_context_ = nullptr;
egl_timestamp_client_ = 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 ||
(!egl_timestamp_client_ && !gpu_timing_client_ && !gl_fence_supported_)) {
// If EGLTimestamps, GPUTimer and GLFence are not available 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)
std::move(frame.callback).Run(feedback);
else
std::move(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;
}
}
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 */);
std::move(frame.callback).Run(feedback);
pending_frames_.pop_front();
};
if (frame.result != gfx::SwapResult::SWAP_ACK) {
frame_presentation_callback(gfx::PresentationFeedback::Failure());
continue;
}
base::TimeTicks timestamp;
base::TimeDelta interval;
uint32_t flags = 0;
// Get timestamp info for a frame if available. If timestamp is not
// available, it means this frame is not yet done.
if (!GetFrameTimestampInfoIfAvailable(frame, &timestamp, &interval, &flags))
break;
frame_presentation_callback(
gfx::PresentationFeedback(timestamp, 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