本篇内容主要讲解“NanoVG优化方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“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倍以上。
到此,相信大家对“NanoVG优化方法是什么”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。