在cocos中,一个Texture2D管理一个纹理(显存中),Texture2D负责纹理的创建、删除。纹理在被glTexImage2D上传至GPU内存后将一直驻留,我们需要小心进行管理,一方面,纹理在使用之后可能不再被使用,我们需要及时删除,另一方面,有些纹理只是暂时不会被使用,这时候要避免重复上传纹理,应该要把纹理缓存起来,cocos通过TextureCache来管理所有的Texture2D对象。

一、数据结构

  1. class CC_DLL TextureCache : public Ref
  2. {
  3. protected:
  4. // ********************************************************
  5. // ***** 以下是多线程异步加载相关数据结构,下面再讲
  6. // ********************************************************
  7. struct AsyncStruct;
  8. std::thread* _loadingThread;
  9. std::deque<AsyncStruct*> _asyncStructQueue;
  10. std::deque<AsyncStruct*> _requestQueue;
  11. std::deque<AsyncStruct*> _responseQueue;
  12. std::mutex _requestMutex;
  13. std::mutex _responseMutex;
  14. std::condition_variable _sleepCondition;
  15. bool _needQuit;
  16. int _asyncRefCount;
  17. // 通过一个unordered_map存储纹理(纹理缓存所在)
  18. // key: string类型,表示纹理对应image file的full path
  19. // mapped_type: Texture2D对象
  20. std::unordered_map<std::string, Texture2D*> _textures;
  21. // etc1压缩纹理对应的alpha纹理文件的前缀名,都是统一的。
  22. static std::string s_etc1AlphaFileSuffix;
  23. };

二、加载纹理(主线程)

1、使用示例

  1. auto textureCache = Director::getInstance()->getTextureCache();
  2. auto imageFilePath1 = "......"; // 纹理1,图像文件途径
  3. auto imageFilePath2 = "......"; // 纹理2
  4. // 加载都在主线程完成,如果图片过大或者数量过多,可能导致卡顿
  5. Texture2D* texture1 = textureCache->addImage(imageFilePath1);
  6. Texture2D* texture2 = textureCache->addImage(imageFilePath2);

2、实现原理

