Cocos2d-x與OpenGL底層的感想


1.為什么會卡頓

這篇文章想寫一些工作經常碰到的一些問題,為什么我做一個2D游戲,渲染100多個精靈就會卡。

他們同樣是做2D游戲,為什么渲染那么多東西幀數非常高,一點卡頓的樣子都沒有?

這里我們排除一些邏輯因素,在相同游戲邏輯復雜度下。我每幀也沒什么邏輯運算也還是卡,這里我們首先排除掉CPU對於游戲幀數瓶頸的限制。我們來談下,為什么都是2D游戲,我渲染那么少的東西就會卡。其他游戲2D大作渲染那么多東西還是很流暢!

2.OpenGL問題

OpenGL是個非常老舊的渲染API,API層非常單薄,僵硬,程序員能控制的東西非常少,無非就是資源的生成,控制一些渲染緩存的狀態,調用Draw接口。其實在現在硬件變革好幾代之后API還是那樣子,已經跟不上硬件的速度。而且手機游戲中為了兼容性問題,一般游戲都是用OpenGLES2.0這個版本,造成對於硬件性能的浪費。

打個比方如果一台手機可以本來的渲染性能可以渲染基准性能是100分,但是圖像接口的問題和驅動層優化問題只能發揮到70分,渲染引擎設計問題繼續砍半只能發揮40分,如果程序員對於引擎不夠了解只能發揮到30。這樣就造成貌似機器性能在飛速發展,但是有些游戲的畫面提升不是很快。

當我們無法改變圖形接口和驅動的時候,我們能否把可以控制的70分給壓榨出來,就是考驗一個游戲廠商的實力(大廠商跟硬件公司合作非常密切,而且單一平台上對於優化更好,所以PS4和XBOX的游戲畫面一直比單機好點。很多硬件廠商會為了某個游戲還會去優化顯卡驅動,他們示可以拿到80分甚至90分的性能,小公司是沒有那個待遇的)。

 

其他設計硬件和渲染管線問題以后提。這里簡單說明單純的渲染API的調用,這個也是最好理解的。OpenGL是狀態機設計模式,每次調用一個API都會去改變渲染管線中某個功能開關。我們每次調用一個API,就會生成一個指令。這個指令告訴顯卡怎么去做,這邊是有性能損失的。(API調用OpenGL渲染是異步的過程,在緩沖區中會維護一個渲染命令隊列,在某個時機去發送給驅動層怎么做,驅動做的事情以后再說。這個時機可以我們手動去觸發glFlush強制發送,但是我們一般沒有需求不推薦。我們這里可以理解調用OpenglAPI成同步的。)所以盡量的少調用OpenGL API渲染性能就上去了。說了也是白說,我要畫那么多東西,當然要多調接口了,不調接口我怎么畫。

這邊就引出一個問題,我怎么少調用API,但是可以多畫東西呢?批次渲染是最通用也是最簡答的做法!

 

3.批次渲染概念

如果我們把一次渲染比作一群人出游,在一個停車場中。大家准備排隊上車。每輛順序出口出去。但是停車場有個奇怪的規定:相鄰穿相同顏色衣服的人可以坐同一輛車出去,如果后面一個如果穿不同顏色衣服人,那么就得新配一輛車。假如100個人穿100種顏色的衣服,那么就得100輛車,如果100穿相同顏色的衣服話只要一輛車。如果排隊順序是A.B.C.D  AB如果都穿紅色站一起一輛車,C綠一輛車,D紅色一輛車。這樣就三輛車。如果調換下ABCD順序變成 A.B.D.C, 這樣就變成兩輛車。車越少,那么全部離開停車場越短,更快到目的地旅行了。批次渲染就是勁量讓穿相同顏色人,塞進同一部車中。更快的出去

在一般的游戲引擎中,我們都會有一個渲染的隊列,每個渲染單元抽樣成一個渲染命令。每個命令都會去調用OpenGL API。

