2012-09-18 12 views
15

Sto provando a implementare uno schema di contorno 2D in OpenGL ES2.0 per iOS. È follemente lento. Come in 5 fps lenti. L'ho rintracciato nelle chiamate a texture2D(). Tuttavia, senza questi shaver convoluzione è annullabile. Ho provato a usare lowp invece di mediump, ma con tutto ciò è solo nero, anche se fornisce un altro 5fps, ma è ancora inutilizzabile.Lo shader di convoluzione semplice GLSL è atrocemente lento

Ecco il mio frammento shader.

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; 
     } 
    } 

Questo è non ottimizzata, e non finalizzato, ma ho bisogno di portare le prestazioni prima di proseguire. Ho provato a sostituire la chiamata a texture2D() nel ciclo con un solido vec4 e non ha alcun problema, nonostante tutto il resto sta succedendo.

Come posso ottimizzare questo? So che è possibile perché ho visto che gli effetti 3D più coinvolti non hanno alcun problema. Non riesco a capire perché questo causi problemi.

+0

"* Ho provato a sostituire la chiamata a texture2D() nel ciclo solo con un solido vec4 e non ha problemi *" Che cosa significa? È diventato più veloce? Non ha cambiato le prestazioni? Quello che è successo? –

+0

"* Non riesco a capire perché questo causa problemi. *" Stai eseguendo * dieci accessi texture * per invocazione shader e non vedi cosa potrebbe causare un problema? Inoltre, accedi al centro texel due volte. –

+0

Ottengo un solido 60fps senza le ricerche di texture (escluso quello finale). Come ho detto, non è ottimizzato, ma non c'è modo di evitare quelle chiamate di texture. Il filtro non potrebbe funzionare diversamente. Ma ho visto molti giochi, mobili e non, che usano effetti basati sui filtri di convoluzione, e non sembrano avere alcun problema. A meno che non ci sia un trucco per evitarli? – user1137704

risposta

6

L'unico modo che conosco per ridurre il tempo impiegato in questo shader consiste nel ridurre il numero di recuperi di trama. Poiché lo shader campiona trame da punti equidistanti attorno ai pixel centrali e li combina in modo lineare, è possibile ridurre il numero di feti utilizzando la modalità GL_LINEAR disponibile per il campionamento della trama.

Fondamentalmente invece di campionare su ogni texel, campionare tra una coppia di texel per ottenere direttamente una somma ponderata linearmente.

Chiamiamo il campionamento in offset (-stepw, -steph) e (-stepw, 0) come x0 e x1 rispettivamente. Allora la vostra somma è

sum = x0*k0 + x1*k1

Ora, invece, se si assaggiare tra questi due texel, ad una distanza di k0/(k0+k1) da x0 e quindi k1/(k0+k1) da x1, allora la GPU eseguirà la ponderazione lineare durante il recupero e dati,

y = x1*k1/(k0+k1) + x0*k0/(k1+k0)

così somma può essere calcolato come

sum = y*(k0 + k1) da una sola raccolta!

Se si ripete questo per gli altri pixel adiacenti, si finirà per eseguire 4 recuperi di trama per ciascuno degli offset adiacenti e un recupero di trama aggiuntivo per il pixel centrale.

Il link spiega questo molto meglio

38

Ho fatto questo cosa esatta me stesso, e vedo diverse cose che potrebbero essere ottimizzati qui.

Prima di tutto, rimuoverei il condizionale enableTexture e dividere lo shader in due programmi, uno per lo stato vero di questo e uno per il falso. I condizionali sono molto costosi in shader di frammenti iOS, in particolare quelli che hanno letture di texture al loro interno.

In secondo luogo, si hanno nove letture della trama dipendenti qui. Si tratta di letture della trama in cui vengono calcolate le coordinate della trama all'interno dello shader del frammento. Le letture delle texture dipendenti sono molto costose sulle GPU PowerVR all'interno dei dispositivi iOS, perché impediscono che l'hardware ottimizzi le letture delle texture usando la cache, ecc. Poiché si esegue il campionamento da un offset fisso per gli 8 pixel circostanti e uno centrale, questi calcoli dovrebbero essere spostato verso il vertex shader.Questo significa anche che questi calcoli non dovranno essere eseguiti per ogni pixel, solo una volta per ogni vertice e quindi l'interpolazione hardware gestirà il resto.

In terzo luogo, per() i loop non sono stati gestiti molto bene dal compilatore di shader iOS fino ad oggi, quindi tendo ad evitare quelli dove posso.

Come ho già detto, ho realizzato gli ombreggiatori di convoluzione come questo nel mio framework open source iOS GPUImage. Per un filtro convoluzione generico, uso il seguente vertex shader:

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; 
} 

e la seguente Shader frammento:

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; 
} 

Il texelWidth e texelHeight uniformi sono l'inverso della larghezza e l'altezza dell'immagine in ingresso e l'uniforme convolutionMatrix specifica i pesi per i vari campioni nella convoluzione.

Su un iPhone 4, questo viene eseguito in 4-8 ms per un fotogramma video 640x480, che è abbastanza buono per il rendering di 60 FPS a quella dimensione dell'immagine. Se hai solo bisogno di fare qualcosa come il rilevamento dei bordi, puoi semplificare quanto sopra, convertire l'immagine in luminanza in un pre-passaggio, quindi campionare solo da un canale di colore. Questo è ancora più veloce, a circa 2 ms per frame sullo stesso dispositivo.

+0

Speciali ringraziamenti. Mi ha salvato !!! – hiepnd

+0

Ottimo esempio. tl; dr: ** evita letture di texture dipendenti **. Sforzo, inoltre, per testare le circonvoluzioni separabili mediante rendering in due passaggi al fine di ridurre il numero di feti (anche se per un esempio di 9 non si ridurrebbe a meno della metà di quello, quindi in questo caso un approccio a due passaggi potrebbe essere una cattiva idea) –

+1

@StevenLu - C'è una sorpresa sorprendentemente forte nelle prestazioni una volta superato oltre 9 letture di texture in un singolo passaggio su molte di queste GPU. La suddivisione in due passaggi può avere un impatto non lineare sulle prestazioni, rispetto al numero di campioni in un singolo passaggio. Ho provato, e l'esecuzione di questa operazione in un singolo passaggio è molto, molto più lenta della separazione del kernel, anche per questo piccolo numero di campioni. –

Problemi correlati