Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[Impeller] Fix alpha management issues for advanced blends. #50070

Merged
merged 10 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2208,6 +2208,9 @@ static Picture BlendModeTest(Vector2 content_scale,

Canvas canvas;
canvas.DrawPaint({.color = Color::Black()});
// TODO(bdero): Why does this cause the left image to double scale on high DPI
// displays.
// canvas.Scale(content_scale);

//----------------------------------------------------------------------------
/// 1. Save layer blending (top squares).
Expand Down Expand Up @@ -2245,7 +2248,6 @@ static Picture BlendModeTest(Vector2 content_scale,

canvas.Save();
canvas.Translate({0, 100});

// Perform the blend in a SaveLayer so that the initial backdrop color is
// fully transparent black. SourceOver blend the result onto the parent pass.
canvas.SaveLayer({});
Expand All @@ -2256,7 +2258,8 @@ static Picture BlendModeTest(Vector2 content_scale,
.blend_mode = BlendMode::kSourceOver});
canvas.Translate(Vector2(100, 0));
}
canvas.RestoreToCount(0);
canvas.Restore();
canvas.Restore();

//----------------------------------------------------------------------------
/// 3. Image blending (bottom images).
Expand All @@ -2278,16 +2281,17 @@ static Picture BlendModeTest(Vector2 content_scale,
}
}

// Uploaded image source (unpremultiplied source texture).
// Uploaded image source (left image).
canvas.Save();
canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver});
{
canvas.DrawImage(dst_image, {0, 0}, {.blend_mode = BlendMode::kSourceOver});
canvas.DrawImage(src_image, {0, 0}, {.blend_mode = blend_mode});
}
canvas.RestoreToCount(0);
canvas.Restore();
canvas.Restore();

// Rendered image source (premultiplied source texture).
// Rendered image source (right image).
canvas.Save();
canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver});
{
Expand All @@ -2301,7 +2305,7 @@ static Picture BlendModeTest(Vector2 content_scale,
canvas.Restore();
}
canvas.Restore();
canvas.RestoreToCount(0);
canvas.Restore();

return canvas.EndRecordingAsPicture();
}
Expand Down Expand Up @@ -3176,7 +3180,7 @@ TEST_P(AiksTest, SolidColorApplyColorFilter) {
});
ASSERT_TRUE(result);
ASSERT_COLOR_NEAR(contents.GetColor(),
Color(0.433247, 0.879523, 0.825324, 0.75));
Color(0.424452, 0.828743, 0.79105, 0.9375));
}

