Line data Source code
1 0 : /*
2 :
3 : OOTexture.m
4 :
5 : Copyright (C) 2007-2013 Jens Ayton and contributors
6 :
7 : Permission is hereby granted, free of charge, to any person obtaining a copy
8 : of this software and associated documentation files (the "Software"), to deal
9 : in the Software without restriction, including without limitation the rights
10 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 : copies of the Software, and to permit persons to whom the Software is
12 : furnished to do so, subject to the following conditions:
13 :
14 : The above copyright notice and this permission notice shall be included in all
15 : copies or substantial portions of the Software.
16 :
17 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 : SOFTWARE.
24 :
25 : */
26 :
27 : #import "OOTexture.h"
28 : #import "OOTextureInternal.h"
29 : #import "OOConcreteTexture.h"
30 : #import "OONullTexture.h"
31 :
32 : #import "OOTextureLoader.h"
33 : #import "OOTextureGenerator.h"
34 :
35 : #import "OOCollectionExtractors.h"
36 : #import "Universe.h"
37 : #import "ResourceManager.h"
38 : #import "OOOpenGLExtensionManager.h"
39 : #import "OOMacroOpenGL.h"
40 : #import "OOCPUInfo.h"
41 : #import "OOCache.h"
42 : #import "OOPixMap.h"
43 :
44 :
45 0 : NSString * const kOOTextureSpecifierNameKey = @"name";
46 0 : NSString * const kOOTextureSpecifierSwizzleKey = @"extract_channel";
47 0 : NSString * const kOOTextureSpecifierMinFilterKey = @"min_filter";
48 0 : NSString * const kOOTextureSpecifierMagFilterKey = @"mag_filter";
49 0 : NSString * const kOOTextureSpecifierNoShrinkKey = @"no_shrink";
50 0 : NSString * const kOOTextureSpecifierExtraShrinkKey = @"extra_shrink";
51 0 : NSString * const kOOTextureSpecifierRepeatSKey = @"repeat_s";
52 0 : NSString * const kOOTextureSpecifierRepeatTKey = @"repeat_t";
53 0 : NSString * const kOOTextureSpecifierCubeMapKey = @"cube_map";
54 0 : NSString * const kOOTextureSpecifierAnisotropyKey = @"anisotropy";
55 0 : NSString * const kOOTextureSpecifierLODBiasKey = @"texture_LOD_bias";
56 :
57 0 : NSString * const kOOTextureSpecifierModulateColorKey = @"color";
58 0 : NSString * const kOOTextureSpecifierIlluminationModeKey = @"illumination_mode";
59 0 : NSString * const kOOTextureSpecifierSelfColorKey = @"self_color";
60 0 : NSString * const kOOTextureSpecifierScaleFactorKey = @"scale_factor";
61 0 : NSString * const kOOTextureSpecifierBindingKey = @"binding";
62 :
63 : // Used only by "internal" specifiers from OOMakeTextureSpecifier.
64 0 : static NSString * const kOOTextureSpecifierFlagValueInternalKey = @"_oo_internal_flags";
65 :
66 :
67 : /* Texture caching:
68 : two and a half parallel caching mechanisms are used. sLiveTextureCache
69 : tracks all live texture objects with cache keys, without retaining them
70 : (using NSValues to refer to the objects).
71 :
72 : sAllLiveTextures tracks all textures, including ones without cache keys,
73 : so that they can be notified of graphics resets. This also uses NSValues
74 : to avoid retaining the textures.
75 :
76 : sRecentTextures tracks up to kRecentTexturesCount textures which
77 : have been used recently, and retains them.
78 :
79 : This means that the number of live texture objects will never fall below
80 : 80% of kRecentTexturesCount (80% comes from the behaviour of OOCache), but
81 : old textures will eventually be released. If the number of active textures
82 : exceeds kRecentTexturesCount, all of them will be reusable through
83 : sLiveTextureCache, but only a most-recently-fetched subset will be kept
84 : around by the cache when the number drops.
85 :
86 : Note the textures in sRecentTextures are a superset of the textures in
87 : sLiveTextureCache, and the textures in sLiveTextureCache are a superset
88 : of sRecentTextures.
89 : */
90 0 : enum
91 : {
92 : kRecentTexturesCount = 50
93 : };
94 :
95 0 : static NSMutableDictionary *sLiveTextureCache;
96 0 : static NSMutableSet *sAllLiveTextures;
97 0 : static OOCache *sRecentTextures;
98 :
99 :
100 0 : static BOOL sCheckedExtensions;
101 0 : OOTextureInfo gOOTextureInfo;
102 :
103 :
104 : @interface OOTexture (OOPrivate)
105 :
106 0 : - (void) addToCaches;
107 0 : + (OOTexture *) existingTextureForKey:(NSString *)key;
108 :
109 0 : - (void) forceRebind;
110 :
111 0 : + (void)checkExtensions;
112 :
113 : #ifndef NDEBUG
114 0 : - (id) retainInContext:(NSString *)context;
115 0 : - (void) releaseInContext:(NSString *)context;
116 0 : - (id) autoreleaseInContext:(NSString *)context;
117 : #endif
118 :
119 : @end
120 :
121 :
122 : #ifndef NDEBUG
123 0 : static NSString *sGlobalTraceContext = nil;
124 :
125 0 : #define SET_TRACE_CONTEXT(str) do { sGlobalTraceContext = (str); } while (0)
126 : #else
127 : #define SET_TRACE_CONTEXT(str) do { } while (0)
128 : #endif
129 0 : #define CLEAR_TRACE_CONTEXT() SET_TRACE_CONTEXT(nil)
130 :
131 :
132 : @implementation OOTexture
133 :
134 : + (id)textureWithName:(NSString *)name
135 : inFolder:(NSString*)directory
136 : options:(OOTextureFlags)options
137 : anisotropy:(GLfloat)anisotropy
138 : lodBias:(GLfloat)lodBias
139 : {
140 : NSString *key = nil;
141 : OOTexture *result = nil;
142 : NSString *path = nil;
143 : BOOL noFNF;
144 :
145 : if (EXPECT_NOT(name == nil)) return nil;
146 : if (EXPECT_NOT(!sCheckedExtensions)) [self checkExtensions];
147 :
148 : if (!gOOTextureInfo.anisotropyAvailable || (options & kOOTextureMinFilterMask) != kOOTextureMinFilterMipMap)
149 : {
150 : anisotropy = 0.0f;
151 : }
152 : if (!gOOTextureInfo.textureLODBiasAvailable || (options & kOOTextureMinFilterMask) != kOOTextureMinFilterMipMap)
153 : {
154 : lodBias = 0.0f;
155 : }
156 :
157 : noFNF = (options & kOOTextureNoFNFMessage) != 0;
158 : options = OOApplyTextureOptionDefaults(options & ~kOOTextureNoFNFMessage);
159 :
160 : // Look for existing texture
161 : key = OOGenerateTextureCacheKey(directory, name, options, anisotropy, lodBias);
162 : result = [OOTexture existingTextureForKey:key];
163 : if (result == nil)
164 : {
165 : path = [ResourceManager pathForFileNamed:name inFolder:directory];
166 : if (path == nil)
167 : {
168 : if (!noFNF) OOLogWARN(kOOLogFileNotFound, @"Could not find texture file \"%@\".", name);
169 : return nil;
170 : }
171 :
172 : // No existing texture, load texture.
173 : result = [[[OOConcreteTexture alloc] initWithPath:path key:key options:options anisotropy:anisotropy lodBias:lodBias] autorelease];
174 : }
175 :
176 :
177 : return result;
178 : }
179 :
180 :
181 : + (id)textureWithName:(NSString *)name
182 : inFolder:(NSString*)directory
183 : {
184 : return [self textureWithName:name
185 : inFolder:directory
186 : options:kOOTextureDefaultOptions
187 : anisotropy:kOOTextureDefaultAnisotropy
188 : lodBias:kOOTextureDefaultLODBias];
189 : }
190 :
191 :
192 : + (id)textureWithConfiguration:(id)configuration
193 : {
194 : return [self textureWithConfiguration:configuration extraOptions:0];
195 : }
196 :
197 :
198 : + (id) textureWithConfiguration:(id)configuration extraOptions:(OOTextureFlags)extraOptions
199 : {
200 : NSString *name = nil;
201 : OOTextureFlags options = 0;
202 : GLfloat anisotropy = 0.0f;
203 : GLfloat lodBias = 0.0f;
204 :
205 : if (!OOInterpretTextureSpecifier(configuration, &name, &options, &anisotropy, &lodBias, NO)) return nil;
206 :
207 : return [self textureWithName:name inFolder:@"Textures" options:options | extraOptions anisotropy:anisotropy lodBias:lodBias];
208 : }
209 :
210 :
211 : + (id) nullTexture
212 : {
213 : return [OONullTexture sharedNullTexture];
214 : }
215 :
216 :
217 : + (id) textureWithGenerator:(OOTextureGenerator *)generator
218 : {
219 : return [self textureWithGenerator:generator enqueue: NO];
220 : }
221 :
222 :
223 : + (id) textureWithGenerator:(OOTextureGenerator *)generator enqueue:(BOOL) enqueue
224 : {
225 : if (generator == nil) return nil;
226 :
227 : #ifndef OOTEXTURE_NO_CACHE
228 : OOTexture *existing = [OOTexture existingTextureForKey:[generator cacheKey]];
229 : if (existing != nil && !enqueue) return [[existing retain] autorelease];
230 : #endif
231 :
232 : if (![generator enqueue])
233 : {
234 : OOLogERR(@"texture.generator.queue.failed", @"Failed to queue generator %@", generator);
235 : return nil;
236 : }
237 : OOLog(@"texture.generator.queue", @"Queued texture generator %@", generator);
238 :
239 : OOTexture *result = [[[OOConcreteTexture alloc] initWithLoader:generator
240 : key:[generator cacheKey]
241 : options:OOApplyTextureOptionDefaults([generator textureOptions])
242 : anisotropy:[generator anisotropy]
243 : lodBias:[generator lodBias]] autorelease];
244 :
245 : return result;
246 : }
247 :
248 :
249 0 : - (id) init
250 : {
251 : if ((self = [super init]))
252 : {
253 : if (EXPECT_NOT(sAllLiveTextures == nil)) sAllLiveTextures = [[NSMutableSet alloc] init];
254 : [sAllLiveTextures addObject:[NSValue valueWithPointer:self]];
255 : }
256 :
257 : return self;
258 : }
259 :
260 :
261 0 : - (void) dealloc
262 : {
263 : [sAllLiveTextures removeObject:[NSValue valueWithPointer:self]];
264 :
265 : [super dealloc];
266 : }
267 :
268 :
269 : - (void)apply
270 : {
271 : OOLogGenericSubclassResponsibility();
272 : }
273 :
274 :
275 : + (void)applyNone
276 : {
277 : OO_ENTER_OPENGL();
278 : OOGL(glBindTexture(GL_TEXTURE_2D, 0));
279 : #if OO_TEXTURE_CUBE_MAP
280 : if (OOCubeMapsAvailable()) OOGL(glBindTexture(GL_TEXTURE_CUBE_MAP, 0));
281 : #endif
282 :
283 : #if GL_EXT_texture_lod_bias
284 : if (gOOTextureInfo.textureLODBiasAvailable) OOGL(glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, 0));
285 : #endif
286 : }
287 :
288 :
289 : - (void)ensureFinishedLoading
290 : {
291 : }
292 :
293 :
294 : - (BOOL) isFinishedLoading
295 : {
296 : return YES;
297 : }
298 :
299 :
300 : - (NSString *) cacheKey
301 : {
302 : return nil;
303 : }
304 :
305 :
306 : - (NSSize) dimensions
307 : {
308 : OOLogGenericSubclassResponsibility();
309 : return NSZeroSize;
310 : }
311 :
312 :
313 : - (NSSize) originalDimensions
314 : {
315 : return [self dimensions];
316 : }
317 :
318 :
319 : - (BOOL) isMipMapped
320 : {
321 : OOLogGenericSubclassResponsibility();
322 : return NO;
323 : }
324 :
325 :
326 : - (struct OOPixMap) copyPixMapRepresentation
327 : {
328 : return kOONullPixMap;
329 : }
330 :
331 :
332 : - (BOOL) isRectangleTexture
333 : {
334 : return NO;
335 : }
336 :
337 :
338 : - (BOOL) isCubeMap
339 : {
340 : return NO;
341 : }
342 :
343 :
344 : - (NSSize)texCoordsScale
345 : {
346 : return NSMakeSize(1.0, 1.0);
347 : }
348 :
349 :
350 : - (GLint)glTextureName
351 : {
352 : OOLogGenericSubclassResponsibility();
353 : return 0;
354 : }
355 :
356 :
357 : + (void)clearCache
358 : {
359 : /* Does not clear sAllLiveTextures - that really must refer to all
360 : live texture objects.
361 : */
362 : SET_TRACE_CONTEXT(@"clearing sLiveTextureCache");
363 : [sLiveTextureCache autorelease];
364 : sLiveTextureCache = nil;
365 :
366 : SET_TRACE_CONTEXT(@"clearing sRecentTextures");
367 : [sRecentTextures autorelease];
368 : sRecentTextures = nil;
369 : CLEAR_TRACE_CONTEXT();
370 : }
371 :
372 :
373 : + (void)rebindAllTextures
374 : {
375 : NSEnumerator *textureEnum = nil;
376 : id texture = nil;
377 :
378 : // Keeping around unused, cached textures is unhelpful at this point.
379 : DESTROY(sRecentTextures);
380 :
381 : for (textureEnum = [sAllLiveTextures objectEnumerator]; (texture = [[textureEnum nextObject] pointerValue]); )
382 : {
383 : [texture forceRebind];
384 : }
385 : }
386 :
387 :
388 : #ifndef NDEBUG
389 : - (void) setTrace:(BOOL)trace
390 : {
391 : if (trace && !_trace)
392 : {
393 : OOLog(@"texture.allocTrace.begin", @"Started tracing texture %p with retain count %lu.", self, [self retainCount]);
394 : }
395 : _trace = trace;
396 : }
397 :
398 :
399 : + (NSArray *) cachedTexturesByAge
400 : {
401 : return [sRecentTextures objectsByAge];
402 : }
403 :
404 :
405 : + (NSSet *) allTextures
406 : {
407 : NSMutableSet *result = [NSMutableSet setWithCapacity:[sAllLiveTextures count]];
408 : NSValue *box = nil;
409 : NSEnumerator *texEnum = nil;
410 : for (texEnum = [sAllLiveTextures objectEnumerator]; (box = [texEnum nextObject]); )
411 : {
412 : [result addObject:[box pointerValue]];
413 : }
414 :
415 : return result;
416 : }
417 :
418 :
419 : - (size_t) dataSize
420 : {
421 : NSSize dimensions = [self dimensions];
422 : size_t size = dimensions.width * dimensions.height;
423 : if ([self isCubeMap]) size *= 6;
424 : if ([self isMipMapped]) size = size * 4 / 3;
425 :
426 : return size;
427 : }
428 :
429 :
430 : - (NSString *) name
431 : {
432 : OOLogGenericSubclassResponsibility();
433 : return nil;
434 : }
435 : #endif
436 :
437 :
438 0 : - (void) forceRebind
439 : {
440 : OOLogGenericSubclassResponsibility();
441 : }
442 :
443 :
444 0 : - (void) addToCaches
445 : {
446 : #ifndef OOTEXTURE_NO_CACHE
447 : NSString *cacheKey = [self cacheKey];
448 : if (cacheKey == nil) return;
449 :
450 : // Add self to in-use textures cache, wrapped in an NSValue so the texture isn't retained by the cache.
451 : if (EXPECT_NOT(sLiveTextureCache == nil)) sLiveTextureCache = [[NSMutableDictionary alloc] init];
452 :
453 : SET_TRACE_CONTEXT(@"in-use textures cache - SHOULD NOT RETAIN");
454 : [sLiveTextureCache setObject:[NSValue valueWithPointer:self] forKey:cacheKey];
455 : CLEAR_TRACE_CONTEXT();
456 :
457 : // Add self to recent textures cache.
458 : if (EXPECT_NOT(sRecentTextures == nil))
459 : {
460 : sRecentTextures = [[OOCache alloc] init];
461 : [sRecentTextures setName:@"recent textures"];
462 : [sRecentTextures setAutoPrune:YES];
463 : [sRecentTextures setPruneThreshold:kRecentTexturesCount];
464 : }
465 :
466 : SET_TRACE_CONTEXT(@"adding to recent textures cache");
467 : [sRecentTextures setObject:self forKey:cacheKey];
468 : CLEAR_TRACE_CONTEXT();
469 : #endif
470 : }
471 :
472 :
473 0 : - (void) removeFromCaches
474 : {
475 : #ifndef OOTEXTURE_NO_CACHE
476 : NSString *cacheKey = [self cacheKey];
477 : if (cacheKey == nil) return;
478 :
479 : [sLiveTextureCache removeObjectForKey:cacheKey];
480 : if (EXPECT_NOT([sRecentTextures objectForKey:cacheKey] == self))
481 : {
482 : /* Experimental for now: I think the recent crash problems may
483 : * be because if the last reference to a texture is in
484 : * sRecentTextures, and the texture is regenerated, it
485 : * replaces the texture, causing a release. Therefore, if this
486 : * texture *isn't* overretained in the texture cache, the 2009
487 : * crash avoider will delete its replacement from the cache
488 : * ... possibly before that texture has been fully added to
489 : * the cache itself. So, the texture is only removed from the
490 : * cache by key if it was in it with that key. The extra time
491 : * needed to generate a planet texture compared with loading a
492 : * standard one may be why this problem shows up. - CIM 20140122
493 : */
494 : NSAssert2(0, @"Texture retain count error for %@; cacheKey is %@.", self, cacheKey); //miscount in autorelease
495 : // The following line is needed in order to avoid crashes when there's a 'texture retain count error'. Please do not delete. -- Kaks 20091221
496 : [sRecentTextures removeObjectForKey:cacheKey]; // make sure there's no reference left inside sRecentTexture ( was a show stopper for 1.73)
497 : }
498 : #endif
499 : }
500 :
501 :
502 0 : + (OOTexture *) existingTextureForKey:(NSString *)key
503 : {
504 : #ifndef OOTEXTURE_NO_CACHE
505 : if (key != nil)
506 : {
507 : return (OOTexture *)[[sLiveTextureCache objectForKey:key] pointerValue];
508 : }
509 : return nil;
510 : #else
511 : return nil;
512 : #endif
513 : }
514 :
515 :
516 0 : + (void)checkExtensions
517 : {
518 : OO_ENTER_OPENGL();
519 :
520 : sCheckedExtensions = YES;
521 :
522 : OOOpenGLExtensionManager *extMgr = [OOOpenGLExtensionManager sharedManager];
523 : BOOL ver120 = [extMgr versionIsAtLeastMajor:1 minor:2];
524 : BOOL ver130 = [extMgr versionIsAtLeastMajor:1 minor:3];
525 :
526 : #if GL_EXT_texture_filter_anisotropic
527 : gOOTextureInfo.anisotropyAvailable = [extMgr haveExtension:@"GL_EXT_texture_filter_anisotropic"];
528 : OOGL(glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gOOTextureInfo.anisotropyScale));
529 : gOOTextureInfo.anisotropyScale *= OOClamp_0_1_f([[NSUserDefaults standardUserDefaults] oo_floatForKey:@"texture-anisotropy-scale" defaultValue:0.5]);
530 : #endif
531 :
532 : #ifdef GL_CLAMP_TO_EDGE
533 : gOOTextureInfo.clampToEdgeAvailable = ver120 || [extMgr haveExtension:@"GL_SGIS_texture_edge_clamp"];
534 : #endif
535 :
536 : #if OO_GL_CLIENT_STORAGE
537 : gOOTextureInfo.clientStorageAvailable = [extMgr haveExtension:@"GL_APPLE_client_storage"];
538 : #endif
539 :
540 : gOOTextureInfo.textureMaxLevelAvailable = ver120 || [extMgr haveExtension:@"GL_SGIS_texture_lod"];
541 :
542 : #if GL_EXT_texture_lod_bias
543 : if ([[NSUserDefaults standardUserDefaults] oo_boolForKey:@"use-texture-lod-bias" defaultValue:YES])
544 : {
545 : gOOTextureInfo.textureLODBiasAvailable = [extMgr haveExtension:@"GL_EXT_texture_lod_bias"];
546 : }
547 : else
548 : {
549 : gOOTextureInfo.textureLODBiasAvailable = NO;
550 : }
551 : #endif
552 :
553 : #if GL_EXT_texture_rectangle
554 : gOOTextureInfo.rectangleTextureAvailable = [extMgr haveExtension:@"GL_EXT_texture_rectangle"];
555 : #endif
556 :
557 : #if OO_TEXTURE_CUBE_MAP
558 : if (![[NSUserDefaults standardUserDefaults] boolForKey:@"disable-cube-maps"])
559 : {
560 : gOOTextureInfo.cubeMapAvailable = ver130 || [extMgr haveExtension:@"GL_ARB_texture_cube_map"];
561 : }
562 : else
563 : {
564 : gOOTextureInfo.cubeMapAvailable = NO;
565 : }
566 :
567 : #endif
568 : }
569 :
570 :
571 : #ifndef NDEBUG
572 0 : - (id) retainInContext:(NSString *)context
573 : {
574 : if (_trace)
575 : {
576 : if (context) OOLog(@"texture.allocTrace.retain", @"Texture %p retained (retain count -> %lu) - %@.", self, [self retainCount] + 1, context);
577 : else OOLog(@"texture.allocTrace.retain", @"Texture %p retained (retain count -> %lu).", self, [self retainCount] + 1);
578 : }
579 :
580 : return [super retain];
581 : }
582 :
583 :
584 0 : - (void) releaseInContext:(NSString *)context
585 : {
586 : if (_trace)
587 : {
588 : if (context) OOLog(@"texture.allocTrace.release", @"Texture %p released (retain count -> %lu) - %@.", self, [self retainCount] - 1, context);
589 : else OOLog(@"texture.allocTrace.release", @"Texture %p released (retain count -> %lu).", self, [self retainCount] - 1);
590 : }
591 :
592 : [super release];
593 : }
594 :
595 :
596 0 : - (id) autoreleaseInContext:(NSString *)context
597 : {
598 : if (_trace)
599 : {
600 : if (context) OOLog(@"texture.allocTrace.autoreleased", @"Texture %p autoreleased - %@.", self, context);
601 : else OOLog(@"texture.allocTrace.autoreleased", @"Texture %p autoreleased.", self);
602 : }
603 :
604 : return [super autorelease];
605 : }
606 :
607 :
608 0 : - (id) retain
609 : {
610 : return [self retainInContext:sGlobalTraceContext];
611 : }
612 :
613 :
614 0 : - (oneway void) release
615 : {
616 : [self releaseInContext:sGlobalTraceContext];
617 : }
618 :
619 :
620 0 : - (id) autorelease
621 : {
622 : return [self autoreleaseInContext:sGlobalTraceContext];
623 : }
624 : #endif
625 :
626 : @end
627 :
628 :
629 : @implementation NSDictionary (OOTextureConveniences)
630 :
631 : - (NSDictionary *) oo_textureSpecifierForKey:(id)key defaultName:(NSString *)name
632 : {
633 : return OOTextureSpecFromObject([self objectForKey:key], name);
634 : }
635 :
636 : @end
637 :
638 : @implementation NSArray (OOTextureConveniences)
639 :
640 : - (NSDictionary *) oo_textureSpecifierAtIndex:(unsigned)index defaultName:(NSString *)name
641 : {
642 : return OOTextureSpecFromObject([self objectAtIndex:index], name);
643 : }
644 :
645 : @end
646 :
647 0 : NSDictionary *OOTextureSpecFromObject(id object, NSString *defaultName)
648 : {
649 : if (object == nil) object = defaultName;
650 : if ([object isKindOfClass:[NSString class]])
651 : {
652 : if ([object isEqualToString:@""]) return nil;
653 : return [NSDictionary dictionaryWithObject:object forKey:@"name"];
654 : }
655 : if (![object isKindOfClass:[NSDictionary class]]) return nil;
656 :
657 : // If we're here, it's a dictionary.
658 : if (defaultName == nil || [object oo_stringForKey:@"name"] != nil) return object;
659 :
660 : // If we get here, there's no "name" key and there is a default, so we fill it in:
661 : NSMutableDictionary *mutableResult = [NSMutableDictionary dictionaryWithDictionary:object];
662 : [mutableResult setObject:[[defaultName copy] autorelease] forKey:@"name"];
663 : return mutableResult;
664 : }
665 :
666 :
667 0 : uint8_t OOTextureComponentsForFormat(OOTextureDataFormat format)
668 : {
669 : switch (format)
670 : {
671 : case kOOTextureDataRGBA:
672 : return 4;
673 :
674 : case kOOTextureDataGrayscale:
675 : return 1;
676 :
677 : case kOOTextureDataGrayscaleAlpha:
678 : return 2;
679 :
680 : case kOOTextureDataInvalid:
681 : break;
682 : }
683 :
684 : return 0;
685 : }
686 :
687 :
688 0 : BOOL OOCubeMapsAvailable(void)
689 : {
690 : return gOOTextureInfo.cubeMapAvailable;
691 : }
692 :
693 :
694 0 : BOOL OOInterpretTextureSpecifier(id specifier, NSString **outName, OOTextureFlags *outOptions, float *outAnisotropy, float *outLODBias, BOOL ignoreExtract)
695 : {
696 : NSString *name = nil;
697 : OOTextureFlags options = kOOTextureDefaultOptions;
698 : float anisotropy = kOOTextureDefaultAnisotropy;
699 : float lodBias = kOOTextureDefaultLODBias;
700 :
701 : if ([specifier isKindOfClass:[NSString class]])
702 : {
703 : name = specifier;
704 : }
705 : else if ([specifier isKindOfClass:[NSDictionary class]])
706 : {
707 : name = [specifier oo_stringForKey:kOOTextureSpecifierNameKey];
708 : if (name == nil)
709 : {
710 : OOLog(@"texture.load.noName", @"Invalid texture configuration dictionary (must specify name):\n%@", specifier);
711 : return NO;
712 : }
713 :
714 : int quickFlags = [specifier oo_intForKey:kOOTextureSpecifierFlagValueInternalKey defaultValue:-1];
715 : if (quickFlags != -1)
716 : {
717 : options = quickFlags;
718 : }
719 : else
720 : {
721 : NSString *filterString = [specifier oo_stringForKey:kOOTextureSpecifierMinFilterKey defaultValue:@"default"];
722 : if ([filterString isEqualToString:@"nearest"]) options |= kOOTextureMinFilterNearest;
723 : else if ([filterString isEqualToString:@"linear"]) options |= kOOTextureMinFilterLinear;
724 : else if ([filterString isEqualToString:@"mipmap"]) options |= kOOTextureMinFilterMipMap;
725 : else options |= kOOTextureMinFilterDefault; // Covers "default"
726 :
727 : filterString = [specifier oo_stringForKey:kOOTextureSpecifierMagFilterKey defaultValue:@"default"];
728 : if ([filterString isEqualToString:@"nearest"]) options |= kOOTextureMagFilterNearest;
729 : else options |= kOOTextureMagFilterLinear; // Covers "default" and "linear"
730 :
731 : if ([specifier oo_boolForKey:kOOTextureSpecifierNoShrinkKey defaultValue:NO]) options |= kOOTextureNoShrink;
732 : if ([specifier oo_boolForKey:kOOTextureSpecifierExtraShrinkKey defaultValue:NO]) options |= kOOTextureExtraShrink;
733 : if ([specifier oo_boolForKey:kOOTextureSpecifierRepeatSKey defaultValue:NO]) options |= kOOTextureRepeatS;
734 : if ([specifier oo_boolForKey:kOOTextureSpecifierRepeatTKey defaultValue:NO]) options |= kOOTextureRepeatT;
735 : if ([specifier oo_boolForKey:kOOTextureSpecifierCubeMapKey defaultValue:NO]) options |= kOOTextureAllowCubeMap;
736 :
737 : if (!ignoreExtract)
738 : {
739 : NSString *extractChannel = [specifier oo_stringForKey:@"extract_channel"];
740 : if (extractChannel != nil)
741 : {
742 : if ([extractChannel isEqualToString:@"r"]) options |= kOOTextureExtractChannelR;
743 : else if ([extractChannel isEqualToString:@"g"]) options |= kOOTextureExtractChannelG;
744 : else if ([extractChannel isEqualToString:@"b"]) options |= kOOTextureExtractChannelB;
745 : else if ([extractChannel isEqualToString:@"a"]) options |= kOOTextureExtractChannelA;
746 : else
747 : {
748 : OOLogWARN(@"texture.load.extractChannel.invalid", @"Unknown value \"%@\" for extract_channel in specifier \"%@\" (should be \"r\", \"g\", \"b\" or \"a\").", extractChannel,specifier);
749 : }
750 : }
751 : }
752 : }
753 : anisotropy = [specifier oo_floatForKey:@"anisotropy" defaultValue:kOOTextureDefaultAnisotropy];
754 : lodBias = [specifier oo_floatForKey:@"texture_LOD_bias" defaultValue:kOOTextureDefaultLODBias];
755 : }
756 : else
757 : {
758 : // Bad type
759 : if (specifier != nil) OOLog(kOOLogParameterError, @"%s: expected string or dictionary, got %@.", __PRETTY_FUNCTION__, [specifier class]);
760 : return NO;
761 : }
762 :
763 : if ([name length] == 0) return NO;
764 :
765 : if (outName != NULL) *outName = name;
766 : if (outOptions != NULL) *outOptions = options;
767 : if (outAnisotropy != NULL) *outAnisotropy = anisotropy;
768 : if (outLODBias != NULL) *outLODBias = lodBias;
769 :
770 : return YES;
771 : }
772 :
773 :
774 0 : NSDictionary *OOMakeTextureSpecifier(NSString *name, OOTextureFlags options, float anisotropy, float lodBias, BOOL internal)
775 : {
776 : NSMutableDictionary *result = [NSMutableDictionary dictionary];
777 :
778 : [result setObject:name forKey:kOOTextureSpecifierNameKey];
779 :
780 : if (anisotropy != kOOTextureDefaultAnisotropy) [result oo_setFloat:anisotropy forKey:kOOTextureSpecifierAnisotropyKey];
781 : if (lodBias != kOOTextureDefaultLODBias) [result oo_setFloat:lodBias forKey:kOOTextureSpecifierLODBiasKey];
782 :
783 : if (internal)
784 : {
785 : [result oo_setUnsignedInteger:options forKey:kOOTextureSpecifierFlagValueInternalKey];
786 : }
787 : else
788 : {
789 : NSString *value = nil;
790 : switch (options & kOOTextureMinFilterMask)
791 : {
792 : case kOOTextureMinFilterDefault:
793 : break;
794 :
795 : case kOOTextureMinFilterNearest:
796 : value = @"nearest";
797 : break;
798 :
799 : case kOOTextureMinFilterLinear:
800 : value = @"linear";
801 : break;
802 :
803 : case kOOTextureMinFilterMipMap:
804 : value = @"mipmap";
805 : break;
806 : }
807 : if (value != nil) [result setObject:value forKey:kOOTextureSpecifierNoShrinkKey];
808 :
809 : value = nil;
810 : switch (options & kOOTextureMagFilterMask)
811 : {
812 : case kOOTextureMagFilterNearest:
813 : value = @"nearest";
814 : break;
815 :
816 : case kOOTextureMagFilterLinear:
817 : break;
818 : }
819 : if (value != nil) [result setObject:value forKey:kOOTextureSpecifierMagFilterKey];
820 :
821 : value = nil;
822 : switch (options & kOOTextureExtractChannelMask)
823 : {
824 : case kOOTextureExtractChannelNone:
825 : break;
826 :
827 : case kOOTextureExtractChannelR:
828 : value = @"r";
829 : break;
830 :
831 : case kOOTextureExtractChannelG:
832 : value = @"g";
833 : break;
834 :
835 : case kOOTextureExtractChannelB:
836 : value = @"b";
837 : break;
838 :
839 : case kOOTextureExtractChannelA:
840 : value = @"a";
841 : break;
842 : }
843 : if (value != nil) [result setObject:value forKey:kOOTextureSpecifierSwizzleKey];
844 :
845 : if (options & kOOTextureNoShrink) [result oo_setBool:YES forKey:kOOTextureSpecifierNoShrinkKey];
846 : if (options & kOOTextureRepeatS) [result oo_setBool:YES forKey:kOOTextureSpecifierRepeatSKey];
847 : if (options & kOOTextureRepeatT) [result oo_setBool:YES forKey:kOOTextureSpecifierRepeatTKey];
848 : if (options & kOOTextureAllowCubeMap) [result oo_setBool:YES forKey:kOOTextureSpecifierCubeMapKey];
849 : }
850 :
851 : return result;
852 : }
853 :
854 :
855 0 : OOTextureFlags OOApplyTextureOptionDefaults(OOTextureFlags options)
856 : {
857 : // Set default flags if needed
858 : if ((options & kOOTextureMinFilterMask) == kOOTextureMinFilterDefault)
859 : {
860 : if ([UNIVERSE reducedDetail])
861 : {
862 : options |= kOOTextureMinFilterLinear;
863 : }
864 : else
865 : {
866 : options |= kOOTextureMinFilterMipMap;
867 : }
868 : }
869 :
870 : if (!gOOTextureInfo.textureMaxLevelAvailable)
871 : {
872 : /* In the unlikely case of an OpenGL system without GL_SGIS_texture_lod,
873 : disable mip-mapping completely. Strictly this is only needed for
874 : non-square textures, but extra logic for such a rare case isn't
875 : worth it.
876 : */
877 : if ((options & kOOTextureMinFilterMask) == kOOTextureMinFilterMipMap)
878 : {
879 : options ^= kOOTextureMinFilterMipMap ^ kOOTextureMinFilterLinear;
880 : }
881 : }
882 :
883 : if (options & kOOTextureAllowRectTexture)
884 : {
885 : // Apply rectangle texture restrictions (regardless of whether rectangle textures are available, for consistency)
886 : options &= kOOTextureFlagsAllowedForRectangleTexture;
887 : if ((options & kOOTextureMinFilterMask) == kOOTextureMinFilterMipMap)
888 : {
889 : options = (kOOTextureMinFilterMask & ~kOOTextureMinFilterMask) | kOOTextureMinFilterLinear;
890 : }
891 :
892 : #if GL_EXT_texture_rectangle
893 : if (!gOOTextureInfo.rectangleTextureAvailable)
894 : {
895 : options &= ~kOOTextureAllowRectTexture;
896 : }
897 : #else
898 : options &= ~kOOTextureAllowRectTexture;
899 : #endif
900 : }
901 :
902 : options &= kOOTextureDefinedFlags;
903 :
904 : return options;
905 : }
906 :
907 :
908 0 : NSString *OOGenerateTextureCacheKey(NSString *directory, NSString *name, OOTextureFlags options, float anisotropy, float lodBias)
909 : {
910 : if (!gOOTextureInfo.anisotropyAvailable || (options & kOOTextureMinFilterMask) != kOOTextureMinFilterMipMap)
911 : {
912 : anisotropy = 0.0f;
913 : }
914 : if (!gOOTextureInfo.textureLODBiasAvailable || (options & kOOTextureMinFilterMask) != kOOTextureMinFilterMipMap)
915 : {
916 : lodBias = 0.0f;
917 : }
918 : options = OOApplyTextureOptionDefaults(options & ~kOOTextureNoFNFMessage);
919 :
920 : return [NSString stringWithFormat:@"%@%@%@:0x%.4X/%g/%g", directory ? directory : (NSString *)@"", directory ? @"/" : @"", name, options, anisotropy, lodBias];
921 : }
922 :
923 :
924 0 : NSString *OOTextureCacheKeyForSpecifier(id specifier)
925 : {
926 : NSString *name;
927 : OOTextureFlags options;
928 : float anisotropy;
929 : float lodBias;
930 :
931 : OOInterpretTextureSpecifier(specifier, &name, &options, &anisotropy, &lodBias, NO);
932 : return OOGenerateTextureCacheKey(@"Textures", name, options, anisotropy, lodBias);
933 : }
|