chromium/components/viz/service/frame_sinks/external_begin_frame_source_android.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/viz/service/frame_sinks/external_begin_frame_source_android.h"

#include <sys/types.h>
#include <utility>

#include "base/android/build_info.h"
#include "base/android/jni_android.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/trace_event/trace_event.h"
#include "ui/gfx/android/achoreographer_compat.h"
#include "ui/gl/gl_features.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/viz/service/service_jni_headers/ExternalBeginFrameSourceAndroid_jni.h"

namespace {

base::TimeTicks ToTimeTicks(int64_t time_nanos) {
  // Warning: It is generally unsafe to manufacture TimeTicks values. The
  // following assumption is being made, AND COULD EASILY BREAK AT ANY TIME:
  // Upstream, Java code is providing "System.nanos() / 1000," and this is the
  // same timestamp that would be provided by the CLOCK_MONOTONIC POSIX clock.
  DCHECK_EQ(base::TimeTicks::GetClock(),
            base::TimeTicks::Clock::LINUX_CLOCK_MONOTONIC);
  return base::TimeTicks() + base::Nanoseconds(time_nanos);
}

}  // namespace

namespace viz {

class ExternalBeginFrameSourceAndroid::AChoreographerImpl {
 public:
  static std::unique_ptr<AChoreographerImpl> Create(
      ExternalBeginFrameSourceAndroid* client);

  AChoreographerImpl(ExternalBeginFrameSourceAndroid* client,
                     AChoreographer* choreographer);
  ~AChoreographerImpl();

  void SetEnabled(bool enabled);

 private:
  static void FrameCallback64(int64_t frame_time_nanos, void* data);
  static void RefreshRateCallback(int64_t vsync_period_nanos, void* data);
  static void VsyncCallback(
      const AChoreographerFrameCallbackData* callback_data,
      void* data);

  void OnVSync(int64_t frame_time_nanos,
               std::optional<PossibleDeadlines> possible_deadlines,
               base::WeakPtr<AChoreographerImpl>* self);
  void SetVsyncPeriod(int64_t vsync_period_nanos);
  void RequestVsyncIfNeeded();

  const raw_ptr<ExternalBeginFrameSourceAndroid> client_;
  const raw_ptr<AChoreographer> achoreographer_;

