| // Copyright 2016 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 "chrome/browser/vr/ui_scene.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/containers/adapters.h" |
| #include "base/numerics/ranges.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/values.h" |
| #include "chrome/browser/vr/databinding/binding_base.h" |
| #include "chrome/browser/vr/elements/draw_phase.h" |
| #include "chrome/browser/vr/elements/keyboard.h" |
| #include "chrome/browser/vr/elements/reticle.h" |
| #include "chrome/browser/vr/elements/ui_element.h" |
| #include "chrome/browser/vr/frame_lifecycle.h" |
| #include "ui/gfx/transform.h" |
| |
| namespace vr { |
| |
| namespace { |
| |
| template <typename P, typename V> |
| void AddPredicatedVisibleSubTree(UiElement* root, P predicate, V* elements) { |
| if (!root->IsVisible()) |
| return; |
| if (predicate(root)) { |
| elements->push_back(root); |
| } |
| for (auto& child : root->children()) { |
| AddPredicatedVisibleSubTree(child.get(), predicate, elements); |
| } |
| } |
| |
| template <typename P> |
| UiScene::Elements GetVisibleElementsWithPredicate(UiElement* root, |
| P predicate) { |
| UiScene::Elements result; |
| AddPredicatedVisibleSubTree(root, predicate, &result); |
| return result; |
| } |
| |
| template <typename P> |
| UiScene::MutableElements GetVisibleElementsWithPredicateMutable(UiElement* root, |
| P predicate) { |
| UiScene::MutableElements result; |
| AddPredicatedVisibleSubTree(root, predicate, &result); |
| return result; |
| } |
| |
| void GetAllElementsRecursive(std::vector<UiElement*>* elements, UiElement* e) { |
| e->set_descendants_updated(false); |
| elements->push_back(e); |
| for (auto& child : e->children()) |
| GetAllElementsRecursive(elements, child.get()); |
| } |
| |
| template <typename P> |
| UiElement* FindElement(UiElement* e, P predicate) { |
| if (predicate.Run(e)) |
| return e; |
| for (auto& child : e->children()) { |
| if (UiElement* result = FindElement(child.get(), predicate)) { |
| return result; |
| } |
| } |
| return nullptr; |
| } |
| |
| template <typename P> |
| bool AnyVisibleElementSatisfiesPredicate(UiElement* root, P predicate) { |
| if (!root->IsVisible()) |
| return false; |
| if (predicate(root)) |
| return true; |
| for (auto& child : root->children()) |
| if (AnyVisibleElementSatisfiesPredicate(child.get(), predicate)) |
| return true; |
| return false; |
| } |
| |
| void InitializeElementRecursive(UiElement* e, SkiaSurfaceProvider* provider) { |
| e->Initialize(provider); |
| for (auto& child : e->children()) |
| InitializeElementRecursive(child.get(), provider); |
| } |
| |
| } // namespace |
| |
| void UiScene::AddUiElement(UiElementName parent, |
| std::unique_ptr<UiElement> element) { |
| InitializeElement(element.get()); |
| GetUiElementByName(parent)->AddChild(std::move(element)); |
| is_dirty_ = true; |
| } |
| |
| void UiScene::AddParentUiElement(UiElementName child, |
| std::unique_ptr<UiElement> element) { |
| InitializeElement(element.get()); |
| auto* child_ptr = GetUiElementByName(child); |
| CHECK_NE(nullptr, child_ptr); |
| auto* parent_ptr = child_ptr->parent(); |
| CHECK_NE(nullptr, parent_ptr); |
| auto* element_ptr = element.get(); |
| element_ptr->AddChild( |
| parent_ptr->ReplaceChild(child_ptr, std::move(element))); |
| is_dirty_ = true; |
| } |
| |
| std::unique_ptr<UiElement> UiScene::RemoveUiElement(int element_id) { |
| UiElement* to_remove = GetUiElementById(element_id); |
| CHECK_NE(nullptr, to_remove); |
| CHECK_NE(nullptr, to_remove->parent()); |
| is_dirty_ = true; |
| return to_remove->parent()->RemoveChild(to_remove); |
| } |
| |
| bool UiScene::OnBeginFrame(const base::TimeTicks& current_time, |
| const gfx::Transform& head_pose) { |
| // Before dirtying the scene, we process scheduled tasks for the frame. |
| { |
| TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.ScheduledTasks"); |
| for (auto it = scheduled_tasks_.begin(); it != scheduled_tasks_.end();) { |
| auto& task = *it; |
| task->Tick(current_time); |
| if (task->empty()) { |
| it = scheduled_tasks_.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| bool scene_dirty = !initialized_scene_ || is_dirty_; |
| initialized_scene_ = true; |
| is_dirty_ = false; |
| |
| auto& elements = GetAllElements(); |
| |
| FrameLifecycle::set_phase(kDirty); |
| for (auto* element : elements) { |
| element->set_update_phase(kDirty); |
| element->set_last_frame_time(current_time); |
| } |
| |
| { |
| TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateBindings"); |
| |
| // Propagate updates across bindings. |
| root_element_->UpdateBindings(); |
| FrameLifecycle::set_phase(kUpdatedBindings); |
| } |
| |
| // Per-frame callbacks run every frame, always, as opposed to bindings, which |
| // run selectively based on element visibility. |
| for (auto callback : per_frame_callback_) { |
| callback.Run(); |
| } |
| |
| { |
| TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateAnimationsAndOpacity"); |
| |
| // Process all animations and pre-binding work. I.e., induce any |
| // time-related "dirtiness" on the scene graph. |
| scene_dirty |= root_element_->DoBeginFrame(head_pose, first_frame_); |
| FrameLifecycle::set_phase(kUpdatedComputedOpacity); |
| } |
| |
| { |
| TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateLayout"); |
| scene_dirty |= root_element_->SizeAndLayOut(); |
| FrameLifecycle::set_phase(kUpdatedLayout); |
| } |
| |
| { |
| TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateWorldSpaceTransform"); |
| |
| // Now that we have finalized our local values, we can safely update our |
| // final, baked transform. |
| const bool parent_transform_changed = false; |
| scene_dirty |= |
| root_element_->UpdateWorldSpaceTransform(parent_transform_changed); |
| } |
| |
| FrameLifecycle::set_phase(kUpdatedWorldSpaceTransform); |
| |
| first_frame_ = false; |
| return scene_dirty; |
| } |
| |
| bool UiScene::HasDirtyTextures() const { |
| return AnyVisibleElementSatisfiesPredicate( |
| root_element_.get(), |
| [](UiElement* element) { return element->HasDirtyTexture(); }); |
| } |
| |
| void UiScene::UpdateTextures() { |
| TRACE_EVENT0("gpu", "UiScene::UpdateTextures"); |
| std::vector<UiElement*> elements = GetVisibleElementsMutable(); |
| for (auto* element : elements) { |
| element->UpdateTexture(); |
| element->set_update_phase(kUpdatedTextures); |
| } |
| FrameLifecycle::set_phase(kUpdatedTextures); |
| } |
| |
| UiElement& UiScene::root_element() { |
| return *root_element_; |
| } |
| |
| UiElement* UiScene::GetUiElementById(int element_id) const { |
| return FindElement( |
| root_element_.get(), |
| base::BindRepeating([](int id, UiElement* e) { return e->id() == id; }, |
| element_id)); |
| } |
| |
| UiElement* UiScene::GetUiElementByName(UiElementName name) const { |
| return FindElement( |
| root_element_.get(), |
| base::BindRepeating( |
| [](UiElementName name, UiElement* e) { return e->name() == name; }, |
| name)); |
| } |
| |
| std::vector<UiElement*>& UiScene::GetAllElements() { |
| if (root_element_->descendants_updated()) { |
| all_elements_.clear(); |
| GetAllElementsRecursive(&all_elements_, root_element_.get()); |
| } |
| return all_elements_; |
| } |
| |
| UiScene::Elements UiScene::GetElementsToHitTest() { |
| return GetVisibleElementsWithPredicate( |
| root_element_.get(), |
| [](UiElement* element) { return element->IsHitTestable(); }); |
| } |
| |
| UiScene::MutableElements UiScene::GetVisibleElementsMutable() { |
| return GetVisibleElementsWithPredicateMutable( |
| root_element_.get(), [](UiElement* element) { return true; }); |
| } |
| |
| UiScene::Elements UiScene::GetElementsToDraw() { |
| return GetVisibleElementsWithPredicate( |
| root_element_.get(), [](UiElement* element) { |
| return element->draw_phase() == kPhaseForeground || |
| element->draw_phase() == kPhaseBackplanes || |
| element->draw_phase() == kPhaseBackground; |
| }); |
| } |
| |
| bool UiScene::HasWebXrOverlayElementsToDraw() { |
| auto* webvr_root = GetUiElementByName(kWebVrRoot); |
| return AnyVisibleElementSatisfiesPredicate( |
| webvr_root, [](UiElement* element) { |
| return element->draw_phase() == kPhaseOverlayForeground; |
| }); |
| } |
| |
| UiScene::Elements UiScene::GetWebVrOverlayElementsToDraw() { |
| auto* webvr_root = GetUiElementByName(kWebVrRoot); |
| return GetVisibleElementsWithPredicate(webvr_root, [](UiElement* element) { |
| return element->draw_phase() == kPhaseOverlayForeground; |
| }); |
| } |
| |
| UiScene::UiScene() { |
| root_element_ = std::make_unique<UiElement>(); |
| root_element_->SetName(kRoot); |
| } |
| |
| UiScene::~UiScene() = default; |
| |
| void UiScene::OnGlInitialized(SkiaSurfaceProvider* provider) { |
| gl_initialized_ = true; |
| provider_ = provider; |
| InitializeElementRecursive(root_element_.get(), provider_); |
| } |
| |
| void UiScene::AddPerFrameCallback(PerFrameCallback callback) { |
| per_frame_callback_.push_back(callback); |
| } |
| |
| void UiScene::AddSequence(std::unique_ptr<Sequence> sequence) { |
| scheduled_tasks_.push_back(std::move(sequence)); |
| } |
| |
| void UiScene::InitializeElement(UiElement* element) { |
| CHECK_GE(element->id(), 0); |
| CHECK_EQ(GetUiElementById(element->id()), nullptr); |
| CHECK_GE(element->draw_phase(), 0); |
| if (gl_initialized_) |
| InitializeElementRecursive(element, provider_); |
| } |
| |
| void UiScene::RunFirstFrameForTest() { |
| OnBeginFrame(base::TimeTicks(), gfx::Transform()); |
| } |
| |
| } // namespace vr |