J'ai besoin d'aide pour optimiser l'ensemble de shaders suivant:
Sommet:
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);
}
Les shaders reçoivent une texture 2D et sont destinés à la mettre à l'échelle magnifiquement sur une surface 2D haute résolution (l'écran de l'appareil). Il s'agit d'une optimisation de l'algorithme de mise à l'échelle SABR en cas de besoin.
Cela fonctionne déjà et fonctionne bien sur les appareils Android très haut de gamme (comme LG Nexus 4), mais il est vraiment lent sur les appareils plus faibles.
Les appareils Android qui comptent vraiment pour moi sont le Samsung Galaxy S 2 \ 3, avec le GPU Mali 400MP - qui fonctionnent horriblement avec ce shader.
Jusqu'à présent, j'ai essayé:
- Élimination des écarts (conseils du guide Mali d'ARM) - a fait une amélioration mineure.
- Remplacer les fonctions mix () par les miennes - n'a pas servi.
- réduire la précision du flotteur à lowp - n'a rien changé.
Je mesure les performances en calculant le temps de rendu (avant et après eglSwapBuffers) - cela me donne une mesure très linéaire et cohérente des performances.
Au-delà, je ne sais pas vraiment où chercher ni ce qui peut être optimisé ici ...
Je sais que c'est un algorithme lourd, et je ne demande pas de conseils sur les méthodes de mise à l'échelle alternatives à utiliser - j'en ai essayé beaucoup et cet algorithme donne le meilleur résultat visuel. Je souhaite utiliser exactement le même algorithme de manière optimisée.
MISE À JOUR
J'ai constaté que si je fais toutes les récupérations de texture avec un vecteur constant au lieu de vecteurs dépendants, j'obtiens une amélioration majeure des performances, c'est donc évidemment un gros goulot d'étranglement - probablement à cause du cache. Cependant, j'ai encore besoin de faire ces récupérations. J'ai joué avec au moins quelques-unes des récupérations avec des variations de vec2 (sans swizzling) mais cela n'a rien amélioré. Je me demande ce qui pourrait être un bon moyen de sonder efficacement 21 texels.
J'ai trouvé qu'une grande partie des calculs est effectuée plusieurs fois avec exactement le même ensemble de texels - car la sortie est mise à l'échelle par au moins x2, et j'interroge avec GL_NEAREST. Il y a au moins 4 fragments qui tombent exactement sur les mêmes texels. Si l'échelle est x4 sur un appareil haute résolution, il y a 16 fragments qui tombent sur les mêmes texels - ce qui est un gros gaspillage. Existe-t-il un moyen d'effectuer une passe de shader supplémentaire qui calculera toutes les valeurs qui ne changent pas sur plusieurs fragments? J'ai pensé à rendre une texture hors écran supplémentaire, mais j'ai besoin de stocker plusieurs valeurs par texel, pas seulement une.
MISE À JOUR
- J'ai également remarqué que le CPU est presque inutilisé alors que le GPU est un gros goulot d'étranglement. Des conseils sur la façon de tirer parti de la puissance du processeur et de transférer la logique du GPU vers le CPU dans cette situation?