[Impeller] Implement, non user-facing, dithering for LinearGradients. (flutter/engine#44181)

Partial work towards https://github.com/flutter/flutter/issues/131450.

---

Run the Playground locally:

```bash
$ENGINE/out/host_debug_unopt/impeller_unittests \
  --enable_playground \
  --gtest_filter="*CanRenderLinearGradientWithDithering*"
```

Summary of changes:

- Added a playground/test for dithering disabled and enabled.
- Added `bool dither` to Impeller's `Paint`, but `SetDither` (use-facing) is ignored.
- Converted [Skia's dithering](f9de059517/src/opts/SkRasterPipeline_opts.h (L1717)) to GLSL and invoked it when `dither` is set.

## Before

![Screenshot 2023-07-31 at 1 29 16 PM](https://github.com/flutter/engine/assets/168174/51d2f7a0-dc22-44fe-b7f9-990b826c5fd9)

## After

![Screenshot 2023-07-31 at 1 29 25 PM](https://github.com/flutter/engine/assets/168174/78da2efe-2c5d-438f-b7f7-d8eb092c6a2c)

## Deleted Scenes

<details>
<summary>Here are some of my earlier attempts that are fun to share :)</summary>

![Screenshot 2023-07-31 at 11 35 07 AM](https://github.com/flutter/engine/assets/168174/719f97fc-1a3d-4920-8687-486c4de28b79)

![Screenshot 2023-07-31 at 12 12 38 PM](https://github.com/flutter/engine/assets/168174/ed262f83-442f-484b-8288-30e8e0d5e768)

![Screenshot 2023-07-31 at 12 19 56 PM](https://github.com/flutter/engine/assets/168174/057b1f95-fbd2-4ae2-bb25-8dd967cf8466)

![Screenshot 2023-07-31 at 12 25 59 PM](https://github.com/flutter/engine/assets/168174/34857c6e-49cd-40c1-9e91-646b7bfbf97c)

![Screenshot 2023-07-31 at 12 44 08 PM](https://github.com/flutter/engine/assets/168174/3b272428-b5be-4ca5-8cfe-1b12062a64f4)

</details>
This commit is contained in:
Matan Lurey
2023-07-31 19:02:25 -07:00
committed by GitHub
parent 79696e3f7a
commit bc3b2e01ef
11 changed files with 118 additions and 10 deletions

View File

@@ -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

View File

@@ -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<Color> colors = {Color{0.8, 0.8, 0.8, 1.0},
Color{0.2, 0.2, 0.2, 1.0}};
std::vector<Scalar> 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) {

View File

@@ -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<Point> bounds{start_point, end_point};

View File

@@ -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;

View File

@@ -9,6 +9,7 @@ copy("impeller") {
"color.glsl",
"constants.glsl",
"conversions.glsl",
"dithering.glsl",
"gaussian.glsl",
"gradient.glsl",
"path.glsl",

View File

@@ -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 <impeller/types.glsl>
/// 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

View File

@@ -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) {

View File

@@ -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());

View File

@@ -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<Scalar> stops_;
Entity::TileMode tile_mode_;
Color decal_border_color_ = Color::BlackTransparent();
bool dither_ = false;
FML_DISALLOW_COPY_AND_ASSIGN(LinearGradientContents);
};

View File

@@ -5,6 +5,7 @@
precision mediump float;
#include <impeller/color.glsl>
#include <impeller/dithering.glsl>
#include <impeller/texture.glsl>
#include <impeller/types.glsl>
@@ -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);
}
}

View File

@@ -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
}
}
}