blob: 899ee3347764ef4500e37b77562042310753ecef [file] [log] [blame]
// 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/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 "ui/gfx/transform.h"
namespace vr {
namespace {
template <typename P>
UiScene::Elements GetVisibleElements(UiElement* root,
P predicate) {
UiScene::Elements elements;
for (auto& element : *root) {
if (element.IsVisible() && predicate(&element))
elements.push_back(&element);
}
return elements;
}
} // namespace
void UiScene::AddUiElement(UiElementName parent,
std::unique_ptr<UiElement> element) {
CHECK_GE(element->id(), 0);
CHECK_EQ(GetUiElementById(element->id()), nullptr);
CHECK_GE(element->draw_phase(), 0);
if (gl_initialized_) {
for (auto& child : *element) {
child.Initialize(provider_);
}
}
GetUiElementByName(parent)->AddChild(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) {
bool scene_dirty = !initialized_scene_ || is_dirty_;
initialized_scene_ = true;
is_dirty_ = false;
{
TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateBindings");
// Propagate updates across bindings.
for (auto& element : *root_element_) {
element.UpdateBindings();
element.set_update_phase(UiElement::kUpdatedBindings);
}
}
{
TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateAnimations");
// Process all animations and pre-binding work. I.e., induce any
// time-related "dirtiness" on the scene graph.
for (auto& element : *root_element_) {
element.set_update_phase(UiElement::kDirty);
if ((element.DoBeginFrame(current_time, head_pose) ||
element.updated_bindings_this_frame()) &&
(element.IsVisible() || element.updated_visiblity_this_frame())) {
scene_dirty = true;
}
}
}
{
TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateTexturesAndSizes");
// Update textures and sizes.
// TODO(mthiesse): We should really only be updating the sizes here, and not
// actually redrawing the textures because we draw all of the textures as a
// second phase after OnBeginFrame, once we've processed input. For now this
// is fine, it's just a small amount of duplicated work.
// Textures will have to know what their size would be, if they were to draw
// with their current state, and changing anything other than texture
// synchronously in response to input should be prohibited.
for (auto& element : *root_element_) {
if (element.PrepareToDraw())
scene_dirty = true;
element.set_update_phase(UiElement::kUpdatedTexturesAndSizes);
}
}
if (!scene_dirty) {
// Nothing to update, so set all elements to the final update phase and
// return early.
for (auto& element : *root_element_) {
element.set_update_phase(UiElement::kUpdatedWorldSpaceTransform);
}
return false;
}
{
TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateLayout");
// Update layout, which depends on size. Note that the layout phase changes
// the size of layout-type elements, as they adjust to fit the cumulative
// size of their children. This must be done in reverse order, such that
// children are correctly sized when laid out by their parent.
for (auto& element : base::Reversed(*root_element_)) {
element.DoLayOutChildren();
element.set_update_phase(UiElement::kUpdatedLayout);
}
}
{
TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateWorldSpaceTransform");
// Now that we have finalized our local values, we can safely update our
// final, baked transform.
root_element_->UpdateWorldSpaceTransformRecursive();
}
return scene_dirty;
}
bool UiScene::UpdateTextures() {
bool needs_redraw = false;
// Update textures and sizes.
for (auto& element : *root_element_) {
if (element.PrepareToDraw())
needs_redraw = true;
}
return needs_redraw;
}
UiElement& UiScene::root_element() {
return *root_element_;
}
UiElement* UiScene::GetUiElementById(int element_id) const {
for (auto& element : *root_element_) {
if (element.id() == element_id)
return &element;
}
return nullptr;
}
UiElement* UiScene::GetUiElementByName(UiElementName name) const {
for (auto& element : *root_element_) {
if (element.name() == name)
return &element;
}
return nullptr;
}
UiScene::Elements UiScene::GetVisibleElementsToDraw() const {
return GetVisibleElements(GetUiElementByName(kRoot), [](UiElement* element) {
return element->draw_phase() == kPhaseForeground ||
element->draw_phase() == kPhaseBackground;
});
}
UiScene::Elements UiScene::GetVisibleWebVrOverlayElementsToDraw() const {
return GetVisibleElements(
GetUiElementByName(kWebVrRoot), [](UiElement* element) {
return element->draw_phase() == kPhaseOverlayForeground;
});
}
UiScene::Elements UiScene::GetPotentiallyVisibleElements() const {
UiScene::Elements elements;
for (auto& element : *root_element_) {
if (element.draw_phase() != kPhaseNone)
elements.push_back(&element);
}
return elements;
}
UiScene::UiScene() {
root_element_ = std::make_unique<UiElement>();
root_element_->SetName(kRoot);
root_element_->SetDrawPhase(kPhaseNone);
root_element_->set_hit_testable(false);
}
UiScene::~UiScene() = default;
void UiScene::OnGlInitialized(SkiaSurfaceProvider* provider) {
gl_initialized_ = true;
provider_ = provider;
for (auto& element : *root_element_)
element.Initialize(provider_);
}
} // namespace vr