1. 程式人生 > >NanoVG 優化筆記:效能提高5倍的祕密

NanoVG 優化筆記:效能提高5倍的祕密

NanoVG 優化筆記

nanovg正如其名稱所示的那樣,是一個非常小巧的向量繪圖函式庫。相比cairo和skia的數十萬行程式碼,nanovg不足5000行的C語言程式碼,稱為nano也是名副其實了。nanovg的設計、介面和程式碼質量都堪稱典範,唯一美中不足的就是效能不太理想。特別是在Android的低端機型和大螢幕的機型上,一個簡單的介面每秒只能畫十幾幀。最近我把AWTK移植到Android上時,就碰到了這個尷尬的問題。

經過優化之後,AWTK在低端機型上,整體渲染效能有了3到5倍的提升。這裡做個筆記,供有需要的朋友參考。

nanovg的效能瓶頸在於片段著色器(fragment shader),片段著色器可以認為是為GPU提供的一個回撥函式,該回調函式在處理每個畫素時被呼叫,在每一幀繪製時都會執行數百萬次,可見該函式的對效能的影響是很大的。

我們先看看nanovg的片段著色器(fragment shader)程式碼:

	static const char* fillFragShader =
		"#ifdef GL_ES\n"
		"#if defined(GL_FRAGMENT_PRECISION_HIGH) || defined(NANOVG_GL3)\n"
		" precision highp float;\n"
		"#else\n"
		" precision mediump float;\n"
		"#endif\n"
		"#endif\n"
		"#ifdef NANOVG_GL3\n"
		"#ifdef USE_UNIFORMBUFFER\n"
		"	layout(std140) uniform frag {\n"
		"		mat3 scissorMat;\n"
		"		mat3 paintMat;\n"
		"		vec4 innerCol;\n"
		"		vec4 outerCol;\n"
		"		vec2 scissorExt;\n"
		"		vec2 scissorScale;\n"
		"		vec2 extent;\n"
		"		float radius;\n"
		"		float feather;\n"
		"		float strokeMult;\n"
		"		float strokeThr;\n"
		"		int texType;\n"
		"		int type;\n"
		"	};\n"
		"#else\n" // NANOVG_GL3 && !USE_UNIFORMBUFFER
		"	uniform vec4 frag[UNIFORMARRAY_SIZE];\n"
		"#endif\n"
		"	uniform sampler2D tex;\n"
		"	in vec2 ftcoord;\n"
		"	in vec2 fpos;\n"
		"	out vec4 outColor;\n"
		"#else\n" // !NANOVG_GL3
		"	uniform vec4 frag[UNIFORMARRAY_SIZE];\n"
		"	uniform sampler2D tex;\n"
		"	varying vec2 ftcoord;\n"
		"	varying vec2 fpos;\n"
		"#endif\n"
		"#ifndef USE_UNIFORMBUFFER\n"
		"	#define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)\n"
		"	#define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)\n"
		"	#define innerCol frag[6]\n"
		"	#define outerCol frag[7]\n"
		"	#define scissorExt frag[8].xy\n"
		"	#define scissorScale frag[8].zw\n"
		"	#define extent frag[9].xy\n"
		"	#define radius frag[9].z\n"
		"	#define feather frag[9].w\n"
		"	#define strokeMult frag[10].x\n"
		"	#define strokeThr frag[10].y\n"
		"	#define texType int(frag[10].z)\n"
		"	#define type int(frag[10].w)\n"
		"#endif\n"
		"\n"
		"float sdroundrect(vec2 pt, vec2 ext, float rad) {\n"
		"	vec2 ext2 = ext - vec2(rad,rad);\n"
		"	vec2 d = abs(pt) - ext2;\n"
		"	return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;\n"
		"}\n"
		"\n"
		"// Scissoring\n"
		"float scissorMask(vec2 p) {\n"
		"	vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n"
		"	sc = vec2(0.5,0.5) - sc * scissorScale;\n"
		"	return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n"
		"}\n"
		"#ifdef EDGE_AA\n"
		"// Stroke - from [0..1] to clipped pyramid, where the slope is 1px.\n"
		"float strokeMask() {\n"
		"	return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y);\n"
		"}\n"
		"#endif\n"
		"\n"
		"void main(void) {\n"
		"   vec4 result;\n"
		"	float scissor = scissorMask(fpos);\n"
		"#ifdef EDGE_AA\n"
		"	float strokeAlpha = strokeMask();\n"
		"	if (strokeAlpha < strokeThr) discard;\n"
		"#else\n"
		"	float strokeAlpha = 1.0;\n"
		"#endif\n"
		"	if (type == 0) {			// Gradient\n"
		"		// Calculate gradient color using box gradient\n"
		"		vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n"
		"		float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n"
		"		vec4 color = mix(innerCol,outerCol,d);\n"
		"		// Combine alpha\n"
		"		color *= strokeAlpha * scissor;\n"
		"		result = color;\n"
		"	} else if (type == 1) {		// Image\n"
		"		// Calculate color fron texture\n"
		"		vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;\n"
		"#ifdef NANOVG_GL3\n"
		"		vec4 color = texture(tex, pt);\n"
		"#else\n"
		"		vec4 color = texture2D(tex, pt);\n"
		"#endif\n"
		"		if (texType == 1) color = vec4(color.xyz*color.w,color.w);"
		"		if (texType == 2) color = vec4(color.x);"
		"		// Apply color tint and alpha.\n"
		"		color *= innerCol;\n"
		"		// Combine alpha\n"
		"		color *= strokeAlpha * scissor;\n"
		"		result = color;\n"
		"	} else if (type == 2) {		// Stencil fill\n"
		"		result = vec4(1,1,1,1);\n"
		"	} else if (type == 3) {		// Textured tris\n"
		"#ifdef NANOVG_GL3\n"
		"		vec4 color = texture(tex, ftcoord);\n"
		"#else\n"
		"		vec4 color = texture2D(tex, ftcoord);\n"
		"#endif\n"
		"		if (texType == 1) color = vec4(color.xyz*color.w,color.w);"
		"		if (texType == 2) color = vec4(color.x);"
		"		color *= scissor;\n"
		"		result = color * innerCol;\n"
		"	}\n"
		"#ifdef NANOVG_GL3\n"
		"	outColor = result;\n"
		"#else\n"
		"	gl_FragColor = result;\n"
		"#endif\n"
		"}\n";

它的功能很完整也很複雜,裁剪和反走樣都做了處理。仔細分析之後,我發現了幾個效能問題:

一、顏色填充的問題

簡單顏色填充和漸變顏色填充使用了相同的程式碼:

		"	if (type == 0) {			// Gradient\n"
		"		// Calculate gradient color using box gradient\n"
		"		vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n"
		"		float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n"
		"		vec4 color = mix(innerCol,outerCol,d);\n"
		"		// Combine alpha\n"
		"		color *= strokeAlpha * scissor;\n"
		"		result = color;\n"

問題

簡單顏色填充只需一條指令,而漸變顏色填充則需要數十條指令。這兩種情況重用一段程式碼,會讓簡單顏色填充慢10倍以上。

方案

把顏色填充分成以下幾種情況,分別進行優化:

  • 矩形簡單顏色填充。

對於無需裁剪的矩形(這是最常見的情況),直接賦值即可,效能提高20倍以上。

      " if (type == 5) {    //fast fill color\n"
      "   result = innerCol;\n"
  • 通用多邊形簡單顏色填充。

去掉漸變的取樣函式,效能會提高一倍以上:

    " } else if(type == 7) {      // fill color\n"
      "   strokeAlpha = strokeMask();\n"
      "   if (strokeAlpha < strokeThr) discard;\n"
      "   float scissor = scissorMask(fpos);\n"
      "   vec4 color = innerCol;\n"
      "   color *= strokeAlpha * scissor;\n"
      "   result = color;\n"

  • 漸變顏色填充(只佔極小的部分)。

這種情況非常少見,還是使用之前的程式碼。

效果:

平均情況,填充效能提高10倍以上!

二、字型的問題

對於文字而言,需要顯示的畫素和不顯示的畫素,平均算下來在1:1左右。

		"	} else if (type == 3) {		// Textured tris\n"
		"#ifdef NANOVG_GL3\n"
		"		vec4 color = texture(tex, ftcoord);\n"
		"#else\n"
		"		vec4 color = texture2D(tex, ftcoord);\n"
		"#endif\n"
		"		if (texType == 1) color = vec4(color.xyz*color.w,color.w);"
		"		if (texType == 2) color = vec4(color.x);"
		"		color *= scissor;\n"
		"		result = color * innerCol;\n"
		"	}\n"

問題:

如果顯示的畫素和不顯示的畫素都走完整的流程,會浪費調一半的時間。

方案:

  • 當color.x < 0.02時直接跳過。
  • 裁剪和反走樣放到判斷語句之後。
      " } else if (type == 3) {   // Textured tris\n"
      "#ifdef NANOVG_GL3\n"
      "   vec4 color = texture(tex, ftcoord);\n"
      "#else\n"
      "   vec4 color = texture2D(tex, ftcoord);\n"
      "#endif\n"
      "   if(color.x < 0.02) discard;\n"
      "   strokeAlpha = strokeMask();\n"
      "   if (strokeAlpha < strokeThr) discard;\n"
      "   float scissor = scissorMask(fpos);\n"
      "   color = vec4(color.x);"
      "   color *= scissor;\n"
      "   result = color * innerCol;\n"
      " }\n"

效果:

字型渲染效能提高一倍!

三、反走樣的問題

反走樣的實現函式如下(其實我也不懂):

		"float strokeMask() {\n"
		"	return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y);\n"
		"}\n"

問題:

與簡單的賦值操作相比,加上反走樣功能,效能會下降5-10倍。但是不加反走樣功能,繪製多邊形時邊緣效果比較差。不加不好看,加了又太慢,看起來是個兩難的選擇。

方案:

矩形填充是可以不用反走樣功能的。而90%以上的情況都是矩形填充。矩形填充單獨處理,一條指令搞定,效能提高20倍以上:

      " if (type == 5) {    //fast fill color\n"
      "   result = innerCol;\n"

效果:

配合裁剪和矩形的優化,效能提高10倍以上。

四、裁剪的問題

裁剪放到Shader中雖然合理,但是效能就要大大折扣了。

		"// Scissoring\n"
		"float scissorMask(vec2 p) {\n"
		"	vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n"
		"	sc = vec2(0.5,0.5) - sc * scissorScale;\n"
		"	return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n"
		"}\n"

問題:

與簡單的賦值操作相比,加上裁剪功能,效能會下降10以上倍。但是不加裁剪功能,像滾動檢視這樣的控制元件就沒法實現,這看起來也是個兩難的選擇。

方案:

而90%以上的填充都是在裁剪區域的內部的,沒有必要每個畫素都去判斷,放在Shader之外進行判斷即可。

static int glnvg__pathInScissor(const NVGpath* path, NVGscissor* scissor) {
  int32_t i = 0;
  float cx = scissor->xform[4];
  float cy = scissor->xform[5];
  float hw = scissor->extent[0];
  float hh = scissor->extent[1];

  float l = cx - hw;
  float t = cy - hh;
  float r = l + 2 * hw - 1;
  float b = t + 2 * hh - 1;

  const NVGvertex* verts = path->fill;
  for (i = 0; i < path->nfill; i++) {
    const NVGvertex* iter = verts + i;
    int x = iter->x;
    int y = iter->y;
    if (x < l || x > r || y < t || y > b) {
      return 0;
    }
  }

  return 1;
}

效果:

配合裁剪和矩形的優化,效能提高10倍以上。

五、綜合

綜合裁剪、反走樣和矩形,新增3個型別,進行特殊處理:

  • 快速填充無需裁剪的矩形:NSVG_SHADER_FAST_FILLCOLOR
  • 快速填充無需裁剪的圖片:NSVG_SHADER_FAST_FILLIMG
  • 快速用簡單顏色填充多邊形:NSVG_SHADER_FILLCOLOR

裁剪、反走樣和矩形可以組合更多型別,進行更精細的優化。但即使只作這三種情況處理,AWTK在Android平臺的整體效能已經有了3-5倍的提高,demoui在我們測試的機型上,都穩穩的保持在60FPS,沒有必要為了效能增加它的複雜度了。

詳細情況和完整程式碼請參考

相關推薦

NanoVG 優化筆記效能提高5祕密

NanoVG 優化筆記 nanovg正如其名稱所示的那樣,是一個非常小巧的向量繪圖函式庫。相比cairo和skia的數十萬行程式碼

Caseupdate中把in改寫成join效能提高

(1)優化前 如下一條SQL,把從1985-05-21入職前的員工薪資都增加500,執行約20.70 s, 從執行計劃中可以看出對錶salaries進行的是索引全掃描,掃描行數約260W行。 mysql> update salaries set sala

下單介面調優實戰,效能提高10

概述 最近公司的下單介面有些慢,老闆擔心無法支撐雙11,想讓我優化一把,但是前提是不允許大改,因為下單介面太複雜了,如果改動太大,怕有風險。另外開發成本和測試成本也非常大。對於這種有挑戰性的任務,我向來是非常喜歡的,因為在解決問題的過程中,可以學習到很多東西。

【京緣網路電商系統】下單介面調優實戰過程公開 效能提高10

對於我們公司定製的電商系統,客戶反映最近下單介面有點慢心無法支撐雙12(好像是雙十一搞了場超大的垮了),現在想讓我優化一把,但是前提是不允許大改,因為下單介面太複雜了,如果改動太大,怕有風險。另外開發成本和測試成本也非常大。對於這種有挑戰性的任務,我向來是非常喜歡的,因為在解決問題的過程中,可以

彙總:將Web應用效能提高10的10條建議

提高 web 應用的效能從來沒有比現在更重要過。網路經濟的比重一直在增長;全球經濟超過 5% 的價值是在因特網上產生的(資料參見下面的資料)。這個時刻線上的超連線世界意味著使用者對其的期望值也處於歷史上的最高點。如果你的網站不能及時的響應,或者你的 app 不能無延時的工作,使用者會很快的投奔到你的

巧用這19條MySQL優化,效率至少提高3

本文我們來談談專案中常用的MySQL優化方法,共19條,具體如下: 1、EXPLAIN 做MySQL優化,我們要善用EXPLAIN檢視SQL執行計劃。 下面來個簡單的示例,標註(1、2、3、4、5)我們要重點關注的資料: type列,連線型別。一個好的SQL語句至少要達到range級別。杜絕出現a

將 Web 應用效能提高的10條建議

提高 web 應用的效能從來沒有比現在更重要過。網路經濟的比重一直在增長;全球經濟超過 5% 的價值是在因特網上產生的(資料參見下面的資料)。這個時刻線上的超連線世界意味著使用者對其的期望值也處於歷史上的最高點。如果你的網站不能及時的響應,或者你的 app 不能無延時的

alijdk 8.1.1的優化使ssl效能提升2以上

簡單說如果你的java容器提供https服務的,效能可以提升兩倍以上,這是一個非常非常非常非常非常值得升級的提升。在jdk8.0時摸高壓測qps到3000時再也上不去,主要是ssl裡面的一個鎖效率低,優化後qps達到 8000也很穩定。官方的8u102開始也採用了alijdk

學會這5個PS小技巧,讓工作效率提高5

PS爸爸是我們最熟悉不過的軟體了,但裡面功能繁多,許多隱蔽而又方便的設定和功能常常被大家忽略了,導致為了達到想要的效果而多走彎路。所以今天和大家分享其中5個PS小技巧,可以大大提升工作效率。  有些文字表述不是太清晰,大家看一遍GIF圖,應該就可以很好的理解了。  1. 對圖

刷題筆記leetcode第5Longest Palindromic Substring

記錄一下~~    作者刷leetcode刷到吐血,第五題感覺沒有太好的官方解決方法,我是這麼做的    問題:    思路:由於字串可能很大,消耗計算量的做法肯定是不能用的,最後只遍歷一次,找出所有迴文        1、遍歷每個字元       2、每次得到字元後,再迴圈

DisplayPort 迎來重大更新,資料頻寬效能提高3

VESA宣佈了他們對DisplayPort介面三年來的第一次重大更新。 與DP 1.4a相比,DisplayPort 2.0提供

