[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

## After

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





</details>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -9,6 +9,7 @@ copy("impeller") {
|
||||
"color.glsl",
|
||||
"constants.glsl",
|
||||
"conversions.glsl",
|
||||
"dithering.glsl",
|
||||
"gaussian.glsl",
|
||||
"gradient.glsl",
|
||||
"path.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 <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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user