blob: b9fe89494f519d04f0efbc3e3f5b12b6f51bc6ff [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/trees/de_jelly_state.h"
#include <algorithm>
#include <utility>
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/scroll_node.h"
#include "cc/trees/transform_node.h"
#include "components/viz/common/display/de_jelly.h"
namespace cc {
DeJellyState::DeJellyState() = default;
DeJellyState::~DeJellyState() = default;
void DeJellyState::AdvanceFrame(LayerTreeImpl* layer_tree_impl) {
if (!layer_tree_impl->settings().allow_de_jelly_effect)
return;
should_de_jelly_ = false;
// Move the |new_transforms_| from the previous draw into
// |previous_transforms_|.
std::swap(previous_transforms_, new_transforms_);
new_transforms_.clear();
// Make sure we have an active scroll node. Otherwise we won't perform any
// de-jelly.
const ScrollNode* current_scroll =
layer_tree_impl->property_trees()->scroll_tree().Node(
layer_tree_impl->property_trees()
->scroll_tree()
.currently_scrolling_node());
if (!current_scroll) {
new_scroll_node_transform_.reset();
return;
}
scroll_transform_node_ = current_scroll->transform_id;
// Check to make sure the ToScreen transform of our scroll node is not a
// complex transform (doesn't work well with de-jelly). Also make sure the
// scale is not changing.
absl::optional<gfx::Transform> previous_scroll_transform =
new_scroll_node_transform_;
new_scroll_node_transform_ =
layer_tree_impl->property_trees()->transform_tree().ToScreen(
current_scroll->transform_id);
if (!previous_scroll_transform ||
!previous_scroll_transform->IsScaleOrTranslation() ||
!new_scroll_node_transform_->IsScaleOrTranslation() ||
new_scroll_node_transform_->To2dScale() !=
previous_scroll_transform->To2dScale()) {
return;
}
// Compute the fallback movement of a scrolling element based purely on the
// scroll offset of the currently scrolling node.
float previous_scroll_offset = scroll_offset_;
scroll_offset_ = layer_tree_impl->property_trees()
->transform_tree()
.Node(scroll_transform_node_)
->scroll_offset.y();
float scroll_delta = scroll_offset_ - previous_scroll_offset;
fallback_delta_y_ =
new_scroll_node_transform_->MapVector(gfx::Vector3dF(0, scroll_delta, 0))
.y();
// Don't attempt de-jelly while the omnibox is transitioning in or out. There
// is no correct way to handle this.
float top_controls_shown_ratio =
layer_tree_impl->top_controls_shown_ratio()->Current(
true /* is_active_tree */);
if (top_controls_shown_ratio != 0.0f && top_controls_shown_ratio != 1.0f)
return;
// We've passed our initial checks, allow de-jelly in UpdateSharedQuadState.
should_de_jelly_ = true;
}
void DeJellyState::UpdateSharedQuadState(
LayerTreeImpl* layer_tree_impl,
int transform_id,
viz::CompositorRenderPass* target_render_pass) {
if (!should_de_jelly_)
return;
DCHECK(layer_tree_impl->settings().allow_de_jelly_effect);
viz::SharedQuadState* state =
target_render_pass->shared_quad_state_list.back();
state->de_jelly_delta_y = 0.0f;
// Check if |transform_id| is a child of our |scroll_transform_node_|
// and if it scrolls (is not sticky or fixed).
bool does_not_scroll = false;
auto node_id = transform_id;
while (node_id != scroll_transform_node_ && node_id != kInvalidNodeId) {
const auto* current_node =
layer_tree_impl->property_trees()->transform_tree().Node(node_id);
// Position fixed.
if (current_node->moved_by_outer_viewport_bounds_delta_y) {
does_not_scroll = true;
break;
}
// Position sticky.
if (current_node->sticky_position_constraint_id > -1) {
const StickyPositionNodeData* sticky_data =
layer_tree_impl->property_trees()
->transform_tree()
.GetStickyPositionData(node_id);
if (sticky_data &&
sticky_data->total_containing_block_sticky_offset.y() > 0.0f) {
does_not_scroll = true;
break;
}
}
node_id = current_node->parent_id;
}
does_not_scroll |= node_id == kInvalidNodeId;
if (does_not_scroll)
return;
// Get the current node's ToScreen transform.
gfx::Transform transform =
layer_tree_impl->property_trees()->transform_tree().ToScreen(
transform_id);
new_transforms_[transform_id] = transform;
// Get the previous transform (if any).
const auto& found = previous_transforms_.find(transform_id);
float delta_y = 0.0f;
if (found == previous_transforms_.end()) {
delta_y = fallback_delta_y_;
} else {
// Calculate the delta of point (0, 0) from the previous frame.
gfx::Transform previous_transform = found->second;
gfx::PointF new_point = transform.MapPoint(gfx::PointF(0, 0));
gfx::PointF old_point = previous_transform.MapPoint(gfx::PointF(0, 0));
delta_y = old_point.y() - new_point.y();
}
if (delta_y == 0.0f) {
return;
}
// To minimize jarring visible effects, we de-jelly differently at
// different magnitudes of |delta_y|. This is controlled by three variables:
// kLinearDeJellyStart, kFixedDeJellyStart, kZeroDeJellyStart.
// _____________
// | | _/| |
// de_jelly_delta_y | | _/ | |
// |_____|_/ | |_______________
// +----------------------------------------
// kLinear kFixed kZero
//
const float kLinearDeJellyStart = 2.0f;
const float kFixedDeJellyStart =
viz::MaxDeJellyHeight() + kLinearDeJellyStart;
const float kZeroDeJellyStart = 100.0f + kLinearDeJellyStart;
float sign = std::abs(delta_y) / delta_y;
float de_jelly_delta_y = std::abs(delta_y);
if (de_jelly_delta_y > kZeroDeJellyStart) {
de_jelly_delta_y = 0.0f;
} else if (de_jelly_delta_y > kFixedDeJellyStart) {
de_jelly_delta_y = kFixedDeJellyStart - kLinearDeJellyStart;
} else if (de_jelly_delta_y > kLinearDeJellyStart) {
de_jelly_delta_y = std::max(0.0f, de_jelly_delta_y - kLinearDeJellyStart);
} else {
de_jelly_delta_y = 0.0f;
}
// Re-apply the sign.
de_jelly_delta_y *= sign;
if (de_jelly_delta_y == 0.0f) {
return;
}
state->de_jelly_delta_y = de_jelly_delta_y;
}
} // namespace cc