blob: e71f9fc5d71fadbfb0bef8b9d30217f503b41e6c [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/display/resolved_frame_data.h"
#include <set>
#include <utility>
#include "base/containers/to_vector.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "cc/base/math_util.h"
#include "components/viz/common/features.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/offset_tag.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/service/surfaces/surface.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/gfx/overlay_layer_id.h"
namespace viz {
namespace {
gfx::Rect EnclosingOffsetRect(const gfx::Rect& rect, gfx::Vector2dF offset) {
if (rect.IsEmpty()) {
return gfx::Rect();
}
gfx::RectF offset_rect(rect);
offset_rect.Offset(offset);
return gfx::ToEnclosingRect(offset_rect);
}
} // namespace
const std::optional<gfx::Rect>& GetOptionalDamageRectFromQuad(
const DrawQuad* quad) {
if (auto* texture_quad = quad->DynamicCast<TextureDrawQuad>()) {
return texture_quad->damage_rect;
} else {
static std::optional<gfx::Rect> no_damage;
return no_damage;
}
}
ResolvedQuadData::ResolvedQuadData(const DrawQuad& quad)
: remapped_resource_id(quad.resource_id) {}
FixedPassData::FixedPassData() = default;
FixedPassData::FixedPassData(FixedPassData&& other) = default;
FixedPassData& FixedPassData::operator=(FixedPassData&& other) = default;
FixedPassData::~FixedPassData() = default;
AggregationPassData::AggregationPassData() = default;
AggregationPassData::AggregationPassData(AggregationPassData&& other) = default;
AggregationPassData& AggregationPassData::operator=(
AggregationPassData&& other) = default;
AggregationPassData::~AggregationPassData() = default;
void AggregationPassData::Reset() {
*this = AggregationPassData();
}
PersistentPassData::PersistentPassData() = default;
PersistentPassData::PersistentPassData(PersistentPassData&& other) = default;
PersistentPassData& PersistentPassData::operator=(PersistentPassData& other) =
default;
PersistentPassData& PersistentPassData::operator=(
const PersistentPassData& other) = default;
PersistentPassData& PersistentPassData::operator=(PersistentPassData&& other) =
default;
PersistentPassData::~PersistentPassData() = default;
ResolvedPassData::ResolvedPassData(FixedPassData fixed_data)
: fixed_(std::move(fixed_data)) {}
ResolvedPassData::~ResolvedPassData() = default;
ResolvedPassData::ResolvedPassData(ResolvedPassData&& other) = default;
ResolvedPassData& ResolvedPassData::operator=(ResolvedPassData&& other) =
default;
const CompositorRenderPass& ResolvedPassData::render_pass() const {
CHECK(fixed_.render_pass);
return *fixed_.render_pass;
}
void ResolvedPassData::CopyAndResetPersistentPassData() {
previous_persistent_data_ = current_persistent_data_;
current_persistent_data_ = PersistentPassData();
}
void ResolvedPassData::SetCompositorRenderPass(CompositorRenderPass* pass) {
CHECK(pass);
CHECK_EQ(pass->id, fixed_.render_pass_id);
fixed_.render_pass = pass;
}
void ResolvedPassData::ResetCompositorRenderPass() {
fixed_.render_pass = nullptr;
}
ResolvedFrameData::ResolvedFrameData(DisplayResourceProvider* resource_provider,
Surface* surface,
uint32_t previous_frame_index,
AggregatedRenderPassId prev_root_pass_id)
: resource_provider_(resource_provider),
surface_id_(surface->surface_id()),
surface_(surface),
previous_frame_index_(previous_frame_index),
prev_root_pass_id_(prev_root_pass_id) {
DCHECK(resource_provider_);
DCHECK(surface_);
RegisterWithResourceProvider();
}
ResolvedFrameData::~ResolvedFrameData() {
// Release resources used by this ResolvedFrameData.
resource_provider_->DestroyChild(child_resource_id_);
}
void ResolvedFrameData::SetFullDamageForNextAggregation() {
previous_frame_index_ = kInvalidFrameIndex;
}
gfx::Size ResolvedFrameData::size_in_pixels() const {
return surface_->size_in_pixels();
}
float ResolvedFrameData::device_scale_factor() const {
return surface_->device_scale_factor();
}
gfx::OverlayLayerId::NamespaceId ResolvedFrameData::GetClientNamespaceId()
const {
return {surface_id_.frame_sink_id().client_id(),
surface_id_.frame_sink_id().sink_id()};
}
void ResolvedFrameData::ForceReleaseResource() {
// Resources for future frames are stored under a new child id going forward.
resource_provider_->DestroyChild(child_resource_id_);
RegisterWithResourceProvider();
}
void ResolvedFrameData::UpdateForAggregation(
AggregatedRenderPassId::Generator& render_pass_id_generator) {
CHECK(!used_in_aggregation_);
used_in_aggregation_ = true;
if (previous_frame_index() != surface_->GetActiveFrameIndex()) {
// There is a new active CompositorFrame.
UpdateActiveFrame(render_pass_id_generator);
} else if (is_valid()) {
// The same active CompositorFrame as last aggregation.
ReuseActiveFrame();
}
}
void ResolvedFrameData::UpdateActiveFrame(
AggregatedRenderPassId::Generator& render_pass_id_generator) {
// If there are modified render passes they need to be rebuilt based on
// current active CompositorFrame.
offset_tag_render_passes_.clear();
auto& compositor_frame = surface_->GetActiveFrame();
auto& resource_list = compositor_frame.resource_list;
// Ref the resources in the surface, and let the provider know we've received
// new resources from the compositor frame.
if (surface_->client()) {
surface_->client()->RefResources(resource_list);
}
auto& render_passes = compositor_frame.render_pass_list;
size_t num_render_pass = render_passes.size();
DCHECK(!render_passes.empty());
resource_provider_->ReceiveFromChild(child_resource_id_, resource_list);
// Figure out which resources are actually used in the render pass.
// Note that we first gather them in a vector, since ResourceIdSet (which we
// actually need) is a flat_set, which means bulk insertion we do at the end
// is more efficient.
std::vector<ResourceId> referenced_resources;
referenced_resources.reserve(resource_list.size());
// Will be repopulated based on active frame.
render_pass_id_map_.clear();
std::vector<ResolvedPassData> previous_resolved_passes;
resolved_passes_.swap(previous_resolved_passes);
render_pass_id_map_.reserve(num_render_pass);
resolved_passes_.reserve(num_render_pass);
auto& child_to_parent_map =
resource_provider_->GetChildToParentMap(child_resource_id_);
// Reset and compute new render pass / quad data for this frame. This stores
// remapped display resource ids.
for (size_t i = 0; i < num_render_pass; ++i) {
auto& render_pass = render_passes[i];
const bool is_root = i == num_render_pass - 1;
FixedPassData fixed;
fixed.render_pass = render_pass.get();
AggregatedRenderPassId& remapped_id = aggregated_id_map_[render_pass->id];
if (remapped_id.is_null()) {
if (is_root && !prev_root_pass_id_.is_null()) {
remapped_id = prev_root_pass_id_;
} else {
remapped_id = render_pass_id_generator.GenerateNextId();
}
}
fixed.remapped_id = remapped_id;
fixed.is_root = is_root;
fixed.render_pass_id = render_pass->id;
// Loop through the quads, remapping resource ids and storing them.
auto& draw_quads = fixed.draw_quads;
draw_quads.reserve(render_pass->quad_list.size());
for (auto* quad : render_pass->quad_list) {
if (quad->material == DrawQuad::Material::kCompositorRenderPass) {
// Check CompositorRenderPassDrawQuad refers to a render pass
// that exists and is drawn before the current render pass.
auto quad_render_pass_id =
CompositorRenderPassDrawQuad::MaterialCast(quad)->render_pass_id;
auto iter = render_pass_id_map_.find(quad_render_pass_id);
if (iter == render_pass_id_map_.end()) {
DLOG(ERROR) << "CompositorRenderPassDrawQuad with invalid id";
SetInvalid();
return;
}
++iter->second->fixed_.embed_count;
}
draw_quads.emplace_back(*quad);
if (ResourceId& resource_id = draw_quads.back().remapped_resource_id;
resource_id != kInvalidResourceId) {
// If we're using a resource which was not declared in the
// |resource_list| then this is an invalid frame, we can abort.
auto iter = child_to_parent_map.find(resource_id);
if (iter == child_to_parent_map.end()) {
DLOG(ERROR) << "Invalid resource for " << surface_id();
SetInvalid();
return;
}
referenced_resources.push_back(resource_id);
// Update `ResolvedQuadData::remapped_resource_id` to have the remapped
// display resource_id.
resource_id = iter->second;
}
}
resolved_passes_.emplace_back(std::move(fixed));
// Build render pass id map and check for duplicate ids at the same time.
if (!render_pass_id_map_
.insert(std::make_pair(render_pass->id, &resolved_passes_.back()))
.second) {
DLOG(ERROR) << "Duplicate render pass ids";
SetInvalid();
return;
}
}
frame_index_ = surface_->GetActiveFrameIndex();
DCHECK_NE(frame_index_, 0u);
// Get persistent_data from the previous frame to the current frame.
MovePersistentPassDataFromPreviousFrame(previous_resolved_passes);
previous_resolved_passes.clear();
// Clear id mappings that weren't used in this frame.
base::EraseIf(aggregated_id_map_, [this](auto& entry) {
return render_pass_id_map_.find(entry.first) == render_pass_id_map_.end();
});
valid_ = true;
ComputeOffsetTagContainingRects();
// Declare the used resources to the provider. This will cause all resources
// that were received but not used in the render passes to be unreferenced in
// the surface, and returned to the child in the resource provider.
resource_provider_->DeclareUsedResourcesFromChild(
child_resource_id_, ResourceIdSet(std::move(referenced_resources)));
}
void ResolvedFrameData::UpdateOffsetTags(OffsetTagLookupFn lookup_value_fn) {
auto& offset_tags_to_find =
surface_->GetActiveFrameMetadata().offset_tag_definitions;
if (offset_tags_to_find.empty() && !has_non_zero_offset_tag_value_) {
// Early return if there were no offset tags last and this aggregation. This
// is the common case so avoid doing any work on this path.
return;
}
// Find the offset value for all defined tags first.
has_non_zero_offset_tag_value_ = false;
for (auto& tag_def : offset_tags_to_find) {
auto offset = lookup_value_fn(tag_def);
if (!tag_def.constraints.IsOffsetValid(offset)) {
#if BUILDFLAG(IS_ANDROID)
if (base::FeatureList::IsEnabled(
features::kAndroidDumpForBadCompositedUiState)) {
SCOPED_CRASH_KEY_STRING32("BCIV", "offset", offset.ToString());
SCOPED_CRASH_KEY_STRING32("BCIV", "OffsetTagConstraints",
tag_def.constraints.ToString());
base::debug::DumpWithoutCrashing();
}
#endif
offset = tag_def.constraints.Clamp(offset);
}
auto& tag_data = offset_tag_data_[tag_def.tag];
tag_data.current_offset = offset;
tag_data.defined_in_frame = true;
if (!offset.IsZero()) {
has_non_zero_offset_tag_value_ = true;
}
}
bool offset_tag_values_changed_from_last_frame =
std::ranges::any_of(offset_tag_data_, [](auto& entry) {
return entry.second.current_offset != entry.second.last_offset;
});
if (offset_tag_values_changed_from_last_frame) {
offset_tag_render_passes_.clear();
} else if (!offset_tag_render_passes_.empty()) {
// If offset tag values haven't changed and the copied render passes weren't
// cleared elsewhere they can be reused.
CHECK_EQ(offset_tag_render_passes_.size(), resolved_passes_.size());
for (size_t i = 0; i < offset_tag_render_passes_.size(); ++i) {
resolved_passes_[i].SetCompositorRenderPass(
offset_tag_render_passes_[i].get());
}
// Skip running RecomputeOffsetTagDamage() since nothing changed since
// last frame and there is no added damage.
return;
}
if (has_non_zero_offset_tag_value_) {
RebuildRenderPassesForOffsetTags();
}
RecomputeOffsetTagDamage();
}
void ResolvedFrameData::ComputeOffsetTagContainingRects() {
auto& offset_tags_to_find = GetMetadata().offset_tag_definitions;
if (offset_tags_to_find.empty()) {
return;
}
// `offset_tag_data_` hasn't been populated for this frame yet so build a set
// to check if a definition exists when a tag is seen on a layer.
base::flat_set<OffsetTag> tag_set(
base::ToVector(offset_tags_to_find,
[](const OffsetTagDefinition& def) { return def.tag; }));
for (auto& resolved_pass : resolved_passes_) {
const CompositorRenderPass& render_pass = resolved_pass.render_pass();
for (auto* sqs : render_pass.shared_quad_state_list) {
if (sqs->offset_tag && tag_set.contains(sqs->offset_tag)) {
gfx::Transform combined_transform =
render_pass.transform_to_root_target *
sqs->quad_to_target_transform;
gfx::Rect containing_rect_in_root =
cc::MathUtil::MapEnclosingClippedRect(combined_transform,
sqs->visible_quad_layer_rect);
offset_tag_data_[sqs->offset_tag].current_containing_rect.Union(
containing_rect_in_root);
}
}
}
for (auto& [tag, data] : offset_tag_data_) {
data.current_containing_rect.Intersect(GetOutputRect());
}
}
void ResolvedFrameData::RebuildRenderPassesForOffsetTags() {
CHECK(offset_tag_render_passes_.empty());
CHECK(has_non_zero_offset_tag_value_);
// Create copies of the render passes and modify tagged quad positions by
// adjusting `quad_to_target_transform` transform.
// TODO(kylechar): This only needs to make a copy of render passes that have
// tagged quads.
offset_tag_render_passes_.reserve(resolved_passes_.size());
for (auto& resolved_pass : resolved_passes_) {
CHECK(resolved_pass.fixed_.render_pass);
// DeepCopy() can't copy CopyOutputRequests. Remove them from `source_pass`
// before copying and then add them back afterwards. The requests are
// copied to the AggregatedRenderPass by Surface::TakeCopyOutputRequests()
// which will look in the original render pass.
auto source_pass = resolved_pass.fixed_.render_pass;
auto copy_requests = std::move(source_pass->copy_requests);
auto modified_pass = source_pass->DeepCopy();
source_pass->copy_requests = std::move(copy_requests);
for (auto* sqs : modified_pass->shared_quad_state_list) {
if (sqs->offset_tag && offset_tag_data_.contains(sqs->offset_tag)) {
auto& tag_data = offset_tag_data_[sqs->offset_tag];
if (!tag_data.current_offset.IsZero()) {
sqs->quad_to_target_transform.PostTranslate(tag_data.current_offset);
if (!sqs->mask_filter_info.IsEmpty()) {
// Slim compositor enforces that mask filter info isn't added on
// a fixed parent layer that has a child layer with offset tag, so
// we can assume the mask filter info should also be translated.
// See crbug.com/361804880 for details.
sqs->mask_filter_info.ApplyTransform(
gfx::Transform::MakeTranslation(tag_data.current_offset));
}
}
}
}
// Replace the CompositorRenderPass pointer so that modified frame is used
// during aggregation.
resolved_pass.fixed_.render_pass = modified_pass.get();
offset_tag_render_passes_.push_back(std::move(modified_pass));
}
}
void ResolvedFrameData::RecomputeOffsetTagDamage() {
CHECK(offset_tag_added_damage_.IsEmpty());
// Get the surface damage before this function modifies it.
const gfx::Rect surface_damage_rect = GetSurfaceDamage();
const gfx::Rect output_rect = GetOutputRect();
if (surface_damage_rect == output_rect) {
// If the frame already has full damage then there is no point computing
// damage to add.
return;
}
for (auto& [tag, data] : offset_tag_data_) {
if (data.current_offset != data.last_offset) {
// If the offset value for a tag changed then both the old and new
// locations of tagged quads are damaged since content moved.
offset_tag_added_damage_.Union(EnclosingOffsetRect(
data.current_containing_rect, data.current_offset));
offset_tag_added_damage_.Union(
EnclosingOffsetRect(data.last_containing_rect, data.last_offset));
} else if (!data.current_offset.IsZero()) {
// If the offset didn't change and current offset is non-zero then adjust
// client provided damage to take into account quads that were offset.
// This assumes that any damage which intersects the tagged quads comes
// from the tagged quads. This isn't necessarily true but there isn't
// enough information here to know what layer/quads introduced the damage
// so this is pessimistic.
offset_tag_added_damage_.Union(
EnclosingOffsetRect(gfx::IntersectRects(data.current_containing_rect,
surface_damage_rect),
data.current_offset));
if (!data.last_containing_rect.IsEmpty() &&
!data.current_containing_rect.Contains(data.last_containing_rect)) {
// This case aims to detect when a layer had a tag removed or a tagged
// layer was deleted. The client will add damage for the removed layer
// at it's default location but since `last_offset` is non-zero the
// content was drawn elsewhere. Viz needs to add damage where the
// removed layer was drawn. There is no simple way to track when tagged
// layers are removed, so this uses an imperfect proxy of containing
// rect shrinking, and if that happens it adds damage for all tagged
// layers last frame.
//
// It's possible the containing rect shrinks without removing a tagged
// layer, eg. size or position of the tagged layers change. This case
// will result in adding extra damage that isn't needed which has no
// impact on correctness.
//
// It's also possible a tagged layer was removed but the containing rect
// doesn't shrink, eg. either another tagged layer was added or other
// tagged layers had size/position change. The current containing rect
// still contains all last frames tagged layers and client adds damage
// for the removed layer so the code above to shift client damage will
// handle it.
offset_tag_added_damage_.Union(
EnclosingOffsetRect(data.last_containing_rect, data.last_offset));
}
}
}
// Clip added damage if it extends beyond output rect.
offset_tag_added_damage_.Intersect(output_rect);
}
void ResolvedFrameData::SetInvalid() {
frame_index_ = surface_->GetActiveFrameIndex();
render_pass_id_map_.clear();
resolved_passes_.clear();
valid_ = false;
}
bool ResolvedFrameData::WasUsedInAggregation() const {
return used_in_aggregation_;
}
void ResolvedFrameData::ResetAfterAggregation() {
// Reset aggregation scoped data.
for (auto& resolved_pass : resolved_passes_) {
resolved_pass.aggregation().Reset();
resolved_pass.CopyAndResetPersistentPassData();
resolved_pass.ResetCompositorRenderPass();
}
previous_frame_index_ = frame_index_;
used_in_aggregation_ = false;
// Delete entries for tags that weren't defined in this frame.
base::EraseIf(offset_tag_data_,
[](auto& entry) { return !entry.second.defined_in_frame; });
// Reset remaining offset tag data for next aggregation.
for (auto& [tag, data] : offset_tag_data_) {
data.last_containing_rect = data.current_containing_rect;
data.current_containing_rect = gfx::Rect();
data.last_offset = data.current_offset;
data.current_offset = gfx::Vector2dF();
data.defined_in_frame = false;
}
offset_tag_added_damage_ = gfx::Rect();
// Don't reset `has_non_zero_offset_tag_value_` until next aggregation when
// UpdateOffsetTags() is called.
}
const CompositorFrameMetadata& ResolvedFrameData::GetMetadata() const {
// TODO(crbug.com/354664676): Add back CHECK(valid_) once this is only called
// for valid frames.
return surface_->GetActiveFrameMetadata();
}
bool ResolvedFrameData::WillDraw() const {
return GetRootRenderPassData().aggregation().will_draw;
}
ResolvedPassData& ResolvedFrameData::GetRenderPassDataById(
CompositorRenderPassId render_pass_id) {
return const_cast<ResolvedPassData&>(
const_cast<const ResolvedFrameData*>(this)->GetRenderPassDataById(
render_pass_id));
}
const ResolvedPassData& ResolvedFrameData::GetRenderPassDataById(
CompositorRenderPassId render_pass_id) const {
DCHECK(valid_);
auto iter = render_pass_id_map_.find(render_pass_id);
DCHECK(iter != render_pass_id_map_.end());
return *iter->second;
}
ResolvedPassData& ResolvedFrameData::GetRootRenderPassData() {
DCHECK(valid_);
return resolved_passes_.back();
}
const ResolvedPassData& ResolvedFrameData::GetRootRenderPassData() const {
DCHECK(valid_);
return resolved_passes_.back();
}
FrameDamageType ResolvedFrameData::GetFrameDamageType() const {
DCHECK(valid_);
DCHECK(used_in_aggregation_);
if (previous_frame_index_ == frame_index_) {
// This is the same frame as the one used last aggregation.
return FrameDamageType::kNone;
} else if (previous_frame_index_ > kInvalidFrameIndex &&
frame_index_ == previous_frame_index_ + 1) {
// This is the next frame after the one used last aggregation.
return FrameDamageType::kFrame;
}
return FrameDamageType::kFull;
}
gfx::Rect ResolvedFrameData::GetSurfaceDamage() const {
switch (GetFrameDamageType()) {
case FrameDamageType::kFull:
return GetOutputRect();
case FrameDamageType::kFrame:
return gfx::UnionRects(resolved_passes_.back().render_pass().damage_rect,
offset_tag_added_damage_);
case FrameDamageType::kNone:
return offset_tag_added_damage_;
}
}
const gfx::Rect& ResolvedFrameData::GetOutputRect() const {
DCHECK(valid_);
return resolved_passes_.back().render_pass().output_rect;
}
void ResolvedFrameData::ReuseActiveFrame() {
const CompositorRenderPassList& render_pass_list =
surface_->GetActiveFrame().render_pass_list;
// `render_pass_list` and `resolved_passes_` should have the same size and
// order.
CHECK_EQ(render_pass_list.size(), resolved_passes_.size());
for (size_t i = 0; i < resolved_passes_.size(); ++i) {
ResolvedPassData& resolved_pass = resolved_passes_[i];
const auto& render_pass = render_pass_list[i];
CHECK_EQ(resolved_pass.render_pass_id(), render_pass->id);
resolved_pass.SetCompositorRenderPass(render_pass.get());
}
// OffsetTag containing rects are the same as last frame.
for (auto& [tag, containing] : offset_tag_data_) {
containing.current_containing_rect = containing.last_containing_rect;
}
}
void ResolvedFrameData::RegisterWithResourceProvider() {
child_resource_id_ = resource_provider_->CreateChild(
base::BindRepeating(&SurfaceClient::UnrefResources, surface_->client()),
surface_id_);
}
void ResolvedFrameData::MovePersistentPassDataFromPreviousFrame(
const std::vector<ResolvedPassData>& previous_resolved_passes) {
for (const auto& previous_resolved_pass : previous_resolved_passes) {
auto render_pass_id = previous_resolved_pass.render_pass_id();
// iter to |current_persistent_data_|
auto iter = render_pass_id_map_.find(render_pass_id);
if (iter != render_pass_id_map_.end()) {
iter->second->previous_persistent_data() =
previous_resolved_pass.previous_persistent_data();
}
}
}
} // namespace viz