| /* |
| 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 = 0; |
| |
| glShaderSourceARB(shader, 1, &source, NULL); |
| glCompileShaderARB(shader); |
| glGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &status); |
| if (status == 0) { |
| GLint length = 0; |
| char *info; |
| |
| glGetObjectParameterivARB(shader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length); |
| info = (char *) SDL_malloc(length + 1); |
| if (!info) { |
| SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!"); |
| } else { |
| glGetInfoLogARB(shader, length, NULL, info); |
| SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile shader:\n%s\n%s", source, info); |
| SDL_free(info); |
| } |
| return SDL_FALSE; |
| } else { |
| return SDL_TRUE; |
| } |
| } |
| |
| static SDL_bool LinkProgram(ShaderData *data) |
| { |
| GLint status = 0; |
| |
| glAttachObjectARB(data->program, data->vert_shader); |
| glAttachObjectARB(data->program, data->frag_shader); |
| glLinkProgramARB(data->program); |
| |
| glGetObjectParameterivARB(data->program, GL_OBJECT_LINK_STATUS_ARB, &status); |
| if (status == 0) { |
| GLint length = 0; |
| char *info; |
| |
| glGetObjectParameterivARB(data->program, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length); |
| info = (char *) SDL_malloc(length + 1); |
| if (!info) { |
| SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!"); |
| } else { |
| glGetInfoLogARB(data->program, length, NULL, info); |
| SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to link program:\n%s", info); |
| SDL_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 */ |
| if (!LinkProgram(data)) { |
| return SDL_FALSE; |
| } |
| |
| /* 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: */ |