Oolite
Loading...
Searching...
No Matches
OOShaderProgram.m
Go to the documentation of this file.
1/*
2
3OOShaderProgram.m
4
5
6Copyright (C) 2007-2013 Jens Ayton
7
8Permission is hereby granted, free of charge, to any person obtaining a copy
9of this software and associated documentation files (the "Software"), to deal
10in the Software without restriction, including without limitation the rights
11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12copies of the Software, and to permit persons to whom the Software is
13furnished to do so, subject to the following conditions:
14
15The above copyright notice and this permission notice shall be included in all
16copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24SOFTWARE.
25
26*/
27
29
30#if OO_SHADERS
31
32#import "OOShaderProgram.h"
34#import "OOStringParsing.h"
35#import "ResourceManager.h"
37#import "OOMacroOpenGL.h"
39#import "OODebugFlags.h"
40#import "Universe.h"
41#import "MyOpenGLView.h"
42
43
44static NSMutableDictionary *sShaderCache = nil;
45static OOShaderProgram *sActiveProgram = nil;
46
47
48static BOOL GetShaderSource(NSString *fileName, NSString *shaderType, NSString *prefix, NSString **outResult);
49static 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{
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{
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{
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
216static 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
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
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
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{
391
392 NSString *attrKey = nil;
393
394 foreachkey (attrKey, attributeBindings)
395 {
396 OOGL(glBindAttribLocationARB(program, [attributeBindings oo_unsignedIntForKey:attrKey], [attrKey UTF8String]));
397 }
398}
399
400- (void) bindStandardMatrixUniforms
401{
402 if (standardMatrixUniformLocations != nil)
403 {
404 OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager];
405 id obj;
406 NSArray *pair;
407
409
410 [matrixManager syncModelView];
411 foreach (obj, standardMatrixUniformLocations)
412 {
413 if ([obj isKindOfClass:[NSArray class]])
414 {
415 pair = (NSArray*)obj;
416 if ([[pair oo_stringAtIndex: 2] compare: @"mat3"] == 0)
417 {
418 OOGL(GLUniformMatrix3([pair oo_intAtIndex: 0], [matrixManager getMatrix: [pair oo_intAtIndex: 1]]));
419 }
420 else
421 {
422 GLUniformMatrix([pair oo_intAtIndex: 0], [matrixManager getMatrix: [pair oo_intAtIndex: 1]]);
423 }
424 }
425 }
426 }
427 return;
428}
429
430
431
432@end
433
434
435/* Attempt to load fragment or vertex shader source from a file.
436 Returns YES if source was loaded or no shader was specified, and NO if an
437 external shader was specified but could not be found.
438*/
439static BOOL GetShaderSource(NSString *fileName, NSString *shaderType, NSString *prefix, NSString **outResult)
440{
441 NSString *result = nil;
442 NSArray *extensions = nil;
443 NSString *extension = nil;
444 NSString *nameWithExtension = nil;
445
446 if (fileName == nil) return YES; // It's OK for one or the other of the shaders to be undefined.
447
448 result = [ResourceManager stringFromFilesNamed:fileName inFolder:@"Shaders"];
449 if (result == nil)
450 {
451 extensions = [NSArray arrayWithObjects:shaderType, [shaderType substringToIndex:4], nil]; // vertex and vert, or fragment and frag
452
453 // Futureproofing -- in future, we may wish to support automatic selection between supported shader languages.
454 if (![fileName pathHasExtensionInArray:extensions])
455 {
456 foreach (extension, extensions)
457 {
458 nameWithExtension = [fileName stringByAppendingPathExtension:extension];
459 result = [ResourceManager stringFromFilesNamed:nameWithExtension
460 inFolder:@"Shaders"];
461 if (result != nil) break;
462 }
463 }
464 if (result == nil)
465 {
466 OOLog(kOOLogFileNotFound, @"GLSL ERROR: failed to find fragment program %@.", fileName);
467 return NO;
468 }
469 }
470 /*
471 if (result != nil && prefix != nil)
472 {
473 result = [prefix stringByAppendingString:result];
474 }
475 */
476 if (outResult != NULL) *outResult = result;
477 return YES;
478}
479
480
481static NSString *GetGLSLInfoLog(GLhandleARB shaderObject)
482{
483 GLint length;
484 GLcharARB *log = NULL;
485 NSString *result = nil;
486
488
489 if (EXPECT_NOT(shaderObject == NULL_SHADER)) return nil;
490
491 OOGL(glGetObjectParameterivARB(shaderObject, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length));
492 log = malloc(length);
493 if (log == NULL)
494 {
495 length = 1024;
496 log = malloc(length);
497 if (log == NULL) return @"<out of memory>";
498 }
499 OOGL(glGetInfoLogARB(shaderObject, length, NULL, log));
500
501 result = [NSString stringWithUTF8String:log];
502 if (result == nil) result = [[[NSString alloc] initWithBytes:log length:length - 1 encoding:NSISOLatin1StringEncoding] autorelease];
503 free(log);
504
505 return result;
506}
507
508#endif // OO_SHADERS
NSUInteger gDebugFlags
Definition main.m:7
#define foreachkey(VAR, DICT)
Definition OOCocoa.h:353
@ DEBUG_SHADER_VALIDATION
#define EXPECT_NOT(x)
#define OOLogWARN(class, format,...)
Definition OOLogging.h:113
#define OOLogERR(class, format,...)
Definition OOLogging.h:112
#define OOLog(class, format,...)
Definition OOLogging.h:88
NSString *const kOOLogFileNotFound
Definition OOLogging.m:652
#define OO_ENTER_OPENGL()
void GLUniformMatrix3(int location, OOMatrix M)
Definition OOMatrix.m:457
#define NULL_SHADER
Definition OOOpenGL.h:44
#define OOGL(statement)
Definition OOOpenGL.h:251
return nil
static BOOL GetShaderSource(NSString *fileName, NSString *shaderType, NSString *prefix, NSString **outResult)
NSArray * standardMatrixUniformLocations:(GLhandleARB program)
NSString * stringFromFilesNamed:inFolder:(NSString *fileName,[inFolder] NSString *folderName)