從OpenGL再說(shuō)離屏渲染

離屏渲染應(yīng)該是所有iOS開(kāi)發(fā)者繞不開(kāi)的話題,關(guān)于離屏渲染的文章也有很多。objc.io 的文章繪制像素到屏幕上說(shuō)過(guò):
一般情況下票腰,你需要避免離屏渲染,因?yàn)檫@是很大的消耗女气。直接將圖層合成到幀的緩沖區(qū)中(在屏幕上)比先創(chuàng)建屏幕外緩沖區(qū)杏慰,然后渲染到紋理中,最后將結(jié)果渲染到幀的緩沖區(qū)中要廉價(jià)很多炼鞠。因?yàn)檫@其中涉及兩次昂貴的環(huán)境轉(zhuǎn)換(轉(zhuǎn)換環(huán)境到屏幕外緩沖區(qū)缘滥,然后轉(zhuǎn)換環(huán)境到幀緩沖區(qū))。
之前不懂GPU的工作原理簇搅,不懂OpenGL/Metal這些底層繪制API完域,對(duì)這一段話理解的非常模糊,后來(lái)學(xué)了下OpenGL和Metal瘩将,再結(jié)合之前看的文章和自己的理解吟税,對(duì)離屏渲染(Offscreen Rendering)做一次梳理。有些地方是我自己的理解姿现,不見(jiàn)得正確肠仪。先拋出下面的問(wèn)題:
1、到底什么是離屏渲染备典?是在GPU上面還是CPU上面執(zhí)行的咪辱?
2饲嗽、為什么要有離屏渲染?什么情況下會(huì)產(chǎn)生離屏渲染?
2吮旅、幀緩沖區(qū)是什么?當(dāng)前屏幕緩沖區(qū)和屏幕外緩沖區(qū)又是什么被廓?
3党觅、切換緩沖區(qū)是什么操作?真的比較耗時(shí)嗎倚喂?

什么是離屏渲染?

2014年的WWDC Advanced Graphics and Animations for iOS Apps 對(duì)Core Animation的渲染機(jī)制做了詳細(xì)解釋?zhuān)?br>

圖1

現(xiàn)今移動(dòng)設(shè)備GPU都是采用Tile Based Rendering方式繪制每篷,關(guān)于Tile Based Rendering在這篇文章有詳細(xì)描述 Performance Tuning for
Tile-Based Architectures

Core Animation打包圖層和動(dòng)畫(huà)信息到Render Server(這是一個(gè)單獨(dú)的進(jìn)程,所有app都會(huì)與這個(gè)進(jìn)程通信以完成最終的繪制)焦读,由Render Server調(diào)用OpenGL/Metal指令子库,最終在GPU上面完成繪制。而在GPU上面的工作對(duì)于了解OpenGL的人比較熟悉了矗晃,頂點(diǎn)著色器(Vertex Shader)和GPU tiling一起構(gòu)成Tiler操作仑嗅,然后通過(guò)片元著色器(Pixel Shader)做Renderer操作,最后輸出到渲染緩存(Render Buffer)中喧兄。這對(duì)應(yīng)一個(gè)渲染管線的完整流程无畔,實(shí)際上渲染管線還包括諸如光柵化,剔除吠冤,混合等操作浑彰,在上面的示意圖中省略了。對(duì)于普通的屏幕內(nèi)渲染拯辙,GPU只有一個(gè)Rendering Pass郭变。
圖2

對(duì)于離屏渲染,就存在多個(gè)Rendering Pass了涯保。上面是Masking操作的示意圖诉濒,一共有3步操作,對(duì)應(yīng)3個(gè)Rendering Pass夕春。最后的Compisiting pass輸出到最后的幀緩存未荒,是屏幕內(nèi)渲染,而前面的pass1和pass2是繪制到texture供最后一個(gè)pass所用及志,即離屏渲染片排。

離屏渲染在GPU上面執(zhí)行還是在CPU上面執(zhí)行?