  base::TimeDelta vsync_period_;
  bool vsync_notification_enabled_ = false;
  // This is a heap-allocated WeakPtr to this object. The WeakPtr is either
  // * passed to `postFrameCallback` if there is one (and exactly one) callback
  //   pending. This is in case this is deleted before a pending callback
  //   fires, in which case the callback is responsible for deleting the
  //   WeakPtr.
  // * or owned by this member variable when there is no pending callback.
  // Thus whether this is nullptr also indicates whether there is a pending
  // frame callback.
  std::unique_ptr<base::WeakPtr<AChoreographerImpl>> self_for_frame_callback_;
  base::WeakPtrFactory<AChoreographerImpl> weak_ptr_factory_{this};
};

// static
std::unique_ptr<ExternalBeginFrameSourceAndroid::AChoreographerImpl>
ExternalBeginFrameSourceAndroid::AChoreographerImpl::Create(
    ExternalBeginFrameSourceAndroid* client) {
  if (base::android::BuildInfo::GetInstance()->sdk_int() <
      base::android::SDK_VERSION_R) {
    return nullptr;
  }
  if (!gfx::AChoreographerCompat::Get().supported)
    return nullptr;

  AChoreographer* choreographer =
      gfx::AChoreographerCompat::Get().AChoreographer_getInstanceFn();
  if (!choreographer)
    return nullptr;

  return std::make_unique<AChoreographerImpl>(client, choreographer);
}

ExternalBeginFrameSourceAndroid::AChoreographerImpl::AChoreographerImpl(
    ExternalBeginFrameSourceAndroid* client,
    AChoreographer* choreographer)
    : client_(client),
      achoreographer_(choreographer),
      vsync_period_(base::Microseconds(16666)) {
  gfx::AChoreographerCompat::Get().AChoreographer_registerRefreshRateCallbackFn(
      achoreographer_, &RefreshRateCallback, this);
  self_for_frame_callback_ =
      std::make_unique<base::WeakPtr<AChoreographerImpl>>(
          weak_ptr_factory_.GetWeakPtr());
}

ExternalBeginFrameSourceAndroid::AChoreographerImpl::~AChoreographerImpl() {
  gfx::AChoreographerCompat::Get()
      .AChoreographer_unregisterRefreshRateCallbackFn(
          achoreographer_, &RefreshRateCallback, this);
}

void ExternalBeginFrameSourceAndroid::AChoreographerImpl::SetEnabled(
    bool enabled) {
  if (vsync_notification_enabled_ == enabled)
    return;
  vsync_notification_enabled_ = enabled;
  RequestVsyncIfNeeded();
}

// static
void ExternalBeginFrameSourceAndroid::AChoreographerImpl::FrameCallback64(
    int64_t frame_time_nanos,
    void* data) {
  TRACE_EVENT0("toplevel,viz", "VSync");
  auto* self = static_cast<base::WeakPtr<AChoreographerImpl>*>(data);
  if (!(*self)) {
    delete self;
    return;
  }
  (*self)->OnVSync(frame_time_nanos, /*possible_deadlines=*/std::nullopt, self);
}

// static
void ExternalBeginFrameSourceAndroid::AChoreographerImpl::VsyncCallback(
    const AChoreographerFrameCallbackData* callback_data,
    void* data) {
  TRACE_EVENT0("toplevel", "Extend_VSync");
  auto* self = static_cast<base::WeakPtr<AChoreographerImpl>*>(data);
  if (!(*self)) {
    delete self;
    return;
  }
  DCHECK(gfx::AChoreographerCompat33::Get().supported);
  int64_t frame_time_nanos =
      gfx::AChoreographerCompat33::Get()
          .AChoreographerFrameCallbackData_getFrameTimeNanosFn(callback_data);
  size_t preferred_index =
      gfx::AChoreographerCompat33::Get()
          .AChoreographerFrameCallbackData_getPreferredFrameTimelineIndexFn(
              callback_data);
  size_t size = gfx::AChoreographerCompat33::Get()
                    .AChoreographerFrameCallbackData_getFrameTimelinesLengthFn(
                        callback_data);
  CHECK_LT(preferred_index, size);

  PossibleDeadlines possible_deadlines(preferred_index);
  for (size_t i = 0; i < size; ++i) {
    int64_t vsync_id =
        gfx::AChoreographerCompat33::Get()
            .AChoreographerFrameCallbackData_getFrameTimelineVsyncIdFn(
                callback_data, i);
    int64_t deadline =
        gfx::AChoreographerCompat33::Get()
            .AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanosFn(
                callback_data, i);
    int64_t present_time =
        gfx::AChoreographerCompat33::Get()
            .AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanosFn(
                callback_data, i);
    possible_deadlines.deadlines.emplace_back(
        vsync_id, base::Nanoseconds(deadline - frame_time_nanos),
        base::Nanoseconds(present_time - frame_time_nanos));
  }

  (*self)->OnVSync(frame_time_nanos, std::move(possible_deadlines), self);
}

// static
void ExternalBeginFrameSourceAndroid::AChoreographerImpl::RefreshRateCallback(
    int64_t vsync_period_nanos,
    void* data) {
  static_cast<AChoreographerImpl*>(data)->SetVsyncPeriod(vsync_period_nanos);
}

void ExternalBeginFrameSourceAndroid::AChoreographerImpl::OnVSync(
    int64_t frame_time_nanos,
    std::optional<PossibleDeadlines> possible_deadlines,
    base::WeakPtr<AChoreographerImpl>* self) {
  DCHECK(!self_for_frame_callback_);
  DCHECK(self);
  self_for_frame_callback_.reset(self);
  if (vsync_notification_enabled_) {
    client_->OnVSyncImpl(frame_time_nanos, vsync_period_,
                         std::move(possible_deadlines));
    RequestVsyncIfNeeded();
  }
}

void ExternalBeginFrameSourceAndroid::AChoreographerImpl::SetVsyncPeriod(
    int64_t vsync_period_nanos) {
  vsync_period_ = base::Nanoseconds(vsync_period_nanos);
}

void ExternalBeginFrameSourceAndroid::AChoreographerImpl::
    RequestVsyncIfNeeded() {
  if (!vsync_notification_enabled_ || !self_for_frame_callback_)
    return;
  if (gfx::AChoreographerCompat33::Get().supported) {
    gfx::AChoreographerCompat33::Get().AChoreographer_postVsyncCallbackFn(
        achoreographer_, &VsyncCallback, self_for_frame_callback_.release());
  } else {
    gfx::AChoreographerCompat::Get().AChoreographer_postFrameCallback64Fn(
        achoreographer_, &FrameCallback64, self_for_frame_callback_.release());
  }
}

// ============================================================================

ExternalBeginFrameSourceAndroid::ExternalBeginFrameSourceAndroid(
    uint32_t restart_id,
    float refresh_rate,
    bool requires_align_with_java)
    : ExternalBeginFrameSource(this, restart_id) {
  // Android WebView requires begin frame to be inside the "animate" stage of
  // input-animate-draw stages of the java Choreographer, which requires using
  // java Choreographer.
  if (requires_align_with_java) {
    achoreographer_ = nullptr;
  } else {
    achoreographer_ = AChoreographerImpl::Create(this);
  }
  if (!achoreographer_) {
    j_object_ = Java_ExternalBeginFrameSourceAndroid_Constructor(
        base::android::AttachCurrentThread(), reinterpret_cast<jlong>(this),
        refresh_rate);
  }
}

ExternalBeginFrameSourceAndroid::~ExternalBeginFrameSourceAndroid() {
  SetEnabled(false);
}

void ExternalBeginFrameSourceAndroid::OnVSync(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj,
    jlong time_micros,
    jlong period_micros) {
  OnVSyncImpl(time_micros * 1000, base::Microseconds(period_micros),
              /*possible_deadlines=*/std::nullopt);
}

void ExternalBeginFrameSourceAndroid::OnVSyncImpl(
    int64_t time_nanos,
    base::TimeDelta vsync_period,
    std::optional<PossibleDeadlines> possible_deadlines) {
  DCHECK_EQ(base::TimeTicks::GetClock(),
            base::TimeTicks::Clock::LINUX_CLOCK_MONOTONIC);
  base::TimeTicks frame_time = ToTimeTicks(time_nanos);
  // TODO(crbug.com/40829076): If `possible_deadlines` is present, should
  // really pick a deadline from `possible_deadlines`. However some code
  // still assume the deadline is a multiple of interval from frame time.
  base::TimeTicks deadline = frame_time + vsync_period;

  auto begin_frame_args = begin_frame_args_generator_.GenerateBeginFrameArgs(
      source_id(), frame_time, deadline, vsync_period);
  if (features::IsAndroidFrameDeadlineEnabled()) {
    begin_frame_args.possible_deadlines = std::move(possible_deadlines);
  }
  OnBeginFrame(begin_frame_args);
}

void ExternalBeginFrameSourceAndroid::UpdateRefreshRate(float refresh_rate) {
  if (j_object_) {
    Java_ExternalBeginFrameSourceAndroid_updateRefreshRate(
        base::android::AttachCurrentThread(), j_object_, refresh_rate);
  }
}

void ExternalBeginFrameSourceAndroid::OnNeedsBeginFrames(
    bool needs_begin_frames) {
  SetEnabled(needs_begin_frames);
}

void ExternalBeginFrameSourceAndroid::SetEnabled(bool enabled) {
  if (achoreographer_) {
    achoreographer_->SetEnabled(enabled);
  } else {
    DCHECK(j_object_);
    Java_ExternalBeginFrameSourceAndroid_setEnabled(
        base::android::AttachCurrentThread(), j_object_, enabled);
  }
}

}  // namespace viz