version bump
[blackbox.git] / lib / Pen.cc
blob42c97dfd7a2d5507cad37e0607be781204350069
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Pen.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2005 Sean 'Shaleh' Perry <shaleh@debian.org>
4 // Copyright (c) 1997 - 2000, 2002 - 2005
5 // Bradley T Hughes <bhughes at trolltech.com>
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a
8 // copy of this software and associated documentation files (the "Software"),
9 // to deal in the Software without restriction, including without limitation
10 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 // and/or sell copies of the Software, and to permit persons to whom the
12 // Software is furnished to do so, subject to the following conditions:
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 // DEALINGS IN THE SOFTWARE.
25 #include "Pen.hh"
26 #include "Display.hh"
27 #include "Color.hh"
28 #include "Util.hh"
30 #include <algorithm>
32 #include <X11/Xlib.h>
33 #ifdef XFT
34 # include <X11/Xft/Xft.h>
35 #endif
37 #include <assert.h>
38 #include <stdio.h>
40 // #define PENCACHE_DEBUG
43 The Pen cache is comprised of multiple arrays of cache buckets. The
44 bucket arrays are small to allow for fast lookups. A simple hash is
45 used to determine which array is used.
47 The 'cache_size' variable above controls the number of bucket
48 arrays; the 'cache_buckets' variable controls the size of each
49 bucket array.
51 Each bucket stores a hit count, and the buckets are reordered at
52 runtime from highest to lowest hit count. When no suitable item can
53 be found in the cache, the first bucket that is not in use will be
54 used.
56 A cache fault happens if all buckets are in use, at the cache will
57 print a diagnostic message to 'stderr' and call abort(). Normally,
58 a cache fault indicates that the cache was not big enough, and the
59 variables above should be increased accordingly.
61 --------------------------------------------------------------------
63 > +----+----+----+----+----+----+----+----+
64 | | 14 | 10 | 4 | 3 | 2 | 2 | 2 | 0 |
65 | +----+----+----+----+----+----+----+----+
66 cache_size
67 | +----+----+----+----+----+----+----+----+
68 | | 9 | 8 | 7 | 6 | 4 | 3 | 0 | 0 |
69 | +----+----+----+----+----+----+----+----+
71 | ... more bucket arrays ...
74 ^------------- cache_buckets -----------^
76 static const unsigned int cache_size = 32u;
77 static const unsigned int cache_buckets = 8u;
78 static const unsigned int context_count = cache_size * cache_buckets;
80 static int key(unsigned int screen, const bt::Color &color)
82 static unsigned int noise = 0u;
83 // PRNG to introduce entropy, since we don't have a perfect hash
84 noise = (noise * 0x0019660d + 0x3c6ef35f);
85 int k = color.red() ^ color.green() ^ color.blue() ^ noise;
86 return (screen * context_count) + ((k % cache_size) * cache_buckets);
90 namespace bt {
92 class PenCacheContext : public NoCopy {
93 public:
94 inline PenCacheContext(void)
95 : _screen(~0u), _gc(0), _function(0), _linewidth(0), _subwindow(0),
96 _used(false)
97 { }
98 ~PenCacheContext(void);
100 void set(const Color &color, const int function, const int linewidth,
101 const int subwindow);
103 unsigned int _screen;
104 GC _gc;
105 Color _color;
106 int _function;
107 int _linewidth;
108 int _subwindow;
109 bool _used;
112 class PenCacheItem : public NoCopy {
113 public:
114 inline PenCacheItem(void)
115 : _ctx(0), _count(0u), _hits(0u)
117 inline const GC &gc(void) const
118 { return _ctx->_gc; }
120 PenCacheContext *_ctx;
121 unsigned int _count;
122 unsigned int _hits;
125 #ifdef XFT
126 class XftCacheContext : public NoCopy {
127 public:
128 inline XftCacheContext(void)
129 : _screen(~0u), _drawable(None), _xftdraw(0), _used(false)
131 ~XftCacheContext(void);
133 void set(Drawable drawable);
135 unsigned int _screen;
136 Drawable _drawable;
137 XftDraw *_xftdraw;
138 bool _used;
141 class XftCacheItem : public NoCopy {
142 public:
143 inline XftCacheItem(void)
144 : _ctx(0), _count(0u), _hits(0u)
146 inline Drawable drawable(void) const
147 { return _ctx->_drawable; }
148 inline XftDraw *xftdraw(void) const
149 { return _ctx->_xftdraw; }
151 XftCacheContext *_ctx;
152 unsigned int _count;
153 unsigned int _hits;
155 #endif
157 class PenCache {
158 public:
159 PenCache(const Display &display);
160 ~PenCache(void);
162 // cleans up the cache
163 void purge(void);
165 PenCacheContext *contexts;
166 PenCacheItem **cache;
168 PenCacheContext *nextContext(unsigned int screen);
169 void release(PenCacheContext *ctx);
171 PenCacheItem *find(unsigned int screen,
172 const Color &color,
173 int function,
174 int linewidth,
175 int subwindow);
176 void release(PenCacheItem *item);
178 #ifdef XFT
179 XftCacheContext *xftcontexts;
180 XftCacheItem **xftcache;
182 XftCacheContext *nextXftContext(unsigned int screen);
183 void release(XftCacheContext *ctx);
185 XftCacheItem *findXft(unsigned int screen, Drawable drawable);
186 void release(XftCacheItem *xftitem);
187 #endif
189 const Display &_display;
190 const unsigned int cache_total_size;
194 static PenCache *pencache = 0;
197 void createPenCache(const Display &display)
199 assert(pencache == 0);
200 pencache = new PenCache(display);
204 void destroyPenCache(void) {
205 delete pencache;
206 pencache = 0;
209 } // namespace bt
212 bt::PenCacheContext::~PenCacheContext(void) {
213 if (_gc)
214 XFreeGC(pencache->_display.XDisplay(), _gc);
215 _gc = 0;
219 void bt::PenCacheContext::set(const Color &color,
220 const int function,
221 const int linewidth,
222 const int subwindow) {
223 XGCValues gcv;
224 _color = color;
225 gcv.foreground = _color.pixel(_screen);
226 _function = gcv.function = function;
227 _linewidth = gcv.line_width = linewidth;
228 _subwindow = gcv.subwindow_mode = subwindow;
230 unsigned long mask =
231 (GCForeground | GCFunction | GCLineWidth | GCSubwindowMode);
233 XChangeGC(pencache->_display.XDisplay(), _gc, mask, &gcv);
237 #ifdef XFT
238 bt::XftCacheContext::~XftCacheContext(void) {
239 if (_xftdraw)
240 XftDrawDestroy(_xftdraw);
241 _xftdraw = 0;
245 void bt::XftCacheContext::set(Drawable drawable) {
246 XftDrawChange(_xftdraw, drawable);
247 _drawable = drawable;
249 #endif
252 bt::PenCache::PenCache(const Display &display)
253 : _display(display),
254 cache_total_size(context_count * _display.screenCount())
256 unsigned int i;
258 contexts = new PenCacheContext[cache_total_size];
259 cache = new PenCacheItem*[cache_total_size];
260 for (i = 0; i < cache_total_size; ++i) {
261 cache[i] = new PenCacheItem;
264 #ifdef XFT
265 xftcontexts = new XftCacheContext[cache_total_size];
266 xftcache = new XftCacheItem*[cache_total_size];
267 for (i = 0; i < cache_total_size; ++i) {
268 xftcache[i] = new XftCacheItem;
270 #endif
274 bt::PenCache::~PenCache(void) {
275 std::for_each(cache, cache + cache_total_size, PointerAssassin());
276 delete [] cache;
277 delete [] contexts;
279 #ifdef XFT
280 std::for_each(xftcache, xftcache + cache_total_size, PointerAssassin());
281 delete [] xftcache;
282 delete [] xftcontexts;
283 #endif
287 void bt::PenCache::purge(void) {
288 for (unsigned int i = 0; i < cache_total_size; ++i) {
289 PenCacheItem *d = cache[ i ];
291 if (d->_ctx && d->_count == 0) {
292 #ifdef PENCACHE_DEBUG
293 fprintf(stderr, "bt::PenCache: GC : context %03u release\n", i);
294 #endif
295 release(d->_ctx);
296 d->_ctx = 0;
302 bt::PenCacheContext *bt::PenCache::nextContext(unsigned int screen) {
303 Window hd = pencache->_display.screenInfo(screen).rootWindow();
305 PenCacheContext *c;
306 unsigned int i;
307 for (i = 0; i < cache_total_size; ++i) {
308 c = contexts + i;
310 if (!c->_gc) {
311 #ifdef PENCACHE_DEBUG
312 fprintf(stderr, "bt::PenCache: GC : context %03u create\n", i);
313 #endif
314 c->_gc = XCreateGC(pencache->_display.XDisplay(), hd, 0, 0);
315 c->_used = false;
316 c->_screen = screen;
318 if (!c->_used && c->_screen == screen)
319 return c;
322 fprintf(stderr, "bt::PenCache: context fault at %u of %u\n",
323 i, cache_total_size);
324 abort();
325 return 0; // not reached
329 void bt::PenCache::release(PenCacheContext *ctx) {
330 ctx->_used = false;
331 ctx->_color.deallocate(); // allows unused colors to be freed
335 bt::PenCacheItem *bt::PenCache::find(unsigned int screen,
336 const Color &color,
337 int function,
338 int linewidth,
339 int subwindow) {
340 int k = key(screen, color);
341 unsigned int i = 0; // loop variable
342 PenCacheItem *c = cache[ k ], *prev = 0;
345 this will either loop cache_buckets times then return/abort or
346 it will stop matching
348 while (c->_ctx
349 && (c->_ctx->_color != color
350 || c->_ctx->_function != function
351 || c->_ctx->_linewidth != linewidth
352 || c->_ctx->_subwindow != subwindow)) {
353 if (i < (cache_buckets - 1)) {
354 prev = c;
355 c = cache[ ++k ];
356 ++i;
357 continue;
359 if (c->_count == 0 && c->_ctx->_screen == screen) {
360 #ifdef PENCACHE_DEBUG
361 fprintf(stderr, "bt::PenCache: GC : key %03d hijack\n", k);
362 #endif
363 // use this cache item
364 c->_ctx->set(color, function, linewidth, subwindow);
365 c->_ctx->_used = true;
366 c->_count = 1;
367 c->_hits = 1;
368 return c;
370 // cache fault!
371 fprintf(stderr,
372 "bt::PenCache: GC : cache fault at %d, "
373 "count: %u, screen: %u, item screen: %u\n",
374 k, c->_count, screen, c->_ctx->_screen);
375 // let's try again
376 k = key(screen, color);
377 i = 0;
378 c = cache[k];
381 if (c->_ctx) {
382 #ifdef PENCACHE_DEBUG
383 fprintf(stderr, "bt::PenCache: GC : key %03d cache hit\n", k);
384 #endif
385 // reuse existing context
386 c->_count++;
387 c->_hits++;
388 if (prev && c->_hits > prev->_hits) {
389 cache[ k ] = prev;
390 cache[ k - 1 ] = c;
392 } else {
393 c->_ctx = nextContext(screen);
394 #ifdef PENCACHE_DEBUG
395 fprintf(stderr, "bt::PenCache: GC : key %03d new context\n", k);
396 #endif
397 c->_ctx->set(color, function, linewidth, subwindow);
398 c->_ctx->_used = true;
399 c->_count = 1;
400 c->_hits = 1;
403 return c;
407 void bt::PenCache::release(PenCacheItem *item)
408 { --item->_count; }
411 #ifdef XFT
412 bt::XftCacheContext *bt::PenCache::nextXftContext(unsigned int screen) {
413 const ScreenInfo &screeninfo = _display.screenInfo(screen);
415 XftCacheContext *c;
416 unsigned int i;
417 for (i = 0; i < cache_total_size; ++i) {
418 c = xftcontexts + i;
420 if (!c->_xftdraw) {
421 #ifdef PENCACHE_DEBUG
422 fprintf(stderr, "bt::PenCache: Xft: context %03u create\n", i);
423 #endif
424 c->_xftdraw =
425 XftDrawCreate(_display.XDisplay(), screeninfo.rootWindow(),
426 screeninfo.visual(), screeninfo.colormap());
427 c->_used = false;
428 c->_screen = screen;
430 if (!c->_used && c->_screen == screen)
431 return c;
434 fprintf(stderr, "bt::PenCache: Xft context fault at %u of %u\n",
435 i, cache_total_size);
436 abort();
437 return 0; // not reached
441 void bt::PenCache::release(XftCacheContext *context)
442 { context->_used = false; }
445 bt::XftCacheItem *bt::PenCache::findXft(unsigned int screen,
446 Drawable drawable) {
447 int k = (screen * context_count) + ((drawable % cache_size) * cache_buckets);
448 unsigned int i = 0; // loop variable
449 XftCacheItem *c = xftcache[ k ], *prev = 0;
452 this will either loop cache_buckets times then return/abort or
453 it will stop matching
455 while (c->_ctx &&
456 (c->_ctx->_drawable != drawable || c->_ctx->_screen != screen)) {
457 if (i < (cache_buckets - 1)) {
458 prev = c;
459 c = xftcache[ ++k ];
460 ++i;
461 continue;
463 if (c->_count == 0 && c->_ctx->_screen == screen) {
464 #ifdef PENCACHE_DEBUG
465 fprintf(stderr, "bt::PenCache: Xft: key %03d hijack\n", k);
466 #endif
467 // use this cache item
468 if (drawable != c->_ctx->_drawable)
469 c->_ctx->set(drawable);
470 c->_ctx->_used = true;
471 c->_count = 1;
472 c->_hits = 1;
473 return c;
475 // cache fault... try
476 fprintf(stderr,
477 "bt::PenCache: Xft cache fault at %d\n"
478 " count: %u, screen: %u, item screen: %u\n",
479 k, c->_count, screen, c->_ctx->_screen);
480 abort();
483 if (c->_ctx) {
484 #ifdef PENCACHE_DEBUG
485 fprintf(stderr, "bt::PenCache: Xft: key %03d cache hit\n", k);
486 #endif
487 // reuse existing context
488 if (drawable != c->_ctx->_drawable)
489 c->_ctx->set(drawable);
490 c->_count++;
491 c->_hits++;
492 if (prev && c->_hits > prev->_hits) {
493 xftcache[ k ] = prev;
494 xftcache[ k - 1 ] = c;
496 } else {
497 c->_ctx = nextXftContext(screen);
498 #ifdef PENCACHE_DEBUG
499 fprintf(stderr, "bt::PenCache: Xft: key %03d new context\n", k);
500 #endif
501 c->_ctx->set(drawable);
502 c->_ctx->_used = true;
503 c->_count = 1;
504 c->_hits = 1;
507 return c;
511 void bt::PenCache::release(XftCacheItem *xftitem)
512 { --xftitem->_count; }
513 #endif
516 bt::Pen::Pen(unsigned int screen_, const Color &color_)
517 : _screen(screen_), _color(color_), _function(GXcopy), _linewidth(0),
518 _subwindow(ClipByChildren), _item(0), _xftitem(0)
522 bt::Pen::~Pen(void) {
523 if (_item)
524 pencache->release(_item);
525 _item = 0;
527 #ifdef XFT
528 if (_xftitem)
529 pencache->release(_xftitem);
530 _xftitem = 0;
531 #endif
535 void bt::Pen::setGCFunction(int function) {
536 if (_item)
537 pencache->release(_item);
538 _item = 0;
540 _function = function;
544 void bt::Pen::setLineWidth(int linewidth) {
545 if (_item)
546 pencache->release(_item);
547 _item = 0;
549 _linewidth = linewidth;
553 void bt::Pen::setSubWindowMode(int subwindow) {
554 if (_item)
555 pencache->release(_item);
556 _item = 0;
558 _subwindow = subwindow;
562 ::Display *bt::Pen::XDisplay(void) const
563 { return pencache->_display.XDisplay(); }
566 const bt::Display &bt::Pen::display(void) const
567 { return pencache->_display; }
570 const GC &bt::Pen::gc(void) const {
571 if (!_item) {
572 _item = pencache->find(_screen, _color,
573 _function, _linewidth, _subwindow);
575 assert(_item != 0);
576 return _item->gc();
580 XftDraw *bt::Pen::xftDraw(Drawable drawable) const {
581 #ifdef XFT
582 if (_xftitem && _xftitem->drawable() != drawable) {
583 pencache->release(_xftitem);
584 _xftitem = 0;
586 if (!_xftitem) {
587 _xftitem = pencache->findXft(_screen, drawable);
589 assert(_xftitem != 0);
590 return _xftitem->xftdraw();
591 #else
592 return 0;
593 #endif
596 void bt::Pen::clearCache(void)
597 { pencache->purge(); }