Oolite 1.91.0.7604-240417-a536cbe
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 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
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*/
441static 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
484static NSString *GetGLSLInfoLog(GLhandleARB shaderObject)
485{
486 GLint length;
487 GLcharARB *log = NULL;
488 NSString *result = nil;
489
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
NSUInteger gDebugFlags
Definition main.m:7
@ 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)