dri_common.c revision af69d88d
1/*
2 * Copyright 1998-1999 Precision Insight, Inc., Cedar Park, Texas.
3 * Copyright © 2008 Red Hat, Inc.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Soft-
7 * ware"), to deal in the Software without restriction, including without
8 * limitation the rights to use, copy, modify, merge, publish, distribute,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, provided that the above copyright
11 * notice(s) and this permission notice appear in all copies of the Soft-
12 * ware and that both the above copyright notice(s) and this permission
13 * notice appear in supporting documentation.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
17 * ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY
18 * RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN
19 * THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSE-
20 * QUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
22 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFOR-
23 * MANCE OF THIS SOFTWARE.
24 *
25 * Except as contained in this notice, the name of a copyright holder shall
26 * not be used in advertising or otherwise to promote the sale, use or
27 * other dealings in this Software without prior written authorization of
28 * the copyright holder.
29 *
30 * Authors:
31 *   Kevin E. Martin <kevin@precisioninsight.com>
32 *   Brian Paul <brian@precisioninsight.com>
33 *   Kristian Høgsberg (krh@redhat.com)
34 */
35
36#if defined(GLX_DIRECT_RENDERING) && !defined(GLX_USE_APPLEGL)
37
38#include <unistd.h>
39#include <dlfcn.h>
40#include <stdarg.h>
41#include "glxclient.h"
42#include "dri_common.h"
43#include "loader.h"
44
45#ifndef RTLD_NOW
46#define RTLD_NOW 0
47#endif
48#ifndef RTLD_GLOBAL
49#define RTLD_GLOBAL 0
50#endif
51
52_X_HIDDEN void
53dri_message(int level, const char *f, ...)
54{
55   va_list args;
56   int threshold = _LOADER_WARNING;
57   const char *libgl_debug;
58
59   libgl_debug = getenv("LIBGL_DEBUG");
60   if (libgl_debug) {
61      if (strstr(libgl_debug, "quiet"))
62         threshold = _LOADER_FATAL;
63      else if (strstr(libgl_debug, "verbose"))
64         threshold = _LOADER_DEBUG;
65   }
66
67   /* Note that the _LOADER_* levels are lower numbers for more severe. */
68   if (level <= threshold) {
69      fprintf(stderr, "libGL%s: ", level <= _LOADER_WARNING ? " error" : "");
70      va_start(args, f);
71      vfprintf(stderr, f, args);
72      va_end(args);
73   }
74}
75
76#ifndef DEFAULT_DRIVER_DIR
77/* this is normally defined in Mesa/configs/default with DRI_DRIVER_SEARCH_PATH */
78#define DEFAULT_DRIVER_DIR "/usr/local/lib/dri"
79#endif
80
81/**
82 * Try to \c dlopen the named driver.
83 *
84 * This function adds the "_dri.so" suffix to the driver name and searches the
85 * directories specified by the \c LIBGL_DRIVERS_PATH environment variable in
86 * order to find the driver.
87 *
88 * \param driverName - a name like "i965", "radeon", "nouveau", etc.
89 *
90 * \returns
91 * A handle from \c dlopen, or \c NULL if driver file not found.
92 */
93_X_HIDDEN void *
94driOpenDriver(const char *driverName)
95{
96   void *glhandle, *handle;
97   const char *libPaths, *p, *next;
98   char realDriverName[200];
99   int len;
100
101   /* Attempt to make sure libGL symbols will be visible to the driver */
102   glhandle = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL);
103
104   libPaths = NULL;
105   if (geteuid() == getuid()) {
106      /* don't allow setuid apps to use LIBGL_DRIVERS_PATH */
107      libPaths = getenv("LIBGL_DRIVERS_PATH");
108      if (!libPaths)
109         libPaths = getenv("LIBGL_DRIVERS_DIR");        /* deprecated */
110   }
111   if (libPaths == NULL)
112      libPaths = DEFAULT_DRIVER_DIR;
113
114   handle = NULL;
115   for (p = libPaths; *p; p = next) {
116      next = strchr(p, ':');
117      if (next == NULL) {
118         len = strlen(p);
119         next = p + len;
120      }
121      else {
122         len = next - p;
123         next++;
124      }
125
126#ifdef GLX_USE_TLS
127      snprintf(realDriverName, sizeof realDriverName,
128               "%.*s/tls/%s_dri.so", len, p, driverName);
129      InfoMessageF("OpenDriver: trying %s\n", realDriverName);
130      handle = dlopen(realDriverName, RTLD_NOW | RTLD_GLOBAL);
131#endif
132
133      if (handle == NULL) {
134         snprintf(realDriverName, sizeof realDriverName,
135                  "%.*s/%s_dri.so", len, p, driverName);
136         InfoMessageF("OpenDriver: trying %s\n", realDriverName);
137         handle = dlopen(realDriverName, RTLD_NOW | RTLD_GLOBAL);
138      }
139
140      if (handle != NULL)
141         break;
142      else
143         InfoMessageF("dlopen %s failed (%s)\n", realDriverName, dlerror());
144   }
145
146   if (!handle)
147      ErrorMessageF("unable to load driver: %s_dri.so\n", driverName);
148
149   if (glhandle)
150      dlclose(glhandle);
151
152   return handle;
153}
154
155_X_HIDDEN const __DRIextension **
156driGetDriverExtensions(void *handle, const char *driver_name)
157{
158   const __DRIextension **extensions = NULL;
159   const __DRIextension **(*get_extensions)(void);
160   char *get_extensions_name;
161
162   if (asprintf(&get_extensions_name, "%s_%s",
163                __DRI_DRIVER_GET_EXTENSIONS, driver_name) != -1) {
164      get_extensions = dlsym(handle, get_extensions_name);
165      if (get_extensions) {
166         free(get_extensions_name);
167         return get_extensions();
168      } else {
169         InfoMessageF("driver does not expose %s(): %s\n",
170                      get_extensions_name, dlerror());
171         free(get_extensions_name);
172      }
173   }
174
175   extensions = dlsym(handle, __DRI_DRIVER_EXTENSIONS);
176   if (extensions == NULL) {
177      ErrorMessageF("driver exports no extensions (%s)\n", dlerror());
178      return NULL;
179   }
180
181   return extensions;
182}
183
184static GLboolean
185__driGetMSCRate(__DRIdrawable *draw,
186		int32_t * numerator, int32_t * denominator,
187		void *loaderPrivate)
188{
189   __GLXDRIdrawable *glxDraw = loaderPrivate;
190
191   return __glxGetMscRate(glxDraw->psc, numerator, denominator);
192}
193
194_X_HIDDEN const __DRIsystemTimeExtension systemTimeExtension = {
195   .base = {__DRI_SYSTEM_TIME, 1 },
196
197   .getUST              = __glXGetUST,
198   .getMSCRate          = __driGetMSCRate
199};
200
201#define __ATTRIB(attrib, field) \
202    { attrib, offsetof(struct glx_config, field) }
203
204static const struct
205{
206   unsigned int attrib, offset;
207} attribMap[] = {
208   __ATTRIB(__DRI_ATTRIB_BUFFER_SIZE, rgbBits),
209      __ATTRIB(__DRI_ATTRIB_LEVEL, level),
210      __ATTRIB(__DRI_ATTRIB_RED_SIZE, redBits),
211      __ATTRIB(__DRI_ATTRIB_GREEN_SIZE, greenBits),
212      __ATTRIB(__DRI_ATTRIB_BLUE_SIZE, blueBits),
213      __ATTRIB(__DRI_ATTRIB_ALPHA_SIZE, alphaBits),
214      __ATTRIB(__DRI_ATTRIB_DEPTH_SIZE, depthBits),
215      __ATTRIB(__DRI_ATTRIB_STENCIL_SIZE, stencilBits),
216      __ATTRIB(__DRI_ATTRIB_ACCUM_RED_SIZE, accumRedBits),
217      __ATTRIB(__DRI_ATTRIB_ACCUM_GREEN_SIZE, accumGreenBits),
218      __ATTRIB(__DRI_ATTRIB_ACCUM_BLUE_SIZE, accumBlueBits),
219      __ATTRIB(__DRI_ATTRIB_ACCUM_ALPHA_SIZE, accumAlphaBits),
220      __ATTRIB(__DRI_ATTRIB_SAMPLE_BUFFERS, sampleBuffers),
221      __ATTRIB(__DRI_ATTRIB_SAMPLES, samples),
222      __ATTRIB(__DRI_ATTRIB_DOUBLE_BUFFER, doubleBufferMode),
223      __ATTRIB(__DRI_ATTRIB_STEREO, stereoMode),
224      __ATTRIB(__DRI_ATTRIB_AUX_BUFFERS, numAuxBuffers),
225#if 0
226      __ATTRIB(__DRI_ATTRIB_TRANSPARENT_TYPE, transparentPixel),
227      __ATTRIB(__DRI_ATTRIB_TRANSPARENT_INDEX_VALUE, transparentIndex),
228      __ATTRIB(__DRI_ATTRIB_TRANSPARENT_RED_VALUE, transparentRed),
229      __ATTRIB(__DRI_ATTRIB_TRANSPARENT_GREEN_VALUE, transparentGreen),
230      __ATTRIB(__DRI_ATTRIB_TRANSPARENT_BLUE_VALUE, transparentBlue),
231      __ATTRIB(__DRI_ATTRIB_TRANSPARENT_ALPHA_VALUE, transparentAlpha),
232      __ATTRIB(__DRI_ATTRIB_RED_MASK, redMask),
233      __ATTRIB(__DRI_ATTRIB_GREEN_MASK, greenMask),
234      __ATTRIB(__DRI_ATTRIB_BLUE_MASK, blueMask),
235      __ATTRIB(__DRI_ATTRIB_ALPHA_MASK, alphaMask),
236#endif
237      __ATTRIB(__DRI_ATTRIB_MAX_PBUFFER_WIDTH, maxPbufferWidth),
238      __ATTRIB(__DRI_ATTRIB_MAX_PBUFFER_HEIGHT, maxPbufferHeight),
239      __ATTRIB(__DRI_ATTRIB_MAX_PBUFFER_PIXELS, maxPbufferPixels),
240      __ATTRIB(__DRI_ATTRIB_OPTIMAL_PBUFFER_WIDTH, optimalPbufferWidth),
241      __ATTRIB(__DRI_ATTRIB_OPTIMAL_PBUFFER_HEIGHT, optimalPbufferHeight),
242#if 0
243      __ATTRIB(__DRI_ATTRIB_SWAP_METHOD, swapMethod),
244#endif
245__ATTRIB(__DRI_ATTRIB_BIND_TO_TEXTURE_RGB, bindToTextureRgb),
246      __ATTRIB(__DRI_ATTRIB_BIND_TO_TEXTURE_RGBA, bindToTextureRgba),
247      __ATTRIB(__DRI_ATTRIB_BIND_TO_MIPMAP_TEXTURE,
248                     bindToMipmapTexture),
249      __ATTRIB(__DRI_ATTRIB_YINVERTED, yInverted),
250      __ATTRIB(__DRI_ATTRIB_FRAMEBUFFER_SRGB_CAPABLE, sRGBCapable)
251};
252
253static int
254scalarEqual(struct glx_config *mode, unsigned int attrib, unsigned int value)
255{
256   unsigned int glxValue;
257   int i;
258
259   for (i = 0; i < ARRAY_SIZE(attribMap); i++)
260      if (attribMap[i].attrib == attrib) {
261         glxValue = *(unsigned int *) ((char *) mode + attribMap[i].offset);
262         return glxValue == GLX_DONT_CARE || glxValue == value;
263      }
264
265   return GL_TRUE;              /* Is a non-existing attribute equal to value? */
266}
267
268static int
269driConfigEqual(const __DRIcoreExtension *core,
270               struct glx_config *config, const __DRIconfig *driConfig)
271{
272   unsigned int attrib, value, glxValue;
273   int i;
274
275   i = 0;
276   while (core->indexConfigAttrib(driConfig, i++, &attrib, &value)) {
277      switch (attrib) {
278      case __DRI_ATTRIB_RENDER_TYPE:
279         glxValue = 0;
280         if (value & __DRI_ATTRIB_RGBA_BIT) {
281            glxValue |= GLX_RGBA_BIT;
282         }
283         if (value & __DRI_ATTRIB_COLOR_INDEX_BIT) {
284            glxValue |= GLX_COLOR_INDEX_BIT;
285         }
286         if (value & __DRI_ATTRIB_FLOAT_BIT) {
287            glxValue |= GLX_RGBA_FLOAT_BIT_ARB;
288         }
289         if (value & __DRI_ATTRIB_UNSIGNED_FLOAT_BIT) {
290            glxValue |= GLX_RGBA_UNSIGNED_FLOAT_BIT_EXT;
291         }
292         if (glxValue != config->renderType)
293            return GL_FALSE;
294         break;
295
296      case __DRI_ATTRIB_CONFIG_CAVEAT:
297         if (value & __DRI_ATTRIB_NON_CONFORMANT_CONFIG)
298            glxValue = GLX_NON_CONFORMANT_CONFIG;
299         else if (value & __DRI_ATTRIB_SLOW_BIT)
300            glxValue = GLX_SLOW_CONFIG;
301         else
302            glxValue = GLX_NONE;
303         if (glxValue != config->visualRating)
304            return GL_FALSE;
305         break;
306
307      case __DRI_ATTRIB_BIND_TO_TEXTURE_TARGETS:
308         glxValue = 0;
309         if (value & __DRI_ATTRIB_TEXTURE_1D_BIT)
310            glxValue |= GLX_TEXTURE_1D_BIT_EXT;
311         if (value & __DRI_ATTRIB_TEXTURE_2D_BIT)
312            glxValue |= GLX_TEXTURE_2D_BIT_EXT;
313         if (value & __DRI_ATTRIB_TEXTURE_RECTANGLE_BIT)
314            glxValue |= GLX_TEXTURE_RECTANGLE_BIT_EXT;
315         if (config->bindToTextureTargets != GLX_DONT_CARE &&
316             glxValue != config->bindToTextureTargets)
317            return GL_FALSE;
318         break;
319
320      default:
321         if (!scalarEqual(config, attrib, value))
322            return GL_FALSE;
323      }
324   }
325
326   return GL_TRUE;
327}
328
329static struct glx_config *
330createDriMode(const __DRIcoreExtension * core,
331	      struct glx_config *config, const __DRIconfig **driConfigs)
332{
333   __GLXDRIconfigPrivate *driConfig;
334   int i;
335
336   for (i = 0; driConfigs[i]; i++) {
337      if (driConfigEqual(core, config, driConfigs[i]))
338         break;
339   }
340
341   if (driConfigs[i] == NULL)
342      return NULL;
343
344   driConfig = malloc(sizeof *driConfig);
345   if (driConfig == NULL)
346      return NULL;
347
348   driConfig->base = *config;
349   driConfig->driConfig = driConfigs[i];
350
351   return &driConfig->base;
352}
353
354_X_HIDDEN struct glx_config *
355driConvertConfigs(const __DRIcoreExtension * core,
356                  struct glx_config *configs, const __DRIconfig **driConfigs)
357{
358   struct glx_config head, *tail, *m;
359
360   tail = &head;
361   head.next = NULL;
362   for (m = configs; m; m = m->next) {
363      tail->next = createDriMode(core, m, driConfigs);
364      if (tail->next == NULL) {
365         /* no matching dri config for m */
366         continue;
367      }
368
369
370      tail = tail->next;
371   }
372
373   return head.next;
374}
375
376_X_HIDDEN void
377driDestroyConfigs(const __DRIconfig **configs)
378{
379   int i;
380
381   for (i = 0; configs[i]; i++)
382      free((__DRIconfig *) configs[i]);
383   free(configs);
384}
385
386_X_HIDDEN __GLXDRIdrawable *
387driFetchDrawable(struct glx_context *gc, GLXDrawable glxDrawable)
388{
389   struct glx_display *const priv = __glXInitialize(gc->psc->dpy);
390   __GLXDRIdrawable *pdraw;
391   struct glx_screen *psc;
392
393   if (priv == NULL)
394      return NULL;
395
396   if (glxDrawable == None)
397      return NULL;
398
399   psc = priv->screens[gc->screen];
400   if (priv->drawHash == NULL)
401      return NULL;
402
403   if (__glxHashLookup(priv->drawHash, glxDrawable, (void *) &pdraw) == 0) {
404      pdraw->refcount ++;
405      return pdraw;
406   }
407
408   pdraw = psc->driScreen->createDrawable(psc, glxDrawable,
409                                          glxDrawable, gc->config);
410
411   if (pdraw == NULL) {
412      ErrorMessageF("failed to create drawable\n");
413      return NULL;
414   }
415
416   if (__glxHashInsert(priv->drawHash, glxDrawable, pdraw)) {
417      (*pdraw->destroyDrawable) (pdraw);
418      return NULL;
419   }
420   pdraw->refcount = 1;
421
422   return pdraw;
423}
424
425_X_HIDDEN void
426driReleaseDrawables(struct glx_context *gc)
427{
428   const struct glx_display *priv = gc->psc->display;
429   __GLXDRIdrawable *pdraw;
430
431   if (priv == NULL)
432      return;
433
434   if (__glxHashLookup(priv->drawHash,
435		       gc->currentDrawable, (void *) &pdraw) == 0) {
436      if (pdraw->drawable == pdraw->xDrawable) {
437	 pdraw->refcount --;
438	 if (pdraw->refcount == 0) {
439	    (*pdraw->destroyDrawable)(pdraw);
440	    __glxHashDelete(priv->drawHash, gc->currentDrawable);
441	 }
442      }
443   }
444
445   if (__glxHashLookup(priv->drawHash,
446		       gc->currentReadable, (void *) &pdraw) == 0) {
447      if (pdraw->drawable == pdraw->xDrawable) {
448	 pdraw->refcount --;
449	 if (pdraw->refcount == 0) {
450	    (*pdraw->destroyDrawable)(pdraw);
451	    __glxHashDelete(priv->drawHash, gc->currentReadable);
452	 }
453      }
454   }
455
456   gc->currentDrawable = None;
457   gc->currentReadable = None;
458
459}
460
461_X_HIDDEN bool
462dri2_convert_glx_attribs(unsigned num_attribs, const uint32_t *attribs,
463                         unsigned *major_ver, unsigned *minor_ver,
464                         uint32_t *render_type, uint32_t *flags, unsigned *api,
465                         int *reset, unsigned *error)
466{
467   unsigned i;
468   bool got_profile = false;
469   uint32_t profile;
470
471   *major_ver = 1;
472   *minor_ver = 0;
473   *render_type = GLX_RGBA_TYPE;
474   *reset = __DRI_CTX_RESET_NO_NOTIFICATION;
475   *flags = 0;
476   *api = __DRI_API_OPENGL;
477
478   if (num_attribs == 0) {
479      return true;
480   }
481
482   /* This is actually an internal error, but what the heck.
483    */
484   if (attribs == NULL) {
485      *error = __DRI_CTX_ERROR_UNKNOWN_ATTRIBUTE;
486      return false;
487   }
488
489   for (i = 0; i < num_attribs; i++) {
490      switch (attribs[i * 2]) {
491      case GLX_CONTEXT_MAJOR_VERSION_ARB:
492	 *major_ver = attribs[i * 2 + 1];
493	 break;
494      case GLX_CONTEXT_MINOR_VERSION_ARB:
495	 *minor_ver = attribs[i * 2 + 1];
496	 break;
497      case GLX_CONTEXT_FLAGS_ARB:
498	 *flags = attribs[i * 2 + 1];
499	 break;
500      case GLX_CONTEXT_PROFILE_MASK_ARB:
501	 profile = attribs[i * 2 + 1];
502	 got_profile = true;
503	 break;
504      case GLX_RENDER_TYPE:
505         *render_type = attribs[i * 2 + 1];
506	 break;
507      case GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB:
508         switch (attribs[i * 2 + 1]) {
509         case GLX_NO_RESET_NOTIFICATION_ARB:
510            *reset = __DRI_CTX_RESET_NO_NOTIFICATION;
511            break;
512         case GLX_LOSE_CONTEXT_ON_RESET_ARB:
513            *reset = __DRI_CTX_RESET_LOSE_CONTEXT;
514            break;
515         default:
516            *error = __DRI_CTX_ERROR_UNKNOWN_ATTRIBUTE;
517            return false;
518         }
519         break;
520      default:
521	 /* If an unknown attribute is received, fail.
522	  */
523	 *error = __DRI_CTX_ERROR_UNKNOWN_ATTRIBUTE;
524	 return false;
525      }
526   }
527
528   if (!got_profile) {
529      if (*major_ver > 3 || (*major_ver == 3 && *minor_ver >= 2))
530	 *api = __DRI_API_OPENGL_CORE;
531   } else {
532      switch (profile) {
533      case GLX_CONTEXT_CORE_PROFILE_BIT_ARB:
534	 /* There are no profiles before OpenGL 3.2.  The
535	  * GLX_ARB_create_context_profile spec says:
536	  *
537	  *     "If the requested OpenGL version is less than 3.2,
538	  *     GLX_CONTEXT_PROFILE_MASK_ARB is ignored and the functionality
539	  *     of the context is determined solely by the requested version."
540	  */
541	 *api = (*major_ver > 3 || (*major_ver == 3 && *minor_ver >= 2))
542	    ? __DRI_API_OPENGL_CORE : __DRI_API_OPENGL;
543	 break;
544      case GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB:
545	 *api = __DRI_API_OPENGL;
546	 break;
547      case GLX_CONTEXT_ES2_PROFILE_BIT_EXT:
548	 *api = __DRI_API_GLES2;
549	 break;
550      default:
551	 *error = __DRI_CTX_ERROR_BAD_API;
552	 return false;
553      }
554   }
555
556   /* Unknown flag value.
557    */
558   if (*flags & ~(__DRI_CTX_FLAG_DEBUG | __DRI_CTX_FLAG_FORWARD_COMPATIBLE
559                  | __DRI_CTX_FLAG_ROBUST_BUFFER_ACCESS)) {
560      *error = __DRI_CTX_ERROR_UNKNOWN_FLAG;
561      return false;
562   }
563
564   /* There are no forward-compatible contexts before OpenGL 3.0.  The
565    * GLX_ARB_create_context spec says:
566    *
567    *     "Forward-compatible contexts are defined only for OpenGL versions
568    *     3.0 and later."
569    */
570   if (*major_ver < 3 && (*flags & __DRI_CTX_FLAG_FORWARD_COMPATIBLE) != 0) {
571      *error = __DRI_CTX_ERROR_BAD_FLAG;
572      return false;
573   }
574
575   if (*major_ver >= 3 && *render_type == GLX_COLOR_INDEX_TYPE) {
576      *error = __DRI_CTX_ERROR_BAD_FLAG;
577      return false;
578   }
579
580   /* The GLX_EXT_create_context_es2_profile spec says:
581    *
582    *     "... If the version requested is 2.0, and the
583    *     GLX_CONTEXT_ES2_PROFILE_BIT_EXT bit is set in the
584    *     GLX_CONTEXT_PROFILE_MASK_ARB attribute (see below), then the context
585    *     returned will implement OpenGL ES 2.0. This is the only way in which
586    *     an implementation may request an OpenGL ES 2.0 context."
587    */
588   if (*api == __DRI_API_GLES2 && (*major_ver != 2 || *minor_ver != 0)) {
589      *error = __DRI_CTX_ERROR_BAD_API;
590      return false;
591   }
592
593   *error = __DRI_CTX_ERROR_SUCCESS;
594   return true;
595}
596
597#endif /* GLX_DIRECT_RENDERING */
598