0

viz::SurfaceDrawQuad: Add parameters to override child paint flags

Videos and canvases have their own viz::Surface. This means that instead
of the cc::LayerTree emitting the viz::DrawQuad for the content, the
cc::LayerTree has a cc::SurfaceLayer, which emits a
viz::SurfaceDrawQuad, which refers to a viz::DrawQuad which is
submitted to a separate frame sink.

As a consequence of this architecture, the cc::Layer that knows the
cc::PaintFlags::FilterQuality and cc::PaintFlags::DynamicRangeLimit
is the cc::SurfaceLayer, which has no direct way to propagate those
parameters to the viz::TextureDrawQuad that will need to use them
(because that quad is submitted to the separate frame sink).

These cc::PaintFlags are propagated via various unreliable methods,
and as a result there are bugs (e.g, when they are changed on an
offscreen canvas, they have no effect until the canvas updates its
contents), and lots of complexity.

This architectural choice is one of many expedient hacks done to work
around the inefficiency of the cc::LayerTree implementation.

Add a mechanism whereby the cc::SurfaceLayer will be able to
propagate these parameters down to their child layers. To do this:
* For cc::PaintFlags::FilterQuality and DynamicRangeLimit, add new
  mojom interfaces.
* To viz::SurfaceDrawQuad, add optional override_child_filter_quality
  and override_child_dynamic_range_limit.
* In SurfaceAggregator::EmitSurfaceContent, propagate these parameters
  from to viz::SurfaceAggregator::CopyQuadsToPass.
* In viz::SurfaceAggregator::CopyQuadsToPass, if these parameters are
  present, use them to override viz::TextureDrawQuad::nearest_neighbor.

Add several tests for the new viz::SurfaceAggregator behavior, and for
the new mojo serialization.

This is not actually used anywhere except for several new unit tests.
The next patch will propagate this back through viz::SurfaceLayer to
blink.

This is incidental clean-up being done while implementing the
dynamic-range-limit CSS property.

Bug: 40277822, 380896841
Change-Id: I4f822849c31dffb45ffcb373aaee30331b879696
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6049919
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Reviewed-by: Mike West <mkwst@chromium.org>
Commit-Queue: ccameron chromium <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1389689}
This commit is contained in:
Christopher Cameron
2024-11-29 09:29:33 +00:00
committed by Chromium LUCI CQ
parent 3387a4eb9a
commit aebe9d1b30
13 changed files with 259 additions and 5 deletions

@ -132,3 +132,24 @@ mojom("element_id") {
},
]
}
mojom("paint_flags") {
generate_java = true
sources = [ "paint_flags.mojom" ]
cpp_typemaps = [
{
types = [
{
mojom = "cc.mojom.FilterQuality"
cpp = "::cc::PaintFlags::FilterQuality"
},
{
mojom = "cc.mojom.DynamicRangeLimit"
cpp = "::cc::PaintFlags::DynamicRangeLimitMixture"
},
]
traits_headers = [ "paint_flags_mojom_traits.h" ]
traits_public_deps = [ "//cc/paint" ]
},
]
}

@ -0,0 +1,18 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module cc.mojom;
enum FilterQuality {
kNone,
kLow,
kMedium,
kHigh,
};
struct DynamicRangeLimit {
float standard_mix;
float constrained_high_mix;
};

