我的绘画应用程序是使用OpenGL ES 1.0和一些Quartz编写的。 我正在尝试使用OpenGL ES 2.0重写它以获得更好的性能和新功能。 我写了2个着色器:一个渲染用户对纹理的输入,然后根据一些规则将这个纹理与一些其他纹理混合。 突然我意识到iPad第一代上的第二个着色器工作时间太长 - 我只有10-15 fps。 iPad 2与60+ fps完美配合。我有点震惊,因为原始应用程序(OpenGL ES 1.0)在两个设备上都能正常工作。它只渲染两个多边形(但几乎全屏)。 我已经尝试了一些优化,比如改变精度,评论一些数学运算,硬编码一些纹理调用 - 它有点帮助,但我仍然远离60 fps。只有当我完全评论这个着色器的调用时,我才有60 fps。
我错过了什么吗?我对OpenGL没有多少经验,但我相信这个着色器必须在两代设备上都能正常工作,就像原始应用程序一样。我的顶点和片段着色器是:
===============顶点着色器===================
uniform mat4 modelViewProjectionMatrix;
attribute vec3 position;
attribute vec2 texCoords;
varying vec2 fTexCoords;
void main()
{
fTexCoords = texCoords;
vec4 postmp = vec4(position.xyz, 1.0);
gl_Position = modelViewProjectionMatrix * postmp;
}
===============片段着色器===================
precision highp float;
varying lowp vec4 colorVarying;
varying highp vec2 fTexCoords;
uniform sampler2D texture; // black & white user should paint
uniform sampler2D drawingTexture; // texture with user drawings I rendered earlier
uniform sampler2D paperTexture; // texture of sheet of paper
uniform float currentArea; // which area we should not shadow
uniform float isShadowingOn; // bool - should we shadow some areas of picture
void main()
{
// I pass 1024*1024 texture here but I only need 560*800 so I do some calculations to find real texture coordinates
vec2 convertedTexCoords = vec2(fTexCoords.x * 560.0/1024.0, fTexCoords.y * 800.0/1024.0);
vec4 bgImageColor = texture2D(texture, convertedTexCoords);
float area = bgImageColor.a;
bgImageColor.a = 1.0;
vec4 paperColor = texture2D(paperTexture, convertedTexCoords);
vec4 drawingColor = texture2D(drawingTexture, convertedTexCoords);
// if special area
if ( abs(area - 1.0) < 0.0001) {
// if shadowing ON
if (isShadowingOn == 1.0) {
// if color of original image is black
if ( (bgImageColor.r < 0.1) && (bgImageColor.g < 0.1) && (bgImageColor.b < 0.1) ) {
gl_FragColor = vec4(bgImageColor.rgb, 1.0) * vec4(0.5, 0.5, 0.5, 1.0);
}
// if color of original image is grey
else if ( abs(bgImageColor.r - bgImageColor.g) < 0.15 && abs(bgImageColor.r - bgImageColor.b) < 0.15 && abs(bgImageColor.g - bgImageColor.b) < 0.15 && bgImageColor.r < 0.8 && bgImageColor.g < 0.8 && bgImageColor.b < 0.8){ gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb * 0.4 - drawingColor.rgb * 0.4, 1.0);}
else
{
gl_FragColor = vec4(bgImageColor.rgb, 1.0) * vec4(0.5, 0.5, 0.5, 1.0);
}
}
// if shadowing is OFF
else {
// if color of original image is black
if ( (bgImageColor.r < 0.1) && (bgImageColor.g < 0.1) && (bgImageColor.b < 0.1) ) {
gl_FragColor = vec4(bgImageColor.rgb, 1.0);
}
// if color of original image is gray
else if ( abs(bgImageColor.r - bgImageColor.g) < 0.15 && abs(bgImageColor.r - bgImageColor.b) < 0.15 && abs(bgImageColor.g - bgImageColor.b) < 0.15
&& bgImageColor.r < 0.8 && bgImageColor.g < 0.8 && bgImageColor.b < 0.8){
gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb * 0.4 - drawingColor.rgb * 0.4, 1.0);
}
// rest
else {
gl_FragColor = vec4(bgImageColor.rgb, 1.0);
}
}
}
// if area of fragment is equal to current area
else if ( abs(area-currentArea/255.0) < 0.0001 ) {
gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb - drawingColor.rgb, 1.0);
}
// if area of fragment is NOT equal to current area
else {
if (isShadowingOn == 1.0) {
gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb - drawingColor.rgb, 1.0) * vec4(0.5, 0.5, 0.5, 1.0);
} else {
gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb - drawingColor.rgb, 1.0);
}
}
}
答案 0 :(得分:2)
在着色器中进行分支是非常昂贵的,因为它消除了GPU并行运行着色器的可能性,并且在片段着色器中有很多分支(一个着色器)无论如何应该尽可能快)。更糟糕的是,您根据GPU本身计算的值进行分支,这也会大大降低您的性能。
你真的应该尝试删除尽可能多的分支,而不是让GPU做一些“额外的工作”,例如。不试图优化纹理图集并渲染所有内容(如果可能的话),这仍然会比当前版本更快。如果这不起作用,请尝试将着色器拆分为多个较小的着色器,每个着色器仅执行较大着色器的特定部分并在CPU上而不是在GPU上进行分支(每次绘制调用时只需执行一次此操作不是每个“像素”)。
答案 1 :(得分:0)
除了JustSid关于着色器分支的有效观点之外,我还看到其他一些错误。首先,如果我只是通过Imagination Texhnologies的PVRUniSco编辑器(你真的应该得到它,并且是their free SDK的一部分)运行这个片段着色器,我看到了:
显示42个周期的最佳情况,该着色器最差为52周期。从similar case of fragment shader tuning I asked about开始,我发现11-16周期片段着色器需要35-68毫秒才能在iPad 1(15 - 29 FPS)上渲染。你需要让它变得更紧,以获得合理的渲染时间。
要消除某些分支,您可以使用步进功能或使用Alpha通道玩技巧。我已经完成了这项工作,并且看到了着色器渲染时间的大幅减少。我不会传递isShadowingOn
制服,但我会把它分成两个着色器,用于打开和关闭不同的情况。
除了分支,我可以看到您正在执行bgImageColor
,paperColor
和drawingColor
的从属纹理读取,因为计算了要在片段中获取的纹理坐标着色器。这在iOS设备中基于图块的延迟渲染器上非常昂贵,因为它阻止了纹理提取的某些优化被使用。我建议不要计算每个片段,而是将此计算移动到顶点着色器,并将结果作为变量传递给片段着色器。使用不同的坐标来获取纹理,你会看到性能的巨大提升。
你可以做些小事来调整它。例如,
gl_FragColor = vec4((paperColor.rgb * bgImageColor.rgb - drawingColor.rgb) * 0.4, 1.0);
应该比
略快gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb * 0.4 - drawingColor.rgb * 0.4, 1.0);
编辑器将对您的着色器进行实时编译,因此您可以在代码中尝试这些操作,并根据估计的GPU周期查看结果。