LCOV - code coverage report
Current view: top level - Core/Materials - OOTextureLoader.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 20 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          Line data    Source code
       1           0 : /*
       2             : 
       3             : OOTextureLoader.m
       4             : 
       5             : 
       6             : Copyright (C) 2007-2014 Jens Ayton
       7             : 
       8             : Permission is hereby granted, free of charge, to any person obtaining a copy
       9             : of this software and associated documentation files (the "Software"), to deal
      10             : in the Software without restriction, including without limitation the rights
      11             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      12             : copies of the Software, and to permit persons to whom the Software is
      13             : furnished to do so, subject to the following conditions:
      14             : 
      15             : The above copyright notice and this permission notice shall be included in all
      16             : copies or substantial portions of the Software.
      17             : 
      18             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      19             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      20             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      21             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      22             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      23             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      24             : SOFTWARE.
      25             : 
      26             : */
      27             : 
      28             : #import "OOPNGTextureLoader.h"
      29             : #import "OOTextureLoader.h"
      30             : #import "OOFunctionAttributes.h"
      31             : #import "OOCollectionExtractors.h"
      32             : #import "OOMaths.h"
      33             : #import "Universe.h"
      34             : #import "OOTextureScaling.h"
      35             : #import "OOPixMapChannelOperations.h"
      36             : #import "OOConvertCubeMapToLatLong.h"
      37             : #include <stdlib.h>
      38             : #import "ResourceManager.h"
      39             : #import "OOOpenGLExtensionManager.h"
      40             : #import "OODebugStandards.h"
      41             : 
      42             : 
      43           0 : #define DUMP_CONVERTED_CUBE_MAPS        0
      44             : 
      45           0 : enum
      46             : {
      47             :         // Thresholds for reduced-detail texture shrinking in different circumstances.
      48             :         kNeverShrinkThreshold           = UINT32_MAX,
      49             :         kDefaultShrinkThreshold         = 512,
      50             :         kExtraShrinkThreshold           = 128,
      51             :         kExtraShrinkMaxSize                     = 256,
      52             :         kCubeShrinkThreshold            = 256
      53             : };
      54             : 
      55             : 
      56           0 : static unsigned                         sGLMaxSize;
      57           0 : static uint32_t                         sUserMaxSize;
      58           0 : static BOOL                                     sReducedDetail;
      59           0 : static BOOL                                     sHaveNPOTTextures = NO; // TODO: support "true" non-power-of-two textures.
      60           0 : static BOOL                                     sHaveSetUp = NO;
      61             : 
      62             : 
      63             : @interface OOTextureLoader (OOPrivate)
      64             : 
      65           0 : + (void)setUp;
      66             : 
      67           0 : - (void)applySettings;
      68           0 : - (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             :                 {
     170             :                         case kOOTextureExtractChannelR:
     171             :                                 _extractChannelIndex = 0;
     172             :                                 break;
     173             :                                 
     174             :                         case kOOTextureExtractChannelG:
     175             :                                 _extractChannelIndex = 1;
     176             :                                 break;
     177             :                                 
     178             :                         case kOOTextureExtractChannelB:
     179             :                                 _extractChannelIndex = 2;
     180             :                                 break;
     181             :                                 
     182             :                         case kOOTextureExtractChannelA:
     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           0 : - (void)dealloc
     197             : {
     198             :         [_path autorelease];
     199             :         _path = nil;
     200             :         free(_data);
     201             :         _data = NULL;
     202             :         
     203             :         [super dealloc];
     204             : }
     205             : 
     206             : 
     207           0 : - (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           0 : - (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             :         {
     257             :                 [[OOAsyncWorkManager sharedAsyncWorkManager] waitForTaskToComplete:self];
     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             : {
     289             :         OOLogGenericSubclassResponsibility();
     290             : }
     291             : 
     292             : 
     293           0 : + (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);
     305             :         sUserMaxSize = MAX(sUserMaxSize, 64U);
     306             :         
     307             :         
     308             :         sHaveSetUp = YES;
     309             : }
     310             : 
     311             : 
     312             : /*** Methods performed on the loader thread. ***/
     313             : 
     314           0 : - (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           0 : - (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           0 : - (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           0 : - (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             :                 {
     506             :                         if (!sHaveNPOTTextures)
     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           0 : - (void) completeAsyncTask
     546             : {
     547             :         _ready = YES;
     548             : }
     549             : 
     550             : @end

Generated by: LCOV version 1.14