|  | /* | 
|  | Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org> | 
|  |  | 
|  | This software is provided 'as-is', without any express or implied | 
|  | warranty.  In no event will the authors be held liable for any damages | 
|  | arising from the use of this software. | 
|  |  | 
|  | Permission is granted to anyone to use this software for any purpose, | 
|  | including commercial applications, and to alter it and redistribute it | 
|  | freely. | 
|  | */ | 
|  | /* This is a simple example of using GLSL shaders with SDL */ | 
|  |  | 
|  | #include "SDL.h" | 
|  |  | 
|  | #ifdef HAVE_OPENGL | 
|  |  | 
|  | #include "SDL_opengl.h" | 
|  |  | 
|  |  | 
|  | static SDL_bool shaders_supported; | 
|  | static int      current_shader = 0; | 
|  |  | 
|  | enum { | 
|  | SHADER_COLOR, | 
|  | SHADER_TEXTURE, | 
|  | SHADER_TEXCOORDS, | 
|  | NUM_SHADERS | 
|  | }; | 
|  |  | 
|  | typedef struct { | 
|  | GLhandleARB program; | 
|  | GLhandleARB vert_shader; | 
|  | GLhandleARB frag_shader; | 
|  | const char *vert_source; | 
|  | const char *frag_source; | 
|  | } ShaderData; | 
|  |  | 
|  | static ShaderData shaders[NUM_SHADERS] = { | 
|  |  | 
|  | /* SHADER_COLOR */ | 
|  | { 0, 0, 0, | 
|  | /* vertex shader */ | 
|  | "varying vec4 v_color;\n" | 
|  | "\n" | 
|  | "void main()\n" | 
|  | "{\n" | 
|  | "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" | 
|  | "    v_color = gl_Color;\n" | 
|  | "}", | 
|  | /* fragment shader */ | 
|  | "varying vec4 v_color;\n" | 
|  | "\n" | 
|  | "void main()\n" | 
|  | "{\n" | 
|  | "    gl_FragColor = v_color;\n" | 
|  | "}" | 
|  | }, | 
|  |  | 
|  | /* SHADER_TEXTURE */ | 
|  | { 0, 0, 0, | 
|  | /* vertex shader */ | 
|  | "varying vec4 v_color;\n" | 
|  | "varying vec2 v_texCoord;\n" | 
|  | "\n" | 
|  | "void main()\n" | 
|  | "{\n" | 
|  | "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" | 
|  | "    v_color = gl_Color;\n" | 
|  | "    v_texCoord = vec2(gl_MultiTexCoord0);\n" | 
|  | "}", | 
|  | /* fragment shader */ | 
|  | "varying vec4 v_color;\n" | 
|  | "varying vec2 v_texCoord;\n" | 
|  | "uniform sampler2D tex0;\n" | 
|  | "\n" | 
|  | "void main()\n" | 
|  | "{\n" | 
|  | "    gl_FragColor = texture2D(tex0, v_texCoord) * v_color;\n" | 
|  | "}" | 
|  | }, | 
|  |  | 
|  | /* SHADER_TEXCOORDS */ | 
|  | { 0, 0, 0, | 
|  | /* vertex shader */ | 
|  | "varying vec2 v_texCoord;\n" | 
|  | "\n" | 
|  | "void main()\n" | 
|  | "{\n" | 
|  | "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" | 
|  | "    v_texCoord = vec2(gl_MultiTexCoord0);\n" | 
|  | "}", | 
|  | /* fragment shader */ | 
|  | "varying vec2 v_texCoord;\n" | 
|  | "\n" | 
|  | "void main()\n" | 
|  | "{\n" | 
|  | "    vec4 color;\n" | 
|  | "    vec2 delta;\n" | 
|  | "    float dist;\n" | 
|  | "\n" | 
|  | "    delta = vec2(0.5, 0.5) - v_texCoord;\n" | 
|  | "    dist = dot(delta, delta);\n" | 
|  | "\n" | 
|  | "    color.r = v_texCoord.x;\n" | 
|  | "    color.g = v_texCoord.x * v_texCoord.y;\n" | 
|  | "    color.b = v_texCoord.y;\n" | 
|  | "    color.a = 1.0 - (dist * 4.0);\n" | 
|  | "    gl_FragColor = color;\n" | 
|  | "}" | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static PFNGLATTACHOBJECTARBPROC glAttachObjectARB; | 
|  | static PFNGLCOMPILESHADERARBPROC glCompileShaderARB; | 
|  | static PFNGLCREATEPROGRAMOBJECTARBPROC glCreateProgramObjectARB; | 
|  | static PFNGLCREATESHADEROBJECTARBPROC glCreateShaderObjectARB; | 
|  | static PFNGLDELETEOBJECTARBPROC glDeleteObjectARB; | 
|  | static PFNGLGETINFOLOGARBPROC glGetInfoLogARB; | 
|  | static PFNGLGETOBJECTPARAMETERIVARBPROC glGetObjectParameterivARB; | 
|  | static PFNGLGETUNIFORMLOCATIONARBPROC glGetUniformLocationARB; | 
|  | static PFNGLLINKPROGRAMARBPROC glLinkProgramARB; | 
|  | static PFNGLSHADERSOURCEARBPROC glShaderSourceARB; | 
|  | static PFNGLUNIFORM1IARBPROC glUniform1iARB; | 
|  | static PFNGLUSEPROGRAMOBJECTARBPROC glUseProgramObjectARB; | 
|  |  | 
|  | static SDL_bool CompileShader(GLhandleARB shader, const char *source) | 
|  | { | 
|  | GLint status; | 
|  |  | 
|  | glShaderSourceARB(shader, 1, &source, NULL); | 
|  | glCompileShaderARB(shader); | 
|  | glGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &status); | 
|  | if (status == 0) { | 
|  | GLint length; | 
|  | char *info; | 
|  |  | 
|  | glGetObjectParameterivARB(shader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length); | 
|  | info = SDL_stack_alloc(char, length+1); | 
|  | glGetInfoLogARB(shader, length, NULL, info); | 
|  | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile shader:\n%s\n%s", source, info); | 
|  | SDL_stack_free(info); | 
|  |  | 
|  | return SDL_FALSE; | 
|  | } else { | 
|  | return SDL_TRUE; | 
|  | } | 
|  | } | 
|  |  | 
|  | static SDL_bool CompileShaderProgram(ShaderData *data) | 
|  | { | 
|  | const int num_tmus_bound = 4; | 
|  | int i; | 
|  | GLint location; | 
|  |  | 
|  | glGetError(); | 
|  |  | 
|  | /* Create one program object to rule them all */ | 
|  | data->program = glCreateProgramObjectARB(); | 
|  |  | 
|  | /* Create the vertex shader */ | 
|  | data->vert_shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); | 
|  | if (!CompileShader(data->vert_shader, data->vert_source)) { | 
|  | return SDL_FALSE; | 
|  | } | 
|  |  | 
|  | /* Create the fragment shader */ | 
|  | data->frag_shader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); | 
|  | if (!CompileShader(data->frag_shader, data->frag_source)) { | 
|  | return SDL_FALSE; | 
|  | } | 
|  |  | 
|  | /* ... and in the darkness bind them */ | 
|  | glAttachObjectARB(data->program, data->vert_shader); | 
|  | glAttachObjectARB(data->program, data->frag_shader); | 
|  | glLinkProgramARB(data->program); | 
|  |  | 
|  | /* Set up some uniform variables */ | 
|  | glUseProgramObjectARB(data->program); | 
|  | for (i = 0; i < num_tmus_bound; ++i) { | 
|  | char tex_name[5]; | 
|  | SDL_snprintf(tex_name, SDL_arraysize(tex_name), "tex%d", i); | 
|  | location = glGetUniformLocationARB(data->program, tex_name); | 
|  | if (location >= 0) { | 
|  | glUniform1iARB(location, i); | 
|  | } | 
|  | } | 
|  | glUseProgramObjectARB(0); | 
|  |  | 
|  | return (glGetError() == GL_NO_ERROR) ? SDL_TRUE : SDL_FALSE; | 
|  | } | 
|  |  | 
|  | static void DestroyShaderProgram(ShaderData *data) | 
|  | { | 
|  | if (shaders_supported) { | 
|  | glDeleteObjectARB(data->vert_shader); | 
|  | glDeleteObjectARB(data->frag_shader); | 
|  | glDeleteObjectARB(data->program); | 
|  | } | 
|  | } | 
|  |  | 
|  | static SDL_bool InitShaders() | 
|  | { | 
|  | int i; | 
|  |  | 
|  | /* Check for shader support */ | 
|  | shaders_supported = SDL_FALSE; | 
|  | if (SDL_GL_ExtensionSupported("GL_ARB_shader_objects") && | 
|  | SDL_GL_ExtensionSupported("GL_ARB_shading_language_100") && | 
|  | SDL_GL_ExtensionSupported("GL_ARB_vertex_shader") && | 
|  | SDL_GL_ExtensionSupported("GL_ARB_fragment_shader")) { | 
|  | glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC) SDL_GL_GetProcAddress("glAttachObjectARB"); | 
|  | glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC) SDL_GL_GetProcAddress("glCompileShaderARB"); | 
|  | glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glCreateProgramObjectARB"); | 
|  | glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC) SDL_GL_GetProcAddress("glCreateShaderObjectARB"); | 
|  | glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC) SDL_GL_GetProcAddress("glDeleteObjectARB"); | 
|  | glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC) SDL_GL_GetProcAddress("glGetInfoLogARB"); | 
|  | glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC) SDL_GL_GetProcAddress("glGetObjectParameterivARB"); | 
|  | glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC) SDL_GL_GetProcAddress("glGetUniformLocationARB"); | 
|  | glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC) SDL_GL_GetProcAddress("glLinkProgramARB"); | 
|  | glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC) SDL_GL_GetProcAddress("glShaderSourceARB"); | 
|  | glUniform1iARB = (PFNGLUNIFORM1IARBPROC) SDL_GL_GetProcAddress("glUniform1iARB"); | 
|  | glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glUseProgramObjectARB"); | 
|  | if (glAttachObjectARB && | 
|  | glCompileShaderARB && | 
|  | glCreateProgramObjectARB && | 
|  | glCreateShaderObjectARB && | 
|  | glDeleteObjectARB && | 
|  | glGetInfoLogARB && | 
|  | glGetObjectParameterivARB && | 
|  | glGetUniformLocationARB && | 
|  | glLinkProgramARB && | 
|  | glShaderSourceARB && | 
|  | glUniform1iARB && | 
|  | glUseProgramObjectARB) { | 
|  | shaders_supported = SDL_TRUE; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!shaders_supported) { | 
|  | return SDL_FALSE; | 
|  | } | 
|  |  | 
|  | /* Compile all the shaders */ | 
|  | for (i = 0; i < NUM_SHADERS; ++i) { | 
|  | if (!CompileShaderProgram(&shaders[i])) { | 
|  | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to compile shader!\n"); | 
|  | return SDL_FALSE; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* We're done! */ | 
|  | return SDL_TRUE; | 
|  | } | 
|  |  | 
|  | static void QuitShaders() | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < NUM_SHADERS; ++i) { | 
|  | DestroyShaderProgram(&shaders[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Quick utility function for texture creation */ | 
|  | static int | 
|  | power_of_two(int input) | 
|  | { | 
|  | int value = 1; | 
|  |  | 
|  | while (value < input) { | 
|  | value <<= 1; | 
|  | } | 
|  | return value; | 
|  | } | 
|  |  | 
|  | GLuint | 
|  | SDL_GL_LoadTexture(SDL_Surface * surface, GLfloat * texcoord) | 
|  | { | 
|  | GLuint texture; | 
|  | int w, h; | 
|  | SDL_Surface *image; | 
|  | SDL_Rect area; | 
|  | SDL_BlendMode saved_mode; | 
|  |  | 
|  | /* Use the surface width and height expanded to powers of 2 */ | 
|  | w = power_of_two(surface->w); | 
|  | h = power_of_two(surface->h); | 
|  | texcoord[0] = 0.0f;         /* Min X */ | 
|  | texcoord[1] = 0.0f;         /* Min Y */ | 
|  | texcoord[2] = (GLfloat) surface->w / w;     /* Max X */ | 
|  | texcoord[3] = (GLfloat) surface->h / h;     /* Max Y */ | 
|  |  | 
|  | image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, | 
|  | #if SDL_BYTEORDER == SDL_LIL_ENDIAN     /* OpenGL RGBA masks */ | 
|  | 0x000000FF, | 
|  | 0x0000FF00, 0x00FF0000, 0xFF000000 | 
|  | #else | 
|  | 0xFF000000, | 
|  | 0x00FF0000, 0x0000FF00, 0x000000FF | 
|  | #endif | 
|  | ); | 
|  | if (image == NULL) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Save the alpha blending attributes */ | 
|  | SDL_GetSurfaceBlendMode(surface, &saved_mode); | 
|  | SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE); | 
|  |  | 
|  | /* Copy the surface into the GL texture image */ | 
|  | area.x = 0; | 
|  | area.y = 0; | 
|  | area.w = surface->w; | 
|  | area.h = surface->h; | 
|  | SDL_BlitSurface(surface, &area, image, &area); | 
|  |  | 
|  | /* Restore the alpha blending attributes */ | 
|  | SDL_SetSurfaceBlendMode(surface, saved_mode); | 
|  |  | 
|  | /* Create an OpenGL texture for the image */ | 
|  | glGenTextures(1, &texture); | 
|  | glBindTexture(GL_TEXTURE_2D, texture); | 
|  | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | 
|  | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 
|  | glTexImage2D(GL_TEXTURE_2D, | 
|  | 0, | 
|  | GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels); | 
|  | SDL_FreeSurface(image);     /* No longer needed */ | 
|  |  | 
|  | return texture; | 
|  | } | 
|  |  | 
|  | /* A general OpenGL initialization function.    Sets all of the initial parameters. */ | 
|  | void InitGL(int Width, int Height)                    /* We call this right after our OpenGL window is created. */ | 
|  | { | 
|  | GLdouble aspect; | 
|  |  | 
|  | glViewport(0, 0, Width, Height); | 
|  | glClearColor(0.0f, 0.0f, 0.0f, 0.0f);        /* This Will Clear The Background Color To Black */ | 
|  | glClearDepth(1.0);                /* Enables Clearing Of The Depth Buffer */ | 
|  | glDepthFunc(GL_LESS);                /* The Type Of Depth Test To Do */ | 
|  | glEnable(GL_DEPTH_TEST);            /* Enables Depth Testing */ | 
|  | glShadeModel(GL_SMOOTH);            /* Enables Smooth Color Shading */ | 
|  |  | 
|  | glMatrixMode(GL_PROJECTION); | 
|  | glLoadIdentity();                /* Reset The Projection Matrix */ | 
|  |  | 
|  | aspect = (GLdouble)Width / Height; | 
|  | glOrtho(-3.0, 3.0, -3.0 / aspect, 3.0 / aspect, 0.0, 1.0); | 
|  |  | 
|  | glMatrixMode(GL_MODELVIEW); | 
|  | } | 
|  |  | 
|  | /* The main drawing function. */ | 
|  | void DrawGLScene(SDL_Window *window, GLuint texture, GLfloat * texcoord) | 
|  | { | 
|  | /* Texture coordinate lookup, to make it simple */ | 
|  | enum { | 
|  | MINX, | 
|  | MINY, | 
|  | MAXX, | 
|  | MAXY | 
|  | }; | 
|  |  | 
|  | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);        /* Clear The Screen And The Depth Buffer */ | 
|  | glLoadIdentity();                /* Reset The View */ | 
|  |  | 
|  | glTranslatef(-1.5f,0.0f,0.0f);        /* Move Left 1.5 Units */ | 
|  |  | 
|  | /* draw a triangle (in smooth coloring mode) */ | 
|  | glBegin(GL_POLYGON);                /* start drawing a polygon */ | 
|  | glColor3f(1.0f,0.0f,0.0f);            /* Set The Color To Red */ | 
|  | glVertex3f( 0.0f, 1.0f, 0.0f);        /* Top */ | 
|  | glColor3f(0.0f,1.0f,0.0f);            /* Set The Color To Green */ | 
|  | glVertex3f( 1.0f,-1.0f, 0.0f);        /* Bottom Right */ | 
|  | glColor3f(0.0f,0.0f,1.0f);            /* Set The Color To Blue */ | 
|  | glVertex3f(-1.0f,-1.0f, 0.0f);        /* Bottom Left */ | 
|  | glEnd();                    /* we're done with the polygon (smooth color interpolation) */ | 
|  |  | 
|  | glTranslatef(3.0f,0.0f,0.0f);         /* Move Right 3 Units */ | 
|  |  | 
|  | /* Enable blending */ | 
|  | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); | 
|  | glEnable(GL_BLEND); | 
|  | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | 
|  |  | 
|  | /* draw a textured square (quadrilateral) */ | 
|  | glEnable(GL_TEXTURE_2D); | 
|  | glBindTexture(GL_TEXTURE_2D, texture); | 
|  | glColor3f(1.0f,1.0f,1.0f); | 
|  | if (shaders_supported) { | 
|  | glUseProgramObjectARB(shaders[current_shader].program); | 
|  | } | 
|  |  | 
|  | glBegin(GL_QUADS);                /* start drawing a polygon (4 sided) */ | 
|  | glTexCoord2f(texcoord[MINX], texcoord[MINY]); | 
|  | glVertex3f(-1.0f, 1.0f, 0.0f);        /* Top Left */ | 
|  | glTexCoord2f(texcoord[MAXX], texcoord[MINY]); | 
|  | glVertex3f( 1.0f, 1.0f, 0.0f);        /* Top Right */ | 
|  | glTexCoord2f(texcoord[MAXX], texcoord[MAXY]); | 
|  | glVertex3f( 1.0f,-1.0f, 0.0f);        /* Bottom Right */ | 
|  | glTexCoord2f(texcoord[MINX], texcoord[MAXY]); | 
|  | glVertex3f(-1.0f,-1.0f, 0.0f);        /* Bottom Left */ | 
|  | glEnd();                    /* done with the polygon */ | 
|  |  | 
|  | if (shaders_supported) { | 
|  | glUseProgramObjectARB(0); | 
|  | } | 
|  | glDisable(GL_TEXTURE_2D); | 
|  |  | 
|  | /* swap buffers to display, since we're double buffered. */ | 
|  | SDL_GL_SwapWindow(window); | 
|  | } | 
|  |  | 
|  | int main(int argc, char **argv) | 
|  | { | 
|  | int done; | 
|  | SDL_Window *window; | 
|  | SDL_Surface *surface; | 
|  | GLuint texture; | 
|  | GLfloat texcoords[4]; | 
|  |  | 
|  | /* Enable standard application logging */ | 
|  | SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); | 
|  |  | 
|  | /* Initialize SDL for video output */ | 
|  | if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) { | 
|  | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to initialize SDL: %s\n", SDL_GetError()); | 
|  | exit(1); | 
|  | } | 
|  |  | 
|  | /* Create a 640x480 OpenGL screen */ | 
|  | window = SDL_CreateWindow( "Shader Demo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL ); | 
|  | if ( !window ) { | 
|  | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create OpenGL window: %s\n", SDL_GetError()); | 
|  | SDL_Quit(); | 
|  | exit(2); | 
|  | } | 
|  |  | 
|  | if ( !SDL_GL_CreateContext(window)) { | 
|  | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create OpenGL context: %s\n", SDL_GetError()); | 
|  | SDL_Quit(); | 
|  | exit(2); | 
|  | } | 
|  |  | 
|  | surface = SDL_LoadBMP("icon.bmp"); | 
|  | if ( ! surface ) { | 
|  | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to load icon.bmp: %s\n", SDL_GetError()); | 
|  | SDL_Quit(); | 
|  | exit(3); | 
|  | } | 
|  | texture = SDL_GL_LoadTexture(surface, texcoords); | 
|  | SDL_FreeSurface(surface); | 
|  |  | 
|  | /* Loop, drawing and checking events */ | 
|  | InitGL(640, 480); | 
|  | if (InitShaders()) { | 
|  | SDL_Log("Shaders supported, press SPACE to cycle them.\n"); | 
|  | } else { | 
|  | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Shaders not supported!\n"); | 
|  | } | 
|  | done = 0; | 
|  | while ( ! done ) { | 
|  | DrawGLScene(window, texture, texcoords); | 
|  |  | 
|  | /* This could go in a separate function */ | 
|  | { SDL_Event event; | 
|  | while ( SDL_PollEvent(&event) ) { | 
|  | if ( event.type == SDL_QUIT ) { | 
|  | done = 1; | 
|  | } | 
|  | if ( event.type == SDL_KEYDOWN ) { | 
|  | if ( event.key.keysym.sym == SDLK_SPACE ) { | 
|  | current_shader = (current_shader + 1) % NUM_SHADERS; | 
|  | } | 
|  | if ( event.key.keysym.sym == SDLK_ESCAPE ) { | 
|  | done = 1; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | QuitShaders(); | 
|  | SDL_Quit(); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | #else /* HAVE_OPENGL */ | 
|  |  | 
|  | int | 
|  | main(int argc, char *argv[]) | 
|  | { | 
|  | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "No OpenGL support on this system\n"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | #endif /* HAVE_OPENGL */ | 
|  |  | 
|  | /* vi: set ts=4 sw=4 expandtab: */ |