9f350c27ae0733a76a472604f53abb9e06e41726
[sxemacs] / src / ui / X11 / xgccache.c
1 /* Efficient caching of X GCs (graphics contexts).
2    Copyright (C) 1993 Free Software Foundation, Inc.
3    Copyright (C) 1994, 1995 Board of Trustees, University of Illinois.
4
5 This file is part of SXEmacs
6
7 SXEmacs 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 3 of the License, or
10 (at your option) any later version.
11
12 SXEmacs 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
18 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
19
20
21 /* Synched up with: Not in FSF. */
22
23 /* Emacs uses a lot of different display attributes; for example, assume
24    that only four fonts are in use (normal, bold, italic, and bold-italic).
25    Then assume that one stipple or background is used for text selections,
26    and another is used for highlighting mousable regions.  That makes 16
27    GCs already.  Add in the fact that another GC may be needed to display
28    the text cursor in any of those regions, and you've got 32.  Add in
29    more fonts, and it keeps increasing exponentially.
30
31    We used to keep these GCs in a cache of merged (fully qualified) faces.
32    However, a lot of other code in xterm.c used XChangeGC of existing GCs,
33    which is kind of slow and kind of random.  Also, managing the face cache
34    was tricky because it was hard to know when a face was no longer visible
35    on the frame -- we had to mark all frames as garbaged whenever a face
36    was changed, which caused an unpleasant amount of flicker (since faces are
37    created/destroyed (= changed) whenever a frame is created/destroyed.
38
39    So this code maintains a cache at the GC level instead of at the face
40    level.  There is an upper limit on the size of the cache, after which we
41    will stop creating GCs and start reusing them (reusing the least-recently-
42    used ones first).  So if faces get changed, their GCs will eventually be
43    recycled.  Also more sharing of GCs is possible.
44
45    This code uses hash tables.  It could be that, if the cache size is small
46    enough, a linear search might be faster; but I doubt it, since we need
47    `equal' comparisons, not `eq', and I expect that the optimal cache size
48    will be ~100.
49
50    Written by jwz, 14 jun 93
51  */
52
53 #include <config.h>
54 #include <X11/Xlib.h>
55 #include "xgccache.h"
56
57 #define GC_CACHE_SIZE 100
58
59 #define GCCACHE_HASH
60
61 #ifdef GCCACHE_HASH
62 #include "lisp.h"
63 #include "hash.h"
64 #endif
65
66 struct gcv_and_mask {
67         XGCValues gcv;
68         unsigned long mask;
69 };
70
71 struct gc_cache_cell {
72         GC gc;
73         struct gcv_and_mask gcvm;
74         struct gc_cache_cell *prev, *next;
75 };
76
77 struct gc_cache {
78         Display *dpy;           /* used only as arg to XCreateGC/XFreeGC */
79         Window window;          /* used only as arg to XCreateGC */
80         int size;
81         struct gc_cache_cell *head;
82         struct gc_cache_cell *tail;
83 #ifdef GCCACHE_HASH
84         struct hash_table *table;
85 #endif
86
87         int create_count;
88         int delete_count;
89 };
90
91 #ifdef GCCACHE_HASH
92 static long unsigned int
93 gc_cache_hash(const void *arg)
94 {
95         const struct gcv_and_mask *gcvm = (const struct gcv_and_mask *)arg;
96         const long unsigned int *longs = (const long unsigned int*)&gcvm->gcv;
97         long unsigned int hash = gcvm->mask;
98         size_t i;
99         /* This could look at the mask and only use the used slots in the
100            hash code.  That would win in that we wouldn't have to initialize
101            every slot of the gcv when calling gc_cache_lookup.  But we need
102            the hash function to be as fast as possible; some timings should
103            be done. */
104         for (i = 0; i < (sizeof(XGCValues) / sizeof(long unsigned int)); i++) {
105                 hash = (hash << 1) ^ *longs++;
106         }
107         return hash;
108 }
109
110 #endif                          /* GCCACHE_HASH */
111
112 static int gc_cache_eql(const void *arg1, const void *arg2)
113 {
114         /* See comment in gc_cache_hash */
115         return !memcmp(arg1, arg2, sizeof(struct gcv_and_mask));
116 }
117
118 struct gc_cache *make_gc_cache(Display * dpy, Window window)
119 {
120         struct gc_cache *cache = xnew(struct gc_cache);
121         cache->dpy = dpy;
122         cache->window = window;
123         cache->size = 0;
124         cache->head = cache->tail = 0;
125         cache->create_count = cache->delete_count = 0;
126 #ifdef GCCACHE_HASH
127         cache->table =
128             make_general_hash_table(GC_CACHE_SIZE, gc_cache_hash, gc_cache_eql);
129 #endif
130         return cache;
131 }
132
133 void free_gc_cache(struct gc_cache *volatile cache)
134 {
135         struct gc_cache_cell *volatile rest, *volatile next;
136         rest = cache->head;
137         while (rest) {
138                 XFreeGC(cache->dpy, rest->gc);
139                 next = rest->next;
140                 xfree(rest);
141                 rest = next;
142         }
143 #ifdef GCCACHE_HASH
144         free_hash_table(cache->table);
145 #endif
146         xfree(cache);
147 }
148
149 GC gc_cache_lookup(struct gc_cache *cache, XGCValues * gcv, unsigned long mask)
150 {
151         struct gc_cache_cell *cell = NULL, *next = NULL, *prev = NULL;
152         struct gcv_and_mask gcvm;
153
154         if ((!!cache->head) != (!!cache->tail))
155                 abort();
156         else if (cache->head && (cache->head->prev || cache->tail->next))
157                 abort();
158         else {
159                 gcvm.mask = mask;
160                 gcvm.gcv = *gcv;        /* this copies... */
161
162 #ifdef GCCACHE_HASH
163
164                 if (gethash(&gcvm, cache->table, 
165                             (const void **)((void*)&cell)))
166 #else                           /* !GCCACHE_HASH */
167
168                 /* start at the end (most recently used) */
169                 cell = cache->tail;     
170                 while (cell) {
171                         if (gc_cache_eql(&gcvm, &cell->gcvm))
172                                 break;
173                         else
174                                 cell = cell->prev;
175                 }
176
177                 /* #### This whole file needs some serious overhauling. */
178                 if (!(mask | GCTile) && cell->gc->values.tile)
179                         cell = 0;
180                 else if (!(mask | GCStipple) && cell->gc->values.stipple)
181                         cell = 0;
182                 
183                 if (cell)
184 #endif                          /* !GCCACHE_HASH */
185
186                 {
187                         /* Found a cell.  Move this cell to the end of
188                            the list, so that it will be less likely to
189                            be collected than a cell that was accessed
190                            less recently.
191                         */
192                         if (cell && cell == cache->tail)
193                                 return cell->gc;
194                         
195                         next = cell->next;
196                         prev = cell->prev;
197                         if (prev)
198                                 prev->next = next;
199                         if (next)
200                                 next->prev = prev;
201                         if (cache->head == cell)
202                                 cache->head = next;
203                         cell->next = 0;
204                         cell->prev = cache->tail;
205                         cache->tail->next = cell;
206                         cache->tail = cell;
207                         if (cache->head == cell)
208                                 abort();
209                         else if (cell->next)
210                                 abort();
211                         else if (cache->head->prev)
212                                 abort();
213                         else if (cache->tail->next)
214                                 abort();
215                         if (cell)
216                                 return cell->gc;
217                         else
218                                 return NULL;
219                 }
220                 
221                 /* else, cache miss. */
222                 
223                 if (cache->size == GC_CACHE_SIZE)
224                         /* Reuse the first cell on the list
225                            (least-recently-used).  Remove it from the
226                            list, and unhash it from the table.
227                         */
228                 {
229                         cell = cache->head;
230                         cache->head = cell->next;
231                         cache->head->prev = 0;
232                         if (cache->tail == cell)
233                                 cache->tail = 0;        /* only one */
234                         XFreeGC(cache->dpy, cell->gc);
235                         cache->delete_count++;
236 #ifdef GCCACHE_HASH
237                         remhash(&cell->gcvm, cache->table);
238 #endif
239                 } else if (cache->size > GC_CACHE_SIZE)
240                         abort();
241                 else {
242                         /* Allocate a new cell (don't put it in the
243                            list or table yet). */
244                         cell = xnew(struct gc_cache_cell);
245                         cache->size++;
246                 }
247                 if (cell != NULL) {
248                         /* Now we've got a cell (new or reused).  Fill
249                            it in. */
250                         memcpy(&cell->gcvm.gcv, gcv, sizeof(XGCValues));
251                         cell->gcvm.mask = mask;
252                 
253                         /* Put the cell on the end of the list. */
254                         cell->next = 0;
255                         cell->prev = cache->tail;
256                         if (cache->tail)
257                                 cache->tail->next = cell;
258                         cache->tail = cell;
259                         if (!cache->head)
260                                 cache->head = cell;
261                         cache->create_count++;
262 #ifdef GCCACHE_HASH
263                         /* Hash it in the table */
264                         puthash(&cell->gcvm, cell, cache->table);
265 #endif
266                         /* Now make and return the GC. */
267                         cell->gc = XCreateGC(cache->dpy, cache->window, 
268                                              mask, gcv);
269                         /* debug */
270                         assert(cell->gc == gc_cache_lookup(cache, gcv, mask));
271                         return cell->gc;
272                 }
273         }
274         return NULL; /* No cell determined */
275 }
276 \f
277 #ifdef DEBUG_SXEMACS
278
279 void describe_gc_cache(struct gc_cache *cache);
280 void describe_gc_cache(struct gc_cache *cache)
281 {
282         int count = 0;
283         struct gc_cache_cell *cell = cache->head;
284         stderr_out("\nsize:    %d", cache->size);
285         stderr_out("\ncreated: %d", cache->create_count);
286         stderr_out("\ndeleted: %d", cache->delete_count);
287         while (cell) {
288                 struct gc_cache_cell *cell2;
289                 int i = 0;
290                 stderr_out("\n%d:\t0x%lx  GC: 0x%08lx  hash: 0x%08lx\n",
291                            count, (long)cell, (long)cell->gc,
292                            gc_cache_hash(&cell->gcvm));
293                 for (cell2 = cache->head; cell2; cell2 = cell2->next, i++)
294                         if (count != i &&
295                             gc_cache_hash(&cell->gcvm) ==
296                             gc_cache_hash(&cell2->gcvm))
297                                 stderr_out("\tHASH COLLISION with cell %d\n",
298                                            i);
299                 stderr_out("\tmask:       %8lx\n", cell->gcvm.mask);
300
301 #define FROB(field) do {                                                \
302   if ((int)cell->gcvm.gcv.field != (~0))                                \
303     stderr_out ("\t%-12s%8x\n", #field ":", (int)cell->gcvm.gcv.field); \
304 } while (0)
305                 FROB(function);
306                 FROB(plane_mask);
307                 FROB(foreground);
308                 FROB(background);
309                 FROB(line_width);
310                 FROB(line_style);
311                 FROB(cap_style);
312                 FROB(join_style);
313                 FROB(fill_style);
314                 FROB(fill_rule);
315                 FROB(arc_mode);
316                 FROB(tile);
317                 FROB(stipple);
318                 FROB(ts_x_origin);
319                 FROB(ts_y_origin);
320                 FROB(font);
321                 FROB(subwindow_mode);
322                 FROB(graphics_exposures);
323                 FROB(clip_x_origin);
324                 FROB(clip_y_origin);
325                 FROB(clip_mask);
326                 FROB(dash_offset);
327 #undef FROB
328
329                 count++;
330                 if (cell->next && cell == cache->tail)
331                         stderr_out("\nERROR!  tail is here!\n\n");
332                 else if (!cell->next && cell != cache->tail)
333                         stderr_out("\nERROR!  tail is not at the end\n\n");
334                 cell = cell->next;
335         }
336         if (count != cache->size)
337                 stderr_out("\nERROR!  count should be %d\n\n", cache->size);
338 }
339
340 #endif                          /* DEBUG_SXEMACS */