Skip to content

Commit

Permalink
Chainable texture filters (flutter#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent 1828c4c commit b3cbf05
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 5 deletions.
3 changes: 2 additions & 1 deletion impeller/entity/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ impeller_component("entity") {
"contents/content_context.h",
"contents/contents.cc",
"contents/contents.h",
"contents/filter_contents.cc",
"contents/filter_contents.h",
"contents/linear_gradient_contents.cc",
"contents/linear_gradient_contents.h",
"contents/solid_color_contents.cc",
Expand All @@ -55,7 +57,6 @@ impeller_component("entity") {
"../typographer",
]


deps = [
"//flutter/fml",

Expand Down
6 changes: 6 additions & 0 deletions impeller/entity/contents/content_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ class ContentContext {
color0.src_alpha_blend_factor = BlendFactor::kOneMinusDestinationAlpha;
color0.src_color_blend_factor = BlendFactor::kOneMinusDestinationAlpha;
break;
case Entity::BlendMode::kPlus:
color0.dst_alpha_blend_factor = BlendFactor::kOneMinusSourceAlpha;
color0.dst_color_blend_factor = BlendFactor::kOne;
color0.src_alpha_blend_factor = BlendFactor::kSourceAlpha;
color0.src_color_blend_factor = BlendFactor::kOne;
break;
}
desc.SetColorAttachmentDescriptor(0u, std::move(color0));
}
Expand Down
203 changes: 203 additions & 0 deletions impeller/entity/contents/filter_contents.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// 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 "filter_contents.h"

#include <memory>
#include <optional>
#include <variant>

#include "flutter/fml/logging.h"
#include "impeller/base/validation.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/solid_color_contents.h"
#include "impeller/entity/contents/texture_contents.h"
#include "impeller/entity/entity.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/renderer/command_buffer.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/sampler_library.h"

namespace impeller {

/*******************************************************************************
******* FilterContents
******************************************************************************/

std::shared_ptr<FilterContents> FilterContents::MakeBlend(
Entity::BlendMode blend_mode,
InputTextures input_textures) {
auto blend = std::make_shared<BlendFilterContents>();
blend->SetInputTextures(input_textures);
blend->SetBlendMode(blend_mode);
return blend;
}

FilterContents::FilterContents() = default;

FilterContents::~FilterContents() = default;

void FilterContents::SetInputTextures(InputTextures input_textures) {
input_textures_ = std::move(input_textures);
}

bool FilterContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
// Run the filter.

auto maybe_texture = RenderFilterToTexture(renderer, entity, pass);
if (!maybe_texture.has_value()) {
return false;
}
auto& texture = maybe_texture.value();

// Draw the resulting texture to the given destination rect, respecting the
// transform and clip stack.

auto contents = std::make_shared<TextureContents>();
contents->SetTexture(texture);
contents->SetSourceRect(IRect::MakeSize(texture->GetSize()));

return contents->Render(renderer, entity, pass);
}

std::optional<std::shared_ptr<Texture>> FilterContents::RenderFilterToTexture(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
auto output_size = GetOutputSize();
if (output_size.IsZero()) {
return std::nullopt;
}

// Resolve all inputs as textures.

std::vector<std::shared_ptr<Texture>> input_textures;
input_textures.reserve(input_textures_.size());
for (const auto& input : input_textures_) {
if (auto filter = std::get_if<std::shared_ptr<FilterContents>>(&input)) {
auto texture =
filter->get()->RenderFilterToTexture(renderer, entity, pass);
if (!texture.has_value()) {
return std::nullopt;
}
input_textures.push_back(std::move(texture.value()));
} else if (auto texture = std::get_if<std::shared_ptr<Texture>>(&input)) {
input_textures.push_back(*texture);
} else {
FML_UNREACHABLE();
}
}

// Create a new texture and render the filter to it.

auto context = renderer.GetContext();

auto subpass_target = RenderTarget::CreateOffscreen(*context, output_size);
auto subpass_texture = subpass_target.GetRenderTargetTexture();
if (!subpass_texture) {
return std::nullopt;
}

auto sub_command_buffer = context->CreateRenderCommandBuffer();
sub_command_buffer->SetLabel("Offscreen Filter Command Buffer");
if (!sub_command_buffer) {
return std::nullopt;
}

auto sub_renderpass = sub_command_buffer->CreateRenderPass(subpass_target);
if (!sub_renderpass) {
return std::nullopt;
}
sub_renderpass->SetLabel("OffscreenFilterPass");

if (!RenderFilter(input_textures, renderer, *sub_renderpass)) {
return std::nullopt;
}

if (!sub_renderpass->EncodeCommands(*context->GetTransientsAllocator())) {
return std::nullopt;
}

if (!sub_command_buffer->SubmitCommands()) {
return std::nullopt;
}

return subpass_texture;
}

ISize FilterContents::GetOutputSize() const {
if (input_textures_.empty()) {
return {};
}

if (auto filter =
std::get_if<std::shared_ptr<FilterContents>>(&input_textures_[0])) {
return filter->get()->GetOutputSize();
}

if (auto texture =
std::get_if<std::shared_ptr<Texture>>(&input_textures_[0])) {
return texture->get()->GetSize();
}

FML_UNREACHABLE();
}

/*******************************************************************************
******* BlendFilterContents
******************************************************************************/

BlendFilterContents::BlendFilterContents() = default;

BlendFilterContents::~BlendFilterContents() = default;

void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) {
blend_mode_ = blend_mode;
}

bool BlendFilterContents::RenderFilter(
const std::vector<std::shared_ptr<Texture>>& input_textures,
const ContentContext& renderer,
RenderPass& pass) const {
using VS = TexturePipeline::VertexShader;
using FS = TexturePipeline::FragmentShader;

auto& host_buffer = pass.GetTransientsBuffer();

VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{Point(0, 0), Point(0, 0)},
{Point(1, 0), Point(1, 0)},
{Point(1, 1), Point(1, 1)},
{Point(0, 0), Point(0, 0)},
{Point(1, 1), Point(1, 1)},
{Point(0, 1), Point(0, 1)},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);

VS::FrameInfo frame_info;
frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
frame_info.alpha = 1;

auto uniform_view = host_buffer.EmplaceUniform(frame_info);
auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({});

Command cmd;
cmd.label = "Blend Filter";
auto options = OptionsFromPass(pass);
options.blend_mode = blend_mode_;
cmd.pipeline = renderer.GetTexturePipeline(options);
cmd.BindVertices(vtx_buffer);
VS::BindFrameInfo(cmd, uniform_view);
for (const auto& texture : input_textures) {
FS::BindTextureSampler(cmd, texture, sampler);
pass.AddCommand(cmd);
}

return true;
}

} // namespace impeller
94 changes: 94 additions & 0 deletions impeller/entity/contents/filter_contents.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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.

