Подтвердить что ты не робот

Простой свершильный шейдер GLSL ужасно медленный

Я пытаюсь реализовать 2D контурный шейдер в OpenGL ES2.0 для iOS. Это безумно медленно. Как в 5fps медленно. Я отследил его до вызовов texture2D(). Тем не менее, без них любой сверточный шейдер отменяется. Я пробовал использовать lowp вместо mediump, но при этом все просто черное, хотя оно дает еще 5 кадров в секунду, но оно все еще непригодно.

Вот мой шейдер фрагмента.

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

Это неоптимизировано и не завершено, но мне нужно повысить производительность, прежде чем продолжить. Я попытался заменить вызов texture2D() в цикле с помощью только сплошного vec4, и он не запускает никаких проблем, несмотря на все остальное.

Как я могу это оптимизировать? Я знаю, это возможно, потому что я видел, как больше задействованных эффектов в 3D работает без проблем. Я не понимаю, почему это вызывает какие-либо проблемы.

4b9b3361

Ответ 1

Я сделал эту самую вещь сам, и я вижу несколько вещей, которые можно было бы оптимизировать здесь.

Во-первых, я бы удалил условный enableTexture и вместо этого разделил ваш шейдер на две программы, один для истинного состояния этого и один для false. Условные обозначения очень дороги в шейдерах фрагментов iOS, особенно в тех, которые имеют текстуры в них.

Во-вторых, здесь есть девять зависимых текстур. Это текстуры, в которых координаты текстуры вычисляются внутри шейдера фрагмента. Зависимые чтения текстур очень велики на графических процессорах PowerVR в устройствах iOS, поскольку они не позволяют аппаратным средствам оптимизировать считывание текстуры с использованием кеширования и т.д. Поскольку вы взяли выборку из фиксированного смещения для 8 окружающих пикселей и одного центрального, эти вычисления должны быть переместился в вершинный шейдер. Это также означает, что эти вычисления не должны выполняться для каждого пикселя, только один раз для каждой вершины, а затем аппаратная интерполяция будет обрабатывать остальные.

В-третьих, для циклов for() все обработанные шейдерным компилятором iOS не были обработаны до настоящего времени, поэтому я стараюсь избегать тех, где могу.

Как я уже упоминал, я сделал сверточные шейдеры, как это, в моем open-source iOS GPUImage framework. Для общего фильтра свертки я использую следующий вершинный шейдер:

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

и следующий шейдер фрагмента:

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

Формы texelWidth и texelHeight являются инверсией ширины и высоты входного изображения, а convolutionMatrix uniform задает весы для различных образцов в вашей свертке.

На iPhone 4 это работает в 4-8 мс для кадра видеоизображения 640x480, что достаточно для 60 кадров в секунду при этом размере изображения. Если вам просто нужно сделать что-то вроде обнаружения края, вы можете упростить это, преобразовать изображение в яркость в предварительный проход, а затем только образец из одного цветового канала. Это еще быстрее, примерно на 2 мс на кадр на одном устройстве.

Ответ 2

Единственный способ уменьшить время, затрачиваемое на этот шейдер, - уменьшить количество выборки текстур. Так как ваш шейдер отображает текстуры с одинаково разнесенных точек вокруг центральных пикселей и линейно объединяет их, вы можете уменьшить количество выборки, используя доступ к параметру GL_LINEAR для выборки текстуры.

В основном вместо выборочной выборки в каждом текселе, образец между парами текселей, чтобы получить линейно взвешенную сумму.

Назовем выборку со смещением (-stepw, -steph) и (-stepw, 0) как x0 и x1 соответственно. Тогда ваша сумма

sum = x0*k0 + x1*k1

Теперь, если вы попробуете между этими двумя текселями, на расстоянии k0/(k0+k1) от x0 и, следовательно, k1/(k0+k1) от x1, тогда GPU выполнит линейное взвешивание во время выборки и даст вам

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

Таким образом, сумма может быть рассчитана как

sum = y*(k0 + k1) только из одной выборки!

Если вы повторите это для других соседних пикселей, вы в итоге сделаете 4 выборки текстур для каждого смежного смещения и одну дополнительную текстуру для центрального пикселя.

Ссылка объясняет это намного лучше