chromium/device/vr/android/web_xr_presentation_state.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 "device/vr/android/web_xr_presentation_state.h"

#include <iomanip>
#include <sstream>
#include <vector>

#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "ui/gl/gl_fence.h"

namespace device {

WebXrSharedBuffer::WebXrSharedBuffer() = default;
WebXrSharedBuffer::~WebXrSharedBuffer() = default;

WebXrFrame::WebXrFrame() = default;

WebXrFrame::~WebXrFrame() = default;

bool WebXrFrame::IsValid() const {
  return index >= 0;
}

void WebXrFrame::Recycle() {
  DCHECK(!state_locked);
  DCHECK(reclaimed_sync_tokens.empty());
  index = -1;
  deferred_start_processing.Reset();
  recycle_once_unlocked = false;
  gvr_handoff_fence.reset();
  begin_frame_args.reset();
}

WebXrPresentationState::WebXrPresentationState() {
  for (auto& frame : frames_storage_) {
    // Create frames in "idle" state.
    frame = std::make_unique<WebXrFrame>();
    idle_frames_.push(frame.get());
  }
}

WebXrPresentationState::~WebXrPresentationState() {}

void WebXrPresentationState::SetStateMachineType(StateMachineType type) {
  state_machine_type_ = type;
}

bool WebXrPresentationState::CanStartFrameAnimating() {
  return !idle_frames_.empty();
}

WebXrFrame* WebXrPresentationState::GetAnimatingFrame() const {
  DCHECK(HaveAnimatingFrame());
  DCHECK(animating_frame_->IsValid());
  return animating_frame_;
}

WebXrFrame* WebXrPresentationState::GetProcessingFrame() const {
  DCHECK(HaveProcessingFrame());
  DCHECK(processing_frame_->IsValid());
  return processing_frame_;
}

WebXrFrame* WebXrPresentationState::GetRenderingFrame() const {
  DCHECK(HaveRenderingFrame());
  DCHECK(rendering_frame_->IsValid());
  return rendering_frame_;
}

std::string WebXrPresentationState::DebugState() const {
  std::ostringstream ss;

  ss << "[";
  if (HaveAnimatingFrame()) {
    ss << std::setw(3) << GetAnimatingFrame()->index;
  } else {
    ss << "---";
  }
  ss << "|";
  if (HaveProcessingFrame()) {
    ss << std::setw(3) << GetProcessingFrame()->index;
  } else {
    ss << "---";
  }
  ss << "|";
  switch (state_machine_type_) {
    case StateMachineType::kBrowserComposited: {
      if (HaveRenderingFrame()) {
        ss << std::setw(3) << GetRenderingFrame()->index;
      } else {
        ss << "---";
      }
      break;
    }
    case StateMachineType::kVizComposited: {
      if (rendering_frames_.size() > 0) {
        for (size_t i = 0; i < rendering_frames_.size(); i++) {
          auto* frame = rendering_frames_[i].get();
          ss << std::setw(3) << frame->index;
          ss << "(" << frame->shared_buffer->id << ", "
             << frame->camera_image_shared_buffer->id << ")";
          // Append a "," separator, unless this is the last element to list.
          if (i != rendering_frames_.size() - 1) {
            ss << ",";
          }
        }
      } else {
        ss << "---";
      }
      break;
    }
  }
  ss << "]";

  return ss.str();
}

WebXrPresentationState::FrameIndexType
WebXrPresentationState::StartFrameAnimating() {
  DCHECK(!HaveAnimatingFrame());
  DCHECK(!idle_frames_.empty());
  animating_frame_ = idle_frames_.front().get();
  idle_frames_.pop();

  animating_frame_->index = next_frame_index_++;

  DVLOG(3) << DebugState() << __func__;
  return animating_frame_->index;
}

void WebXrPresentationState::TransitionFrameAnimatingToProcessing() {
  DVLOG(3) << DebugState() << __func__;
  DCHECK(HaveAnimatingFrame());
  DCHECK(animating_frame_->IsValid());
  DCHECK(!animating_frame_->state_locked);
  DCHECK(!HaveProcessingFrame());
  processing_frame_ = animating_frame_;
  animating_frame_ = nullptr;
  DVLOG(3) << DebugState() << __func__;
}

void WebXrPresentationState::RecycleUnusedAnimatingFrame() {
  DCHECK(HaveAnimatingFrame());
  animating_frame_->Recycle();
  idle_frames_.push(animating_frame_.get());
  animating_frame_ = nullptr;
  DVLOG(3) << DebugState() << __func__;
}

void WebXrPresentationState::TransitionFrameProcessingToRendering() {
  DVLOG(3) << DebugState() << __func__;
  DCHECK(HaveProcessingFrame());
  DCHECK(processing_frame_->IsValid());
  DCHECK(!processing_frame_->state_locked);

  switch (state_machine_type_) {
    // In the BrowserComposited StateMachine we can only have one RenderingFrame
    // at a time. Assert that we don't have one, and then assign it to the slot.
    case StateMachineType::kBrowserComposited: {
      DCHECK(!HaveRenderingFrame());
      rendering_frame_ = processing_frame_;
      break;
    }
    // In the VizComposited StateMachine multiple frames may be "Rendering",
    // where "Rendering" means that the frame has been passed to the viz
    // compositor. We need to wait until the viz compositor is done with a frame
    // before it can be recycled.
    case StateMachineType::kVizComposited: {
      rendering_frames_.push_back(processing_frame_.get());
      break;
    }
  }

  processing_frame_ = nullptr;
  DVLOG(3) << DebugState() << __func__;
}

void WebXrPresentationState::EndFrameRendering(WebXrFrame* frame) {
  DCHECK(frame);
  // If we have a rendering frame, that means we should be in the
  // BrowserComposited mode. In that case, the caller may have called us with
  // the result of GetRenderingFrame() to simplify code-paths for operations
  // that they need to do with the Frame. Ensure that if we have a rendering
  // frame, we were called with it, and then process in that mode.
  if (HaveRenderingFrame()) {
    DCHECK_EQ(frame, rendering_frame_);
    EndFrameRendering();
    return;
  }

  // In this case, we don't have a RenderingFrame. If we're not running the viz
  // composited state machine, this is an error. If we are, then there should
  // be exactly one instance of this frame in the rendering_frames_ list.
  // Remove it from the list, and then recycle the frame.
  DVLOG(3) << DebugState() << __func__;
  DCHECK_EQ(state_machine_type_, StateMachineType::kVizComposited);
  auto erased = std::erase_if(rendering_frames_,
                              [frame](const WebXrFrame* rendering_frame) {
                                return frame == rendering_frame;
                              });

  // Not using DCHECK_EQ as the compiler doesn't pick the right type to compare.
  DCHECK(erased == 1);
  frame->Recycle();
  idle_frames_.push(frame);
  DVLOG(3) << DebugState() << __func__;
}

void WebXrPresentationState::EndFrameRendering() {
  DVLOG(3) << DebugState() << __func__;
  DCHECK_EQ(state_machine_type_, StateMachineType::kBrowserComposited);
  DCHECK(HaveRenderingFrame());
  DCHECK(rendering_frame_->IsValid());
  rendering_frame_->Recycle();
  idle_frames_.push(rendering_frame_.get());
  rendering_frame_ = nullptr;
  DVLOG(3) << DebugState() << __func__;
}

bool WebXrPresentationState::RecycleProcessingFrameIfPossible() {
  DCHECK(HaveProcessingFrame());
  bool can_cancel = !processing_frame_->state_locked;
  if (can_cancel) {
    processing_frame_->Recycle();
    idle_frames_.push(processing_frame_.get());
    processing_frame_ = nullptr;
  } else {
    processing_frame_->recycle_once_unlocked = true;
  }
  DVLOG(3) << DebugState() << __func__;
  return can_cancel;
}

std::vector<std::unique_ptr<WebXrSharedBuffer>>
WebXrPresentationState::TakeSharedBuffers() {
  std::vector<std::unique_ptr<WebXrSharedBuffer>> shared_buffers;
  for (auto& frame : frames_storage_) {
    if (frame->shared_buffer)
      shared_buffers.emplace_back(std::move(frame->shared_buffer));
    if (frame->camera_image_shared_buffer)
      shared_buffers.emplace_back(std::move(frame->camera_image_shared_buffer));
  }
  return shared_buffers;
}

void WebXrPresentationState::EndPresentation() {
  TRACE_EVENT0("gpu", __FUNCTION__);

  if (HaveRenderingFrame()) {
    rendering_frame_->Recycle();
    idle_frames_.push(rendering_frame_.get());
    rendering_frame_ = nullptr;
  }
  for (device::WebXrFrame* frame : rendering_frames_) {
    frame->Recycle();
    idle_frames_.push(frame);
  }
  rendering_frames_.clear();
  if (HaveProcessingFrame()) {
    RecycleProcessingFrameIfPossible();
  }
  if (HaveAnimatingFrame()) {
    RecycleUnusedAnimatingFrame();
  }

  last_ui_allows_sending_vsync = false;
}

bool WebXrPresentationState::CanProcessFrame() const {
  if (!mailbox_bridge_ready_) {
    DVLOG(2) << __FUNCTION__ << ": waiting for mailbox bridge";
    return false;
  }
  if (processing_frame_) {
    DVLOG(2) << __FUNCTION__ << ": waiting for previous processing frame";
    return false;
  }

  // If we're running the viz composited state machine, we need to keep a frame
  // in the "Animating" state until we have our BeginFrameArgs.
  if (state_machine_type_ == StateMachineType::kVizComposited &&
      !animating_frame_->begin_frame_args) {
    DVLOG(2) << __FUNCTION__ << ": waiting for BeginFrameArgs";
    return false;
  }

  return true;
}

void WebXrPresentationState::ProcessOrDefer(base::OnceClosure callback) {
  DCHECK(animating_frame_ && !animating_frame_->deferred_start_processing);
  if (CanProcessFrame()) {
    TransitionFrameAnimatingToProcessing();
    std::move(callback).Run();
  } else {
    DVLOG(2) << "Deferring processing frame, not ready";
    animating_frame_->deferred_start_processing = std::move(callback);
  }
}

void WebXrPresentationState::TryDeferredProcessing() {
  if (!animating_frame_ || !animating_frame_->deferred_start_processing ||
      !CanProcessFrame()) {
    return;
  }
  DVLOG(2) << "Running deferred SubmitFrame";
  // Run synchronously, not via PostTask, to ensure we don't
  // get a new SendVSync scheduling in between.
  TransitionFrameAnimatingToProcessing();

  // After the above call, the frame that was in animating_frame_ is now in
  // processing_frame_.
  std::move(processing_frame_->deferred_start_processing).Run();
}

}  // namespace device