Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 6 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
152 changes: 152 additions & 0 deletions impeller/entity/contents/filters/blend_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <optional>

#include "impeller/core/formats.h"
#include "impeller/entity/contents/anonymous_contents.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/contents.h"
#include "impeller/entity/contents/filters/inputs/filter_input.h"
Expand Down Expand Up @@ -178,6 +179,150 @@ static std::optional<Entity> AdvancedBlend(
entity.GetBlendMode(), entity.GetStencilDepth());
}

Entity BlendFilterContents::CreateForgroundBlend(
const std::shared_ptr<FilterInput>& input,
const ContentContext& renderer,
const Entity& entity,
const Rect& coverage,
Color foreground_color,
BlendMode blend_mode) const {
RenderProc render_proc = [foreground_color, coverage, input, blend_mode](
const ContentContext& renderer,
const Entity& entity, RenderPass& pass) -> bool {
using VS = BlendScreenPipeline::VertexShader;
using FS = BlendScreenPipeline::FragmentShader;

auto& host_buffer = pass.GetTransientsBuffer();

auto dst_snapshot = input->GetSnapshot(renderer, entity);
if (!dst_snapshot.has_value()) {
return false;
}

// This does not use the snapshot transform, since this value will
// be different depending on whether the filter was applied via a
// saveLayer or directly to a particular contents. By construction,
// the provided rectangle should exactly map to the texture coordinates.

@bdero bdero Apr 6, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transform of the snapshot needs to either be absorbed or deferred, otherwise this won't render correctly when the FilterInput decides to defer the transform and returns a non-identity matrix (TextureFilterInput or ContentsFilterInput holding a TextureContents sometimes defer their transforms to avoid unnecessary extra passes).

You can either absorb the snapshot transform by applying it to the positions (take the snapshot texture bounds and multiply by the transform) or draw the coverage rectangle and transform the texture coordinates instead (GetCoverageUVs computes the correctly transformed UVs for this).

Coverage rects and snapshot transforms computed by FilterInputs are already in screen space relative to the EntityPass texture position, and so the only difference between rendering into an inline subpass (current behavior of the blend filter) vs Contents::Render is that stuff rendered in a subpass needs to be shifted by the negative coverage origin (assuming the texture size is optimal), and then shifted back by pre-translating the transform in the returned Entity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I took another look at this and found that I was more or less applying the origin offset twice since I was still appending the transform shift to the entity transform - which explained the difference.

This now uses getSnapshotUVs and renders correctly in both scenarios.

std::array<Point, 4> dst_uvs = {Point(0.0, 0.0), Point(1.0, 0.0),
Point(0.0, 1.0), Point(1.0, 1.0)};

auto size = coverage.size;
auto origin = coverage.origin;
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{origin, dst_uvs[0], dst_uvs[0]},
{Point(origin.x + size.width, origin.y), dst_uvs[1], dst_uvs[1]},
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3],
dst_uvs[3]},
{origin, dst_uvs[0], dst_uvs[0]},
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3],
dst_uvs[3]},
{Point(origin.x, origin.y + size.height), dst_uvs[2], dst_uvs[2]},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);

Command cmd;
cmd.label = "Foreground Advanced Blend Filter";
cmd.BindVertices(vtx_buffer);
cmd.stencil_reference = entity.GetStencilDepth();
auto options = OptionsFromPass(pass);

switch (blend_mode) {
case BlendMode::kScreen:
cmd.pipeline = renderer.GetBlendScreenPipeline(options);
break;
case BlendMode::kOverlay:
cmd.pipeline = renderer.GetBlendOverlayPipeline(options);
break;
case BlendMode::kDarken:
cmd.pipeline = renderer.GetBlendDarkenPipeline(options);
break;
case BlendMode::kLighten:
cmd.pipeline = renderer.GetBlendLightenPipeline(options);
break;
case BlendMode::kColorDodge:
cmd.pipeline = renderer.GetBlendColorDodgePipeline(options);
break;
case BlendMode::kColorBurn:
cmd.pipeline = renderer.GetBlendColorBurnPipeline(options);
break;
case BlendMode::kHardLight:
cmd.pipeline = renderer.GetBlendHardLightPipeline(options);
break;
case BlendMode::kSoftLight:
cmd.pipeline = renderer.GetBlendSoftLightPipeline(options);
break;
case BlendMode::kDifference:
cmd.pipeline = renderer.GetBlendDifferencePipeline(options);
break;
case BlendMode::kExclusion:
cmd.pipeline = renderer.GetBlendExclusionPipeline(options);
break;
case BlendMode::kMultiply:
cmd.pipeline = renderer.GetBlendMultiplyPipeline(options);
break;
case BlendMode::kHue:
cmd.pipeline = renderer.GetBlendHuePipeline(options);
break;
case BlendMode::kSaturation:
cmd.pipeline = renderer.GetBlendSaturationPipeline(options);
break;
case BlendMode::kColor:
cmd.pipeline = renderer.GetBlendColorPipeline(options);
break;
case BlendMode::kLuminosity:
cmd.pipeline = renderer.GetBlendLuminosityPipeline(options);
break;
default:
return false;
}

FS::BlendInfo blend_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.dst_y_coord_scale = dst_snapshot->texture->GetYCoordScale();
blend_info.dst_input_alpha = dst_snapshot->opacity;

blend_info.color_factor = 1;
blend_info.color = foreground_color;
// This texture will not be sampled from due to the color factor. But
// this is present so that validation doesn't trip on a missing
// binding.
FS::BindTextureSamplerSrc(cmd, dst_snapshot->texture, dst_sampler);

auto blend_uniform = host_buffer.EmplaceUniform(blend_info);
FS::BindBlendInfo(cmd, blend_uniform);

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());
sub_entity.SetTransformation(Matrix::MakeTranslation(coverage.origin) *
entity.GetTransformation());
return sub_entity;
}

static std::optional<Entity> PipelineBlend(
const FilterInput::Vector& inputs,
const ContentContext& renderer,
Expand Down Expand Up @@ -373,6 +518,13 @@ std::optional<Entity> BlendFilterContents::RenderFilter(
}

if (blend_mode_ <= Entity::kLastAdvancedBlendMode) {
auto potential_alpha = GetAlpha().value_or(1.0);
if (inputs.size() == 1 && foreground_color_.has_value() &&
potential_alpha >= 1.0 - kEhCloseEnough) {
return CreateForgroundBlend(inputs[0], renderer, entity, coverage,
foreground_color_.value(), blend_mode_);
}

return advanced_blend_proc_(inputs, renderer, entity, coverage,
foreground_color_, GetAbsorbOpacity(),
GetAlpha());
Expand Down
11 changes: 11 additions & 0 deletions impeller/entity/contents/filters/blend_filter_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ class BlendFilterContents : public ColorFilterContents {
const Matrix& effect_transform,
const Rect& coverage) const override;

/// @brief Optimized advanced blend that avoids a second subpass when there is
/// only a single input and a foreground color.
///
/// These contents cannot absorb opacity.
Entity CreateForgroundBlend(const std::shared_ptr<FilterInput>& input,
const ContentContext& renderer,
const Entity& entity,
const Rect& coverage,
Color foreground_color,
BlendMode blend_mode) const;

BlendMode blend_mode_ = BlendMode::kSourceOver;
AdvancedBlendProc advanced_blend_proc_;
std::optional<Color> foreground_color_;
Expand Down