1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3    Copyright (C) 2009-2015 Red Hat, Inc.
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 #include <config.h>
19 #include "image-cache.h"
20 #include "red-parse-qxl.h"
21 #include "display-channel.h"
22 
image_cache_find(ImageCache * cache,uint64_t id)23 static ImageCacheItem *image_cache_find(ImageCache *cache, uint64_t id)
24 {
25     ImageCacheItem *item = cache->hash_table[id % IMAGE_CACHE_HASH_SIZE];
26 
27     while (item) {
28         if (item->id == id) {
29             return item;
30         }
31         item = item->next;
32     }
33     return nullptr;
34 }
35 
image_cache_hit(ImageCache * cache,uint64_t id)36 static bool image_cache_hit(ImageCache *cache, uint64_t id)
37 {
38     ImageCacheItem *item;
39     if (!(item = image_cache_find(cache, id))) {
40         return FALSE;
41     }
42 #ifdef IMAGE_CACHE_AGE
43     item->age = cache->age;
44 #endif
45     ring_remove(&item->lru_link);
46     ring_add(&cache->lru, &item->lru_link);
47     return TRUE;
48 }
49 
image_cache_remove(ImageCache * cache,ImageCacheItem * item)50 static void image_cache_remove(ImageCache *cache, ImageCacheItem *item)
51 {
52     ImageCacheItem **now;
53 
54     now = &cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE];
55     for (;;) {
56         spice_assert(*now);
57         if (*now == item) {
58             *now = item->next;
59             break;
60         }
61         now = &(*now)->next;
62     }
63     ring_remove(&item->lru_link);
64     pixman_image_unref(item->image);
65     g_free(item);
66 #ifndef IMAGE_CACHE_AGE
67     cache->num_items--;
68 #endif
69 }
70 
71 #define IMAGE_CACHE_MAX_ITEMS 2
72 
image_cache_put(SpiceImageCache * spice_cache,uint64_t id,pixman_image_t * image)73 static void image_cache_put(SpiceImageCache *spice_cache, uint64_t id, pixman_image_t *image)
74 {
75     ImageCache *cache = SPICE_UPCAST(ImageCache, spice_cache);
76     ImageCacheItem *item;
77 
78 #ifndef IMAGE_CACHE_AGE
79     if (cache->num_items == IMAGE_CACHE_MAX_ITEMS) {
80         SPICE_VERIFY(SPICE_OFFSETOF(ImageCacheItem, lru_link) == 0);
81         ImageCacheItem *tail =
82             SPICE_CONTAINEROF(ring_get_tail(&cache->lru), ImageCacheItem, lru_link);
83         spice_assert(tail);
84         image_cache_remove(cache, tail);
85     }
86 #endif
87 
88     item = g_new(ImageCacheItem, 1);
89     item->id = id;
90 #ifdef IMAGE_CACHE_AGE
91     item->age = cache->age;
92 #else
93     cache->num_items++;
94 #endif
95     item->image = pixman_image_ref(image);
96     ring_item_init(&item->lru_link);
97 
98     item->next = cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE];
99     cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE] = item;
100 
101     ring_add(&cache->lru, &item->lru_link);
102 }
103 
image_cache_get(SpiceImageCache * spice_cache,uint64_t id)104 static pixman_image_t *image_cache_get(SpiceImageCache *spice_cache, uint64_t id)
105 {
106     ImageCache *cache = SPICE_UPCAST(ImageCache, spice_cache);
107 
108     ImageCacheItem *item = image_cache_find(cache, id);
109     if (!item) {
110         spice_error("not found");
111     }
112     return pixman_image_ref(item->image);
113 }
114 
image_cache_init(ImageCache * cache)115 void image_cache_init(ImageCache *cache)
116 {
117     static const SpiceImageCacheOps image_cache_ops = {
118         image_cache_put,
119         image_cache_get,
120     };
121 
122     cache->base.ops = &image_cache_ops;
123     memset(cache->hash_table, 0, sizeof(cache->hash_table));
124     ring_init(&cache->lru);
125 #ifdef IMAGE_CACHE_AGE
126     cache->age = 0;
127 #else
128     cache->num_items = 0;
129 #endif
130 }
131 
image_cache_reset(ImageCache * cache)132 void image_cache_reset(ImageCache *cache)
133 {
134     ImageCacheItem *item;
135 
136     SPICE_VERIFY(SPICE_OFFSETOF(ImageCacheItem, lru_link) == 0);
137     while ((item = SPICE_CONTAINEROF(ring_get_head(&cache->lru), ImageCacheItem, lru_link))) {
138         image_cache_remove(cache, item);
139     }
140 #ifdef IMAGE_CACHE_AGE
141     cache->age = 0;
142 #endif
143 }
144 
145 #define IMAGE_CACHE_DEPTH 4
146 
image_cache_aging(ImageCache * cache)147 void image_cache_aging(ImageCache *cache)
148 {
149     SPICE_VERIFY(SPICE_OFFSETOF(ImageCacheItem, lru_link) == 0);
150 #ifdef IMAGE_CACHE_AGE
151     ImageCacheItem *item;
152 
153     cache->age++;
154     while ((item = (ImageCacheItem *)ring_get_tail(&cache->lru)) &&
155            cache->age - item->age > IMAGE_CACHE_DEPTH) {
156         image_cache_remove(cache, item);
157     }
158 #endif
159 }
160 
image_cache_localize(ImageCache * cache,SpiceImage ** image_ptr,SpiceImage * image_store,Drawable * drawable)161 void image_cache_localize(ImageCache *cache, SpiceImage **image_ptr,
162                           SpiceImage *image_store, Drawable *drawable)
163 {
164     SpiceImage *image = *image_ptr;
165 
166     if (image == nullptr) {
167         spice_assert(drawable != nullptr);
168         spice_assert(drawable->red_drawable->self_bitmap_image != nullptr);
169         *image_ptr = drawable->red_drawable->self_bitmap_image;
170         return;
171     }
172 
173     if (image_cache_hit(cache, image->descriptor.id)) {
174         image_store->descriptor = image->descriptor;
175         image_store->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE;
176         image_store->descriptor.flags = 0;
177         *image_ptr = image_store;
178         return;
179     }
180 
181     switch (image->descriptor.type) {
182     case SPICE_IMAGE_TYPE_QUIC: {
183         image_store->descriptor = image->descriptor;
184         image_store->u.quic = image->u.quic;
185         *image_ptr = image_store;
186 #ifdef IMAGE_CACHE_AGE
187         image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
188 #else
189         if (image_store->descriptor.width * image->descriptor.height >= 640 * 480) {
190             image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
191         }
192 #endif
193         break;
194     }
195     case SPICE_IMAGE_TYPE_BITMAP:
196     case SPICE_IMAGE_TYPE_SURFACE:
197         /* nothing */
198         break;
199     default:
200         spice_error("invalid image type");
201     }
202 }
203 
image_cache_localize_brush(ImageCache * cache,SpiceBrush * brush,SpiceImage * image_store)204 void image_cache_localize_brush(ImageCache *cache, SpiceBrush *brush, SpiceImage *image_store)
205 {
206     if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
207         image_cache_localize(cache, &brush->u.pattern.pat, image_store, nullptr);
208     }
209 }
210 
image_cache_localize_mask(ImageCache * cache,SpiceQMask * mask,SpiceImage * image_store)211 void image_cache_localize_mask(ImageCache *cache, SpiceQMask *mask, SpiceImage *image_store)
212 {
213     if (mask->bitmap) {
214         image_cache_localize(cache, &mask->bitmap, image_store, nullptr);
215     }
216 }
217