前面的圖2很明確指出離屏渲染是在GPU上面執(zhí)行的,但是有很多文章說(shuō)CPU上面也會(huì)有離屏渲染速侈,比如使用Core Graphics繪制的時(shí)候率寡。Apple提供了檢測(cè)離屏渲染的工具:Color Off-screen Rendered

圖3

我重寫(xiě)UIView的drawRect方法(使用Core Graphics繪制),用Color Off-screen Rendered檢測(cè)(iOS12模擬器)沒(méi)有離屏渲染倚搬。因此嚴(yán)格來(lái)說(shuō)CPU渲染不應(yīng)該算作離屏渲染冶共,離屏渲染發(fā)生在GPU上面。而且CPU渲染導(dǎo)致的卡頓和GPU的離屏渲染導(dǎo)致的卡頓原理完全不一樣每界,在做性能優(yōu)化的時(shí)候應(yīng)該區(qū)別對(duì)待捅僵。
Core Graphics做繪制的時(shí)候,會(huì)有上下文Context眨层,也有一個(gè)Bitmap畫(huà)布命咐,但是這個(gè)Bitmap畫(huà)布是在CPU內(nèi)存上面的,上下文Context也和上面說(shuō)的環(huán)境轉(zhuǎn)換:昂貴的環(huán)境轉(zhuǎn)換(轉(zhuǎn)換環(huán)境到屏幕外緩沖區(qū)谐岁,然后轉(zhuǎn)換環(huán)境到幀緩沖區(qū))不是一碼概念。因此把CPU繪制歸為離屏渲染個(gè)人感覺(jué)非常不妥。
UIKit 早期成員 Andy Matuschak伊佃,在一篇回復(fù)中有這樣一段話:
In particular, a few (implementing drawRect and doing any CoreGraphics drawing, drawing with CoreText [which is just using CoreGraphics]) are indeed “offscreen drawing,” but they’re not what we usually mean when we say that. They’re very different from the rest of the list. When you implement drawRect or draw with CoreGraphics, you’re using the CPU to draw, and that drawing will happen synchronously within your application. You’re just calling some function which writes bits in a bitmap buffer, basically.

離屏渲染性能不好在哪里窜司?

Advanced Graphics and Animations for iOS Apps 這個(gè) session 以UIVisualEffectView為例描述GPU的處理邏輯,這里有五個(gè)Rendering Pass航揉,上面藍(lán)色為T(mén)iler操作的時(shí)間分布塞祈,紅色對(duì)應(yīng)Renderer操作。實(shí)際上GPU時(shí)間大部分都花在Renderer操作上面帅涂,同樣最后一個(gè)Rendering Pass是屏幕內(nèi)渲染议薪,那么UIVisualEffectView存在4個(gè)屏幕外的Rendering Pass。Rendering Pass之間還還存在黃色的Idle Time媳友,這個(gè)就是環(huán)境轉(zhuǎn)換(Context Switch)的時(shí)間斯议,一個(gè)Context Switch大概占用0.1ms-0.2ms的時(shí)間,那么UIVisualEffectView的所有Rendering Pass會(huì)累積0.5-1.0ms的Idle Time醇锚,這個(gè)在16.67ms的幀時(shí)間內(nèi)還是相當(dāng)大的哼御。因此離屏渲染性能不好在于:
1、更多的Rendering Pass焊唬,GPU運(yùn)算量增大恋昼;
2、Rendering Pass之間的Context Switch導(dǎo)致的Idle Time赶促。

圖4

為什么要有離屏渲染?

