Низкая производительность шейдеров на iPad 1-го поколения - PullRequest
1 голос
/ 06 марта 2012

У меня есть мое приложение для рисования, которое написано с использованием OpenGL ES 1.0 и некоторого Quartz. Я пытаюсь переписать его, используя OpenGL ES 2.0 для повышения производительности и новых функций. Я написал 2 шейдера: один отображает входные данные пользователя для текстуры, а второй смешивает эту текстуру с некоторыми другими текстурами в соответствии с некоторыми правилами. Внезапно я понял, что второй шейдер работает слишком долго на iPad 1-го поколения - у меня только 10-15 кадров в секунду. iPad 2 отлично работает с 60+ кадров в секунду. Я был слегка шокирован, потому что оригинальное приложение (OpenGL ES 1.0) отлично работает на обоих устройствах. Он отображает только два полигона (но почти на весь экран). Я попробовал некоторые оптимизации, такие как изменение точности, прокомментировал некоторые математические операции, жестко закодировал некоторые вызовы текстур - это немного помогло, но я все еще далек от 60 кадров в секунду. Только когда я полностью комментирую вызов этого шейдера, у меня 60 кадров в секунду.

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

=============== Vertex Shader ===================

uniform mat4 modelViewProjectionMatrix;

attribute vec3 position;
attribute vec2 texCoords;

varying vec2 fTexCoords;

void main()

{ 

    fTexCoords = texCoords;

    vec4 postmp = vec4(position.xyz, 1.0);
    gl_Position = modelViewProjectionMatrix * postmp;


}

=============== Фрагмент шейдера ================== *

        precision highp float;  

        varying lowp vec4 colorVarying;
        varying highp vec2 fTexCoords;
        uniform sampler2D texture; // black & white user should paint
        uniform sampler2D drawingTexture; // texture with user drawings I rendered earlier
        uniform sampler2D paperTexture; // texture of sheet of paper 
        uniform float currentArea; // which area we should not shadow
        uniform float isShadowingOn; // bool - should we shadow some areas of picture    

        void main()
        {
            // I pass 1024*1024 texture here but I only need 560*800 so I do some calculations to find real texture coordinates

            vec2 convertedTexCoords = vec2(fTexCoords.x * 560.0/1024.0, fTexCoords.y * 800.0/1024.0); 

            vec4 bgImageColor = texture2D(texture, convertedTexCoords);        
            float area = bgImageColor.a;        
            bgImageColor.a = 1.0;            
            vec4 paperColor = texture2D(paperTexture, convertedTexCoords);       
            vec4 drawingColor = texture2D(drawingTexture, convertedTexCoords);

    // if special area         
            if ( abs(area - 1.0) < 0.0001) {            
                // if shadowing ON        
                if (isShadowingOn == 1.0) {               
                   // if color of original image is black        
                    if ( (bgImageColor.r < 0.1) && (bgImageColor.g < 0.1) && (bgImageColor.b < 0.1) ) {        
                        gl_FragColor = vec4(bgImageColor.rgb, 1.0) * vec4(0.5, 0.5, 0.5, 1.0);         
                    }                    
                    // if color of original image is grey

                    else if ( abs(bgImageColor.r - bgImageColor.g) < 0.15 && abs(bgImageColor.r - bgImageColor.b) < 0.15 && abs(bgImageColor.g - bgImageColor.b) < 0.15 && bgImageColor.r < 0.8 && bgImageColor.g < 0.8 && bgImageColor.b < 0.8){   gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb * 0.4 - drawingColor.rgb * 0.4, 1.0);} 


                 else 
                 {    
                 gl_FragColor = vec4(bgImageColor.rgb, 1.0) * vec4(0.5, 0.5, 0.5, 1.0);     
                    }
                } 

                // if shadowing is OFF        
                else {           
                    // if color of original image is black    
                if ( (bgImageColor.r < 0.1) && (bgImageColor.g < 0.1) && (bgImageColor.b < 0.1) ) {
                    gl_FragColor = vec4(bgImageColor.rgb, 1.0); 
                } 

                    // if color of original image is gray
                else if ( abs(bgImageColor.r - bgImageColor.g) < 0.15 && abs(bgImageColor.r - bgImageColor.b) < 0.15 && abs(bgImageColor.g - bgImageColor.b) < 0.15 
                 && bgImageColor.r < 0.8 && bgImageColor.g < 0.8 && bgImageColor.b < 0.8){
                    gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb * 0.4 - drawingColor.rgb * 0.4, 1.0);

                    } 

                    // rest
                else {
                    gl_FragColor = vec4(bgImageColor.rgb, 1.0); 
                }


                }
            } 

    // if area of fragment is equal to current area
        else if ( abs(area-currentArea/255.0) < 0.0001 ) { 
            gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb - drawingColor.rgb, 1.0); 
        }

    // if area of fragment is NOT equal to current area 
        else {
            if (isShadowingOn == 1.0) {
                gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb - drawingColor.rgb, 1.0) * vec4(0.5, 0.5, 0.5, 1.0);        
            } else {
                gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb - drawingColor.rgb, 1.0);
            }
        }
    }

Ответы [ 2 ]

2 голосов
/ 06 марта 2012

Ветвление действительно дорого делать в шейдере, так как это устраняет возможности для графического процессора для параллельного запуска шейдера, и у вас много ветвей в вашем фрагментном шейдере (тот шейдер, которыйдолжно быть как можно быстрее).Хуже того, вы выполняете ветвление на основе значений, рассчитанных на самом графическом процессоре, что также резко снижает производительность.

Вы действительно должны попытаться удалить как можно больше веток, вместо этого пусть GPU выполняет некоторую «дополнительную работу», например.не пытаясь оптимизировать текстурный атлас и визуализировать все (если это возможно), это все равно будет быстрее, чем ваша текущая версия.Если это не сработает, попробуйте разделить ваш шейдер на несколько меньших шейдеров, каждый из которых выполняет только определенную часть более крупного шейдера и выполняет ветвление на ЦП, а не на ГП (вам нужно делать это только один раз за вызов отрисовки ине для каждого "пикселя").

0 голосов
/ 07 марта 2012

Помимо правильной точки зрения JustSid по поводу ветвления в шейдере, я вижу здесь несколько других вещей не так. Во-первых, если я просто запущу этот фрагментный шейдер через PVRUniSco Editor от Imagination Texhnologies (который вы действительно должны получить и является частью их бесплатного SDK ), я вижу это:

PVRUniScoEditor results

, которая показывает лучшую производительность 42 цикла, худшую из 52 для этого шейдера. Из подобного случая настройки фрагментного шейдера, о котором я спрашивал о , я обнаружил, что фрагментный шейдер с 11-16 циклами рендерился на iPad 1 (15 - 29 FPS) за 35-68 мс. Вам нужно будет сделать это намного жестче, чтобы получить разумное время для рендеринга.

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

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

Есть также небольшие вещи, которые вы можете сделать, чтобы настроить это. Например,

gl_FragColor = vec4((paperColor.rgb * bgImageColor.rgb - drawingColor.rgb) * 0.4, 1.0);

должен быть немного быстрее, чем

gl_FragColor = vec4(paperColor.rgb * bgImageColor.rgb * 0.4 - drawingColor.rgb * 0.4, 1.0);

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

...