Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 81a6de7

Browse files
author
jonahwilliams
committed
[Impeller] improve atlas performance by reducing size of subpass
1 parent 7c24f2f commit 81a6de7

File tree

3 files changed

+268
-11
lines changed

3 files changed

+268
-11
lines changed

impeller/entity/contents/atlas_contents.cc

Lines changed: 174 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
// found in the LICENSE file.
44

55
#include <optional>
6+
#include <unordered_map>
67
#include <utility>
78

9+
#include "flutter/fml/macros.h"
10+
811
#include "impeller/entity/contents/atlas_contents.h"
912
#include "impeller/entity/contents/content_context.h"
1013
#include "impeller/entity/contents/filters/color_filter_contents.h"
@@ -19,6 +22,10 @@
1922
#include "impeller/renderer/sampler_library.h"
2023
#include "impeller/renderer/vertex_buffer_builder.h"
2124

25+
#ifdef FML_OS_PHYSICAL_IOS
26+
#include "impeller/entity/contents/framebuffer_blend_contents.h"
27+
#endif
28+
2229
namespace impeller {
2330

2431
AtlasContents::AtlasContents() = default;
@@ -57,6 +64,85 @@ void AtlasContents::SetCullRect(std::optional<Rect> cull_rect) {
5764
cull_rect_ = cull_rect;
5865
}
5966

67+
struct AtlasBlenderKey {
68+
Color color;
69+
Rect rect;
70+
71+
struct Hash {
72+
std::size_t operator()(const AtlasBlenderKey& key) const {
73+
return fml::HashCombine(key.color.red, key.color.green, key.color.blue,
74+
key.color.alpha, key.rect.size.width,
75+
key.rect.size.height, key.rect.origin.x,
76+
key.rect.origin.y);
77+
}
78+
};
79+
80+
struct Equal {
81+
bool operator()(const AtlasBlenderKey& lhs,
82+
const AtlasBlenderKey& rhs) const {
83+
return lhs.rect == rhs.rect && lhs.color == rhs.color;
84+
}
85+
};
86+
};
87+
88+
std::shared_ptr<SubAtlasResult> AtlasContents::GenerateSubAtlas() const {
89+
FML_DCHECK(colors_.size() > 0 && blend_mode_ != BlendMode::kSource &&
90+
blend_mode_ != BlendMode::kDestination);
91+
92+
std::unordered_map<AtlasBlenderKey, std::vector<Matrix>,
93+
AtlasBlenderKey::Hash, AtlasBlenderKey::Equal>
94+
sub_atlas = {};
95+
96+
for (auto i = 0u; i < texture_coords_.size(); i++) {
97+
AtlasBlenderKey key = {.color = colors_[i], .rect = texture_coords_[i]};
98+
if (sub_atlas.find(key) == sub_atlas.end()) {
99+
sub_atlas[key] = {transforms_[i]};
100+
} else {
101+
sub_atlas[key].push_back(transforms_[i]);
102+
}
103+
}
104+
105+
auto result = std::make_shared<SubAtlasResult>();
106+
Scalar x_offset = 0.0;
107+
Scalar y_offset = 0.0;
108+
Scalar x_extent = 0.0;
109+
Scalar y_extent = 0.0;
110+
111+
for (auto it = sub_atlas.begin(); it != sub_atlas.end(); it++) {
112+
// This size was arbitrarily chosen to keep the textures from getting too
113+
// wide. We could instead use a more generic rect packer but in the majority
114+
// of cases the sample rects will be fairly close in size making this a good
115+
// enough approximation.
116+
if (x_offset >= 1000) {
117+
y_offset = y_extent + 1;
118+
x_offset = 0.0;
119+
}
120+
121+
auto key = it->first;
122+
auto transforms = it->second;
123+
124+
auto new_rect = Rect::MakeXYWH(x_offset, y_offset, key.rect.size.width,
125+
key.rect.size.height);
126+
auto sub_transform = Matrix::MakeTranslation(Vector2(x_offset, y_offset));
127+
128+
x_offset += (key.rect.size.width + 1.0);
129+
130+
result->sub_texture_coords.push_back(key.rect);
131+
result->sub_colors.push_back(key.color);
132+
result->sub_transforms.push_back(sub_transform);
133+
134+
x_extent = std::max(x_extent, x_offset);
135+
y_extent = std::max(y_extent, y_offset + key.rect.size.height);
136+
137+
for (auto transform : transforms) {
138+
result->result_texture_coords.push_back(new_rect);
139+
result->result_transforms.push_back(transform);
140+
}
141+
}
142+
result->size = ISize(std::ceil(x_extent), std::ceil(y_extent));
143+
return result;
144+
}
145+
60146
std::optional<Rect> AtlasContents::GetCoverage(const Entity& entity) const {
61147
if (cull_rect_.has_value()) {
62148
return cull_rect_.value().TransformBounds(entity.GetTransformation());
@@ -120,17 +206,56 @@ bool AtlasContents::Render(const ContentContext& renderer,
120206
return child_contents.Render(renderer, entity, pass);
121207
}
122208

209+
auto sub_atlas = GenerateSubAtlas();
210+
auto sub_coverage = Rect::MakeSize(sub_atlas->size);
211+
123212
auto src_contents = std::make_shared<AtlasTextureContents>(*this);
124-
src_contents->SetCoverage(coverage);
213+
src_contents->SetSubAtlas(sub_atlas);
214+
src_contents->SetCoverage(sub_coverage);
125215

126216
auto dst_contents = std::make_shared<AtlasColorContents>(*this);
127-
dst_contents->SetCoverage(coverage);
128-
217+
dst_contents->SetSubAtlas(sub_atlas);
218+
dst_contents->SetCoverage(sub_coverage);
219+
220+
#ifdef FML_OS_PHYSICAL_IOS
221+
auto new_texture = renderer.MakeSubpass(
222+
sub_atlas->size, [&](const ContentContext& context, RenderPass& pass) {
223+
Entity entity;
224+
entity.SetContents(dst_contents);
225+
entity.SetBlendMode(BlendMode::kSource);
226+
if (!entity.Render(context, pass)) {
227+
return false;
228+
}
229+
if (blend_mode_ >= Entity::kLastPipelineBlendMode) {
230+
auto contents = std::make_shared<FramebufferBlendContents>();
231+
contents->SetBlendMode(blend_mode_);
232+
contents->SetChildContents(src_contents);
233+
entity.SetContents(std::move(contents));
234+
entity.SetBlendMode(BlendMode::kSource);
235+
return entity.Render(context, pass);
236+
}
237+
entity.SetContents(src_contents);
238+
entity.SetBlendMode(blend_mode_);
239+
return entity.Render(context, pass);
240+
});
241+
#else
129242
auto contents = ColorFilterContents::MakeBlend(
130243
blend_mode_,
131244
{FilterInput::Make(dst_contents), FilterInput::Make(src_contents)});
132-
contents->SetAlpha(alpha_);
133-
return contents->Render(renderer, entity, pass);
245+
auto snapshot = contents->RenderToSnapshot(renderer, entity);
246+
if (!snapshot.has_value()) {
247+
return false;
248+
}
249+
auto new_texture = snapshot.value().texture;
250+
#endif
251+
252+
auto child_contents = AtlasTextureContents(*this);
253+
child_contents.SetAlpha(alpha_);
254+
child_contents.SetCoverage(coverage);
255+
child_contents.SetTexture(new_texture);
256+
child_contents.SetUseDestination(true);
257+
child_contents.SetSubAtlas(sub_atlas);
258+
return child_contents.Render(renderer, entity, pass);
134259
}
135260

136261
// AtlasTextureContents
@@ -154,15 +279,38 @@ void AtlasTextureContents::SetCoverage(Rect coverage) {
154279
coverage_ = coverage;
155280
}
156281

282+
void AtlasTextureContents::SetUseDestination(bool value) {
283+
use_destination_ = value;
284+
}
285+
286+
void AtlasTextureContents::SetSubAtlas(
287+
const std::shared_ptr<SubAtlasResult>& subatlas) {
288+
subatlas_ = subatlas;
289+
}
290+
291+
void AtlasTextureContents::SetTexture(std::shared_ptr<Texture> texture) {
292+
texture_ = std::move(texture);
293+
}
294+
157295
bool AtlasTextureContents::Render(const ContentContext& renderer,
158296
const Entity& entity,
159297
RenderPass& pass) const {
160298
using VS = TextureFillVertexShader;
161299
using FS = TextureFillFragmentShader;
162300

163-
auto texture = parent_.GetTexture();
164-
auto texture_coords = parent_.GetTextureCoordinates();
165-
auto transforms = parent_.GetTransforms();
301+
auto texture = texture_.value_or(parent_.GetTexture());
302+
std::vector<Rect> texture_coords;
303+
std::vector<Matrix> transforms;
304+
if (subatlas_.has_value()) {
305+
auto subatlas = subatlas_.value();
306+
texture_coords = use_destination_ ? subatlas->result_texture_coords
307+
: subatlas->sub_texture_coords;
308+
transforms = use_destination_ ? subatlas->result_transforms
309+
: subatlas->sub_transforms;
310+
} else {
311+
texture_coords = parent_.GetTextureCoordinates();
312+
transforms = parent_.GetTransforms();
313+
}
166314

167315
const auto texture_size = texture->GetSize();
168316
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
@@ -237,15 +385,30 @@ void AtlasColorContents::SetCoverage(Rect coverage) {
237385
coverage_ = coverage;
238386
}
239387

388+
void AtlasColorContents::SetSubAtlas(
389+
const std::shared_ptr<SubAtlasResult>& subatlas) {
390+
subatlas_ = subatlas;
391+
}
392+
240393
bool AtlasColorContents::Render(const ContentContext& renderer,
241394
const Entity& entity,
242395
RenderPass& pass) const {
243396
using VS = GeometryColorPipeline::VertexShader;
244397
using FS = GeometryColorPipeline::FragmentShader;
245398

246-
auto texture_coords = parent_.GetTextureCoordinates();
247-
auto transforms = parent_.GetTransforms();
248-
auto colors = parent_.GetColors();
399+
std::vector<Rect> texture_coords;
400+
std::vector<Matrix> transforms;
401+
std::vector<Color> colors;
402+
if (subatlas_.has_value()) {
403+
auto subatlas = subatlas_.value();
404+
texture_coords = subatlas->sub_texture_coords;
405+
colors = subatlas->sub_colors;
406+
transforms = subatlas->sub_transforms;
407+
} else {
408+
texture_coords = parent_.GetTextureCoordinates();
409+
transforms = parent_.GetTransforms();
410+
colors = parent_.GetColors();
411+
}
249412

250413
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
251414
vertex_builder.Reserve(texture_coords.size() * 6);

impeller/entity/contents/atlas_contents.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@
1515

1616
namespace impeller {
1717

18+
struct SubAtlasResult {
19+
// Sub atlas values.
20+
std::vector<Rect> sub_texture_coords;
21+
std::vector<Color> sub_colors;
22+
std::vector<Matrix> sub_transforms;
23+
24+
// Result atlas values.
25+
std::vector<Rect> result_texture_coords;
26+
std::vector<Matrix> result_transforms;
27+
28+
// Size of the sub-atlass.
29+
ISize size;
30+
};
31+
1832
class AtlasContents final : public Contents {
1933
public:
2034
explicit AtlasContents();
@@ -47,6 +61,11 @@ class AtlasContents final : public Contents {
4761

4862
const std::vector<Color>& GetColors() const;
4963

64+
/// @brief Compress a drawAtlas call with blending into a smaller sized atlas.
65+
/// This atlas has no overlapping to ensure
66+
/// blending behaves as if it were done in the fragment shader.
67+
std::shared_ptr<SubAtlasResult> GenerateSubAtlas() const;
68+
5069
// |Contents|
5170
std::optional<Rect> GetCoverage(const Entity& entity) const override;
5271

@@ -88,10 +107,19 @@ class AtlasTextureContents final : public Contents {
88107

89108
void SetCoverage(Rect coverage);
90109

110+
void SetTexture(std::shared_ptr<Texture> texture);
111+
112+
void SetUseDestination(bool value);
113+
114+
void SetSubAtlas(std::shared_ptr<SubAtlasResult> subatlas);
115+
91116
private:
92117
const AtlasContents& parent_;
93118
Scalar alpha_ = 1.0;
94119
Rect coverage_;
120+
std::optional<std::shared_ptr<Texture>> texture_;
121+
bool use_destination_ = false;
122+
std::optional<std::shared_ptr<SubAtlasResult>> subatlas_ = std::nullopt;
95123

96124
FML_DISALLOW_COPY_AND_ASSIGN(AtlasTextureContents);
97125
};
@@ -114,10 +142,13 @@ class AtlasColorContents final : public Contents {
114142

115143
void SetCoverage(Rect coverage);
116144

145+
void SetSubAtlas(std::shared_ptr<SubAtlasResult> subatlas);
146+
117147
private:
118148
const AtlasContents& parent_;
119149
Scalar alpha_ = 1.0;
120150
Rect coverage_;
151+
std::optional<std::shared_ptr<SubAtlasResult>> subatlas_ = std::nullopt;
121152

122153
FML_DISALLOW_COPY_AND_ASSIGN(AtlasColorContents);
123154
};

impeller/entity/entity_unittests.cc

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,69 @@ TEST_P(EntityTest, SdfText) {
20692069
ASSERT_TRUE(OpenPlaygroundHere(callback));
20702070
}
20712071

2072+
TEST_P(EntityTest, AtlasContentsSubAtlas) {
2073+
auto boston = CreateTextureForFixture("boston.jpg");
2074+
2075+
{
2076+
auto contents = std::make_shared<AtlasContents>();
2077+
contents->SetBlendMode(BlendMode::kSourceOver);
2078+
contents->SetTexture(boston);
2079+
contents->SetColors({
2080+
Color::Red(),
2081+
Color::Red(),
2082+
Color::Red(),
2083+
});
2084+
contents->SetTextureCoordinates({
2085+
Rect::MakeLTRB(0, 0, 10, 10),
2086+
Rect::MakeLTRB(0, 0, 10, 10),
2087+
Rect::MakeLTRB(0, 0, 10, 10),
2088+
});
2089+
contents->SetTransforms({
2090+
Matrix::MakeTranslation(Vector2(0, 0)),
2091+
Matrix::MakeTranslation(Vector2(100, 100)),
2092+
Matrix::MakeTranslation(Vector2(200, 200)),
2093+
});
2094+
2095+
// Since all colors and sample rects are the same, there should
2096+
// only be a single entry in the sub atlas.
2097+
auto subatlas = contents->GenerateSubAtlas();
2098+
ASSERT_EQ(subatlas->sub_texture_coords.size(), 1u);
2099+
}
2100+
2101+
{
2102+
auto contents = std::make_shared<AtlasContents>();
2103+
contents->SetBlendMode(BlendMode::kSourceOver);
2104+
contents->SetTexture(boston);
2105+
contents->SetColors({
2106+
Color::Red(),
2107+
Color::Green(),
2108+
Color::Blue(),
2109+
});
2110+
contents->SetTextureCoordinates({
2111+
Rect::MakeLTRB(0, 0, 10, 10),
2112+
Rect::MakeLTRB(0, 0, 10, 10),
2113+
Rect::MakeLTRB(0, 0, 10, 10),
2114+
});
2115+
contents->SetTransforms({
2116+
Matrix::MakeTranslation(Vector2(0, 0)),
2117+
Matrix::MakeTranslation(Vector2(100, 100)),
2118+
Matrix::MakeTranslation(Vector2(200, 200)),
2119+
});
2120+
2121+
// Since all colors are different, there are three entires.
2122+
auto subatlas = contents->GenerateSubAtlas();
2123+
ASSERT_EQ(subatlas->sub_texture_coords.size(), 3u);
2124+
2125+
// The translations are kept but the sample rects point into
2126+
// different parts of the sub atlas.
2127+
ASSERT_EQ(subatlas->result_texture_coords[0], Rect::MakeXYWH(0, 0, 10, 10));
2128+
ASSERT_EQ(subatlas->result_texture_coords[1],
2129+
Rect::MakeXYWH(11, 0, 10, 10));
2130+
ASSERT_EQ(subatlas->result_texture_coords[2],
2131+
Rect::MakeXYWH(22, 0, 10, 10));
2132+
}
2133+
}
2134+
20722135
static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) {
20732136
Vector3 yuv;
20742137
switch (yuv_color_space) {

0 commit comments

Comments
 (0)