addImage

  1. // 从路径为path的image file加载纹理,
  2. // 支持的文件格式:.png, .bmp, .tiff, .jpeg, .pvr.
  3. Texture2D * TextureCache::addImage(const std::string &path) {
  4. Texture2D* texture = nullptr;
  5. Image* image = nullptr; // Image的作用就是将image file 加载到内存,比如png、pvr图片文件
  6. // 获取完整路径
  7. std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);
  8. if (fullpath.size() == 0) {
  9. return nullptr;
  10. }
  11. // 先从缓存中查找
  12. auto it = _textures.find(fullpath);
  13. if (it != _textures.end())
  14. texture = it->second;
  15. if (!texture) { // 缓存中没有,才会加载,否则直接返回缓存命中
  16. do {
  17. image = new (std::nothrow) Image();
  18. CC_BREAK_IF(nullptr == image);
  19. // (JPG/PNG格式文件需要通过库解压缩获得RGB数据,压缩纹理则直接读文件
  20. bool bRet = image->initWithImageFile(fullpath);
  21. CC_BREAK_IF(!bRet);
  22. texture = new (std::nothrow) Texture2D();
  23. // 初始化纹理(glTexImage2D),见纹理知识点
  24. if (texture && texture->initWithImage(image)) {
  25. #if CC_ENABLE_CACHE_TEXTURE_DATA
  26. // cache the texture file name
  27. VolatileTextureMgr::addImageTexture(texture, fullpath);
  28. #endif
  29. // 插入缓存中
  30. _textures.emplace(fullpath, texture);
  31. // -- ANDROID ETC1 ALPHA SUPPORTS.
  32. // 为ETC1压缩纹理添加alpha支持,见https://www.yuque.com/tvvhealth/cs/hfkcft#yZUOX
  33. // 做法就是为每个纹理都额外做一张相同大小alpha纹理,然后在片段着色器中将alpha值与rgb值相乘
  34. std::string alphaFullPath = path + s_etc1AlphaFileSuffix; // alpha灰度图是etc文件+后缀"@alpha"
  35. if (image->getFileType() == Image::Format::ETC // 针对etc压缩纹理
  36. && !s_etc1AlphaFileSuffix.empty()
  37. && FileUtils::getInstance()->isFileExist(alphaFullPath)) { // alpha灰度图存在。
  38. Image alphaImage;
  39. if (alphaImage.initWithImageFile(alphaFullPath)) { // alpha纹理
  40. Texture2D *pAlphaTexture = new(std::nothrow) Texture2D;
  41. if(pAlphaTexture != nullptr && pAlphaTexture->initWithImage(&alphaImage)) {
  42. texture->setAlphaTexture(pAlphaTexture);
  43. }
  44. CC_SAFE_RELEASE(pAlphaTexture);
  45. }
  46. }
  47. //parse 9-patch info
  48. this->parseNinePatchImage(image, texture, path);
  49. }
  50. else { // 纹理初始化失败,无法加载纹理
  51. CCLOG("cocos2d: Couldn't create texture for file:%s in TextureCache", path.c_str());
  52. CC_SAFE_RELEASE(texture);
  53. texture = nullptr;
  54. }
  55. } while (0);
  56. }
  57. CC_SAFE_RELEASE(image);
  58. return texture;
  59. }
  60. // image 已经创建好,key为image file的full path
  61. Texture2D* TextureCache::addImage(Image *image, const std::string &key)
  62. {
  63. CCASSERT(image != nullptr, "TextureCache: image MUST not be nil");
  64. CCASSERT(image->getData() != nullptr, "TextureCache: image MUST not be nil");
  65. Texture2D * texture = nullptr;
  66. do
  67. {
  68. // 缓冲命中,则直接跳过
  69. auto it = _textures.find(key);
  70. if (it != _textures.end()) {
  71. texture = it->second;
  72. break;
  73. }
  74. // 否则创建Texture2D,
  75. texture = new (std::nothrow) Texture2D();
  76. if (texture) {
  77. if (texture->initWithImage(image)) {
  78. _textures.emplace(key, texture);
  79. }
  80. else {
  81. CC_SAFE_RELEASE(texture);
  82. texture = nullptr;
  83. CCLOG("cocos2d: initWithImage failed!");
  84. }
  85. }
  86. else {
  87. CCLOG("cocos2d: Allocating memory for Texture2D failed!");
  88. }
  89. } while (0);
  90. #if CC_ENABLE_CACHE_TEXTURE_DATA
  91. VolatileTextureMgr::addImage(texture, image);
  92. #endif
  93. return texture;
  94. }

三、异步加载纹理

1、使用示例

  1. auto textureCache = Director::getInstance()->getTextureCache();
  2. // 纹理1
  3. auto imageFilePath1 = "......"; // 图像文件途径
  4. auto finishedCallback1 = [](Texture2D* texture){}; // 纹理1加载完成回调
  5. auto finishedCallbackKey1 = "finishedCallbackKey1"; // 用于清除加载完成回调,使得加载完成之后,不会触发回调。
  6. // 纹理2
  7. auto imageFilePath2 = "......";
  8. auto finishedCallback2 = [](Texture2D* texture){};
  9. auto finishedCallbackKey2 = "finishedCallbackKey2";
  10. textureCache->addImageAsync(imageFilePath1, finishedCallback1, finishedCallbackKey1); // 异步加载纹理1
  11. textureCache->addImageAsync(imageFilePath2, finishedCallback2, finishedCallbackKey2); // 异步加载纹理2
  12. textureCache->unbindImageAsync(finishedCallbackKey2); // 加载纹理2时,不要触发回调
  13. textureCache->unbindAllImageAsync(); // 加载所有纹理都不触发完成回调。

2、实现原理

纹理加载是一个耗时的操作,异步加载可以避免卡顿。一个纹理加载可以分两个过程:

  • 任务1
    • load image data from disk
    • 这是最耗时的操作,所以这一步在子线程中完成。
    • 一个接一个顺序完成(互斥锁)
  • 任务2
    • convert image data to texture
    • 通过glTexImage2D命令上传纹理,必须线程安全所以在gl线程(主线程)中完成。
    • 逐帧执行,每次执行都执行完等待队列中的所有任务,即一帧内将当前所有image convert to texture,cocos认为convert image to texture is faster than load image from disk。

