blob: 5d9dc691c8326075b60e9fbefeaba05279873edc [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/geometry/path_builder.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/sampler_library.h"
namespace impeller {
BlendFilterContents::BlendFilterContents() {
SetBlendMode(Entity::BlendMode::kSourceOver);
}
BlendFilterContents::~BlendFilterContents() = default;
using PipelineProc =
std::shared_ptr<Pipeline> (ContentContext::*)(ContentContextOptions) const;
template <typename TPipeline>
static bool AdvancedBlend(const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Rect& coverage,
std::optional<Color> foreground_color,
PipelineProc pipeline_proc) {
using VS = typename TPipeline::VertexShader;
using FS = typename TPipeline::FragmentShader;
const size_t total_inputs =
inputs.size() + (foreground_color.has_value() ? 1 : 0);
if (total_inputs < 2) {
return false;
}
auto dst_snapshot = inputs[0]->GetSnapshot(renderer, entity);
if (!dst_snapshot.has_value()) {
return true;
}
auto maybe_dst_uvs = dst_snapshot->GetCoverageUVs(coverage);
if (!maybe_dst_uvs.has_value()) {
return true;
}
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 true;
}
auto maybe_src_uvs = src_snapshot->GetCoverageUVs(coverage);
if (!maybe_src_uvs.has_value()) {
return true;
}
src_uvs = maybe_src_uvs.value();
}
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 = OptionsFromPassAndEntity(pass, entity);
std::shared_ptr<Pipeline> pipeline =
std::invoke(pipeline_proc, renderer, options);
Command cmd;
cmd.label = "Advanced Blend Filter";
cmd.BindVertices(vtx_buffer);
cmd.pipeline = std::move(pipeline);
auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({});
FS::BindTextureSamplerDst(cmd, dst_snapshot->texture, sampler);
typename FS::BlendInfo blend_info;
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);
}
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;
}
static bool PipelineBlend(const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Rect& coverage,
Entity::BlendMode pipeline_blend,
std::optional<Color> foreground_color) {
using VS = BlendPipeline::VertexShader;
using FS = BlendPipeline::FragmentShader;
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;
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(cmd, uniform_view);
pass.AddCommand(cmd);
return true;
};
// Draw the first texture using kSource.
options.blend_mode = Entity::BlendMode::kSource;
cmd.pipeline = renderer.GetBlendPipeline(options);
if (!add_blend_command(inputs[0]->GetSnapshot(renderer, entity))) {
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->SetPath(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;
}
#define BLEND_CASE(mode) \
case Entity::BlendMode::k##mode: \
advanced_blend_proc_ = \
[](const FilterInput::Vector& inputs, const ContentContext& renderer, \
const Entity& entity, RenderPass& pass, const Rect& coverage, \
std::optional<Color> fg_color) { \
PipelineProc p = &ContentContext::GetBlend##mode##Pipeline; \
return AdvancedBlend<BlendScreenPipeline>( \
inputs, renderer, entity, pass, coverage, fg_color, p); \
}; \
break;
void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) {
if (blend_mode > Entity::BlendMode::kLastAdvancedBlendMode) {
VALIDATION_LOG << "Invalid blend mode " << static_cast<int>(blend_mode)
<< " assigned to BlendFilterContents.";
}
blend_mode_ = blend_mode;
if (blend_mode > Entity::BlendMode::kLastPipelineBlendMode) {
static_assert(Entity::BlendMode::kLastAdvancedBlendMode ==
Entity::BlendMode::kLuminosity);
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;
}
bool BlendFilterContents::RenderFilter(const FilterInput::Vector& inputs,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Rect& coverage) const {
if (inputs.empty()) {
return true;
}
if (inputs.size() == 1 && !foreground_color_.has_value()) {
// Nothing to blend.
return PipelineBlend(inputs, renderer, entity, pass, coverage,
Entity::BlendMode::kSource, std::nullopt);
}
if (blend_mode_ <= Entity::BlendMode::kLastPipelineBlendMode) {
return PipelineBlend(inputs, renderer, entity, pass, coverage, blend_mode_,
foreground_color_);
}
if (blend_mode_ <= Entity::BlendMode::kLastAdvancedBlendMode) {
return advanced_blend_proc_(inputs, renderer, entity, pass, coverage,
foreground_color_);
}
FML_UNREACHABLE();
}
} // namespace impeller