離屏渲染既然不好液肌,為什么它還存在?這要從OpenGL/Metal和GPU說(shuō)起鸥滨,GPU有少量的邏輯處理單元和大量的核心嗦哆,CPU則相反。CPU適合做邏輯運(yùn)算爵赵,復(fù)雜的運(yùn)算吝秕,而GPU適合做簡(jiǎn)單運(yùn)算,大量重復(fù)運(yùn)算空幻。對(duì)于Tiler中的大量頂點(diǎn)運(yùn)算烁峭,Renderer中的著色混合等都適合在GPU上面并行運(yùn)算。但是GPU不適合做邏輯運(yùn)算秕铛,所以一次只能繪制簡(jiǎn)單的圖元(Primitives)约郁,對(duì)應(yīng)到OpenGL中就是GL_POINTS、GL_LINES但两、GL_LINE_STRIP鬓梅、GL_TRIANGLES、GL_TRIANGLES_STRIP谨湘,比如一個(gè)CALayer就由兩個(gè)三角形(GL_TRIANGLES)組成绽快,繪制普通Layer的時(shí)候GPU只需要一個(gè)Render Pass即可芥丧。而mask效果是將一個(gè)層作為“形狀”來(lái)繪制另一個(gè)層,而這個(gè)“形狀”是無(wú)法通過(guò)點(diǎn)坊罢,線续担,三角形這些基本圖元來(lái)描述的,因此mask效果無(wú)法用GPU一步繪制出來(lái)活孩,但是可以多步組合繪制出來(lái)物遇,如上圖2描述的三個(gè)Rendering Pass組合繪制mask效果。
除此之外憾儒,我們知道CALayer的shouldRasterize屬性可以強(qiáng)制離屏渲染询兴。Advanced Graphics and Animations for iOS Apps 這樣描述:

圖5

Rasterization會(huì)使用GPU將多個(gè)Layer繪制到一個(gè)image(Texture)中,并且這個(gè)image是會(huì)緩存的起趾,以便后續(xù)直接使用緩存進(jìn)行渲染诗舰。在Rendering階段,存在一個(gè)顏色混合(https://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/03%20Blending/)的操作阳掐,即對(duì)應(yīng)到每一個(gè)像素點(diǎn)始衅,在繪制的時(shí)候都會(huì)取rendertBuffer中的原有顏色與當(dāng)前顏色按照指定公式計(jì)算得到顏色值。對(duì)應(yīng)到OpenGL就是:

glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

這個(gè)操作在GPU上面是比較耗時(shí)的(因?yàn)樵谝苿?dòng)平臺(tái)GPU上缭保,取rendertBuffer中取顏色是消耗操作)汛闸。如果CALayer樹(shù)結(jié)構(gòu)比較復(fù)雜,圖層眾多艺骂,GPU每一幀都得混合所有的層诸老,導(dǎo)致GPU消耗巨大。Rasterization的作用在于可以指定用GPU混合一些復(fù)雜的CALayer成Texture钳恕,后續(xù)直接使用别伏,從而避免GPU的混合消耗∮嵌睿可見(jiàn)離屏渲染不一定降低性能厘肮,有時(shí)候還可以優(yōu)化性能。注意圖4指明Rasterization會(huì)增加內(nèi)存消耗睦番,同時(shí)只適合在圖層內(nèi)容變化不頻繁的場(chǎng)景类茂。

幀緩沖區(qū)是什么?

幀緩沖區(qū)(Frame Buffer)在OpenGL和Metal里面是最基礎(chǔ)的概念托嚣,可以理解為一塊內(nèi)存畫(huà)布巩检,類(lèi)似于Core Graphics的畫(huà)布一樣。對(duì)于屏幕內(nèi)渲染示启,會(huì)將畫(huà)布的內(nèi)容輸出到屏幕上兢哭,指定目標(biāo)為Render Buffer,在OpenGL中用glFramebufferRenderbuffer來(lái)指定:

glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sampleColorRenderbuffer);
glDrawArrays 或者 glDrawElements

對(duì)于離屏渲染夫嗓,畫(huà)布的內(nèi)容輸出到Texture上迟螺,使用glFramebufferTexture2D指定冲秽,比如:

// rendering pass1
glGenFramebuffers(1, &framebuffer1);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer1);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
glDrawArrays 或者 glDrawElements

// rendering pass2
glGenFramebuffers(1, &framebuffer2);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer2);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2, 0);
glDrawArrays 或者 glDrawElements

...

