[Impeller] add a generic porter duff blend foreground shader. (flutter/engine#41098)

The pipeline blend component of https://github.com/flutter/flutter/issues/124025 . This should also land alongside https://github.com/flutter/flutter/pull/124640 which was necessary before we had completely optimized the image filters for texture inputs.

Fixes https://github.com/flutter/flutter/issues/121650
Fixes https://github.com/flutter/flutter/issues/124025
This commit is contained in:
Jonah Williams
2023-04-15 15:00:48 -07:00
committed by GitHub
parent 6ec71f0b48
commit 03613be33d
12 changed files with 500 additions and 24 deletions

View File

@@ -1278,6 +1278,7 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_saturation.frag + ../../../flutter/LICENSE
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/border_mask_blur.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/border_mask_blur.vert + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/color_matrix_color_filter.frag + ../../../flutter/LICENSE
@@ -3867,6 +3868,7 @@ FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_ov
FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_saturation.frag
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/border_mask_blur.frag
FILE: ../../../flutter/impeller/entity/shaders/border_mask_blur.vert
FILE: ../../../flutter/impeller/entity/shaders/color_matrix_color_filter.frag

View File

@@ -2035,6 +2035,26 @@ TEST_P(AiksTest, TranslucentSaveLayerWithBlendImageFilterAndDrawsCorrectly) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, TranslucentSaveLayerWithColorImageFilterAndDrawsCorrectly) {
Canvas canvas;
canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
canvas.SaveLayer({
.color = Color::Black().WithAlpha(0.5),
.color_filter =
[](FilterInput::Ref input) {
return ColorFilterContents::MakeBlend(
BlendMode::kDestinationOver, {std::move(input)}, Color::Red());
},
});
canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, TranslucentSaveLayerImageDrawsCorrectly) {
Canvas canvas;

View File

@@ -59,7 +59,7 @@ std::shared_ptr<Contents> Paint::WithFilters(
std::shared_ptr<Contents> input,
std::optional<bool> is_solid_color) const {
bool is_solid_color_val = is_solid_color.value_or(!color_source);
input = WithColorFilter(input);
input = WithColorFilter(input, /*absorb_opacity=*/true);
input = WithInvertFilter(input);
input = WithMaskBlur(input, is_solid_color_val, Matrix());
input = WithImageFilter(input, Matrix(), /*is_subpass=*/false);

View File

@@ -70,6 +70,7 @@ impeller_shaders("entity_shaders") {
"shaders/vertices.frag",
"shaders/yuv_to_rgb_filter.frag",
"shaders/yuv_to_rgb_filter.vert",
"shaders/blending/porter_duff_blend.frag",
]
}

View File

@@ -287,6 +287,8 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
CreateDefaultPipeline<GeometryColorPipeline>(*context_);
yuv_to_rgb_filter_pipelines_[{}] =
CreateDefaultPipeline<YUVToRGBFilterPipeline>(*context_);
porter_duff_blend_pipelines_[{}] =
CreateDefaultPipeline<PorterDuffBlendPipeline>(*context_);
if (solid_fill_pipelines_[{}]->GetDescriptor().has_value()) {
auto clip_pipeline_descriptor =

View File

@@ -35,6 +35,7 @@
#include "impeller/entity/linear_to_srgb_filter.vert.h"
#include "impeller/entity/morphology_filter.frag.h"
#include "impeller/entity/morphology_filter.vert.h"
#include "impeller/entity/porter_duff_blend.frag.h"
#include "impeller/entity/radial_gradient_fill.frag.h"
#include "impeller/entity/rrect_blur.frag.h"
#include "impeller/entity/rrect_blur.vert.h"
@@ -125,7 +126,6 @@ using RadialGradientSSBOFillPipeline =
using SweepGradientSSBOFillPipeline =
RenderPipelineT<GradientFillVertexShader,
SweepGradientSsboFillFragmentShader>;
using BlendPipeline = RenderPipelineT<BlendVertexShader, BlendFragmentShader>;
using RRectBlurPipeline =
RenderPipelineT<RrectBlurVertexShader, RrectBlurFragmentShader>;
using BlendPipeline = RenderPipelineT<BlendVertexShader, BlendFragmentShader>;
@@ -165,6 +165,8 @@ using GlyphAtlasPipeline =
RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
using GlyphAtlasSdfPipeline =
RenderPipelineT<GlyphAtlasSdfVertexShader, GlyphAtlasSdfFragmentShader>;
using PorterDuffBlendPipeline =
RenderPipelineT<BlendVertexShader, 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 =
@@ -469,6 +471,11 @@ class ContentContext {
return GetPipeline(yuv_to_rgb_filter_pipelines_, opts);
}
std::shared_ptr<Pipeline<PipelineDescriptor>> GetPorterDuffBlendPipeline(
ContentContextOptions opts) const {
return GetPipeline(porter_duff_blend_pipelines_, opts);
}
// Advanced blends.
std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendColorPipeline(
@@ -705,6 +712,7 @@ class ContentContext {
mutable Variants<GlyphAtlasSdfPipeline> glyph_atlas_sdf_pipelines_;
mutable Variants<GeometryColorPipeline> geometry_color_pipelines_;
mutable Variants<YUVToRGBFilterPipeline> yuv_to_rgb_filter_pipelines_;
mutable Variants<PorterDuffBlendPipeline> porter_duff_blend_pipelines_;
// Advanced blends.
mutable Variants<BlendColorPipeline> blend_color_pipelines_;
mutable Variants<BlendColorBurnPipeline> blend_colorburn_pipelines_;

View File

@@ -179,7 +179,7 @@ static std::optional<Entity> AdvancedBlend(
entity.GetBlendMode(), entity.GetStencilDepth());
}
std::optional<Entity> BlendFilterContents::CreateForegroundBlend(
std::optional<Entity> BlendFilterContents::CreateForegroundAdvancedBlend(
const std::shared_ptr<FilterInput>& input,
const ContentContext& renderer,
const Entity& entity,
@@ -318,21 +318,145 @@ std::optional<Entity> BlendFilterContents::CreateForegroundBlend(
auto contents = AnonymousContents::Make(render_proc, coverage_proc);
// If there is pending opacity but it was not absorbed by this entity, we have
// to convert this back to a snapshot so it can be passed on. This generally
// implies that there is another filter about to run, so we'd perform this
// operation anyway.
auto potential_opacity = alpha.value_or(1.0) * dst_snapshot->opacity;
if (!absorb_opacity && potential_opacity < 1.0) {
auto result_snapshot = contents->RenderToSnapshot(renderer, entity);
if (!result_snapshot.has_value()) {
return std::nullopt;
}
result_snapshot->opacity = potential_opacity;
return Entity::FromSnapshot(result_snapshot.value(), entity.GetBlendMode(),
Entity sub_entity;
sub_entity.SetContents(std::move(contents));
sub_entity.SetStencilDepth(entity.GetStencilDepth());
sub_entity.SetTransformation(entity.GetTransformation());
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,
const Entity& entity,
const Rect& coverage,
Color foreground_color,
BlendMode blend_mode,
std::optional<Scalar> alpha,
bool absorb_opacity) const {
auto dst_snapshot = input->GetSnapshot(renderer, entity);
if (!dst_snapshot.has_value()) {
return std::nullopt;
}
if (blend_mode == BlendMode::kClear) {
return std::nullopt;
}
if (blend_mode == BlendMode::kDestination) {
return Entity::FromSnapshot(dst_snapshot, entity.GetBlendMode(),
entity.GetStencilDepth());
}
if (blend_mode == BlendMode::kSource) {
auto contents = std::make_shared<SolidColorContents>();
contents->SetGeometry(Geometry::MakeRect(coverage));
contents->SetColor(foreground_color);
Entity foreground_entity;
foreground_entity.SetBlendMode(entity.GetBlendMode());
foreground_entity.SetStencilDepth(entity.GetStencilDepth());
foreground_entity.SetContents(std::move(contents));
return foreground_entity;
}
RenderProc render_proc = [foreground_color, coverage, dst_snapshot,
blend_mode, absorb_opacity, alpha](
const ContentContext& renderer,
const Entity& entity, RenderPass& pass) -> bool {
using VS = PorterDuffBlendPipeline::VertexShader;
using FS = PorterDuffBlendPipeline::FragmentShader;
auto& host_buffer = pass.GetTransientsBuffer();
auto maybe_dst_uvs = dst_snapshot->GetCoverageUVs(coverage);
if (!maybe_dst_uvs.has_value()) {
return false;
}
auto dst_uvs = maybe_dst_uvs.value();
auto size = coverage.size;
auto origin = coverage.origin;
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]},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);
Command cmd;
cmd.label = "Foreground PorterDuff Blend Filter";
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 = dst_snapshot->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, dst_snapshot->texture, dst_sampler);
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;
auto blend_coefficients =
kPorterDuffCoefficients[static_cast<int>(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());
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(cmd, uniform_view);
return pass.AddCommand(cmd);
};
CoverageProc coverage_proc =
[coverage](const Entity& entity) -> std::optional<Rect> {
return coverage;
};
auto contents = AnonymousContents::Make(render_proc, coverage_proc);
Entity sub_entity;
sub_entity.SetContents(std::move(contents));
sub_entity.SetStencilDepth(entity.GetStencilDepth());
@@ -531,17 +655,23 @@ std::optional<Entity> BlendFilterContents::RenderFilter(
}
if (blend_mode_ <= Entity::kLastPipelineBlendMode) {
if (inputs.size() == 1 && foreground_color_.has_value() &&
GetAbsorbOpacity()) {
return CreateForegroundPorterDuffBlend(
inputs[0], renderer, entity, coverage, foreground_color_.value(),
blend_mode_, GetAlpha(), GetAbsorbOpacity());
}
return PipelineBlend(inputs, renderer, entity, coverage, blend_mode_,
foreground_color_, GetAbsorbOpacity(), GetAlpha());
}
if (blend_mode_ <= Entity::kLastAdvancedBlendMode) {
if (inputs.size() == 1 && foreground_color_.has_value()) {
return CreateForegroundBlend(inputs[0], renderer, entity, coverage,
foreground_color_.value(), blend_mode_,
GetAlpha(), GetAbsorbOpacity());
if (inputs.size() == 1 && foreground_color_.has_value() &&
GetAbsorbOpacity()) {
return CreateForegroundAdvancedBlend(
inputs[0], renderer, entity, coverage, foreground_color_.value(),
blend_mode_, GetAlpha(), GetAbsorbOpacity());
}
return advanced_blend_proc_(inputs, renderer, entity, coverage,
foreground_color_, GetAbsorbOpacity(),
GetAlpha());

View File

@@ -42,7 +42,21 @@ class BlendFilterContents : public ColorFilterContents {
/// only a single input and a foreground color.
///
/// These contents cannot absorb opacity.
std::optional<Entity> CreateForegroundBlend(
std::optional<Entity> CreateForegroundAdvancedBlend(
const std::shared_ptr<FilterInput>& input,
const ContentContext& renderer,
const Entity& entity,
const Rect& coverage,
Color foreground_color,
BlendMode blend_mode,
std::optional<Scalar> alpha,
bool absorb_opacity) const;
/// @brief Optimized porter-duff blend that avoids a second subpass when there
/// is only a single input and a foreground color.
///
/// These contents cannot absorb opacity.
std::optional<Entity> CreateForegroundPorterDuffBlend(
const std::shared_ptr<FilterInput>& input,
const ContentContext& renderer,
const Entity& entity,

View File

@@ -88,8 +88,8 @@ SamplerDescriptor TiledTextureContents::CreateDescriptor(
bool TiledTextureContents::UsesEmulatedTileMode(
const Capabilities& capabilities) const {
return TileModeToAddressMode(x_tile_mode_, capabilities).has_value() &&
TileModeToAddressMode(y_tile_mode_, capabilities).has_value();
return !TileModeToAddressMode(x_tile_mode_, capabilities).has_value() ||
!TileModeToAddressMode(y_tile_mode_, capabilities).has_value();
}
bool TiledTextureContents::Render(const ContentContext& renderer,
@@ -121,7 +121,7 @@ bool TiledTextureContents::Render(const ContentContext& renderer,
frame_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale();
Command cmd;
cmd.label = "TiledTextureFill";
cmd.label = uses_emulated_tile_mode ? "TiledTextureFill" : "TextureFill";
cmd.stencil_reference = entity.GetStencilDepth();
auto options = OptionsFromPassAndEntity(pass, entity);

View File

@@ -2467,6 +2467,70 @@ TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) {
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) {
auto image = CreateTextureForFixture("boston.jpg");
auto filter = ColorFilterContents::MakeBlend(
BlendMode::kClear, FilterInput::Make({image}), Color::Red());
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
Entity entity;
entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
Matrix::MakeTranslation({500, 300}) *
Matrix::MakeScale(Vector2{0.5, 0.5}));
entity.SetContents(filter);
return entity.Render(context, pass);
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) {
auto image = CreateTextureForFixture("boston.jpg");
auto filter = ColorFilterContents::MakeBlend(
BlendMode::kSource, FilterInput::Make({image}), Color::Red());
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
Entity entity;
entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
Matrix::MakeTranslation({500, 300}) *
Matrix::MakeScale(Vector2{0.5, 0.5}));
entity.SetContents(filter);
return entity.Render(context, pass);
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) {
auto image = CreateTextureForFixture("boston.jpg");
auto filter = ColorFilterContents::MakeBlend(
BlendMode::kDestination, FilterInput::Make({image}), Color::Red());
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
Entity entity;
entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
Matrix::MakeTranslation({500, 300}) *
Matrix::MakeScale(Vector2{0.5, 0.5}));
entity.SetContents(filter);
return entity.Render(context, pass);
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) {
auto image = CreateTextureForFixture("boston.jpg");
auto filter = ColorFilterContents::MakeBlend(
BlendMode::kSourceIn, FilterInput::Make({image}), Color::Red());
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
Entity entity;
entity.SetTransformation(Matrix::MakeScale(GetContentScale()) *
Matrix::MakeTranslation({500, 300}) *
Matrix::MakeScale(Vector2{0.5, 0.5}));
entity.SetContents(filter);
return entity.Render(context, pass);
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) {
auto arrow_head = PathBuilder{}
.MoveTo({50, 120})

View File

@@ -0,0 +1,44 @@
// 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/blending.glsl>
#include <impeller/color.glsl>
#include <impeller/texture.glsl>
#include <impeller/types.glsl>
uniform f16sampler2D texture_sampler_dst;
uniform FragInfo {
float16_t src_coeff;
float16_t src_coeff_dst_alpha;
float16_t dst_coeff;
float16_t dst_coeff_src_alpha;
float16_t dst_coeff_src_color;
float16_t input_alpha;
f16vec4 color;
}
frag_info;
in vec2 v_texture_coords;
out f16vec4 frag_color;
f16vec4 Sample(f16sampler2D texture_sampler, vec2 texture_coords) {
// gles 2.0 is the only backend without native decal support.
#ifdef IMPELLER_TARGET_OPENGLES
return IPSampleDecal(texture_sampler, texture_coords);
#else
return texture(texture_sampler, texture_coords);
#endif
}
void main() {
f16vec4 dst =
texture(texture_sampler_dst, v_texture_coords) * frag_info.input_alpha;
f16vec4 src = frag_info.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);
}

View File

@@ -8442,6 +8442,124 @@
}
}
},
"flutter/impeller/entity/gles/porter_duff_blend.frag.gles": {
"Mali-G78": {
"core": "Mali-G78",
"filename": "flutter/impeller/entity/gles/porter_duff_blend.frag.gles",
"has_side_effects": false,
"has_uniform_computation": true,
"modifies_coverage": false,
"reads_color_buffer": false,
"type": "Fragment",
"uses_late_zs_test": false,
"uses_late_zs_update": false,
"variants": {
"Main": {
"fp16_arithmetic": 100,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"varying",
"texture"
],
"longest_path_cycles": [
0.09375,
0.09375,
0.03125,
0.0,
0.0,
0.25,
0.25
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"varying",
"texture"
],
"shortest_path_bound_pipelines": [
"varying",
"texture"
],
"shortest_path_cycles": [
0.09375,
0.09375,
0.0,
0.0,
0.0,
0.25,
0.25
],
"total_bound_pipelines": [
"varying",
"texture"
],
"total_cycles": [
0.09375,
0.09375,
0.03125,
0.0,
0.0,
0.25,
0.25
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 10,
"work_registers_used": 19
}
}
},
"Mali-T880": {
"core": "Mali-T880",
"filename": "flutter/impeller/entity/gles/porter_duff_blend.frag.gles",
"has_uniform_computation": false,
"type": "Fragment",
"variants": {
"Main": {
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"arithmetic"
],
"longest_path_cycles": [
1.649999976158142,
1.0,
1.0
],
"pipelines": [
"arithmetic",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"arithmetic"
],
"shortest_path_cycles": [
1.649999976158142,
1.0,
1.0
],
"total_bound_pipelines": [
"arithmetic"
],
"total_cycles": [
2.0,
1.0,
1.0
]
},
"thread_occupancy": 100,
"uniform_registers_used": 2,
"work_registers_used": 2
}
}
}
},
"flutter/impeller/entity/gles/position_color.vert.gles": {
"Mali-G78": {
"core": "Mali-G78",
@@ -11857,6 +11975,79 @@
}
}
},
"flutter/impeller/entity/porter_duff_blend.frag.vkspv": {
"Mali-G78": {
"core": "Mali-G78",
"filename": "flutter/impeller/entity/porter_duff_blend.frag.vkspv",
"has_side_effects": false,
"has_uniform_computation": true,
"modifies_coverage": false,
"reads_color_buffer": false,
"type": "Fragment",
"uses_late_zs_test": false,
"uses_late_zs_update": false,
"variants": {
"Main": {
"fp16_arithmetic": 55,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"varying",
"texture"
],
"longest_path_cycles": [
0.140625,
0.140625,
0.0,
0.0,
0.0,
0.25,
0.25
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"varying",
"texture"
],
"shortest_path_bound_pipelines": [
"varying",
"texture"
],
"shortest_path_cycles": [
0.140625,
0.140625,
0.0,
0.0,
0.0,
0.25,
0.25
],
"total_bound_pipelines": [
"varying",
"texture"
],
"total_cycles": [
0.140625,
0.140625,
0.0,
0.0,
0.0,
0.25,
0.25
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 14,
"work_registers_used": 10
}
}
}
},
"flutter/impeller/entity/position_color.vert.vkspv": {
"Mali-G78": {
"core": "Mali-G78",