diff options
author | Kamil Kaminski <kamilkss@gmail.com> | 2011-08-09 22:25:44 -0500 |
---|---|---|
committer | Kamil Kaminski <kamilkss@gmail.com> | 2011-08-09 22:25:44 -0500 |
commit | 157f167983a734c59e2e512905f654c44ced68a7 (patch) | |
tree | 4f3fc2cb5fac6eacc266a67fb0fa099a7cc3c098 /shader.c | |
download | glshader2.1-157f167983a734c59e2e512905f654c44ced68a7.tar.gz glshader2.1-157f167983a734c59e2e512905f654c44ced68a7.tar.bz2 glshader2.1-157f167983a734c59e2e512905f654c44ced68a7.zip |
initial commit
Diffstat (limited to 'shader.c')
-rw-r--r-- | shader.c | 720 |
1 files changed, 720 insertions, 0 deletions
diff --git a/shader.c b/shader.c new file mode 100644 index 0000000..60b9a11 --- /dev/null +++ b/shader.c @@ -0,0 +1,720 @@ +/* shader.c + * + * Kamil Kaminski + * kkaminsk.com + * + * Example of using a VBO with programmable pipeline on OpenGL 2.1 + * + * + */ + +#include <SDL/SDL.h> +#include <SDL/SDL_image.h> +#include <GL/glew.h> +#include <GL/freeglut.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#define BUFFER_OFFSET(i) ((char *)NULL + (i)) + +/* for simplicity we pack */ +/* this structure represents a point with attributes */ +#pragma pack(1) +typedef struct gltpoint +{ + GLfloat normal[3]; + GLfloat texcoord[2]; + GLubyte color[3]; +} gltpoint_t; +#pragma pack() + +/* globals */ +int program_running = 1; +SDL_Surface *screen; +GLuint buffer_objects[3]; + +const GLchar vshader[] = + "void main(void)\n" + "{\n" + " // normal MVP transform\n" + " vec4 clipCoord = gl_ModelViewProjectionMatrix * gl_Vertex;\n" + " gl_Position = clipCoord;\n" + "\n" + " // Copy the primary color\n" + " gl_FrontColor = gl_Color;\n" + "\n" + " // Calculate NDC\n" + " vec4 ndc = vec4(clipCoord.xyz, 0) / clipCoord.w;\n" + "\n" + " // Map from [-1,1] to [0,1] before outputting\n" + " gl_FrontSecondaryColor = (ndc * 0.5) + 0.5;\n" + "}\n"; + +const GLchar fshader[] = + "uniform float flickerFactor;\n" + "\n" + "void main(void)\n" + "{\n" + " // Mix primary and secondary colors, 50/50\n" + " vec4 temp = mix(gl_Color, vec4(vec3(gl_SecondaryColor), 1.0), 0.5);\n" + "\n" + " // Multiply by flicker factor\n" + " gl_FragColor = temp * flickerFactor;\n" + "}\n"; + +/* few light arrays */ +const GLfloat fNoLight[] = { 0.0f, 0.0f, 0.0f, 1.0f }; +const GLfloat fLowLight[] = { 0.25f, 0.25f, 0.25f, 1.0f }; +const GLfloat fMedLight[] = { 0.50f, 0.50f, 0.50f, 1.0f }; +const GLfloat fShinyLight[] = { 0.70f, 0.70f, 0.70f, 1.0f }; +const GLfloat fBrightLight[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + +/* light values and coordinates */ +const GLfloat lightPos[] = { -10.f, 5.0f, 5.0f, 1.0f }; + +struct +{ + GLuint prog, vert, frag; +} p; + +/* function prototypes */ +static size_t loadfile_to_mem(char *, char **); +static int vertprog_create(GLuint *, const char *); +static int fragprog_create(GLuint *, const char *); +static int prog_create(GLuint *, GLuint *, GLuint *); +static int gltGenVBOInterleaved(GLuint *, float *, float *, float *, + unsigned char *, unsigned short *, GLsizeiptr, GLsizeiptr); +static int gltDrawVBOInterleaved(GLuint *, size_t, GLenum); +static void gl_error_check(const char *, const char *, const int); +static void resize(int, int); +static void keys(SDL_keysym *, unsigned int *); +static void process_events(void); +static void render(void); +static void setup_sdl(void); +static void setup_glew(void); +static void setup_opengl(void); +static void dump_shaders(void); + +/* populates param memory with content of the file param fname */ +static size_t loadfile_to_mem(char *fname, char **memory) +{ + if (!fname || !memory) + return 0; + + size_t bytes = 0; + FILE *file = fopen(fname, "rt"); + + if (file != NULL) + { + fseek(file, 0, SEEK_END); + size_t end = ftell(file); + fseek(file, 0, SEEK_SET); + + *memory = (char *) malloc(sizeof(char) * end); + if (!memory) + { + fprintf(stderr, "%s failed, %s:%d\n", __FUNCTION__, __FILE__, __LINE__); + perror("malloc"); + fclose(file); + return 0; + } + + bytes = fread(*memory, sizeof(char), end, file); + fclose(file); + } + else + { + fprintf(stderr, "%s: failed to open \"%s\", %s:%d\n", + __FUNCTION__, fname, __FILE__, __LINE__); + perror("fopen"); + } + + return bytes; +} + +/* we have a separate buffer objects for vertex data, attributes, and indices + * interleaved arrays improve cache performance, the key is locality + * for simplicity, vertex is composed of 3 components and texcoord of 2 components + */ +static int gltGenVBOInterleaved(GLuint *bufferobjects, float *vert_arr, + float *texcoord_arr, float *norm_arr, unsigned char *color_arr, + unsigned short *indices_arr, GLsizeiptr points_n, GLsizeiptr indices_n) +{ + if (!bufferobjects || !vert_arr || !texcoord_arr || !norm_arr || !color_arr || + !indices_arr || !points_n || !indices_n) + return -1; + + /* create an interleaved data structure, is it worth the cpu time? */ + GLsizeiptr attrib_buff_sz = ((points_n * (3+2) * sizeof(GLfloat)) + + (points_n * 3 * sizeof(GLubyte))); + + if ((attrib_buff_sz % sizeof(gltpoint_t)) != 0) + { + fprintf(stderr, "%s: fatal, gltpoint_t appears to be padded!\n", __FUNCTION__); + return -1; + } + + /* I call point a "vbo slice" */ + size_t slices_n = attrib_buff_sz / sizeof(gltpoint_t); + + gltpoint_t *gltpoints_arr = (gltpoint_t *) malloc(attrib_buff_sz); + if (!gltpoints_arr) + { + fprintf(stderr, "%s failed, %s:%d\n", __FUNCTION__, __FILE__, __LINE__); + perror("malloc"); + return -1; + } + + /* build interleaved data structure, seems cache friendly */ + int i; + for (i = 0; i < slices_n; i++) + { + gltpoints_arr[i].normal[0] = norm_arr[i * 3 + 0]; + gltpoints_arr[i].normal[1] = norm_arr[i * 3 + 1]; + gltpoints_arr[i].normal[2] = norm_arr[i * 3 + 2]; + gltpoints_arr[i].texcoord[0] = texcoord_arr[i * 2 + 0]; + gltpoints_arr[i].texcoord[1] = texcoord_arr[i * 2 + 1]; + gltpoints_arr[i].color[0] = color_arr[i * 3 + 0]; + gltpoints_arr[i].color[1] = color_arr[i * 3 + 1]; + gltpoints_arr[i].color[2] = color_arr[i * 3 + 2]; + } + + glGenBuffers(3, bufferobjects); + + /* vertex data */ + glBindBuffer(GL_ARRAY_BUFFER, bufferobjects[0]); + glBufferData(GL_ARRAY_BUFFER, points_n * 3 * sizeof(float), + (const GLvoid *) vert_arr, GL_STATIC_DRAW); + + /* attribute data, normals, texcoords, and color */ + glBindBuffer(GL_ARRAY_BUFFER, bufferobjects[1]); + glBufferData(GL_ARRAY_BUFFER, attrib_buff_sz, + (const GLvoid *) gltpoints_arr, GL_STATIC_DRAW); + + /* indices data */ + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferobjects[2]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * indices_n, + (const GLvoid *) indices_arr, GL_STATIC_DRAW); + + free(gltpoints_arr); + + return 0; +} + +static int gltDrawVBOInterleaved(GLuint *bufferobjects, size_t indices_n, GLenum mode) +{ + if (!bufferobjects || !indices_n) + return -1; + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + /* vertex data */ + glBindBuffer(GL_ARRAY_BUFFER, bufferobjects[0]); + glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0)); + + /* attribute data, normals, texcoords, and color */ + glBindBuffer(GL_ARRAY_BUFFER, bufferobjects[1]); + glNormalPointer(GL_FLOAT, sizeof(gltpoint_t), BUFFER_OFFSET(0)); + glClientActiveTexture(GL_TEXTURE0); + glTexCoordPointer(2, GL_FLOAT, sizeof(gltpoint_t), BUFFER_OFFSET(sizeof(GLfloat) * 3)); + glColorPointer(3, GL_UNSIGNED_BYTE, sizeof(gltpoint_t), BUFFER_OFFSET(sizeof(GLfloat) * 5)); + + /* indices data */ + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferobjects[2]); + glDrawElements(mode, indices_n, GL_UNSIGNED_SHORT, 0); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + return 0; +} + +static void gl_error_check(const char *func, const char *file, const int line) +{ + GLenum err_code; + const GLubyte *err_str; + + /* this single poke pops of the error from opengl state, hence the do loop */ + if ((err_code = glGetError()) != GL_NO_ERROR) + { + fprintf(stderr, "errors encountered leading up to: %s in %s:%d\n", + func, file, line); + + do + { + err_str = gluErrorString(err_code); + fprintf(stderr, "OpenGL error: %s\n", err_str); + } + while ((err_code = glGetError()) != GL_NO_ERROR); + } +} + +static void resize(int w, int h) +{ + printf("window: resizing to %dx%d\n", w, h); + GLfloat fAspect = (GLfloat) w / (GLfloat) h; + if (h == 0) + h = 1; + glViewport(0, 0, w, h); + + /* reset coordinate system */ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + /* produce the perspective projection */ + /* void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar) */ + gluPerspective(40.0f, fAspect, 1.0, 40.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + /* this needs to be ran again, glut does it for you I suppose */ + SDL_SetVideoMode(w, h, 32, SDL_OPENGL | SDL_RESIZABLE); +} + +static void setup_opengl(void) +{ + /* setup fog */ + glEnable(GL_FOG); + glFogfv(GL_FOG_COLOR, fLowLight); /* set fog color to match background */ + glFogf(GL_FOG_START, 4.0f); + glFogf(GL_FOG_END, 20.0f); + glFogi(GL_FOG_MODE, GL_LINEAR); /* fog equation */ + + glEnable(GL_DEPTH_TEST); /* hidden surface removal, aka Z-buffer */ + glFrontFace(GL_CCW); /* counter clock-wise polygons face out */ + glEnable(GL_CULL_FACE); /* do not calculate inside of a pyramid */ + + /* enable lighting, primitives now need to define color properties */ + glEnable(GL_LIGHTING); + + /* global illumination, ambient RGBA intensity of the entire scene */ + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fLowLight); + + /* setup and enable light 0 */ + glLightfv(GL_LIGHT0, GL_AMBIENT, fMedLight); + glLightfv(GL_LIGHT0, GL_DIFFUSE, fShinyLight); + glLightfv(GL_LIGHT0, GL_SPECULAR, fBrightLight); + glLightfv(GL_LIGHT0, GL_POSITION, lightPos); + glEnable(GL_LIGHT0); + + /* set material properties to follow all glColor values */ + /* all primitives specified after the glMaterial call are affected by the + * last values set, until another call to glMaterial is made */ + glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); + + /* enable color tracking, should be called after glColorMaterial */ + glEnable(GL_COLOR_MATERIAL); + + /* set globally, sets specular reflection to a white color, + * this call follows all subsequent primitives */ + glMaterialfv(GL_FRONT, GL_SPECULAR, fBrightLight); + + /* default (0), by increasing this value you reduce the size and increase + * the focus of the specular highlight, causing a shiny spot to appear */ + glMateriali(GL_FRONT, GL_SHININESS, 64); + + /* multisampling for polygons, conflicts with *polygon* anti-aliasing */ + /* enabled by default, that's what man page says, hmmm! */ + glEnable(GL_MULTISAMPLE); + + /* AA is disabled, but we will keep the blending function and hints though */ + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + + /* anisotropic filtering */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8); + + /* how OpenGL combines the colors from texels with the color of the underlying + * geometry is controlled by the texture environment mode */ + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + /* once texture is loaded and enabled, it will applied to every primitive + * that specifies coordinates, ORLY? */ + glEnable(GL_TEXTURE_2D); + + /* specular highlights for textured items */ + glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); + + /* gray background */ + glClearColor(0.5f, 0.5f, 0.5f, 1.0f); +} + +static void keys(SDL_keysym *keysym, unsigned int *keys_held) +{ + switch (keysym->sym) + { + case SDLK_ESCAPE: + program_running = 0; + break; + default: + break; + } +} + +/* process SDL events */ +static void process_events(void) +{ + SDL_Event event; + unsigned static int keys_held[323]; + SDLKey sym; + + while (SDL_PollEvent(&event)) + { + sym = event.key.keysym.sym; + + switch (event.type) + { + case SDL_KEYUP: + { + /* reset the key to 0 */ + keys_held[sym] = 0; + break; + } + case SDL_KEYDOWN: + { + keys_held[sym] = 1; + keys(&event.key.keysym, keys_held); + break; + } + case SDL_VIDEORESIZE: + { + resize(event.resize.w, event.resize.h); + break; + } + case SDL_QUIT: + { + program_running = 0; + break; + } + default: + break; + } + } +} + +static void setup_sdl(void) +{ + if (SDL_Init(SDL_INIT_VIDEO) < 0 ) + { + fprintf(stderr, "SDL: unable to init, %s\n", SDL_GetError()); + exit(-1); + } + atexit(SDL_Quit); + SDL_WM_SetCaption("Programmable Pipeline", NULL); + + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + + if ((screen = SDL_SetVideoMode(320, 240, 32, SDL_OPENGL | SDL_RESIZABLE)) == NULL) + { + fprintf(stderr, "SDL: unable to set video mode: %s\n", SDL_GetError()); + exit(-1); + } + + SDL_EnableUNICODE(1); + /* SDL doesn't trigger off a ResizeEvent at startup, but as we need this + * for OpenGL, we do this ourselves */ + SDL_Event resizeEvent; + resizeEvent.type = SDL_VIDEORESIZE; + resizeEvent.resize.w = 320; + resizeEvent.resize.h = 240; + SDL_PushEvent(&resizeEvent); +} + +static void setup_glew(void) +{ + /* initalize glew */ + GLenum glewerr = glewInit(); + if (GLEW_OK != glewerr) + { + fprintf(stderr, "GLEW error: %s\n", glewGetErrorString(glewerr)); + exit(-1); + } + else + fprintf(stdout, "GLEW: using version %s\n", glewGetString(GLEW_VERSION)); + + /* display OpenGL version */ + GLint major; + GLint minor; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + fprintf(stdout, "GLEW: initialized OpenGL %d.%d\n", major, minor); +} + +static int fragprog_create(GLuint *handle, const char *frag_src) +{ + if (!handle) + return -1; + + GLint ret, length; + GLuint fragment; + + /* create handle */ + fragment = glCreateShader(GL_FRAGMENT_SHADER); + + /* specify shader text */ + glShaderSource(fragment, 1, &frag_src, NULL); + + /* compile shader and check for any errors */ + glCompileShader(fragment); + glGetShaderiv(fragment, GL_COMPILE_STATUS, &ret); + if (!ret) + { + glGetShaderiv(fragment, GL_INFO_LOG_LENGTH, &length); + GLchar info_log[length]; + glGetShaderInfoLog(fragment, length, NULL, info_log); + fprintf(stderr, "%s", info_log); + glDeleteShader(fragment); + return -1; + } + + /* save the handle */ + *handle = fragment; + + return 0; +} + +static int vertprog_create(GLuint *handle, const char *vert_src) +{ + if (!handle) + return -1; + + GLint ret, length; + GLuint vertex; + + /* create handle */ + vertex = glCreateShader(GL_VERTEX_SHADER); + + /* specify shader text */ + glShaderSource(vertex, 1, &vert_src, NULL); + + /* compile shader and check for any errors */ + glCompileShader(vertex); + glGetShaderiv(vertex, GL_COMPILE_STATUS, &ret); + if (!ret) + { + glGetShaderiv(vertex, GL_INFO_LOG_LENGTH, &length); + GLchar info_log[length]; + glGetShaderInfoLog(vertex, length, NULL, info_log); + fprintf(stderr, "%s", info_log); + glDeleteShader(vertex); + return -1; + } + + /* save the handle */ + *handle = vertex; + + return 0; +} + +static int prog_create(GLuint *prog_handle, GLuint *vert_handle, GLuint *frag_handle) +{ + if (!prog_handle || !vert_handle || !frag_handle) + return -1; + + GLint ret, length; + GLuint program; + + /* create handle */ + program = glCreateProgram(); + + /* attach shaders */ + glAttachShader(program, *vert_handle); + glAttachShader(program, *frag_handle); + + /* link, should be done whenever we attach or detach shaders to program */ + glLinkProgram(program); + glGetProgramiv(program, GL_LINK_STATUS, &ret); + if (!ret) + { + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); + GLchar info_log[length]; + glGetProgramInfoLog(program, length, NULL, info_log); + fprintf(stderr, "%s", info_log); + glDeleteProgram(program); + return -1; + } + + /* validate the program, should be done whenever program is linked or relinked */ + glValidateProgram(program); + glGetProgramiv(program, GL_VALIDATE_STATUS, &ret); + if (!ret) + { + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); + GLchar info_log[length]; + glGetProgramInfoLog(program, length, NULL, info_log); + fprintf(stderr, "%s", info_log); + glDeleteProgram(program); + return -1; + } + + /* save the handle */ + *prog_handle = program; + + return 0; +} + +static void render(void) +{ + /* clear the window with current clearing color */ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + /* save the matrix state */ + glPushMatrix(); + { + /* draw ground using immiediate mode */ + glDisable(GL_MULTISAMPLE); + glEnable(GL_BLEND); + glEnable(GL_LINE_SMOOTH); + + GLfloat fExtent = 20.0f; + GLfloat fStep = 0.5f; + GLfloat y = -0.4f; + GLfloat iLine; + + glLineWidth(1.0f); + glBegin(GL_LINES); + for (iLine = -fExtent; iLine <= fExtent; iLine += fStep) + { + glVertex3f(iLine, y, fExtent); + glVertex3f(iLine, y, -fExtent); + glVertex3f(fExtent, y, iLine); + glVertex3f(-fExtent, y, iLine); + } + glEnd(); + + glDisable(GL_LINE_SMOOTH); + glDisable(GL_BLEND); + glEnable(GL_MULTISAMPLE); + /* end of ground */ + + glPushMatrix(); + /* draw vbo */ + glTranslatef(0.0f, 0.0f, -4.0f); + gltDrawVBOInterleaved(buffer_objects, 3, GL_TRIANGLES); + glPopMatrix(); + } + /* restore the matrix state */ + glPopMatrix(); + + /* buffer swap */ + SDL_GL_SwapBuffers(); +} + +static void setup_vbo(void) +{ + /* setup a vbo triangle */ + float vert_arr[9] = { + 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.5f, 0.83f, 0.0f }; + + float norm_arr[9] = { + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f }; + + float texcoord_arr[6] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 0.5f, 1.0f }; + + unsigned char color_arr[9] = { + 255, 0, 0, + 0, 255, 0, + 0, 0, 255 }; + + unsigned short indices_arr[3] = { 0, 1, 2 }; + + gltGenVBOInterleaved(buffer_objects, vert_arr, texcoord_arr, norm_arr, + color_arr, indices_arr, 3, 3); + gl_error_check(__FUNCTION__, __FILE__, __LINE__); +} + +static void setup_shaders(void) +{ +#if 0 + char *vert_src, *frag_src; + loadfile_to_mem("vert", &vert_src); + loadfile_to_mem("frag", &frag_src); + free(vert_src); + free(frag_src); +#endif + + vertprog_create(&p.vert, vshader); + fragprog_create(&p.frag, fshader); + prog_create(&p.prog, &p.vert, &p.frag); + + /* this is where the magic happens, fixed-functionality is replaced depending + * on what shaders are attached to the program */ + glUseProgram(p.prog); + + /* find out where the flicker constant lives, program has to be in use */ + GLint flickerLocation = glGetUniformLocation(p.prog, "flickerFactor"); + /* initially set the blink parameter to 1 (no flicker) */ + if (flickerLocation != -1) + glUniform1f(flickerLocation, 1.0f); +} + +static void dump_shaders(void) +{ + FILE *f1, *f2; + f1 = fopen("vert", "w"); + f2 = fopen("frag", "w"); + + if (f1 && f2) + { + fwrite(vshader, sizeof(char), sizeof(vshader), f1); + fwrite(fshader, sizeof(char), sizeof(fshader), f2); + + fclose(f1); + fclose(f2); + } + else + fprintf(stderr, "%s: failed\n", __FUNCTION__); +} + +int main(int argc, char **argv) +{ + setup_sdl(); + setup_glew(); + setup_opengl(); + /* would also check for vert and frag extensions, otherwise use fixed-func */ + setup_vbo(); + setup_shaders(); + gl_error_check(__FUNCTION__, __FILE__, __LINE__); + + /* main loop */ + while (program_running) + { + process_events(); + render(); + } + gl_error_check(__FUNCTION__, __FILE__, __LINE__); + + /* cleanup */ + glDeleteProgram(p.prog); /* automatically detaches any shaders */ + glDeleteShader(p.vert); + glDeleteShader(p.frag); + glDeleteBuffers(3, buffer_objects); + if (screen) + SDL_FreeSurface(screen); + + puts("bye!"); + + return 0; +} + |