点击查看【processon】
异步加载纹理的相关数据结构:

  1. class CC_DLL TextureCache : public Ref
  2. {
  3. protected:
  4. struct AsyncStruct; // 加载一个纹理的任务结构
  5. std::thread* _loadingThread; // load image data from disk的子线程
  6. std::deque<AsyncStruct*> _asyncStructQueue; //
  7. std::deque<AsyncStruct*> _requestQueue; // 等待需要被加载的
  8. std::deque<AsyncStruct*> _responseQueue; // 等待需要被响应的
  9. std::mutex _requestMutex;
  10. std::mutex _responseMutex;
  11. std::condition_variable _sleepCondition;
  12. bool _needQuit;
  13. int _asyncRefCount;
  14. };
  15. struct TextureCache::AsyncStruct
  16. {
  17. public:
  18. AsyncStruct(const std::string& fn,
  19. const std::function<void(Texture2D*)>& f,
  20. const std::string& key )
  21. : filename(fn)
  22. , callback(f)
  23. , callbackKey( key )
  24. , pixelFormat(Texture2D::getDefaultAlphaPixelFormat())
  25. , loadSuccess(false)
  26. {}
  27. std::string filename;
  28. std::function<void(Texture2D*)> callback;
  29. std::string callbackKey;
  30. Image image;
  31. Image imageAlpha;
  32. Texture2D::PixelFormat pixelFormat;
  33. bool loadSuccess;
  34. };

addImageAsync

相关数据结构:

  1. // 子线程中要执行的一个个纹理加载任务(load image from disk)
  2. struct TextureCache::AsyncStruct
  3. {
  4. public:
  5. AsyncStruct(const std::string& fn,
  6. const std::function<void( Texture2D * )>& f,
  7. const std::string& key )
  8. : filename( fn )
  9. , callback( f )
  10. , callbackKey( key )
  11. , pixelFormat( Texture2D::getDefaultAlphaPixelFormat() )
  12. , loadSuccess( false )
  13. {}
  14. std::string filename; // image file full path
  15. std::function<void( Texture2D * )> callback; // 加载结束的回调
  16. std::string callbackKey; // 用于清除callback,使回调不会发生
  17. Image image; // image data
  18. Image imageAlpha; // etc1的alpha纹理
  19. Texture2D::PixelFormat pixelFormat; // 将纹理加载为指定的格式
  20. bool loadSuccess; // 是否加载成功
  21. };
  22. class CC_DLL TextureCache : public Ref
  23. {
  24. protected:
  25. struct AsyncStruct;
  26. std::thread* _loadingThread; // 加载子线程
  27. // 双向队列
  28. std::deque<AsyncStruct*> _asyncStructQueue; //
  29. std::deque<AsyncStruct*> _requestQueue; // 子线程加载任务的等待队列
  30. std::deque<AsyncStruct*> _responseQueue; //
  31. std::mutex _requestMutex; // 互斥锁
  32. std::mutex _responseMutex;
  33. std::condition_variable _sleepCondition;
  34. bool _needQuit;
  35. int _asyncRefCount;
  36. };
  1. // 支持格式:.png, .jpg
  2. void TextureCache::addImageAsync(const std::string &path,
  3. const std::function<void(Texture2D*)>& callback)
  4. {
  5. addImageAsync( path, callback, path ); // callback key为image file path
  6. }
  7. //path : 纹理文件路径,可以是相对路径
  8. //callback : 加载完成后的回调,如果加载失败,指针参数nullptr
  9. //callbackkey: 标识callback,用于清除加载完成时的回调
  10. void TextureCache::addImageAsync(const std::string& path,
  11. const std::function<void(Texture2D*)>& callback,
  12. const std::string& callbackKey) {
  13. Texture2D *texture = nullptr;
  14. std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);
  15. //检查纹理是否已经加载,若已加载则直接回调。
  16. auto it = _textures.find(fullpath);
  17. if (it != _textures.end())
  18. texture = it->second;
  19. if (texture != nullptr) {
  20. if (callback) callback(texture);
  21. return;
  22. }
  23. //文件不存在,直接回调失败
  24. if (fullpath.empty() || !FileUtils::getInstance()->isFileExist(fullpath)) {
  25. if (callback) callback(nullptr);
  26. return;
  27. }
  28. // 启动线程,用于load image data from disk
  29. if (_loadingThread == nullptr) {
  30. // create a new thread to load images
  31. _needQuit = false;
  32. _loadingThread = new (std::nothrow) std::thread(&TextureCache::loadImage, this);
  33. }
  34. if (0 == _asyncRefCount) {
  35. // 逐帧加载(load image data from disk)
  36. Director::getInstance()->getScheduler()->schedule(CC_SCHEDULE_SELECTOR( TextureCache::addImageAsyncCallBack), this, 0, false);
  37. }
  38. ++_asyncRefCount;
  39. AsyncStruct *data = new (std::nothrow) AsyncStruct(fullpath, callback, callbackKey);
  40. _asyncStructQueue.push_back(data);
  41. std::unique_lock<std::mutex> ul(_requestMutex);
  42. _requestQueue.push_back(data); // 任务入队列
  43. _sleepCondition.notify_one();
  44. }

