diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 36cc37fc4d..6c1b893c9e 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -1097,6 +1097,7 @@ ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/branching.glsl + ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/color.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/constants.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/conversions.glsl + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/dithering.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/gaussian.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/gradient.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/path.glsl + ../../../flutter/LICENSE @@ -3789,6 +3790,7 @@ FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/branching.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/color.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/constants.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/conversions.glsl +FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/dithering.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/gaussian.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/gradient.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/path.glsl diff --git a/engine/src/flutter/impeller/aiks/aiks_unittests.cc b/engine/src/flutter/impeller/aiks/aiks_unittests.cc index 0b061b28fe..0dc42ed6a8 100644 --- a/engine/src/flutter/impeller/aiks/aiks_unittests.cc +++ b/engine/src/flutter/impeller/aiks/aiks_unittests.cc @@ -419,6 +419,36 @@ TEST_P(AiksTest, CanRenderLinearGradientDecalWithColorFilter) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +namespace { +void CanRenderLinearGradientWithDithering(AiksTest* aiks_test, + bool use_dithering) { + Canvas canvas; + Paint paint; + canvas.Translate({100.0, 100.0, 0}); + + // 0xffcccccc --> 0xff333333, taken from + // https://github.com/flutter/flutter/issues/118073#issue-1521699748 + std::vector colors = {Color{0.8, 0.8, 0.8, 1.0}, + Color{0.2, 0.2, 0.2, 1.0}}; + std::vector stops = {0.0, 1.0}; + + paint.color_source = ColorSource::MakeLinearGradient( + {0, 0}, {800, 500}, std::move(colors), std::move(stops), + Entity::TileMode::kClamp, {}); + paint.dither = use_dithering; + canvas.DrawRect({0, 0, 800, 500}, paint); + ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} +} // namespace + +TEST_P(AiksTest, CanRenderLinearGradientWithDitheringDisabled) { + CanRenderLinearGradientWithDithering(this, false); +} + +TEST_P(AiksTest, CanRenderLinearGradientWithDitheringEnabled) { + CanRenderLinearGradientWithDithering(this, true); +} // namespace + namespace { void CanRenderLinearGradientWithOverlappingStops(AiksTest* aiks_test, Entity::TileMode tile_mode) { diff --git a/engine/src/flutter/impeller/aiks/color_source.cc b/engine/src/flutter/impeller/aiks/color_source.cc index ccb89c9e64..c7532eea7e 100644 --- a/engine/src/flutter/impeller/aiks/color_source.cc +++ b/engine/src/flutter/impeller/aiks/color_source.cc @@ -55,6 +55,7 @@ ColorSource ColorSource::MakeLinearGradient(Point start_point, contents->SetStops(stops); contents->SetEndPoints(start_point, end_point); contents->SetTileMode(tile_mode); + contents->SetDither(paint.dither); contents->SetEffectTransform(effect_transform); std::vector bounds{start_point, end_point}; diff --git a/engine/src/flutter/impeller/aiks/paint.h b/engine/src/flutter/impeller/aiks/paint.h index 45b3bad529..7f3839be18 100644 --- a/engine/src/flutter/impeller/aiks/paint.h +++ b/engine/src/flutter/impeller/aiks/paint.h @@ -51,6 +51,7 @@ struct Paint { Color color = Color::Black(); ColorSource color_source; + bool dither = false; Scalar stroke_width = 0.0; Cap stroke_cap = Cap::kButt; diff --git a/engine/src/flutter/impeller/compiler/shader_lib/impeller/BUILD.gn b/engine/src/flutter/impeller/compiler/shader_lib/impeller/BUILD.gn index 22c857c215..08fd0e8af6 100644 --- a/engine/src/flutter/impeller/compiler/shader_lib/impeller/BUILD.gn +++ b/engine/src/flutter/impeller/compiler/shader_lib/impeller/BUILD.gn @@ -9,6 +9,7 @@ copy("impeller") { "color.glsl", "constants.glsl", "conversions.glsl", + "dithering.glsl", "gaussian.glsl", "gradient.glsl", "path.glsl", diff --git a/engine/src/flutter/impeller/compiler/shader_lib/impeller/dithering.glsl b/engine/src/flutter/impeller/compiler/shader_lib/impeller/dithering.glsl new file mode 100644 index 0000000000..e8e4e8439c --- /dev/null +++ b/engine/src/flutter/impeller/compiler/shader_lib/impeller/dithering.glsl @@ -0,0 +1,54 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DITHERING_GLSL_ +#define DITHERING_GLSL_ + +#include + +/// The dithering rate, which is 1.0 / 64.0, or 0.015625. +const float kDitherRate = 1.0 / 64.0; + +/// Returns the closest color to the input color using 8x8 ordered dithering. +/// +/// Ordered dithering divides the output into a grid of cells, and then assigns +/// a different threshold to each cell. The threshold is used to determine if +/// the color should be rounded up (white) or down (black). +// +/// This technique was chosen mostly because Skia also uses it: +/// https://github.com/google/skia/blob/f9de059517a6f58951510fc7af0cba21e13dd1a8/src/opts/SkRasterPipeline_opts.h#L1717 +/// +/// See also: +/// - https://en.wikipedia.org/wiki/Ordered_dithering +/// - https://surma.dev/things/ditherpunk/ +/// - https://shader-tutorial.dev/advanced/color-banding-dithering/ +vec4 IPOrderedDither8x8(vec4 color, vec2 dest) { + // Get the x and y coordinates of the pixel in the 8x8 grid. + uint x = uint(dest.x) % 8; + uint y = uint(dest.y); + y ^= x; + + // Get the dither value from the matrix. + uint m = (y & 1) << 5 | // + (x & 1) << 4 | // + (y & 2) << 2 | // + (x & 2) << 1 | // + (y & 4) >> 1 | // + (x & 4) >> 2; // + + // Scale that dither to [0,1), then (-0.5,+0.5), here using 63/128 = 0.4921875 + // as 0.5-epsilon. We want to make sure our dither is less than 0.5 in either + // direction to keep exact values like 0 and 1 unchanged after rounding. + float dither = float(m) * (2.0 / 128.0) - (63.0 / 128.0); + + // Apply the dither to the color. + color.rgb += dither * kDitherRate; + + // Clamp the color values to [0,1]. + color.rgb = clamp(color.rgb, 0.0, 1.0); + + return color; +} + +#endif diff --git a/engine/src/flutter/impeller/display_list/dl_dispatcher.cc b/engine/src/flutter/impeller/display_list/dl_dispatcher.cc index 9e71de28e1..c2d65bc260 100644 --- a/engine/src/flutter/impeller/display_list/dl_dispatcher.cc +++ b/engine/src/flutter/impeller/display_list/dl_dispatcher.cc @@ -186,7 +186,12 @@ void DlDispatcher::setAntiAlias(bool aa) { } // |flutter::DlOpReceiver| -void DlDispatcher::setDither(bool dither) {} +void DlDispatcher::setDither(bool dither) { + // TODO(https://github.com/flutter/flutter/issues/131450): Implement dither. + // + // This is intentionally left blank because we don't want to ship enabling + // dithering from the framework yet (it's only used in Impeller-only tests). +} static Paint::Style ToStyle(flutter::DlDrawStyle style) { switch (style) { diff --git a/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc b/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc index efe021a529..b071bf8893 100644 --- a/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc @@ -56,6 +56,10 @@ bool LinearGradientContents::IsOpaque() const { return true; } +void LinearGradientContents::SetDither(bool dither) { + dither_ = dither; +} + bool LinearGradientContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { @@ -146,6 +150,7 @@ bool LinearGradientContents::RenderSSBO(const ContentContext& renderer, auto colors = CreateGradientColors(colors_, stops_); frag_info.colors_length = colors.size(); + frag_info.dither = dither_; auto color_buffer = host_buffer.Emplace(colors.data(), colors.size() * sizeof(StopData), DefaultUniformAlignment()); diff --git a/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.h b/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.h index 58509f709a..e7556af744 100644 --- a/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.h +++ b/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.h @@ -49,6 +49,8 @@ class LinearGradientContents final : public ColorSourceContents { void SetTileMode(Entity::TileMode tile_mode); + void SetDither(bool dither); + private: bool RenderTexture(const ContentContext& renderer, const Entity& entity, @@ -64,6 +66,7 @@ class LinearGradientContents final : public ColorSourceContents { std::vector stops_; Entity::TileMode tile_mode_; Color decal_border_color_ = Color::BlackTransparent(); + bool dither_ = false; FML_DISALLOW_COPY_AND_ASSIGN(LinearGradientContents); }; diff --git a/engine/src/flutter/impeller/entity/shaders/linear_gradient_ssbo_fill.frag b/engine/src/flutter/impeller/entity/shaders/linear_gradient_ssbo_fill.frag index 2b70fc8845..818db480e9 100644 --- a/engine/src/flutter/impeller/entity/shaders/linear_gradient_ssbo_fill.frag +++ b/engine/src/flutter/impeller/entity/shaders/linear_gradient_ssbo_fill.frag @@ -5,6 +5,7 @@ precision mediump float; #include +#include #include #include @@ -25,6 +26,7 @@ uniform FragInfo { float tile_mode; vec4 decal_border_color; int colors_length; + bool dither; } frag_info; @@ -59,4 +61,8 @@ void main() { } } frag_color = IPPremultiply(frag_color) * frag_info.alpha; + + if (frag_info.dither) { + frag_color = IPOrderedDither8x8(frag_color, v_position); + } } diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index d1ab7bb95e..49e15fa8ff 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -11681,7 +11681,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 32, + "fp16_arithmetic": 37, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -11709,9 +11709,9 @@ "varying" ], "shortest_path_cycles": [ + 0.1875, 0.15625, - 0.15625, - 0.15625, + 0.1875, 0.0625, 0.0, 0.25, @@ -11721,10 +11721,10 @@ "load_store" ], "total_cycles": [ - 0.637499988079071, - 0.375, - 0.637499988079071, - 0.125, + 0.8125, + 0.4375, + 0.8125, + 0.625, 4.0, 0.25, 0.0 @@ -11732,8 +11732,8 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 16, - "work_registers_used": 15 + "uniform_registers_used": 24, + "work_registers_used": 20 } } }