Line data Source code
1 0 : /*
2 :
3 : OOConcreteTexture.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 "OOTextureInternal.h"
28 : #import "OOConcreteTexture.h"
29 :
30 : #import "OOTextureLoader.h"
31 :
32 : #import "OOCollectionExtractors.h"
33 : #import "Universe.h"
34 : #import "ResourceManager.h"
35 : #import "OOOpenGLExtensionManager.h"
36 : #import "OOMacroOpenGL.h"
37 : #import "OOCPUInfo.h"
38 : #import "OOPixMap.h"
39 :
40 : #ifndef NDEBUG
41 : #import "OOTextureGenerator.h"
42 : #endif
43 :
44 :
45 : #if OOLITE_BIG_ENDIAN
46 : #define RGBA_IMAGE_TYPE GL_UNSIGNED_INT_8_8_8_8_REV
47 : #elif OOLITE_LITTLE_ENDIAN
48 : #define RGBA_IMAGE_TYPE GL_UNSIGNED_BYTE
49 : #else
50 : #error Neither OOLITE_BIG_ENDIAN nor OOLITE_LITTLE_ENDIAN is defined as nonzero!
51 : #endif
52 :
53 :
54 : @interface OOConcreteTexture (Private)
55 :
56 0 : - (void)setUpTexture;
57 0 : - (void)uploadTexture;
58 0 : - (void)uploadTextureDataWithMipMap:(BOOL)mipMap format:(OOTextureDataFormat)format;
59 : #if OO_TEXTURE_CUBE_MAP
60 : - (void) uploadTextureCubeMapDataWithMipMap:(BOOL)mipMap format:(OOTextureDataFormat)format;
61 : #endif
62 :
63 0 : - (GLenum) glTextureTarget;
64 :
65 : #if OOTEXTURE_RELOADABLE
66 0 : - (BOOL) isReloadable;
67 : #endif
68 :
69 : @end
70 :
71 :
72 : static BOOL DecodeFormat(OOTextureDataFormat format, uint32_t options, GLenum *outFormat, GLenum *outInternalFormat, GLenum *outType);
73 :
74 :
75 : @implementation OOConcreteTexture
76 :
77 : - (id) initWithLoader:(OOTextureLoader *)loader
78 : key:(NSString *)key
79 : options:(uint32_t)options
80 : anisotropy:(GLfloat)anisotropy
81 : lodBias:(GLfloat)lodBias
82 : {
83 : if (loader == nil)
84 : {
85 : [self release];
86 : return nil;
87 : }
88 :
89 : self = [super init];
90 : if (EXPECT_NOT(self == nil)) return nil;
91 :
92 : _loader = [loader retain];
93 : _options = options;
94 :
95 : #if GL_EXT_texture_filter_anisotropic
96 : _anisotropy = OOClamp_0_1_f(anisotropy) * gOOTextureInfo.anisotropyScale;
97 : #endif
98 : #if GL_EXT_texture_lod_bias
99 : _lodBias = lodBias;
100 : #endif
101 :
102 : #ifndef NDEBUG
103 : if ([loader isKindOfClass:[OOTextureGenerator class]])
104 : {
105 : _name = [[NSString alloc] initWithFormat:@"<%@>", [loader class]];
106 : }
107 : #endif
108 :
109 : _key = [key copy];
110 :
111 : [self addToCaches];
112 :
113 : return self;
114 : }
115 :
116 :
117 : - (id)initWithPath:(NSString *)path
118 : key:(NSString *)key
119 : options:(uint32_t)options
120 : anisotropy:(float)anisotropy
121 : lodBias:(GLfloat)lodBias
122 : {
123 : OOTextureLoader *loader = [OOTextureLoader loaderWithPath:path options:options];
124 : if (loader == nil)
125 : {
126 : [self release];
127 : return nil;
128 : }
129 :
130 : if ((self = [self initWithLoader:loader key:key options:options anisotropy:anisotropy lodBias:lodBias]))
131 : {
132 : #if OOTEXTURE_RELOADABLE
133 : _path = [path retain];
134 : #endif
135 : }
136 :
137 : return self;
138 : }
139 :
140 :
141 0 : - (void)dealloc
142 : {
143 : #ifndef NDEBUG
144 : OOLog(_trace ? @"texture.allocTrace.dealloc" : @"texture.dealloc", @"Deallocating and uncaching texture %p", self);
145 : #endif
146 :
147 : #if OOTEXTURE_RELOADABLE
148 : DESTROY(_path);
149 : #endif
150 :
151 : if (_loaded)
152 : {
153 : if (_textureName != 0)
154 : {
155 : OO_ENTER_OPENGL();
156 : OOGL(glDeleteTextures(1, &_textureName));
157 : _textureName = 0;
158 : }
159 : free(_bytes);
160 : _bytes = NULL;
161 : }
162 :
163 : #ifndef OOTEXTURE_NO_CACHE
164 : [self removeFromCaches];
165 : [_key autorelease];
166 : _key = nil;
167 : #endif
168 :
169 : DESTROY(_loader);
170 :
171 : #ifndef NDEBUG
172 : DESTROY(_name);
173 : #endif
174 :
175 : [super dealloc];
176 : }
177 :
178 :
179 0 : - (NSString *) descriptionComponents
180 : {
181 : NSString *stateDesc = nil;
182 :
183 : if (_loaded)
184 : {
185 : if (_valid)
186 : {
187 : stateDesc = [NSString stringWithFormat:@"%u x %u", _width, _height];
188 : }
189 : else
190 : {
191 : stateDesc = @"LOAD ERROR";
192 : }
193 : }
194 : else
195 : {
196 : stateDesc = @"loading";
197 : }
198 :
199 : return [NSString stringWithFormat:@"%@, %@", _key, stateDesc];
200 : }
201 :
202 :
203 0 : - (NSString *) shortDescriptionComponents
204 : {
205 : return _key;
206 : }
207 :
208 :
209 : #ifndef NDEBUG
210 0 : - (NSString *) name
211 : {
212 : if (_name != nil) return _name;
213 :
214 : #if OOTEXTURE_RELOADABLE
215 : NSString *name = [_path lastPathComponent];
216 : #else
217 : NSString *name = [[[[self cacheKey] componentsSeparatedByString:@":"] objectAtIndex:0] lastPathComponent];
218 : #endif
219 :
220 : NSString *channelSuffix = nil;
221 : switch (_options & kOOTextureExtractChannelMask)
222 : {
223 : case kOOTextureExtractChannelR:
224 : channelSuffix = @":r";
225 : break;
226 :
227 : case kOOTextureExtractChannelG:
228 : channelSuffix = @":g";
229 : break;
230 :
231 : case kOOTextureExtractChannelB:
232 : channelSuffix = @":b";
233 : break;
234 :
235 : case kOOTextureExtractChannelA:
236 : channelSuffix = @":a";
237 : break;
238 : }
239 :
240 : if (channelSuffix != nil) name = [name stringByAppendingString:channelSuffix];
241 :
242 : return name;
243 : }
244 : #endif
245 :
246 :
247 0 : - (void)apply
248 : {
249 : OO_ENTER_OPENGL();
250 :
251 : if (EXPECT_NOT(!_loaded)) [self setUpTexture];
252 : else if (EXPECT_NOT(!_uploaded)) [self uploadTexture];
253 : else OOGL(glBindTexture([self glTextureTarget], _textureName));
254 :
255 : #if GL_EXT_texture_lod_bias
256 : if (gOOTextureInfo.textureLODBiasAvailable) OOGL(glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, _lodBias));
257 : #endif
258 : }
259 :
260 :
261 0 : - (void)ensureFinishedLoading
262 : {
263 : if (!_loaded) [self setUpTexture];
264 : }
265 :
266 :
267 0 : - (BOOL) isFinishedLoading
268 : {
269 : return _loaded || [_loader isReady];
270 : }
271 :
272 :
273 0 : - (NSString *) cacheKey
274 : {
275 : return _key;
276 : }
277 :
278 :
279 0 : - (NSSize)dimensions
280 : {
281 : [self ensureFinishedLoading];
282 :
283 : return NSMakeSize(_width, _height);
284 : }
285 :
286 :
287 0 : - (NSSize) originalDimensions
288 : {
289 : [self ensureFinishedLoading];
290 :
291 : return NSMakeSize(_originalWidth, _originalHeight);
292 : }
293 :
294 :
295 0 : - (BOOL) isMipMapped
296 : {
297 : [self ensureFinishedLoading];
298 :
299 : return _mipLevels != 0;
300 : }
301 :
302 :
303 0 : - (struct OOPixMap) copyPixMapRepresentation
304 : {
305 : [self ensureFinishedLoading];
306 :
307 : OOPixMap px = kOONullPixMap;
308 :
309 : if (_bytes != NULL)
310 : {
311 : // If possible, just copy our existing buffer.
312 : px = OOMakePixMap(_bytes, _width, _height, _format, 0, 0);
313 : px = OODuplicatePixMap(px, 0);
314 : }
315 : #if OOTEXTURE_RELOADABLE
316 : else
317 : {
318 : // Otherwise, read it back from OpenGL.
319 : OO_ENTER_OPENGL();
320 :
321 : GLenum format, internalFormat, type;
322 : if (!DecodeFormat(_format, _options, &format, &internalFormat, &type))
323 : {
324 : return kOONullPixMap;
325 : }
326 :
327 : if (![self isCubeMap])
328 : {
329 :
330 : px = OOAllocatePixMap(_width, _height, _format, 0, 0);
331 : if (!OOIsValidPixMap(px)) return kOONullPixMap;
332 :
333 : glGetTexImage(GL_TEXTURE_2D, 0, format, type, px.pixels);
334 : }
335 : #if OO_TEXTURE_CUBE_MAP
336 : else
337 : {
338 : px = OOAllocatePixMap(_width, _width * 6, _format, 0, 0);
339 : if (!OOIsValidPixMap(px)) return kOONullPixMap;
340 : uint8_t *pixels = px.pixels;
341 :
342 : unsigned i;
343 : for (i = 0; i < 6; i++)
344 : {
345 : glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, type, pixels);
346 : pixels += OOPixMapBytesPerPixelForFormat(_format) * _width * _width;
347 : }
348 : }
349 : #endif
350 : }
351 : #endif
352 :
353 : return px;
354 : }
355 :
356 :
357 0 : - (BOOL) isRectangleTexture
358 : {
359 : #if GL_EXT_texture_rectangle
360 : return _isRectTexture;
361 : #else
362 : return NO;
363 : #endif
364 : }
365 :
366 :
367 0 : - (BOOL) isCubeMap
368 : {
369 : #if OO_TEXTURE_CUBE_MAP
370 : return _isCubeMap;
371 : #else
372 : return NO;
373 : #endif
374 : }
375 :
376 :
377 0 : - (NSSize)texCoordsScale
378 : {
379 : #if GL_EXT_texture_rectangle
380 : if (_loaded)
381 : {
382 : if (!_isRectTexture)
383 : {
384 : return NSMakeSize(1.0f, 1.0f);
385 : }
386 : else
387 : {
388 : return NSMakeSize(_width, _height);
389 : }
390 : }
391 : else
392 : {
393 : // Not loaded
394 : if (!(_options & kOOTextureAllowRectTexture))
395 : {
396 : return NSMakeSize(1.0f, 1.0f);
397 : }
398 : else
399 : {
400 : // Finishing may clear the rectangle texture flag (if the texture turns out to be POT)
401 : [self ensureFinishedLoading];
402 : return [self texCoordsScale];
403 : }
404 : }
405 : #else
406 : return NSMakeSize(1.0f, 1.0f);
407 : #endif
408 : }
409 :
410 :
411 0 : - (GLint)glTextureName
412 : {
413 : [self ensureFinishedLoading];
414 :
415 : return _textureName;
416 : }
417 :
418 : @end
419 :
420 :
421 : @implementation OOConcreteTexture (Private)
422 :
423 : - (void)setUpTexture
424 : {
425 : OOPixMap pm;
426 :
427 : // This will block until loading is completed, if necessary.
428 : if ([_loader getResult:&pm format:&_format originalWidth:&_originalWidth originalHeight:&_originalHeight])
429 : {
430 : _bytes = pm.pixels;
431 : _width = pm.width;
432 : _height = pm.height;
433 :
434 : #if OO_TEXTURE_CUBE_MAP
435 : if (_options & kOOTextureAllowCubeMap && _height == _width * 6 && gOOTextureInfo.cubeMapAvailable)
436 : {
437 : _isCubeMap = YES;
438 : }
439 : #endif
440 :
441 : #if !defined(NDEBUG) && OOTEXTURE_RELOADABLE
442 : if (_trace)
443 : {
444 : static unsigned dumpID = 0;
445 : NSString *name = [NSString stringWithFormat:@"tex dump %u \"%@\"", ++dumpID,[self name]];
446 : OOLog(@"texture.trace.dump", @"Dumped traced texture %@ to \'%@.png\'", self, name);
447 : OODumpPixMap(pm, name);
448 : }
449 : #endif
450 :
451 : [self uploadTexture];
452 : }
453 : else
454 : {
455 : _textureName = 0;
456 : _valid = NO;
457 : _uploaded = YES;
458 : }
459 :
460 : _loaded = YES;
461 :
462 : DESTROY(_loader);
463 : }
464 :
465 :
466 : - (void) uploadTexture
467 : {
468 : GLint filter;
469 : BOOL mipMap = NO;
470 :
471 : OO_ENTER_OPENGL();
472 :
473 : if (!_uploaded)
474 : {
475 : GLenum texTarget = [self glTextureTarget];
476 :
477 : OOGL(glGenTextures(1, &_textureName));
478 : OOGL(glBindTexture(texTarget, _textureName));
479 :
480 : // Select wrap mode
481 : GLint clampMode = gOOTextureInfo.clampToEdgeAvailable ? GL_CLAMP_TO_EDGE : GL_CLAMP;
482 : GLint wrapS = (_options & kOOTextureRepeatS) ? GL_REPEAT : clampMode;
483 : GLint wrapT = (_options & kOOTextureRepeatT) ? GL_REPEAT : clampMode;
484 :
485 : #if OO_TEXTURE_CUBE_MAP
486 : if (texTarget == GL_TEXTURE_CUBE_MAP)
487 : {
488 : wrapS = wrapT = clampMode;
489 : OOGL(glTexParameteri(texTarget, GL_TEXTURE_WRAP_R, clampMode));
490 : }
491 : #endif
492 :
493 : OOGL(glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, wrapS));
494 : OOGL(glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, wrapT));
495 :
496 : // Select min filter
497 : filter = _options & kOOTextureMinFilterMask;
498 : if (filter == kOOTextureMinFilterNearest) filter = GL_NEAREST;
499 : else if (filter == kOOTextureMinFilterMipMap)
500 : {
501 : mipMap = YES;
502 : filter = GL_LINEAR_MIPMAP_LINEAR;
503 : }
504 : else filter = GL_LINEAR;
505 : OOGL(glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, filter));
506 :
507 : #if GL_EXT_texture_filter_anisotropic
508 : if (gOOTextureInfo.anisotropyAvailable && mipMap && 1.0 < _anisotropy)
509 : {
510 : OOGL(glTexParameterf(texTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, _anisotropy));
511 : }
512 : #endif
513 :
514 : // Select mag filter
515 : filter = _options & kOOTextureMagFilterMask;
516 : if (filter == kOOTextureMagFilterNearest) filter = GL_NEAREST;
517 : else filter = GL_LINEAR;
518 : OOGL(glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, filter));
519 :
520 : // if (gOOTextureInfo.clientStorageAvailable) EnableClientStorage();
521 :
522 : if (texTarget == GL_TEXTURE_2D)
523 : {
524 : [self uploadTextureDataWithMipMap:mipMap format:_format];
525 : OOLog(@"texture.upload", @"Uploaded texture %u (%ux%u pixels, %@)", _textureName, _width, _height, _key);
526 : }
527 : #if OO_TEXTURE_CUBE_MAP
528 : else if (texTarget == GL_TEXTURE_CUBE_MAP)
529 : {
530 : [self uploadTextureCubeMapDataWithMipMap:mipMap format:_format];
531 : OOLog(@"texture.upload", @"Uploaded cube map texture %u (%ux%ux6 pixels, %@)", _textureName, _width, _width, _key);
532 : }
533 : #endif
534 : else
535 : {
536 : [NSException raise:NSInternalInconsistencyException format:@"Unhandled texture target 0x%X.", texTarget];
537 : }
538 :
539 : _valid = YES;
540 : _uploaded = YES;
541 :
542 : #if OOTEXTURE_RELOADABLE
543 : if ([self isReloadable])
544 : {
545 : free(_bytes);
546 : _bytes = NULL;
547 : }
548 : #endif
549 : }
550 : }
551 :
552 :
553 : - (void)uploadTextureDataWithMipMap:(BOOL)mipMap format:(OOTextureDataFormat)format
554 : {
555 : GLenum glFormat = 0, internalFormat = 0, type = 0;
556 : unsigned w = _width,
557 : h = _height,
558 : level = 0;
559 : char *bytes = _bytes;
560 : uint8_t components = OOTextureComponentsForFormat(format);
561 :
562 : OO_ENTER_OPENGL();
563 :
564 : if (!DecodeFormat(format, _options, &glFormat, &internalFormat, &type)) return;
565 :
566 : while (0 < w && 0 < h)
567 : {
568 : OOGL(glTexImage2D(GL_TEXTURE_2D, level++, internalFormat, w, h, 0, glFormat, type, bytes));
569 : if (!mipMap) return;
570 : bytes += w * components * h;
571 : w >>= 1;
572 : h >>= 1;
573 : }
574 :
575 : // Note: we only reach here if (mipMap).
576 : _mipLevels = level - 1;
577 : OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, _mipLevels));
578 : }
579 :
580 :
581 : #if OO_TEXTURE_CUBE_MAP
582 : - (void) uploadTextureCubeMapDataWithMipMap:(BOOL)mipMap format:(OOTextureDataFormat)format
583 : {
584 : OO_ENTER_OPENGL();
585 :
586 : GLenum glFormat = 0, internalFormat = 0, type = 0;
587 : if (!DecodeFormat(format, _options, &glFormat, &internalFormat, &type)) return;
588 : uint8_t components = OOTextureComponentsForFormat(format);
589 :
590 : // Calculate stride between cube map sides.
591 : size_t sideSize = _width * _width * components;
592 : if (mipMap)
593 : {
594 : sideSize = sideSize * 4 / 3;
595 : sideSize = (sideSize + 15) & ~15;
596 : }
597 :
598 : unsigned side;
599 : for (side = 0; side < 6; side++)
600 : {
601 : char *bytes = _bytes;
602 : bytes += side * sideSize;
603 :
604 : unsigned w = _width, level = 0;
605 :
606 : while (0 < w)
607 : {
608 : OOGL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + side, level++, internalFormat, w, w, 0, glFormat, type, bytes));
609 : if (!mipMap) break;
610 : bytes += w * w * components;
611 : w >>= 1;
612 : }
613 : }
614 : }
615 : #endif
616 :
617 :
618 : - (GLenum) glTextureTarget
619 : {
620 : GLenum texTarget = GL_TEXTURE_2D;
621 : #if OO_TEXTURE_CUBE_MAP
622 : if (_isCubeMap)
623 : {
624 : texTarget = GL_TEXTURE_CUBE_MAP;
625 : }
626 : #endif
627 : return texTarget;
628 : }
629 :
630 :
631 0 : - (void) forceRebind
632 : {
633 : if (_loaded && _uploaded && _valid)
634 : {
635 : OO_ENTER_OPENGL();
636 :
637 : _uploaded = NO;
638 : OOGL(glDeleteTextures(1, &_textureName));
639 : _textureName = 0;
640 :
641 : #if OOTEXTURE_RELOADABLE
642 : if ([self isReloadable])
643 : {
644 : OOLog(@"texture.reload", @"Reloading texture %@", self);
645 :
646 : free(_bytes);
647 : _bytes = NULL;
648 : _loaded = NO;
649 : _uploaded = NO;
650 : _valid = NO;
651 :
652 : _loader = [[OOTextureLoader loaderWithPath:_path options:_options] retain];
653 : }
654 : #endif
655 : }
656 : }
657 :
658 :
659 : #if OOTEXTURE_RELOADABLE
660 :
661 : - (BOOL) isReloadable
662 : {
663 : return _path != nil;
664 : }
665 :
666 : #endif
667 :
668 : @end
669 :
670 :
671 0 : static BOOL DecodeFormat(OOTextureDataFormat format, uint32_t options, GLenum *outFormat, GLenum *outInternalFormat, GLenum *outType)
672 : {
673 : NSCParameterAssert(outFormat != NULL && outInternalFormat != NULL && outType != NULL);
674 :
675 : switch (format)
676 : {
677 : case kOOTextureDataRGBA:
678 : *outFormat = GL_RGBA;
679 : *outInternalFormat = options & kOOTextureSRGBA ? GL_SRGB_ALPHA : GL_RGBA;
680 : *outType = RGBA_IMAGE_TYPE;
681 : return YES;
682 :
683 : case kOOTextureDataGrayscale:
684 : if (options & kOOTextureAlphaMask)
685 : {
686 : *outFormat = GL_ALPHA;
687 : *outInternalFormat = GL_ALPHA8;
688 : }
689 : else
690 : {
691 : *outFormat = GL_LUMINANCE;
692 : *outInternalFormat = GL_LUMINANCE8;
693 : }
694 : *outType = GL_UNSIGNED_BYTE;
695 : return YES;
696 :
697 : case kOOTextureDataGrayscaleAlpha:
698 : *outFormat = GL_LUMINANCE_ALPHA;
699 : *outInternalFormat = GL_LUMINANCE8_ALPHA8;
700 : *outType = GL_UNSIGNED_BYTE;
701 : return YES;
702 :
703 : default:
704 : OOLog(kOOLogParameterError, @"Unexpected texture format %u.", format);
705 : return NO;
706 : }
707 : }
|