TEST_P(AiksTest, DrawScaledTextWithPerspectiveNoSaveLayer) {
Expand Down
19 changes: 19 additions & 0 deletions impeller/compiler/shader_lib/impeller/blending.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,28 @@
#define BLENDING_GLSL_

#include <impeller/branching.glsl>
#include <impeller/color.glsl>
#include <impeller/constants.glsl>
#include <impeller/types.glsl>

/// Composite a blended color onto the destination.
/// All three parameters are unpremultiplied. Returns a premultiplied result.
///
/// This routine is the same as `ApplyBlendedColor` in
/// `impeller/geometry/color.cc`.
f16vec4 IPApplyBlendedColor(f16vec4 dst, f16vec4 src, f16vec3 blend_result) {
dst = IPHalfPremultiply(dst);
src =
// Use the blended color for areas where the source and destination
// colors overlap.
IPHalfPremultiply(f16vec4(blend_result, src.a * dst.a)) +
// Use the original source color for any remaining non-overlapping areas.
IPHalfPremultiply(src) * (1.0hf - dst.a);

// Source-over composite the blended source color atop the destination.
return src + dst * (1.0hf - src.a);
}

//------------------------------------------------------------------------------
/// HSV utilities.
///
Expand Down
28 changes: 14 additions & 14 deletions impeller/entity/shaders/blending/advanced_blend.frag
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,20 @@ f16vec4 Sample(f16sampler2D texture_sampler, vec2 texture_coords) {
}

void main() {
f16vec4 dst_sample = Sample(texture_sampler_dst, // sampler
v_dst_texture_coords // texture coordinates
) *
blend_info.dst_input_alpha;

f16vec4 dst = dst_sample;
f16vec4 src = blend_info.color_factor > 0.0hf
? blend_info.color
: Sample(texture_sampler_src, // sampler
v_src_texture_coords // texture coordinates
) *
blend_info.src_input_alpha;
f16vec4 dst =
IPHalfUnpremultiply(Sample(texture_sampler_dst, // sampler
v_dst_texture_coords // texture coordinates
));
dst *= blend_info.dst_input_alpha;
f16vec4 src = IPHalfUnpremultiply(
blend_info.color_factor > 0.0hf
? blend_info.color
: Sample(texture_sampler_src, // sampler
v_src_texture_coords // texture coordinates
));
src *= blend_info.src_input_alpha;

f16vec3 blend_result = AdvancedBlend(dst.rgb, src.rgb, int(blend_type));
f16vec4 blended = mix(src, f16vec4(blend_result, src.a), dst.a);
frag_color = mix(dst_sample, blended, src.a);

frag_color = IPApplyBlendedColor(dst, src, blend_result);
}
15 changes: 8 additions & 7 deletions impeller/entity/shaders/blending/framebuffer_blend.frag
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ vec4 Sample(sampler2D texture_sampler, vec2 texture_coords) {
}

void main() {
f16vec4 dst = f16vec4(ReadDestination());
f16vec4 src = f16vec4(Sample(texture_sampler_src, // sampler
v_src_texture_coords // texture coordinates
)) *
frag_info.src_input_alpha;
f16vec4 dst = IPHalfUnpremultiply(f16vec4(ReadDestination()));
f16vec4 src = IPHalfUnpremultiply(
f16vec4(Sample(texture_sampler_src, // sampler
v_src_texture_coords // texture coordinates
)));
src.a *= frag_info.src_input_alpha;

f16vec3 blend_result = AdvancedBlend(dst.rgb, src.rgb, int(blend_type));
f16vec4 blended = mix(src, f16vec4(blend_result, src.a), dst.a);
frag_color = vec4(mix(dst, blended, src.a));

frag_color = IPApplyBlendedColor(dst, src, blend_result);
}
48 changes: 30 additions & 18 deletions impeller/geometry/color.cc
Original file line number Diff line number Diff line change
Expand Up @@ -193,30 +193,42 @@ static constexpr inline Color FromRGB(Vector3 color, Scalar alpha) {
return {color.x, color.y, color.z, alpha};
}

/// Composite a blended color onto the destination.
/// All three parameters are unpremultiplied. Returns a premultiplied result.
///
/// This routine is the same as `IPApplyBlendedColor` in the Impeller shader
/// library.
static constexpr inline Color ApplyBlendedColor(Color dst,
Color src,
Vector3 blend_result) {
dst = dst.Premultiply();
src =
// Use the blended color for areas where the source and destination
// colors overlap.
FromRGB(blend_result, src.alpha * dst.alpha).Premultiply() +
// Use the original source color for any remaining non-overlapping areas.
src.Premultiply() * (1.0f - dst.alpha);

// Source-over composite the blended source color atop the destination.
return src + dst * (1.0f - src.alpha);
}

static constexpr inline Color DoColorBlend(
Color d,
Color s,
Color dst,
Color src,
const std::function<Vector3(Vector3, Vector3)>& blend_rgb_func) {
d = d.Premultiply();
s = s.Premultiply();
const Vector3 rgb = blend_rgb_func(ToRGB(d), ToRGB(s));
const Color blended = Color::Lerp(s, FromRGB(rgb, d.alpha), d.alpha);
return Color::Lerp(d, blended, s.alpha).Unpremultiply();
const Vector3 blend_result = blend_rgb_func(ToRGB(dst), ToRGB(src));
return ApplyBlendedColor(dst, src, blend_result).Unpremultiply();
}

static constexpr inline Color DoColorBlendComponents(
Color d,
Color s,
Color dst,
Color src,
const std::function<Scalar(Scalar, Scalar)>& blend_func) {
d = d.Premultiply();
s = s.Premultiply();
const Color blended = Color::Lerp(s,
Color(blend_func(d.red, s.red), //
blend_func(d.green, s.green), //
blend_func(d.blue, s.blue), //
d.alpha),
d.alpha);
return Color::Lerp(d, blended, s.alpha).Unpremultiply();
Vector3 blend_result = Vector3(blend_func(dst.red, src.red), //
blend_func(dst.green, src.green), //
blend_func(dst.blue, src.blue)); //
return ApplyBlendedColor(dst, src, blend_result).Unpremultiply();
}

Color Color::Blend(Color src, BlendMode blend_mode) const {
Expand Down
152 changes: 152 additions & 0 deletions impeller/geometry/geometry_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1431,6 +1431,158 @@ TEST(GeometryTest, ColorSRGBToLinear) {
}
}

struct ColorBlendTestData {
static constexpr Color kDestinationColor =
Color::CornflowerBlue().WithAlpha(0.75);
static constexpr Color kSourceColors[] = {Color::White().WithAlpha(0.75),
Color::LimeGreen().WithAlpha(0.75),
Color::Black().WithAlpha(0.75)};

// THIS RESULT TABLE IS GENERATED!
//
// Uncomment the `GenerateColorBlendResults` test below to print a new table
// after making changes to `Color::Blend`.
static constexpr Color kExpectedResults
[sizeof(kSourceColors)]
[static_cast<std::underlying_type_t<BlendMode>>(BlendMode::kLast) + 1] = {
{
{0, 0, 0, 0}, // Clear
{1, 1, 1, 0.75}, // Source
{0.392157, 0.584314, 0.929412, 0.75}, // Destination
{0.878431, 0.916863, 0.985882, 0.9375}, // SourceOver
{0.513726, 0.667451, 0.943529, 0.9375}, // DestinationOver
{1, 1, 1, 0.5625}, // SourceIn
{0.392157, 0.584314, 0.929412, 0.5625}, // DestinationIn
{1, 1, 1, 0.1875}, // SourceOut
{0.392157, 0.584314, 0.929412, 0.1875}, // DestinationOut
{0.848039, 0.896078, 0.982353, 0.75}, // SourceATop
{0.544118, 0.688235, 0.947059, 0.75}, // DestinationATop
{0.696078, 0.792157, 0.964706, 0.375}, // Xor
{1, 1, 1, 1}, // Plus
{0.392157, 0.584314, 0.929412, 0.5625}, // Modulate
{0.878431, 0.916863, 0.985882, 0.9375}, // Screen
{0.74902, 0.916863, 0.985882, 0.9375}, // Overlay
{0.513726, 0.667451, 0.943529, 0.9375}, // Darken
{0.878431, 0.916863, 0.985882, 0.9375}, // Lighten
{0.878431, 0.916863, 0.985882, 0.9375}, // ColorDodge
{0.513725, 0.667451, 0.943529, 0.9375}, // ColorBurn
{0.878431, 0.916863, 0.985882, 0.9375}, // HardLight
{0.654166, 0.775505, 0.964318, 0.9375}, // SoftLight
{0.643137, 0.566275, 0.428235, 0.9375}, // Difference
{0.643137, 0.566275, 0.428235, 0.9375}, // Exclusion
{0.513726, 0.667451, 0.943529, 0.9375}, // Multiply
{0.617208, 0.655639, 0.724659, 0.9375}, // Hue
{0.617208, 0.655639, 0.724659, 0.9375}, // Saturation
{0.617208, 0.655639, 0.724659, 0.9375}, // Color
{0.878431, 0.916863, 0.985882, 0.9375}, // Luminosity
},
{
{0, 0, 0, 0}, // Clear
{0.196078, 0.803922, 0.196078, 0.75}, // Source
{0.392157, 0.584314, 0.929412, 0.75}, // Destination
{0.235294, 0.76, 0.342745, 0.9375}, // SourceOver
{0.352941, 0.628235, 0.782745, 0.9375}, // DestinationOver
{0.196078, 0.803922, 0.196078, 0.5625}, // SourceIn
{0.392157, 0.584314, 0.929412, 0.5625}, // DestinationIn
{0.196078, 0.803922, 0.196078, 0.1875}, // SourceOut
{0.392157, 0.584314, 0.929412, 0.1875}, // DestinationOut
{0.245098, 0.74902, 0.379412, 0.75}, // SourceATop
{0.343137, 0.639216, 0.746078, 0.75}, // DestinationATop
{0.294118, 0.694118, 0.562745, 0.375}, // Xor
{0.441176, 1, 0.844118, 1}, // Plus
{0.0768935, 0.469742, 0.182238, 0.5625}, // Modulate
{0.424452, 0.828743, 0.79105, 0.9375}, // Screen
{0.209919, 0.779839, 0.757001, 0.9375}, // Overlay
{0.235294, 0.628235, 0.342745, 0.9375}, // Darken
{0.352941, 0.76, 0.782745, 0.9375}, // Lighten
{0.41033, 0.877647, 0.825098, 0.9375}, // ColorDodge
{0.117647, 0.567403, 0.609098, 0.9375}, // ColorBurn
{0.209919, 0.779839, 0.443783, 0.9375}, // HardLight
{0.266006, 0.693915, 0.758818, 0.9375}, // SoftLight
{0.235294, 0.409412, 0.665098, 0.9375}, // Difference
{0.378316, 0.546897, 0.681707, 0.9375}, // Exclusion
{0.163783, 0.559493, 0.334441, 0.9375}, // Multiply
{0.266235, 0.748588, 0.373686, 0.9375}, // Hue
{0.339345, 0.629787, 0.811502, 0.9375}, // Saturation
{0.241247, 0.765953, 0.348698, 0.9375}, // Color
{0.346988, 0.622282, 0.776792, 0.9375}, // Luminosity
},
{
{0, 0, 0, 0}, // Clear
{0, 0, 0, 0.75}, // Source
{0.392157, 0.584314, 0.929412, 0.75}, // Destination
{0.0784314, 0.116863, 0.185882, 0.9375}, // SourceOver
{0.313726, 0.467451, 0.743529, 0.9375}, // DestinationOver
{0, 0, 0, 0.5625}, // SourceIn
{0.392157, 0.584314, 0.929412, 0.5625}, // DestinationIn
{0, 0, 0, 0.1875}, // SourceOut
{0.392157, 0.584314, 0.929412, 0.1875}, // DestinationOut
{0.0980392, 0.146078, 0.232353, 0.75}, // SourceATop
{0.294118, 0.438235, 0.697059, 0.75}, // DestinationATop
{0.196078, 0.292157, 0.464706, 0.375}, // Xor
{0.294118, 0.438235, 0.697059, 1}, // Plus
{0, 0, 0, 0.5625}, // Modulate
{0.313726, 0.467451, 0.743529, 0.9375}, // Screen
{0.0784314, 0.218039, 0.701176, 0.9375}, // Overlay
{0.0784314, 0.116863, 0.185882, 0.9375}, // Darken
{0.313726, 0.467451, 0.743529, 0.9375}, // Lighten
{0.313726, 0.467451, 0.743529, 0.9375}, // ColorDodge
{0.0784314, 0.116863, 0.185882, 0.9375}, // ColorBurn
{0.0784314, 0.116863, 0.185882, 0.9375}, // HardLight
{0.170704, 0.321716, 0.704166, 0.9375}, // SoftLight
{0.313726, 0.467451, 0.743529, 0.9375}, // Difference
{0.313726, 0.467451, 0.743529, 0.9375}, // Exclusion
{0.0784314, 0.116863, 0.185882, 0.9375}, // Multiply
{0.417208, 0.455639, 0.524659, 0.9375}, // Hue
{0.417208, 0.455639, 0.524659, 0.9375}, // Saturation
{0.417208, 0.455639, 0.524659, 0.9375}, // Color
{0.0784314, 0.116863, 0.185882, 0.9375}, // Luminosity
},
};
};

/// To print a new ColorBlendTestData::kExpectedResults table, uncomment this
/// test and run with:
/// --gtest_filter="GeometryTest.GenerateColorBlendResults"
/*
TEST(GeometryTest, GenerateColorBlendResults) {
auto& o = std::cout;
using BlendT = std::underlying_type_t<BlendMode>;
o << "{";
for (const auto& source : ColorBlendTestData::kSourceColors) {
o << "{";
for (BlendT blend_i = 0;
blend_i < static_cast<BlendT>(BlendMode::kLast) + 1; blend_i++) {
auto blend = static_cast<BlendMode>(blend_i);
Color c = ColorBlendTestData::kDestinationColor.Blend(source, blend);
o << "{" << c.red << "," << c.green << "," << c.blue << "," << c.alpha
<< "}, // " << BlendModeToString(blend) << std::endl;
}
o << "},";
}
o << "};" << std::endl;
}
*/

#define _BLEND_MODE_RESULT_CHECK(blend_mode) \
blend_i = static_cast<BlendT>(BlendMode::k##blend_mode); \
expected = ColorBlendTestData::kExpectedResults[source_i][blend_i]; \
EXPECT_COLOR_NEAR(dst.Blend(src, BlendMode::k##blend_mode), expected);

TEST(GeometryTest, ColorBlendReturnsExpectedResults) {
using BlendT = std::underlying_type_t<BlendMode>;
Color dst = ColorBlendTestData::kDestinationColor;
for (size_t source_i = 0;
source_i < sizeof(ColorBlendTestData::kSourceColors) / sizeof(Color);
source_i++) {
Color src = ColorBlendTestData::kSourceColors[source_i];

size_t blend_i;
Color expected;
IMPELLER_FOR_EACH_BLEND_MODE(_BLEND_MODE_RESULT_CHECK)
}
}

#define _BLEND_MODE_NAME_CHECK(blend_mode) \
case BlendMode::k##blend_mode: \
ASSERT_STREQ(result, #blend_mode); \
Expand Down
Loading