Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add tessellation cache
  • Loading branch information
knopp committed Aug 3, 2023
commit ad80d8fb0aaa511b4530309c5293965825d68035
7 changes: 6 additions & 1 deletion impeller/aiks/aiks_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "impeller/aiks/aiks_context.h"

#include "impeller/aiks/picture.h"
#include "impeller/entity/contents/tessellation_cache.h"

namespace impeller {

Expand Down Expand Up @@ -42,7 +43,11 @@ bool AiksContext::Render(const Picture& picture, RenderTarget& render_target) {
}

if (picture.pass) {
return picture.pass->Render(*content_context_, render_target);
auto res = picture.pass->Render(*content_context_, render_target);
// FIXME(knopp): This should be called for the last surface of the frame,
// but there's currently no way to do this.
content_context_->GetTessellationCache().FinishFrame();
return res;
Comment on lines +46 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no concept of a frame, but you could probably use the concept of a particular presentation. See context_vk, this tracks increments in the swapchain. We might need to bring a similar concept out of the platform specific backend.

}

return true;
Expand Down
4 changes: 3 additions & 1 deletion impeller/display_list/skia_conversions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ Path ToPath(const SkPath& path) {
}
builder.SetConvexity(path.isConvex() ? Convexity::kConvex
: Convexity::kUnknown);
return builder.TakePath(fill_type);
auto result = builder.TakePath(fill_type);
result.SetOriginalGenerationId(path.getGenerationID());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this generation ID come from?

Copy link
Member Author

@knopp knopp Aug 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's assigned to path on request from a global counter. Any time the path is mutated it's generationId is reset. That way if path has same generationId it is guaranteed to be same instance unmodified.

return result;
}

