07.WebApp2.0時代啟程:倒立者贏,從CPU到GPU,一張圖片的旅行
緊接上文,終端開發使用的WindVane、wax、ReactNative等已經是一種跨平臺的技術,我們稱之為上層跨平臺,Cocos2d-x這種直接使用C/C++,我們成為底層跨平臺。上層跨平臺,提升開發效率;下層跨平臺,提升程式效能。
1. 為什麼Cocos2d-x效能比Native開發要好?
因為Cocos2d-X是遊戲引擎唄,人家是專業做遊戲特效的好不好,直接呼叫GPU的OpenGL繪圖的好不好。開啟Cocos2d-X程式碼,感觸最深的不是CCNode這些遊戲節點,cocos2d-x已經開始為App開發做準備了!
大家看到了什麼?UIImageView?UIScrollView?OMG!當我們還在糾結要不要學習一下游戲開發的時候,人家都已經開始做UI系統了,有木有!!
很長一段時間,我們以為Game和App之間有一種天然的鴻溝,長久以來,這兩種技術之間沒有直接的聯絡,App可以做各種UI控制元件,遊戲可以做各種UI特效,當遊戲開發者想NativeApp大步的走過來的時候,我們還在學習如何用UITableView去優化圖片的顯示,豈不知在遊戲世界裡,早已實現了圖片的非同步載入、解碼、快取了!
別忘了,從iOS7.0開始,蘋果已經預設整合了GameKit.framework,顫抖吧!遊戲和App即將面臨傻傻分不清楚的時候了。
2. 一張圖片在OpenGL的世界裡,是如何從SDCard顯示到螢幕
這一次,我們不是講UIImage如何載入一個圖片,而是自己讀取圖片檔案,解碼影像,上傳紋理,推送頂點緩衝,重新整理幀緩衝,一步步顯示圖片到螢幕上。
上圖之前,我們先了解幾個概念:
2.1 幀緩衝:
我們要顯示的檢視繪製到螢幕上,本質是一個int32 buffer[width x height x 4]的一個陣列,不管2D還是3D,我們都要吧GPU的物件投影到這個buffer中,一個畫素4位元組表示[R/G/B/A]。
2.2 頂點緩衝:
終端裝置,只支援精簡版的OpenGL,稱之為OpenGL ES,這裡我們只用2.0版本。在這個版本中,我們只能畫三角形,一個正方形的圖片紋理,需要兩個三角形才可以描述:
我們要畫圖片之前,就要告訴OpenGLES圖片頂點的位置,先忽略三角形的順序問題,就這樣告訴GPU:【ABDBDC】,同時設定當前的啟用的紋理,就可以繪製圖片了。
2.3 上傳紋理:
現在絕大多數手機,都有獨立的顯示卡晶片,每個顯示卡晶片都有獨立的視訊記憶體,這麼就清楚了:我們常說的記憶體是CPU直接操控的記憶體條容量,算上視訊記憶體,就有兩個快取空間了,程式設計師可以發揮的空間又多了,是不是有點小激動?
2.4 圖片解碼:
常見的jpg、png等圖片檔案,都是壓縮後的檔案,如果不壓縮,一張1024×1024的圖片,至少需要4M空間。一張圖片檔案解碼到記憶體,需要解碼器來解碼,不幸的是,Android很多機型沒有整合硬體解碼,慶幸的是iOS裝置內部支援硬體解碼。如果一張圖片檔案可以瞬間在底層硬體解碼完成,要比上層程式碼優化,更加直接!這就是我們為什麼一直追求的是底層跨平臺的原因。
2.5 講了很多概念了,我們上圖吧
3. 原來圖片可以不佔用記憶體!
我們用C的API來讀取一張圖片,用libpng來解碼一張png圖片,呼叫OpenGL ES2.0API上傳圖片到GPU視訊記憶體,指定繪圖座標,顯示一張圖片,我們上程式碼吧:
一)讀取檔案:
AEFileData AEFileDataWithPathfile(std::string& pathfile) {
AEFileData data;
FILE* file = fopen(pathfile.c_str(), "rb");
if (file == NULL) {
return data;
}
fseek(file, 0, SEEK_END);
data.size = (GLuint)ftell(file);
fseek(file, 0, SEEK_SET);
data.bytes = (GLchar*)malloc(sizeof(GLchar) * data.size);
fread(data.bytes, sizeof(GLchar), data.size, file);
fclose(file);
return data;
}
二)解碼圖片:
為了跨平臺通用,我們用libpng來解碼圖片,獲取png的format和rgba陣列,【程式碼太多,就不貼了】
三)上傳GPU:
glGenTextures(1, &_textureId);
glBindTexture(type, _textureId);
glTexImage2D(type, 0, _format, _size.width, _size.height, 0, _format, GL_UNSIGNED_BYTE, pixels);
四)釋放記憶體
AEFileData file = AEFileDataWithPathfile(pathfile);
this->initPNG((GLuchar*)file.bytes, file.size);
__FREE(file.bytes);
五)指定頂點索引,並繪製
vb[0] = {d11, t11, color};
vb[1] = {d21, t21, color};
vb[2] = {d12, t12, color};
vb[3] = {d21, t21, color};
vb[4] = {d12, t12, color};
vb[5] = {d22, t22, color};
glDrawArrays(GL_TRIANGLES, 0, _vertexIndex);
4. 對比iOS與Android,圖片的渲染和展示做了什麼?
Android的技術框架更偏於上層,看不到底層C/C++程式碼實現,優化的空間有限;Objective-C更靠近C語言,優化的空間明顯。下面我們以iOS為例:
5. 主流框架的圖片優化思路
要讀懂圖片的展示與優化,就要看看當前主流的圖片優化框架,他們是如何做優化的?主流的ImageCache框架:SDWebImage/FastImageCache/TBImageCache
左邊的是SDWebImage,中間是FastImageCache,右邊是TBImageCache
SDWebImage:
將解碼後的圖片分別儲存到記憶體和SDCard,將複雜的操作放到了子執行緒中;
FastImageCache:
直接將檔案讀取到mmap對映的檔案中,減少了一次從系統核心到使用者空間的一次拷貝【然並卵】
TBImageCache:
在首次載入圖片時,根據使用者需要,對圖片增加圓角陰影處理,增加了非同步執行緒的處理的工作量,同時儲存到sdcard中,消耗了額外的資源,換取的是更好的圖片效能【解決了圓角陰影在終端的痛點】
圖片快取框架,只是讓CPU負載曲線更均衡
,有沒有可能再進一步優化?
6. 遊戲世界與App世界對比
CPU與GPU是終端的雙駕馬車,缺一不可,CPU load過高,導致終端卡頓,同理GPU也是如此。iOS的UIKit框架封裝的太好了,也是優化做的最好的,讓我們看不到底層的原理,看不到iOS是如何平衡CPU、GPU、RAM、GPU RAM的4者之間的關係,讓我們大膽大猜測一下吧:
一)UIImageView管理者UIImage物件
UIImage就是記憶體中得bitmap陣列,當我們需要繪製一張圖片的時候,CPU發出指令,上傳BitMap到GPU的視訊記憶體中,GPU返回生成的紋理ID給CPU。
二)為了提高iOS的效能,蘋果做出了一個優化策略,不釋放CPU的bitmap
當GPU視訊記憶體空間不足的時候,iOS會釋放一部分圖片紋理,以節省視訊記憶體空間,但不會釋放記憶體的bitmap。當一張圖片重新需要繪製的時候,iO快速從記憶體bitmap快速上傳到視訊記憶體,完成繪製過程。
三)當記憶體的bitmap釋放的時候,同步釋放視訊記憶體的紋理物件
本質意義上說,bitmap在記憶體中得儲存,不是真正顯示的那個快取,當bitmap上傳到GPU的時候,GPU會在視訊記憶體中申請同樣大小的空間,來存放bitmap,並返回紋理ID,每當CPU需要顯示某張圖片的時候,只需要高速GPU要繪製那個紋理ID就可以了。
四)上傳GPU後,我們完全可以釋放記憶體bitmap
釋放記憶體的bitmap很簡單: free(bitmap);
這樣記憶體裡只剩下了對應的GPU紋理的ID,當GPU記憶體緊張的時候,程式要要自己釋放不需要的紋理ID,如果這個ID在某個時候有需要使用了,怎麼辦?那要把之前的讀取檔案到上傳GPU重走一遍,太耗效能了,我想蘋果也是不想看到這個原因,才不會輕易釋放bitmap的
總結:遊戲的架構模式,需要開發者自己優化所有場景
遊戲開發者不僅需要優化CPU記憶體,也要優化GPU記憶體,通過將所有圖片合成一張大的紋理圖片,來統一管理GPU記憶體,需要把大量浮點數運算根據不同的場景,或者交給CPU或者交給GPU,讓CPU和GPU的負載達到均衡,各司其職。
這一些優化,對於我們做系統上層應用的無線開發,是很難想象有多少坑等著我們,終端App的開發,還是使用上層框架好,圖形影像的世界水很深,有想法的同學,可以繼續關注下一章分享,想了解我們的同學,可以用Android下載安裝我們的Demo:
下一章,我們一起見證所及即所得的開發方式,WebApp2.0初體驗
相關文章
- 一張圖弄清Activity的啟動過程
- 請教一個程式執行時,從磁碟到記憶體到CPU的過程?記憶體
- Tomcat啟動過程(一):從BootStrap到EndPonitTomcatboot
- 10 張圖開啟 CPU 快取一致性的大門快取
- java生成一張圖片Java
- 從顯示一張圖片開始學習OpenGL ES
- php將兩張身份證圖片合併到一張圖PHP
- Android儲存多張圖片到本地Android
- GPU和CPU的區別GPU
- CPU和GPU的區別GPU
- iOS CPU VS GPUiOSGPU
- 一張圖片入門PythonPython
- 從一片森林(JavaScript)到另一片森林(C++)JavaScriptC++
- unity GPU bound or CPU boundUnityGPU
- gpu是什麼 gpu和cpu的區別介紹GPU
- GPU程式設計--CPU和GPU的設計區別GPU程式設計
- 無需下載軟體怎麼將多張圖片組合成一張圖片
- 實現簡單的輪播圖(單張圖片、多張圖片)
- CSS將背景圖片集中在一張圖片上CSS
- CPU GPU設計工作原理GPU
- 物聯網時代到來,Java程式設計師躺贏!Java程式設計師
- 攜程旅行 abtest
- Tomcat啟動過程(三):從SocketProcessor到ContainerTomcatAI
- HTML5利用canvas,把多張圖合併成一張圖片HTMLCanvas
- 從GPU製造者到伺服器提供商:英偉達的計算平臺轉型GPU伺服器
- 從理論去分析一張圖
- vue生命週期鉤子,一張圖片Vue
- 從“閃電戰”到全面戰:榮耀開啟“嚇人的技術”2.0時代
- CPU Turbo和GPU Turbo的區別 CPU Turbo是什麼意思?GPU
- Unity效能分析(二)CPU/GPU分析UnityGPU
- Qt獲取一張圖片的平均色(主色調)QT
- 用一張圖片,黑客就能黑掉你的電腦黑客
- 一個APP從啟動到主頁面顯示經歷了哪些過程?APP
- ubuntu,從新建一個使用者,到轉到新建使用者的命令列操作Ubuntu命令列
- 倒立擺模型分析模型
- 一張圖看懂MIUI大版本更新 從MIUI V1到MIUI8UI
- 攜程旅行web逆向Web
- 從資料庫中的表取幾張圖片,用flash形式動態的顯示圖片資料庫