| /* |
| * 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. |
| */ |
| |
| #include "cobalt/renderer/rasterizer/common/scratch_surface_cache.h" |
| |
| #include <limits> |
| |
| #include "base/debug/trace_event.h" |
| |
| namespace cobalt { |
| namespace renderer { |
| namespace rasterizer { |
| namespace common { |
| |
| namespace { |
| |
| // Approximate the memory usage of a given surface size. |
| size_t ApproximateSurfaceMemory(const math::Size& size) { |
| // Here we assume that we use 4 bytes per pixel. |
| return size.width() * size.height() * 4; |
| } |
| |
| } // namespace |
| |
| ScratchSurfaceCache::ScratchSurfaceCache(Delegate* delegate, |
| int cache_capacity_in_bytes) |
| : delegate_(delegate), |
| cache_capacity_in_bytes_(cache_capacity_in_bytes), |
| surface_memory_(0) {} |
| |
| ScratchSurfaceCache::~ScratchSurfaceCache() { |
| DCHECK(surface_stack_.empty()); |
| for (std::vector<Surface*>::iterator iter = unused_surfaces_.begin(); |
| iter != unused_surfaces_.end(); ++iter) { |
| delegate_->DestroySurface(*iter); |
| } |
| } |
| |
| ScratchSurfaceCache::Surface* ScratchSurfaceCache::AcquireScratchSurface( |
| const math::Size& size) { |
| TRACE_EVENT2("cobalt::renderer", |
| "ScratchSurfaceCache::AcquireScratchSurface()", "width", |
| size.width(), "height", size.height()); |
| |
| // First check if we can find a suitable surface in our cache that is at |
| // least the size requested. |
| Surface* surface = FindBestCachedSurface(size); |
| |
| // If we didn't have any suitable surfaces in our cache, create a new one. |
| if (!surface) { |
| // Increase our total memory used on surfaces, and then initiate a purge |
| // to reduce memory to below our cache limit, if necessary. |
| surface_memory_ += ApproximateSurfaceMemory(size); |
| Purge(); |
| |
| // Create the surface. |
| surface = delegate_->CreateSurface(size); |
| |
| if (!surface) { |
| // We were unable to allocate a scratch surface, either because we are |
| // low on memory or because the requested surface has large dimensions. |
| // Return null. |
| surface_memory_ -= ApproximateSurfaceMemory(size); |
| return NULL; |
| } |
| } |
| |
| DCHECK(surface); |
| |
| // Track that we have handed out this surface. |
| surface_stack_.push_back(surface); |
| |
| delegate_->PrepareForUse(surface, size); |
| |
| return surface; |
| } |
| |
| void ScratchSurfaceCache::ReleaseScratchSurface(Surface* surface) { |
| TRACE_EVENT2("cobalt::renderer", |
| "ScratchSurfaceCache::ReleaseScratchSurface()", "width", |
| surface->GetSize().width(), "height", |
| surface->GetSize().height()); |
| |
| DCHECK_EQ(surface_stack_.back(), surface); |
| surface_stack_.pop_back(); |
| |
| // Add this surface to the end (where most recently used surfaces go) of the |
| // unused surfaces list, so that it can be returned by later calls to acquire |
| // a surface. |
| unused_surfaces_.push_back(surface); |
| } |
| |
| namespace { |
| |
| float GetMatchScoreForSurfaceAndSize(ScratchSurfaceCache::Surface* surface, |
| const math::Size& size) { |
| math::Size surface_size = surface->GetSize(); |
| |
| // We use the negated sum of the squared differences between the requested |
| // width/height and the surface width/height as the score. |
| // This promotes returning the smallest surface possible that has a similar |
| // ratio. |
| int width_diff = surface_size.width() - size.width(); |
| int height_diff = surface_size.height() - size.height(); |
| |
| return -width_diff * width_diff - height_diff * height_diff; |
| } |
| |
| } // namespace |
| |
| ScratchSurfaceCache::Surface* ScratchSurfaceCache::FindBestCachedSurface( |
| const math::Size& size) { |
| // Iterate through all cached surfaces to find the one that is the best match |
| // for the given size. The function GetMatchScoreForSurfaceAndSize() is |
| // responsible for assigning a score to a Surface/math::Size pair. |
| float max_surface_score = -std::numeric_limits<float>::infinity(); |
| std::vector<Surface*>::iterator max_iter = unused_surfaces_.end(); |
| for (std::vector<Surface*>::iterator iter = unused_surfaces_.begin(); |
| iter != unused_surfaces_.end(); ++iter) { |
| Surface* current_surface = *iter; |
| math::Size surface_size = current_surface->GetSize(); |
| if (surface_size.width() >= size.width() && |
| surface_size.height() >= size.height()) { |
| float surface_score = |
| GetMatchScoreForSurfaceAndSize(current_surface, size); |
| |
| if (surface_score > max_surface_score) { |
| max_surface_score = surface_score; |
| max_iter = iter; |
| } |
| } |
| } |
| |
| // If any of the cached unused surfaces had at least the specified |
| // width/height, return the one that had the highest score. |
| if (max_iter != unused_surfaces_.end()) { |
| Surface* surface = *max_iter; |
| // Remove this surface from the list of unused surfaces. |
| unused_surfaces_.erase(max_iter); |
| // Return the cached surface. |
| return surface; |
| } else { |
| // Otherwise return NULL to indicate that we do not have any suitable cached |
| // surfaces. |
| return NULL; |
| } |
| } |
| |
| void ScratchSurfaceCache::Purge() { |
| // Delete surfaces from the front (least recently used) of |unused_surfaces_| |
| // until we have deleted all surfaces or lowered our memory usage to under |
| // |cache_capacity_in_bytes_|. |
| while (!unused_surfaces_.empty() && |
| surface_memory_ > cache_capacity_in_bytes_) { |
| Surface* to_free = unused_surfaces_.front(); |
| math::Size surface_size = to_free->GetSize(); |
| surface_memory_ -= ApproximateSurfaceMemory( |
| math::Size(surface_size.width(), surface_size.height())); |
| delegate_->DestroySurface(to_free); |
| unused_surfaces_.erase(unused_surfaces_.begin()); |
| } |
| } |
| |
| } // namespace common |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |