[Impeller] Make porter duff blended atlas calls faster. (flutter/engine#44059)

This implements part of https://github.com/flutter/flutter/issues/131345

---

Makes all porter-duff draw atlas blending avoid subpass creation, by tweaking the existing porter duff shader to support per-vertex colors and output opacity. This requires a small trick: What the blend shaders and what (drawAtlas/drawVertices) consider the source and destination is reversed. That is a blend mode color filter treats the color as the source whereas drawVertices/drawAtlas treat their per-vertex colors as the destination. Therefore I cannot simply make all color inputs per vertex and use the exact same shaders :(

However, for the porter duff blends we should just be able to reverse the blend mode (srcover -> dstOver, xor->xor) (I'm pretty sure I did this correctly) so that the blending is still correct. I don't think we can do this for the advanced blends, but supporting those is less important than the common atlas blending parameters like dstIn.

This PR only updates drawAtlas.
This commit is contained in:
Jonah Williams 2023-08-15 12:40:09 -07:00 committed by GitHub
parent 015275a126
commit 4367ca66ae
9 changed files with 483 additions and 63 deletions

View File

@ -1308,6 +1308,7 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_screen.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_softlight.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.vert + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/border_mask_blur.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/border_mask_blur.vert + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/clip.frag + ../../../flutter/LICENSE
@ -4028,6 +4029,7 @@ FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_sa
FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_screen.frag
FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_softlight.frag
FILE: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.frag
FILE: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.vert
FILE: ../../../flutter/impeller/entity/shaders/border_mask_blur.frag
FILE: ../../../flutter/impeller/entity/shaders/border_mask_blur.vert
FILE: ../../../flutter/impeller/entity/shaders/clip.frag

View File

@ -71,6 +71,7 @@ impeller_shaders("entity_shaders") {
"shaders/yuv_to_rgb_filter.frag",
"shaders/yuv_to_rgb_filter.vert",
"shaders/blending/porter_duff_blend.frag",
"shaders/blending/porter_duff_blend.vert",
]
if (impeller_debug) {

View File

@ -11,6 +11,7 @@
#include "impeller/core/formats.h"
#include "impeller/entity/contents/atlas_contents.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/filters/blend_filter_contents.h"
#include "impeller/entity/contents/filters/color_filter_contents.h"
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/framebuffer_blend_contents.h"
@ -19,6 +20,7 @@
#include "impeller/entity/geometry/geometry.h"
#include "impeller/entity/texture_fill.frag.h"
#include "impeller/entity/texture_fill.vert.h"
#include "impeller/geometry/color.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/sampler_library.h"
#include "impeller/renderer/vertex_buffer_builder.h"
@ -210,6 +212,87 @@ bool AtlasContents::Render(const ContentContext& renderer,
return child_contents.Render(renderer, entity, pass);
}
constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};
constexpr Scalar width[6] = {0, 1, 0, 1, 0, 1};
constexpr Scalar height[6] = {0, 0, 1, 0, 1, 1};
if (blend_mode_ <= BlendMode::kModulate) {
// Simple Porter-Duff blends can be accomplished without a subpass.
using VS = PorterDuffBlendPipeline::VertexShader;
using FS = PorterDuffBlendPipeline::FragmentShader;
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.Reserve(texture_coords_.size() * 6);
const auto texture_size = texture_->GetSize();
auto& host_buffer = pass.GetTransientsBuffer();
for (size_t i = 0; i < texture_coords_.size(); i++) {
auto sample_rect = texture_coords_[i];
auto matrix = transforms_[i];
auto transformed_points =
Rect::MakeSize(sample_rect.size).GetTransformedPoints(matrix);
auto color = colors_[i].Premultiply();
for (size_t j = 0; j < 6; j++) {
VS::PerVertexData data;
data.vertices = transformed_points[indices[j]];
data.texture_coords =
(sample_rect.origin + Point(sample_rect.size.width * width[j],
sample_rect.size.height * height[j])) /
texture_size;
data.color = color;
vtx_builder.AppendVertex(data);
}
}
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);
Command cmd;
DEBUG_COMMAND_INFO(
cmd, SPrintF("DrawAtlas Blend (%s)", BlendModeToString(blend_mode_)));
cmd.BindVertices(vtx_buffer);
cmd.stencil_reference = entity.GetStencilDepth();
auto options = OptionsFromPass(pass);
cmd.pipeline = renderer.GetPorterDuffBlendPipeline(options);
FS::FragInfo frag_info;
VS::FrameInfo frame_info;
auto dst_sampler_descriptor = sampler_descriptor_;
if (renderer.GetDeviceCapabilities().SupportsDecalTileMode()) {
dst_sampler_descriptor.width_address_mode = SamplerAddressMode::kDecal;
dst_sampler_descriptor.height_address_mode = SamplerAddressMode::kDecal;
}
auto dst_sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler(
dst_sampler_descriptor);
FS::BindTextureSamplerDst(cmd, texture_, dst_sampler);
frame_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale();
frag_info.output_alpha = alpha_;
frag_info.input_alpha = 1.0;
auto inverted_blend_mode =
InvertPorterDuffBlend(blend_mode_).value_or(BlendMode::kSource);
auto blend_coefficients =
kPorterDuffCoefficients[static_cast<int>(inverted_blend_mode)];
frag_info.src_coeff = blend_coefficients[0];
frag_info.src_coeff_dst_alpha = blend_coefficients[1];
frag_info.dst_coeff = blend_coefficients[2];
frag_info.dst_coeff_src_alpha = blend_coefficients[3];
frag_info.dst_coeff_src_color = blend_coefficients[4];
FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info));
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation();
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(cmd, uniform_view);
return pass.AddCommand(std::move(cmd));
}
// Advanced blends.
auto sub_atlas = GenerateSubAtlas();
auto sub_coverage = Rect::MakeSize(sub_atlas->size);

