blob: 38d187f26a58ba3c20c9d9cd38f989baf39f5b72 [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 <optional>
#include <unordered_map>
#include <utility>
#include "flutter/fml/macros.h"
#include "impeller/core/formats.h"
#include "impeller/entity/contents/atlas_contents.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/filters/color_filter_contents.h"
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/framebuffer_blend_contents.h"
#include "impeller/entity/contents/texture_contents.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/geometry.h"
#include "impeller/entity/texture_fill.frag.h"
#include "impeller/entity/texture_fill.vert.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/sampler_library.h"
#include "impeller/renderer/vertex_buffer_builder.h"
namespace impeller {
AtlasContents::AtlasContents() = default;
AtlasContents::~AtlasContents() = default;
void AtlasContents::SetTexture(std::shared_ptr<Texture> texture) {
texture_ = std::move(texture);
}
std::shared_ptr<Texture> AtlasContents::GetTexture() const {
return texture_;
}
void AtlasContents::SetTransforms(std::vector<Matrix> transforms) {
transforms_ = std::move(transforms);
bounding_box_cache_.reset();
}
void AtlasContents::SetTextureCoordinates(std::vector<Rect> texture_coords) {
texture_coords_ = std::move(texture_coords);
bounding_box_cache_.reset();
}
void AtlasContents::SetColors(std::vector<Color> colors) {
colors_ = std::move(colors);
}
void AtlasContents::SetAlpha(Scalar alpha) {
alpha_ = alpha;
}
void AtlasContents::SetBlendMode(BlendMode blend_mode) {
blend_mode_ = blend_mode;
}
void AtlasContents::SetCullRect(std::optional<Rect> cull_rect) {
cull_rect_ = cull_rect;
}
struct AtlasBlenderKey {
Color color;
Rect rect;
uint32_t color_key;
struct Hash {
std::size_t operator()(const AtlasBlenderKey& key) const {
return fml::HashCombine(key.color_key, key.rect.size.width,
key.rect.size.height, key.rect.origin.x,
key.rect.origin.y);
}
};
struct Equal {
bool operator()(const AtlasBlenderKey& lhs,
const AtlasBlenderKey& rhs) const {
return lhs.rect == rhs.rect && lhs.color_key == rhs.color_key;
}
};
};
std::shared_ptr<SubAtlasResult> AtlasContents::GenerateSubAtlas() const {
FML_DCHECK(colors_.size() > 0 && blend_mode_ != BlendMode::kSource &&
blend_mode_ != BlendMode::kDestination);
std::unordered_map<AtlasBlenderKey, std::vector<Matrix>,
AtlasBlenderKey::Hash, AtlasBlenderKey::Equal>
sub_atlas = {};
for (auto i = 0u; i < texture_coords_.size(); i++) {
AtlasBlenderKey key = {.color = colors_[i],
.rect = texture_coords_[i],
.color_key = Color::ToIColor(colors_[i])};
if (sub_atlas.find(key) == sub_atlas.end()) {
sub_atlas[key] = {transforms_[i]};
} else {
sub_atlas[key].push_back(transforms_[i]);
}
}
auto result = std::make_shared<SubAtlasResult>();
Scalar x_offset = 0.0;
Scalar y_offset = 0.0;
Scalar x_extent = 0.0;
Scalar y_extent = 0.0;
for (auto it = sub_atlas.begin(); it != sub_atlas.end(); it++) {
// This size was arbitrarily chosen to keep the textures from getting too
// wide. We could instead use a more generic rect packer but in the majority
// of cases the sample rects will be fairly close in size making this a good
// enough approximation.
if (x_offset >= 1000) {
y_offset = y_extent + 1;
x_offset = 0.0;
}
auto key = it->first;
auto transforms = it->second;
auto new_rect = Rect::MakeXYWH(x_offset, y_offset, key.rect.size.width,
key.rect.size.height);
auto sub_transform = Matrix::MakeTranslation(Vector2(x_offset, y_offset));
x_offset += std::ceil(key.rect.size.width) + 1.0;
result->sub_texture_coords.push_back(key.rect);
result->sub_colors.push_back(key.color);
result->sub_transforms.push_back(sub_transform);
x_extent = std::max(x_extent, x_offset);
y_extent = std::max(y_extent, std::ceil(y_offset + key.rect.size.height));
for (auto transform : transforms) {
result->result_texture_coords.push_back(new_rect);
result->result_transforms.push_back(transform);
}
}
result->size = ISize(std::ceil(x_extent), std::ceil(y_extent));
return result;
}
std::optional<Rect> AtlasContents::GetCoverage(const Entity& entity) const {
if (cull_rect_.has_value()) {
return cull_rect_.value().TransformBounds(entity.GetTransformation());
}
return ComputeBoundingBox().TransformBounds(entity.GetTransformation());
}
Rect AtlasContents::ComputeBoundingBox() const {
if (!bounding_box_cache_.has_value()) {
Rect bounding_box = {};
for (size_t i = 0; i < texture_coords_.size(); i++) {
auto matrix = transforms_[i];
auto sample_rect = texture_coords_[i];
auto bounds = Rect::MakeSize(sample_rect.size).TransformBounds(matrix);
bounding_box = bounds.Union(bounding_box);
}
bounding_box_cache_ = bounding_box;
}
return bounding_box_cache_.value();
}
void AtlasContents::SetSamplerDescriptor(SamplerDescriptor desc) {
sampler_descriptor_ = std::move(desc);
}
const SamplerDescriptor& AtlasContents::GetSamplerDescriptor() const {
return sampler_descriptor_;
}
const std::vector<Matrix>& AtlasContents::GetTransforms() const {
return transforms_;
}
const std::vector<Rect>& AtlasContents::GetTextureCoordinates() const {
return texture_coords_;
}
const std::vector<Color>& AtlasContents::GetColors() const {
return colors_;
}
bool AtlasContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
if (texture_ == nullptr || blend_mode_ == BlendMode::kClear ||
alpha_ <= 0.0) {
return true;
}
// Ensure that we use the actual computed bounds and not a cull-rect
// approximation of them.
auto coverage = ComputeBoundingBox();
if (blend_mode_ == BlendMode::kSource || colors_.size() == 0) {
auto child_contents = AtlasTextureContents(*this);
child_contents.SetAlpha(alpha_);
child_contents.SetCoverage(coverage);
return child_contents.Render(renderer, entity, pass);
}
if (blend_mode_ == BlendMode::kDestination) {
auto child_contents = AtlasColorContents(*this);
child_contents.SetAlpha(alpha_);
child_contents.SetCoverage(coverage);
return child_contents.Render(renderer, entity, pass);
}
auto sub_atlas = GenerateSubAtlas();
auto sub_coverage = Rect::MakeSize(sub_atlas->size);
auto src_contents = std::make_shared<AtlasTextureContents>(*this);
src_contents->SetSubAtlas(sub_atlas);
src_contents->SetCoverage(sub_coverage);
auto dst_contents = std::make_shared<AtlasColorContents>(*this);
dst_contents->SetSubAtlas(sub_atlas);
dst_contents->SetCoverage(sub_coverage);
std::shared_ptr<Texture> new_texture;
if (renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) {
new_texture = renderer.MakeSubpass(
"Atlas Blend", sub_atlas->size,
[&](const ContentContext& context, RenderPass& pass) {
Entity entity;
entity.SetContents(dst_contents);
entity.SetBlendMode(BlendMode::kSource);
if (!entity.Render(context, pass)) {
return false;
}
if (blend_mode_ >= Entity::kLastPipelineBlendMode) {
auto contents = std::make_shared<FramebufferBlendContents>();
contents->SetBlendMode(blend_mode_);
contents->SetChildContents(src_contents);
entity.SetContents(std::move(contents));
entity.SetBlendMode(BlendMode::kSource);
return entity.Render(context, pass);
}
entity.SetContents(src_contents);
entity.SetBlendMode(blend_mode_);
return entity.Render(context, pass);
});
} else {
auto contents = ColorFilterContents::MakeBlend(
blend_mode_,
{FilterInput::Make(dst_contents), FilterInput::Make(src_contents)});
auto snapshot =
contents->RenderToSnapshot(renderer, // renderer
entity, // entity
std::nullopt, // coverage_limit
std::nullopt, // sampler_descriptor
true, // msaa_enabled
"AtlasContents Snapshot"); // label
if (!snapshot.has_value()) {
return false;
}
new_texture = snapshot.value().texture;
}
auto child_contents = AtlasTextureContents(*this);
child_contents.SetAlpha(alpha_);
child_contents.SetCoverage(coverage);
child_contents.SetTexture(new_texture);
child_contents.SetUseDestination(true);
child_contents.SetSubAtlas(sub_atlas);
return child_contents.Render(renderer, entity, pass);
}
// AtlasTextureContents
// ---------------------------------------------------------
AtlasTextureContents::AtlasTextureContents(const AtlasContents& parent)
: parent_(parent) {}
AtlasTextureContents::~AtlasTextureContents() {}
std::optional<Rect> AtlasTextureContents::GetCoverage(
const Entity& entity) const {
return coverage_.TransformBounds(entity.GetTransformation());
}
void AtlasTextureContents::SetAlpha(Scalar alpha) {
alpha_ = alpha;
}
void AtlasTextureContents::SetCoverage(Rect coverage) {
coverage_ = coverage;
}
void AtlasTextureContents::SetUseDestination(bool value) {
use_destination_ = value;
}
void AtlasTextureContents::SetSubAtlas(
const std::shared_ptr<SubAtlasResult>& subatlas) {
subatlas_ = subatlas;
}
void AtlasTextureContents::SetTexture(std::shared_ptr<Texture> texture) {
texture_ = std::move(texture);
}
bool AtlasTextureContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
using VS = TextureFillVertexShader;
using FS = TextureFillFragmentShader;
auto texture = texture_ ? texture_ : parent_.GetTexture();
if (texture == nullptr) {
return true;
}
std::vector<Rect> texture_coords;
std::vector<Matrix> transforms;
if (subatlas_) {
texture_coords = use_destination_ ? subatlas_->result_texture_coords
: subatlas_->sub_texture_coords;
transforms = use_destination_ ? subatlas_->result_transforms
: subatlas_->sub_transforms;
} else {
texture_coords = parent_.GetTextureCoordinates();
transforms = parent_.GetTransforms();
}
const Size texture_size(texture->GetSize());
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
vertex_builder.Reserve(texture_coords.size() * 6);
constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};
constexpr Scalar width[6] = {0, 1, 0, 1, 0, 1};
constexpr Scalar height[6] = {0, 0, 1, 0, 1, 1};
for (size_t i = 0; i < texture_coords.size(); i++) {
auto sample_rect = texture_coords[i];
auto matrix = transforms[i];
auto transformed_points =
Rect::MakeSize(sample_rect.size).GetTransformedPoints(matrix);
for (size_t j = 0; j < 6; j++) {
VS::PerVertexData data;
data.position = transformed_points[indices[j]];
data.texture_coords =
(sample_rect.origin + Point(sample_rect.size.width * width[j],
sample_rect.size.height * height[j])) /
texture_size;
vertex_builder.AppendVertex(data);
}
}
if (!vertex_builder.HasVertices()) {
return true;
}
Command cmd;
cmd.label = "AtlasTexture";
auto& host_buffer = pass.GetTransientsBuffer();
VS::FrameInfo frame_info;
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation();
frame_info.texture_sampler_y_coord_scale = texture->GetYCoordScale();
FS::FragInfo frag_info;
frag_info.alpha = alpha_;
auto options = OptionsFromPassAndEntity(pass, entity);
cmd.pipeline = renderer.GetTexturePipeline(options);
cmd.stencil_reference = entity.GetStencilDepth();
cmd.BindVertices(vertex_builder.CreateVertexBuffer(host_buffer));
VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info));
FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info));
FS::BindTextureSampler(cmd, texture,
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
parent_.GetSamplerDescriptor()));
return pass.AddCommand(std::move(cmd));
}
// AtlasColorContents
// ---------------------------------------------------------
AtlasColorContents::AtlasColorContents(const AtlasContents& parent)
: parent_(parent) {}
AtlasColorContents::~AtlasColorContents() {}
std::optional<Rect> AtlasColorContents::GetCoverage(
const Entity& entity) const {
return coverage_.TransformBounds(entity.GetTransformation());
}
void AtlasColorContents::SetAlpha(Scalar alpha) {
alpha_ = alpha;
}
void AtlasColorContents::SetCoverage(Rect coverage) {
coverage_ = coverage;
}
void AtlasColorContents::SetSubAtlas(
const std::shared_ptr<SubAtlasResult>& subatlas) {
subatlas_ = subatlas;
}
bool AtlasColorContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
using VS = GeometryColorPipeline::VertexShader;
using FS = GeometryColorPipeline::FragmentShader;
std::vector<Rect> texture_coords;
std::vector<Matrix> transforms;
std::vector<Color> colors;
if (subatlas_.has_value()) {
auto subatlas = subatlas_.value();
texture_coords = subatlas->sub_texture_coords;
colors = subatlas->sub_colors;
transforms = subatlas->sub_transforms;
} else {
texture_coords = parent_.GetTextureCoordinates();
transforms = parent_.GetTransforms();
colors = parent_.GetColors();
}
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
vertex_builder.Reserve(texture_coords.size() * 6);
constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};
for (size_t i = 0; i < texture_coords.size(); i++) {
auto sample_rect = texture_coords[i];
auto matrix = transforms[i];
auto transformed_points =
Rect::MakeSize(sample_rect.size).GetTransformedPoints(matrix);
for (size_t j = 0; j < 6; j++) {
VS::PerVertexData data;
data.position = transformed_points[indices[j]];
data.color = colors[i].Premultiply();
vertex_builder.AppendVertex(data);
}
}
if (!vertex_builder.HasVertices()) {
return true;
}
Command cmd;
cmd.label = "AtlasColors";
auto& host_buffer = pass.GetTransientsBuffer();
VS::FrameInfo frame_info;
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation();
FS::FragInfo frag_info;
frag_info.alpha = alpha_;
auto opts = OptionsFromPassAndEntity(pass, entity);
opts.blend_mode = BlendMode::kSourceOver;
cmd.pipeline = renderer.GetGeometryColorPipeline(opts);
cmd.stencil_reference = entity.GetStencilDepth();
cmd.BindVertices(vertex_builder.CreateVertexBuffer(host_buffer));
VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info));
FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info));
return pass.AddCommand(std::move(cmd));
}
} // namespace impeller