2010-08-02 10:58:00 +08:00
|
|
|
/****************************************************************************
|
2012-09-24 21:22:20 +08:00
|
|
|
Copyright (c) 2010-2012 cocos2d-x.org
|
2011-03-19 14:45:51 +08:00
|
|
|
Copyright (c) 2008-2010 Ricardo Quesada
|
2011-07-05 10:47:25 +08:00
|
|
|
Copyright (c) 2011 Zynga Inc.
|
2010-08-02 10:58:00 +08:00
|
|
|
|
|
|
|
http://www.cocos2d-x.org
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
|
|
all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
THE SOFTWARE.
|
|
|
|
****************************************************************************/
|
|
|
|
|
2013-06-21 15:29:21 +08:00
|
|
|
#include <errno.h>
|
|
|
|
#include <stack>
|
|
|
|
#include <cctype>
|
|
|
|
#include <list>
|
|
|
|
|
2010-07-16 16:28:11 +08:00
|
|
|
#include "CCTextureCache.h"
|
|
|
|
#include "CCTexture2D.h"
|
|
|
|
#include "ccMacros.h"
|
2010-07-30 16:35:07 +08:00
|
|
|
#include "CCDirector.h"
|
2012-06-19 16:20:46 +08:00
|
|
|
#include "platform/CCFileUtils.h"
|
|
|
|
#include "platform/CCThread.h"
|
2011-07-28 17:32:09 +08:00
|
|
|
#include "support/ccUtils.h"
|
2011-11-16 11:04:29 +08:00
|
|
|
#include "CCScheduler.h"
|
2012-06-19 16:20:46 +08:00
|
|
|
#include "cocoa/CCString.h"
|
2013-06-21 15:29:21 +08:00
|
|
|
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2013-06-13 05:46:32 +08:00
|
|
|
#ifdef EMSCRIPTEN
|
|
|
|
#include <emscripten/emscripten.h>
|
|
|
|
#include "platform/emscripten/CCTextureCacheEmscripten.h"
|
|
|
|
#endif // EMSCRIPTEN
|
|
|
|
|
2012-03-20 15:04:53 +08:00
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
NS_CC_BEGIN
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
// implementation TextureCache
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2013-06-21 15:29:21 +08:00
|
|
|
TextureCache* TextureCache::_sharedTextureCache = nullptr;
|
|
|
|
|
2013-07-12 06:24:23 +08:00
|
|
|
TextureCache * TextureCache::getInstance()
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2013-06-21 15:29:21 +08:00
|
|
|
if (!_sharedTextureCache)
|
2012-05-30 14:24:59 +08:00
|
|
|
{
|
2013-06-13 05:46:32 +08:00
|
|
|
#ifdef EMSCRIPTEN
|
2013-06-21 15:29:21 +08:00
|
|
|
_sharedTextureCache = new TextureCacheEmscripten();
|
2013-06-13 05:46:32 +08:00
|
|
|
#else
|
2013-06-21 15:29:21 +08:00
|
|
|
_sharedTextureCache = new TextureCache();
|
2013-06-13 05:46:32 +08:00
|
|
|
#endif // EMSCRIPTEN
|
2012-05-30 14:24:59 +08:00
|
|
|
}
|
2013-06-21 15:29:21 +08:00
|
|
|
return _sharedTextureCache;
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
TextureCache::TextureCache()
|
2013-06-26 14:48:19 +08:00
|
|
|
: _loadingThread(nullptr)
|
|
|
|
, _asyncStructQueue(nullptr)
|
2013-06-21 15:29:21 +08:00
|
|
|
, _imageInfoQueue(nullptr)
|
|
|
|
, _needQuit(false)
|
|
|
|
, _asyncRefCount(0)
|
|
|
|
, _textures(new Dictionary())
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2013-07-20 13:01:27 +08:00
|
|
|
CCASSERT(_sharedTextureCache == nullptr, "Attempted to allocate a second instance of a singleton.");
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
TextureCache::~TextureCache()
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2013-06-20 14:13:12 +08:00
|
|
|
CCLOGINFO("cocos2d: deallocing TextureCache: %p", this);
|
2013-04-27 00:55:36 +08:00
|
|
|
|
2013-06-15 14:03:30 +08:00
|
|
|
CC_SAFE_RELEASE(_textures);
|
2013-06-21 15:29:21 +08:00
|
|
|
|
2013-06-26 14:48:19 +08:00
|
|
|
CC_SAFE_DELETE(_loadingThread);
|
2013-06-21 15:43:17 +08:00
|
|
|
_sharedTextureCache = nullptr;
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-07-12 06:24:23 +08:00
|
|
|
void TextureCache::destroyInstance()
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2013-06-21 15:43:17 +08:00
|
|
|
// notify sub thread to quick
|
|
|
|
_sharedTextureCache->_needQuit = true;
|
|
|
|
_sharedTextureCache->_sleepCondition.notify_one();
|
2013-06-26 15:11:13 +08:00
|
|
|
if (_sharedTextureCache->_loadingThread) _sharedTextureCache->_loadingThread->join();
|
2013-06-21 15:29:21 +08:00
|
|
|
|
|
|
|
CC_SAFE_RELEASE_NULL(_sharedTextureCache);
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-07-07 13:01:21 +08:00
|
|
|
const char* TextureCache::description() const
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2013-06-20 14:13:12 +08:00
|
|
|
return String::createWithFormat("<TextureCache | Number of textures = %u>", _textures->count())->getCString();
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
Dictionary* TextureCache::snapshotTextures()
|
2012-04-27 18:47:49 +08:00
|
|
|
{
|
2013-06-20 14:13:12 +08:00
|
|
|
Dictionary* pRet = new Dictionary();
|
|
|
|
DictElement* pElement = NULL;
|
2013-06-15 14:03:30 +08:00
|
|
|
CCDICT_FOREACH(_textures, pElement)
|
2012-04-27 18:47:49 +08:00
|
|
|
{
|
|
|
|
pRet->setObject(pElement->getObject(), pElement->getStrKey());
|
|
|
|
}
|
2013-06-09 15:03:38 +08:00
|
|
|
pRet->autorelease();
|
2012-04-27 18:47:49 +08:00
|
|
|
return pRet;
|
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void TextureCache::addImageAsync(const char *path, Object *target, SEL_CallFuncO selector)
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2013-07-20 13:01:27 +08:00
|
|
|
CCASSERT(path != NULL, "TextureCache: fileimage MUST not be NULL");
|
2012-04-19 14:35:52 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
Texture2D *texture = NULL;
|
2012-04-19 14:35:52 +08:00
|
|
|
|
|
|
|
// optimization
|
|
|
|
|
|
|
|
std::string pathKey = path;
|
|
|
|
|
2013-07-12 06:24:23 +08:00
|
|
|
pathKey = FileUtils::getInstance()->fullPathForFilename(pathKey.c_str());
|
2013-07-16 16:33:02 +08:00
|
|
|
texture = static_cast<Texture2D*>(_textures->objectForKey(pathKey.c_str()));
|
2012-04-19 14:35:52 +08:00
|
|
|
|
|
|
|
std::string fullpath = pathKey;
|
|
|
|
if (texture != NULL)
|
|
|
|
{
|
|
|
|
if (target && selector)
|
|
|
|
{
|
|
|
|
(target->*selector)(texture);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// lazy init
|
2013-06-21 15:29:21 +08:00
|
|
|
if (_asyncStructQueue == NULL)
|
2012-04-19 14:35:52 +08:00
|
|
|
{
|
2013-06-21 15:29:21 +08:00
|
|
|
_asyncStructQueue = new queue<AsyncStruct*>();
|
2013-08-13 06:09:28 +08:00
|
|
|
_imageInfoQueue = new queue<ImageInfo*>();
|
2011-11-16 11:04:29 +08:00
|
|
|
|
2013-06-21 15:49:45 +08:00
|
|
|
// create a new thread to load images
|
2013-06-26 14:48:19 +08:00
|
|
|
_loadingThread = new std::thread(&TextureCache::loadImage, this);
|
2013-06-21 15:29:21 +08:00
|
|
|
|
|
|
|
_needQuit = false;
|
2012-05-28 02:41:12 +08:00
|
|
|
}
|
|
|
|
|
2013-06-21 15:29:21 +08:00
|
|
|
if (0 == _asyncRefCount)
|
2012-05-30 14:24:59 +08:00
|
|
|
{
|
2013-07-12 06:24:23 +08:00
|
|
|
Director::getInstance()->getScheduler()->scheduleSelector(schedule_selector(TextureCache::addImageAsyncCallBack), this, 0, false);
|
2012-05-30 14:24:59 +08:00
|
|
|
}
|
|
|
|
|
2013-06-21 15:29:21 +08:00
|
|
|
++_asyncRefCount;
|
2012-05-30 14:24:59 +08:00
|
|
|
|
2012-05-28 02:41:12 +08:00
|
|
|
if (target)
|
|
|
|
{
|
|
|
|
target->retain();
|
2012-04-19 14:35:52 +08:00
|
|
|
}
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2012-04-19 14:35:52 +08:00
|
|
|
// generate async struct
|
2013-06-13 01:03:12 +08:00
|
|
|
AsyncStruct *data = new AsyncStruct(fullpath, target, selector);
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2012-04-19 14:35:52 +08:00
|
|
|
// add async struct into queue
|
2013-06-21 15:43:17 +08:00
|
|
|
_asyncStructQueueMutex.lock();
|
2013-06-21 15:29:21 +08:00
|
|
|
_asyncStructQueue->push(data);
|
2013-06-21 15:43:17 +08:00
|
|
|
_asyncStructQueueMutex.unlock();
|
2011-11-25 16:14:06 +08:00
|
|
|
|
2013-06-21 15:43:17 +08:00
|
|
|
_sleepCondition.notify_one();
|
2013-06-21 15:29:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void TextureCache::loadImage()
|
|
|
|
{
|
2013-06-21 15:43:17 +08:00
|
|
|
AsyncStruct *pAsyncStruct = nullptr;
|
2013-06-21 15:29:21 +08:00
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
// create autorelease pool for iOS
|
|
|
|
Thread thread;
|
|
|
|
thread.createAutoreleasePool();
|
|
|
|
|
|
|
|
std::queue<AsyncStruct*> *pQueue = _asyncStructQueue;
|
2013-06-21 15:43:17 +08:00
|
|
|
_asyncStructQueueMutex.lock();
|
2013-06-21 15:29:21 +08:00
|
|
|
if (pQueue->empty())
|
|
|
|
{
|
2013-06-21 15:43:17 +08:00
|
|
|
_asyncStructQueueMutex.unlock();
|
2013-06-21 15:29:21 +08:00
|
|
|
if (_needQuit) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else {
|
2013-06-21 15:43:17 +08:00
|
|
|
std::unique_lock<std::mutex> lk(_sleepMutex);
|
|
|
|
_sleepCondition.wait(lk);
|
2013-06-21 15:29:21 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pAsyncStruct = pQueue->front();
|
|
|
|
pQueue->pop();
|
2013-06-21 15:43:17 +08:00
|
|
|
_asyncStructQueueMutex.unlock();
|
2013-06-21 15:29:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *filename = pAsyncStruct->filename.c_str();
|
|
|
|
|
|
|
|
// generate image
|
|
|
|
Image *pImage = new Image();
|
2013-07-17 17:12:04 +08:00
|
|
|
if (pImage && !pImage->initWithImageFileThreadSafe(filename))
|
2013-06-21 15:29:21 +08:00
|
|
|
{
|
|
|
|
CC_SAFE_RELEASE(pImage);
|
|
|
|
CCLOG("can not load %s", filename);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate image info
|
|
|
|
ImageInfo *pImageInfo = new ImageInfo();
|
|
|
|
pImageInfo->asyncStruct = pAsyncStruct;
|
|
|
|
pImageInfo->image = pImage;
|
|
|
|
|
|
|
|
// put the image info into the queue
|
2013-06-21 15:49:45 +08:00
|
|
|
_imageInfoMutex.lock();
|
2013-06-21 15:29:21 +08:00
|
|
|
_imageInfoQueue->push(pImageInfo);
|
2013-06-21 15:49:45 +08:00
|
|
|
_imageInfoMutex.unlock();
|
2013-06-21 15:29:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if(_asyncStructQueue != nullptr)
|
|
|
|
{
|
|
|
|
delete _asyncStructQueue;
|
2013-06-21 15:43:17 +08:00
|
|
|
_asyncStructQueue = nullptr;
|
2013-06-21 15:29:21 +08:00
|
|
|
delete _imageInfoQueue;
|
2013-06-21 15:43:17 +08:00
|
|
|
_imageInfoQueue = nullptr;
|
2013-06-21 15:29:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void TextureCache::addImageAsyncCallBack(float dt)
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2012-04-19 14:35:52 +08:00
|
|
|
// the image is generated in loading thread
|
2013-06-21 15:29:21 +08:00
|
|
|
std::queue<ImageInfo*> *imagesQueue = _imageInfoQueue;
|
2012-04-19 14:35:52 +08:00
|
|
|
|
2013-06-21 15:43:17 +08:00
|
|
|
_imageInfoMutex.lock();
|
2012-04-19 14:35:52 +08:00
|
|
|
if (imagesQueue->empty())
|
|
|
|
{
|
2013-06-21 15:43:17 +08:00
|
|
|
_imageInfoMutex.unlock();
|
2012-04-19 14:35:52 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ImageInfo *pImageInfo = imagesQueue->front();
|
|
|
|
imagesQueue->pop();
|
2013-06-21 15:43:17 +08:00
|
|
|
_imageInfoMutex.unlock();
|
2012-04-19 14:35:52 +08:00
|
|
|
|
|
|
|
AsyncStruct *pAsyncStruct = pImageInfo->asyncStruct;
|
2013-06-20 14:13:12 +08:00
|
|
|
Image *pImage = pImageInfo->image;
|
2012-04-19 14:35:52 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
Object *target = pAsyncStruct->target;
|
2012-04-19 14:35:52 +08:00
|
|
|
SEL_CallFuncO selector = pAsyncStruct->selector;
|
|
|
|
const char* filename = pAsyncStruct->filename.c_str();
|
|
|
|
|
|
|
|
// generate texture in render thread
|
2013-06-20 14:13:12 +08:00
|
|
|
Texture2D *texture = new Texture2D();
|
2013-06-21 15:29:21 +08:00
|
|
|
|
2012-03-21 10:36:20 +08:00
|
|
|
texture->initWithImage(pImage);
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2012-06-06 10:06:51 +08:00
|
|
|
#if CC_ENABLE_CACHE_TEXTURE_DATA
|
2012-05-30 14:24:59 +08:00
|
|
|
// cache the texture file name
|
2013-07-19 15:37:54 +08:00
|
|
|
VolatileTexture::addImageTexture(texture, filename);
|
2011-12-01 17:04:56 +08:00
|
|
|
#endif
|
2012-04-19 14:35:52 +08:00
|
|
|
// cache the texture
|
2013-06-15 14:03:30 +08:00
|
|
|
_textures->setObject(texture, filename);
|
2012-04-19 14:35:52 +08:00
|
|
|
texture->autorelease();
|
2011-11-16 11:04:29 +08:00
|
|
|
|
2012-04-19 14:35:52 +08:00
|
|
|
if (target && selector)
|
|
|
|
{
|
|
|
|
(target->*selector)(texture);
|
|
|
|
target->release();
|
|
|
|
}
|
2011-11-16 11:04:29 +08:00
|
|
|
|
2012-05-30 14:24:59 +08:00
|
|
|
pImage->release();
|
2012-04-19 14:35:52 +08:00
|
|
|
delete pAsyncStruct;
|
|
|
|
delete pImageInfo;
|
2012-05-30 14:24:59 +08:00
|
|
|
|
2013-06-21 15:29:21 +08:00
|
|
|
--_asyncRefCount;
|
|
|
|
if (0 == _asyncRefCount)
|
2012-05-30 14:24:59 +08:00
|
|
|
{
|
2013-07-12 06:24:23 +08:00
|
|
|
Director::getInstance()->getScheduler()->unscheduleSelector(schedule_selector(TextureCache::addImageAsyncCallBack), this);
|
2012-05-30 14:24:59 +08:00
|
|
|
}
|
2012-04-19 14:35:52 +08:00
|
|
|
}
|
2011-11-16 11:04:29 +08:00
|
|
|
}
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
Texture2D * TextureCache::addImage(const char * path)
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2013-07-20 13:01:27 +08:00
|
|
|
CCASSERT(path != NULL, "TextureCache: fileimage MUST not be NULL");
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
Texture2D * texture = NULL;
|
|
|
|
Image* pImage = NULL;
|
2012-04-19 14:35:52 +08:00
|
|
|
// Split up directory and filename
|
|
|
|
// MUTEX:
|
|
|
|
// Needed since addImageAsync calls this method from a different thread
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2011-03-09 12:00:04 +08:00
|
|
|
std::string pathKey = path;
|
2010-12-30 17:30:11 +08:00
|
|
|
|
2013-07-12 06:24:23 +08:00
|
|
|
pathKey = FileUtils::getInstance()->fullPathForFilename(pathKey.c_str());
|
2013-02-27 11:35:38 +08:00
|
|
|
if (pathKey.size() == 0)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
2013-07-16 16:33:02 +08:00
|
|
|
texture = static_cast<Texture2D*>(_textures->objectForKey(pathKey.c_str()));
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2013-06-21 15:29:21 +08:00
|
|
|
std::string fullpath = pathKey;
|
2013-02-27 11:35:38 +08:00
|
|
|
if (! texture)
|
2012-04-19 14:35:52 +08:00
|
|
|
{
|
2013-01-18 18:05:32 +08:00
|
|
|
std::string lowerCase(pathKey);
|
2012-04-19 14:35:52 +08:00
|
|
|
for (unsigned int i = 0; i < lowerCase.length(); ++i)
|
|
|
|
{
|
|
|
|
lowerCase[i] = tolower(lowerCase[i]);
|
|
|
|
}
|
|
|
|
// all images are handled by UIImage except PVR extension that is handled by our own handler
|
|
|
|
do
|
|
|
|
{
|
2013-07-26 17:34:44 +08:00
|
|
|
pImage = new Image();
|
|
|
|
CC_BREAK_IF(NULL == pImage);
|
2012-12-27 16:07:48 +08:00
|
|
|
|
2013-07-26 17:34:44 +08:00
|
|
|
bool bRet = pImage->initWithImageFile(fullpath.c_str());
|
|
|
|
CC_BREAK_IF(!bRet);
|
2013-05-18 08:09:28 +08:00
|
|
|
|
2013-07-26 17:34:44 +08:00
|
|
|
texture = new Texture2D();
|
|
|
|
|
|
|
|
if( texture &&
|
|
|
|
texture->initWithImage(pImage) )
|
|
|
|
{
|
2012-06-06 10:06:51 +08:00
|
|
|
#if CC_ENABLE_CACHE_TEXTURE_DATA
|
2013-07-26 17:34:44 +08:00
|
|
|
// cache the texture file name
|
|
|
|
VolatileTexture::addImageTexture(texture, fullpath.c_str());
|
2011-04-06 16:29:58 +08:00
|
|
|
#endif
|
2013-07-26 17:34:44 +08:00
|
|
|
_textures->setObject(texture, pathKey.c_str());
|
|
|
|
texture->release();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CCLOG("cocos2d: Couldn't create texture for file:%s in TextureCache", path);
|
2012-04-19 14:35:52 +08:00
|
|
|
}
|
|
|
|
} while (0);
|
|
|
|
}
|
|
|
|
|
2012-12-27 15:42:55 +08:00
|
|
|
CC_SAFE_RELEASE(pImage);
|
|
|
|
|
2012-04-19 14:35:52 +08:00
|
|
|
return texture;
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
Texture2D* TextureCache::addUIImage(Image *image, const char *key)
|
2010-08-26 14:06:41 +08:00
|
|
|
{
|
2013-07-20 13:01:27 +08:00
|
|
|
CCASSERT(image != NULL, "TextureCache: image MUST not be nil");
|
2012-04-19 14:35:52 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
Texture2D * texture = NULL;
|
2012-04-19 14:35:52 +08:00
|
|
|
// textureForKey() use full path,so the key should be full path
|
|
|
|
std::string forKey;
|
|
|
|
if (key)
|
|
|
|
{
|
2013-07-12 06:24:23 +08:00
|
|
|
forKey = FileUtils::getInstance()->fullPathForFilename(key);
|
2012-04-19 14:35:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Don't have to lock here, because addImageAsync() will not
|
|
|
|
// invoke opengl function in loading thread.
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
// If key is nil, then create a new texture each time
|
2013-06-20 14:13:12 +08:00
|
|
|
if(key && (texture = (Texture2D *)_textures->objectForKey(forKey.c_str())))
|
2012-04-19 14:35:52 +08:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// prevents overloading the autorelease pool
|
2013-06-20 14:13:12 +08:00
|
|
|
texture = new Texture2D();
|
2012-08-08 18:39:33 +08:00
|
|
|
texture->initWithImage(image);
|
2012-04-19 14:35:52 +08:00
|
|
|
|
|
|
|
if(key && texture)
|
|
|
|
{
|
2013-06-15 14:03:30 +08:00
|
|
|
_textures->setObject(texture, forKey.c_str());
|
2012-04-19 14:35:52 +08:00
|
|
|
texture->autorelease();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-06-20 14:13:12 +08:00
|
|
|
CCLOG("cocos2d: Couldn't add UIImage in TextureCache");
|
2012-04-19 14:35:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
} while (0);
|
2013-08-16 11:02:44 +08:00
|
|
|
|
2012-06-06 10:06:51 +08:00
|
|
|
#if CC_ENABLE_CACHE_TEXTURE_DATA
|
2013-06-20 14:13:12 +08:00
|
|
|
VolatileTexture::addImage(texture, image);
|
2012-04-24 15:02:18 +08:00
|
|
|
#endif
|
|
|
|
|
2012-04-19 14:35:52 +08:00
|
|
|
return texture;
|
2010-08-26 14:06:41 +08:00
|
|
|
}
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2010-07-16 16:28:11 +08:00
|
|
|
// TextureCache - Remove
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void TextureCache::removeAllTextures()
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2013-06-15 14:03:30 +08:00
|
|
|
_textures->removeAllObjects();
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void TextureCache::removeUnusedTextures()
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2012-04-28 11:34:13 +08:00
|
|
|
/*
|
2013-06-20 14:13:12 +08:00
|
|
|
DictElement* pElement = NULL;
|
2013-06-15 14:03:30 +08:00
|
|
|
CCDICT_FOREACH(_textures, pElement)
|
2012-04-19 14:35:52 +08:00
|
|
|
{
|
2013-06-20 14:13:12 +08:00
|
|
|
CCLOG("cocos2d: TextureCache: texture: %s", pElement->getStrKey());
|
2013-07-09 14:40:43 +08:00
|
|
|
Texture2D *value = static_cast<Texture2D*>(pElement->getObject());
|
2012-04-19 14:35:52 +08:00
|
|
|
if (value->retainCount() == 1)
|
|
|
|
{
|
2013-06-20 14:13:12 +08:00
|
|
|
CCLOG("cocos2d: TextureCache: removing unused texture: %s", pElement->getStrKey());
|
2013-06-15 14:03:30 +08:00
|
|
|
_textures->removeObjectForElememt(pElement);
|
2012-04-19 14:35:52 +08:00
|
|
|
}
|
|
|
|
}
|
2012-04-28 11:34:13 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
/** Inter engineer zhuoshi sun finds that this way will get better performance
|
|
|
|
*/
|
2013-06-15 14:03:30 +08:00
|
|
|
if (_textures->count())
|
2012-04-28 11:34:13 +08:00
|
|
|
{
|
|
|
|
// find elements to be removed
|
2013-06-20 14:13:12 +08:00
|
|
|
DictElement* pElement = NULL;
|
|
|
|
list<DictElement*> elementToRemove;
|
2013-06-15 14:03:30 +08:00
|
|
|
CCDICT_FOREACH(_textures, pElement)
|
2012-04-28 11:34:13 +08:00
|
|
|
{
|
2013-06-20 14:13:12 +08:00
|
|
|
CCLOG("cocos2d: TextureCache: texture: %s", pElement->getStrKey());
|
2013-07-09 14:40:43 +08:00
|
|
|
Texture2D *value = static_cast<Texture2D*>(pElement->getObject());
|
2012-04-28 11:34:13 +08:00
|
|
|
if (value->retainCount() == 1)
|
|
|
|
{
|
|
|
|
elementToRemove.push_back(pElement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove elements
|
2013-07-16 16:33:02 +08:00
|
|
|
for (auto iter = elementToRemove.begin(); iter != elementToRemove.end(); ++iter)
|
2012-04-28 11:34:13 +08:00
|
|
|
{
|
2013-06-20 14:13:12 +08:00
|
|
|
CCLOG("cocos2d: TextureCache: removing unused texture: %s", (*iter)->getStrKey());
|
2013-06-15 14:03:30 +08:00
|
|
|
_textures->removeObjectForElememt(*iter);
|
2012-04-28 11:34:13 +08:00
|
|
|
}
|
|
|
|
}
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void TextureCache::removeTexture(Texture2D* texture)
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2012-04-19 14:35:52 +08:00
|
|
|
if( ! texture )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
Array* keys = _textures->allKeysForObject(texture);
|
2013-06-15 14:03:30 +08:00
|
|
|
_textures->removeObjectsForKeys(keys);
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void TextureCache::removeTextureForKey(const char *textureKeyName)
|
2010-07-16 14:15:06 +08:00
|
|
|
{
|
2012-04-19 14:35:52 +08:00
|
|
|
if (textureKeyName == NULL)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2010-07-16 14:15:06 +08:00
|
|
|
|
2013-07-12 06:24:23 +08:00
|
|
|
string fullPath = FileUtils::getInstance()->fullPathForFilename(textureKeyName);
|
2013-06-15 14:03:30 +08:00
|
|
|
_textures->removeObjectForKey(fullPath);
|
2010-12-30 17:30:11 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
Texture2D* TextureCache::textureForKey(const char* key)
|
2010-12-30 17:30:11 +08:00
|
|
|
{
|
2013-07-12 06:24:23 +08:00
|
|
|
return static_cast<Texture2D*>(_textures->objectForKey(FileUtils::getInstance()->fullPathForFilename(key)));
|
2010-07-16 14:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void TextureCache::reloadAllTextures()
|
2011-04-06 16:29:58 +08:00
|
|
|
{
|
2012-06-06 10:06:51 +08:00
|
|
|
#if CC_ENABLE_CACHE_TEXTURE_DATA
|
2011-04-06 16:29:58 +08:00
|
|
|
VolatileTexture::reloadAllTextures();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void TextureCache::dumpCachedTextureInfo()
|
2011-07-19 15:14:59 +08:00
|
|
|
{
|
2012-04-19 14:35:52 +08:00
|
|
|
unsigned int count = 0;
|
|
|
|
unsigned int totalBytes = 0;
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
DictElement* pElement = NULL;
|
2013-06-15 14:03:30 +08:00
|
|
|
CCDICT_FOREACH(_textures, pElement)
|
2012-04-19 14:35:52 +08:00
|
|
|
{
|
2013-07-09 14:40:43 +08:00
|
|
|
Texture2D* tex = static_cast<Texture2D*>(pElement->getObject());
|
2013-07-20 04:16:38 +08:00
|
|
|
unsigned int bpp = tex->getBitsPerPixelForFormat();
|
2011-11-25 10:48:05 +08:00
|
|
|
// Each texture takes up width * height * bytesPerPixel bytes.
|
2012-04-19 14:35:52 +08:00
|
|
|
unsigned int bytes = tex->getPixelsWide() * tex->getPixelsHigh() * bpp / 8;
|
|
|
|
totalBytes += bytes;
|
|
|
|
count++;
|
|
|
|
CCLOG("cocos2d: \"%s\" rc=%lu id=%lu %lu x %lu @ %ld bpp => %lu KB",
|
|
|
|
pElement->getStrKey(),
|
|
|
|
(long)tex->retainCount(),
|
|
|
|
(long)tex->getName(),
|
|
|
|
(long)tex->getPixelsWide(),
|
|
|
|
(long)tex->getPixelsHigh(),
|
|
|
|
(long)bpp,
|
|
|
|
(long)bytes / 1024);
|
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
CCLOG("cocos2d: TextureCache dumpDebugInfo: %ld textures, for %lu KB (%.2f MB)", (long)count, (long)totalBytes / 1024, totalBytes / (1024.0f*1024.0f));
|
2011-07-05 10:47:25 +08:00
|
|
|
}
|
|
|
|
|
2012-06-06 10:06:51 +08:00
|
|
|
#if CC_ENABLE_CACHE_TEXTURE_DATA
|
2011-04-06 16:29:58 +08:00
|
|
|
|
2013-07-16 16:33:02 +08:00
|
|
|
std::list<VolatileTexture*> VolatileTexture::_textures;
|
|
|
|
bool VolatileTexture::_isReloading = false;
|
2011-11-25 10:48:05 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
VolatileTexture::VolatileTexture(Texture2D *t)
|
2013-07-16 16:33:02 +08:00
|
|
|
: _texture(t)
|
2013-06-15 14:03:30 +08:00
|
|
|
, _cashedImageType(kInvalid)
|
|
|
|
, _textureData(NULL)
|
2013-07-26 04:36:19 +08:00
|
|
|
, _pixelFormat(Texture2D::PixelFormat::RGBA8888)
|
2013-06-15 14:03:30 +08:00
|
|
|
, _fileName("")
|
|
|
|
, _text("")
|
2013-07-16 16:33:02 +08:00
|
|
|
, _uiImage(NULL)
|
2011-11-25 10:48:05 +08:00
|
|
|
{
|
2013-06-15 14:03:30 +08:00
|
|
|
_texParams.minFilter = GL_LINEAR;
|
|
|
|
_texParams.magFilter = GL_LINEAR;
|
|
|
|
_texParams.wrapS = GL_CLAMP_TO_EDGE;
|
|
|
|
_texParams.wrapT = GL_CLAMP_TO_EDGE;
|
2013-07-16 16:33:02 +08:00
|
|
|
_textures.push_back(this);
|
2011-11-25 10:48:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
VolatileTexture::~VolatileTexture()
|
|
|
|
{
|
2013-07-16 16:33:02 +08:00
|
|
|
_textures.remove(this);
|
|
|
|
CC_SAFE_RELEASE(_uiImage);
|
2011-11-25 10:48:05 +08:00
|
|
|
}
|
|
|
|
|
2013-07-19 15:37:54 +08:00
|
|
|
void VolatileTexture::addImageTexture(Texture2D *tt, const char* imageFileName)
|
2011-11-25 10:48:05 +08:00
|
|
|
{
|
2013-07-16 16:33:02 +08:00
|
|
|
if (_isReloading)
|
2011-04-06 16:29:58 +08:00
|
|
|
{
|
2012-04-24 15:02:18 +08:00
|
|
|
return;
|
2011-04-06 16:29:58 +08:00
|
|
|
}
|
|
|
|
|
2012-04-24 15:02:18 +08:00
|
|
|
VolatileTexture *vt = findVolotileTexture(tt);
|
2011-11-25 10:48:05 +08:00
|
|
|
|
2013-06-15 14:03:30 +08:00
|
|
|
vt->_cashedImageType = kImageFile;
|
|
|
|
vt->_fileName = imageFileName;
|
|
|
|
vt->_pixelFormat = tt->getPixelFormat();
|
2011-11-25 10:48:05 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void VolatileTexture::addImage(Texture2D *tt, Image *image)
|
2011-11-25 10:48:05 +08:00
|
|
|
{
|
2012-04-24 15:02:18 +08:00
|
|
|
VolatileTexture *vt = findVolotileTexture(tt);
|
2012-05-23 11:18:04 +08:00
|
|
|
image->retain();
|
2013-07-16 16:33:02 +08:00
|
|
|
vt->_uiImage = image;
|
2013-06-15 14:03:30 +08:00
|
|
|
vt->_cashedImageType = kImage;
|
2012-04-24 15:02:18 +08:00
|
|
|
}
|
2012-04-19 14:35:52 +08:00
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
VolatileTexture* VolatileTexture::findVolotileTexture(Texture2D *tt)
|
2012-04-24 15:02:18 +08:00
|
|
|
{
|
2012-04-19 14:35:52 +08:00
|
|
|
VolatileTexture *vt = 0;
|
2013-07-16 16:33:02 +08:00
|
|
|
auto i = _textures.begin();
|
|
|
|
while (i != _textures.end())
|
2012-04-19 14:35:52 +08:00
|
|
|
{
|
|
|
|
VolatileTexture *v = *i++;
|
2013-07-16 16:33:02 +08:00
|
|
|
if (v->_texture == tt)
|
2012-04-24 15:02:18 +08:00
|
|
|
{
|
2012-04-19 14:35:52 +08:00
|
|
|
vt = v;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-04-24 15:02:18 +08:00
|
|
|
|
|
|
|
if (! vt)
|
|
|
|
{
|
2012-04-19 14:35:52 +08:00
|
|
|
vt = new VolatileTexture(tt);
|
2012-04-24 15:02:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return vt;
|
|
|
|
}
|
|
|
|
|
2013-07-27 22:06:30 +08:00
|
|
|
void VolatileTexture::addDataTexture(Texture2D *tt, void* data, int dataLen, Texture2D::PixelFormat pixelFormat, const Size& contentSize)
|
2012-04-24 15:02:18 +08:00
|
|
|
{
|
2013-07-16 16:33:02 +08:00
|
|
|
if (_isReloading)
|
2012-04-24 15:02:18 +08:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
VolatileTexture *vt = findVolotileTexture(tt);
|
2012-04-19 14:35:52 +08:00
|
|
|
|
2013-06-15 14:03:30 +08:00
|
|
|
vt->_cashedImageType = kImageData;
|
|
|
|
vt->_textureData = data;
|
2013-07-26 17:34:44 +08:00
|
|
|
vt->_dataLen = dataLen;
|
2013-06-15 14:03:30 +08:00
|
|
|
vt->_pixelFormat = pixelFormat;
|
|
|
|
vt->_textureSize = contentSize;
|
2011-11-25 10:48:05 +08:00
|
|
|
}
|
|
|
|
|
2013-07-10 04:21:43 +08:00
|
|
|
void VolatileTexture::addStringTexture(Texture2D *tt, const char* text, const FontDefinition& fontDefinition)
|
2011-04-06 16:29:58 +08:00
|
|
|
{
|
2013-07-16 16:33:02 +08:00
|
|
|
if (_isReloading)
|
2011-04-06 16:29:58 +08:00
|
|
|
{
|
2012-04-24 15:02:18 +08:00
|
|
|
return;
|
2011-04-06 16:29:58 +08:00
|
|
|
}
|
|
|
|
|
2012-04-24 15:02:18 +08:00
|
|
|
VolatileTexture *vt = findVolotileTexture(tt);
|
2011-04-06 16:29:58 +08:00
|
|
|
|
2013-06-15 14:03:30 +08:00
|
|
|
vt->_cashedImageType = kString;
|
|
|
|
vt->_text = text;
|
2013-07-10 04:21:43 +08:00
|
|
|
vt->_fontDefinition = fontDefinition;
|
2011-04-06 16:29:58 +08:00
|
|
|
}
|
2011-11-25 10:48:05 +08:00
|
|
|
|
2013-08-06 11:34:48 +08:00
|
|
|
void VolatileTexture::setTexParameters(Texture2D *t, const Texture2D::TexParams &texParams)
|
2012-08-31 03:02:05 +08:00
|
|
|
{
|
|
|
|
VolatileTexture *vt = findVolotileTexture(t);
|
|
|
|
|
2013-07-04 08:59:22 +08:00
|
|
|
if (texParams.minFilter != GL_NONE)
|
|
|
|
vt->_texParams.minFilter = texParams.minFilter;
|
|
|
|
if (texParams.magFilter != GL_NONE)
|
|
|
|
vt->_texParams.magFilter = texParams.magFilter;
|
|
|
|
if (texParams.wrapS != GL_NONE)
|
|
|
|
vt->_texParams.wrapS = texParams.wrapS;
|
|
|
|
if (texParams.wrapT != GL_NONE)
|
|
|
|
vt->_texParams.wrapT = texParams.wrapT;
|
2012-08-31 03:02:05 +08:00
|
|
|
}
|
|
|
|
|
2013-06-20 14:13:12 +08:00
|
|
|
void VolatileTexture::removeTexture(Texture2D *t)
|
2012-04-24 15:02:18 +08:00
|
|
|
{
|
2013-07-16 16:33:02 +08:00
|
|
|
auto i = _textures.begin();
|
|
|
|
while (i != _textures.end())
|
2011-11-25 10:48:05 +08:00
|
|
|
{
|
|
|
|
VolatileTexture *vt = *i++;
|
2013-07-16 16:33:02 +08:00
|
|
|
if (vt->_texture == t)
|
2012-04-24 15:02:18 +08:00
|
|
|
{
|
2011-11-25 10:48:05 +08:00
|
|
|
delete vt;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VolatileTexture::reloadAllTextures()
|
2011-07-19 15:14:59 +08:00
|
|
|
{
|
2013-07-16 16:33:02 +08:00
|
|
|
_isReloading = true;
|
2011-07-19 15:14:59 +08:00
|
|
|
|
|
|
|
CCLOG("reload all texture");
|
2013-07-16 16:33:02 +08:00
|
|
|
auto iter = _textures.begin();
|
2011-07-19 15:14:59 +08:00
|
|
|
|
2013-07-16 16:33:02 +08:00
|
|
|
while (iter != _textures.end())
|
2011-07-19 15:14:59 +08:00
|
|
|
{
|
2012-04-24 15:02:18 +08:00
|
|
|
VolatileTexture *vt = *iter++;
|
2011-07-19 15:14:59 +08:00
|
|
|
|
2013-06-15 14:03:30 +08:00
|
|
|
switch (vt->_cashedImageType)
|
2012-04-19 14:35:52 +08:00
|
|
|
{
|
2012-04-08 14:16:29 +08:00
|
|
|
case kImageFile:
|
|
|
|
{
|
2013-07-26 17:34:44 +08:00
|
|
|
Image* pImage = new Image();
|
|
|
|
unsigned long nSize = 0;
|
|
|
|
unsigned char* pBuffer = FileUtils::getInstance()->getFileData(vt->_fileName.c_str(), "rb", &nSize);
|
|
|
|
|
2013-08-12 18:18:09 +08:00
|
|
|
if (pImage && pImage->initWithImageData(pBuffer, nSize))
|
2012-04-08 14:16:29 +08:00
|
|
|
{
|
2013-07-25 21:37:12 +08:00
|
|
|
Texture2D::PixelFormat oldPixelFormat = Texture2D::getDefaultAlphaPixelFormat();
|
2013-06-20 14:13:12 +08:00
|
|
|
Texture2D::setDefaultAlphaPixelFormat(vt->_pixelFormat);
|
2013-07-26 17:34:44 +08:00
|
|
|
vt->_texture->initWithImage(pImage);
|
2013-06-20 14:13:12 +08:00
|
|
|
Texture2D::setDefaultAlphaPixelFormat(oldPixelFormat);
|
2012-04-08 14:16:29 +08:00
|
|
|
}
|
2013-07-26 17:34:44 +08:00
|
|
|
|
|
|
|
CC_SAFE_DELETE_ARRAY(pBuffer);
|
|
|
|
CC_SAFE_RELEASE(pImage);
|
2012-04-08 14:16:29 +08:00
|
|
|
}
|
2012-02-07 11:18:39 +08:00
|
|
|
break;
|
2012-04-19 14:35:52 +08:00
|
|
|
case kImageData:
|
|
|
|
{
|
2013-07-26 17:34:44 +08:00
|
|
|
vt->_texture->initWithData(vt->_textureData,
|
|
|
|
vt->_dataLen,
|
2013-06-15 14:03:30 +08:00
|
|
|
vt->_pixelFormat,
|
|
|
|
vt->_textureSize.width,
|
|
|
|
vt->_textureSize.height,
|
|
|
|
vt->_textureSize);
|
2012-04-19 14:35:52 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kString:
|
|
|
|
{
|
2013-07-16 16:33:02 +08:00
|
|
|
vt->_texture->initWithString(vt->_text.c_str(), vt->_fontDefinition);
|
2012-04-19 14:35:52 +08:00
|
|
|
}
|
|
|
|
break;
|
2012-04-24 15:02:18 +08:00
|
|
|
case kImage:
|
|
|
|
{
|
2013-07-16 16:33:02 +08:00
|
|
|
vt->_texture->initWithImage(vt->_uiImage);
|
2012-04-24 15:02:18 +08:00
|
|
|
}
|
|
|
|
break;
|
2012-04-19 14:35:52 +08:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2013-07-16 16:33:02 +08:00
|
|
|
vt->_texture->setTexParameters(vt->_texParams);
|
2011-11-25 10:48:05 +08:00
|
|
|
}
|
|
|
|
|
2013-07-16 16:33:02 +08:00
|
|
|
_isReloading = false;
|
2011-04-06 16:29:58 +08:00
|
|
|
}
|
|
|
|
|
2012-06-06 10:06:51 +08:00
|
|
|
#endif // CC_ENABLE_CACHE_TEXTURE_DATA
|
2011-04-06 16:29:58 +08:00
|
|
|
|
2012-03-20 15:04:53 +08:00
|
|
|
NS_CC_END
|
2010-07-16 16:28:11 +08:00
|
|
|
|