1/*
2 * Mesa 3-D graphics library
3 *
4 * Copyright (C) 1999-2006  Brian Paul   All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included
14 * in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25
26/**
27 * \file xm_buffer.h
28 * Framebuffer and renderbuffer-related functions.
29 */
30
31
32#include "glxheader.h"
33#include "xmesaP.h"
34#include "main/errors.h"
35#include "main/formats.h"
36#include "main/framebuffer.h"
37#include "main/renderbuffer.h"
38#include "swrast/s_renderbuffer.h"
39#include "util/u_memory.h"
40
41
42#define XMESA_RENDERBUFFER 0x1234
43
44
45#if defined(USE_XSHM)
46static volatile int mesaXErrorFlag = 0;
47
48/**
49 * Catches potential Xlib errors.
50 */
51static int
52mesaHandleXError(XMesaDisplay *dpy, XErrorEvent *event)
53{
54   (void) dpy;
55   (void) event;
56   mesaXErrorFlag = 1;
57   return 0;
58}
59
60/**
61 * Allocate a shared memory XImage back buffer for the given XMesaBuffer.
62 * Return:  GL_TRUE if success, GL_FALSE if error
63 */
64static GLboolean
65alloc_back_shm_ximage(XMesaBuffer b, GLuint width, GLuint height)
66{
67   /*
68    * We have to do a _lot_ of error checking here to be sure we can
69    * really use the XSHM extension.  It seems different servers trigger
70    * errors at different points if the extension won't work.  Therefore
71    * we have to be very careful...
72    */
73   GC gc;
74   int (*old_handler)(XMesaDisplay *, XErrorEvent *);
75
76   if (width == 0 || height == 0) {
77      /* this will be true the first time we're called on 'b' */
78      return GL_FALSE;
79   }
80
81   b->backxrb->ximage = XShmCreateImage(b->xm_visual->display,
82                                        b->xm_visual->visinfo->visual,
83                                        b->xm_visual->visinfo->depth,
84                                        ZPixmap, NULL, &b->shminfo,
85                                        width, height);
86   if (b->backxrb->ximage == NULL) {
87      _mesa_warning(NULL, "alloc_back_buffer: Shared memory error (XShmCreateImage), disabling.\n");
88      b->shm = 0;
89      return GL_FALSE;
90   }
91
92   /* 0600 = user read+write */
93   b->shminfo.shmid = shmget(IPC_PRIVATE, b->backxrb->ximage->bytes_per_line
94                             * b->backxrb->ximage->height, IPC_CREAT | 0600);
95   if (b->shminfo.shmid < 0) {
96      _mesa_warning(NULL, "shmget failed while allocating back buffer.\n");
97      XDestroyImage(b->backxrb->ximage);
98      b->backxrb->ximage = NULL;
99      _mesa_warning(NULL, "alloc_back_buffer: Shared memory error (shmget), disabling.\n");
100      b->shm = 0;
101      return GL_FALSE;
102   }
103
104   b->shminfo.shmaddr = b->backxrb->ximage->data
105                      = (char*)shmat(b->shminfo.shmid, 0, 0);
106   if (b->shminfo.shmaddr == (char *) -1) {
107      _mesa_warning(NULL, "shmat() failed while allocating back buffer.\n");
108      XDestroyImage(b->backxrb->ximage);
109      shmctl(b->shminfo.shmid, IPC_RMID, 0);
110      b->backxrb->ximage = NULL;
111      _mesa_warning(NULL, "alloc_back_buffer: Shared memory error (shmat), disabling.\n");
112      b->shm = 0;
113      return GL_FALSE;
114   }
115
116   b->shminfo.readOnly = False;
117   mesaXErrorFlag = 0;
118   old_handler = XSetErrorHandler(mesaHandleXError);
119   /* This may trigger the X protocol error we're ready to catch: */
120   XShmAttach(b->xm_visual->display, &b->shminfo);
121   XSync(b->xm_visual->display, False);
122
123   if (mesaXErrorFlag) {
124      /* we are on a remote display, this error is normal, don't print it */
125      XFlush(b->xm_visual->display);
126      mesaXErrorFlag = 0;
127      XDestroyImage(b->backxrb->ximage);
128      shmdt(b->shminfo.shmaddr);
129      shmctl(b->shminfo.shmid, IPC_RMID, 0);
130      b->backxrb->ximage = NULL;
131      b->shm = 0;
132      (void) XSetErrorHandler(old_handler);
133      return GL_FALSE;
134   }
135
136   shmctl(b->shminfo.shmid, IPC_RMID, 0); /* nobody else needs it */
137
138   /* Finally, try an XShmPutImage to be really sure the extension works */
139   gc = XCreateGC(b->xm_visual->display, b->frontxrb->drawable, 0, NULL);
140   XShmPutImage(b->xm_visual->display, b->frontxrb->drawable, gc,
141		 b->backxrb->ximage, 0, 0, 0, 0, 1, 1 /*one pixel*/, False);
142   XSync(b->xm_visual->display, False);
143   XFreeGC(b->xm_visual->display, gc);
144   (void) XSetErrorHandler(old_handler);
145   if (mesaXErrorFlag) {
146      XFlush(b->xm_visual->display);
147      mesaXErrorFlag = 0;
148      XDestroyImage(b->backxrb->ximage);
149      shmdt(b->shminfo.shmaddr);
150      shmctl(b->shminfo.shmid, IPC_RMID, 0);
151      b->backxrb->ximage = NULL;
152      b->shm = 0;
153      return GL_FALSE;
154   }
155
156   return GL_TRUE;
157}
158#else
159static GLboolean
160alloc_back_shm_ximage(XMesaBuffer b, GLuint width, GLuint height)
161{
162   /* Can't compile XSHM support */
163   return GL_FALSE;
164}
165#endif
166
167
168
169/**
170 * Setup an off-screen pixmap or Ximage to use as the back buffer.
171 * Input:  b - the X/Mesa buffer
172 */
173static void
174alloc_back_buffer(XMesaBuffer b, GLuint width, GLuint height)
175{
176   if (b->db_mode == BACK_XIMAGE) {
177      /* Deallocate the old backxrb->ximage, if any */
178      if (b->backxrb->ximage) {
179#if defined(USE_XSHM)
180	 if (b->shm) {
181	    XShmDetach(b->xm_visual->display, &b->shminfo);
182	    XDestroyImage(b->backxrb->ximage);
183	    shmdt(b->shminfo.shmaddr);
184	 }
185	 else
186#endif
187	   XMesaDestroyImage(b->backxrb->ximage);
188	 b->backxrb->ximage = NULL;
189      }
190
191      if (width == 0 || height == 0)
192         return;
193
194      /* Allocate new back buffer */
195      if (b->shm == 0 || !alloc_back_shm_ximage(b, width, height)) {
196	 /* Allocate a regular XImage for the back buffer. */
197	 b->backxrb->ximage = XCreateImage(b->xm_visual->display,
198                                      b->xm_visual->visinfo->visual,
199                                      GET_VISUAL_DEPTH(b->xm_visual),
200				      ZPixmap, 0,   /* format, offset */
201				      NULL,
202                                      width, height,
203				      8, 0);  /* pad, bytes_per_line */
204	 if (!b->backxrb->ximage) {
205	    _mesa_warning(NULL, "alloc_back_buffer: XCreateImage failed.\n");
206            return;
207	 }
208         b->backxrb->ximage->data = malloc(b->backxrb->ximage->height
209                                        * b->backxrb->ximage->bytes_per_line);
210         if (!b->backxrb->ximage->data) {
211            _mesa_warning(NULL, "alloc_back_buffer: MALLOC failed.\n");
212            XMesaDestroyImage(b->backxrb->ximage);
213            b->backxrb->ximage = NULL;
214         }
215      }
216      b->backxrb->pixmap = None;
217   }
218   else if (b->db_mode == BACK_PIXMAP) {
219      /* Free the old back pixmap */
220      if (b->backxrb->pixmap) {
221         XMesaFreePixmap(b->xm_visual->display, b->backxrb->pixmap);
222         b->backxrb->pixmap = 0;
223      }
224
225      if (width > 0 && height > 0) {
226         /* Allocate new back pixmap */
227         b->backxrb->pixmap = XMesaCreatePixmap(b->xm_visual->display,
228                                                b->frontxrb->drawable,
229                                                width, height,
230                                                GET_VISUAL_DEPTH(b->xm_visual));
231      }
232
233      b->backxrb->ximage = NULL;
234      b->backxrb->drawable = b->backxrb->pixmap;
235   }
236}
237
238
239static void
240xmesa_delete_renderbuffer(struct gl_context *ctx, struct gl_renderbuffer *rb)
241{
242   /* XXX Note: the ximage or Pixmap attached to this renderbuffer
243    * should probably get freed here, but that's currently done in
244    * XMesaDestroyBuffer().
245    */
246   free(rb);
247}
248
249
250/**
251 * Reallocate renderbuffer storage for front color buffer.
252 * Called via gl_renderbuffer::AllocStorage()
253 */
254static GLboolean
255xmesa_alloc_front_storage(struct gl_context *ctx, struct gl_renderbuffer *rb,
256                          GLenum internalFormat, GLuint width, GLuint height)
257{
258   struct xmesa_renderbuffer *xrb = xmesa_renderbuffer(rb);
259
260   /* just clear these to be sure we don't accidentally use them */
261   xrb->origin2 = NULL;
262   xrb->origin3 = NULL;
263   xrb->origin4 = NULL;
264
265   /* for the FLIP macro: */
266   xrb->bottom = height - 1;
267
268   rb->Width = width;
269   rb->Height = height;
270   rb->InternalFormat = internalFormat;
271
272   return GL_TRUE;
273}
274
275
276/**
277 * Reallocate renderbuffer storage for back color buffer.
278 * Called via gl_renderbuffer::AllocStorage()
279 */
280static GLboolean
281xmesa_alloc_back_storage(struct gl_context *ctx, struct gl_renderbuffer *rb,
282                         GLenum internalFormat, GLuint width, GLuint height)
283{
284   struct xmesa_renderbuffer *xrb = xmesa_renderbuffer(rb);
285
286   /* reallocate the back buffer XImage or Pixmap */
287   assert(xrb->Parent);
288   alloc_back_buffer(xrb->Parent, width, height);
289
290   /* same as front buffer */
291   /* XXX why is this here? */
292   (void) xmesa_alloc_front_storage(ctx, rb, internalFormat, width, height);
293
294   /* plus... */
295   if (xrb->ximage) {
296      /* Needed by PIXELADDR2 macro */
297      xrb->width2 = xrb->ximage->bytes_per_line / 2;
298      xrb->origin2 = (GLushort *) xrb->ximage->data + xrb->width2 * (height - 1);
299
300      /* Needed by PIXELADDR3 macro */
301      xrb->width3 = xrb->ximage->bytes_per_line;
302      xrb->origin3 = (GLubyte *) xrb->ximage->data + xrb->width3 * (height - 1);
303
304      /* Needed by PIXELADDR4 macro */
305      xrb->width4 = xrb->ximage->width;
306      xrb->origin4 = (GLuint *) xrb->ximage->data + xrb->width4 * (height - 1);
307   }
308   else {
309      /* out of memory or buffer size is 0 x 0 */
310      xrb->width2 = xrb->width3 = xrb->width4 = 0;
311      xrb->origin2 = NULL;
312      xrb->origin3 = NULL;
313      xrb->origin4 = NULL;
314   }
315
316   return GL_TRUE;
317}
318
319
320/**
321 * Used for allocating front/back renderbuffers for an X window.
322 */
323struct xmesa_renderbuffer *
324xmesa_new_renderbuffer(struct gl_context *ctx, GLuint name,
325                       const struct xmesa_visual *xmvis,
326                       GLboolean backBuffer)
327{
328   struct xmesa_renderbuffer *xrb = CALLOC_STRUCT(xmesa_renderbuffer);
329   if (xrb) {
330      GLuint name = 0;
331      _mesa_init_renderbuffer(&xrb->Base.Base, name);
332
333      xrb->Base.Base.Delete = xmesa_delete_renderbuffer;
334      if (backBuffer)
335         xrb->Base.Base.AllocStorage = xmesa_alloc_back_storage;
336      else
337         xrb->Base.Base.AllocStorage = xmesa_alloc_front_storage;
338
339      xrb->Base.Base.InternalFormat = GL_RGBA;
340      xrb->Base.Base._BaseFormat = GL_RGBA;
341      xrb->Base.Base.ClassID = XMESA_RENDERBUFFER;
342
343      switch (xmvis->undithered_pf) {
344      case PF_8R8G8B:
345         /* This will really only happen for pixmaps.  We'll access the
346          * pixmap via a temporary XImage which will be 32bpp.
347          */
348         xrb->Base.Base.Format = MESA_FORMAT_B8G8R8X8_UNORM;
349         break;
350      case PF_8A8R8G8B:
351         xrb->Base.Base.Format = MESA_FORMAT_B8G8R8A8_UNORM;
352         break;
353      case PF_8A8B8G8R:
354         xrb->Base.Base.Format = MESA_FORMAT_R8G8B8A8_UNORM;
355         break;
356      case PF_5R6G5B:
357         xrb->Base.Base.Format = MESA_FORMAT_B5G6R5_UNORM;
358         break;
359      default:
360         _mesa_warning(ctx, "Bad pixel format in xmesa_new_renderbuffer");
361         xrb->Base.Base.Format = MESA_FORMAT_B8G8R8A8_UNORM;
362         break;
363      }
364
365      /* only need to set Red/Green/EtcBits fields for user-created RBs */
366   }
367   return xrb;
368}
369
370
371/**
372 * Called via gl_framebuffer::Delete() method when this buffer
373 * is _really_ being deleted.
374 */
375void
376xmesa_delete_framebuffer(struct gl_framebuffer *fb)
377{
378   XMesaBuffer b = XMESA_BUFFER(fb);
379
380   if (b->num_alloced > 0) {
381      /* If no other buffer uses this X colormap then free the colors. */
382      if (!xmesa_find_buffer(b->display, b->cmap, b)) {
383         XFreeColors(b->display, b->cmap,
384                     b->alloced_colors, b->num_alloced, 0);
385      }
386   }
387
388   if (b->gc)
389      XMesaFreeGC(b->display, b->gc);
390   if (b->cleargc)
391      XMesaFreeGC(b->display, b->cleargc);
392   if (b->swapgc)
393      XMesaFreeGC(b->display, b->swapgc);
394
395   if (fb->Visual.doubleBufferMode) {
396      /* free back ximage/pixmap/shmregion */
397      if (b->backxrb->ximage) {
398#if defined(USE_XSHM)
399         if (b->shm) {
400            XShmDetach( b->display, &b->shminfo );
401            XDestroyImage( b->backxrb->ximage );
402            shmdt( b->shminfo.shmaddr );
403         }
404         else
405#endif
406            XMesaDestroyImage( b->backxrb->ximage );
407         b->backxrb->ximage = NULL;
408      }
409      if (b->backxrb->pixmap) {
410         XMesaFreePixmap( b->display, b->backxrb->pixmap );
411      }
412   }
413
414   _mesa_free_framebuffer_data(fb);
415   free(fb);
416}
417
418
419/**
420 * Called via ctx->Driver.MapRenderbuffer()
421 */
422void
423xmesa_MapRenderbuffer(struct gl_context *ctx,
424                      struct gl_renderbuffer *rb,
425                      GLuint x, GLuint y, GLuint w, GLuint h,
426                      GLbitfield mode,
427                      GLubyte **mapOut, GLint *rowStrideOut,
428                      bool flip_y)
429{
430   struct xmesa_renderbuffer *xrb = xmesa_renderbuffer(rb);
431
432   if (xrb->Base.Base.ClassID == XMESA_RENDERBUFFER) {
433      XImage *ximage = xrb->ximage;
434
435      assert(!xrb->map_mode); /* only a single mapping allowed */
436
437      xrb->map_mode = mode;
438      xrb->map_x = x;
439      xrb->map_y = y;
440      xrb->map_w = w;
441      xrb->map_h = h;
442
443      if (ximage) {
444         int y2 = rb->Height - y - 1;
445
446         *mapOut = (GLubyte *) ximage->data
447            + y2 * ximage->bytes_per_line
448            + x * ximage->bits_per_pixel / 8;
449      }
450      else {
451         /* this must be a pixmap/window renderbuffer */
452         int (*old_handler)(XMesaDisplay *, XErrorEvent *);
453         int y2 = rb->Height - y - h;
454
455         assert(xrb->pixmap);
456
457         /* Install error handler for XGetImage() in case the window
458          * isn't mapped.  If we fail we'll create a temporary XImage.
459          */
460         mesaXErrorFlag = 0;
461         old_handler = XSetErrorHandler(mesaHandleXError);
462
463         /* read pixel data out of the pixmap/window into an XImage */
464         ximage = XGetImage(xrb->Parent->display,
465                            xrb->pixmap, x, y2, w, h,
466                            AllPlanes, ZPixmap);
467
468         XSetErrorHandler(old_handler);
469
470         if (mesaXErrorFlag) {
471            /* create new, temporary XImage */
472            int bytes_per_line =
473               _mesa_format_row_stride(xrb->Base.Base.Format,
474                                       xrb->Base.Base.Width);
475            char *image = malloc(bytes_per_line *
476                                          xrb->Base.Base.Height);
477            ximage = XCreateImage(xrb->Parent->display,
478                                  xrb->Parent->xm_visual->visinfo->visual,
479                                  xrb->Parent->xm_visual->visinfo->depth,
480                                  ZPixmap, /* format */
481                                  0, /* offset */
482                                  image, /* data */
483                                  xrb->Base.Base.Width,
484                                  xrb->Base.Base.Height,
485                                  8, /* pad */
486                                  bytes_per_line);
487         }
488
489         if (!ximage) {
490            *mapOut = NULL;
491            *rowStrideOut = 0;
492            return;
493         }
494
495         xrb->map_ximage = ximage;
496
497         /* the first row of the OpenGL image is last row of the XImage */
498         *mapOut = (GLubyte *) ximage->data
499            + (h - 1) * ximage->bytes_per_line;
500      }
501
502      /* We return a negative stride here since XImage data is upside down
503       * with respect to OpenGL images.
504       */
505      *rowStrideOut = -ximage->bytes_per_line;
506      return;
507   }
508
509   /* otherwise, this is an ordinary malloc-based renderbuffer */
510   _swrast_map_soft_renderbuffer(ctx, rb, x, y, w, h, mode,
511                                 mapOut, rowStrideOut, false);
512}
513
514
515/**
516 * Called via ctx->Driver.UnmapRenderbuffer()
517 */
518void
519xmesa_UnmapRenderbuffer(struct gl_context *ctx, struct gl_renderbuffer *rb)
520{
521   struct xmesa_renderbuffer *xrb = xmesa_renderbuffer(rb);
522
523   if (xrb->Base.Base.ClassID == XMESA_RENDERBUFFER) {
524      XImage *ximage = xrb->ximage;
525
526      if (!ximage) {
527         /* this must be a pixmap/window renderbuffer */
528         assert(xrb->pixmap);
529         assert(xrb->map_ximage);
530         if (xrb->map_ximage) {
531            if (xrb->map_mode & GL_MAP_WRITE_BIT) {
532               /* put modified ximage data back into the pixmap/window */
533               int y2 = rb->Height - xrb->map_y - xrb->map_h;
534               GC gc = XCreateGC(xrb->Parent->display, xrb->pixmap, 0, NULL);
535
536               XPutImage(xrb->Parent->display,
537                         xrb->pixmap,              /* dest */
538                         gc,
539                         xrb->map_ximage,          /* source */
540                         0, 0,                     /* src x, y */
541                         xrb->map_x, y2,           /* dest x, y */
542                         xrb->map_w, xrb->map_h);  /* size */
543
544               XFreeGC(xrb->Parent->display, gc);
545            }
546            XMesaDestroyImage(xrb->map_ximage);
547            xrb->map_ximage = NULL;
548         }
549      }
550
551      xrb->map_mode = 0x0;
552
553      return;
554   }
555
556   /* otherwise, this is an ordinary malloc-based renderbuffer */
557   _swrast_unmap_soft_renderbuffer(ctx, rb);
558}
559
560
561