我需要帮助优化以下着色器集:
顶点:
precision mediump float;
uniform vec2 rubyTextureSize;
attribute vec4 vPosition;
attribute vec2 a_TexCoordinate;
varying vec2 tc;
void main() {
gl_Position = vPosition;
tc = a_TexCoordinate;
}
fragment :
precision mediump float;
/*
Uniforms
- rubyTexture: texture sampler
- rubyTextureSize: size of the texture before rendering
*/
uniform sampler2D rubyTexture;
uniform vec2 rubyTextureSize;
uniform vec2 rubyTextureFract;
/*
Varying attributes
- tc: coordinate of the texel being processed
- xyp_[]_[]_[]: a packed coordinate for 3 areas within the texture
*/
varying vec2 tc;
/*
Constants
*/
/*
Inequation coefficients for interpolation
Equations are in the form: Ay + Bx = C
45, 30, and 60 denote the angle from x each line the cooeficient variable set builds
*/
const vec4 Ai = vec4(1.0, -1.0, -1.0, 1.0);
const vec4 B45 = vec4(1.0, 1.0, -1.0, -1.0);
const vec4 C45 = vec4(1.5, 0.5, -0.5, 0.5);
const vec4 B30 = vec4(0.5, 2.0, -0.5, -2.0);
const vec4 C30 = vec4(1.0, 1.0, -0.5, 0.0);
const vec4 B60 = vec4(2.0, 0.5, -2.0, -0.5);
const vec4 C60 = vec4(2.0, 0.0, -1.0, 0.5);
const vec4 M45 = vec4(0.4, 0.4, 0.4, 0.4);
const vec4 M30 = vec4(0.2, 0.4, 0.2, 0.4);
const vec4 M60 = M30.yxwz;
const vec4 Mshift = vec4(0.2);
// Coefficient for weighted edge detection
const float coef = 2.0;
// Threshold for if luminance values are "equal"
const vec4 threshold = vec4(0.32);
// Conversion from RGB to Luminance (from GIMP)
const vec3 lum = vec3(0.21, 0.72, 0.07);
// Performs same logic operation as && for vectors
bvec4 _and_(bvec4 A, bvec4 B) {
return bvec4(A.x && B.x, A.y && B.y, A.z && B.z, A.w && B.w);
}
// Performs same logic operation as || for vectors
bvec4 _or_(bvec4 A, bvec4 B) {
return bvec4(A.x || B.x, A.y || B.y, A.z || B.z, A.w || B.w);
}
// Converts 4 3-color vectors into 1 4-value luminance vector
vec4 lum_to(vec3 v0, vec3 v1, vec3 v2, vec3 v3) {
// return vec4(dot(lum, v0), dot(lum, v1), dot(lum, v2), dot(lum, v3));
return mat4(v0.x, v1.x, v2.x, v3.x, v0.y, v1.y, v2.y, v3.y, v0.z, v1.z,
v2.z, v3.z, 0.0, 0.0, 0.0, 0.0) * vec4(lum, 0.0);
}
// Gets the difference between 2 4-value luminance vectors
vec4 lum_df(vec4 A, vec4 B) {
return abs(A - B);
}
// Determines if 2 4-value luminance vectors are "equal" based on threshold
bvec4 lum_eq(vec4 A, vec4 B) {
return lessThan(lum_df(A, B), threshold);
}
vec4 lum_wd(vec4 a, vec4 b, vec4 c, vec4 d, vec4 e, vec4 f, vec4 g, vec4 h) {
return lum_df(a, b) + lum_df(a, c) + lum_df(d, e) + lum_df(d, f)
+ 4.0 * lum_df(g, h);
}
// Gets the difference between 2 3-value rgb colors
float c_df(vec3 c1, vec3 c2) {
vec3 df = abs(c1 - c2);
return df.r + df.g + df.b;
}
void main() {
/*
Mask for algorhithm
+-----+-----+-----+-----+-----+
| | 1 | 2 | 3 | |
+-----+-----+-----+-----+-----+
| 5 | 6 | 7 | 8 | 9 |
+-----+-----+-----+-----+-----+
| 10 | 11 | 12 | 13 | 14 |
+-----+-----+-----+-----+-----+
| 15 | 16 | 17 | 18 | 19 |
+-----+-----+-----+-----+-----+
| | 21 | 22 | 23 | |
+-----+-----+-----+-----+-----+
*/
float x = rubyTextureFract.x;
float y = rubyTextureFract.y;
vec4 xyp_1_2_3 = tc.xxxy + vec4(-x, 0.0, x, -2.0 * y);
vec4 xyp_6_7_8 = tc.xxxy + vec4(-x, 0.0, x, -y);
vec4 xyp_11_12_13 = tc.xxxy + vec4(-x, 0.0, x, 0.0);
vec4 xyp_16_17_18 = tc.xxxy + vec4(-x, 0.0, x, y);
vec4 xyp_21_22_23 = tc.xxxy + vec4(-x, 0.0, x, 2.0 * y);
vec4 xyp_5_10_15 = tc.xyyy + vec4(-2.0 * x, -y, 0.0, y);
vec4 xyp_9_14_9 = tc.xyyy + vec4(2.0 * x, -y, 0.0, y);
// Get mask values by performing texture lookup with the uniform sampler
vec3 P1 = texture2D(rubyTexture, xyp_1_2_3.xw).rgb;
vec3 P2 = texture2D(rubyTexture, xyp_1_2_3.yw).rgb;
vec3 P3 = texture2D(rubyTexture, xyp_1_2_3.zw).rgb;
vec3 P6 = texture2D(rubyTexture, xyp_6_7_8.xw).rgb;
vec3 P7 = texture2D(rubyTexture, xyp_6_7_8.yw).rgb;
vec3 P8 = texture2D(rubyTexture, xyp_6_7_8.zw).rgb;
vec3 P11 = texture2D(rubyTexture, xyp_11_12_13.xw).rgb;
vec3 P12 = texture2D(rubyTexture, xyp_11_12_13.yw).rgb;
vec3 P13 = texture2D(rubyTexture, xyp_11_12_13.zw).rgb;
vec3 P16 = texture2D(rubyTexture, xyp_16_17_18.xw).rgb;
vec3 P17 = texture2D(rubyTexture, xyp_16_17_18.yw).rgb;
vec3 P18 = texture2D(rubyTexture, xyp_16_17_18.zw).rgb;
vec3 P21 = texture2D(rubyTexture, xyp_21_22_23.xw).rgb;
vec3 P22 = texture2D(rubyTexture, xyp_21_22_23.yw).rgb;
vec3 P23 = texture2D(rubyTexture, xyp_21_22_23.zw).rgb;
vec3 P5 = texture2D(rubyTexture, xyp_5_10_15.xy).rgb;
vec3 P10 = texture2D(rubyTexture, xyp_5_10_15.xz).rgb;
vec3 P15 = texture2D(rubyTexture, xyp_5_10_15.xw).rgb;
vec3 P9 = texture2D(rubyTexture, xyp_9_14_9.xy).rgb;
vec3 P14 = texture2D(rubyTexture, xyp_9_14_9.xz).rgb;
vec3 P19 = texture2D(rubyTexture, xyp_9_14_9.xw).rgb;
// Store luminance values of each point in groups of 4
// so that we may operate on all four corners at once
vec4 p7 = lum_to(P7, P11, P17, P13);
vec4 p8 = lum_to(P8, P6, P16, P18);
vec4 p11 = p7.yzwx; // P11, P17, P13, P7
vec4 p12 = lum_to(P12, P12, P12, P12);
vec4 p13 = p7.wxyz; // P13, P7, P11, P17
vec4 p14 = lum_to(P14, P2, P10, P22);
vec4 p16 = p8.zwxy; // P16, P18, P8, P6
vec4 p17 = p7.zwxy; // P17, P13, P7, P11
vec4 p18 = p8.wxyz; // P18, P8, P6, P16
vec4 p19 = lum_to(P19, P3, P5, P21);
vec4 p22 = p14.wxyz; // P22, P14, P2, P10
vec4 p23 = lum_to(P23, P9, P1, P15);
// Scale current texel coordinate to [0..1]
vec2 fp = fract(tc * rubyTextureSize);
// Determine amount of "smoothing" or mixing that could be done on texel corners
vec4 AiMulFpy = Ai * fp.y;
vec4 B45MulFpx = B45 * fp.x;
vec4 ma45 = smoothstep(C45 - M45, C45 + M45, AiMulFpy + B45MulFpx);
vec4 ma30 = smoothstep(C30 - M30, C30 + M30, AiMulFpy + B30 * fp.x);
vec4 ma60 = smoothstep(C60 - M60, C60 + M60, AiMulFpy + B60 * fp.x);
vec4 marn = smoothstep(C45 - M45 + Mshift, C45 + M45 + Mshift,
AiMulFpy + B45MulFpx);
// Perform edge weight calculations
vec4 e45 = lum_wd(p12, p8, p16, p18, p22, p14, p17, p13);
vec4 econt = lum_wd(p17, p11, p23, p13, p7, p19, p12, p18);
vec4 e30 = lum_df(p13, p16);
vec4 e60 = lum_df(p8, p17);
// Calculate rule results for interpolation
bvec4 r45_1 = _and_(notEqual(p12, p13), notEqual(p12, p17));
bvec4 r45_2 = _and_(not (lum_eq(p13, p7)), not (lum_eq(p13, p8)));
bvec4 r45_3 = _and_(not (lum_eq(p17, p11)), not (lum_eq(p17, p16)));
bvec4 r45_4_1 = _and_(not (lum_eq(p13, p14)), not (lum_eq(p13, p19)));
bvec4 r45_4_2 = _and_(not (lum_eq(p17, p22)), not (lum_eq(p17, p23)));
bvec4 r45_4 = _and_(lum_eq(p12, p18), _or_(r45_4_1, r45_4_2));
bvec4 r45_5 = _or_(lum_eq(p12, p16), lum_eq(p12, p8));
bvec4 r45 = _and_(r45_1, _or_(_or_(_or_(r45_2, r45_3), r45_4), r45_5));
bvec4 r30 = _and_(notEqual(p12, p16), notEqual(p11, p16));
bvec4 r60 = _and_(notEqual(p12, p8), notEqual(p7, p8));
// Combine rules with edge weights
bvec4 edr45 = _and_(lessThan(e45, econt), r45);
bvec4 edrrn = lessThanEqual(e45, econt);
bvec4 edr30 = _and_(lessThanEqual(coef * e30, e60), r30);
bvec4 edr60 = _and_(lessThanEqual(coef * e60, e30), r60);
// Finalize interpolation rules and cast to float (0.0 for false, 1.0 for true)
vec4 final45 = vec4(_and_(_and_(not (edr30), not (edr60)), edr45));
vec4 final30 = vec4(_and_(_and_(edr45, not (edr60)), edr30));
vec4 final60 = vec4(_and_(_and_(edr45, not (edr30)), edr60));
vec4 final36 = vec4(_and_(_and_(edr60, edr30), edr45));
vec4 finalrn = vec4(_and_(not (edr45), edrrn));
// Determine the color to mix with for each corner
vec4 px = step(lum_df(p12, p17), lum_df(p12, p13));
// Determine the mix amounts by combining the final rule result and corresponding
// mix amount for the rule in each corner
vec4 mac = final36 * max(ma30, ma60) + final30 * ma30 + final60 * ma60
+ final45 * ma45 + finalrn * marn;
/*
Calculate the resulting color by traversing clockwise and counter-clockwise around
the corners of the texel
Finally choose the result that has the largest difference from the texel's original
color
*/
vec3 res1 = P12;
res1 = mix(res1, mix(P13, P17, px.x), mac.x);
res1 = mix(res1, mix(P7, P13, px.y), mac.y);
res1 = mix(res1, mix(P11, P7, px.z), mac.z);
res1 = mix(res1, mix(P17, P11, px.w), mac.w);
vec3 res2 = P12;
res2 = mix(res2, mix(P17, P11, px.w), mac.w);
res2 = mix(res2, mix(P11, P7, px.z), mac.z);
res2 = mix(res2, mix(P7, P13, px.y), mac.y);
res2 = mix(res2, mix(P13, P17, px.x), mac.x);
gl_FragColor = vec4(mix(res1, res2, step(c_df(P12, res1), c_df(P12, res2))),
1.0);
}
着色器接收 2D 纹理,旨在将其精美地缩放到高分辨率 2D 表面(设备屏幕)上。 它是对 SABR 缩放算法的优化,以防万一。
它已经可以运行,并且在非常高端的设备(如 LG Nexus 4)上表现良好,但在较弱的设备上确实很慢。
对我来说真正重要的设备是配备 Mali 400MP GPU 的 Samsung Galaxy S 2\3 - 使用此着色器时性能非常糟糕。
到目前为止我已经尝试过:
- 消除变量(来自 ARM 的 Mali 指南的建议)- 做了微小的改进。
- 用我自己的函数覆盖 mix() 函数——没有用。
- 将浮点精度降低到 lowp - 没有改变任何东西。
我通过计算渲染时间(在 eglSwapBuffers 之前和之后)来衡量性能 - 这为我提供了一个非常线性且一致的性能衡量标准。
除此之外,我真的不知道去哪里寻找或可以在这里优化什么...
我知道这是一个繁重的算法,我并不是要征求有关使用哪些替代缩放方法的建议 - 我已经尝试了很多,并且该算法提供了最佳的视觉效果。我希望以优化的方式使用完全相同的算法。
更新
我发现,如果我使用常量向量而不是依赖向量来执行所有纹理提取,我会获得重大的性能改进,所以这显然是一个很大的瓶颈 - 可能是因为缓存。 但是,我仍然需要进行这些提取。我玩过至少一些使用 vec2 varyings 的抓取(没有任何 swizzling),但它没有改善任何东西。我想知道什么是有效轮询 21 个纹素的好方法。
我发现计算的主要部分是使用完全相同的纹理元素集多次完成的 - 因为输出至少缩放了 x2,并且我使用 GL_NEAREST 进行轮询。至少有 4 个 fragment 落在完全相同的纹素上。如果在高分辨率设备上缩放为 x4,则有 16 个 fragment 落在相同的纹素上——这是一个很大的浪费。 有什么方法可以执行额外的着色器 channel 来计算所有不会跨多个 fragment 改变的值?我考虑过渲染到额外的屏幕外纹理,但我需要为每个纹素存储多个值,而不仅仅是一个。
更新
- 尝试使用已知的 bool 规则简化 bool 表达式 - 为我节省了一些操作,但对性能没有任何影响。
更新
- 考虑了一种将计算传递给顶点着色器的方法——只需要一个“几何体”来创建我的全屏,但在缩放之前有很多顶点对应于每个原始像素。例如,如果我的原始纹理是 320x200,而我的目标屏幕是 1280x800,那么将有 320x200 个顶点均匀分布。然后,在这些顶点中进行大部分计算。问题是 - 我的目标设备 (S2\S3) 不支持顶点纹理采样。
更新
- 在 LG Nexus 4 和三星 Galaxy S3 上测得的性能表明,Nexus 4 的运行速度要快 10 倍以上。怎么会这样?这些是同一代、相同分辨率等的 2 台设备……Mali 400MP 在某些情况下真的很糟糕吗?我敢肯定,与 Nexus 4 相比,有一些非常具体的东西让它运行得如此缓慢(但还没有找到)。
最佳答案
根据我的经验,移动 GPU 性能大致与 texture2D
调用的数量成正比。您有 21 个,这确实很多。通常,内存查找比计算慢数百倍,因此您可以进行大量计算,但仍然会在纹理查找上遇到瓶颈。 (这也意味着优化其余代码可能效果不大,因为这意味着它不会在等待纹理查找时忙碌,而是在等待纹理查找时空闲。)所以你需要减少您调用的 texture2D 的数量。
很难说如何做到这一点,因为我不太了解你的着色器,但有一些想法:
- 将其分为水平 channel 和垂直 channel 。这仅适用于某些着色器,例如模糊,但它可以严重减少纹理查找的次数。例如,一个 5x5 的高斯模糊会天真地进行 25 次纹理查找;如果水平然后垂直完成,它只使用 10。
- 使用线性过滤来“作弊”。如果在启用线性过滤的情况下准确采样 4 个像素而不是 1 个像素的中间,则可以免费获得所有 4 个像素的平均值。但是我不知道它如何影响你的着色器。还是在模糊示例中,使用线性过滤一次对中间像素两侧的两个像素进行采样,允许您使用 3 次 texture2D 调用对 5 个像素进行采样,从而将 5x5 模糊减少到仅水平和垂直方向的 6 个采样。
- 只需使用较小的内核(这样您就不会采集那么多样本)。这会影响质量,因此您可能需要某种方法来检测设备性能,并在设备运行缓慢时切换到质量较低的着色器。
关于android - 优化重 fragment 着色器的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16498755/