| // 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", "EndPresentation"); |
| |
| 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) << __func__ << ": waiting for mailbox bridge"; |
| return false; |
| } |
| if (processing_frame_) { |
| DVLOG(2) << __func__ << ": 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) << __func__ << ": 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 |