opengl es - El sombreador de convolución GLSL simple es atrozmente lento
opengl-es filter (2)
Estoy intentando implementar un sombreador de esquema 2D en OpenGL ES2.0 para iOS. Es increíblemente lento. Como en 5fps lento. Lo he rastreado hasta las llamadas texture2D (). Sin embargo, sin esos, cualquier sombreado de convolución se puede deshacer. He intentado usar lowp en lugar de mediump, pero con eso todo es negro, aunque da otros 5 fps, pero aún no se puede utilizar.
Aquí está mi fragmento de sombreado.
varying mediump vec4 colorVarying;
varying mediump vec2 texCoord;
uniform bool enableTexture;
uniform sampler2D texture;
uniform mediump float k;
void main() {
const mediump float step_w = 3.0/128.0;
const mediump float step_h = 3.0/128.0;
const mediump vec4 b = vec4(0.0, 0.0, 0.0, 1.0);
const mediump vec4 one = vec4(1.0, 1.0, 1.0, 1.0);
mediump vec2 offset[9];
mediump float kernel[9];
offset[0] = vec2(-step_w, step_h);
offset[1] = vec2(-step_w, 0.0);
offset[2] = vec2(-step_w, -step_h);
offset[3] = vec2(0.0, step_h);
offset[4] = vec2(0.0, 0.0);
offset[5] = vec2(0.0, -step_h);
offset[6] = vec2(step_w, step_h);
offset[7] = vec2(step_w, 0.0);
offset[8] = vec2(step_w, -step_h);
kernel[0] = kernel[2] = kernel[6] = kernel[8] = 1.0/k;
kernel[1] = kernel[3] = kernel[5] = kernel[7] = 2.0/k;
kernel[4] = -16.0/k;
if (enableTexture) {
mediump vec4 sum = vec4(0.0);
for (int i=0;i<9;i++) {
mediump vec4 tmp = texture2D(texture, texCoord + offset[i]);
sum += tmp * kernel[i];
}
gl_FragColor = (sum * b) + ((one-sum) * texture2D(texture, texCoord));
} else {
gl_FragColor = colorVarying;
}
}
Esto no está optimizado ni finalizado, pero debo mejorar el rendimiento antes de continuar. He intentado reemplazar la llamada texture2D () en el bucle con solo un vec4 sólido y funciona sin problemas, a pesar de todo lo demás.
¿Cómo puedo optimizar esto? Sé que es posible porque he visto efectos mucho más complicados en 3D ejecutando sin problemas. No puedo ver por qué esto está causando ningún problema en absoluto.
He hecho esto exactamente, y veo varias cosas que podrían optimizarse aquí.
En primer lugar, eliminaría el condicional enableTexture
la enableTexture
y en su lugar dividiría su sombreado en dos programas, uno para el estado verdadero de este y otro para el falso. Los condicionales son muy caros en los sombreadores de fragmentos de iOS, particularmente aquellos que tienen lecturas de textura dentro de ellos.
En segundo lugar, tienes nueve lecturas de textura dependientes aquí. Estas son lecturas de textura donde las coordenadas de la textura se calculan dentro del sombreador de fragmentos. Las lecturas de textura dependientes son muy caras en las GPU PowerVR dentro de los dispositivos iOS, ya que evitan que el hardware optimice las lecturas de textura utilizando el almacenamiento en caché, etc. Debido a que está realizando una muestra de un desplazamiento fijo para los 8 píxeles circundantes y uno central, estos cálculos deben ser movido hacia arriba en el sombreador de vértice. Esto también significa que estos cálculos no tendrán que realizarse para cada píxel, solo una vez para cada vértice y luego la interpolación de hardware manejará el resto.
Tercero, el compilador de sombreadores de iOS hasta la fecha no ha manejado los bucles () hasta la fecha, por lo que tiendo a evitar aquellos en los que pueda.
Como mencioné, he hecho sombreadores de convolución como este en mi marco de código abierto GPUImage iOS. Para un filtro de convolución genérico, uso el siguiente sombreado de vértices:
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
uniform highp float texelWidth;
uniform highp float texelHeight;
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 topLeftTextureCoordinate;
varying vec2 topRightTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying vec2 bottomLeftTextureCoordinate;
varying vec2 bottomRightTextureCoordinate;
void main()
{
gl_Position = position;
vec2 widthStep = vec2(texelWidth, 0.0);
vec2 heightStep = vec2(0.0, texelHeight);
vec2 widthHeightStep = vec2(texelWidth, texelHeight);
vec2 widthNegativeHeightStep = vec2(texelWidth, -texelHeight);
textureCoordinate = inputTextureCoordinate.xy;
leftTextureCoordinate = inputTextureCoordinate.xy - widthStep;
rightTextureCoordinate = inputTextureCoordinate.xy + widthStep;
topTextureCoordinate = inputTextureCoordinate.xy - heightStep;
topLeftTextureCoordinate = inputTextureCoordinate.xy - widthHeightStep;
topRightTextureCoordinate = inputTextureCoordinate.xy + widthNegativeHeightStep;
bottomTextureCoordinate = inputTextureCoordinate.xy + heightStep;
bottomLeftTextureCoordinate = inputTextureCoordinate.xy - widthNegativeHeightStep;
bottomRightTextureCoordinate = inputTextureCoordinate.xy + widthHeightStep;
}
y el siguiente fragmento de sombreado:
precision highp float;
uniform sampler2D inputImageTexture;
uniform mediump mat3 convolutionMatrix;
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 topLeftTextureCoordinate;
varying vec2 topRightTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying vec2 bottomLeftTextureCoordinate;
varying vec2 bottomRightTextureCoordinate;
void main()
{
mediump vec4 bottomColor = texture2D(inputImageTexture, bottomTextureCoordinate);
mediump vec4 bottomLeftColor = texture2D(inputImageTexture, bottomLeftTextureCoordinate);
mediump vec4 bottomRightColor = texture2D(inputImageTexture, bottomRightTextureCoordinate);
mediump vec4 centerColor = texture2D(inputImageTexture, textureCoordinate);
mediump vec4 leftColor = texture2D(inputImageTexture, leftTextureCoordinate);
mediump vec4 rightColor = texture2D(inputImageTexture, rightTextureCoordinate);
mediump vec4 topColor = texture2D(inputImageTexture, topTextureCoordinate);
mediump vec4 topRightColor = texture2D(inputImageTexture, topRightTextureCoordinate);
mediump vec4 topLeftColor = texture2D(inputImageTexture, topLeftTextureCoordinate);
mediump vec4 resultColor = topLeftColor * convolutionMatrix[0][0] + topColor * convolutionMatrix[0][1] + topRightColor * convolutionMatrix[0][2];
resultColor += leftColor * convolutionMatrix[1][0] + centerColor * convolutionMatrix[1][1] + rightColor * convolutionMatrix[1][2];
resultColor += bottomLeftColor * convolutionMatrix[2][0] + bottomColor * convolutionMatrix[2][1] + bottomRightColor * convolutionMatrix[2][2];
gl_FragColor = resultColor;
}
Los uniformes texelWidth
y texelHeight
son la inversa del ancho y la altura de la imagen de entrada, y el uniforme de convolutionMatrix
especifica los pesos de las diversas muestras en su convolución.
En un iPhone 4, esto se ejecuta en 4-8 ms para un cuadro de video de cámara de 640x480, que es lo suficientemente bueno para una representación de 60 FPS en ese tamaño de imagen. Si solo necesita hacer algo como la detección de bordes, puede simplificar lo anterior, convertir la imagen a luminancia en una pre-pasada, luego solo muestrear de un canal de color. Eso es aún más rápido, a aproximadamente 2 ms por fotograma en el mismo dispositivo.
La única forma que conozco de reducir el tiempo empleado en este sombreado es reduciendo el número de recuperaciones de texturas. Debido a que su sombreado muestrea texturas de puntos espaciados de manera equitativa alrededor de los píxeles centrales y los combina linealmente, puede reducir el número de capturas haciendo uso del modo GL_LINEAR disponible para el muestreo de texturas.
Básicamente, en lugar de muestrear en cada texel, muestre entre un par de texels para obtener directamente una suma ponderada linealmente.
Llamemos al muestreo en offset (-stepw, -steph) y (-stepw, 0) como x0 y x1 respectivamente. Entonces tu suma es
sum = x0*k0 + x1*k1
Ahora, en cambio, si muestrea entre estos dos texeles, a una distancia de k0/(k0+k1)
de x0 y, por lo tanto, k1/(k0+k1)
de x1, la GPU realizará la ponderación lineal durante la búsqueda y le dará ,
y = x1*k1/(k0+k1) + x0*k0/(k1+k0)
Así, la suma se puede calcular como
sum = y*(k0 + k1)
de una sola búsqueda!
Si repite esto para los otros píxeles adyacentes, terminará haciendo 4 recuperaciones de textura para cada una de las compensaciones adyacentes, y una recuperación de textura adicional para el píxel central.
El link explica esto mucho mejor.