COCOS2DX引擎深入四————圖片的異步加載

遊戲剛啓動時,最常見的畫面就是進度條了。而進度條展示的都是資源的加載進度。包括圖片資源的。


cocos2dx引擎的整體流程都是基於單線程的,但是加載圖片資源是個很耗時的操作,如果放在主線程中執行,很容易讓畫面卡頓。

所以cocos2dx提供了異步加載圖片資源的方法。先看看test裏怎麼用的,再看看具體如何實現。


首先就是加載你的資源

TextureCacheTest::TextureCacheTest()
: _numberOfSprites(20)
, _numberOfLoadedSprites(0)
{
    auto size = Director::getInstance()->getWinSize();

    _labelLoading = Label::createWithTTF("loading...", "fonts/arial.ttf", 15);
    _labelPercent = Label::createWithTTF("%0", "fonts/arial.ttf", 15);

    _labelLoading->setPosition(Vec2(size.width / 2, size.height / 2 - 20));
    _labelPercent->setPosition(Vec2(size.width / 2, size.height / 2 + 20));

    this->addChild(_labelLoading);
    this->addChild(_labelPercent);

    // load textrues
    Director::getInstance()->getTextureCache()->addImageAsync("Images/HelloWorld.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_01.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_02.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_03.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_04.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_05.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_06.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_07.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_08.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_09.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_10.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_11.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_12.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_13.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/grossini_dance_14.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/background1.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/background2.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/background3.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
    Director::getInstance()->getTextureCache()->addImageAsync("Images/blocks.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
}

然後通過註冊回調函數,將進度展現到你的遊戲當中

void TextureCacheTest::loadingCallBack(cocos2d::Texture2D *texture)
{
    ++_numberOfLoadedSprites;
    char tmp[10];
    sprintf(tmp,"%%%d", (int)(((float)_numberOfLoadedSprites / _numberOfSprites) * 100));
    _labelPercent->setString(tmp);

    if (_numberOfLoadedSprites == _numberOfSprites)
    {
        this->removeChild(_labelLoading, true);
        this->removeChild(_labelPercent, true);
        addSprite();
    }
}

整個過程是異步的,不會讓遊戲產生卡頓的情況。


再看看具體是怎麼實現的。


void TextureCache::addImageAsync(const std::string &path, const std::function<void(Texture2D*)>& callback)
{
    Texture2D *texture = nullptr;

    std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);

    auto it = _textures.find(fullpath);
    if( it != _textures.end() )
        texture = it->second;

    if (texture != nullptr)
    {
        callback(texture);
        return;
    }

    // lazy init
    if (_asyncStructQueue == nullptr)
    {             
        _asyncStructQueue = new queue<AsyncStruct*>();
        _imageInfoQueue   = new deque<ImageInfo*>();        

        // create a new thread to load images
        _loadingThread = new std::thread(&TextureCache::loadImage, this);

        _needQuit = false;
    }

    if (0 == _asyncRefCount)
    {
        Director::getInstance()->getScheduler()->schedule(schedule_selector(TextureCache::addImageAsyncCallBack), this, 0, false);
    }

    ++_asyncRefCount;

    // generate async struct
    AsyncStruct *data = new AsyncStruct(fullpath, callback);

    // add async struct into queue
    _asyncStructQueueMutex.lock();
    _asyncStructQueue->push(data);
    _asyncStructQueueMutex.unlock();

    _sleepCondition.notify_one();
}

首先先在texture緩存裏查找,如果找到了,說明之前加載進來了,直接調用回調。沒找到則另開一個線程加載圖片,並將兩個圖片路徑和回調加入隊列_asyncStructQueue中

void TextureCache::loadImage()
{
    AsyncStruct *asyncStruct = nullptr;

    while (true)
    {
        std::queue<AsyncStruct*> *pQueue = _asyncStructQueue;
        _asyncStructQueueMutex.lock();
        if (pQueue->empty())
        {
            _asyncStructQueueMutex.unlock();
            if (_needQuit) {
                break;
            }
            else {
                std::unique_lock<std::mutex> lk(_sleepMutex);
                _sleepCondition.wait(lk);
                continue;
            }
        }
        else
        {
            asyncStruct = pQueue->front();
            pQueue->pop();
            _asyncStructQueueMutex.unlock();
        }        

        Image *image = nullptr;
        bool generateImage = false;

        auto it = _textures.find(asyncStruct->filename);
        if( it == _textures.end() )
        {
           _imageInfoMutex.lock();
           ImageInfo *imageInfo;
           size_t pos = 0;
           size_t infoSize = _imageInfoQueue->size();
           for (; pos < infoSize; pos++)
           {
               imageInfo = (*_imageInfoQueue)[pos];
               if(imageInfo->asyncStruct->filename.compare(asyncStruct->filename) == 0)
                   break;
           }
           _imageInfoMutex.unlock();
           if(infoSize == 0 || pos == infoSize)
               generateImage = true;
        }

        if (generateImage)
        {
            const std::string& filename = asyncStruct->filename;
            // generate image      
            image = new Image();
            if (image && !image->initWithImageFileThreadSafe(filename))
            {
                CC_SAFE_RELEASE(image);
                CCLOG("can not load %s", filename.c_str());
                continue;
            }
        }    

        // generate image info
        ImageInfo *imageInfo = new ImageInfo();
        imageInfo->asyncStruct = asyncStruct;
        imageInfo->image = image;

        // put the image info into the queue
        _imageInfoMutex.lock();
        _imageInfoQueue->push_back(imageInfo);
        _imageInfoMutex.unlock();
    }
    
	if(_asyncStructQueue != nullptr)
    {
        delete _asyncStructQueue;
	    _asyncStructQueue = nullptr;
        delete _imageInfoQueue;
	    _imageInfoQueue = nullptr;
    }
}

這個線程在取出_imageInfoQueue隊列裏每個圖片的路徑信息,從而構建圖片資源信息。因爲是在另一個線程裏進行的,所以不會卡頓。接下來就剩把圖片信息傳給opengl了。而這個過程耗時就在這,把大量的圖片信息一次性傳給opengl,這是相當耗時的操作,這會是畫面出現卡頓。所以cocos2dx採用每一幀讓opengl處理一張圖片的方法,

void TextureCache::addImageAsyncCallBack(float dt)
{
    // the image is generated in loading thread
    std::deque<ImageInfo*> *imagesQueue = _imageInfoQueue;

    _imageInfoMutex.lock();
    if (imagesQueue->empty())
    {
        _imageInfoMutex.unlock();
    }
    else
    {
        ImageInfo *imageInfo = imagesQueue->front();
        imagesQueue->pop_front();
        _imageInfoMutex.unlock();

        AsyncStruct *asyncStruct = imageInfo->asyncStruct;
        Image *image = imageInfo->image;

        const std::string& filename = asyncStruct->filename;

        Texture2D *texture = nullptr;
        if (image)
        {
            // generate texture in render thread
            texture = new Texture2D();

            texture->initWithImage(image);

#if CC_ENABLE_CACHE_TEXTURE_DATA
            // cache the texture file name
            VolatileTextureMgr::addImageTexture(texture, filename);
#endif
            // cache the texture. retain it, since it is added in the map
            _textures.insert( std::make_pair(filename, texture) );
            texture->retain();

            texture->autorelease();
        }
        else
        {
            auto it = _textures.find(asyncStruct->filename);
            if(it != _textures.end())
                texture = it->second;
        }
        
        if (asyncStruct->callback)
        {
            asyncStruct->callback(texture);
        }
        
        if(image)
        {
            image->release();
        }       
        delete asyncStruct;
        delete imageInfo;

        --_asyncRefCount;
        if (0 == _asyncRefCount)
        {
            Director::getInstance()->getScheduler()->unschedule(schedule_selector(TextureCache::addImageAsyncCallBack), this);
        }
    }
}

這個回調在addImageAsync第一次被調用時就被註冊了,定時器的fps跟主線程的一樣。   texture->initWithImage(image);執行的就是glTexImage2D這個耗時的操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章