Oolite 1.91.0.7604-240417-a536cbe
Loading...
Searching...
No Matches
OOTextureLoader.m
Go to the documentation of this file.
1/*
2
3OOTextureLoader.m
4
5
6Copyright (C) 2007-2014 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#import "OOTextureLoader.h"
32#import "OOMaths.h"
33#import "Universe.h"
34#import "OOTextureScaling.h"
37#include <stdlib.h>
38#import "ResourceManager.h"
40#import "OODebugStandards.h"
41
42
43#define DUMP_CONVERTED_CUBE_MAPS 0
44
45enum
46{
47 // Thresholds for reduced-detail texture shrinking in different circumstances.
53};
54
55
56static unsigned sGLMaxSize;
57static uint32_t sUserMaxSize;
58static BOOL sReducedDetail;
59static BOOL sHaveNPOTTextures = NO; // TODO: support "true" non-power-of-two textures.
60static BOOL sHaveSetUp = NO;
61
62
63@interface OOTextureLoader (OOPrivate)
64
65+ (void)setUp;
66
67- (void)applySettings;
68- (void)getDesiredWidth:(OOPixMapDimension *)outDesiredWidth andHeight:(OOPixMapDimension *)outDesiredHeight;
69
70
71@end
72
73
74@implementation OOTextureLoader
75
76+ (id)loaderWithPath:(NSString *)inPath options:(uint32_t)options
77{
78 NSString *extension = nil;
79 id result = nil;
80
81 if (EXPECT_NOT(inPath == nil)) return nil;
82 if (EXPECT_NOT(!sHaveSetUp)) [self setUp];
83
84 // Get reduced detail setting (every time, in case it changes; we don't want to call through to Universe on the loading thread in case the implementation becomes non-trivial).
85 sReducedDetail = [UNIVERSE reducedDetail];
86
87 // Get a suitable loader. FIXME -- this should sniff the data instead of relying on extensions.
88 extension = [[inPath pathExtension] lowercaseString];
89 if ([extension isEqualToString:@"png"])
90 {
91 result = [[[OOPNGTextureLoader alloc] initWithPath:inPath options:options] autorelease];
92 }
93 else
94 {
95 OOLog(@"texture.load.unknownType", @"Can't use %@ as a texture - extension \"%@\" does not identify a known type.", inPath, extension);
96 }
97
98 if (result != nil)
99 {
100 if (![[OOAsyncWorkManager sharedAsyncWorkManager] addTask:result priority:kOOAsyncPriorityMedium]) result = nil;
101 }
102
103 return result;
104}
105
106
107+ (id)loaderWithTextureSpecifier:(id)specifier extraOptions:(uint32_t)extraOptions folder:(NSString *)folder
108{
109 NSString *name = nil;
110 NSString *path = nil;
111 uint32_t options = 0;
112
113 if (!OOInterpretTextureSpecifier(specifier, &name, &options, NULL, NULL, NO)) return nil;
114 options |= extraOptions;
115 path = [ResourceManager pathForFileNamed:name inFolder:folder];
116 if (path == nil)
117 {
118 if (!(options & kOOTextureNoFNFMessage))
119 {
120 OOLogWARN(kOOLogFileNotFound, @"Could not find texture file \"%@\".", name);
121 OOStandardsError(@"Texture file not found");
122 }
123 return nil;
124 }
125
126 return [self loaderWithPath:path options:options];
127}
128
129
130- (id)initWithPath:(NSString *)inPath options:(uint32_t)options
131{
132 self = [super init];
133 if (self == nil) return nil;
134
135 _path = [inPath copy];
136 if (EXPECT_NOT(_path == nil))
137 {
138 [self release];
139 return nil;
140 }
141
142 _options = options;
143
144 _maxSize = MIN(sUserMaxSize, sGLMaxSize);
145
146 _generateMipMaps = (options & kOOTextureMinFilterMask) == kOOTextureMinFilterMipMap;
147 _avoidShrinking = (options & kOOTextureNoShrink) != 0;
148 _noScalingWhatsoever = (options & kOOTextureNeverScale) != 0;
149 if (_avoidShrinking || _noScalingWhatsoever)
150 {
151 _shrinkThreshold = kNeverShrinkThreshold;
152 }
153 else if (options & kOOTextureExtraShrink)
154 {
155 _shrinkThreshold = kExtraShrinkThreshold;
156 _maxSize = MIN(_maxSize, (uint32_t)kExtraShrinkMaxSize);
157 }
158 else {
159 _shrinkThreshold = kDefaultShrinkThreshold;
160 }
161#if OO_TEXTURE_CUBE_MAP
162 _allowCubeMap = (options & kOOTextureAllowCubeMap) != 0;
163#endif
164
165 if (options & kOOTextureExtractChannelMask)
166 {
167 _extractChannel = YES;
168 switch (options & kOOTextureExtractChannelMask)
169 {
171 _extractChannelIndex = 0;
172 break;
173
175 _extractChannelIndex = 1;
176 break;
177
179 _extractChannelIndex = 2;
180 break;
181
183 _extractChannelIndex = 3;
184 break;
185
186 default:
187 OOLogERR(@"texture.load.unknownExtractChannelMask", @"Unknown texture extract channel mask (0x%.4X). This is an internal error, please report it.", options & kOOTextureExtractChannelMask);
188 _extractChannel = NO;
189 }
190 }
191
192 return self;
193}
194
195
196- (void)dealloc
197{
198 [_path autorelease];
199 _path = nil;
200 free(_data);
201 _data = NULL;
202
203 [super dealloc];
204}
205
206
207- (NSString *)descriptionComponents
208{
209 NSString *state = nil;
210
211 if (_ready)
212 {
213 if (_data != NULL) state = @"ready";
214 else state = @"failed";
215 }
216 else
217 {
218 state = @"loading";
219#if INSTRUMENT_TEXTURE_LOADING
220 if (debugHasLoaded) state = @"loaded";
221#endif
222 }
223
224 return [NSString stringWithFormat:@"{%@ -- %@}", _path, state];
225}
226
227
228- (NSString *)shortDescriptionComponents
229{
230 return [_path lastPathComponent];
231}
232
233
234- (NSString *)path
235{
236 return _path;
237}
238
239
240- (BOOL)isReady
241{
242 return _ready;
243}
244
245
246- (BOOL) getResult:(OOPixMap *)result
247 format:(OOTextureDataFormat *)outFormat
248 originalWidth:(uint32_t *)outWidth
249 originalHeight:(uint32_t *)outHeight
250{
251 NSParameterAssert(result != NULL && outFormat != NULL);
252
253 BOOL OK = YES;
254
255 if (!_ready)
256 {
258 }
259 if (_data == NULL) OK = NO;
260
261 if (OK)
262 {
263 *result = OOMakePixMap(_data, _width, _height, OOTextureComponentsForFormat(_format), 0, 0);
264 _data = NULL;
265 *outFormat = _format;
266 OK = OOIsValidPixMap(*result);
267 if (outWidth != NULL) *outWidth = _originalWidth;
268 if (outHeight != NULL) *outHeight = _originalHeight;
269 }
270
271 if (!OK)
272 {
273 *result = kOONullPixMap;
274 *outFormat = kOOTextureDataInvalid;
275 }
276
277 return OK;
278}
279
280
281- (NSString *) cacheKey
282{
283 return [NSString stringWithFormat:@"%@:0x%.4X", [[self path] lastPathComponent], _options];
284}
285
286
287- (void)loadTexture
288{
290}
291
292
293+ (void)setUp
294{
295 // Load two maximum sizes - graphics hardware limit and user-specified limit.
296 GLint maxSize;
297 OOGL(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize));
298 sGLMaxSize = MAX(maxSize, 64);
299 OOLog(@"texture.load.rescale.maxSize", @"GL maximum texture size: %u", sGLMaxSize);
300
301 // Why 0x80000000? Because it's the biggest number OORoundUpToPowerOf2() can handle.
302 sUserMaxSize = [[NSUserDefaults standardUserDefaults] oo_unsignedIntForKey:@"max-texture-size" defaultValue:0x80000000];
303 if (sUserMaxSize < 0x80000000) OOLog(@"texture.load.rescale.maxSize", @"User maximum texture size: %u", sUserMaxSize);
304 sUserMaxSize = OORoundUpToPowerOf2_32(sUserMaxSize);
306
307
308 sHaveSetUp = YES;
309}
310
311
312/*** Methods performed on the loader thread. ***/
313
314- (void)performAsyncTask
315{
316 @try
317 {
318 OOLog(@"texture.load.asyncLoad", @"Loading texture %@", [_path lastPathComponent]);
319
320 [self loadTexture];
321
322 // Catch an error I've seen but not diagnosed yet.
323 if (_data != NULL && OOTextureComponentsForFormat(_format) == 0)
324 {
325 OOLog(@"texture.load.failed.internalError", @"Texture loader internal error for %@: data is non-null but data format is invalid (%u).", _path, _format);
326 free(_data);
327 _data = NULL;
328 }
329
330 if (_data != NULL) [self applySettings];
331
332 OOLog(@"texture.load.asyncLoad.done", @"%@", @"Loading complete.");
333 }
334 @catch (NSException *exception)
335 {
336 OOLog(@"texture.load.asyncLoad.exception", @"***** Exception loading texture %@: %@ (%@).", _path, [exception name], [exception reason]);
337
338 // Be sure to signal load failure.
339 free(_data);
340 _data = NULL;
341 }
342}
343
344
345- (void) generateMipMapsForCubeMap
346{
347 // Generate mip maps for each cube face.
348 NSParameterAssert(_data != NULL);
349
350 uint8_t components = OOTextureComponentsForFormat(_format);
351 size_t srcSideSize = _width * _width * components; // Space for one side without mip-maps.
352 size_t newSideSize = srcSideSize * 4 / 3; // Space for one side with mip-maps.
353 newSideSize = (newSideSize + 15) & ~15; // Round up to multiple of 16 bytes.
354 size_t newSize = newSideSize * 6; // Space for all six sides.
355
356 void *newData = malloc(newSize);
357 if (EXPECT_NOT(newData == NULL))
358 {
359 _generateMipMaps = NO;
360 _options = (_options & ~kOOTextureMinFilterMask) | kOOTextureMinFilterLinear;
361 return;
362 }
363
364 unsigned i;
365 for (i = 0; i < 6; i++)
366 {
367 void *srcBytes = ((uint8_t *)_data) + srcSideSize * i;
368 void *dstBytes = ((uint8_t *)newData) + newSideSize * i;
369
370 memcpy(dstBytes, srcBytes, srcSideSize);
371 OOGenerateMipMaps(dstBytes, _width, _width, _format);
372 }
373
374 free(_data);
375 _data = newData;
376}
377
378
379- (void)applySettings
380{
381 OOPixMapDimension desiredWidth, desiredHeight;
382 BOOL rescale;
383 size_t newSize;
384 uint8_t components;
385 OOPixMap pixMap;
386
387 components = OOTextureComponentsForFormat(_format);
388
389 // Apply defaults.
390 if (_originalWidth == 0) _originalWidth = _width;
391 if (_originalHeight == 0) _originalHeight = _height;
392 if (_rowBytes == 0) _rowBytes = _width * components;
393
394 pixMap = OOMakePixMap(_data, _width, _height, components, _rowBytes, 0);
395
396 if (_extractChannel)
397 {
398 if (OOExtractPixMapChannel(&pixMap, _extractChannelIndex, NO))
399 {
400 _format = kOOTextureDataGrayscale;
401 components = 1;
402 }
403 else
404 {
405 OOLogWARN(@"texture.load.extractChannel.invalid", @"Cannot extract channel from texture \"%@\"", [_path lastPathComponent]);
406 }
407 }
408
409 [self getDesiredWidth:&desiredWidth andHeight:&desiredHeight];
410
411 if (_isCubeMap && !OOCubeMapsAvailable())
412 {
413 OOPixMapToRGBA(&pixMap);
414 desiredHeight = MIN(desiredWidth * 2, 512U);
415 if (sReducedDetail && desiredHeight > kCubeShrinkThreshold) desiredHeight /= 2;
416 desiredWidth = desiredHeight * 2;
417
418 OOPixMap converted = OOConvertCubeMapToLatLong(pixMap, desiredHeight, _generateMipMaps);
419 OOFreePixMap(&pixMap);
420 pixMap = converted;
421 _isCubeMap = NO;
422
423#if DUMP_CONVERTED_CUBE_MAPS
424 OODumpPixMap(pixMap, [NSString stringWithFormat:@"converted cube map %@", [[_path lastPathComponent] stringByDeletingPathExtension]]);
425#endif
426 }
427
428 // Rescale if needed.
429 rescale = (_width != desiredWidth || _height != desiredHeight);
430 if (rescale)
431 {
432 BOOL leaveSpaceForMipMaps = _generateMipMaps;
433#if OO_TEXTURE_CUBE_MAP
434 if (_isCubeMap) leaveSpaceForMipMaps = NO;
435#endif
436
437 OOLog(@"texture.load.rescale", @"Rescaling texture \"%@\" from %u x %u to %u x %u.", [_path lastPathComponent], pixMap.width, pixMap.height, desiredWidth, desiredHeight);
438
439 pixMap = OOScalePixMap(pixMap, desiredWidth, desiredHeight, leaveSpaceForMipMaps);
440 if (EXPECT_NOT(!OOIsValidPixMap(pixMap))) return;
441
442 _data = pixMap.pixels;
443 _width = pixMap.width;
444 _height = pixMap.height;
445 _rowBytes = pixMap.rowBytes;
446 }
447
448#if OO_TEXTURE_CUBE_MAP
449 if (_isCubeMap)
450 {
451 if (_generateMipMaps)
452 {
453 [self generateMipMapsForCubeMap];
454 }
455 return;
456 }
457#endif
458
459 // Generate mip maps if needed.
460 if (_generateMipMaps)
461 {
462 // Make space if needed.
463 newSize = desiredWidth * components * desiredHeight;
464 newSize = (newSize * 4) / 3;
465 // +1 to fix overrun valgrind spotted - CIM
466 _generateMipMaps = OOExpandPixMap(&pixMap, newSize+1);
467
468 _data = pixMap.pixels;
469 _width = pixMap.width;
470 _height = pixMap.height;
471 _rowBytes = pixMap.rowBytes;
472 }
473 if (_generateMipMaps)
474 {
475 OOGenerateMipMaps(_data, _width, _height, _format);
476 }
477
478 // All done.
479}
480
481
482- (void)getDesiredWidth:(OOPixMapDimension *)outDesiredWidth andHeight:(OOPixMapDimension *)outDesiredHeight
483{
484 OOPixMapDimension desiredWidth, desiredHeight;
485
486 // Work out appropriate final size for textures.
487 if (!_noScalingWhatsoever)
488 {
489 // Cube maps are six times as high as they are wide, and we need to preserve that.
490 if (_allowCubeMap && _height == _width * 6)
491 {
492 _isCubeMap = YES;
493
494 desiredWidth = OORoundUpToPowerOf2_PixMap((2 * _width) / 3);
495 desiredWidth = MIN(desiredWidth, sGLMaxSize / 8);
496 if (sReducedDetail)
497 {
498 if (256 < desiredWidth) desiredWidth /= 2;
499 }
500 desiredWidth = MIN(desiredWidth, sUserMaxSize / 4);
501
502 desiredHeight = desiredWidth * 6;
503 }
504 else
505 {
507 {
508 // Round to nearest power of two. NOTE: this is duplicated in OOTextureVerifierStage.m.
509 desiredWidth = OORoundUpToPowerOf2_PixMap((2 * _width) / 3);
510 desiredHeight = OORoundUpToPowerOf2_PixMap((2 * _height) / 3);
511 }
512 else
513 {
514 desiredWidth = _width;
515 desiredHeight = _height;
516 }
517
518 desiredWidth = MIN(desiredWidth, sGLMaxSize);
519 desiredHeight = MIN(desiredHeight, sGLMaxSize);
520
521 if (!_avoidShrinking)
522 {
523 if (sReducedDetail)
524 {
525 if (_shrinkThreshold < desiredWidth) desiredWidth /= 2;
526 if (_shrinkThreshold < desiredHeight) desiredHeight /= 2;
527 }
528
529 desiredWidth = MIN(desiredWidth, _maxSize);
530 desiredHeight = MIN(desiredHeight, _maxSize);
531 }
532 }
533 }
534 else
535 {
536 desiredWidth = _width;
537 desiredHeight = _height;
538 }
539
540 if (outDesiredWidth != NULL) *outDesiredWidth = desiredWidth;
541 if (outDesiredHeight != NULL) *outDesiredHeight = desiredHeight;
542}
543
544
545- (void) completeAsyncTask
546{
547 _ready = YES;
548}
549
550@end
@ kOOAsyncPriorityMedium
OOPixMap OOConvertCubeMapToLatLong(OOPixMap sourcePixMap, OOPixMapDimension height, BOOL leaveSpaceForMipMaps)
void OOStandardsError(NSString *message)
#define EXPECT_NOT(x)
#define OOLogWARN(class, format,...)
Definition OOLogging.h:113
#define OOLogERR(class, format,...)
Definition OOLogging.h:112
#define OOLogGenericSubclassResponsibility()
Definition OOLogging.h:129
#define OOLog(class, format,...)
Definition OOLogging.h:88
NSString *const kOOLogFileNotFound
Definition OOLogging.m:652
#define MAX(A, B)
Definition OOMaths.h:114
#define MIN(A, B)
Definition OOMaths.h:111
#define OOGL(statement)
Definition OOOpenGL.h:251
BOOL OOExtractPixMapChannel(OOPixMap *ioPixMap, uint8_t channelIndex, BOOL compactWhenDone)
BOOL OOPixMapToRGBA(OOPixMap *ioPixMap)
uint_fast32_t OOPixMapDimension
Definition OOPixMap.h:33
void OOFreePixMap(OOPixMap *ioPixMap)
Definition OOPixMap.m:86
void OODumpPixMap(OOPixMap pixMap, NSString *name)
Definition OOPixMap.m:145
OOPixMapFormat
Definition OOPixMap.h:39
const OOPixMap kOONullPixMap
Definition OOPixMap.m:31
#define OORoundUpToPowerOf2_PixMap
Definition OOPixMap.h:35
OOPixMap OOMakePixMap(void *pixels, OOPixMapDimension width, OOPixMapDimension height, OOPixMapFormat format, size_t rowBytes, size_t bufferSize)
Definition OOPixMap.m:53
BOOL OOIsValidPixMap(OOPixMap pixMap)
Definition OOPixMap.m:42
BOOL OOExpandPixMap(OOPixMap *ioPixMap, size_t desiredSize)
Definition OOPixMap.m:130
return nil
static unsigned sGLMaxSize
static BOOL sHaveSetUp
static BOOL sReducedDetail
@ kCubeShrinkThreshold
@ kNeverShrinkThreshold
@ kExtraShrinkMaxSize
@ kExtraShrinkThreshold
@ kDefaultShrinkThreshold
static uint32_t sUserMaxSize
static BOOL sHaveNPOTTextures
OOPixMap OOScalePixMap(OOPixMap srcPixMap, OOPixMapDimension dstWidth, OOPixMapDimension dstHeight, BOOL leaveSpaceForMipMaps)
BOOL OOGenerateMipMaps(void *textureBytes, OOPixMapDimension width, OOPixMapDimension height, OOPixMapFormat format)
BOOL OOCubeMapsAvailable(void)
Definition OOTexture.m:688
uint8_t OOTextureComponentsForFormat(OOTextureDataFormat format)
Definition OOTexture.m:667
@ kOOTextureDataGrayscale
Definition OOTexture.h:110
@ kOOTextureDataInvalid
Definition OOTexture.h:107
@ kOOTextureExtractChannelA
Definition OOTexture.h:70
@ kOOTextureExtractChannelB
Definition OOTexture.h:69
@ kOOTextureNoFNFMessage
Definition OOTexture.h:58
@ kOOTextureNoShrink
Definition OOTexture.h:53
@ kOOTextureExtractChannelMask
Definition OOTexture.h:65
@ kOOTextureMinFilterLinear
Definition OOTexture.h:47
@ kOOTextureAllowCubeMap
Definition OOTexture.h:61
@ kOOTextureMinFilterMask
Definition OOTexture.h:72
@ kOOTextureMinFilterMipMap
Definition OOTexture.h:48
@ kOOTextureNeverScale
Definition OOTexture.h:59
@ kOOTextureExtraShrink
Definition OOTexture.h:54
@ kOOTextureExtractChannelG
Definition OOTexture.h:68
@ kOOTextureExtractChannelR
Definition OOTexture.h:67
BOOL OOInterpretTextureSpecifier(id specifier, NSString **outName, OOTextureFlags *outOptions, float *outAnisotropy, float *outLODBias, BOOL ignoreExtract)
Definition OOTexture.m:694
void waitForTaskToComplete:(id< OOAsyncWorkTask > task)
OOAsyncWorkManager * sharedAsyncWorkManager()
NSString * pathForFileNamed:inFolder:(NSString *fileName,[inFolder] NSString *folderName)
OOPixMapDimension height
Definition OOPixMap.h:50
size_t rowBytes
Definition OOPixMap.h:52
void * pixels
Definition OOPixMap.h:49
OOPixMapDimension width
Definition OOPixMap.h:50