blob: 687d19f4012b576059e9b41a8c36ece4fee61a45 [file] [log] [blame]
// 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/entity/contents/filters/blend_filter_contents.h"
#include <array>
#include <memory>
#include <optional>
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/contents.h"
#include "impeller/entity/contents/filters/inputs/filter_input.h"
#include "impeller/entity/contents/solid_color_contents.h"
#include "impeller/entity/entity.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/sampler_library.h"
namespace impeller {
BlendFilterContents::BlendFilterContents() {
SetBlendMode(BlendMode::kSourceOver);
}
BlendFilterContents::~BlendFilterContents() = default;
using PipelineProc = std::shared_ptr<Pipeline<PipelineDescriptor>> (
ContentContext::*)(ContentContextOptions) const;
template <typename TPipeline>
static std::optional<Snapshot> AdvancedBlend(
const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
const Rect& coverage,
std::optional<Color> foreground_color,
bool absorb_opacity,
PipelineProc pipeline_proc) {
using VS = typename TPipeline::VertexShader;
using FS = typename TPipeline::FragmentShader;
//----------------------------------------------------------------------------
/// Handle inputs.
///
const size_t total_inputs =
inputs.size() + (foreground_color.has_value() ? 1 : 0);
if (total_inputs < 2) {
return std::nullopt;
}
auto dst_snapshot = inputs[0]->GetSnapshot(renderer, entity);
if (!dst_snapshot.has_value()) {
return std::nullopt;
}
auto maybe_dst_uvs = dst_snapshot->GetCoverageUVs(coverage);
if (!maybe_dst_uvs.has_value()) {
return std::nullopt;
}
auto dst_uvs = maybe_dst_uvs.value();
std::optional<Snapshot> src_snapshot;
std::array<Point, 4> src_uvs;
if (!foreground_color.has_value()) {
src_snapshot = inputs[1]->GetSnapshot(renderer, entity);
if (!src_snapshot.has_value()) {
return dst_snapshot;
}
auto maybe_src_uvs = src_snapshot->GetCoverageUVs(coverage);
if (!maybe_src_uvs.has_value()) {
return dst_snapshot;
}
src_uvs = maybe_src_uvs.value();
}
//----------------------------------------------------------------------------
/// Render to texture.
///
ContentContext::SubpassCallback callback = [&](const ContentContext& renderer,
RenderPass& pass) {
auto& host_buffer = pass.GetTransientsBuffer();
auto size = pass.GetRenderTargetSize();
VertexBufferBuilder<typename VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{Point(0, 0), dst_uvs[0], src_uvs[0]},
{Point(size.width, 0), dst_uvs[1], src_uvs[1]},
{Point(size.width, size.height), dst_uvs[3], src_uvs[3]},
{Point(0, 0), dst_uvs[0], src_uvs[0]},
{Point(size.width, size.height), dst_uvs[3], src_uvs[3]},
{Point(0, size.height), dst_uvs[2], src_uvs[2]},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);
auto options = OptionsFromPass(pass);
options.blend_mode = BlendMode::kSource;
std::shared_ptr<Pipeline<PipelineDescriptor>> pipeline =
std::invoke(pipeline_proc, renderer, options);
Command cmd;
cmd.label = "Advanced Blend Filter";
cmd.BindVertices(vtx_buffer);
cmd.pipeline = std::move(pipeline);
typename FS::BlendInfo blend_info;
auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({});
FS::BindTextureSamplerDst(cmd, dst_snapshot->texture, sampler);
blend_info.dst_y_coord_scale = dst_snapshot->texture->GetYCoordScale();
blend_info.dst_input_alpha = absorb_opacity ? dst_snapshot->opacity : 1.0f;
if (foreground_color.has_value()) {
blend_info.color_factor = 1;
blend_info.color = foreground_color.value();
// 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, sampler);
} else {
blend_info.color_factor = 0;
FS::BindTextureSamplerSrc(cmd, src_snapshot->texture, sampler);
blend_info.src_y_coord_scale = src_snapshot->texture->GetYCoordScale();
}
auto blend_uniform = host_buffer.EmplaceUniform(blend_info);
FS::BindBlendInfo(cmd, blend_uniform);
typename VS::FrameInfo frame_info;
frame_info.mvp = Matrix::MakeOrthographic(size);
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(cmd, uniform_view);
pass.AddCommand(cmd);
return true;
};
auto out_texture = renderer.MakeSubpass(ISize(coverage.size), callback);
if (!out_texture) {
return std::nullopt;
}
out_texture->SetLabel("Advanced Blend Filter Texture");
return Snapshot{.texture = out_texture,
.transform = Matrix::MakeTranslation(coverage.origin),
.sampler_descriptor = dst_snapshot->sampler_descriptor,
.opacity = absorb_opacity ? 1.0f : dst_snapshot->opacity};
}
static std::optional<Snapshot> PipelineBlend(
const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
const Rect& coverage,
BlendMode pipeline_blend,
std::optional<Color> foreground_color,
bool absorb_opacity) {
using VS = BlendPipeline::VertexShader;
using FS = BlendPipeline::FragmentShader;
auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity);
ContentContext::SubpassCallback callback = [&](const ContentContext& renderer,
RenderPass& pass) {
auto& host_buffer = pass.GetTransientsBuffer();
auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({});
Command cmd;
cmd.label = "Pipeline Blend Filter";
auto options = OptionsFromPass(pass);
auto add_blend_command = [&](std::optional<Snapshot> input) {
if (!input.has_value()) {
return false;
}
auto input_coverage = input->GetCoverage();
if (!input_coverage.has_value()) {
return false;
}
FS::BindTextureSamplerSrc(cmd, input->texture, sampler);
auto size = input->texture->GetSize();
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{Point(0, 0), Point(0, 0)},
{Point(size.width, 0), Point(1, 0)},
{Point(size.width, size.height), Point(1, 1)},
{Point(0, 0), Point(0, 0)},
{Point(size.width, size.height), Point(1, 1)},
{Point(0, size.height), Point(0, 1)},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);
cmd.BindVertices(vtx_buffer);
VS::FrameInfo frame_info;
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
Matrix::MakeTranslation(-coverage.origin) *
input->transform;
FS::FragInfo frag_info;
frag_info.texture_sampler_y_coord_scale =
input->texture->GetYCoordScale();
frag_info.input_alpha = absorb_opacity ? input->opacity : 1.0f;
FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info));
VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info));
pass.AddCommand(cmd);
return true;
};
// Draw the first texture using kSource.
options.blend_mode = BlendMode::kSource;
cmd.pipeline = renderer.GetBlendPipeline(options);
if (!add_blend_command(input_snapshot)) {
return true;
}
// Write subsequent textures using the selected blend mode.
if (inputs.size() >= 2) {
options.blend_mode = pipeline_blend;
cmd.pipeline = renderer.GetBlendPipeline(options);
for (auto texture_i = inputs.begin() + 1; texture_i < inputs.end();
texture_i++) {
auto input = texture_i->get()->GetSnapshot(renderer, entity);
if (!add_blend_command(input)) {
return true;
}
}
}
// If a foreground color is set, blend it in.
if (foreground_color.has_value()) {
auto contents = std::make_shared<SolidColorContents>();
contents->SetGeometry(Geometry::MakeFillPath(
PathBuilder{}
.AddRect(Rect::MakeSize(pass.GetRenderTargetSize()))
.TakePath()));
contents->SetColor(foreground_color.value());
Entity foreground_entity;
foreground_entity.SetBlendMode(pipeline_blend);
foreground_entity.SetContents(contents);
if (!foreground_entity.Render(renderer, pass)) {
return false;
}
}
return true;
};
auto out_texture = renderer.MakeSubpass(ISize(coverage.size), callback);
if (!out_texture) {
return std::nullopt;
}
out_texture->SetLabel("Pipeline Blend Filter Texture");
return Snapshot{
.texture = out_texture,
.transform = Matrix::MakeTranslation(coverage.origin),
.sampler_descriptor =
inputs[0]->GetSnapshot(renderer, entity)->sampler_descriptor,
.opacity = absorb_opacity ? 1.0f : input_snapshot->opacity};
}
#define BLEND_CASE(mode) \
case BlendMode::k##mode: \
advanced_blend_proc_ = [](const FilterInput::Vector& inputs, \
const ContentContext& renderer, \
const Entity& entity, const Rect& coverage, \
std::optional<Color> fg_color, \
bool absorb_opacity) { \
PipelineProc p = &ContentContext::GetBlend##mode##Pipeline; \
return AdvancedBlend<BlendScreenPipeline>( \
inputs, renderer, entity, coverage, fg_color, absorb_opacity, p); \
}; \
break;
void BlendFilterContents::SetBlendMode(BlendMode blend_mode) {
if (blend_mode > Entity::kLastAdvancedBlendMode) {
VALIDATION_LOG << "Invalid blend mode " << static_cast<int>(blend_mode)
<< " assigned to BlendFilterContents.";
}
blend_mode_ = blend_mode;
if (blend_mode > Entity::kLastPipelineBlendMode) {
switch (blend_mode) {
BLEND_CASE(Screen)
BLEND_CASE(Overlay)
BLEND_CASE(Darken)
BLEND_CASE(Lighten)
BLEND_CASE(ColorDodge)
BLEND_CASE(ColorBurn)
BLEND_CASE(HardLight)
BLEND_CASE(SoftLight)
BLEND_CASE(Difference)
BLEND_CASE(Exclusion)
BLEND_CASE(Multiply)
BLEND_CASE(Hue)
BLEND_CASE(Saturation)
BLEND_CASE(Color)
BLEND_CASE(Luminosity)
default:
FML_UNREACHABLE();
}
}
}
void BlendFilterContents::SetForegroundColor(std::optional<Color> color) {
foreground_color_ = color;
}
std::optional<Snapshot> BlendFilterContents::RenderFilter(
const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
const Matrix& effect_transform,
const Rect& coverage) const {
if (inputs.empty()) {
return std::nullopt;
}
if (inputs.size() == 1 && !foreground_color_.has_value()) {
// Nothing to blend.
return PipelineBlend(inputs, renderer, entity, coverage, BlendMode::kSource,
std::nullopt, GetAbsorbOpacity());
}
if (blend_mode_ <= Entity::kLastPipelineBlendMode) {
return PipelineBlend(inputs, renderer, entity, coverage, blend_mode_,
foreground_color_, GetAbsorbOpacity());
}
if (blend_mode_ <= Entity::kLastAdvancedBlendMode) {
return advanced_blend_proc_(inputs, renderer, entity, coverage,
foreground_color_, GetAbsorbOpacity());
}
FML_UNREACHABLE();
}
} // namespace impeller