loadImage(任务1)

子线程执行,执行load image data from disk任务。

  1. void TextureCache::loadImage() {
  2. AsyncStruct *asyncStruct = nullptr;
  3. while( !_needQuit ) {
  4. std::unique_lock<std::mutex> ul( _requestMutex );
  5. // pop an AsyncStruct from request queue
  6. if( _requestQueue.empty() ) {
  7. asyncStruct = nullptr;
  8. }
  9. else {
  10. asyncStruct = _requestQueue.front();
  11. _requestQueue.pop_front();
  12. }
  13. if( nullptr == asyncStruct ) {
  14. if( _needQuit ) {
  15. break;
  16. }
  17. _sleepCondition.wait( ul );
  18. continue;
  19. }
  20. ul.unlock();
  21. // load image
  22. asyncStruct->loadSuccess = asyncStruct->image.initWithImageFileThreadSafe( asyncStruct->filename );
  23. // ETC1 ALPHA supports.
  24. // check whether alpha texture exists & load it
  25. if( asyncStruct->loadSuccess &&
  26. asyncStruct->image.getFileType() == Image::Format::ETC &&
  27. !s_etc1AlphaFileSuffix.empty() ) {
  28. auto alphaFile = asyncStruct->filename + s_etc1AlphaFileSuffix;
  29. if( FileUtils::getInstance()->isFileExist( alphaFile ) )
  30. { asyncStruct->imageAlpha.initWithImageFileThreadSafe( alphaFile ); }
  31. }
  32. // 任务1完成,转入_responseQueue队列,等待执行任务2
  33. // push the asyncStruct to response queue
  34. _responseMutex.lock();
  35. _responseQueue.push_back( asyncStruct );
  36. _responseMutex.unlock();
  37. }
  38. }

addImageAsyncCallBack(任务2)

逐帧执行,每次都清空_responseQueue。

  1. void TextureCache::addImageAsyncCallBack( float /*dt*/ )
  2. {
  3. Texture2D *texture = nullptr;
  4. AsyncStruct *asyncStruct = nullptr;
  5. while( true )
  6. {
  7. // pop an AsyncStruct from response queue
  8. _responseMutex.lock();
  9. if( _responseQueue.empty() ) {
  10. asyncStruct = nullptr;
  11. }
  12. else {
  13. asyncStruct = _responseQueue.front();
  14. _responseQueue.pop_front();
  15. // the asyncStruct's sequence order in _asyncStructQueue must equal to the order in _responseQueue
  16. CC_ASSERT( asyncStruct == _asyncStructQueue.front() );
  17. _asyncStructQueue.pop_front();
  18. }
  19. _responseMutex.unlock();
  20. if( nullptr == asyncStruct )
  21. {
  22. break;
  23. }
  24. // check the image has been convert to texture or not
  25. auto it = _textures.find( asyncStruct->filename );
  26. if( it != _textures.end() )
  27. {
  28. texture = it->second;
  29. }
  30. else
  31. {
  32. // convert image to texture
  33. if( asyncStruct->loadSuccess )
  34. {
  35. Image *image = &( asyncStruct->image );
  36. // generate texture in render thread
  37. texture = new( std::nothrow ) Texture2D();
  38. texture->initWithImage( image, asyncStruct->pixelFormat );
  39. //parse 9-patch info
  40. this->parseNinePatchImage( image, texture, asyncStruct->filename );
  41. #if CC_ENABLE_CACHE_TEXTURE_DATA
  42. // cache the texture file name
  43. VolatileTextureMgr::addImageTexture( texture, asyncStruct->filename );
  44. #endif
  45. // cache the texture. retain it, since it is added in the map
  46. _textures.emplace( asyncStruct->filename, texture );
  47. texture->retain();
  48. texture->autorelease();
  49. // ETC1 ALPHA supports.
  50. if( asyncStruct->imageAlpha.getFileType() == Image::Format::ETC )
  51. {
  52. auto alphaTexture = new( std::nothrow ) Texture2D();
  53. if( alphaTexture != nullptr && alphaTexture->initWithImage( &asyncStruct->imageAlpha, asyncStruct->pixelFormat ) )
  54. {
  55. texture->setAlphaTexture( alphaTexture );
  56. }
  57. CC_SAFE_RELEASE( alphaTexture );
  58. }
  59. }
  60. else
  61. {
  62. texture = nullptr;
  63. CCLOG( "cocos2d: failed to call TextureCache::addImageAsync(%s)", asyncStruct->filename.c_str() );
  64. }
  65. }
  66. // call callback function
  67. if( asyncStruct->callback )
  68. {
  69. ( asyncStruct->callback )( texture );
  70. }
  71. // release the asyncStruct
  72. delete asyncStruct;
  73. --_asyncRefCount;
  74. }
  75. if( 0 == _asyncRefCount )
  76. {
  77. Director::getInstance()->getScheduler()->unschedule( CC_SCHEDULE_SELECTOR( TextureCache::addImageAsyncCallBack ), this );
  78. }
  79. }