@ -0,0 +1,71 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CC_MOJOM_PAINT_FLAGS_MOJOM_TRAITS_H_
#define CC_MOJOM_PAINT_FLAGS_MOJOM_TRAITS_H_
#include "cc/mojom/paint_flags.mojom-shared.h"
#include "cc/paint/paint_flags.h"
#include "mojo/public/cpp/bindings/struct_traits.h"
namespace mojo {
template <>
struct EnumTraits<cc::mojom::FilterQuality, cc::PaintFlags::FilterQuality> {
static cc::mojom::FilterQuality ToMojom(cc::PaintFlags::FilterQuality input) {
switch (input) {
case cc::PaintFlags::FilterQuality::kNone:
return cc::mojom::FilterQuality::kNone;
case cc::PaintFlags::FilterQuality::kLow:
return cc::mojom::FilterQuality::kLow;
case cc::PaintFlags::FilterQuality::kMedium:
return cc::mojom::FilterQuality::kMedium;
case cc::PaintFlags::FilterQuality::kHigh:
return cc::mojom::FilterQuality::kHigh;
}
NOTREACHED();
}
static bool FromMojom(cc::mojom::FilterQuality input,
cc::PaintFlags::FilterQuality* out) {
switch (input) {
case cc::mojom::FilterQuality::kNone:
*out = cc::PaintFlags::FilterQuality::kNone;
return true;
case cc::mojom::FilterQuality::kLow:
*out = cc::PaintFlags::FilterQuality::kLow;
return true;
case cc::mojom::FilterQuality::kMedium:
*out = cc::PaintFlags::FilterQuality::kMedium;
return true;
case cc::mojom::FilterQuality::kHigh:
*out = cc::PaintFlags::FilterQuality::kHigh;
return true;
}
NOTREACHED();
}
};
template <>
struct StructTraits<cc::mojom::DynamicRangeLimitDataView,
cc::PaintFlags::DynamicRangeLimitMixture> {
static float standard_mix(const cc::PaintFlags::DynamicRangeLimitMixture& m) {
return m.standard_mix;
}
static float constrained_high_mix(
const cc::PaintFlags::DynamicRangeLimitMixture& m) {
return m.constrained_high_mix;
}
static bool Read(cc::mojom::DynamicRangeLimitDataView data,
cc::PaintFlags::DynamicRangeLimitMixture* out) {
out->standard_mix = data.standard_mix();
out->constrained_high_mix = data.constrained_high_mix();
return true;
}
};
} // namespace mojo
#endif // CC_MOJOM_PAINT_FLAGS_MOJOM_TRAITS_H_