#pragma once

#include <memory>
#include <variant>
#include <vector>

#include "impeller/entity/contents/contents.h"
#include "impeller/entity/entity.h"
#include "impeller/renderer/formats.h"

namespace impeller {

/*******************************************************************************
******* FilterContents
******************************************************************************/

class FilterContents : public Contents {
public:
using InputVariant =
std::variant<std::shared_ptr<Texture>, std::shared_ptr<FilterContents>>;
using InputTextures = std::vector<InputVariant>;

static std::shared_ptr<FilterContents> MakeBlend(
Entity::BlendMode blend_mode,
InputTextures input_textures);

FilterContents();

~FilterContents() override;

/// @brief The input texture sources for this filter. All texture sources are
/// expected to have or produce premultiplied alpha colors.
/// Any input can either be a `Texture` or another `FilterContents`.
///
/// The number of required or optional textures depends on the
/// particular filter's implementation.
void SetInputTextures(InputTextures input_textures);

// |Contents|
bool Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const override;

/// @brief Renders dependency filters, creates a subpass, and calls the
/// `RenderFilter` defined by the subclasses.
std::optional<std::shared_ptr<Texture>> RenderFilterToTexture(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const;

private:
/// @brief Takes a set of zero or more input textures and writes to an output
/// texture.
virtual bool RenderFilter(
const std::vector<std::shared_ptr<Texture>>& input_textures,
const ContentContext& renderer,
RenderPass& pass) const = 0;

/// @brief Determines the size of the output texture.
virtual ISize GetOutputSize() const;

InputTextures input_textures_;
Rect destination_;

FML_DISALLOW_COPY_AND_ASSIGN(FilterContents);
};

/*******************************************************************************
******* BlendFilterContents
******************************************************************************/

class BlendFilterContents : public FilterContents {
public:
BlendFilterContents();

~BlendFilterContents() override;

void SetBlendMode(Entity::BlendMode blend_mode);

private:
bool RenderFilter(const std::vector<std::shared_ptr<Texture>>& input_textures,
const ContentContext& renderer,
RenderPass& pass) const override;

Entity::BlendMode blend_mode_ = Entity::BlendMode::kSourceOver;

FML_DISALLOW_COPY_AND_ASSIGN(BlendFilterContents);
};

} // namespace impeller
3 changes: 2 additions & 1 deletion impeller/entity/entity.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ void Entity::IncrementStencilDepth(uint32_t increment) {
stencil_depth_ += increment;
}

bool Entity::Render(ContentContext& renderer, RenderPass& parent_pass) const {
bool Entity::Render(const ContentContext& renderer,
RenderPass& parent_pass) const {
if (!contents_) {
return true;
}
Expand Down
3 changes: 2 additions & 1 deletion impeller/entity/entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Entity {
kDestination,
kSourceOver,
kDestinationOver,
kPlus,
};

Entity();
Expand Down Expand Up @@ -57,7 +58,7 @@ class Entity {

uint32_t GetStencilDepth() const;

bool Render(ContentContext& renderer, RenderPass& parent_pass) const;
bool Render(const ContentContext& renderer, RenderPass& parent_pass) const;

private:
Matrix transformation_;
Expand Down
2 changes: 1 addition & 1 deletion impeller/entity/entity_pass.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ std::optional<Rect> EntityPass::GetSubpassCoverage(
return entities_coverage;
}

// If the delete tells us the coverage is smaller than it needs to be, then
// If the delegate tells us the coverage is smaller than it needs to be, then
// great. OTOH, if the delegate is being wasteful, limit coverage to what is
// actually needed.
return entities_coverage->Intersection(delegate_coverage.value());
Expand Down
Loading

0 comments on commit b3cbf05

Please sign in to comment.