四、删除纹理

  1. class CC_DLL TextureCache : public Ref {
  2. Texture2D* getTextureForKey(const std::string& key) const; // 根据key返回缓存中的纹理,key可以是相对或者绝对路径
  3. void removeTextureForKey(const std::string &key); // 引用计数-1
  4. // 移除缓存中的所有缓存纹理,引用计数全部-1
  5. // Call this method if you receive the "Memory Warning".
  6. void removeAllTextures();
  7. // 释放引用计数=1的缓存纹理内存
  8. // 纹理Texture2D被创建时,引用计数=1,因此=1表示此纹理处于空闲未被使用状态。注意空闲不代表后面不会被使用。
  9. // 注意!!!这个函数真正释放了纹理内存,其他的remove是引用计数-1,并不一定会马上释放内存,比如该纹理正在被场景使用
  10. // 那么当场景移除该纹理关联的Sprite时,纹理才会被真正释放。
  11. void removeUnusedTextures();
  12. void removeTexture(Texture2D* texture); // 移除缓存中的指定纹理,引用计数-1
  13. }

五、缓存统计

getDescription

输出当前缓存纹理数量情况。

  1. std::string TextureCache::getDescription() const
  2. {
  3. return StringUtils::format( "<TextureCache | Number of textures = %d>", static_cast<int>( _textures.size() ) );
  4. }

getCachedTextureInfo