@ -100,6 +100,8 @@ class CC_PAINT_EXPORT CorePaintFlags {
// "high" in log-luminance space (which is equivalent to a geometric mean in
// linear luminance).
struct DynamicRangeLimitMixture {
// The default constructor is equivalent to DynamicRangeLimit::kHigh.
DynamicRangeLimitMixture() = default;
explicit DynamicRangeLimitMixture(DynamicRangeLimit limit) {
switch (limit) {
case DynamicRangeLimit::kStandard:

@ -5,6 +5,7 @@
#ifndef COMPONENTS_VIZ_COMMON_QUADS_SURFACE_DRAW_QUAD_H_
#define COMPONENTS_VIZ_COMMON_QUADS_SURFACE_DRAW_QUAD_H_
#include "cc/paint/paint_flags.h"
#include "components/viz/common/quads/draw_quad.h"
#include "components/viz/common/surfaces/surface_range.h"
#include "components/viz/common/viz_common_export.h"
@ -47,6 +48,17 @@ class VIZ_COMMON_EXPORT SurfaceDrawQuad : public DrawQuad {
// avoiding an intermediate texture.
bool allow_merge = true;
// If `override_child_filter_quality` has a value, then, when copying quads
// from the child surface replace (during surface aggregation), replace the
// value of TextureDrawQuad::nearest_neighbor with the value indicated by
// `override_child_filter_quality`, for any TextureDrawQuad encountered.
// TODO(https://crbug.com/40277822): Apply this logic to
// `override_dynamic_range_limit`, too, when it becomes a property of
// TextureDrawQuad.
std::optional<cc::PaintFlags::FilterQuality> override_child_filter_quality;
std::optional<cc::PaintFlags::DynamicRangeLimitMixture>
override_child_dynamic_range_limit;
static const SurfaceDrawQuad* MaterialCast(const DrawQuad* quad);
private:

@ -999,7 +999,9 @@ void SurfaceAggregator::EmitSurfaceContent(
CopyQuadsToPass(resolved_frame, resolved_pass, copy_pass.get(),
resolved_frame.device_scale_factor(), gfx::Transform(), {},
dest_root_target_clip_rect, MaskFilterInfoExt());
dest_root_target_clip_rect, MaskFilterInfoExt(),
surface_quad->override_child_filter_quality,
surface_quad->override_child_dynamic_range_limit);
SetRenderPassDamageRect(copy_pass.get(), resolved_pass);
@ -1049,7 +1051,9 @@ void SurfaceAggregator::EmitSurfaceContent(
CopyQuadsToPass(resolved_frame, resolved_root_pass, dest_pass,
resolved_frame.device_scale_factor(), combined_transform,
surface_quad_clip, dest_root_target_clip_rect,
mask_filter_info);
mask_filter_info,
surface_quad->override_child_filter_quality,
surface_quad->override_child_dynamic_range_limit);
} else {
auto* shared_quad_state = CopyAndScaleSharedQuadState(
surface_quad_sqs, embedder_client_namespace_id,
@ -1371,7 +1375,10 @@ void SurfaceAggregator::CopyQuadsToPass(
const gfx::Transform& target_transform,
const std::optional<gfx::Rect> clip_rect,
const std::optional<gfx::Rect> dest_root_target_clip_rect,
const MaskFilterInfoExt& parent_mask_filter_info_ext) {
const MaskFilterInfoExt& parent_mask_filter_info_ext,
std::optional<cc::PaintFlags::FilterQuality> override_filter_quality,
std::optional<cc::PaintFlags::DynamicRangeLimitMixture>
override_dynamic_range_limit) {
const CompositorRenderPass& source_pass = resolved_pass.render_pass();
const QuadList& source_quad_list = source_pass.quad_list;
const SharedQuadState* last_copied_source_shared_quad_state = nullptr;
@ -1516,6 +1523,10 @@ void SurfaceAggregator::CopyQuadsToPass(
SkColors::kBlack, false);
} else {
dest_quad = dest_pass->CopyFromAndAppendDrawQuad(quad);
if (override_filter_quality.has_value()) {
static_cast<TextureDrawQuad*>(dest_quad)->nearest_neighbor =
override_filter_quality == cc::PaintFlags::FilterQuality::kNone;
}
}
} else {
dest_quad = dest_pass->CopyFromAndAppendDrawQuad(quad);
@ -1617,7 +1628,7 @@ void SurfaceAggregator::CopyPasses(ResolvedFrameData& resolved_frame) {
apply_surface_transform_to_root_pass ? surface_transform
: gfx::Transform(),
{}, /*dest_root_target_clip_rect*/ root_output_rect,
MaskFilterInfoExt());
MaskFilterInfoExt(), std::nullopt, std::nullopt);
SetRenderPassDamageRect(copy_pass.get(), resolved_pass);

@ -213,7 +213,10 @@ class VIZ_SERVICE_EXPORT SurfaceAggregator : public SurfaceObserver {
const gfx::Transform& target_transform,
const std::optional<gfx::Rect> clip_rect,
const std::optional<gfx::Rect> dest_root_target_clip_rect,
const MaskFilterInfoExt& mask_filter_info_pair);
const MaskFilterInfoExt& mask_filter_info_pair,
std::optional<cc::PaintFlags::FilterQuality> filter_quality,
std::optional<cc::PaintFlags::DynamicRangeLimitMixture>
dynamic_range_limit);
// Recursively walks through the render pass and updates the
// |intersects_damage_under| flag on all RenderPassDrawQuads(RPDQ).

@ -6096,6 +6096,82 @@ TEST_F(SurfaceAggregatorWithResourcesTest, SecureOutputTexture) {
render_pass->quad_list.back()->material);
}
TEST_F(SurfaceAggregatorWithResourcesTest, OverrideChildPaintFlags) {
auto support1 = std::make_unique<CompositorFrameSinkSupport>(
nullptr, &manager_, FrameSinkId(3, 1), /*is_root=*/false);
auto support2 = std::make_unique<CompositorFrameSinkSupport>(
nullptr, &manager_, FrameSinkId(4, 2), /*is_root=*/false);
LocalSurfaceId local_frame1_id(7u, base::UnguessableToken::Create());
SurfaceId surface1_id(support1->frame_sink_id(), local_frame1_id);
LocalSurfaceId local_frame2_id(8u, base::UnguessableToken::Create());
SurfaceId surface2_id(support2->frame_sink_id(), local_frame2_id);
std::vector<ResourceId> ids = {ResourceId(11)};
SubmitCompositorFrameWithResources(ids, true, SurfaceId(), support1.get(),
surface1_id);
auto frame = AggregateFrame(surface1_id);
auto* render_pass = frame.render_pass_list.back().get();
EXPECT_EQ(DrawQuad::Material::kTextureContent,
render_pass->quad_list.back()->material);
EXPECT_FALSE(static_cast<TextureDrawQuad*>(render_pass->quad_list.back())
->nearest_neighbor);
SurfaceDrawQuad* surface_quad = nullptr;
{
auto pass = CompositorRenderPass::Create();
pass->SetNew(CompositorRenderPassId{1}, gfx::Rect(0, 0, 20, 20),
gfx::Rect(), gfx::Transform());
auto* sqs = pass->CreateAndAppendSharedQuadState();
sqs->opacity = 1.f;
surface_quad = pass->CreateAndAppendDrawQuad<SurfaceDrawQuad>();
surface_quad->SetNew(sqs, gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1),
SurfaceRange(std::nullopt, surface1_id),
SkColors::kWhite,
/*stretch_content_to_fill_bounds=*/false);
CompositorFrame compositor_frame =
CompositorFrameBuilder().AddRenderPass(std::move(pass)).Build();
support2->SubmitCompositorFrame(local_frame2_id,
std::move(compositor_frame));
}
// By default nearest-neighbor is false.
frame = AggregateFrame(surface2_id);
EXPECT_EQ(1u, frame.render_pass_list.size());
render_pass = frame.render_pass_list.front().get();
EXPECT_EQ(DrawQuad::Material::kTextureContent,
render_pass->quad_list.front()->material);
EXPECT_FALSE(static_cast<TextureDrawQuad*>(render_pass->quad_list.back())
->nearest_neighbor);
// Force nearest-neighbor filtering on the child texture layer.
surface_quad->override_child_filter_quality =
cc::PaintFlags::FilterQuality::kNone;
frame = AggregateFrame(surface2_id);
EXPECT_EQ(1u, frame.render_pass_list.size());
render_pass = frame.render_pass_list.front().get();
EXPECT_EQ(DrawQuad::Material::kTextureContent,
render_pass->quad_list.front()->material);
EXPECT_TRUE(static_cast<TextureDrawQuad*>(render_pass->quad_list.back())
->nearest_neighbor);
// Force not-nearest-neighbor filtering on the child texture layer.
surface_quad->override_child_filter_quality =
cc::PaintFlags::FilterQuality::kLow;
frame = AggregateFrame(surface2_id);
EXPECT_EQ(1u, frame.render_pass_list.size());
render_pass = frame.render_pass_list.front().get();
EXPECT_EQ(DrawQuad::Material::kTextureContent,
render_pass->quad_list.front()->material);
EXPECT_FALSE(static_cast<TextureDrawQuad*>(render_pass->quad_list.back())
->nearest_neighbor);
}
// Ensure that the render passes have correct color spaces. This test
// simulates the Windows HDR behavior.
TEST_F(SurfaceAggregatorValidSurfaceTest, ColorSpaceTestWin) {

@ -972,6 +972,10 @@ TEST_F(StructTraitsTest, RenderPass) {
out_surface_quad->stretch_content_to_fill_bounds);
EXPECT_EQ(surface_quad->allow_merge, out_surface_quad->allow_merge);
EXPECT_EQ(surface_quad->is_reflection, out_surface_quad->is_reflection);
EXPECT_EQ(surface_quad->override_child_filter_quality,
out_surface_quad->override_child_filter_quality);
EXPECT_EQ(surface_quad->override_child_dynamic_range_limit,
out_surface_quad->override_child_dynamic_range_limit);
}
TEST_F(StructTraitsTest, RenderPassWithEmptySharedQuadStateList) {
@ -1050,6 +1054,10 @@ TEST_F(StructTraitsTest, QuadListBasic) {
primary_surface_quad->SetNew(
sqs, rect3, rect3, SurfaceRange(fallback_surface_id, primary_surface_id),
SkColors::kBlue, false);
primary_surface_quad->override_child_filter_quality =
cc::PaintFlags::FilterQuality::kHigh;
primary_surface_quad->override_child_dynamic_range_limit =
cc::PaintFlags::DynamicRangeLimitMixture(0.25f, 0.5f);
const gfx::Rect rect4(1234, 5678, 91012, 13141);
const bool needs_blending = true;
@ -1150,6 +1158,10 @@ TEST_F(StructTraitsTest, QuadListBasic) {
EXPECT_EQ(rect3, out_primary_surface_draw_quad->rect);
EXPECT_EQ(rect3, out_primary_surface_draw_quad->visible_rect);
EXPECT_TRUE(out_primary_surface_draw_quad->needs_blending);
EXPECT_EQ(primary_surface_quad->override_child_filter_quality,
out_primary_surface_draw_quad->override_child_filter_quality);
EXPECT_EQ(primary_surface_quad->override_child_dynamic_range_limit,
out_primary_surface_draw_quad->override_child_dynamic_range_limit);
EXPECT_EQ(primary_surface_id,
out_primary_surface_draw_quad->surface_range.end());
EXPECT_EQ(SkColors::kBlue,

@ -13,6 +13,7 @@
#include <optional>
#include "base/notreached.h"
#include "cc/mojom/paint_flags_mojom_traits.h"
#include "components/viz/common/quads/shared_element_draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "services/viz/public/cpp/compositing/compositor_render_pass_id_mojom_traits.h"
@ -141,6 +142,14 @@ bool StructTraits<viz::mojom::SurfaceQuadStateDataView, viz::DrawQuad>::Read(
quad->stretch_content_to_fill_bounds = data.stretch_content_to_fill_bounds();
quad->is_reflection = data.is_reflection();
quad->allow_merge = data.allow_merge();
if (!data.ReadOverrideChildFilterQuality(
&quad->override_child_filter_quality)) {
return false;
}
if (!data.ReadOverrideChildDynamicRangeLimit(
&quad->override_child_dynamic_range_limit)) {
return false;
}
return data.ReadSurfaceRange(&quad->surface_range);
}

@ -13,6 +13,7 @@
#include "base/memory/stack_allocated.h"
#include "base/notreached.h"
#include "base/unguessable_token.h"
#include "cc/mojom/paint_flags_mojom_traits.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/debug_border_draw_quad.h"
#include "components/viz/common/quads/picture_draw_quad.h"
@ -382,6 +383,20 @@ struct StructTraits<viz::mojom::SurfaceQuadStateDataView, viz::DrawQuad> {
return quad->allow_merge;
}
static std::optional<cc::PaintFlags::FilterQuality>
override_child_filter_quality(const viz::DrawQuad& input) {
const viz::SurfaceDrawQuad* quad =
viz::SurfaceDrawQuad::MaterialCast(&input);
return quad->override_child_filter_quality;
}
static std::optional<cc::PaintFlags::DynamicRangeLimitMixture>
override_child_dynamic_range_limit(const viz::DrawQuad& input) {
const viz::SurfaceDrawQuad* quad =
viz::SurfaceDrawQuad::MaterialCast(&input);
return quad->override_child_dynamic_range_limit;
}
static bool Read(viz::mojom::SurfaceQuadStateDataView data,
viz::DrawQuad* out);
};

@ -63,6 +63,7 @@ mojom("mojom") {
":singleplanar_format",
"//cc/mojom:element_id",
"//cc/mojom:layer_type",
"//cc/mojom:paint_flags",
"//gpu/ipc/common:interfaces",
"//media/mojo/mojom",
"//mojo/public/mojom/base",

@ -4,6 +4,7 @@
module viz.mojom;
import "cc/mojom/paint_flags.mojom";
import "mojo/public/mojom/base/unguessable_token.mojom";
import "services/viz/public/mojom/compositing/compositor_render_pass_id.mojom";
import "services/viz/public/mojom/compositing/resource_id.mojom";
@ -81,6 +82,8 @@ struct SurfaceQuadState {
bool stretch_content_to_fill_bounds;
bool is_reflection;
bool allow_merge;
cc.mojom.FilterQuality? override_child_filter_quality;
cc.mojom.DynamicRangeLimit? override_child_dynamic_range_limit;
};
struct TextureQuadState {