src/video/windows/SDL_windowsopengl.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 08 Apr 2011 13:03:26 -0700
changeset 5535 96594ac5fd1a
parent 5446 6ac8b0ac645e
child 5624 84d302e859d1
permissions -rw-r--r--
SDL 1.3 is now under the zlib license.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2011 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "SDL_config.h"
    22 
    23 #include "SDL_windowsvideo.h"
    24 
    25 /* WGL implementation of SDL OpenGL support */
    26 
    27 #if SDL_VIDEO_OPENGL_WGL
    28 #include "SDL_opengl.h"
    29 
    30 #define DEFAULT_OPENGL "OPENGL32.DLL"
    31 
    32 #ifndef WGL_ARB_create_context
    33 #define WGL_ARB_create_context
    34 #define WGL_CONTEXT_MAJOR_VERSION_ARB   0x2091
    35 #define WGL_CONTEXT_MINOR_VERSION_ARB   0x2092
    36 #define WGL_CONTEXT_LAYER_PLANE_ARB     0x2093
    37 #define WGL_CONTEXT_FLAGS_ARB           0x2093
    38 #define WGL_CONTEXT_DEBUG_BIT_ARB       0x0001
    39 #define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB  0x0002
    40 #endif
    41 
    42 typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC,
    43                                                             HGLRC
    44                                                             hShareContext,
    45                                                             const int
    46                                                             *attribList);
    47 
    48 int
    49 WIN_GL_LoadLibrary(_THIS, const char *path)
    50 {
    51     LPTSTR wpath;
    52     HANDLE handle;
    53 
    54     if (path == NULL) {
    55         path = SDL_getenv("SDL_OPENGL_LIBRARY");
    56     }
    57     if (path == NULL) {
    58         path = DEFAULT_OPENGL;
    59     }
    60     wpath = WIN_UTF8ToString(path);
    61     _this->gl_config.dll_handle = LoadLibrary(wpath);
    62     SDL_free(wpath);
    63     if (!_this->gl_config.dll_handle) {
    64         char message[1024];
    65         SDL_snprintf(message, SDL_arraysize(message), "LoadLibrary(\"%s\")",
    66                      path);
    67         WIN_SetError(message);
    68         return -1;
    69     }
    70     SDL_strlcpy(_this->gl_config.driver_path, path,
    71                 SDL_arraysize(_this->gl_config.driver_path));
    72 
    73     /* Allocate OpenGL memory */
    74     _this->gl_data =
    75         (struct SDL_GLDriverData *) SDL_calloc(1,
    76                                                sizeof(struct
    77                                                       SDL_GLDriverData));
    78     if (!_this->gl_data) {
    79         SDL_OutOfMemory();
    80         return -1;
    81     }
    82 
    83     /* Load function pointers */
    84     handle = _this->gl_config.dll_handle;
    85     _this->gl_data->wglGetProcAddress = (void *(WINAPI *) (const char *))
    86         GetProcAddress(handle, "wglGetProcAddress");
    87     _this->gl_data->wglCreateContext = (HGLRC(WINAPI *) (HDC))
    88         GetProcAddress(handle, "wglCreateContext");
    89     _this->gl_data->wglDeleteContext = (BOOL(WINAPI *) (HGLRC))
    90         GetProcAddress(handle, "wglDeleteContext");
    91     _this->gl_data->wglMakeCurrent = (BOOL(WINAPI *) (HDC, HGLRC))
    92         GetProcAddress(handle, "wglMakeCurrent");
    93     _this->gl_data->wglSwapIntervalEXT = (void (WINAPI *) (int))
    94         GetProcAddress(handle, "wglSwapIntervalEXT");
    95     _this->gl_data->wglGetSwapIntervalEXT = (int (WINAPI *) (void))
    96         GetProcAddress(handle, "wglGetSwapIntervalEXT");
    97 
    98     if (!_this->gl_data->wglGetProcAddress ||
    99         !_this->gl_data->wglCreateContext ||
   100         !_this->gl_data->wglDeleteContext ||
   101         !_this->gl_data->wglMakeCurrent) {
   102         SDL_SetError("Could not retrieve OpenGL functions");
   103         SDL_UnloadObject(handle);
   104         return -1;
   105     }
   106 
   107     return 0;
   108 }
   109 
   110 void *
   111 WIN_GL_GetProcAddress(_THIS, const char *proc)
   112 {
   113     void *func;
   114 
   115     /* This is to pick up extensions */
   116     func = _this->gl_data->wglGetProcAddress(proc);
   117     if (!func) {
   118         /* This is probably a normal GL function */
   119         func = GetProcAddress(_this->gl_config.dll_handle, proc);
   120     }
   121     return func;
   122 }
   123 
   124 void
   125 WIN_GL_UnloadLibrary(_THIS)
   126 {
   127     FreeLibrary((HMODULE) _this->gl_config.dll_handle);
   128     _this->gl_config.dll_handle = NULL;
   129 
   130     /* Free OpenGL memory */
   131     SDL_free(_this->gl_data);
   132     _this->gl_data = NULL;
   133 }
   134 
   135 static void
   136 WIN_GL_SetupPixelFormat(_THIS, PIXELFORMATDESCRIPTOR * pfd)
   137 {
   138     SDL_zerop(pfd);
   139     pfd->nSize = sizeof(*pfd);
   140     pfd->nVersion = 1;
   141     pfd->dwFlags = (PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL);
   142     if (_this->gl_config.double_buffer) {
   143         pfd->dwFlags |= PFD_DOUBLEBUFFER;
   144     }
   145     if (_this->gl_config.stereo) {
   146         pfd->dwFlags |= PFD_STEREO;
   147     }
   148     pfd->iLayerType = PFD_MAIN_PLANE;
   149     pfd->iPixelType = PFD_TYPE_RGBA;
   150     pfd->cRedBits = _this->gl_config.red_size;
   151     pfd->cGreenBits = _this->gl_config.green_size;
   152     pfd->cBlueBits = _this->gl_config.blue_size;
   153     pfd->cAlphaBits = _this->gl_config.alpha_size;
   154     if (_this->gl_config.buffer_size) {
   155         pfd->cColorBits =
   156             _this->gl_config.buffer_size - _this->gl_config.alpha_size;
   157     } else {
   158         pfd->cColorBits = (pfd->cRedBits + pfd->cGreenBits + pfd->cBlueBits);
   159     }
   160     pfd->cAccumRedBits = _this->gl_config.accum_red_size;
   161     pfd->cAccumGreenBits = _this->gl_config.accum_green_size;
   162     pfd->cAccumBlueBits = _this->gl_config.accum_blue_size;
   163     pfd->cAccumAlphaBits = _this->gl_config.accum_alpha_size;
   164     pfd->cAccumBits =
   165         (pfd->cAccumRedBits + pfd->cAccumGreenBits + pfd->cAccumBlueBits +
   166          pfd->cAccumAlphaBits);
   167     pfd->cDepthBits = _this->gl_config.depth_size;
   168     pfd->cStencilBits = _this->gl_config.stencil_size;
   169 }
   170 
   171 /* Choose the closest pixel format that meets or exceeds the target.
   172    FIXME: Should we weight any particular attribute over any other?
   173 */
   174 static int
   175 WIN_GL_ChoosePixelFormat(HDC hdc, PIXELFORMATDESCRIPTOR * target)
   176 {
   177     PIXELFORMATDESCRIPTOR pfd;
   178     int count, index, best = 0;
   179     unsigned int dist, best_dist = ~0U;
   180 
   181     count = DescribePixelFormat(hdc, 1, sizeof(pfd), NULL);
   182 
   183     for (index = 1; index <= count; index++) {
   184 
   185         if (!DescribePixelFormat(hdc, index, sizeof(pfd), &pfd)) {
   186             continue;
   187         }
   188 
   189         if ((pfd.dwFlags & target->dwFlags) != target->dwFlags) {
   190             continue;
   191         }
   192 
   193         if (pfd.iLayerType != target->iLayerType) {
   194             continue;
   195         }
   196         if (pfd.iPixelType != target->iPixelType) {
   197             continue;
   198         }
   199 
   200         dist = 0;
   201 
   202         if (pfd.cColorBits < target->cColorBits) {
   203             continue;
   204         } else {
   205             dist += (pfd.cColorBits - target->cColorBits);
   206         }
   207         if (pfd.cRedBits < target->cRedBits) {
   208             continue;
   209         } else {
   210             dist += (pfd.cRedBits - target->cRedBits);
   211         }
   212         if (pfd.cGreenBits < target->cGreenBits) {
   213             continue;
   214         } else {
   215             dist += (pfd.cGreenBits - target->cGreenBits);
   216         }
   217         if (pfd.cBlueBits < target->cBlueBits) {
   218             continue;
   219         } else {
   220             dist += (pfd.cBlueBits - target->cBlueBits);
   221         }
   222         if (pfd.cAlphaBits < target->cAlphaBits) {
   223             continue;
   224         } else {
   225             dist += (pfd.cAlphaBits - target->cAlphaBits);
   226         }
   227         if (pfd.cAccumBits < target->cAccumBits) {
   228             continue;
   229         } else {
   230             dist += (pfd.cAccumBits - target->cAccumBits);
   231         }
   232         if (pfd.cAccumRedBits < target->cAccumRedBits) {
   233             continue;
   234         } else {
   235             dist += (pfd.cAccumRedBits - target->cAccumRedBits);
   236         }
   237         if (pfd.cAccumGreenBits < target->cAccumGreenBits) {
   238             continue;
   239         } else {
   240             dist += (pfd.cAccumGreenBits - target->cAccumGreenBits);
   241         }
   242         if (pfd.cAccumBlueBits < target->cAccumBlueBits) {
   243             continue;
   244         } else {
   245             dist += (pfd.cAccumBlueBits - target->cAccumBlueBits);
   246         }
   247         if (pfd.cAccumAlphaBits < target->cAccumAlphaBits) {
   248             continue;
   249         } else {
   250             dist += (pfd.cAccumAlphaBits - target->cAccumAlphaBits);
   251         }
   252         if (pfd.cDepthBits < target->cDepthBits) {
   253             continue;
   254         } else {
   255             dist += (pfd.cDepthBits - target->cDepthBits);
   256         }
   257         if (pfd.cStencilBits < target->cStencilBits) {
   258             continue;
   259         } else {
   260             dist += (pfd.cStencilBits - target->cStencilBits);
   261         }
   262 
   263         if (dist < best_dist) {
   264             best = index;
   265             best_dist = dist;
   266         }
   267     }
   268 
   269     return best;
   270 }
   271 
   272 static SDL_bool
   273 HasExtension(const char *extension, const char *extensions)
   274 {
   275     const char *start;
   276     const char *where, *terminator;
   277 
   278     /* Extension names should not have spaces. */
   279     where = SDL_strchr(extension, ' ');
   280     if (where || *extension == '\0')
   281         return SDL_FALSE;
   282 
   283     if (!extensions)
   284         return SDL_FALSE;
   285 
   286     /* It takes a bit of care to be fool-proof about parsing the
   287      * OpenGL extensions string. Don't be fooled by sub-strings,
   288      * etc. */
   289 
   290     start = extensions;
   291 
   292     for (;;) {
   293         where = SDL_strstr(start, extension);
   294         if (!where)
   295             break;
   296 
   297         terminator = where + SDL_strlen(extension);
   298         if (where == start || *(where - 1) == ' ')
   299             if (*terminator == ' ' || *terminator == '\0')
   300                 return SDL_TRUE;
   301 
   302         start = terminator;
   303     }
   304     return SDL_FALSE;
   305 }
   306 
   307 static void
   308 WIN_GL_InitExtensions(_THIS, HDC hdc)
   309 {
   310     const char *(WINAPI * wglGetExtensionsStringARB) (HDC) = 0;
   311     const char *extensions;
   312 
   313     wglGetExtensionsStringARB = (const char *(WINAPI *) (HDC))
   314         _this->gl_data->wglGetProcAddress("wglGetExtensionsStringARB");
   315     if (wglGetExtensionsStringARB) {
   316         extensions = wglGetExtensionsStringARB(hdc);
   317     } else {
   318         extensions = NULL;
   319     }
   320 
   321     /* Check for WGL_ARB_pixel_format */
   322     _this->gl_data->WGL_ARB_pixel_format = 0;
   323     if (HasExtension("WGL_ARB_pixel_format", extensions)) {
   324         _this->gl_data->wglChoosePixelFormatARB = (BOOL(WINAPI *)
   325                                                    (HDC, const int *,
   326                                                     const FLOAT *, UINT,
   327                                                     int *, UINT *))
   328             WIN_GL_GetProcAddress(_this, "wglChoosePixelFormatARB");
   329         _this->gl_data->wglGetPixelFormatAttribivARB =
   330             (BOOL(WINAPI *) (HDC, int, int, UINT, const int *, int *))
   331             WIN_GL_GetProcAddress(_this, "wglGetPixelFormatAttribivARB");
   332 
   333         if ((_this->gl_data->wglChoosePixelFormatARB != NULL) &&
   334             (_this->gl_data->wglGetPixelFormatAttribivARB != NULL)) {
   335             _this->gl_data->WGL_ARB_pixel_format = 1;
   336         }
   337     }
   338 
   339     /* Check for WGL_EXT_swap_control */
   340     if (HasExtension("WGL_EXT_swap_control", extensions)) {
   341         _this->gl_data->wglSwapIntervalEXT =
   342             WIN_GL_GetProcAddress(_this, "wglSwapIntervalEXT");
   343         _this->gl_data->wglGetSwapIntervalEXT =
   344             WIN_GL_GetProcAddress(_this, "wglGetSwapIntervalEXT");
   345     } else {
   346         _this->gl_data->wglSwapIntervalEXT = NULL;
   347         _this->gl_data->wglGetSwapIntervalEXT = NULL;
   348     }
   349 }
   350 
   351 static int
   352 WIN_GL_ChoosePixelFormatARB(_THIS, int *iAttribs, float *fAttribs)
   353 {
   354     HWND hwnd;
   355     HDC hdc;
   356     PIXELFORMATDESCRIPTOR pfd;
   357     HGLRC hglrc;
   358     int pixel_format = 0;
   359     unsigned int matching;
   360 
   361     hwnd =
   362         CreateWindow(SDL_Appname, SDL_Appname, (WS_POPUP | WS_DISABLED), 0, 0,
   363                      10, 10, NULL, NULL, SDL_Instance, NULL);
   364     WIN_PumpEvents(_this);
   365 
   366     hdc = GetDC(hwnd);
   367 
   368     WIN_GL_SetupPixelFormat(_this, &pfd);
   369 
   370     SetPixelFormat(hdc, ChoosePixelFormat(hdc, &pfd), &pfd);
   371 
   372     hglrc = _this->gl_data->wglCreateContext(hdc);
   373     if (hglrc) {
   374         _this->gl_data->wglMakeCurrent(hdc, hglrc);
   375 
   376         WIN_GL_InitExtensions(_this, hdc);
   377 
   378         if (_this->gl_data->WGL_ARB_pixel_format) {
   379             _this->gl_data->wglChoosePixelFormatARB(hdc, iAttribs, fAttribs,
   380                                                     1, &pixel_format,
   381                                                     &matching);
   382         }
   383 
   384         _this->gl_data->wglMakeCurrent(NULL, NULL);
   385         _this->gl_data->wglDeleteContext(hglrc);
   386     }
   387     ReleaseDC(hwnd, hdc);
   388     DestroyWindow(hwnd);
   389     WIN_PumpEvents(_this);
   390 
   391     return pixel_format;
   392 }
   393 
   394 int
   395 WIN_GL_SetupWindow(_THIS, SDL_Window * window)
   396 {
   397     HDC hdc = ((SDL_WindowData *) window->driverdata)->hdc;
   398     PIXELFORMATDESCRIPTOR pfd;
   399     int pixel_format;
   400     int iAttribs[64];
   401     int *iAttr;
   402     float fAttribs[1] = { 0 };
   403 
   404     WIN_GL_SetupPixelFormat(_this, &pfd);
   405 
   406     /* setup WGL_ARB_pixel_format attribs */
   407     iAttr = &iAttribs[0];
   408 
   409     *iAttr++ = WGL_DRAW_TO_WINDOW_ARB;
   410     *iAttr++ = GL_TRUE;
   411     *iAttr++ = WGL_RED_BITS_ARB;
   412     *iAttr++ = _this->gl_config.red_size;
   413     *iAttr++ = WGL_GREEN_BITS_ARB;
   414     *iAttr++ = _this->gl_config.green_size;
   415     *iAttr++ = WGL_BLUE_BITS_ARB;
   416     *iAttr++ = _this->gl_config.blue_size;
   417 
   418     if (_this->gl_config.alpha_size) {
   419         *iAttr++ = WGL_ALPHA_BITS_ARB;
   420         *iAttr++ = _this->gl_config.alpha_size;
   421     }
   422 
   423     *iAttr++ = WGL_DOUBLE_BUFFER_ARB;
   424     *iAttr++ = _this->gl_config.double_buffer;
   425 
   426     *iAttr++ = WGL_DEPTH_BITS_ARB;
   427     *iAttr++ = _this->gl_config.depth_size;
   428 
   429     if (_this->gl_config.stencil_size) {
   430         *iAttr++ = WGL_STENCIL_BITS_ARB;
   431         *iAttr++ = _this->gl_config.stencil_size;
   432     }
   433 
   434     if (_this->gl_config.accum_red_size) {
   435         *iAttr++ = WGL_ACCUM_RED_BITS_ARB;
   436         *iAttr++ = _this->gl_config.accum_red_size;
   437     }
   438 
   439     if (_this->gl_config.accum_green_size) {
   440         *iAttr++ = WGL_ACCUM_GREEN_BITS_ARB;
   441         *iAttr++ = _this->gl_config.accum_green_size;
   442     }
   443 
   444     if (_this->gl_config.accum_blue_size) {
   445         *iAttr++ = WGL_ACCUM_BLUE_BITS_ARB;
   446         *iAttr++ = _this->gl_config.accum_blue_size;
   447     }
   448 
   449     if (_this->gl_config.accum_alpha_size) {
   450         *iAttr++ = WGL_ACCUM_ALPHA_BITS_ARB;
   451         *iAttr++ = _this->gl_config.accum_alpha_size;
   452     }
   453 
   454     if (_this->gl_config.stereo) {
   455         *iAttr++ = WGL_STEREO_ARB;
   456         *iAttr++ = GL_TRUE;
   457     }
   458 
   459     if (_this->gl_config.multisamplebuffers) {
   460         *iAttr++ = WGL_SAMPLE_BUFFERS_ARB;
   461         *iAttr++ = _this->gl_config.multisamplebuffers;
   462     }
   463 
   464     if (_this->gl_config.multisamplesamples) {
   465         *iAttr++ = WGL_SAMPLES_ARB;
   466         *iAttr++ = _this->gl_config.multisamplesamples;
   467     }
   468 
   469     *iAttr++ = WGL_ACCELERATION_ARB;
   470     *iAttr++ = (_this->gl_config.accelerated ? WGL_FULL_ACCELERATION_ARB :
   471                                                WGL_NO_ACCELERATION_ARB);
   472 
   473     *iAttr = 0;
   474 
   475     /* Choose and set the closest available pixel format */
   476     pixel_format = WIN_GL_ChoosePixelFormatARB(_this, iAttribs, fAttribs);
   477     if (!pixel_format) {
   478         pixel_format = WIN_GL_ChoosePixelFormat(hdc, &pfd);
   479     }
   480     if (!pixel_format) {
   481         SDL_SetError("No matching GL pixel format available");
   482         return -1;
   483     }
   484     if (!SetPixelFormat(hdc, pixel_format, &pfd)) {
   485         WIN_SetError("SetPixelFormat()");
   486         return (-1);
   487     }
   488     return 0;
   489 }
   490 
   491 SDL_GLContext
   492 WIN_GL_CreateContext(_THIS, SDL_Window * window)
   493 {
   494     HDC hdc = ((SDL_WindowData *) window->driverdata)->hdc;
   495     HGLRC context;
   496 
   497     if (_this->gl_config.major_version < 3) {
   498         context = _this->gl_data->wglCreateContext(hdc);
   499     } else {
   500         PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
   501         HGLRC temp_context = _this->gl_data->wglCreateContext(hdc);
   502         if (!temp_context) {
   503             SDL_SetError("Could not create GL context");
   504             return NULL;
   505         }
   506 
   507         /* Make the context current */
   508         if (WIN_GL_MakeCurrent(_this, window, temp_context) < 0) {
   509             WIN_GL_DeleteContext(_this, temp_context);
   510             return NULL;
   511         }
   512 
   513         wglCreateContextAttribsARB =
   514             (PFNWGLCREATECONTEXTATTRIBSARBPROC) _this->gl_data->
   515             wglGetProcAddress("wglCreateContextAttribsARB");
   516         if (!wglCreateContextAttribsARB) {
   517             SDL_SetError("GL 3.x is not supported");
   518             context = temp_context;
   519         } else {
   520             int attribs[] = {
   521                 WGL_CONTEXT_MAJOR_VERSION_ARB, _this->gl_config.major_version,
   522                 WGL_CONTEXT_MINOR_VERSION_ARB, _this->gl_config.minor_version,
   523                 0
   524             };
   525             /* Create the GL 3.x context */
   526             context = wglCreateContextAttribsARB(hdc, 0, attribs);
   527             /* Delete the GL 2.x context */
   528             _this->gl_data->wglDeleteContext(temp_context);
   529         }
   530     }
   531 
   532     if (!context) {
   533         WIN_SetError("Could not create GL context");
   534         return NULL;
   535     }
   536 
   537     if (WIN_GL_MakeCurrent(_this, window, context) < 0) {
   538         WIN_GL_DeleteContext(_this, context);
   539         return NULL;
   540     }
   541 
   542     WIN_GL_InitExtensions(_this, hdc);
   543 
   544     return context;
   545 }
   546 
   547 int
   548 WIN_GL_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context)
   549 {
   550     HDC hdc;
   551     int status;
   552 
   553     if (window) {
   554         hdc = ((SDL_WindowData *) window->driverdata)->hdc;
   555     } else {
   556         hdc = NULL;
   557     }
   558     if (!_this->gl_data->wglMakeCurrent(hdc, (HGLRC) context)) {
   559         WIN_SetError("wglMakeCurrent()");
   560         status = -1;
   561     } else {
   562         status = 0;
   563     }
   564     return status;
   565 }
   566 
   567 int
   568 WIN_GL_SetSwapInterval(_THIS, int interval)
   569 {
   570     if (_this->gl_data->wglSwapIntervalEXT) {
   571         _this->gl_data->wglSwapIntervalEXT(interval);
   572         return 0;
   573     } else {
   574         SDL_Unsupported();
   575         return -1;
   576     }
   577 }
   578 
   579 int
   580 WIN_GL_GetSwapInterval(_THIS)
   581 {
   582     if (_this->gl_data->wglGetSwapIntervalEXT) {
   583         return _this->gl_data->wglGetSwapIntervalEXT();
   584     } else {
   585         SDL_Unsupported();
   586         return -1;
   587     }
   588 }
   589 
   590 void
   591 WIN_GL_SwapWindow(_THIS, SDL_Window * window)
   592 {
   593     HDC hdc = ((SDL_WindowData *) window->driverdata)->hdc;
   594 
   595     SwapBuffers(hdc);
   596 }
   597 
   598 void
   599 WIN_GL_DeleteContext(_THIS, SDL_GLContext context)
   600 {
   601     _this->gl_data->wglDeleteContext((HGLRC) context);
   602 }
   603 
   604 #endif /* SDL_VIDEO_OPENGL_WGL */
   605 
   606 /* vi: set ts=4 sw=4 expandtab: */