| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef COBALT_RENDERER_RASTERIZER_COMMON_SURFACE_CACHE_H_ |
| #define COBALT_RENDERER_RASTERIZER_COMMON_SURFACE_CACHE_H_ |
| |
| #include <map> |
| #include <vector> |
| |
| #include "base/hash_tables.h" |
| #include "base/time.h" |
| #include "cobalt/base/c_val_time_interval_entry_stats.h" |
| #include "cobalt/math/size.h" |
| #include "cobalt/render_tree/node.h" |
| #include "cobalt/renderer/rasterizer/common/streaming_best_fit_line.h" |
| |
| namespace cobalt { |
| namespace renderer { |
| namespace rasterizer { |
| namespace common { |
| |
| // A SurfaceCache can be owned and used by a rasterizer in order to cache |
| // surfaces representing sub-trees of a render tree as the rasterizer is |
| // visiting them. The SurfaceCache will manage deciding which nodes to cache |
| // by measuring how long it takes to render each subtree. The cache has |
| // a hard capacity limit given in bytes, and so the SurfaceCache will prioritize |
| // surfaces to cache by which ones have the highest time spent per pixel. |
| // |
| // Clients of SurfaceCache (e.g. rasterizers) should call SurfaceCache::Frame() |
| // once per frame. When this is called, SurfaceCache will reset all "has been |
| // seen this frame" flags, marking any cached surfaces that were not seen as |
| // being purgeable. It will also build a list of all cache candidates for the |
| // next frame. |
| // |
| // As the rasterizer visits each node of the render tree, it may insert lines |
| // like the following in order to use the cache: |
| // |
| // SurfaceCache::Block cache_block(surface_cache, render_tree_node); |
| // if (cache_block.Cached()) return; |
| // |
| // where |surface_cache| is a pointer to a SurfaceCache instance. In this case, |
| // if the node is not already cached, a timer will be started to measure how |
| // long it takes to render. If the node is not in the cache but it is decided |
| // that it should be based on a previous measurement, the SurfaceCache::Block |
| // constructor will activate the recording of the subtree and store the results |
| // into the cache upon the event that SurfaceCache::Block's destructor is |
| // called. If the node is already in the cache, the cached version is rendered |
| // immediately by SurfaceCache::Block's constructor, and then |
| // cache_block.Cached() will return true. |
| |
| class SurfaceCache { |
| public: |
| // An opaque type representing a cached surface. These objects are intended |
| // to be created and destroyed by SurfaceCache::Delegates. |
| class CachedSurface { |
| public: |
| virtual ~CachedSurface() {} |
| }; |
| |
| // Implementations of SurfaceCache::Delegate are responsible for providing all |
| // of the support for how to create/destroy CachedSurface objects, how to |
| // setup the backend rasterizer when it is time to record to the cache, as |
| // well as how to playback a cached surface to a render target. |
| class Delegate { |
| public: |
| // Playback a cached surface. This should exactly reproduce whatever |
| // was recorded within previous calls to StartRecording()/EndRecording(). |
| virtual void ApplySurface(CachedSurface* surface) = 0; |
| |
| // Called to instruct the Delegate to start recording all rasterization |
| // calls within the specified local bounding rectangle (e.g. the results |
| // from calling a render tree node's GetBounds() method). |
| virtual void StartRecording(const math::RectF& local_bounds) = 0; |
| |
| // Called to instruct the Delegate to stop recording and return a |
| // CachedSurface that can be used to playback (via ApplySurface()) the the |
| // rendering calls made since StartRecording() was called. |
| virtual CachedSurface* EndRecording() = 0; |
| |
| // Called to instruct the Delegate that a surface is no longer needed and |
| // can be freed. |
| virtual void ReleaseSurface(CachedSurface* surface) = 0; |
| |
| // Called to query the size a render surface will occupy given its local |
| // size. This will, for example, take into account any scaling that is |
| // induced by the node's ancestors. |
| virtual math::Size GetRenderSize(const math::SizeF& local_size) = 0; |
| |
| // Returns the maximum surface size that can be cached. |
| virtual math::Size MaximumSurfaceSize() = 0; |
| }; |
| |
| private: |
| // The key to our cache's map of which items it has seen before. We use |
| // the address of a render node along with its on-screen render size to |
| // determine whether there is a cache match or not. |
| struct NodeMapKey { |
| NodeMapKey() {} |
| NodeMapKey(render_tree::Node* node, const math::Size& render_size) |
| : node(node), render_size(render_size) {} |
| |
| bool operator<(const NodeMapKey& rhs) const { |
| return (node == rhs.node |
| ? (render_size.width() == rhs.render_size.width() |
| ? render_size.height() < rhs.render_size.height() |
| : render_size.width() < rhs.render_size.width()) |
| : node < rhs.node); |
| } |
| |
| render_tree::Node* node; |
| math::Size render_size; |
| }; |
| // NodeData contains a set of data stored for every node that we have seen |
| // and helps determine whether that node should be cached or not, or if it |
| // is already cached. |
| struct NodeData { |
| explicit NodeData(const NodeMapKey& key) |
| : render_size(key.render_size), |
| surface(NULL), |
| visited_this_frame(true), |
| consecutive_frames_visited(0), |
| is_cache_candidate(false), |
| node(key.node) {} |
| |
| // The size in bytes occupied by a cached surface representing this node. |
| int size_in_bytes() { return render_size.GetArea() * 4; } |
| |
| // Returns whether or not this node is cached or not. |
| bool cached() const { return surface != NULL; } |
| |
| // Creates a key from the NodeData. |
| NodeMapKey key() const { return NodeMapKey(node.get(), render_size); } |
| |
| // Tracks how long it last took to render this node. |
| base::TimeDelta duration; |
| |
| // Tracks the render size of this node, which may be different than |
| // node->GetBounds() because of ancestor node transformations. |
| math::Size render_size; |
| |
| // If the surface is cached, this will point to the CachedSurface object |
| // returned by the Delegate. Otherwise, this will be null. |
| CachedSurface* surface; |
| |
| // Was this node visited since SurfaceCache::Frame() was last called? |
| bool visited_this_frame; |
| |
| // How many frames was this node visited consecutively? |
| int consecutive_frames_visited; |
| |
| // True if this node should be inserted into the cache next time it is |
| // encountered. |
| bool is_cache_candidate; |
| |
| // A reference to the actual node. |
| scoped_refptr<render_tree::Node> node; |
| }; |
| typedef std::map<NodeMapKey, NodeData> NodeMap; |
| |
| public: |
| // The main public interface to SurfaceCache is via SurfaceCache::Block. |
| // A block's lifetime should surround all rendering calls needed to render |
| // a render tree node. Blocks are responsible for measuring node render |
| // times, starting/stopping node recordings, and playing back cached nodes. |
| // Most of the logic lives in Block's constructor and destructor. |
| class Block { |
| public: |
| Block(SurfaceCache* surface_cache, render_tree::Node* node) { |
| // We like this to be inlined so that this call makes very little |
| // performance impact if |surface_cache| is null. |
| cache_ = surface_cache; |
| if (!cache_) { |
| // If there is no cache, we have nothing to do besides indicate that we |
| // are not recording and the surface for this node is not already |
| // cached. |
| state_ = kStateNotRecording; |
| return; |
| } else { |
| InitBlock(node); |
| } |
| } |
| ~Block() { |
| // We like this to be inlined so that this call makes very little |
| // performance impact if |cache_| is null. |
| if (cache_) { |
| ShutdownBlock(); |
| } |
| } |
| |
| // Returns true if the block represents a cached node. This should be |
| // called by clients to return early since Cached() returning true implies |
| // that Block::Block() has already applied the cached surface. |
| bool Cached() const { return state_ == kStateAlreadyCached; } |
| |
| private: |
| enum State { |
| kStateAlreadyCached, |
| kStateRecording, |
| kStateNotRecording, |
| kStateInvalid |
| }; |
| void InitBlock(render_tree::Node* node); |
| void ShutdownBlock(); |
| |
| // If any blocks on the stack are performing any timing, this method |
| // iterates through them all and increases the duration. This is done to |
| // simulate actual render times of cached sub trees when simply playing back |
| // the cached surface. |
| void IncreaseAncestorBlockDurations(const base::TimeDelta& duration); |
| |
| State state_; |
| |
| SurfaceCache* cache_; |
| NodeMapKey key_; |
| |
| base::TimeTicks start_time_; |
| NodeData* node_data_; |
| |
| Block* parent_; |
| |
| friend class SurfaceCache; |
| }; |
| |
| // The Delegate is responsible for handling surface management. |
| // |capcity_in_bytes| specifies the hard capacity of the surface cache. |
| SurfaceCache(Delegate* delegate, size_t capacity_in_bytes); |
| ~SurfaceCache(); |
| |
| // This should be called by clients (e.g. rasterizers) once per frame, to |
| // reset the "seen" flags of the cache and determine what is eligible for |
| // caching in subsequent frames. |
| void Frame(); |
| |
| private: |
| class SortByDurationDifferencePerPixel; |
| class SortByDurationDifference; |
| |
| // Helper function to determine which nodes within |nodes| would be best |
| // to cache, given that they fit within |cache_capacity|. This method takes |
| // ownership of |nodes| and modifies it, so its contents may not remain intact |
| // after this call. |apply_surface_time_model| is the model of how much time |
| // it takes to render a cached surface. The resulting set of nodes that |
| // should be cached will be placed in the output parameter, |results|. The |
| // return value is the total size of all cached nodes, and is guaranteed to |
| // be less than |cache_capacity|. |
| static int SelectNodesToCacheNextFrame(const Line& apply_surface_time_model, |
| int cache_capacity, |
| std::vector<NodeData*>* nodes, |
| std::vector<NodeData*>* results); |
| |
| // Returns true if it is possible to cache the specified node. |
| bool MeetsCachingCriteria(const NodeData& node_data, |
| const Line& apply_surface_time_model) const; |
| // Returns true if the node should be purged from the cache despite the fact |
| // that it continues to be seen. |
| bool MeetsCachePurgeCriteria(const NodeData& node_data, |
| const Line& apply_surface_time_model) const; |
| |
| // Returns true if the specified node is in the cache. |
| bool IsCached(const NodeData& node_data) const; |
| // Returns true if the specified node is marked for being cachable. |
| bool IsCacheCandidate(const NodeData& node_data) const; |
| |
| // Attaches a cached surface produced by the delegate to a NodeData object. |
| void SetCachedSurface(NodeData* node_data, CachedSurface* surface); |
| // Releases a cached surface belonging to the specified NodeData object. |
| void FreeCachedSurface(NodeData* to_free); |
| |
| // Removes a node from the list of seen nodes. |
| void RemoveFromSeen(NodeMap::iterator to_remove); |
| |
| // Removes cached nodes in |purge_candidates_| until enough space is |
| // free. |
| void PurgeUntilSpaceAvailable(size_t space_required_in_bytes); |
| |
| Delegate* delegate_; |
| |
| // A list of all nodes that have been seen since the last frame started. |
| NodeMap seen_; |
| |
| // Number of bytes occupied by cached surface pixel data. |
| int total_used_bytes_; |
| |
| // CVals for introspection into cache details. |
| base::CValTimeIntervalEntryStats<int> total_used_bytes_cval_; |
| base::CValTimeIntervalEntryStats<float> apply_surface_duration_overhead_cval_; |
| base::CValTimeIntervalEntryStats<float> |
| apply_surface_duration_per_pixel_cval_; |
| int cached_surfaces_count_; |
| base::CValTimeIntervalEntryStats<float> cached_surfaces_count_cval_; |
| int surfaces_freed_this_frame_; |
| base::CValTimeIntervalEntryStats<float> surfaces_freed_per_frame_cval_; |
| |
| // Maximum number of bytes that can be occupied by cached surface pixel data. |
| int capacity_in_bytes_; |
| |
| // True if we are currently recording to cache. |
| bool recording_; |
| |
| // A link to the currently active block, so that when a new block is created, |
| // its parent pointer can be established. |
| Block* current_block_; |
| |
| // The average duration for calls to Delegate::ApplySurface(). This is useful |
| // when determining if it is worth it or not to cache a render tree. |
| StreamingBestFitLine apply_surface_time_regressor_; |
| |
| // A locally saved value of the maximum surface size, as given by |
| // Delegate::MaximumSurfaceSize(). |
| math::Size maximum_surface_size_; |
| |
| std::vector<NodeData*> to_purge_; |
| |
| // Used temporarily in the scope of Frame(), but stored as a member to avoid |
| // reallocating storage for it. |
| std::vector<NodeData*> to_consider_for_cache_; |
| std::vector<NodeData*> to_add_; |
| |
| friend class Block; |
| }; |
| |
| } // namespace common |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |
| |
| #endif // COBALT_RENDERER_RASTERIZER_COMMON_SURFACE_CACHE_H_ |