GTK eradication -- the build chain.
[sxemacs] / src / ui / Gtk / gccache-gtk.c
1 /* Efficient caching of Gtk 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 hashtables.  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    Hacked by William Perry, apr 2000
52  */
53
54 #include <config.h>
55 #include <gtk/gtk.h>
56 #include "lisp.h"
57 #include "gccache-gtk.h"
58
59 #define GC_CACHE_SIZE 100
60
61 #define GCCACHE_HASH
62
63 #ifdef GCCACHE_HASH
64 #include "lisp.h"
65 #include "hash.h"
66 #endif
67
68 struct gcv_and_mask {
69         GdkGCValues gcv;
70         GdkGCValuesMask mask;
71 };
72
73 struct gc_cache_cell {
74         GdkGC *gc;
75         struct gcv_and_mask gcvm;
76         struct gc_cache_cell *prev, *next;
77 };
78
79 struct gc_cache {
80         GdkWindow *window;      /* used only as arg to XCreateGC */
81         int size;
82         struct gc_cache_cell *head;
83         struct gc_cache_cell *tail;
84 #ifdef GCCACHE_HASH
85         struct hash_table *table;
86 #endif
87
88         int create_count;
89         int delete_count;
90 };
91
92 #ifdef GCCACHE_HASH
93 static unsigned long gc_cache_hash(const void *arg)
94 {
95         const struct gcv_and_mask *gcvm = (const struct gcv_and_mask *)arg;
96         unsigned long *longs = (unsigned long *)&gcvm->gcv;
97         unsigned long hash = gcvm->mask;
98         int 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(GdkGCValues) / sizeof(unsigned long)); i++)
105                 hash = (hash << 1) ^ *longs++;
106         return hash;
107 }
108
109 #endif                          /* GCCACHE_HASH */
110
111 static int gc_cache_eql(const void *arg1, const void *arg2)
112 {
113         /* See comment in gc_cache_hash */
114         const struct gcv_and_mask *gcvm1 = (const struct gcv_and_mask *)arg1;
115         const struct gcv_and_mask *gcvm2 = (const struct gcv_and_mask *)arg2;
116
117         return !memcmp(&gcvm1->gcv, &gcvm2->gcv, sizeof(gcvm1->gcv))
118             && gcvm1->mask == gcvm2->mask;
119 }
120
121 struct gc_cache *make_gc_cache(GtkWidget * widget)
122 {
123         struct gc_cache *cache = xnew(struct gc_cache);
124         cache->window = widget->window;
125         cache->size = 0;
126         cache->head = cache->tail = 0;
127         cache->create_count = cache->delete_count = 0;
128 #ifdef GCCACHE_HASH
129         cache->table =
130             make_general_hash_table(GC_CACHE_SIZE, gc_cache_hash, gc_cache_eql);
131 #endif
132         return cache;
133 }
134
135 void free_gc_cache(struct gc_cache *cache)
136 {
137         struct gc_cache_cell *rest, *next;
138         rest = cache->head;
139         while (rest) {
140                 gdk_gc_destroy(rest->gc);
141                 next = rest->next;
142                 xfree(rest);
143                 rest = next;
144         }
145 #ifdef GCCACHE_HASH
146         free_hash_table(cache->table);
147 #endif
148         xfree(cache);
149 }
150
151 GdkGC *gc_cache_lookup(struct gc_cache *cache, GdkGCValues * gcv,
152                        GdkGCValuesMask mask)
153 {
154         struct gc_cache_cell *cell, *next, *prev;
155         struct gcv_and_mask gcvm;
156
157         if ((!!cache->head) != (!!cache->tail))
158                 abort();
159         if (cache->head && (cache->head->prev || cache->tail->next))
160                 abort();
161
162         /* Gdk does not have the equivalent of 'None' for the clip_mask, so
163            we need to check it carefully, or gdk_gc_new_with_values will
164            coredump */
165         if ((mask & GDK_GC_CLIP_MASK) && !gcv->clip_mask) {
166                 mask &= ~GDK_GC_CLIP_MASK;
167         }
168
169         gcvm.mask = mask;
170         gcvm.gcv = *gcv;        /* this copies... */
171
172 #ifdef GCCACHE_HASH
173
174         if (gethash(&gcvm, cache->table, (const void **)&cell))
175 #else                           /* !GCCACHE_HASH */
176
177         cell = cache->tail;     /* start at the end (most recently used) */
178         while (cell) {
179                 if (gc_cache_eql(&gcvm, &cell->gcvm))
180                         break;
181                 else
182                         cell = cell->prev;
183         }
184
185         /* #### This whole file needs some serious overhauling. */
186         if (!(mask | GDK_GC_TILE) && cell->gcvm.gcv.tile)
187                 cell = 0;
188         else if (!(mask | GDK_GC_STIPPLE) && cell->gcvm.gcv.stipple)
189                 cell = 0;
190
191         if (cell)
192 #endif                          /* !GCCACHE_HASH */
193
194         {
195                 /* Found a cell.  Move this cell to the end of the list, so that it
196                    will be less likely to be collected than a cell that was accessed
197                    less recently.
198                  */
199                 if (cell == cache->tail)
200                         return cell->gc;
201
202                 next = cell->next;
203                 prev = cell->prev;
204                 if (prev)
205                         prev->next = next;
206                 if (next)
207                         next->prev = prev;
208                 if (cache->head == cell)
209                         cache->head = next;
210                 cell->next = 0;
211                 cell->prev = cache->tail;
212                 cache->tail->next = cell;
213                 cache->tail = cell;
214                 if (cache->head == cell)
215                         abort();
216                 if (cell->next)
217                         abort();
218                 if (cache->head->prev)
219                         abort();
220                 if (cache->tail->next)
221                         abort();
222                 return cell->gc;
223         }
224
225         /* else, cache miss. */
226
227         if (cache->size == GC_CACHE_SIZE)
228                 /* Reuse the first cell on the list (least-recently-used).
229                    Remove it from the list, and unhash it from the table.
230                  */
231         {
232                 cell = cache->head;
233                 cache->head = cell->next;
234                 cache->head->prev = 0;
235                 if (cache->tail == cell)
236                         cache->tail = 0;        /* only one */
237                 gdk_gc_destroy(cell->gc);
238                 cache->delete_count++;
239 #ifdef GCCACHE_HASH
240                 remhash(&cell->gcvm, cache->table);
241 #endif
242         } else if (cache->size > GC_CACHE_SIZE)
243                 abort();
244         else {
245                 /* Allocate a new cell (don't put it in the list or table yet). */
246                 cell = xnew(struct gc_cache_cell);
247                 cache->size++;
248         }
249
250         /* Now we've got a cell (new or reused).  Fill it in. */
251         memcpy(&cell->gcvm.gcv, gcv, sizeof(GdkGCValues));
252         cell->gcvm.mask = mask;
253
254         /* Put the cell on the end of the list. */
255         cell->next = 0;
256         cell->prev = cache->tail;
257         if (cache->tail)
258                 cache->tail->next = cell;
259         cache->tail = cell;
260         if (!cache->head)
261                 cache->head = cell;
262
263         cache->create_count++;
264 #ifdef GCCACHE_HASH
265         /* Hash it in the table */
266         puthash(&cell->gcvm, cell, cache->table);
267 #endif
268
269         /* Now make and return the GC. */
270         cell->gc = gdk_gc_new_with_values(cache->window, gcv, mask);
271
272         /* debug */
273         assert(cell->gc == gc_cache_lookup(cache, gcv, mask));
274
275         return cell->gc;
276 }