| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SRC_UI_LIB_ESCHER_PAPER_PAPER_SHAPE_CACHE_H_ |
| #define SRC_UI_LIB_ESCHER_PAPER_PAPER_SHAPE_CACHE_H_ |
| |
| #include <lib/fit/function.h> |
| |
| #include <functional> |
| #include <vector> |
| |
| #include "src/ui/lib/escher/forward_declarations.h" |
| #include "src/ui/lib/escher/geometry/types.h" |
| #include "src/ui/lib/escher/paper/paper_renderer_config.h" |
| #include "src/ui/lib/escher/shape/mesh_spec.h" |
| #include "src/ui/lib/escher/util/hash_map.h" |
| |
| namespace escher { |
| |
| class BoundingBox; |
| struct RoundedRectSpec; |
| |
| // Stored internally by PaperShapeCache. Exposed externally for convenience, |
| // as a way to get access to both |num_indices| and |num_shadow_volume_indices|. |
| // This allows us to use the same mesh for two different purposes ("regular" |
| // geometry and extruded shadow volume geometry). |
| // NOTE: messy-ish but OK for now because encapsulated in |PaperRenderer|. |
| struct PaperShapeCacheEntry { |
| uint64_t last_touched_frame = 0; |
| MeshPtr mesh; |
| uint32_t num_indices = 0; |
| uint32_t num_shadow_volume_indices = 0; |
| |
| explicit operator bool() const { return mesh.get() != nullptr; } |
| }; |
| |
| // Generates and caches clipped triangle meshes that match the requested shape |
| // specification. |
| class PaperShapeCache { |
| public: |
| static constexpr size_t kNumFramesBeforeEviction = 3; |
| |
| // The generated/cached meshes don't interleave attributes; they are stored in separate vertex |
| // buffers (or at least in disjoint regions of a vertex buffer). |
| static MeshSpec kStandardMeshSpec() { return {{MeshAttribute::kPosition2D, MeshAttribute::kUV}}; } |
| static MeshSpec kShadowVolumeMeshSpec() { |
| return {{MeshAttribute::kPosition2D, MeshAttribute::kUV, MeshAttribute::kBlendWeight1}}; |
| } |
| // TODO(fxbug.dev/44898): use constexpr instead, when vulkan.hpp is new enough. |
| // static constexpr MeshSpec kStandardMeshSpec{{MeshAttribute::kPosition2D, MeshAttribute::kUV}}; |
| // static constexpr MeshSpec kShadowVolumeMeshSpec{ |
| // {MeshAttribute::kPosition2D, MeshAttribute::kUV, MeshAttribute::kBlendWeight1}}; |
| |
| explicit PaperShapeCache(EscherWeakPtr escher, const PaperRendererConfig& config); |
| ~PaperShapeCache(); |
| |
| // Return a (possibly cached) mesh that matches the shape parameters. To |
| // look up the mesh, a hash is computed from the shape parameters along with |
| // the list of clip planes. If the mesh is not found, a new mesh is generated |
| // from the shape parameters, clipped by the list of planes, and |
| // post-processed in whatever way is required by the current |PaperRenderer| |
| // configuration (e.g. perhaps adding a vertex attribute to allow |
| // shadow-volume extrusion in the vertex shader). |
| const PaperShapeCacheEntry& GetRoundedRectMesh(const RoundedRectSpec& spec, |
| const plane3* clip_planes, size_t num_clip_planes); |
| const PaperShapeCacheEntry& GetCircleMesh(float radius, const plane3* clip_planes, |
| size_t num_clip_planes); |
| const PaperShapeCacheEntry& GetRectMesh(vec2 min, vec2 max, const plane3* clip_planes, |
| size_t num_clip_planes); |
| const PaperShapeCacheEntry& GetRectMesh(float width, float height, const plane3* clip_planes, |
| size_t num_clip_planes) { |
| vec2 half_extent(0.5f * width, 0.5f * height); |
| return GetRectMesh(-half_extent, half_extent, clip_planes, num_clip_planes); |
| } |
| |
| // Used for wireframe debugging. |
| const PaperShapeCacheEntry& GetBoxMesh(const plane3* clip_planes, size_t num_clip_planes); |
| |
| void BeginFrame(BatchGpuUploader* uploader, uint64_t frame_number); |
| void EndFrame(); |
| |
| uint64_t frame_number() const { return frame_number_; } |
| |
| void SetConfig(const PaperRendererConfig& config); |
| |
| size_t size() const { return cache_.size(); } |
| |
| uint64_t cache_hit_count() const { return cache_hit_count_; } |
| uint64_t cache_hit_after_plane_culling_count() const { |
| return cache_hit_after_plane_culling_count_; |
| } |
| uint64_t cache_miss_count() const { return cache_miss_count_; } |
| |
| private: |
| enum class ShapeType { kRect, kRoundedRect, kCircle, kBox }; |
| |
| // Args: array of planes to clip the generated mesh, and size of the array. |
| using CacheMissMeshGenerator = |
| fit::function<PaperShapeCacheEntry(const plane3* planes, size_t num_planes)>; |
| |
| // Computes a lookup key by starting with |shape_hash| and then hashing the |
| // list of |clip_planes|. If no mesh is found with this key, a secondary key |
| // is generated similarly, this time after culling the planes against |
| // |bounding_box|. If no mesh is found with the second key, a new mesh is |
| // generated by invoking |mesh_generator|; this mesh is then cached using both |
| // lookup keys, based on the following rationale: |
| // - the mesh is cached with the first key to maximize performance in the |
| // case that it is looked up again with the exact same set of parameters. |
| // - the mesh is cached with the second key because it will be common for the |
| // culled set of planes to be the same even though the original set isn't. |
| // For example, when an object is moving freely within a large clip region, |
| // the list of unculled planes will be empty; it would be a shame to |
| // continually regenerate the mesh in such a situation. |
| const PaperShapeCacheEntry& GetShapeMesh(const Hash& shape_hash, const BoundingBox& bounding_box, |
| const plane3* clip_planes, size_t num_clip_planes, |
| CacheMissMeshGenerator mesh_generator); |
| |
| // Populates |unculled_planes_out| with the planes that clip at least one of |
| // the bounding box corners; other planes are culled, because they cannot |
| // possibly intersect anything within the box. |num_planes_inout| must |
| // initially contain the number of planes in |planes|; when the function |
| // returns it will contain the number of planes in |unculled_planes_out|. |
| // |
| // Returns true if any of the planes clips the entire bounding box, otherwise |
| // return false. If such a plane is encountered, iteration halts immediately |
| // since there would be nothing left within the bounding box for subsequent |
| // planes to clip (this final plane does appear in |unculled_planes_out|). |
| // |
| // Preserves the order of any unculled planes. |
| bool CullPlanesAgainstBoundingBox(const BoundingBox& bounding_box, const plane3* planes, |
| plane3* unculled_planes_out, size_t* num_planes_inout); |
| |
| // Called from EndFrame(); evicts all entries that have not been touched for |
| // kNumFramesBeforeEviction. |
| void TrimCache(); |
| |
| // Return the PaperShapeCacheEntry corresponding to the hash, or nullptr if |
| // none such is present in the cache. |
| PaperShapeCacheEntry* FindEntry(const Hash& hash); |
| |
| // Entry must not already exist. |
| void AddEntry(const Hash& hash, PaperShapeCacheEntry entry); |
| |
| const EscherWeakPtr escher_; |
| HashMap<Hash, PaperShapeCacheEntry> cache_; |
| BatchGpuUploader* uploader_ = nullptr; |
| uint64_t frame_number_ = 0; |
| |
| // Reset every frame. |
| uint64_t cache_hit_count_ = 0; |
| uint64_t cache_hit_after_plane_culling_count_ = 0; |
| uint64_t cache_miss_count_ = 0; |
| |
| PaperRendererShadowType shadow_type_ = PaperRendererShadowType::kNone; |
| }; |
| |
| } // namespace escher |
| |
| #endif // SRC_UI_LIB_ESCHER_PAPER_PAPER_SHAPE_CACHE_H_ |