don't deref null
[blackbox.git] / lib / Pen.cc
blobe05101ea6e9bdc0d202a308f787a140996394089
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;
81 namespace bt {
83 class PenCacheContext : public NoCopy {
84 public:
85 inline PenCacheContext(void)
86 : _screen(~0u), _gc(0), _function(0), _linewidth(1), _subwindow(0),
87 _used(false)
88 { }
89 ~PenCacheContext(void);
91 void set(const Color &color, const int function, const int linewidth,
92 const int subwindow);
94 unsigned int _screen;
95 GC _gc;
96 Color _color;
97 int _function;
98 int _linewidth;
99 int _subwindow;
100 bool _used;
103 class PenCacheItem : public NoCopy {
104 public:
105 inline PenCacheItem(void)
106 : _ctx(0), _count(0u), _hits(0u)
108 inline const GC &gc(void) const
109 { return _ctx->_gc; }
111 PenCacheContext *_ctx;
112 unsigned int _count;
113 unsigned int _hits;
116 #ifdef XFT
117 class XftCacheContext : public NoCopy {
118 public:
119 inline XftCacheContext(void)
120 : _screen(~0u), _drawable(None), _xftdraw(0), _used(false)
122 ~XftCacheContext(void);
124 void set(Drawable drawable);
126 unsigned int _screen;
127 Drawable _drawable;
128 XftDraw *_xftdraw;
129 bool _used;
132 class XftCacheItem : public NoCopy {
133 public:
134 inline XftCacheItem(void)
135 : _ctx(0), _count(0u), _hits(0u)
137 inline Drawable drawable(void) const
138 { return _ctx->_drawable; }
139 inline XftDraw *xftdraw(void) const
140 { return _ctx->_xftdraw; }
142 XftCacheContext *_ctx;
143 unsigned int _count;
144 unsigned int _hits;
146 #endif
148 class PenCache {
149 public:
150 PenCache(const Display &display);
151 ~PenCache(void);
153 // cleans up the cache
154 void purge(void);
156 PenCacheContext *contexts;
157 PenCacheItem **cache;
159 PenCacheContext *nextContext(unsigned int screen);
160 void release(PenCacheContext *ctx);
162 PenCacheItem *find(unsigned int screen,
163 const Color &color,
164 int function,
165 int linewidth,
166 int subwindow);
167 void release(PenCacheItem *item);
169 #ifdef XFT
170 XftCacheContext *xftcontexts;
171 XftCacheItem **xftcache;
173 XftCacheContext *nextXftContext(unsigned int screen);
174 void release(XftCacheContext *ctx);
176 XftCacheItem *findXft(unsigned int screen, Drawable drawable);
177 void release(XftCacheItem *xftitem);
178 #endif
180 const Display &_display;
181 const unsigned int cache_total_size;
185 static PenCache *pencache = 0;
188 void createPenCache(const Display &display)
190 assert(pencache == 0);
191 pencache = new PenCache(display);
195 void destroyPenCache(void) {
196 delete pencache;
197 pencache = 0;
200 } // namespace bt
203 bt::PenCacheContext::~PenCacheContext(void) {
204 if (_gc)
205 XFreeGC(pencache->_display.XDisplay(), _gc);
206 _gc = 0;
210 void bt::PenCacheContext::set(const Color &color,
211 const int function,
212 const int linewidth,
213 const int subwindow) {
214 XGCValues gcv;
215 _color = color;
216 gcv.foreground = _color.pixel(_screen);
217 _function = gcv.function = function;
218 _linewidth = gcv.line_width = linewidth;
219 _subwindow = gcv.subwindow_mode = subwindow;
221 unsigned long mask =
222 (GCForeground | GCFunction | GCLineWidth | GCSubwindowMode);
224 XChangeGC(pencache->_display.XDisplay(), _gc, mask, &gcv);
228 #ifdef XFT
229 bt::XftCacheContext::~XftCacheContext(void) {
230 if (_xftdraw)
231 XftDrawDestroy(_xftdraw);
232 _xftdraw = 0;
236 void bt::XftCacheContext::set(Drawable drawable) {
237 XftDrawChange(_xftdraw, drawable);
238 _drawable = drawable;
240 #endif
243 bt::PenCache::PenCache(const Display &display)
244 : _display(display),
245 cache_total_size(context_count * _display.screenCount())
247 unsigned int i;
249 contexts = new PenCacheContext[cache_total_size];
250 cache = new PenCacheItem*[cache_total_size];
251 for (i = 0; i < cache_total_size; ++i) {
252 cache[i] = new PenCacheItem;
255 #ifdef XFT
256 xftcontexts = new XftCacheContext[cache_total_size];
257 xftcache = new XftCacheItem*[cache_total_size];
258 for (i = 0; i < cache_total_size; ++i) {
259 xftcache[i] = new XftCacheItem;
261 #endif
265 bt::PenCache::~PenCache(void) {
266 std::for_each(cache, cache + cache_total_size, PointerAssassin());
267 delete [] cache;
268 delete [] contexts;
270 #ifdef XFT
271 std::for_each(xftcache, xftcache + cache_total_size, PointerAssassin());
272 delete [] xftcache;
273 delete [] xftcontexts;
274 #endif
278 void bt::PenCache::purge(void) {
279 for (unsigned int i = 0; i < cache_total_size; ++i) {
280 PenCacheItem *d = cache[ i ];
282 if (d->_ctx && d->_count == 0) {
283 #ifdef PENCACHE_DEBUG
284 fprintf(stderr, "bt::PenCache: GC : context %03u release\n", i);
285 #endif
286 release(d->_ctx);
287 d->_ctx = 0;
293 bt::PenCacheContext *bt::PenCache::nextContext(unsigned int screen) {
294 Window hd = pencache->_display.screenInfo(screen).rootWindow();
296 PenCacheContext *c;
297 unsigned int i;
298 for (i = 0; i < cache_total_size; ++i) {
299 c = contexts + i;
301 if (!c->_gc) {
302 #ifdef PENCACHE_DEBUG
303 fprintf(stderr, "bt::PenCache: GC : context %03u create\n", i);
304 #endif
305 c->_gc = XCreateGC(pencache->_display.XDisplay(), hd, 0, 0);
306 c->_used = false;
307 c->_screen = screen;
309 if (!c->_used && c->_screen == screen)
310 return c;
313 fprintf(stderr, "bt::PenCache: context fault at %u of %u\n",
314 i, cache_total_size);
315 abort();
316 return 0; // not reached
320 void bt::PenCache::release(PenCacheContext *ctx) {
321 ctx->_used = false;
322 ctx->_color.deallocate(); // allows unused colors to be freed
326 bt::PenCacheItem *bt::PenCache::find(unsigned int screen,
327 const Color &color,
328 int function,
329 int linewidth,
330 int subwindow) {
331 int k = color.red() ^ color.green() ^ color.blue();
332 k = (screen * context_count) + ((k % cache_size) * cache_buckets);
333 unsigned int i = 0; // loop variable
334 PenCacheItem *c = cache[ k ], *prev = 0;
337 this will either loop cache_buckets times then return/abort or
338 it will stop matching
340 while (c->_ctx
341 && (c->_ctx->_color != color
342 || c->_ctx->_function != function
343 || c->_ctx->_linewidth != linewidth
344 || c->_ctx->_subwindow != subwindow)) {
345 if (i < (cache_buckets - 1)) {
346 prev = c;
347 c = cache[ ++k ];
348 ++i;
349 continue;
351 if (c->_count == 0 && c->_ctx->_screen == screen) {
352 #ifdef PENCACHE_DEBUG
353 fprintf(stderr, "bt::PenCache: GC : key %03d hijack\n", k);
354 #endif
355 // use this cache item
356 c->_ctx->set(color, function, linewidth, subwindow);
357 c->_ctx->_used = true;
358 c->_count = 1;
359 c->_hits = 1;
360 return c;
362 // cache fault!
363 fprintf(stderr,
364 "bt::PenCache: cache fault at %d\n"
365 " count: %u, screen: %u, item screen: %u\n",
366 k, c->_count, screen, c->_ctx->_screen);
367 abort();
370 if (c->_ctx) {
371 #ifdef PENCACHE_DEBUG
372 fprintf(stderr, "bt::PenCache: GC : key %03d cache hit\n", k);
373 #endif
374 // reuse existing context
375 c->_count++;
376 c->_hits++;
377 if (prev && c->_hits > prev->_hits) {
378 cache[ k ] = prev;
379 cache[ k - 1 ] = c;
381 } else {
382 c->_ctx = nextContext(screen);
383 #ifdef PENCACHE_DEBUG
384 fprintf(stderr, "bt::PenCache: GC : key %03d new context\n", k);
385 #endif
386 c->_ctx->set(color, function, linewidth, subwindow);
387 c->_ctx->_used = true;
388 c->_count = 1;
389 c->_hits = 1;
392 return c;
396 void bt::PenCache::release(PenCacheItem *item)
397 { --item->_count; }
400 #ifdef XFT
401 bt::XftCacheContext *bt::PenCache::nextXftContext(unsigned int screen) {
402 const ScreenInfo &screeninfo = _display.screenInfo(screen);
404 XftCacheContext *c;
405 unsigned int i;
406 for (i = 0; i < cache_total_size; ++i) {
407 c = xftcontexts + i;
409 if (!c->_xftdraw) {
410 #ifdef PENCACHE_DEBUG
411 fprintf(stderr, "bt::PenCache: Xft: context %03u create\n", i);
412 #endif
413 c->_xftdraw =
414 XftDrawCreate(_display.XDisplay(), screeninfo.rootWindow(),
415 screeninfo.visual(), screeninfo.colormap());
416 c->_used = false;
417 c->_screen = screen;
419 if (!c->_used && c->_screen == screen)
420 return c;
423 fprintf(stderr, "bt::PenCache: Xft context fault at %u of %u\n",
424 i, cache_total_size);
425 abort();
426 return 0; // not reached
430 void bt::PenCache::release(XftCacheContext *context)
431 { context->_used = false; }
434 bt::XftCacheItem *bt::PenCache::findXft(unsigned int screen,
435 Drawable drawable) {
436 int k = (screen * context_count) + ((drawable % cache_size) * cache_buckets);
437 unsigned int i = 0; // loop variable
438 XftCacheItem *c = xftcache[ k ], *prev = 0;
441 this will either loop cache_buckets times then return/abort or
442 it will stop matching
444 while (c->_ctx &&
445 (c->_ctx->_drawable != drawable || c->_ctx->_screen != screen)) {
446 if (i < (cache_buckets - 1)) {
447 prev = c;
448 c = xftcache[ ++k ];
449 ++i;
450 continue;
452 if (c->_count == 0 && c->_ctx->_screen == screen) {
453 #ifdef PENCACHE_DEBUG
454 fprintf(stderr, "bt::PenCache: Xft: key %03d hijack\n", k);
455 #endif
456 // use this cache item
457 if (drawable != c->_ctx->_drawable)
458 c->_ctx->set(drawable);
459 c->_ctx->_used = true;
460 c->_count = 1;
461 c->_hits = 1;
462 return c;
464 // cache fault... try
465 fprintf(stderr,
466 "bt::PenCache: Xft cache fault at %d\n"
467 " count: %u, screen: %u, item screen: %u\n",
468 k, c->_count, screen, c->_ctx->_screen);
469 abort();
472 if (c->_ctx) {
473 #ifdef PENCACHE_DEBUG
474 fprintf(stderr, "bt::PenCache: Xft: key %03d cache hit\n", k);
475 #endif
476 // reuse existing context
477 if (drawable != c->_ctx->_drawable)
478 c->_ctx->set(drawable);
479 c->_count++;
480 c->_hits++;
481 if (prev && c->_hits > prev->_hits) {
482 xftcache[ k ] = prev;
483 xftcache[ k - 1 ] = c;
485 } else {
486 c->_ctx = nextXftContext(screen);
487 #ifdef PENCACHE_DEBUG
488 fprintf(stderr, "bt::PenCache: Xft: key %03d new context\n", k);
489 #endif
490 c->_ctx->set(drawable);
491 c->_ctx->_used = true;
492 c->_count = 1;
493 c->_hits = 1;
496 return c;
500 void bt::PenCache::release(XftCacheItem *xftitem)
501 { --xftitem->_count; }
502 #endif
505 bt::Pen::Pen(unsigned int screen_, const Color &color_)
506 : _screen(screen_), _color(color_), _function(GXcopy), _linewidth(0),
507 _subwindow(ClipByChildren), _item(0), _xftitem(0)
511 bt::Pen::~Pen(void) {
512 if (_item)
513 pencache->release(_item);
514 _item = 0;
516 #ifdef XFT
517 if (_xftitem)
518 pencache->release(_xftitem);
519 _xftitem = 0;
520 #endif
524 void bt::Pen::setGCFunction(int function) {
525 if (_item)
526 pencache->release(_item);
527 _item = 0;
529 _function = function;
533 void bt::Pen::setLineWidth(int linewidth) {
534 if (_item)
535 pencache->release(_item);
536 _item = 0;
538 _linewidth = linewidth;
542 void bt::Pen::setSubWindowMode(int subwindow) {
543 if (_item)
544 pencache->release(_item);
545 _item = 0;
547 _subwindow = subwindow;
551 ::Display *bt::Pen::XDisplay(void) const
552 { return pencache->_display.XDisplay(); }
555 const bt::Display &bt::Pen::display(void) const
556 { return pencache->_display; }
559 const GC &bt::Pen::gc(void) const {
560 if (!_item) {
561 _item = pencache->find(_screen, _color,
562 _function, _linewidth, _subwindow);
564 assert(_item != 0);
565 return _item->gc();
569 XftDraw *bt::Pen::xftDraw(Drawable drawable) const {
570 #ifdef XFT
571 if (_xftitem && _xftitem->drawable() != drawable) {
572 pencache->release(_xftitem);
573 _xftitem = 0;
575 if (!_xftitem) {
576 _xftitem = pencache->findXft(_screen, drawable);
578 assert(_xftitem != 0);
579 return _xftitem->xftdraw();
580 #else
581 return 0;
582 #endif
585 void bt::Pen::clearCache(void)
586 { pencache->purge(); }