blob: a1d5b7309653815e458280fb22a5904629a7b377 [file] [log] [blame]
// Copyright 2014 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/accelerated_widget_mac/display_link_mac.h"
#include <stdint.h>
#include "base/location.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
namespace base {
template<>
struct ScopedTypeRefTraits<CVDisplayLinkRef> {
static CVDisplayLinkRef InvalidValue() { return nullptr; }
static CVDisplayLinkRef Retain(CVDisplayLinkRef object) {
return CVDisplayLinkRetain(object);
}
static void Release(CVDisplayLinkRef object) {
CVDisplayLinkRelease(object);
}
};
} // namespace base
namespace {
// Empty callback set during tear down.
CVReturn VoidDisplayLinkCallback(CVDisplayLinkRef display_link,
const CVTimeStamp* now,
const CVTimeStamp* output_time,
CVOptionFlags flags_in,
CVOptionFlags* flags_out,
void* context) {
return kCVReturnSuccess;
}
} // namespace
namespace ui {
// static
scoped_refptr<DisplayLinkMac> DisplayLinkMac::GetForDisplay(
CGDirectDisplayID display_id) {
if (!display_id)
return nullptr;
// Return the existing display link for this display, if it exists.
DisplayMap::iterator found = display_map_.Get().find(display_id);
if (found != display_map_.Get().end()) {
return found->second;
}
CVReturn ret = kCVReturnSuccess;
base::ScopedTypeRef<CVDisplayLinkRef> display_link;
ret = CVDisplayLinkCreateWithCGDisplay(
display_id,
display_link.InitializeInto());
if (ret != kCVReturnSuccess) {
LOG(ERROR) << "CVDisplayLinkCreateWithActiveCGDisplays failed: " << ret;
return NULL;
}
scoped_refptr<DisplayLinkMac> display_link_mac;
display_link_mac = new DisplayLinkMac(display_id, display_link);
ret = CVDisplayLinkSetOutputCallback(
display_link_mac->display_link_,
&DisplayLinkCallback,
display_link_mac.get());
if (ret != kCVReturnSuccess) {
LOG(ERROR) << "CVDisplayLinkSetOutputCallback failed: " << ret;
return NULL;
}
return display_link_mac;
}
DisplayLinkMac::DisplayLinkMac(
CGDirectDisplayID display_id,
base::ScopedTypeRef<CVDisplayLinkRef> display_link)
: main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
display_id_(display_id),
display_link_(display_link),
timebase_and_interval_valid_(false) {
DCHECK(display_map_.Get().find(display_id) == display_map_.Get().end());
if (display_map_.Get().empty()) {
CGError register_error = CGDisplayRegisterReconfigurationCallback(
DisplayReconfigurationCallBack, nullptr);
DPLOG_IF(ERROR, register_error != kCGErrorSuccess)
<< "CGDisplayRegisterReconfigurationCallback: "
<< register_error;
}
display_map_.Get().insert(std::make_pair(display_id_, this));
}
DisplayLinkMac::~DisplayLinkMac() {
StopDisplayLink();
// Usually |display_link_| holds the last reference to CVDisplayLinkRef, but
// that's not guaranteed, so it might not free all resources after the
// destructor completes. Ensure the callback is cleared out regardless to
// avoid possible crashes (see http://crbug.com/564780).
CVReturn ret = CVDisplayLinkSetOutputCallback(
display_link_, VoidDisplayLinkCallback, nullptr);
DCHECK_EQ(kCGErrorSuccess, ret);
DisplayMap::iterator found = display_map_.Get().find(display_id_);
DCHECK(found != display_map_.Get().end());
DCHECK(found->second == this);
display_map_.Get().erase(found);
if (display_map_.Get().empty()) {
CGError remove_error = CGDisplayRemoveReconfigurationCallback(
DisplayReconfigurationCallBack, nullptr);
DPLOG_IF(ERROR, remove_error != kCGErrorSuccess)
<< "CGDisplayRemoveReconfigurationCallback: "
<< remove_error;
}
}
bool DisplayLinkMac::GetVSyncParameters(
base::TimeTicks* timebase, base::TimeDelta* interval) {
if (!timebase_and_interval_valid_) {
StartOrContinueDisplayLink();
return false;
}
*timebase = timebase_;
*interval = interval_;
return true;
}
void DisplayLinkMac::NotifyCurrentTime(const base::TimeTicks& now) {
if (now >= recalculate_time_)
StartOrContinueDisplayLink();
}
void DisplayLinkMac::Tick(const CVTimeStamp& cv_time) {
TRACE_EVENT0("ui", "DisplayLinkMac::Tick");
// Verify that videoRefreshPeriod is 32 bits.
DCHECK((cv_time.videoRefreshPeriod & ~0xffffFFFFull) == 0ull);
// Verify that the numerator and denominator make some sense.
uint32_t numerator = static_cast<uint32_t>(cv_time.videoRefreshPeriod);
uint32_t denominator = cv_time.videoTimeScale;
if (numerator <= 0 || denominator <= 0) {
LOG(WARNING) << "Unexpected numerator or denominator, bailing.";
return;
}
timebase_ = base::TimeTicks::FromInternalValue(
cv_time.hostTime / 1000);
interval_ = base::TimeDelta::FromMicroseconds(
1000000 * static_cast<int64_t>(numerator) / denominator);
timebase_and_interval_valid_ = true;
// Don't restart the display link for 10 seconds.
recalculate_time_ = base::TimeTicks::Now() +
base::TimeDelta::FromSeconds(10);
StopDisplayLink();
}
void DisplayLinkMac::StartOrContinueDisplayLink() {
if (CVDisplayLinkIsRunning(display_link_))
return;
CVReturn ret = CVDisplayLinkStart(display_link_);
if (ret != kCVReturnSuccess) {
LOG(ERROR) << "CVDisplayLinkStart failed: " << ret;
}
}
void DisplayLinkMac::StopDisplayLink() {
if (!CVDisplayLinkIsRunning(display_link_))
return;
CVReturn ret = CVDisplayLinkStop(display_link_);
if (ret != kCVReturnSuccess) {
LOG(ERROR) << "CVDisplayLinkStop failed: " << ret;
}
}
// static
CVReturn DisplayLinkMac::DisplayLinkCallback(
CVDisplayLinkRef display_link,
const CVTimeStamp* now,
const CVTimeStamp* output_time,
CVOptionFlags flags_in,
CVOptionFlags* flags_out,
void* context) {
TRACE_EVENT0("ui", "DisplayLinkMac::DisplayLinkCallback");
DisplayLinkMac* display_link_mac = static_cast<DisplayLinkMac*>(context);
display_link_mac->main_thread_task_runner_->PostTask(
FROM_HERE,
base::Bind(&DisplayLinkMac::Tick, display_link_mac, *output_time));
return kCVReturnSuccess;
}
// static
void DisplayLinkMac::DisplayReconfigurationCallBack(
CGDirectDisplayID display,
CGDisplayChangeSummaryFlags flags,
void* user_info) {
DisplayMap::iterator found = display_map_.Get().find(display);
if (found == display_map_.Get().end())
return;
DisplayLinkMac* display_link_mac = found->second;
display_link_mac->timebase_and_interval_valid_ = false;
}
// static
base::LazyInstance<DisplayLinkMac::DisplayMap>::DestructorAtExit
DisplayLinkMac::display_map_ = LAZY_INSTANCE_INITIALIZER;
} // ui