如果兩個渲染命令一起的畫,我們是否可以把兩個命令合成一個命令呢?因為OpenGL是狀態機啊,第一個命令狀態設置后,第二個命令無需重新去設置渲染狀態。這樣不就少調用API 但是畫一樣的多東西!這個就是批次渲染的概念。

我們今天只談下2D游戲,在2D游戲每個渲染命令涉及到OpenGL API其實比較少了,設置Shader  綁定TextureDrawElements 差不多就這三樣,

一般精靈的Shader是一樣的,這個我們是可以合並的。這個比較簡單。

Texture紋理,每個精靈都有自己的紋理,沒辦法合並啊!其實有辦法,把兩張小圖拼成一張大圖,設置頂點數據的UV坐標!搞定這個也可以合並。

DrawElement 這里我們渲染圖元基本都是三角形,如果把三角形的頂點數據放在一個VBO中。那么也是可以合並。

 

4.coco2d-x中的批次渲染

cocos2d 最早以前有個類型叫BatchNode,這個類型地下綁定精靈如果是同一張紋理的就會生成一個批次渲染,但是這個不是很好用,如果美術改了資源的,有個子節點不是用大圖里面紋理會出問題。后面3.X版本,cocos2d-x推出一個技術叫(Auto-batching)、在深度優先的子節點遍歷情況,相鄰節點如果材質相同就自動合並批次。所以貼圖相同的精靈我們是可以合並批次的。

 

5.實際測試

第一個基准測試,我們上面都不渲染,只是單純控制Cocos2d-x不到滿幀,等下好對比。

const int kDrawNodeNum = 6000;

void TestScene::DrawEmptyNode()
{
    for (int nIndex = 0; nIndex < kDrawNodeNum; ++nIndex){
        for (int nIndexColor = 0; nIndexColor < 3; ++nIndexColor){        
            Node* pNode = Node::create();
            this->addChild(pNode);
        }
    }
}

 

基准測試

第二個我們是使用三張分開圖的測試。

void TestScene::DrawPartSprite()
{
    Size size = Director::getInstance()->getVisibleSize();
    char* spritename[] = { "green.png", "red.png", "blue.png" };
    for (int nIndex = 0; nIndex < kDrawNodeNum; ++nIndex){
        for (int nIndexColor = 0; nIndexColor < 3; ++nIndexColor){
            Sprite* pIcon = Sprite::create(spritename[nIndexColor]);
            pIcon->setAnchorPoint(Vec2::ZERO);
            pIcon->setPosition(Vec2(200 + (nIndex * 20 + nIndexColor*10  ) % int(size.width), 200 + (nIndex * 20 + nIndexColor) % int(size.height)));
            this->addChild(pIcon);
        }
    }
}

 

 

分圖測試

第三個我們使用一張合並圖,其實里面東西都一樣的。

void TestScene::DrawTogetherSprite()
{
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("color.plist");

    Size size = Director::getInstance()->getVisibleSize();
    char* spritename[] = { "green.png", "red.png", "blue.png" };

    for (int nIndex = 0; nIndex < kDrawNodeNum; ++nIndex){
        for (int nIndexColor = 0; nIndexColor < 3; ++nIndexColor){
            Sprite* pIcon = Sprite::createWithSpriteFrameName(spritename[nIndexColor]);
            pIcon->setAnchorPoint(Vec2::ZERO);
            pIcon->setPosition(Vec2(200 + (nIndex * 20 + nIndexColor*10) % int(size.width), 200 + (nIndex * 20 + nIndexColor) % int(size.height)));
            this->addChild(pIcon);
        }
    }
}

 

合圖測試

資源就這樣,非常簡單

image

非常簡單的三個圖片,分開渲染是三張分開的圖片,合並渲染是用一個合圖。

其實代碼基本相同。為什么性能竟然有那么大的差距。如果除去CPU性能就是基本性能的消耗。在極端的情況下,我們基本可以達到三倍的提升,三倍其實是個很誇張的數據了。

如果游戲中,如果游戲中精靈批次命中稍微高點,即使對於性能提高10%到20% 對於性能的優化也是有好處的,而你所需要做的可能就是選擇好圖片去合並成一張大圖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM