1 | /* |
2 | Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org> |
3 | |
4 | This software is provided 'as-is', without any express or implied |
5 | warranty. In no event will the authors be held liable for any damages |
6 | arising from the use of this software. |
7 | |
8 | Permission is granted to anyone to use this software for any purpose, |
9 | including commercial applications, and to alter it and redistribute it |
10 | freely. |
11 | */ |
12 | /* This is a simple example of using GLSL shaders with SDL */ |
13 | |
14 | #include "SDL.h" |
15 | |
16 | #ifdef HAVE_OPENGL |
17 | |
18 | #include "SDL_opengl.h" |
19 | |
20 | |
21 | static SDL_bool shaders_supported; |
22 | static int current_shader = 0; |
23 | |
24 | enum { |
25 | SHADER_COLOR, |
26 | SHADER_TEXTURE, |
27 | SHADER_TEXCOORDS, |
28 | NUM_SHADERS |
29 | }; |
30 | |
31 | typedef struct { |
32 | GLhandleARB program; |
33 | GLhandleARB vert_shader; |
34 | GLhandleARB frag_shader; |
35 | const char *vert_source; |
36 | const char *frag_source; |
37 | } ShaderData; |
38 | |
39 | static ShaderData shaders[NUM_SHADERS] = { |
40 | |
41 | /* SHADER_COLOR */ |
42 | { 0, 0, 0, |
43 | /* vertex shader */ |
44 | "varying vec4 v_color;\n" |
45 | "\n" |
46 | "void main()\n" |
47 | "{\n" |
48 | " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" |
49 | " v_color = gl_Color;\n" |
50 | "}" , |
51 | /* fragment shader */ |
52 | "varying vec4 v_color;\n" |
53 | "\n" |
54 | "void main()\n" |
55 | "{\n" |
56 | " gl_FragColor = v_color;\n" |
57 | "}" |
58 | }, |
59 | |
60 | /* SHADER_TEXTURE */ |
61 | { 0, 0, 0, |
62 | /* vertex shader */ |
63 | "varying vec4 v_color;\n" |
64 | "varying vec2 v_texCoord;\n" |
65 | "\n" |
66 | "void main()\n" |
67 | "{\n" |
68 | " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" |
69 | " v_color = gl_Color;\n" |
70 | " v_texCoord = vec2(gl_MultiTexCoord0);\n" |
71 | "}" , |
72 | /* fragment shader */ |
73 | "varying vec4 v_color;\n" |
74 | "varying vec2 v_texCoord;\n" |
75 | "uniform sampler2D tex0;\n" |
76 | "\n" |
77 | "void main()\n" |
78 | "{\n" |
79 | " gl_FragColor = texture2D(tex0, v_texCoord) * v_color;\n" |
80 | "}" |
81 | }, |
82 | |
83 | /* SHADER_TEXCOORDS */ |
84 | { 0, 0, 0, |
85 | /* vertex shader */ |
86 | "varying vec2 v_texCoord;\n" |
87 | "\n" |
88 | "void main()\n" |
89 | "{\n" |
90 | " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" |
91 | " v_texCoord = vec2(gl_MultiTexCoord0);\n" |
92 | "}" , |
93 | /* fragment shader */ |
94 | "varying vec2 v_texCoord;\n" |
95 | "\n" |
96 | "void main()\n" |
97 | "{\n" |
98 | " vec4 color;\n" |
99 | " vec2 delta;\n" |
100 | " float dist;\n" |
101 | "\n" |
102 | " delta = vec2(0.5, 0.5) - v_texCoord;\n" |
103 | " dist = dot(delta, delta);\n" |
104 | "\n" |
105 | " color.r = v_texCoord.x;\n" |
106 | " color.g = v_texCoord.x * v_texCoord.y;\n" |
107 | " color.b = v_texCoord.y;\n" |
108 | " color.a = 1.0 - (dist * 4.0);\n" |
109 | " gl_FragColor = color;\n" |
110 | "}" |
111 | }, |
112 | }; |
113 | |
114 | static PFNGLATTACHOBJECTARBPROC glAttachObjectARB; |
115 | static PFNGLCOMPILESHADERARBPROC glCompileShaderARB; |
116 | static PFNGLCREATEPROGRAMOBJECTARBPROC glCreateProgramObjectARB; |
117 | static PFNGLCREATESHADEROBJECTARBPROC glCreateShaderObjectARB; |
118 | static PFNGLDELETEOBJECTARBPROC glDeleteObjectARB; |
119 | static PFNGLGETINFOLOGARBPROC glGetInfoLogARB; |
120 | static PFNGLGETOBJECTPARAMETERIVARBPROC glGetObjectParameterivARB; |
121 | static PFNGLGETUNIFORMLOCATIONARBPROC glGetUniformLocationARB; |
122 | static PFNGLLINKPROGRAMARBPROC glLinkProgramARB; |
123 | static PFNGLSHADERSOURCEARBPROC glShaderSourceARB; |
124 | static PFNGLUNIFORM1IARBPROC glUniform1iARB; |
125 | static PFNGLUSEPROGRAMOBJECTARBPROC glUseProgramObjectARB; |
126 | |
127 | static SDL_bool CompileShader(GLhandleARB shader, const char *source) |
128 | { |
129 | GLint status; |
130 | |
131 | glShaderSourceARB(shader, 1, &source, NULL); |
132 | glCompileShaderARB(shader); |
133 | glGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &status); |
134 | if (status == 0) { |
135 | GLint length; |
136 | char *info; |
137 | |
138 | glGetObjectParameterivARB(shader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length); |
139 | info = SDL_stack_alloc(char, length+1); |
140 | glGetInfoLogARB(shader, length, NULL, info); |
141 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile shader:\n%s\n%s" , source, info); |
142 | SDL_stack_free(info); |
143 | |
144 | return SDL_FALSE; |
145 | } else { |
146 | return SDL_TRUE; |
147 | } |
148 | } |
149 | |
150 | static SDL_bool CompileShaderProgram(ShaderData *data) |
151 | { |
152 | const int num_tmus_bound = 4; |
153 | int i; |
154 | GLint location; |
155 | |
156 | glGetError(); |
157 | |
158 | /* Create one program object to rule them all */ |
159 | data->program = glCreateProgramObjectARB(); |
160 | |
161 | /* Create the vertex shader */ |
162 | data->vert_shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); |
163 | if (!CompileShader(data->vert_shader, data->vert_source)) { |
164 | return SDL_FALSE; |
165 | } |
166 | |
167 | /* Create the fragment shader */ |
168 | data->frag_shader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); |
169 | if (!CompileShader(data->frag_shader, data->frag_source)) { |
170 | return SDL_FALSE; |
171 | } |
172 | |
173 | /* ... and in the darkness bind them */ |
174 | glAttachObjectARB(data->program, data->vert_shader); |
175 | glAttachObjectARB(data->program, data->frag_shader); |
176 | glLinkProgramARB(data->program); |
177 | |
178 | /* Set up some uniform variables */ |
179 | glUseProgramObjectARB(data->program); |
180 | for (i = 0; i < num_tmus_bound; ++i) { |
181 | char tex_name[5]; |
182 | SDL_snprintf(tex_name, SDL_arraysize(tex_name), "tex%d" , i); |
183 | location = glGetUniformLocationARB(data->program, tex_name); |
184 | if (location >= 0) { |
185 | glUniform1iARB(location, i); |
186 | } |
187 | } |
188 | glUseProgramObjectARB(0); |
189 | |
190 | return (glGetError() == GL_NO_ERROR) ? SDL_TRUE : SDL_FALSE; |
191 | } |
192 | |
193 | static void DestroyShaderProgram(ShaderData *data) |
194 | { |
195 | if (shaders_supported) { |
196 | glDeleteObjectARB(data->vert_shader); |
197 | glDeleteObjectARB(data->frag_shader); |
198 | glDeleteObjectARB(data->program); |
199 | } |
200 | } |
201 | |
202 | static SDL_bool InitShaders() |
203 | { |
204 | int i; |
205 | |
206 | /* Check for shader support */ |
207 | shaders_supported = SDL_FALSE; |
208 | if (SDL_GL_ExtensionSupported("GL_ARB_shader_objects" ) && |
209 | SDL_GL_ExtensionSupported("GL_ARB_shading_language_100" ) && |
210 | SDL_GL_ExtensionSupported("GL_ARB_vertex_shader" ) && |
211 | SDL_GL_ExtensionSupported("GL_ARB_fragment_shader" )) { |
212 | glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC) SDL_GL_GetProcAddress("glAttachObjectARB" ); |
213 | glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC) SDL_GL_GetProcAddress("glCompileShaderARB" ); |
214 | glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glCreateProgramObjectARB" ); |
215 | glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC) SDL_GL_GetProcAddress("glCreateShaderObjectARB" ); |
216 | glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC) SDL_GL_GetProcAddress("glDeleteObjectARB" ); |
217 | glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC) SDL_GL_GetProcAddress("glGetInfoLogARB" ); |
218 | glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC) SDL_GL_GetProcAddress("glGetObjectParameterivARB" ); |
219 | glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC) SDL_GL_GetProcAddress("glGetUniformLocationARB" ); |
220 | glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC) SDL_GL_GetProcAddress("glLinkProgramARB" ); |
221 | glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC) SDL_GL_GetProcAddress("glShaderSourceARB" ); |
222 | glUniform1iARB = (PFNGLUNIFORM1IARBPROC) SDL_GL_GetProcAddress("glUniform1iARB" ); |
223 | glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glUseProgramObjectARB" ); |
224 | if (glAttachObjectARB && |
225 | glCompileShaderARB && |
226 | glCreateProgramObjectARB && |
227 | glCreateShaderObjectARB && |
228 | glDeleteObjectARB && |
229 | glGetInfoLogARB && |
230 | glGetObjectParameterivARB && |
231 | glGetUniformLocationARB && |
232 | glLinkProgramARB && |
233 | glShaderSourceARB && |
234 | glUniform1iARB && |
235 | glUseProgramObjectARB) { |
236 | shaders_supported = SDL_TRUE; |
237 | } |
238 | } |
239 | |
240 | if (!shaders_supported) { |
241 | return SDL_FALSE; |
242 | } |
243 | |
244 | /* Compile all the shaders */ |
245 | for (i = 0; i < NUM_SHADERS; ++i) { |
246 | if (!CompileShaderProgram(&shaders[i])) { |
247 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to compile shader!\n" ); |
248 | return SDL_FALSE; |
249 | } |
250 | } |
251 | |
252 | /* We're done! */ |
253 | return SDL_TRUE; |
254 | } |
255 | |
256 | static void QuitShaders() |
257 | { |
258 | int i; |
259 | |
260 | for (i = 0; i < NUM_SHADERS; ++i) { |
261 | DestroyShaderProgram(&shaders[i]); |
262 | } |
263 | } |
264 | |
265 | /* Quick utility function for texture creation */ |
266 | static int |
267 | power_of_two(int input) |
268 | { |
269 | int value = 1; |
270 | |
271 | while (value < input) { |
272 | value <<= 1; |
273 | } |
274 | return value; |
275 | } |
276 | |
277 | GLuint |
278 | SDL_GL_LoadTexture(SDL_Surface * surface, GLfloat * texcoord) |
279 | { |
280 | GLuint texture; |
281 | int w, h; |
282 | SDL_Surface *image; |
283 | SDL_Rect area; |
284 | SDL_BlendMode saved_mode; |
285 | |
286 | /* Use the surface width and height expanded to powers of 2 */ |
287 | w = power_of_two(surface->w); |
288 | h = power_of_two(surface->h); |
289 | texcoord[0] = 0.0f; /* Min X */ |
290 | texcoord[1] = 0.0f; /* Min Y */ |
291 | texcoord[2] = (GLfloat) surface->w / w; /* Max X */ |
292 | texcoord[3] = (GLfloat) surface->h / h; /* Max Y */ |
293 | |
294 | image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, |
295 | #if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */ |
296 | 0x000000FF, |
297 | 0x0000FF00, 0x00FF0000, 0xFF000000 |
298 | #else |
299 | 0xFF000000, |
300 | 0x00FF0000, 0x0000FF00, 0x000000FF |
301 | #endif |
302 | ); |
303 | if (image == NULL) { |
304 | return 0; |
305 | } |
306 | |
307 | /* Save the alpha blending attributes */ |
308 | SDL_GetSurfaceBlendMode(surface, &saved_mode); |
309 | SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE); |
310 | |
311 | /* Copy the surface into the GL texture image */ |
312 | area.x = 0; |
313 | area.y = 0; |
314 | area.w = surface->w; |
315 | area.h = surface->h; |
316 | SDL_BlitSurface(surface, &area, image, &area); |
317 | |
318 | /* Restore the alpha blending attributes */ |
319 | SDL_SetSurfaceBlendMode(surface, saved_mode); |
320 | |
321 | /* Create an OpenGL texture for the image */ |
322 | glGenTextures(1, &texture); |
323 | glBindTexture(GL_TEXTURE_2D, texture); |
324 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
325 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
326 | glTexImage2D(GL_TEXTURE_2D, |
327 | 0, |
328 | GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels); |
329 | SDL_FreeSurface(image); /* No longer needed */ |
330 | |
331 | return texture; |
332 | } |
333 | |
334 | /* A general OpenGL initialization function. Sets all of the initial parameters. */ |
335 | void InitGL(int Width, int Height) /* We call this right after our OpenGL window is created. */ |
336 | { |
337 | GLdouble aspect; |
338 | |
339 | glViewport(0, 0, Width, Height); |
340 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); /* This Will Clear The Background Color To Black */ |
341 | glClearDepth(1.0); /* Enables Clearing Of The Depth Buffer */ |
342 | glDepthFunc(GL_LESS); /* The Type Of Depth Test To Do */ |
343 | glEnable(GL_DEPTH_TEST); /* Enables Depth Testing */ |
344 | glShadeModel(GL_SMOOTH); /* Enables Smooth Color Shading */ |
345 | |
346 | glMatrixMode(GL_PROJECTION); |
347 | glLoadIdentity(); /* Reset The Projection Matrix */ |
348 | |
349 | aspect = (GLdouble)Width / Height; |
350 | glOrtho(-3.0, 3.0, -3.0 / aspect, 3.0 / aspect, 0.0, 1.0); |
351 | |
352 | glMatrixMode(GL_MODELVIEW); |
353 | } |
354 | |
355 | /* The main drawing function. */ |
356 | void DrawGLScene(SDL_Window *window, GLuint texture, GLfloat * texcoord) |
357 | { |
358 | /* Texture coordinate lookup, to make it simple */ |
359 | enum { |
360 | MINX, |
361 | MINY, |
362 | MAXX, |
363 | MAXY |
364 | }; |
365 | |
366 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* Clear The Screen And The Depth Buffer */ |
367 | glLoadIdentity(); /* Reset The View */ |
368 | |
369 | glTranslatef(-1.5f,0.0f,0.0f); /* Move Left 1.5 Units */ |
370 | |
371 | /* draw a triangle (in smooth coloring mode) */ |
372 | glBegin(GL_POLYGON); /* start drawing a polygon */ |
373 | glColor3f(1.0f,0.0f,0.0f); /* Set The Color To Red */ |
374 | glVertex3f( 0.0f, 1.0f, 0.0f); /* Top */ |
375 | glColor3f(0.0f,1.0f,0.0f); /* Set The Color To Green */ |
376 | glVertex3f( 1.0f,-1.0f, 0.0f); /* Bottom Right */ |
377 | glColor3f(0.0f,0.0f,1.0f); /* Set The Color To Blue */ |
378 | glVertex3f(-1.0f,-1.0f, 0.0f); /* Bottom Left */ |
379 | glEnd(); /* we're done with the polygon (smooth color interpolation) */ |
380 | |
381 | glTranslatef(3.0f,0.0f,0.0f); /* Move Right 3 Units */ |
382 | |
383 | /* Enable blending */ |
384 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); |
385 | glEnable(GL_BLEND); |
386 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
387 | |
388 | /* draw a textured square (quadrilateral) */ |
389 | glEnable(GL_TEXTURE_2D); |
390 | glBindTexture(GL_TEXTURE_2D, texture); |
391 | glColor3f(1.0f,1.0f,1.0f); |
392 | if (shaders_supported) { |
393 | glUseProgramObjectARB(shaders[current_shader].program); |
394 | } |
395 | |
396 | glBegin(GL_QUADS); /* start drawing a polygon (4 sided) */ |
397 | glTexCoord2f(texcoord[MINX], texcoord[MINY]); |
398 | glVertex3f(-1.0f, 1.0f, 0.0f); /* Top Left */ |
399 | glTexCoord2f(texcoord[MAXX], texcoord[MINY]); |
400 | glVertex3f( 1.0f, 1.0f, 0.0f); /* Top Right */ |
401 | glTexCoord2f(texcoord[MAXX], texcoord[MAXY]); |
402 | glVertex3f( 1.0f,-1.0f, 0.0f); /* Bottom Right */ |
403 | glTexCoord2f(texcoord[MINX], texcoord[MAXY]); |
404 | glVertex3f(-1.0f,-1.0f, 0.0f); /* Bottom Left */ |
405 | glEnd(); /* done with the polygon */ |
406 | |
407 | if (shaders_supported) { |
408 | glUseProgramObjectARB(0); |
409 | } |
410 | glDisable(GL_TEXTURE_2D); |
411 | |
412 | /* swap buffers to display, since we're double buffered. */ |
413 | SDL_GL_SwapWindow(window); |
414 | } |
415 | |
416 | int main(int argc, char **argv) |
417 | { |
418 | int done; |
419 | SDL_Window *window; |
420 | SDL_Surface *surface; |
421 | GLuint texture; |
422 | GLfloat texcoords[4]; |
423 | |
424 | /* Enable standard application logging */ |
425 | SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); |
426 | |
427 | /* Initialize SDL for video output */ |
428 | if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) { |
429 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to initialize SDL: %s\n" , SDL_GetError()); |
430 | exit(1); |
431 | } |
432 | |
433 | /* Create a 640x480 OpenGL screen */ |
434 | window = SDL_CreateWindow( "Shader Demo" , SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL ); |
435 | if ( !window ) { |
436 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create OpenGL window: %s\n" , SDL_GetError()); |
437 | SDL_Quit(); |
438 | exit(2); |
439 | } |
440 | |
441 | if ( !SDL_GL_CreateContext(window)) { |
442 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create OpenGL context: %s\n" , SDL_GetError()); |
443 | SDL_Quit(); |
444 | exit(2); |
445 | } |
446 | |
447 | surface = SDL_LoadBMP("icon.bmp" ); |
448 | if ( ! surface ) { |
449 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to load icon.bmp: %s\n" , SDL_GetError()); |
450 | SDL_Quit(); |
451 | exit(3); |
452 | } |
453 | texture = SDL_GL_LoadTexture(surface, texcoords); |
454 | SDL_FreeSurface(surface); |
455 | |
456 | /* Loop, drawing and checking events */ |
457 | InitGL(640, 480); |
458 | if (InitShaders()) { |
459 | SDL_Log("Shaders supported, press SPACE to cycle them.\n" ); |
460 | } else { |
461 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Shaders not supported!\n" ); |
462 | } |
463 | done = 0; |
464 | while ( ! done ) { |
465 | DrawGLScene(window, texture, texcoords); |
466 | |
467 | /* This could go in a separate function */ |
468 | { SDL_Event event; |
469 | while ( SDL_PollEvent(&event) ) { |
470 | if ( event.type == SDL_QUIT ) { |
471 | done = 1; |
472 | } |
473 | if ( event.type == SDL_KEYDOWN ) { |
474 | if ( event.key.keysym.sym == SDLK_SPACE ) { |
475 | current_shader = (current_shader + 1) % NUM_SHADERS; |
476 | } |
477 | if ( event.key.keysym.sym == SDLK_ESCAPE ) { |
478 | done = 1; |
479 | } |
480 | } |
481 | } |
482 | } |
483 | } |
484 | QuitShaders(); |
485 | SDL_Quit(); |
486 | return 1; |
487 | } |
488 | |
489 | #else /* HAVE_OPENGL */ |
490 | |
491 | int |
492 | main(int argc, char *argv[]) |
493 | { |
494 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "No OpenGL support on this system\n" ); |
495 | return 1; |
496 | } |
497 | |
498 | #endif /* HAVE_OPENGL */ |
499 | |
500 | /* vi: set ts=4 sw=4 expandtab: */ |
501 | |