1/*
2 Copyright (c) 2008, 2009 Apple Inc.
3
4 Permission is hereby granted, free of charge, to any person
5 obtaining a copy of this software and associated documentation files
6 (the "Software"), to deal in the Software without restriction,
7 including without limitation the rights to use, copy, modify, merge,
8 publish, distribute, sublicense, and/or sell copies of the Software,
9 and to permit persons to whom the Software is furnished to do so,
10 subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be
13 included in all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 NONINFRINGEMENT.  IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
19 HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 DEALINGS IN THE SOFTWARE.
23
24 Except as contained in this notice, the name(s) of the above
25 copyright holders shall not be used in advertising or otherwise to
26 promote the sale, use or other dealings in this Software without
27 prior written authorization.
28*/
29
30#include <stdbool.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <assert.h>
34#include <pthread.h>
35#include <string.h>
36#include "apple_glx.h"
37#include "apple_glx_context.h"
38#include "apple_glx_drawable.h"
39#include "appledri.h"
40
41static pthread_mutex_t drawables_lock = PTHREAD_MUTEX_INITIALIZER;
42static struct apple_glx_drawable *drawables_list = NULL;
43
44static void
45lock_drawables_list(void)
46{
47   int err;
48
49   err = pthread_mutex_lock(&drawables_lock);
50
51   if (err) {
52      fprintf(stderr, "pthread_mutex_lock failure in %s: %s\n",
53              __func__, strerror(err));
54      abort();
55   }
56}
57
58static void
59unlock_drawables_list(void)
60{
61   int err;
62
63   err = pthread_mutex_unlock(&drawables_lock);
64
65   if (err) {
66      fprintf(stderr, "pthread_mutex_unlock failure in %s: %s\n",
67              __func__, strerror(err));
68      abort();
69   }
70}
71
72struct apple_glx_drawable *
73apple_glx_find_drawable(Display * dpy, GLXDrawable drawable)
74{
75   struct apple_glx_drawable *i, *agd = NULL;
76
77   lock_drawables_list();
78
79   for (i = drawables_list; i; i = i->next) {
80      if (i->drawable == drawable) {
81         agd = i;
82         break;
83      }
84   }
85
86   unlock_drawables_list();
87
88   return agd;
89}
90
91static void
92drawable_lock(struct apple_glx_drawable *agd)
93{
94   int err;
95
96   err = pthread_mutex_lock(&agd->mutex);
97
98   if (err) {
99      fprintf(stderr, "pthread_mutex_lock error: %s\n", strerror(err));
100      abort();
101   }
102}
103
104static void
105drawable_unlock(struct apple_glx_drawable *d)
106{
107   int err;
108
109   err = pthread_mutex_unlock(&d->mutex);
110
111   if (err) {
112      fprintf(stderr, "pthread_mutex_unlock error: %s\n", strerror(err));
113      abort();
114   }
115}
116
117
118static void
119reference_drawable(struct apple_glx_drawable *d)
120{
121   d->lock(d);
122   d->reference_count++;
123   d->unlock(d);
124}
125
126static void
127release_drawable(struct apple_glx_drawable *d)
128{
129   d->lock(d);
130   d->reference_count--;
131   d->unlock(d);
132}
133
134/* The drawables list must be locked prior to calling this. */
135/* Return true if the drawable was destroyed. */
136static bool
137destroy_drawable(struct apple_glx_drawable *d)
138{
139   int err;
140
141   d->lock(d);
142
143   if (d->reference_count > 0) {
144      d->unlock(d);
145      return false;
146   }
147
148   d->unlock(d);
149
150   if (d->previous) {
151      d->previous->next = d->next;
152   }
153   else {
154      /*
155       * The item must be at the head of the list, if it
156       * has no previous pointer.
157       */
158      drawables_list = d->next;
159   }
160
161   if (d->next)
162      d->next->previous = d->previous;
163
164   unlock_drawables_list();
165
166   if (d->callbacks.destroy) {
167      /*
168       * Warning: this causes other routines to be called (potentially)
169       * from surface_notify_handler.  It's probably best to not have
170       * any locks at this point locked.
171       */
172      d->callbacks.destroy(d->display, d);
173   }
174
175   apple_glx_diagnostic("%s: freeing %p\n", __func__, (void *) d);
176
177   /* Stupid recursive locks */
178   while (pthread_mutex_unlock(&d->mutex) == 0);
179
180   err = pthread_mutex_destroy(&d->mutex);
181   if (err) {
182      fprintf(stderr, "pthread_mutex_destroy error: %s\n", strerror(err));
183      abort();
184   }
185
186   free(d);
187
188   /* So that the locks are balanced and the caller correctly unlocks. */
189   lock_drawables_list();
190
191   return true;
192}
193
194/*
195 * This is typically called when a context is destroyed or the current
196 * drawable is made None.
197 */
198static bool
199destroy_drawable_callback(struct apple_glx_drawable *d)
200{
201   bool result;
202
203   d->lock(d);
204
205   apple_glx_diagnostic("%s: %p ->reference_count before -- %d\n", __func__,
206                        (void *) d, d->reference_count);
207
208   d->reference_count--;
209
210   if (d->reference_count > 0) {
211      d->unlock(d);
212      return false;
213   }
214
215   d->unlock(d);
216
217   lock_drawables_list();
218
219   result = destroy_drawable(d);
220
221   unlock_drawables_list();
222
223   return result;
224}
225
226static bool
227is_pbuffer(struct apple_glx_drawable *d)
228{
229   return APPLE_GLX_DRAWABLE_PBUFFER == d->type;
230}
231
232static bool
233is_pixmap(struct apple_glx_drawable *d)
234{
235   return APPLE_GLX_DRAWABLE_PIXMAP == d->type;
236}
237
238static void
239common_init(Display * dpy, GLXDrawable drawable, struct apple_glx_drawable *d)
240{
241   int err;
242   pthread_mutexattr_t attr;
243
244   d->display = dpy;
245   d->reference_count = 0;
246   d->drawable = drawable;
247   d->type = -1;
248
249   err = pthread_mutexattr_init(&attr);
250
251   if (err) {
252      fprintf(stderr, "pthread_mutexattr_init error: %s\n", strerror(err));
253      abort();
254   }
255
256   /*
257    * There are some patterns that require a recursive mutex,
258    * when working with locks that protect the apple_glx_drawable,
259    * and reference functions like ->reference, and ->release.
260    */
261   err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
262
263   if (err) {
264      fprintf(stderr, "error: setting pthread mutex type: %s\n", strerror(err));
265      abort();
266   }
267
268   err = pthread_mutex_init(&d->mutex, &attr);
269
270   if (err) {
271      fprintf(stderr, "pthread_mutex_init error: %s\n", strerror(err));
272      abort();
273   }
274
275   (void) pthread_mutexattr_destroy(&attr);
276
277   d->lock = drawable_lock;
278   d->unlock = drawable_unlock;
279
280   d->reference = reference_drawable;
281   d->release = release_drawable;
282
283   d->destroy = destroy_drawable_callback;
284
285   d->is_pbuffer = is_pbuffer;
286   d->is_pixmap = is_pixmap;
287
288   d->width = -1;
289   d->height = -1;
290   d->row_bytes = 0;
291   d->path[0] = '\0';
292   d->fd = -1;
293   d->buffer = NULL;
294   d->buffer_length = 0;
295
296   d->previous = NULL;
297   d->next = NULL;
298}
299
300static void
301link_tail(struct apple_glx_drawable *agd)
302{
303   lock_drawables_list();
304
305   /* Link the new drawable into the global list. */
306   agd->next = drawables_list;
307
308   if (drawables_list)
309      drawables_list->previous = agd;
310
311   drawables_list = agd;
312
313   unlock_drawables_list();
314}
315
316/*WARNING: this returns a locked and referenced object. */
317bool
318apple_glx_drawable_create(Display * dpy,
319                          int screen,
320                          GLXDrawable drawable,
321                          struct apple_glx_drawable **agdResult,
322                          struct apple_glx_drawable_callbacks *callbacks)
323{
324   struct apple_glx_drawable *d;
325
326   d = calloc(1, sizeof *d);
327
328   if (NULL == d) {
329      perror("malloc");
330      return true;
331   }
332
333   common_init(dpy, drawable, d);
334   d->type = callbacks->type;
335   d->callbacks = *callbacks;
336
337   d->reference(d);
338   d->lock(d);
339
340   link_tail(d);
341
342   apple_glx_diagnostic("%s: new drawable %p\n", __func__, (void *) d);
343
344   *agdResult = d;
345
346   return false;
347}
348
349static int error_count = 0;
350
351static int
352error_handler(Display * dpy, XErrorEvent * err)
353{
354   if (err->error_code == BadWindow) {
355      ++error_count;
356   }
357
358   return 0;
359}
360
361void
362apple_glx_garbage_collect_drawables(Display * dpy)
363{
364   struct apple_glx_drawable *d, *dnext;
365   Window root;
366   int x, y;
367   unsigned int width, height, bd, depth;
368   int (*old_handler) (Display *, XErrorEvent *);
369
370
371   if (NULL == drawables_list)
372      return;
373
374   old_handler = XSetErrorHandler(error_handler);
375
376   XSync(dpy, False);
377
378   lock_drawables_list();
379
380   for (d = drawables_list; d;) {
381      dnext = d->next;
382
383      d->lock(d);
384
385      if (d->reference_count > 0) {
386         /*
387          * Skip this, because some context still retains a reference
388          * to the drawable.
389          */
390         d->unlock(d);
391         d = dnext;
392         continue;
393      }
394
395      d->unlock(d);
396
397      error_count = 0;
398
399      /*
400       * Mesa uses XGetWindowAttributes, but some of these things are
401       * most definitely not Windows, and that's against the rules.
402       * XGetGeometry on the other hand is legal with a Pixmap and Window.
403       */
404      XGetGeometry(dpy, d->drawable, &root, &x, &y, &width, &height, &bd,
405                   &depth);
406
407      if (error_count > 0) {
408         /*
409          * Note: this may not actually destroy the drawable.
410          * If another context retains a reference to the drawable
411          * after the reference count test above.
412          */
413         (void) destroy_drawable(d);
414         error_count = 0;
415      }
416
417      d = dnext;
418   }
419
420   XSetErrorHandler(old_handler);
421
422   unlock_drawables_list();
423}
424
425unsigned int
426apple_glx_get_drawable_count(void)
427{
428   unsigned int result = 0;
429   struct apple_glx_drawable *d;
430
431   lock_drawables_list();
432
433   for (d = drawables_list; d; d = d->next)
434      ++result;
435
436   unlock_drawables_list();
437
438   return result;
439}
440
441struct apple_glx_drawable *
442apple_glx_drawable_find_by_type(GLXDrawable drawable, int type, int flags)
443{
444   struct apple_glx_drawable *d;
445
446   lock_drawables_list();
447
448   for (d = drawables_list; d; d = d->next) {
449      if (d->type == type && d->drawable == drawable) {
450         if (flags & APPLE_GLX_DRAWABLE_REFERENCE)
451            d->reference(d);
452
453         if (flags & APPLE_GLX_DRAWABLE_LOCK)
454            d->lock(d);
455
456         unlock_drawables_list();
457
458         return d;
459      }
460   }
461
462   unlock_drawables_list();
463
464   return NULL;
465}
466
467struct apple_glx_drawable *
468apple_glx_drawable_find(GLXDrawable drawable, int flags)
469{
470   struct apple_glx_drawable *d;
471
472   lock_drawables_list();
473
474   for (d = drawables_list; d; d = d->next) {
475      if (d->drawable == drawable) {
476         if (flags & APPLE_GLX_DRAWABLE_REFERENCE)
477            d->reference(d);
478
479         if (flags & APPLE_GLX_DRAWABLE_LOCK)
480            d->lock(d);
481
482         unlock_drawables_list();
483
484         return d;
485      }
486   }
487
488   unlock_drawables_list();
489
490   return NULL;
491}
492
493/* Return true if the type is valid for the drawable. */
494bool
495apple_glx_drawable_destroy_by_type(Display * dpy,
496                                   GLXDrawable drawable, int type)
497{
498   struct apple_glx_drawable *d;
499
500   lock_drawables_list();
501
502   for (d = drawables_list; d; d = d->next) {
503      if (drawable == d->drawable && type == d->type) {
504         /*
505          * The user has requested that we destroy this resource.
506          * However, there may be references in the contexts to it, so
507          * release it, and call destroy_drawable which doesn't destroy
508          * if the reference_count is > 0.
509          */
510         d->release(d);
511
512         apple_glx_diagnostic("%s d->reference_count %d\n",
513                              __func__, d->reference_count);
514
515         destroy_drawable(d);
516         unlock_drawables_list();
517         return true;
518      }
519   }
520
521   unlock_drawables_list();
522
523   return false;
524}
525
526struct apple_glx_drawable *
527apple_glx_drawable_find_by_uid(unsigned int uid, int flags)
528{
529   struct apple_glx_drawable *d;
530
531   lock_drawables_list();
532
533   for (d = drawables_list; d; d = d->next) {
534      /* Only surfaces have a uid. */
535      if (APPLE_GLX_DRAWABLE_SURFACE == d->type) {
536         if (d->types.surface.uid == uid) {
537            if (flags & APPLE_GLX_DRAWABLE_REFERENCE)
538               d->reference(d);
539
540            if (flags & APPLE_GLX_DRAWABLE_LOCK)
541               d->lock(d);
542
543            unlock_drawables_list();
544
545            return d;
546         }
547      }
548   }
549
550   unlock_drawables_list();
551
552   return NULL;
553}
554