Path ToPath(const SkRRect& rrect) {
Expand Down
6 changes: 6 additions & 0 deletions impeller/entity/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ impeller_component("entity") {
"contents/gradient_generator.h",
"contents/linear_gradient_contents.cc",
"contents/linear_gradient_contents.h",
"contents/tessellation_cache.cc",
"contents/tessellation_cache.h",
"contents/radial_gradient_contents.cc",
"contents/radial_gradient_contents.h",
"contents/runtime_effect_contents.cc",
Expand Down Expand Up @@ -261,6 +263,10 @@ impeller_component("entity") {
"../typographer",
]

cflags = [
"-g",
]

deps = [ "//flutter/fml" ]
}

Expand Down
2 changes: 2 additions & 0 deletions impeller/entity/contents/content_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "impeller/base/strings.h"
#include "impeller/core/formats.h"
#include "impeller/entity/contents/tessellation_cache.h"
#include "impeller/entity/entity.h"
#include "impeller/renderer/command_buffer.h"
#include "impeller/renderer/pipeline_library.h"
Expand Down Expand Up @@ -162,6 +163,7 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
: context_(std::move(context)),
lazy_glyph_atlas_(std::make_shared<LazyGlyphAtlas>()),
tessellator_(std::make_shared<Tessellator>()),
tessellation_cache_(std::make_unique<TessellationCache>()),
scene_context_(std::make_shared<scene::SceneContext>(context_)) {
if (!context_ || !context_->IsValid()) {
return;
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 @@ -329,6 +329,7 @@ struct ContentContextOptions {
};

class Tessellator;
class TessellationCache;

class ContentContext {
public:
Expand All @@ -342,6 +343,10 @@ class ContentContext {

std::shared_ptr<Tessellator> GetTessellator() const;

TessellationCache& GetTessellationCache() const {
return *tessellation_cache_;
}

#ifdef IMPELLER_DEBUG
std::shared_ptr<Pipeline<PipelineDescriptor>> GetCheckerboardPipeline(
ContentContextOptions opts) const {
Expand Down Expand Up @@ -853,6 +858,7 @@ class ContentContext {

bool is_valid_ = false;
std::shared_ptr<Tessellator> tessellator_;
std::unique_ptr<TessellationCache> tessellation_cache_;
Comment on lines 860 to +861
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the tessellation cache own the tessellator?

std::shared_ptr<scene::SceneContext> scene_context_;
bool wireframe_ = false;

Expand Down
70 changes: 70 additions & 0 deletions impeller/entity/contents/tessellation_cache.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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/tessellation_cache.h"

#define IMPELLER_ENABLE_TESSELLATION_CACHE 1

namespace impeller {

Path::Polyline TessellationCache::GetOrCreatePolyline(
const impeller::Path& path,
Scalar scale) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you take the exact scale as a cache key then animated transformations will thrash your cache each frame despite the vertices being almost unchanged.

#if IMPELLER_ENABLE_TESSELLATION_CACHE == 1
auto generation_id = path.GetOriginalGenerationId();
if (!generation_id) {
// This path did not originate from Skia path.
return path.CreatePolyline(scale);
}
PolylineKey key{*generation_id, scale};
auto result = polyline_cache_.Get(key);
if (result) {
return *result;
}
auto polyline = path.CreatePolyline(scale);
polyline_cache_.Set(key, polyline);
return polyline;
#else
return path.CreatePolyline(scale);
#endif
}

Tessellator::Result TessellationCache::Tessellate(
const Tessellator& tesselator,
FillType fill_type,
const Path::Polyline& polyline,
const Tessellator::BuilderCallback& callback) {
#if IMPELLER_ENABLE_TESSELLATION_CACHE == 1
auto generation_id = polyline.original_generation_id;
if (!generation_id) {
return tesselator.Tessellate(fill_type, polyline, callback);
}

TessellatorKey key{*generation_id, fill_type};
auto result = tessellator_cache_.Get(key);
if (result) {
if (callback(result->vertices.data(), result->vertices.size(),
result->indices.data(), result->indices.size())) {
return Tessellator::Result::kSuccess;
} else {
return Tessellator::Result::kInputError;
}
}

return tesselator.Tessellate(
fill_type, polyline,
[&](const float* vertices, size_t vertices_size, const uint16_t* indices,
size_t indices_size) {
TessellatorData data;
data.vertices.assign(vertices, vertices + vertices_size);
data.indices.assign(indices, indices + indices_size);
tessellator_cache_.Set(key, data);
return callback(vertices, vertices_size, indices, indices_size);
});
#else
return tesselator.Tessellate(fill_type, polyline, callback);
#endif
Comment on lines +55 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if the tessellation cache was a bit more general and could also cache data that was generated by compute. For an example, look at the draw points geometry.

For that to work, it would likely have to be able to vend device buffers instead.

On the other hand, we'll likely batch compute workloads in ways that make these buffers difficult to use. So perhaps only using the tessellation cache for the CPU bound workloads is the way forward.

}

} // namespace impeller
128 changes: 128 additions & 0 deletions impeller/entity/contents/tessellation_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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 "flutter/fml/logging.h"
#include "impeller/geometry/path.h"
#include "impeller/tessellator/tessellator.h"

namespace impeller {

class TessellationCache {
public:
Path::Polyline GetOrCreatePolyline(const impeller::Path& path, Scalar scale);

Tessellator::Result Tessellate(const Tessellator& tesselator,
FillType fill_type,
const Path::Polyline& polyline,
const Tessellator::BuilderCallback& callback);

void FinishFrame() {
int cache_hits, cache_misses;
polyline_cache_.FinishFrame(cache_hits, cache_misses);
FML_LOG(INFO) << "PolylineCache: " << cache_hits << " hits, "
<< cache_misses << " misses";

tessellator_cache_.FinishFrame(cache_hits, cache_misses);
FML_LOG(INFO) << "TessellatorCache: " << cache_hits << " hits, "
<< cache_misses << " misses";
}

private:
struct PolylineKey {
std::uint32_t generation_id;
Scalar scale;

bool operator==(const PolylineKey& other) const {
return generation_id == other.generation_id && scale == other.scale;
}
};

struct PolylineKeyHash {
size_t operator()(const PolylineKey& key) const {
return key.generation_id + 31 * std::hash<Scalar>()(key.scale);
}
};

struct TessellatorKey {
std::uint32_t generation_id;
FillType fill_type;

bool operator==(const TessellatorKey& other) const {
return generation_id == other.generation_id &&
fill_type == other.fill_type;
}
};

struct TessellatorKeyHash {
size_t operator()(const TessellatorKey& key) const {
return key.generation_id + 31 * std::hash<FillType>()(key.fill_type);
}
};

struct TessellatorData {
std::vector<float> vertices;
std::vector<uint16_t> indices;
};

template <typename K, typename V, typename Hash, typename Compare>
class FrameAwareCache {
public:
std::optional<V> Get(const K& key) {
auto it = used_.find(key);
if (it != used_.end()) {
++cache_hit_count_;
return it->second;
}
it = maybe_remove_.find(key);
if (it != maybe_remove_.end()) {
++cache_hit_count_;
// promote maybe_remove_ to persistent_
auto insert_it = used_.emplace(it->first, std::move(it->second));
maybe_remove_.erase(it);
return insert_it.first->second;
}
++cache_miss_count_;
return std::nullopt;
}

void Set(const K key, V value) { used_.emplace(key, std::move(value)); }

/// Returns number of cache misses for this frame.
void FinishFrame(int& cache_hits, int& cache_misses) {
maybe_remove_ = std::move(used_);
used_.clear();
cache_hits = cache_hit_count_;
cache_misses = cache_miss_count_;
cache_hit_count_ = 0;
cache_miss_count_ = 0;
}

private:
// Items used during this frame.
std::unordered_map<K, V, Hash, Compare> used_;

// Items that will be removed at the end of the frame unless they are
// accessed.
std::unordered_map<K, V, Hash, Compare> maybe_remove_;

int cache_miss_count_ = 0;
int cache_hit_count_ = 0;
};

FrameAwareCache<PolylineKey,
Path::Polyline,
PolylineKeyHash,
std::equal_to<PolylineKey>>
polyline_cache_;

FrameAwareCache<TessellatorKey,
TessellatorData,
TessellatorKeyHash,
std::equal_to<TessellatorKey>>
tessellator_cache_;
};

} // namespace impeller
17 changes: 10 additions & 7 deletions impeller/entity/geometry/fill_path_geometry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

#include "impeller/entity/geometry/fill_path_geometry.h"
#include "impeller/entity/contents/tessellation_cache.h"

namespace impeller {

Expand All @@ -21,8 +22,9 @@ GeometryResult FillPathGeometry::GetPositionBuffer(

if (path_.GetFillType() == FillType::kNonZero && //
path_.IsConvex()) {
auto [points, indices] = TessellateConvex(
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()));
auto [points, indices] =
TessellateConvex(renderer.GetTessellationCache().GetOrCreatePolyline(
path_, entity.GetTransformation().GetMaxBasisLength()));

vertex_buffer.vertex_buffer = host_buffer.Emplace(
points.data(), points.size() * sizeof(Point), alignof(Point));
Expand All @@ -40,9 +42,10 @@ GeometryResult FillPathGeometry::GetPositionBuffer(
};
}

auto tesselation_result = renderer.GetTessellator()->Tessellate(
path_.GetFillType(),
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()),
auto tesselation_result = renderer.GetTessellationCache().Tessellate(
*renderer.GetTessellator(), path_.GetFillType(),
renderer.GetTessellationCache().GetOrCreatePolyline(
path_, entity.GetTransformation().GetMaxBasisLength()),
[&vertex_buffer, &host_buffer](
const float* vertices, size_t vertices_count, const uint16_t* indices,
size_t indices_count) {
Expand Down Expand Up @@ -106,8 +109,8 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer(
}

VertexBufferBuilder<VS::PerVertexData> vertex_builder;
auto tesselation_result = renderer.GetTessellator()->Tessellate(
path_.GetFillType(),
auto tesselation_result = renderer.GetTessellationCache().Tessellate(
*renderer.GetTessellator(), path_.GetFillType(),
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()),
[&vertex_builder, &texture_coverage, &effect_transform](
const float* vertices, size_t vertices_count, const uint16_t* indices,
Expand Down
1 change: 1 addition & 0 deletions impeller/geometry/path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const {
}
end_contour();
}
polyline.original_generation_id = GetOriginalGenerationId();
return polyline;
}

Expand Down
12 changes: 12 additions & 0 deletions impeller/geometry/path.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class Path {
std::vector<Point> points;
std::vector<PolylineContour> contours;

/// Generation ID of the path that this polyline was generated from.
std::optional<uint32_t> original_generation_id;

/// Convenience method to compute the start (inclusive) and end (exclusive)
/// point of the given contour index.
///
Expand Down Expand Up @@ -154,6 +157,14 @@ class Path {

std::optional<std::pair<Point, Point>> GetMinMaxCoveragePoints() const;

/// If this path was created from a Skia path, this will return the
/// generationId() of the Skia path at the moment of path creation.
std::optional<uint32_t> GetOriginalGenerationId() const {
return original_generation_id_;
}

void SetOriginalGenerationId(uint32_t id) { original_generation_id_ = id; }

private:
friend class PathBuilder;

Expand All @@ -176,6 +187,7 @@ class Path {
std::vector<QuadraticPathComponent> quads_;
std::vector<CubicPathComponent> cubics_;
std::vector<ContourComponent> contours_;
std::optional<uint32_t> original_generation_id_ = std::nullopt;
};

} // namespace impeller