Index: cc/tiles/gpu_image_decode_controller.h |
diff --git a/cc/tiles/gpu_image_decode_controller.h b/cc/tiles/gpu_image_decode_controller.h |
index 5ced06527577704a4c5a71566ef4329b17890ecb..be27f42a5e34bd7be278a8a8f5980d2d9ae647e3 100644 |
--- a/cc/tiles/gpu_image_decode_controller.h |
+++ b/cc/tiles/gpu_image_decode_controller.h |
@@ -24,6 +24,8 @@ namespace cc { |
class ContextProvider; |
+// OVERVIEW: |
+// |
// GpuImageDecodeController handles the decode and upload of images that will |
// be used by Skia's GPU raster path. It also maintains a cache of these |
// decoded/uploaded images for later re-use. |
@@ -49,6 +51,134 @@ class ContextProvider; |
// used in raster. Cache entries for at-raster tasks are marked as such, which |
// prevents future tasks from taking a dependency on them and extending their |
// lifetime longer than is necessary. |
+// |
+// RASTER-SCALE CACHING: |
vmpstr
2016/06/21 20:07:08
Can you move this to a doc? Can you add a section
ericrk
2016/06/22 18:56:38
Added a ref-counting section. Will move to a doc o
|
+// |
+// In order to save memory, images which are going to be scaled may be uploaded |
+// at lower than original resolution. In these cases, we may later need to |
+// re-upload the image at a higher resolution. To handle multiple images of |
+// different scales being in use at the same time, we have a two-part caching |
+// system. |
+// |
+// The first cache, |persistent_cache_|, stores one ImageData per image id. |
+// These ImageDatas are not necessarily associated with a given DrawImage, and |
+// are saved (persisted) even when their ref-count reaches zero (assuming they |
+// fit in the current memory budget). This allows for future re-use of image |
+// resources. |
+// |
+// The second cache, |in_use_cache_|, stores one image data per DrawImage - |
+// this may be the same ImageData that is in the persistent_cache_. These |
+// cache entries are more transient and are deleted as soon as all refs to the |
+// given DrawImage are released (the image is no longer in-use). |
+// |
+// Consider the following two cases, which show the cache-usage patterns. |
+// |
+// Case 1: Larger (1x) scale used before smaller (0.5x) scale. |
+// |
+// Step 1: Create/Find Tasks and ImageData for 1x Scale (GetTaskForImageAndRef) |
+// 1a: No existing cache data exists, 1x ImageData created |
+// 1b: 1x ImageData cached in |persistent_cache_| with image's unique ID as |
+// key. |
+// 1c: 1x ImageData cached in |in_use_cache_| with 1x Scale key. |
+// 1d: 1x Decode task created, cached in 1x ImageData. |
+// 1e: 1x Upload task created, cached in 1x ImageData. |
+// |
+// ╔═══════════════════╗ ╔══════════════════════╗ |
+// ║ Persistent Cache ║ ║ In-Use Cache ║ |
+// ╟───────┬───────────╢ ╟───────────┬──────────╢ |
+// ║ image │ 1x ║ ║ 1x Scale │ 1x ║ |
+// ║ ID Key│ ImageData ║ ║ Key │ ImageData║ |
+// ╚═══════╧═══════════╝ ╚═══════════╧══════════╝ |
+// |
+// Step 2: Create/Find Tasks and ImageData for 0.5x Scale |
+// 2a: Find 1x ImageData in |persistent_cache_|. 0.5x scale can use this. |
+// 2b: 1x ImageData cached in |in_use_cache_| with 0.5x scale key. |
+// |
+// ╔═══════════════════╗ ╔══════════════════════╗ |
+// ║ Persistent Cache ║ ║ In-Use Cache ║ |
+// ╟───────┬───────────╢ ╟───────────┬──────────╢ |
+// ║ image │ 1x ║ ║ 1x Scale │ 1x ║ |
+// ║ ID Key│ ImageData ║ ║ Key │ ImageData║ |
+// ╚═══════╧═══════════╝ ╟───────────┼──────────╢ |
+// ║ 0.5x Scale│ 1x ║ |
+// ║ Key │ ImageData║ |
+// ╚═══════════╧══════════╝ |
+// |
+// Step 4: Draw 1x Scale |
+// 1x scale looked up in |in_use_cache_| via 1x key. |
+// |
+// Step 5: Draw 0.5x Scale |
+// 1x scale looked up in |in_use_cache_| via 0.5x key. |
+// |
+// Step 6: Finished, refs released |
+// Tasks are deleted from ImageData. |
+// Entries are deleted from |in_use_cache_|. |
+// 1x scale preserved in |persistent_cache_|. |
+// |
+// ╔═══════════════════╗ ╔════════════════════╗ |
+// ║ Persistent Cache ║ ║ In-Use Cache ║ |
+// ╟───────┬───────────╢ ╚════════════════════╝ |
+// ║ image │ 1x ║ |
+// ║ ID Key│ ImageData ║ |
+// ╚═══════╧═══════════╝ |
+// |
+// Case 2: Smaller (0.5x) scale used before larger (1x) scale. |
+// |
+// Step 1: Create/Find Tasks and ImageData for 0.5x Scale |
+// (GetTaskForImageAndRef) |
+// 1a: No existing cache data exists, 0.5x ImageData created |
+// 1b: 0.5x ImageData cached in |persistent_cache_| with image's unique ID |
+// as key. |
+// 1c: 0.5x ImageData cached in |in_use_cache_| with 0.5x Scale key. |
+// 1d: 0.5x Decode task created, cached in 0.5x ImageData. |
+// 1e: 0.5x Upload task created, cached in 0.5x ImageData. |
+// |
+// ╔═══════════════════╗ ╔══════════════════════╗ |
+// ║ Persistent Cache ║ ║ In-Use Cache ║ |
+// ╟───────┬───────────╢ ╟───────────┬──────────╢ |
+// ║ image │ 0.5 ║ ║ 0.5x Scale│ 0.5x ║ |
+// ║ ID Key│ ImageData ║ ║ Key │ ImageData║ |
+// ╚═══════╧═══════════╝ ╚═══════════╧══════════╝ |
+// |
+// Step 2: Create/Find Tasks and ImageData for 1x Scale |
+// 2a: Find 0.5x ImageData in |persistent_cache_|. 1x scale can't use this. |
+// 2b: 0.5x scale is about to be replaced by 1x scale, remove it from the |
vmpstr
2016/06/21 20:07:08
When does this happen? What about the case where t
ericrk
2016/06/22 18:56:38
The 0.5x scale is only removed from the persistent
|
+// persistent_cache_. |
+// 2c: 1x ImageData created |
+// 2d: 1x ImageData cached in |persistent_cache_| with image's unique ID as |
+// key. |
+// 2e: 1x ImageData cached in |in_use_cache_| with 1x Scale key. |
+// 2f: 1x Decode task created, cached in 1x ImageData. |
+// 2g: 1x Upload task created, cached in 1x ImageData. |
+// |
+// ╔═══════════════════╗ ╔══════════════════════╗ |
+// ║ Persistent Cache ║ ║ In-Use Cache ║ |
+// ╟───────┬───────────╢ ╟───────────┬──────────╢ |
+// ║ image │ 1x ║ ║ 1x Scale │ 1x ║ |
+// ║ ID Key│ ImageData ║ ║ Key │ ImageData║ |
+// ╚═══════╧═══════════╝ ╟───────────┼──────────╢ |
+// ║ 0.5x Scale│ 0.5x ║ |
+// ║ Key │ ImageData║ |
+// ╚═══════════╧══════════╝ |
+// |
+// Step 4: Draw 0.5x Scale |
+// 0.5x scale looked up in |in_use_cache_| via 0.5x key. |
+// |
+// Step 5: Draw 1x Scale |
+// 1x scale looked up in |in_use_cache_| via 1x key. |
+// |
+// Step 6: Finished, refs released |
+// Tasks are deleted from ImageDatas. |
+// Entries are deleted from |in_use_cache_|. |
+// 1x scale preserved in |persistent_cache_|. |
+// |
+// ╔═══════════════════╗ ╔════════════════════╗ |
+// ║ Persistent Cache ║ ║ In-Use Cache ║ |
+// ╟───────┬───────────╢ ╚════════════════════╝ |
+// ║ image │ 1x ║ |
+// ║ ID Key│ ImageData ║ |
+// ╚═══════╧═══════════╝ |
+// |
class CC_EXPORT GpuImageDecodeController |
: public ImageDecodeController, |
public base::trace_event::MemoryDumpProvider { |
@@ -93,6 +223,7 @@ class CC_EXPORT GpuImageDecodeController |
cached_bytes_limit_ = limit; |
} |
size_t GetBytesUsedForTesting() const { return bytes_used_; } |
+ size_t GetDrawImageSizeForTesting(const DrawImage& image); |
void SetImageDecodingFailedForTesting(const DrawImage& image); |
bool DiscardableIsLockedForTesting(const DrawImage& image); |
@@ -110,13 +241,13 @@ class CC_EXPORT GpuImageDecodeController |
void SetLockedData(std::unique_ptr<base::DiscardableMemory> data); |
void ResetData(); |
base::DiscardableMemory* data() const { return data_.get(); } |
- |
void mark_used() { usage_stats_.used = true; } |
- // May be null if image not yet decoded. |
uint32_t ref_count = 0; |
// Set to true if the image was corrupt and could not be decoded. |
bool decode_failure = false; |
+ // If non-null, this is the pending decode task for this image. |
+ scoped_refptr<TileTask> task; |
private: |
struct UsageStats { |
@@ -149,6 +280,8 @@ class CC_EXPORT GpuImageDecodeController |
// True if the image is counting against our memory limits. |
bool budgeted = false; |
uint32_t ref_count = 0; |
+ // If non-null, this is the pending upload task for this image. |
+ scoped_refptr<TileTask> task; |
private: |
struct UsageStats { |
@@ -164,20 +297,44 @@ class CC_EXPORT GpuImageDecodeController |
UsageStats usage_stats_; |
}; |
- struct ImageData { |
- ImageData(DecodedDataMode mode, size_t size); |
- ~ImageData(); |
+ struct ImageData : public base::RefCounted<ImageData> { |
+ ImageData(DecodedDataMode mode, |
+ size_t size, |
+ int pre_scale_mip_level, |
+ SkFilterQuality pre_scale_filter_quality); |
const DecodedDataMode mode; |
const size_t size; |
bool is_at_raster = false; |
+ // Variables used to identify/track multiple scale levels of a single image. |
+ int pre_scale_mip_level; |
+ SkFilterQuality pre_scale_filter_quality; |
+ bool is_orphaned = false; |
vmpstr
2016/06/21 20:07:08
Set the above variables as well (even though they'
ericrk
2016/06/22 18:56:38
Done.
|
+ |
DecodedImageData decode; |
UploadedImageData upload; |
+ |
+ private: |
+ friend class base::RefCounted<ImageData>; |
+ ~ImageData(); |
}; |
- using ImageDataMRUCache = |
- base::MRUCache<uint32_t, std::unique_ptr<ImageData>>; |
+ // A ref-count and ImageData, used to associate the ImageData with a specific |
+ // DrawImage in the |in_use_cache_|. |
+ struct InUseCacheEntry { |
+ explicit InUseCacheEntry(scoped_refptr<ImageData> image_data); |
+ InUseCacheEntry(const InUseCacheEntry& other); |
+ InUseCacheEntry(InUseCacheEntry&& other); |
+ ~InUseCacheEntry(); |
+ |
+ uint32_t ref_count = 0; |
+ scoped_refptr<ImageData> image_data; |
+ }; |
+ |
+ // Uniquely identifies (without collisions) a specific DrawImage for use in |
+ // the |in_use_cache_|. |
+ using InUseCacheKey = uint64_t; |
// All private functions should only be called while holding |lock_|. Some |
// functions also require the |context_| lock. These are indicated by |
@@ -204,9 +361,19 @@ class CC_EXPORT GpuImageDecodeController |
void DecodeImageIfNecessary(const DrawImage& draw_image, |
ImageData* image_data); |
- std::unique_ptr<GpuImageDecodeController::ImageData> CreateImageData( |
+ scoped_refptr<GpuImageDecodeController::ImageData> CreateImageData( |
const DrawImage& image); |
- SkImageInfo CreateImageInfoForDrawImage(const DrawImage& draw_image) const; |
+ SkImageInfo CreateImageInfoForDrawImage(const DrawImage& draw_image, |
+ int pre_scale_mip_level) const; |
+ |
+ // Finds the ImageData that should be used for the given DrawImage. Looks |
+ // first in the |in_use_cache_|, and then in the |persistent_cache_|. |
vmpstr
2016/06/21 20:07:08
When would it check the persistent_cache? From you
ericrk
2016/06/22 18:56:38
If the image has previously been used, but is not
|
+ ImageData* GetImageDataForDrawImage(const DrawImage& image); |
+ |
+ // Returns true if the given ImageData can be used to draw the specified |
+ // DrawImage. |
+ bool IsCompatibleWithDrawImage(const ImageData* image_data, |
+ const DrawImage& draw_image) const; |
// The following two functions also require the |context_| lock to be held. |
void UploadImageIfNecessary(const DrawImage& draw_image, |
@@ -220,12 +387,14 @@ class CC_EXPORT GpuImageDecodeController |
// All members below this point must only be accessed while holding |lock_|. |
base::Lock lock_; |
- std::unordered_map<uint32_t, scoped_refptr<TileTask>> |
- pending_image_upload_tasks_; |
- std::unordered_map<uint32_t, scoped_refptr<TileTask>> |
- pending_image_decode_tasks_; |
+ // |persistent_cache_| represents the long-lived cache, keeping a certain |
+ // budget of ImageDatas alive even when their ref count reaches zero. |
+ using ImageDataMRUCache = base::MRUCache<uint32_t, scoped_refptr<ImageData>>; |
+ ImageDataMRUCache persistent_cache_; |
- ImageDataMRUCache image_data_; |
+ // |in_use_cache_| represents the in-use (short-lived) cache. Entries are |
+ // cleaned up as soon as their ref count reaches zero. |
+ std::unordered_map<InUseCacheKey, InUseCacheEntry> in_use_cache_; |
size_t cached_items_limit_; |
size_t cached_bytes_limit_; |