View File

@ -44,6 +44,7 @@
#include "impeller/entity/morphology_filter.vert.h"
#include "impeller/entity/points.comp.h"
#include "impeller/entity/porter_duff_blend.frag.h"
#include "impeller/entity/porter_duff_blend.vert.h"
#include "impeller/entity/radial_gradient_fill.frag.h"
#include "impeller/entity/rrect_blur.frag.h"
#include "impeller/entity/rrect_blur.vert.h"
@ -182,7 +183,7 @@ using GlyphAtlasPipeline =
using GlyphAtlasColorPipeline =
RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasColorFragmentShader>;
using PorterDuffBlendPipeline =
RenderPipelineT<BlendVertexShader, PorterDuffBlendFragmentShader>;
RenderPipelineT<PorterDuffBlendVertexShader, PorterDuffBlendFragmentShader>;
// Instead of requiring new shaders for clips, the solid fill stages are used
// to redirect writing to the stencil instead of color attachments.
using ClipPipeline = RenderPipelineT<ClipVertexShader, ClipFragmentShader>;

View File

@ -24,6 +24,41 @@
namespace impeller {
std::optional<BlendMode> InvertPorterDuffBlend(BlendMode blend_mode) {
switch (blend_mode) {
case BlendMode::kClear:
return BlendMode::kClear;
case BlendMode::kSource:
return BlendMode::kDestination;
case BlendMode::kDestination:
return BlendMode::kSource;
case BlendMode::kSourceOver:
return BlendMode::kDestinationOver;
case BlendMode::kDestinationOver:
return BlendMode::kSourceOver;
case BlendMode::kSourceIn:
return BlendMode::kDestinationIn;
case BlendMode::kDestinationIn:
return BlendMode::kSourceIn;
case BlendMode::kSourceOut:
return BlendMode::kDestinationOut;
case BlendMode::kDestinationOut:
return BlendMode::kSourceOut;
case BlendMode::kSourceATop:
return BlendMode::kDestinationATop;
case BlendMode::kDestinationATop:
return BlendMode::kSourceATop;
case BlendMode::kXor:
return BlendMode::kXor;
case BlendMode::kPlus:
return BlendMode::kPlus;
case BlendMode::kModulate:
return BlendMode::kModulate;
default:
return std::nullopt;
}
}
BlendFilterContents::BlendFilterContents() {
SetBlendMode(BlendMode::kSourceOver);
}
@ -352,24 +387,6 @@ std::optional<Entity> BlendFilterContents::CreateForegroundAdvancedBlend(
return sub_entity;
}
constexpr std::array<std::array<Scalar, 5>, 15> kPorterDuffCoefficients = {{
{0, 0, 0, 0, 0}, // Clear
{1, 0, 0, 0, 0}, // Source
{0, 0, 1, 0, 0}, // Destination
{1, 0, 1, -1, 0}, // SourceOver
{1, -1, 1, 0, 0}, // DestinationOver
{0, 1, 0, 0, 0}, // SourceIn
{0, 0, 0, 1, 0}, // DestinationIn
{1, -1, 0, 0, 0}, // SourceOut
{0, 0, 1, -1, 0}, // DestinationOut
{0, 1, 1, -1, 0}, // SourceATop
{1, -1, 0, 1, 0}, // DestinationATop
{1, -1, 1, -1, 0}, // Xor
{1, 0, 1, 0, 0}, // Plus
{0, 0, 0, 0, 1}, // Modulate
{0, 0, 1, 0, -1}, // Screen
}};
std::optional<Entity> BlendFilterContents::CreateForegroundPorterDuffBlend(
const std::shared_ptr<FilterInput>& input,
const ContentContext& renderer,
@ -423,14 +440,17 @@ std::optional<Entity> BlendFilterContents::CreateForegroundPorterDuffBlend(
auto size = coverage.size;
auto origin = coverage.origin;
auto color = foreground_color.Premultiply();
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{origin, dst_uvs[0]},
{Point(origin.x + size.width, origin.y), dst_uvs[1]},
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3]},
{origin, dst_uvs[0]},
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3]},
{Point(origin.x, origin.y + size.height), dst_uvs[2]},
{origin, dst_uvs[0], color},
{Point(origin.x + size.width, origin.y), dst_uvs[1], color},
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3],
color},
{origin, dst_uvs[0], color},
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3],
color},
{Point(origin.x, origin.y + size.height), dst_uvs[2], color},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);
@ -456,9 +476,9 @@ std::optional<Entity> BlendFilterContents::CreateForegroundPorterDuffBlend(
frame_info.texture_sampler_y_coord_scale =
dst_snapshot->texture->GetYCoordScale();
frag_info.color = foreground_color.Premultiply();
frag_info.input_alpha =
absorb_opacity ? dst_snapshot->opacity * alpha.value_or(1.0) : 1.0;
frag_info.output_alpha = 1.0;
auto blend_coefficients =
kPorterDuffCoefficients[static_cast<int>(blend_mode)];

View File

@ -4,11 +4,33 @@
#pragma once
#include <optional>
#include "impeller/entity/contents/filters/color_filter_contents.h"
#include "impeller/entity/contents/filters/inputs/filter_input.h"
#include "impeller/geometry/color.h"
namespace impeller {
constexpr std::array<std::array<Scalar, 5>, 15> kPorterDuffCoefficients = {{
{0, 0, 0, 0, 0}, // Clear
{1, 0, 0, 0, 0}, // Source
{0, 0, 1, 0, 0}, // Destination
{1, 0, 1, -1, 0}, // SourceOver
{1, -1, 1, 0, 0}, // DestinationOver
{0, 1, 0, 0, 0}, // SourceIn
{0, 0, 0, 1, 0}, // DestinationIn
{1, -1, 0, 0, 0}, // SourceOut
{0, 0, 1, -1, 0}, // DestinationOut
{0, 1, 1, -1, 0}, // SourceATop
{1, -1, 0, 1, 0}, // DestinationATop
{1, -1, 1, -1, 0}, // Xor
{1, 0, 1, 0, 0}, // Plus
{0, 0, 0, 0, 1}, // Modulate
{0, 0, 1, 0, -1}, // Screen
}};
std::optional<BlendMode> InvertPorterDuffBlend(BlendMode blend_mode);
class BlendFilterContents : public ColorFilterContents {
public:
using AdvancedBlendProc =

View File

@ -18,11 +18,12 @@ uniform FragInfo {
float16_t dst_coeff_src_alpha;
float16_t dst_coeff_src_color;
float16_t input_alpha;
f16vec4 color;
float16_t output_alpha;
}
frag_info;
in vec2 v_texture_coords;
in f16vec4 v_color;
out f16vec4 frag_color;
@ -38,9 +39,10 @@ f16vec4 Sample(f16sampler2D texture_sampler, vec2 texture_coords) {
void main() {
f16vec4 dst =
texture(texture_sampler_dst, v_texture_coords) * frag_info.input_alpha;
f16vec4 src = frag_info.color;
f16vec4 src = v_color;
frag_color =
src * (frag_info.src_coeff + dst.a * frag_info.src_coeff_dst_alpha) +
dst * (frag_info.dst_coeff + src.a * frag_info.dst_coeff_src_alpha +
src * frag_info.dst_coeff_src_color);
frag_color *= frag_info.output_alpha;
}

View File

@ -0,0 +1,26 @@
// 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.
#include <impeller/conversions.glsl>
#include <impeller/types.glsl>
uniform FrameInfo {
mat4 mvp;
float texture_sampler_y_coord_scale;
}
frame_info;
in vec2 vertices;
in vec2 texture_coords;
in vec4 color;
out vec2 v_texture_coords;
out f16vec4 v_color;
void main() {
gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0);
v_color = f16vec4(color);
v_texture_coords =
IPRemapCoords(texture_coords, frame_info.texture_sampler_y_coord_scale);
}

View File

@ -8963,7 +8963,7 @@
"core": "Mali-G78",
"filename": "flutter/impeller/entity/gles/porter_duff_blend.frag.gles",
"has_side_effects": false,
"has_uniform_computation": true,
"has_uniform_computation": false,
"modifies_coverage": false,
"reads_color_buffer": false,
"type": "Fragment",
@ -8971,20 +8971,19 @@
"uses_late_zs_update": false,
"variants": {
"Main": {
"fp16_arithmetic": 100,
"fp16_arithmetic": 71,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"varying",
"texture"
"varying"
],
"longest_path_cycles": [
0.09375,
0.09375,
0.21875,
0.21875,
0.03125,
0.0,
0.0,
0.25,
0.5,
0.25
],
"pipelines": [
@ -8997,35 +8996,33 @@
"texture"
],
"shortest_path_bound_pipelines": [
"varying",
"texture"
"varying"
],
"shortest_path_cycles": [
0.09375,
0.09375,
0.21875,
0.21875,
0.0,
0.0,
0.0,
0.25,
0.5,
0.25
],
"total_bound_pipelines": [
"varying",
"texture"
"varying"
],
"total_cycles": [
0.09375,
0.09375,
0.21875,
0.21875,
0.03125,
0.0,
0.0,
0.25,
0.5,
0.25
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 10,
"uniform_registers_used": 8,
"work_registers_used": 19
}
}
@ -9070,7 +9067,164 @@
]
},
"thread_occupancy": 100,
"uniform_registers_used": 2,
"uniform_registers_used": 1,
"work_registers_used": 2
}
}
}
},
"flutter/impeller/entity/gles/porter_duff_blend.vert.gles": {
"Mali-G78": {
"core": "Mali-G78",
"filename": "flutter/impeller/entity/gles/porter_duff_blend.vert.gles",
"has_uniform_computation": true,
"type": "Vertex",
"variants": {
"Position": {
"fp16_arithmetic": 0,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
0.140625,
0.140625,
0.0,
0.0,
2.0,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
0.140625,
0.140625,
0.0,
0.0,
2.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
0.140625,
0.140625,
0.0,
0.0,
2.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 22,
"work_registers_used": 32
},
"Varying": {
"fp16_arithmetic": 0,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
0.015625,
0.015625,
0.015625,
0.0,
5.0,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
0.015625,
0.015625,
0.015625,
0.0,
5.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
0.015625,
0.015625,
0.015625,
0.0,
5.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 10,
"work_registers_used": 12
}
}
},
"Mali-T880": {
"core": "Mali-T880",
"filename": "flutter/impeller/entity/gles/porter_duff_blend.vert.gles",
"has_uniform_computation": false,
"type": "Vertex",
"variants": {
"Main": {
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
2.9700000286102295,
7.0,
0.0
],
"pipelines": [
"arithmetic",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
2.9700000286102295,
7.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
3.0,
7.0,
0.0
]
},
"thread_occupancy": 100,
"uniform_registers_used": 6,
"work_registers_used": 2
}
}
@ -12298,20 +12452,19 @@
"uses_late_zs_update": false,
"variants": {
"Main": {
"fp16_arithmetic": 55,
"fp16_arithmetic": 100,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"varying",
"texture"
"varying"
],
"longest_path_cycles": [
0.140625,
0.140625,
0.21875,
0.21875,
0.0,
0.0,
0.0,
0.25,
0.5,
0.25
],
"pipelines": [
@ -12324,35 +12477,145 @@
"texture"
],
"shortest_path_bound_pipelines": [
"varying",
"texture"
"varying"
],
"shortest_path_cycles": [
0.140625,
0.140625,
0.21875,
0.21875,
0.0,
0.0,
0.0,
0.25,
0.5,
0.25
],
"total_bound_pipelines": [
"varying",
"texture"
"varying"
],
"total_cycles": [
0.140625,
0.140625,
0.21875,
0.21875,
0.0,
0.0,
0.0,
0.25,
0.5,
0.25
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 14,
"work_registers_used": 11
}
}
}
},
"flutter/impeller/entity/porter_duff_blend.vert.vkspv": {
"Mali-G78": {
"core": "Mali-G78",
"filename": "flutter/impeller/entity/porter_duff_blend.vert.vkspv",
"has_uniform_computation": true,
"type": "Vertex",
"variants": {
"Position": {
"fp16_arithmetic": 0,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
0.125,
0.125,
0.0,
0.0,
2.0,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
0.125,
0.125,
0.0,
0.0,
2.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
0.125,
0.125,
0.0,
0.0,
2.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 30,
"work_registers_used": 32
},
"Varying": {
"fp16_arithmetic": 0,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
0.015625,
0.015625,
0.015625,
0.0,
5.0,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
0.015625,
0.015625,
0.015625,
0.0,
5.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
0.015625,
0.015625,
0.015625,
0.0,
5.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 22,
"work_registers_used": 10
}
}