// final rendering pass
glBindFramebuffer(GL_FRAMEBUFFER, framebufferFinal);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sampleColorRenderbuffer);
// 

glGenFramebuffers為開(kāi)辟幀緩沖區(qū),glBindFramebuffer為切換幀緩沖區(qū)煮仇,對(duì)于離屏渲染會(huì)有更多的內(nèi)存分配和切換操作劳跃。從上面分析離屏消耗消耗主要在于:
1、glGenFramebuffers導(dǎo)致更多的內(nèi)存消耗浙垫;
2、更多的glDrawArrays和glDrawElements導(dǎo)致GPU有更多繪制操作郑诺;
3夹姥、切換緩沖區(qū)帶來(lái)的消耗:實(shí)際上glBindFramebuffer命令并不會(huì)有過(guò)多的消耗,根據(jù)Andy Matuschak辙诞,在回復(fù)中的原話:It’s expensive for the GPU to switch contexts from on-screen to off-screen drawing (it must flush its pipelines and barrier)辙售,意思是離屏渲染的Context切換會(huì)導(dǎo)致渲染管線的flush操作,眾所周知OpenGL中的glFlush操作是非常昂貴的飞涂,所以我理解這才是Context切換昂貴的最終原因旦部。

為什么Context切換會(huì)導(dǎo)致flush

先從表面看圖2,mask效果需要3個(gè)renderIng pass较店,只有最后一個(gè)rendering pass是輸出到屏幕顯示士八,而前面兩個(gè)rendering pass都是渲染出Texture作為最后一個(gè)的輸入,而最后一個(gè)rendering pass要想獲得正確的結(jié)果梁呈,前面的rendering pass必須先完成婚度。GPU是多核并行計(jì)算,而這種依賴(lài)關(guān)系導(dǎo)致rendering pass無(wú)法真正并行執(zhí)行官卡。
實(shí)際上蝗茁,只要是將Frame Buffer渲染到Texture,Texture又用于后續(xù)的渲染寻咒,那么后續(xù)的渲染都會(huì)等待前面的Texture渲染完成哮翘,即glFlush操作,以保證最終結(jié)果的正確毛秘。Performance Tuning for
Tile-Based Architectures
這篇文章也指明使用render to texture的結(jié)果會(huì)導(dǎo)致flush饭寺。

參考

https://objccn.io/issue-3-1/
https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-TileBasedArchitectures.pdf
https://github.com/seedante/iOS-Note/wiki/Mastering-Offscreen-Render
https://developer.apple.com/videos/play/wwdc2014/419/
https://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/03%20Blending/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市熔脂,隨后出現(xiàn)的幾起案子佩研,更是在濱河造成了極大的恐慌,老刑警劉巖霞揉,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旬薯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡适秩,警方通過(guò)查閱死者的電腦和手機(jī)绊序,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)硕舆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人骤公,你說(shuō)我怎么就攤上這事抚官。” “怎么了阶捆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵凌节,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我洒试,道長(zhǎng)倍奢,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任垒棋,我火速辦了婚禮卒煞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叼架。我一直安慰自己畔裕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布乖订。 她就那樣靜靜地躺著扮饶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垢粮。 梳的紋絲不亂的頭發(fā)上贴届,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音蜡吧,去河邊找鬼毫蚓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛昔善,可吹牛的內(nèi)容都是我干的元潘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼君仆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼翩概!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起返咱,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤钥庇,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后咖摹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體评姨,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年萤晴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吐句。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胁后。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嗦枢,靈堂內(nèi)的尸體忽然破棺而出攀芯,到底是詐尸還是另有隱情,我是刑警寧澤文虏,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布侣诺,位于F島的核電站,受9級(jí)特大地震影響择葡,放射性物質(zhì)發(fā)生泄漏紧武。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一敏储、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朋鞍,春花似錦已添、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至坎吻,卻和暖如春缆蝉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘦真。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工刊头, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诸尽。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓原杂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親您机。 傳聞我的和親對(duì)象是個(gè)殘疾皇子穿肄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容