1 /* -*- c-basic-offset: 8 -*-
2    rdesktop: A Remote Desktop Protocol client.
3    Cache routines
4    Copyright (C) Matthew Chapman 1999-2005
5    Copyright (C) Jeroen Meijer 2005
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License along
18    with this program; if not, write to the Free Software Foundation, Inc.,
19    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21 
22 #include "rdesktop.h"
23 
24 #undef IS_SET // !!!FIXME!!!
25 
26 /* BITMAP CACHE */
27 #define NUM_ELEMENTS(array) (sizeof(array) / sizeof(array[0]))
28 #define IS_PERSISTENT(id) (This->pstcache_fd[id] > 0)
29 #define TO_TOP -1
30 #define NOT_SET -1
31 #define IS_SET(idx) (idx >= 0)
32 
33 /*
34  * TODO: Test for optimal value of BUMP_COUNT. TO_TOP gives lowest cpu utilisation but using
35  * a positive value will hopefully result in less frequently used bitmaps having a greater chance
36  * of being evicted from the cache, and therby reducing the need to load bitmaps from disk.
37  * (Jeroen)
38  */
39 #define BUMP_COUNT 40
40 
41 /* Setup the bitmap cache lru/mru linked list */
42 void
43 cache_rebuild_bmpcache_linked_list(RDPCLIENT * This, uint8 id, sint16 * idx, int count)
44 {
45 	int n = count, c = 0;
46 	sint16 n_idx;
47 
48 	/* find top, skip evicted bitmaps */
49 	while (--n >= 0 && This->cache.bmpcache[id][idx[n]].bitmap == NULL);
50 	if (n < 0)
51 	{
52 		This->cache.bmpcache_mru[id] = This->cache.bmpcache_lru[id] = NOT_SET;
53 		return;
54 	}
55 
56 	This->cache.bmpcache_mru[id] = idx[n];
57 	This->cache.bmpcache[id][idx[n]].next = NOT_SET;
58 	n_idx = idx[n];
59 	c++;
60 
61 	/* link list */
62 	while (n >= 0)
63 	{
64 		/* skip evicted bitmaps */
65 		while (--n >= 0 && This->cache.bmpcache[id][idx[n]].bitmap == NULL);
66 
67 		if (n < 0)
68 			break;
69 
70 		This->cache.bmpcache[id][n_idx].previous = idx[n];
71 		This->cache.bmpcache[id][idx[n]].next = n_idx;
72 		n_idx = idx[n];
73 		c++;
74 	}
75 
76 	This->cache.bmpcache[id][n_idx].previous = NOT_SET;
77 	This->cache.bmpcache_lru[id] = n_idx;
78 
79 	if (c != This->cache.bmpcache_count[id])
80 	{
81 		error("Oops. %d in bitmap cache linked list, %d in ui cache...\n", c,
82 		      This->cache.bmpcache_count[id]);
83 		exit(1);
84 	}
85 }
86 
87 /* Move a bitmap to a new position in the linked list. */
88 void
89 cache_bump_bitmap(RDPCLIENT * This, uint8 id, uint16 idx, int bump)
90 {
91 	int p_idx, n_idx, n;
92 
93 	if (!IS_PERSISTENT(id))
94 		return;
95 
96 	if (This->cache.bmpcache_mru[id] == idx)
97 		return;
98 
99 	DEBUG_RDP5(("bump bitmap: id=%d, idx=%d, bump=%d\n", id, idx, bump));
100 
101 	n_idx = This->cache.bmpcache[id][idx].next;
102 	p_idx = This->cache.bmpcache[id][idx].previous;
103 
104 	if (IS_SET(n_idx))
105 	{
106 		/* remove */
107 		--This->cache.bmpcache_count[id];
108 		if (IS_SET(p_idx))
109 			This->cache.bmpcache[id][p_idx].next = n_idx;
110 		else
111 			This->cache.bmpcache_lru[id] = n_idx;
112 		if (IS_SET(n_idx))
113 			This->cache.bmpcache[id][n_idx].previous = p_idx;
114 		else
115 			This->cache.bmpcache_mru[id] = p_idx;
116 	}
117 	else
118 	{
119 		p_idx = NOT_SET;
120 		n_idx = This->cache.bmpcache_lru[id];
121 	}
122 
123 	if (bump >= 0)
124 	{
125 		for (n = 0; n < bump && IS_SET(n_idx); n++)
126 		{
127 			p_idx = n_idx;
128 			n_idx = This->cache.bmpcache[id][p_idx].next;
129 		}
130 	}
131 	else
132 	{
133 		p_idx = This->cache.bmpcache_mru[id];
134 		n_idx = NOT_SET;
135 	}
136 
137 	/* insert */
138 	++This->cache.bmpcache_count[id];
139 	This->cache.bmpcache[id][idx].previous = p_idx;
140 	This->cache.bmpcache[id][idx].next = n_idx;
141 
142 	if (p_idx >= 0)
143 		This->cache.bmpcache[id][p_idx].next = idx;
144 	else
145 		This->cache.bmpcache_lru[id] = idx;
146 
147 	if (n_idx >= 0)
148 		This->cache.bmpcache[id][n_idx].previous = idx;
149 	else
150 		This->cache.bmpcache_mru[id] = idx;
151 }
152 
153 /* Evict the least-recently used bitmap from the cache */
154 void
155 cache_evict_bitmap(RDPCLIENT * This, uint8 id)
156 {
157 	uint16 idx;
158 	int n_idx;
159 
160 	if (!IS_PERSISTENT(id))
161 		return;
162 
163 	idx = This->cache.bmpcache_lru[id];
164 	n_idx = This->cache.bmpcache[id][idx].next;
165 	DEBUG_RDP5(("evict bitmap: id=%d idx=%d n_idx=%d bmp=0x%x\n", id, idx, n_idx,
166 		    This->cache.bmpcache[id][idx].bitmap));
167 
168 	ui_destroy_bitmap(This, This->cache.bmpcache[id][idx].bitmap);
169 	--This->cache.bmpcache_count[id];
170 	This->cache.bmpcache[id][idx].bitmap = 0;
171 
172 	This->cache.bmpcache_lru[id] = n_idx;
173 	This->cache.bmpcache[id][n_idx].previous = NOT_SET;
174 
175 	pstcache_touch_bitmap(This, id, idx, 0);
176 }
177 
178 /* Retrieve a bitmap from the cache */
179 HBITMAP
180 cache_get_bitmap(RDPCLIENT * This, uint8 id, uint16 idx)
181 {
182 	if ((id < NUM_ELEMENTS(This->cache.bmpcache)) && (idx < NUM_ELEMENTS(This->cache.bmpcache[0])))
183 	{
184 		if (This->cache.bmpcache[id][idx].bitmap || pstcache_load_bitmap(This, id, idx))
185 		{
186 			if (IS_PERSISTENT(id))
187 				cache_bump_bitmap(This, id, idx, BUMP_COUNT);
188 
189 			return This->cache.bmpcache[id][idx].bitmap;
190 		}
191 	}
192 	else if ((id < NUM_ELEMENTS(This->cache.volatile_bc)) && (idx == 0x7fff))
193 	{
194 		return This->cache.volatile_bc[id];
195 	}
196 
197 	error("get bitmap %d:%d\n", id, idx);
198 	return NULL;
199 }
200 
201 /* Store a bitmap in the cache */
202 void
203 cache_put_bitmap(RDPCLIENT * This, uint8 id, uint16 idx, HBITMAP bitmap)
204 {
205 	HBITMAP old;
206 
207 	if ((id < NUM_ELEMENTS(This->cache.bmpcache)) && (idx < NUM_ELEMENTS(This->cache.bmpcache[0])))
208 	{
209 		old = This->cache.bmpcache[id][idx].bitmap;
210 		if (old != NULL)
211 			ui_destroy_bitmap(This, old);
212 		This->cache.bmpcache[id][idx].bitmap = bitmap;
213 
214 		if (IS_PERSISTENT(id))
215 		{
216 			if (old == NULL)
217 				This->cache.bmpcache[id][idx].previous = This->cache.bmpcache[id][idx].next = NOT_SET;
218 
219 			cache_bump_bitmap(This, id, idx, TO_TOP);
220 			if (This->cache.bmpcache_count[id] > BMPCACHE2_C2_CELLS)
221 				cache_evict_bitmap(This, id);
222 		}
223 	}
224 	else if ((id < NUM_ELEMENTS(This->cache.volatile_bc)) && (idx == 0x7fff))
225 	{
226 		old = This->cache.volatile_bc[id];
227 		if (old != NULL)
228 			ui_destroy_bitmap(This, old);
229 		This->cache.volatile_bc[id] = bitmap;
230 	}
231 	else
232 	{
233 		error("put bitmap %d:%d\n", id, idx);
234 	}
235 }
236 
237 /* Updates the persistent bitmap cache MRU information on exit */
238 void
239 cache_save_state(RDPCLIENT * This)
240 {
241 	uint32 id = 0, t = 0;
242 	int idx;
243 
244 	for (id = 0; id < NUM_ELEMENTS(This->cache.bmpcache); id++)
245 		if (IS_PERSISTENT(id))
246 		{
247 			DEBUG_RDP5(("Saving cache state for bitmap cache %d...", id));
248 			idx = This->cache.bmpcache_lru[id];
249 			while (idx >= 0)
250 			{
251 				pstcache_touch_bitmap(This, id, idx, ++t);
252 				idx = This->cache.bmpcache[id][idx].next;
253 			}
254 			DEBUG_RDP5((" %d stamps written.\n", t));
255 		}
256 }
257 
258 
259 /* FONT CACHE */
260 /* Retrieve a glyph from the font cache */
261 FONTGLYPH *
262 cache_get_font(RDPCLIENT * This, uint8 font, uint16 character)
263 {
264 	FONTGLYPH *glyph;
265 
266 	if ((font < NUM_ELEMENTS(This->cache.fontcache)) && (character < NUM_ELEMENTS(This->cache.fontcache[0])))
267 	{
268 		glyph = &This->cache.fontcache[font][character];
269 		if (glyph->pixmap != NULL)
270 			return glyph;
271 	}
272 
273 	error("get font %d:%d\n", font, character);
274 	return NULL;
275 }
276 
277 /* Store a glyph in the font cache */
278 void
279 cache_put_font(RDPCLIENT * This, uint8 font, uint16 character, uint16 offset,
280 	       uint16 baseline, uint16 width, uint16 height, HGLYPH pixmap)
281 {
282 	FONTGLYPH *glyph;
283 
284 	if ((font < NUM_ELEMENTS(This->cache.fontcache)) && (character < NUM_ELEMENTS(This->cache.fontcache[0])))
285 	{
286 		glyph = &This->cache.fontcache[font][character];
287 		if (glyph->pixmap != NULL)
288 			ui_destroy_glyph(This, glyph->pixmap);
289 
290 		glyph->offset = offset;
291 		glyph->baseline = baseline;
292 		glyph->width = width;
293 		glyph->height = height;
294 		glyph->pixmap = pixmap;
295 	}
296 	else
297 	{
298 		error("put font %d:%d\n", font, character);
299 	}
300 }
301 
302 
303 /* TEXT CACHE */
304 /* Retrieve a text item from the cache */
305 DATABLOB *
306 cache_get_text(RDPCLIENT * This, uint8 cache_id)
307 {
308 	DATABLOB *text;
309 
310 	text = &This->cache.textcache[cache_id];
311 	return text;
312 }
313 
314 /* Store a text item in the cache */
315 void
316 cache_put_text(RDPCLIENT * This, uint8 cache_id, void *data, int length)
317 {
318 	DATABLOB *text;
319 	void * p = malloc(length);
320 
321 	if(p == NULL)
322 		return;
323 
324 	text = &This->cache.textcache[cache_id];
325 	if (text->data != NULL)
326 		free(text->data);
327 	text->data = p;
328 	text->size = length;
329 	memcpy(text->data, data, length);
330 }
331 
332 
333 /* DESKTOP CACHE */
334 /* Retrieve desktop data from the cache */
335 uint8 *
336 cache_get_desktop(RDPCLIENT * This, uint32 offset, int cx, int cy, int bytes_per_pixel)
337 {
338 	int length = cx * cy * bytes_per_pixel;
339 
340 	if (offset > sizeof(This->cache.deskcache))
341 		offset = 0;
342 
343 	if ((offset + length) <= sizeof(This->cache.deskcache))
344 	{
345 		return &This->cache.deskcache[offset];
346 	}
347 
348 	error("get desktop %d:%d\n", offset, length);
349 	return NULL;
350 }
351 
352 /* Store desktop data in the cache */
353 void
354 cache_put_desktop(RDPCLIENT * This, uint32 offset, int cx, int cy, int scanline, int bytes_per_pixel, uint8 * data)
355 {
356 	int length = cx * cy * bytes_per_pixel;
357 
358 	if (offset > sizeof(This->cache.deskcache))
359 		offset = 0;
360 
361 	if ((offset + length) <= sizeof(This->cache.deskcache))
362 	{
363 		cx *= bytes_per_pixel;
364 		while (cy--)
365 		{
366 			memcpy(&This->cache.deskcache[offset], data, cx);
367 			data += scanline;
368 			offset += cx;
369 		}
370 	}
371 	else
372 	{
373 		error("put desktop %d:%d\n", offset, length);
374 	}
375 }
376 
377 
378 /* CURSOR CACHE */
379 /* Retrieve cursor from cache */
380 HCURSOR
381 cache_get_cursor(RDPCLIENT * This, uint16 cache_idx)
382 {
383 	HCURSOR cursor;
384 
385 	if (cache_idx < NUM_ELEMENTS(This->cache.cursorcache))
386 	{
387 		cursor = This->cache.cursorcache[cache_idx];
388 		if (cursor != NULL)
389 			return cursor;
390 	}
391 
392 	error("get cursor %d\n", cache_idx);
393 	return NULL;
394 }
395 
396 /* Store cursor in cache */
397 void
398 cache_put_cursor(RDPCLIENT * This, uint16 cache_idx, HCURSOR cursor)
399 {
400 	HCURSOR old;
401 
402 	if (cache_idx < NUM_ELEMENTS(This->cache.cursorcache))
403 	{
404 		old = This->cache.cursorcache[cache_idx];
405 		if (old != NULL)
406 			ui_destroy_cursor(This, old);
407 
408 		This->cache.cursorcache[cache_idx] = cursor;
409 	}
410 	else
411 	{
412 		error("put cursor %d\n", cache_idx);
413 	}
414 }
415