Line data Source code
1 0 : /*
2 :
3 : OOShaderProgram.m
4 :
5 :
6 : Copyright (C) 2007-2013 Jens Ayton
7 :
8 : Permission is hereby granted, free of charge, to any person obtaining a copy
9 : of this software and associated documentation files (the "Software"), to deal
10 : in the Software without restriction, including without limitation the rights
11 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : copies of the Software, and to permit persons to whom the Software is
13 : furnished to do so, subject to the following conditions:
14 :
15 : The above copyright notice and this permission notice shall be included in all
16 : copies or substantial portions of the Software.
17 :
18 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 : SOFTWARE.
25 :
26 : */
27 :
28 : #import "OOOpenGLExtensionManager.h"
29 :
30 : #if OO_SHADERS
31 :
32 : #import "OOShaderProgram.h"
33 : #import "OOFunctionAttributes.h"
34 : #import "OOStringParsing.h"
35 : #import "ResourceManager.h"
36 : #import "OOOpenGLExtensionManager.h"
37 : #import "OOMacroOpenGL.h"
38 : #import "OOCollectionExtractors.h"
39 : #import "OODebugFlags.h"
40 : #import "Universe.h"
41 : #import "MyOpenGLView.h"
42 :
43 :
44 : static NSMutableDictionary *sShaderCache = nil;
45 : static OOShaderProgram *sActiveProgram = nil;
46 :
47 :
48 : static BOOL GetShaderSource(NSString *fileName, NSString *shaderType, NSString *prefix, NSString **outResult);
49 : static NSString *GetGLSLInfoLog(GLhandleARB shaderObject);
50 :
51 :
52 : @interface OOShaderProgram (OOPrivate)
53 :
54 : - (id)initWithVertexShaderSource:(NSString *)vertexSource
55 : fragmentShaderSource:(NSString *)fragmentSource
56 : prefixString:(NSString *)prefixString
57 : vertexName:(NSString *)vertexName
58 : fragmentName:(NSString *)fragmentName
59 : attributeBindings:(NSDictionary *)attributeBindings
60 : key:(NSString *)key;
61 :
62 : - (void) bindAttributes:(NSDictionary *)attributeBindings;
63 : - (void) bindStandardMatrixUniforms;
64 :
65 : @end
66 :
67 :
68 : @implementation OOShaderProgram
69 :
70 : + (id) shaderProgramWithVertexShader:(NSString *)vertexShaderSource
71 : fragmentShader:(NSString *)fragmentShaderSource
72 : vertexShaderName:(NSString *)vertexShaderName
73 : fragmentShaderName:(NSString *)fragmentShaderName
74 : prefix:(NSString *)prefixString // String prepended to program source (both vs and fs)
75 : attributeBindings:(NSDictionary *)attributeBindings // Maps vertex attribute names to "locations".
76 : cacheKey:(NSString *)cacheKey
77 : {
78 : OOShaderProgram *result = nil;
79 :
80 : if ([prefixString length] == 0) prefixString = nil;
81 :
82 : // Use cache to avoid creating duplicate shader programs -- saves on GPU resources and potentially state changes.
83 : // FIXME: probably needs to respond to graphics resets.
84 : result = [[sShaderCache objectForKey:cacheKey] pointerValue];
85 :
86 : if (result == nil)
87 : {
88 : // No cached program; create one...
89 : result = [[OOShaderProgram alloc] initWithVertexShaderSource:vertexShaderSource
90 : fragmentShaderSource:fragmentShaderSource
91 : prefixString:prefixString
92 : vertexName:vertexShaderName
93 : fragmentName:fragmentShaderName
94 : attributeBindings:attributeBindings
95 : key:cacheKey];
96 : [result autorelease];
97 :
98 : if (result != nil && cacheKey != nil)
99 : {
100 : // ...and add it to the cache.
101 : if (sShaderCache == nil) sShaderCache = [[NSMutableDictionary alloc] init];
102 : [sShaderCache setObject:[NSValue valueWithPointer:result] forKey:cacheKey]; // Use NSValue so dictionary doesn't retain program
103 : }
104 : }
105 :
106 : return result;
107 : }
108 :
109 :
110 : + (id)shaderProgramWithVertexShaderName:(NSString *)vertexShaderName
111 : fragmentShaderName:(NSString *)fragmentShaderName
112 : prefix:(NSString *)prefixString
113 : attributeBindings:(NSDictionary *)attributeBindings
114 : {
115 : NSString *cacheKey = nil;
116 : OOShaderProgram *result = nil;
117 : NSString *vertexSource = nil;
118 : NSString *fragmentSource = nil;
119 :
120 : if ([prefixString length] == 0) prefixString = nil;
121 :
122 : // Use cache to avoid creating duplicate shader programs -- saves on GPU resources and potentially state changes.
123 : // FIXME: probably needs to respond to graphics resets.
124 : cacheKey = [NSString stringWithFormat:@"vertex:%@\nfragment:%@\n----\n%@", vertexShaderName, fragmentShaderName, prefixString ?: (NSString *)@""];
125 : result = [[sShaderCache objectForKey:cacheKey] pointerValue];
126 :
127 : if (result == nil)
128 : {
129 : // No cached program; create one...
130 : if (!GetShaderSource(vertexShaderName, @"vertex", prefixString, &vertexSource)) return nil;
131 : if (!GetShaderSource(fragmentShaderName, @"fragment", prefixString, &fragmentSource)) return nil;
132 : result = [[OOShaderProgram alloc] initWithVertexShaderSource:vertexSource
133 : fragmentShaderSource:fragmentSource
134 : prefixString:prefixString
135 : vertexName:vertexShaderName
136 : fragmentName:fragmentShaderName
137 : attributeBindings:attributeBindings
138 : key:cacheKey];
139 :
140 : if (result != nil)
141 : {
142 : // ...and add it to the cache.
143 : [result autorelease];
144 : if (sShaderCache == nil) sShaderCache = [[NSMutableDictionary alloc] init];
145 : [sShaderCache setObject:[NSValue valueWithPointer:result] forKey:cacheKey]; // Use NSValue so dictionary doesn't retain program
146 : }
147 : }
148 :
149 : return result;
150 : }
151 :
152 :
153 : - (void)dealloc
154 : {
155 : OO_ENTER_OPENGL();
156 :
157 : #ifndef NDEBUG
158 : if (EXPECT_NOT(sActiveProgram == self))
159 : {
160 : OOLog(@"shader.dealloc.imbalance", @"%@", @"***** OOShaderProgram deallocated while active, indicating a retain/release imbalance. Expect imminent crash.");
161 : [OOShaderProgram applyNone];
162 : }
163 : #endif
164 :
165 : if (key != nil)
166 : {
167 : [sShaderCache removeObjectForKey:key];
168 : [key release];
169 : }
170 :
171 : if (standardMatrixUniformLocations != nil) {
172 : [standardMatrixUniformLocations release];
173 : }
174 :
175 : OOGL(glDeleteObjectARB(program));
176 :
177 : [super dealloc];
178 : }
179 :
180 :
181 : - (void)apply
182 : {
183 : OO_ENTER_OPENGL();
184 :
185 : if (sActiveProgram != self)
186 : {
187 : [sActiveProgram release];
188 : sActiveProgram = [self retain];
189 : OOGL(glUseProgramObjectARB(program));
190 : [self bindStandardMatrixUniforms];
191 : }
192 : }
193 :
194 :
195 : + (void)applyNone
196 : {
197 : OO_ENTER_OPENGL();
198 :
199 : if (sActiveProgram != nil)
200 : {
201 : [sActiveProgram release];
202 : sActiveProgram = nil;
203 : OOGL(glUseProgramObjectARB(NULL_SHADER));
204 : }
205 : }
206 :
207 :
208 : - (GLhandleARB)program
209 : {
210 : return program;
211 : }
212 :
213 : @end
214 :
215 :
216 : static BOOL ValidateShaderObject(GLhandleARB object, NSString *name)
217 : {
218 : GLint type, subtype = 0, status;
219 : GLenum statusType;
220 : NSString *subtypeString = nil;
221 : NSString *actionString = nil;
222 :
223 : OO_ENTER_OPENGL();
224 :
225 : OOGL(glGetObjectParameterivARB(object, GL_OBJECT_TYPE_ARB, &type));
226 : BOOL linking = type == GL_PROGRAM_OBJECT_ARB;
227 :
228 : if (linking)
229 : {
230 : subtypeString = @"shader program";
231 : actionString = @"linking";
232 : statusType = GL_OBJECT_LINK_STATUS_ARB;
233 : }
234 : else
235 : {
236 : // FIXME
237 : OOGL(glGetObjectParameterivARB(object, GL_OBJECT_SUBTYPE_ARB, &subtype));
238 : switch (subtype)
239 : {
240 : case GL_VERTEX_SHADER_ARB:
241 : subtypeString = @"vertex shader";
242 : break;
243 :
244 : case GL_FRAGMENT_SHADER_ARB:
245 : subtypeString = @"fragment shader";
246 : break;
247 :
248 : #if GL_EXT_geometry_shader4
249 : case GL_GEOMETRY_SHADER_EXT:
250 : subtypeString = @"geometry shader";
251 : break;
252 : #endif
253 :
254 : default:
255 : subtypeString = [NSString stringWithFormat:@"<unknown shader type 0x%.4X>", subtype];
256 : }
257 : actionString = @"compilation";
258 : statusType = GL_OBJECT_COMPILE_STATUS_ARB;
259 : }
260 :
261 : OOGL(glGetObjectParameterivARB(object, statusType, &status));
262 : if (status == GL_FALSE)
263 : {
264 : NSString *msgClass = [NSString stringWithFormat:@"shader.%@.failure", linking ? @"link" : @"compile"];
265 : OOLogERR(msgClass, @"GLSL %@ %@ failed for %@:\n>>>>> GLSL log:\n%@\n", subtypeString, actionString, name, GetGLSLInfoLog(object));
266 : return NO;
267 : }
268 :
269 : #ifndef NDEBUG
270 : if (gDebugFlags & DEBUG_SHADER_VALIDATION && 0)
271 : {
272 : OOGL(glValidateProgramARB(object));
273 : OOGL(glGetObjectParameterivARB(object, GL_OBJECT_VALIDATE_STATUS_ARB, &status));
274 : if (status == GL_FALSE)
275 : {
276 : NSString *msgClass = [NSString stringWithFormat:@"shader.%@.validationFailure", linking ? @"link" : @"compile"];
277 : OOLogWARN(msgClass, @"GLSL %@ %@ failed for %@:\n>>>>> GLSL log:\n%@\n", subtypeString, @"validation", name, GetGLSLInfoLog(object));
278 : return NO;
279 : }
280 : }
281 : #endif
282 :
283 : return YES;
284 : }
285 :
286 :
287 : @implementation OOShaderProgram (OOPrivate)
288 :
289 : - (id)initWithVertexShaderSource:(NSString *)vertexSource
290 : fragmentShaderSource:(NSString *)fragmentSource
291 : prefixString:(NSString *)prefixString
292 : vertexName:(NSString *)vertexName
293 : fragmentName:(NSString *)fragmentName
294 : attributeBindings:(NSDictionary *)attributeBindings
295 : key:(NSString *)inKey
296 : {
297 : BOOL OK = YES;
298 : const GLcharARB *sourceStrings[3] = { "", "#line 0\n", NULL };
299 : GLhandleARB vertexShader = NULL_SHADER;
300 : GLhandleARB fragmentShader = NULL_SHADER;
301 :
302 : OO_ENTER_OPENGL();
303 :
304 : self = [super init];
305 : if (self == nil) OK = NO;
306 :
307 : if (OK && vertexSource == nil && fragmentSource == nil) OK = NO; // Must have at least one shader!
308 :
309 : if (OK && prefixString != nil)
310 : {
311 : sourceStrings[0] = [prefixString UTF8String];
312 : }
313 :
314 : if (OK && vertexSource != nil)
315 : {
316 : // Compile vertex shader.
317 : OOGL(vertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB));
318 : if (vertexShader != NULL_SHADER)
319 : {
320 : sourceStrings[2] = [vertexSource UTF8String];
321 : OOGL(glShaderSourceARB(vertexShader, 3, sourceStrings, NULL));
322 : OOGL(glCompileShaderARB(vertexShader));
323 :
324 : OK = ValidateShaderObject(vertexShader, vertexName);
325 : }
326 : else OK = NO;
327 : }
328 :
329 : if (OK && fragmentSource != nil)
330 : {
331 : // Compile fragment shader.
332 : OOGL(fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB));
333 : if (fragmentShader != NULL_SHADER)
334 : {
335 : sourceStrings[2] = [fragmentSource UTF8String];
336 : OOGL(glShaderSourceARB(fragmentShader, 3, sourceStrings, NULL));
337 : OOGL(glCompileShaderARB(fragmentShader));
338 :
339 : OK = ValidateShaderObject(fragmentShader, fragmentName);
340 : }
341 : else OK = NO;
342 : }
343 :
344 : if (OK)
345 : {
346 : // Link shader.
347 : OOGL(program = glCreateProgramObjectARB());
348 : if (program != NULL_SHADER)
349 : {
350 : if (vertexShader != NULL_SHADER) OOGL(glAttachObjectARB(program, vertexShader));
351 : if (fragmentShader != NULL_SHADER) OOGL(glAttachObjectARB(program, fragmentShader));
352 : [self bindAttributes:attributeBindings];
353 : OOGL(glLinkProgramARB(program));
354 :
355 : OK = ValidateShaderObject(program, [NSString stringWithFormat:@"%@/%@", vertexName, fragmentName]);
356 : }
357 : else OK = NO;
358 : }
359 :
360 : if (OK)
361 : {
362 : key = [inKey copy];
363 : }
364 :
365 : if (vertexShader != NULL_SHADER) OOGL(glDeleteObjectARB(vertexShader));
366 : if (fragmentShader != NULL_SHADER) OOGL(glDeleteObjectARB(fragmentShader));
367 :
368 : if (OK)
369 : {
370 : OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager];
371 : standardMatrixUniformLocations = [matrixManager standardMatrixUniformLocations: program];
372 : }
373 : else
374 : {
375 : if (self != nil && program != NULL_SHADER)
376 : {
377 : OOGL(glDeleteObjectARB(program));
378 : program = NULL_SHADER;
379 : }
380 :
381 : [self release];
382 : self = nil;
383 : }
384 : return self;
385 : }
386 :
387 :
388 : - (void) bindAttributes:(NSDictionary *)attributeBindings
389 : {
390 : OO_ENTER_OPENGL();
391 :
392 : NSString *attrKey = nil;
393 : NSEnumerator *keyEnum = nil;
394 :
395 : for (keyEnum = [attributeBindings keyEnumerator]; (attrKey = [keyEnum nextObject]); )
396 : {
397 : OOGL(glBindAttribLocationARB(program, [attributeBindings oo_unsignedIntForKey:attrKey], [attrKey UTF8String]));
398 : }
399 : }
400 :
401 : - (void) bindStandardMatrixUniforms
402 : {
403 : if (standardMatrixUniformLocations != nil)
404 : {
405 : OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager];
406 : NSEnumerator *enumerator = [standardMatrixUniformLocations objectEnumerator];
407 : id obj;
408 : NSArray *pair;
409 :
410 : OO_ENTER_OPENGL();
411 :
412 : [matrixManager syncModelView];
413 : while ((obj = [enumerator nextObject]))
414 : {
415 : if ([obj isKindOfClass:[NSArray class]])
416 : {
417 : pair = (NSArray*)obj;
418 : if ([[pair oo_stringAtIndex: 2] compare: @"mat3"] == 0)
419 : {
420 : OOGL(GLUniformMatrix3([pair oo_intAtIndex: 0], [matrixManager getMatrix: [pair oo_intAtIndex: 1]]));
421 : }
422 : else
423 : {
424 : GLUniformMatrix([pair oo_intAtIndex: 0], [matrixManager getMatrix: [pair oo_intAtIndex: 1]]);
425 : }
426 : }
427 : }
428 : }
429 : return;
430 : }
431 :
432 :
433 :
434 : @end
435 :
436 :
437 : /* Attempt to load fragment or vertex shader source from a file.
438 : Returns YES if source was loaded or no shader was specified, and NO if an
439 : external shader was specified but could not be found.
440 : */
441 : static BOOL GetShaderSource(NSString *fileName, NSString *shaderType, NSString *prefix, NSString **outResult)
442 : {
443 : NSString *result = nil;
444 : NSArray *extensions = nil;
445 : NSEnumerator *extEnum = nil;
446 : NSString *extension = nil;
447 : NSString *nameWithExtension = nil;
448 :
449 : if (fileName == nil) return YES; // It's OK for one or the other of the shaders to be undefined.
450 :
451 : result = [ResourceManager stringFromFilesNamed:fileName inFolder:@"Shaders"];
452 : if (result == nil)
453 : {
454 : extensions = [NSArray arrayWithObjects:shaderType, [shaderType substringToIndex:4], nil]; // vertex and vert, or fragment and frag
455 :
456 : // Futureproofing -- in future, we may wish to support automatic selection between supported shader languages.
457 : if (![fileName pathHasExtensionInArray:extensions])
458 : {
459 : for (extEnum = [extensions objectEnumerator]; (extension = [extEnum nextObject]); )
460 : {
461 : nameWithExtension = [fileName stringByAppendingPathExtension:extension];
462 : result = [ResourceManager stringFromFilesNamed:nameWithExtension
463 : inFolder:@"Shaders"];
464 : if (result != nil) break;
465 : }
466 : }
467 : if (result == nil)
468 : {
469 : OOLog(kOOLogFileNotFound, @"GLSL ERROR: failed to find fragment program %@.", fileName);
470 : return NO;
471 : }
472 : }
473 : /*
474 : if (result != nil && prefix != nil)
475 : {
476 : result = [prefix stringByAppendingString:result];
477 : }
478 : */
479 : if (outResult != NULL) *outResult = result;
480 : return YES;
481 : }
482 :
483 :
484 : static NSString *GetGLSLInfoLog(GLhandleARB shaderObject)
485 : {
486 : GLint length;
487 : GLcharARB *log = NULL;
488 : NSString *result = nil;
489 :
490 : OO_ENTER_OPENGL();
491 :
492 : if (EXPECT_NOT(shaderObject == NULL_SHADER)) return nil;
493 :
494 : OOGL(glGetObjectParameterivARB(shaderObject, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length));
495 : log = malloc(length);
496 : if (log == NULL)
497 : {
498 : length = 1024;
499 : log = malloc(length);
500 : if (log == NULL) return @"<out of memory>";
501 : }
502 : OOGL(glGetInfoLogARB(shaderObject, length, NULL, log));
503 :
504 : result = [NSString stringWithUTF8String:log];
505 : if (result == nil) result = [[[NSString alloc] initWithBytes:log length:length - 1 encoding:NSISOLatin1StringEncoding] autorelease];
506 : free(log);
507 :
508 : return result;
509 : }
510 :
511 : #endif // OO_SHADERS
|