统计每个缓存纹理的以下信息:

  • 引用计数
  • texture id
  • 像素宽高
  • bpp(像素大小,bits per pixel)
  • 字节数 ```cpp

std::string TextureCache::getCachedTextureInfo() const { std::string buffer; char buftmp[4096];

  1. unsigned int count = 0;
  2. unsigned int totalBytes = 0;
  3. for( auto &texture : _textures )
  4. {
  5. memset( buftmp, 0, sizeof( buftmp ) );
  6. Texture2D *tex = texture.second;
  7. unsigned int bpp = tex->getBitsPerPixelForFormat();
  8. // Each texture takes up width * height * bytesPerPixel bytes.
  9. auto bytes = tex->getPixelsWide() * tex->getPixelsHigh() * bpp / 8;
  10. totalBytes += bytes;
  11. count++;
  12. snprintf( buftmp, sizeof( buftmp ) - 1, "\"%s\" rc=%lu id=%lu %lu x %lu @ %ld bpp => %lu KB\n",
  13. texture.first.c_str(),
  14. ( long ) tex->getReferenceCount(),
  15. ( long ) tex->getName(),
  16. ( long ) tex->getPixelsWide(),
  17. ( long ) tex->getPixelsHigh(),
  18. ( long ) bpp,
  19. ( long ) bytes / 1024 );
  20. buffer += buftmp;
  21. }
  22. snprintf( buftmp, sizeof( buftmp ) - 1, "TextureCache dumpDebugInfo: %ld textures, for %lu KB (%.2f MB)\n", ( long ) count, ( long ) totalBytes / 1024, totalBytes / ( 1024.0f * 1024.0f ) );
  23. buffer += buftmp;
  24. return buffer;

}

  1. <a name="4nzON"></a>
  2. # 六、纹理恢复(Android)
  3. 在android中,应用程序由后台切换到前台,OpenGL ES上下文可能会被重新创建,这时应用程序中驻留在GPU内存的纹理都将被清空,cocos通过在纹理被创建并成功上传至GPU内存时,保留创建纹理需要的数据(我们叫它纹理信息),然后在适合的时候(调用reloadAllTextures),创建所有纹理并上传至GPU内存,这些工作由VolatileTextureMgr完成。
  4. ```cpp
  5. // ******************* CCTextureCache.h
  6. #if CC_ENABLE_CACHE_TEXTURE_DATA
  7. class VolatileTexture { // 缓存纹理的创建数据(创建该纹理需要的数据)
  8. typedef enum {
  9. kInvalid = 0,
  10. kImageFile, // 纹理是通过image file创建,缓存以下数据:
  11. // _fileName:纹理文件路径
  12. // _pixelFormat:需要转换成的纹理格式
  13. kImageData, // 纹理是通过data创建,缓存以下数据:
  14. // _textureData: 纹理数据
  15. // _dataLen: 纹理数据长度
  16. // _pixelFormat: 需要转换成的纹理格式
  17. // _textureSize: 纹理像素宽高
  18. kString, // 字体纹理,缓存以下数据:
  19. // _text字符
  20. // _fontDefinition
  21. kImage, // 纹理是通过CCImage创建,缓存以下数据:
  22. // _uiImage:CCImage数据
  23. } ccCachedImageType;
  24. private:
  25. VolatileTexture(Texture2D *t);
  26. ~VolatileTexture();
  27. protected:
  28. ccCachedImageType _cashedImageType; // 纹理类型。
  29. // 以下参数,根据对应的ccCachedImageType来使用
  30. friend class VolatileTextureMgr;
  31. Texture2D *_texture;
  32. Image *_uiImage;
  33. void *_textureData;
  34. int _dataLen;
  35. Size _textureSize;
  36. Texture2D::PixelFormat _pixelFormat;
  37. std::string _fileName;
  38. bool _hasMipmaps;
  39. Texture2D::TexParams _texParams; // 过滤(Filtering)和环绕(Wraping)模式
  40. std::string _text;
  41. FontDefinition _fontDefinition;
  42. };
  43. class CC_DLL VolatileTextureMgr {
  44. public:
  45. // 缓存通过file创建的Texture2D
  46. static void addImageTexture(Texture2D *tt, const std::string& imageFileName);
  47. // 缓存字体纹理
  48. static void addStringTexture(Texture2D *tt, const char* text, const FontDefinition& fontDefinition);
  49. // 缓存通过data创建的纹理
  50. static void addDataTexture(Texture2D *tt, void* data, int dataLen, Texture2D::PixelFormat pixelFormat, const Size& contentSize);
  51. // 缓存通过Image创建的纹理
  52. static void addImage(Texture2D *tt, Image *image);
  53. // 是否是glGenerateMimap动态生成的多级纹理,通过外部工具生成的纹理其实就是当成普通纹理来处理。
  54. static void setHasMipmaps(Texture2D *t, bool hasMipmaps);
  55. // 缓存纹理参数
  56. static void setTexParameters(Texture2D *t, const Texture2D::TexParams &texParams);
  57. // 清除纹理对应的缓存数据,释放对应的VolatileTexture内存
  58. static void removeTexture(Texture2D *t);
  59. // 重新加载所有缓存纹理,在合适的时机调用,在重新加载之前会先清除一次GPU中相关纹理的缓存。
  60. static void reloadAllTextures();
  61. public:
  62. static std::list<VolatileTexture*> _textures; // 双向链表,相关知识:https://www.yuque.com/tvvhealth/cs/cxu0km
  63. static bool _isReloading; // 重新加载纹理是一个耗时的操作,添加状态识别。
  64. private:
  65. // find VolatileTexture by Texture2D*
  66. // if not found, create a new one
  67. static VolatileTexture* findVolotileTexture(Texture2D *tt);
  68. static void reloadTexture(Texture2D* texture, const std::string& filename, Texture2D::PixelFormat pixelFormat);
  69. };
  70. #endif