提高C++效能的程式設計技術筆記虛擬函式、返回值優化+測試程式碼

虛擬函式:在以下幾個方面,虛擬函式可能會造成效能損失:建構函式必須初始化vptr(虛擬函式表);虛擬函式是通過指標間接呼叫的,所以必須先得到指向虛擬函式表的指標,然後再獲得正確的函式偏移量;內聯是在編譯時決定的,編譯器不可能把執行時才解析的虛擬函式設定為內聯。 無法內聯虛擬函式造成的效能損失

歪門邪道效能優化魔改三方庫原始碼,效能提高几十

本文會分享一個React效能優化的故事,這也是我在工作中真實遇到的故事,最終我們是通過魔改第三方庫原始碼將它效能提高了幾十倍。這個第三方庫也是很有名的,在GitHub上有4.5k star,這就是:[react-big-calendar](https://github.com/jquense/react-bi

oracle 效能優化操作七索引提高資料分佈不均勻時查詢效率

索引的選擇性低,但資料的分佈差異很大時,仍然可以利用索引提高效率。 A、資料分佈不均勻的特殊情況下,選擇性不高的索引也要建立。 表ServiceInfo中資料量很大,假設有一百萬行,其中有一個欄位DisposalCourseFlag,取範圍為列舉:[0,1,2,3,4,5,6

提高C++效能的程式設計技術筆記多執行緒記憶體池+測試程式碼

為了使多個執行緒併發地分配和釋放記憶體,必須在分配器方法中新增互斥鎖。 全域性記憶體管理器(通過new()和delete()實現)是通用的,因此它的開銷也非常大。 因為單執行緒記憶體管理器要比多執行緒記憶體管理器快的多,所以如果要分配的大多數記憶體塊限於單執行緒中使用,那麼可以顯著提升效

提高C++效能的程式設計技術筆記單執行緒記憶體池+測試程式碼

頻繁地分配和回收記憶體會嚴重地降低程式的效能。效能降低的原因在於預設的記憶體管理是通用的。應用程式可能會以某種特定的方式使用記憶體,並且為不需要的功能付出效能上的代價。通過開發專用的記憶體管理器可以解決這個問題。對專用記憶體管理器的設計可以從多個角度考慮。我們至少可以想到兩個方面:大小和併發。

提高C++效能的程式設計技術筆記臨時物件+測試程式碼

型別不匹配:一般情況是指當需要X型別的物件時提供的卻是其它型別的物件。編譯器需要以某種方式將提供的型別轉換成要求的X型別。這一過程可能會產生臨時物件。 按值傳遞:建立和銷燬臨時物件的代價是比較高的。倘若可以,我們應該按指標或者引用來傳遞物件以避免生成臨時物件。 按值返回:如果編寫的函式是

提高C++效能的程式設計技術筆記建構函式和解構函式+測試程式碼

物件的建立和銷燬往往會造成效能的損失。在繼承層次中,物件的建立將引起其先輩的建立。物件的銷燬也是如此。其次,物件相關的開銷與物件本身的派生鏈的長度和複雜性相關。所建立的物件(以及其後銷燬的物件)的數量與派生的複雜度成正比。 並不是說繼承根本上就是程式碼效能的絆腳石。我們必須區分全部計算開銷、

提高C++效能的程式設計技術筆記內聯+測試程式碼

內聯類似於巨集,在呼叫方法內部展開被呼叫方法,以此來代替方法的呼叫。一般來說表達內聯意圖的方式有兩種:一種是在定義方法時新增內聯保留字的字首;另一種是在類的頭部宣告中定義方法。 雖然內聯方法的呼叫方式和普通方法相同,但其編譯過程卻相差甚遠。由於內聯方法的程式碼必須內聯展開,這就要求呼叫內聯方

筆記ios效能優化(未完待續。。。)

最近在網上看了很多的記憶體優化方案,感覺都很 nice ,本文主要是對網上的記憶體優化方案做一下列表式總結,不涉及具體的內容(主要目的就是備忘): 用ARC管理記憶體(貌似現在的新專案99%用的都是ARC了) 在正確的地方使用 reuseIdentifier 儘可能使 View