Оптимизация производительности тяжелого фрагментного шейдера

Мне нужна помощь в оптимизации следующего набора шейдеров:

Вершина:

    precision mediump float;

uniform vec2 rubyTextureSize;

attribute vec4 vPosition;
attribute vec2 a_TexCoordinate;

varying vec2 tc;

void main() {
    gl_Position = vPosition;

    tc = a_TexCoordinate;
}

Фрагмент:

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

Шейдеры получают 2D-текстуру и предназначены для ее красивого масштабирования на 2D-поверхности высокого разрешения (экране устройства). Это оптимизация алгоритма масштабирования SABR, если это имеет значение.

Он уже работает и хорошо работает на устройствах очень высокого класса (например, LG Nexus 4), но очень медленно работает на более слабых устройствах.

Устройства, которые действительно важны для меня, — это Samsung Galaxy S 2\3 с графическим процессором Mali 400MP, которые ужасно работают с этим шейдером.

До сих пор я пробовал:

  1. Устранение вариаций (совет из руководства ARM Mali) - небольшое улучшение.
  2. Переопределение функций mix() моими собственными - не помогло.
  3. уменьшив точность float до lowp - ничего не изменилось.

Я измеряю производительность, вычисляя время рендеринга (до и после eglSwapBuffers) — это дает мне очень линейное и последовательное измерение производительности.

Кроме того, я действительно не знаю, где искать или что здесь можно оптимизировать...

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

ОБНОВЛЕНИЕ

  1. Я обнаружил, что если я делаю все выборки текстур с постоянным вектором вместо зависимых векторов, я получаю значительное улучшение производительности, так что это, очевидно, большое узкое место - вероятно, из-за кеша. Тем не менее, мне все еще нужно сделать эти выборки. Я играл, выполняя, по крайней мере, некоторые из выборок с вариациями vec2 (без каких-либо заморочек), но это ничего не улучшило. Интересно, что может быть хорошим способом эффективно опросить 21 тексел.

  2. Я обнаружил, что большая часть вычислений выполняется несколько раз с одним и тем же набором текселей, потому что результат масштабируется как минимум в 2 раза, а я опрашиваю с помощью GL_NEAREST. Там как минимум 4 фрагмента, которые попадают на абсолютно одинаковые тексели. Если масштабирование x4 на устройстве с высоким разрешением, на одни и те же тексели приходится 16 фрагментов — это большая трата. Есть ли способ выполнить дополнительный проход шейдера, который вычислит все значения, которые не изменяются в нескольких фрагментах? Я думал о рендеринге в дополнительную текстуру за пределами экрана, но мне нужно хранить несколько значений для каждого текселя, а не только одно.

ОБНОВЛЕНИЕ

  1. Пытался упростить логические выражения, используя известные логические правила - сэкономил мне несколько операций, но не повлиял на производительность.

ОБНОВЛЕНИЕ

  1. Думал о способе передачи вычислений в вершинный шейдер — просто есть «геометрия», создающая мой полноэкранный режим, но с множеством вершин, соответствующих каждому исходному пикселю перед масштабированием. Например, если моя исходная текстура имеет размер 320x200, а целевой экран — 1280x800, будут равномерно распределены вершины размером 320x200. Затем выполните большую часть вычислений в этих вершинах. Проблема в том, что мои целевые устройства (S2\S3) не поддерживают выборку текстур вершин.

ОБНОВЛЕНИЕ

  1. Сравнение производительности LG Nexus 4 и Samsung Galaxy S3 показывает, что Nexus 4 работает более чем в 10 раз быстрее. Как это может быть? Это 2 устройства одного поколения, одинакового разрешения и т. д. Может ли Mali 400MP быть действительно плохим в определенных ситуациях? Я уверен, что есть что-то очень специфическое, что заставляет его работать так медленно по сравнению с Nexus 4 (но пока не нашел что).

person SirKnigget    schedule 11.05.2013    source источник
comment
У меня нет времени читать всю программу прямо сейчас, но как вы думаете, вы можете сделать несколько проходов и смешивать между проходами? Я не думаю, что это может что-то ускорить, но, по крайней мере, это может освободить некоторые ядра шейдеров... Проблема удаления переменных заключается в том, что труднее предсказать поиск текстур (хотя это дало вам ускорение)   -  person Trax    schedule 12.05.2013
comment
Как вы думаете, вы можете отделить фильтр? Похоже, вы выполняете там свертку, если ядро ​​​​можно разделить на вертикальные и горизонтальные проходы, вам нужно выполнять не 21 поиск, а 10.   -  person Trax    schedule 12.05.2013
comment
Привет, Trax, не могли бы вы поподробнее рассказать о разделении фильтра? Как бы вы отобрали необходимые 21 тексел с 10 поисками?   -  person SirKnigget    schedule 12.05.2013
comment
По поводу нескольких проходов - я думал об этом (в обновлении), но есть несколько промежуточных значений перед окончательным выводом цвета - как тут можно сделать несколько проходов?   -  person SirKnigget    schedule 12.05.2013
comment
Под разделяемым фильтром я имею в виду, если вы можете разбить его, сначала вычислив горизонтальные соседи-›выход, и использовать его в качестве входных данных для вертикального прохода. Но, как вы указали, существует много взаимозависимых продуктов.   -  person Trax    schedule 12.05.2013
comment
Что касается вопроса о нескольких поисковых запросах, попадающих в один и тот же тексель... беспокоиться об этом означает пытаться оптимизировать то, что уже является лучшим случаем. Двойные нажатия будут попаданием в кеш текстуры и уже будут быстрыми. Это долгий путь к памяти для заполнения кеша текстур и, возможно, немного больше работы для распаковки и преобразования формата, которые занимают время в промахах кеша текстур.   -  person ahcox    schedule 20.05.2013
comment
Это оптимизация алгоритма масштабирования SABR, если это имеет значение. У вас есть ссылка на рассматриваемый алгоритм?   -  person ahcox    schedule 20.05.2013
comment
Эта ссылка дает общее представление: board.byuu.org/viewtopic.php?f=10&t=2248, но это не та реализация, которую я здесь использую.   -  person SirKnigget    schedule 21.05.2013


Ответы (3)


По моему опыту, производительность мобильного графического процессора примерно пропорциональна количеству texture2D вызовов. У вас есть 21, что действительно много. Как правило, поиск в памяти происходит в сотни раз медленнее, чем вычисления, поэтому вы можете выполнять много вычислений и по-прежнему испытывать затруднения при поиске текстур. (Это также означает, что оптимизация остальной части вашего кода, вероятно, будет иметь небольшой эффект, поскольку это означает, что вместо того, чтобы быть занятым, пока он ожидает поиска текстуры, он будет бездействовать, пока ожидает поиска текстуры.) Таким образом, вам нужно уменьшить количество текстур2D, которые вы вызываете.

Трудно сказать, как это сделать, так как я не очень понимаю ваш шейдер, но некоторые идеи:

  • разделите его на горизонтальный проход, затем вертикальный проход. Это работает только для некоторых шейдеров, например. размывает, но это может серьезно сократить количество обращений к текстуре. Например, размытие по Гауссу 5x5 наивно выполняет 25 поисковых запросов текстуры; если сделать по горизонтали, а затем по вертикали, он использует только 10.
  • используйте линейную фильтрацию, чтобы «обмануть». Если вы сэмплируете ровно между 4 пикселями вместо середины 1 пикселя с включенной линейной фильтрацией, вы бесплатно получаете среднее значение всех 4 пикселей. Однако я не знаю, как это влияет на ваш шейдер. В примере с размытием снова использование линейной фильтрации для выборки двух пикселей одновременно по обе стороны от среднего пикселя позволяет вам сэмплировать 5 пикселей с 3 вызовами texture2D, уменьшая размытие 5x5 до всего 6 выборок как по горизонтали, так и по вертикали.
  • просто используйте меньшее ядро ​​(так что вы не берете так много образцов). Это влияет на качество, поэтому вам, вероятно, понадобится какой-то способ определить производительность устройства и переключиться на шейдер более низкого качества, когда устройство кажется медленным.
person AshleysBrain    schedule 18.05.2013
comment
Спасибо - я понимаю предложения, но не знаю, как применить их к конкретному шейдеру. Использование двух проходов или линейной фильтрации, по-видимому, не дает требуемых здесь промежуточных значений. У меня уже есть некачественное решение в качестве запасного варианта. - person SirKnigget; 18.05.2013
comment
Я подумал о неортодоксальном способе устранения моих вызовов texture2D — рисовать сетку вершин вместо одного четырехугольника и передавать каждому из них атрибуты, содержащие все необходимые тексели. Это устранило бы всю выборку текстур, но создало бы большой буфер отрисовки для каждого кадра. Прежде чем я попытаюсь реализовать это длинное решение, какой-нибудь совет? - person SirKnigget; 18.05.2013
comment
Определенно избегайте сетки вершин. Вы бы заменили работу, выполняемую аппаратным обеспечением с фиксированными функциями на графическом процессоре, работой на процессоре на той же_частоте... большая потеря. Чтобы было ясно, в общем, перенос работы по конвейеру на ЦП может быть выигрышным в случае, например, грубой отбраковки, когда небольшая работа на ЦП позволяет избежать много работы на ГП, но это не применимо здесь. - person ahcox; 20.05.2013
comment
@ahcox - А как насчет того факта, что: 1. Мой процессор на 0% во время этого выполнения, а GPU является большим узким местом, и 2. Все, что делает этот фрагментный шейдер, будет выполняться в 4-16 раз меньше, чем сейчас (в зависимости от масштабирования фактор). - person SirKnigget; 21.05.2013
comment
@SirKnigget Это начинает выглядеть как совершенно другой алгоритм. В настоящее время это делает сбор на выходной пиксель. Звучит так, как будто вы собираетесь делать разброс для каждого входа от вершинного шейдера. Я даже не вижу эффективного алгоритма с той инверсией задачи, которая может быть выражена в терминах вершинных и фрагментных программ. - person ahcox; 21.05.2013
comment
Будет тот же алгоритм. Сбор на выходной пиксель выполняет смещение в соответствии с исходным размером пикселя текстуры, а фильтрация — GL_NEAREST. Так эффективно на одни и те же исходные тексели ложится куча разных фрагментов. Тем не менее, некоторые вычисления будут относиться к каждому фрагменту и будут выполняться в шейдере фрагментов. - person SirKnigget; 21.05.2013

Есть пара странностей Mali-400, о которых вам, возможно, следует знать:

  • Вы действительно должны использовать вариации без какого-либо swizzling для поиска текстур (т.е. вычисление «xyp_1_2_3.xw» и т. д. в вершинном шейдере и использовать одну вариацию для поиска текстуры вместо их swizzling).
  • При определенном количестве инструкций (к сожалению, NDA не позволяет мне раскрыть это число) производительность довольно сильно падает. Вы можете получить количество инструкций от автономного компилятора. Чтобы исправить это, вы можете разделить свой шейдер на несколько более мелких и использовать недокументированное расширение GL_ARM_framebuffer_read-extension для чтения результата предыдущего. (Кажется, Google может подсказать вам, как его использовать. Небольшой поиск в двоичных файлах автономного шейдерного компилятора также может помочь)
person kusma    schedule 19.05.2013
comment
Я попытался использовать несколько переменных vec2 для поиска (как можно больше вариантов) и выполнить остальные поиски обычным способом. Просто стало медленнее... - person SirKnigget; 19.05.2013
comment
На самом деле vec2 больше касается точности, чем производительности. В противном случае изменение должно проходить через регистр fp16. - person kusma; 19.05.2013
comment
Как вы предлагаете применить то, что вы сказали? Я проверил максимальные вариации на этом графическом процессоре, использовал их все (как vec2) для передачи координат текстуры и сэмплировал с ними, а остальные текселы сэмплировал с обычными вычислениями смещения. Не помогло, но может я что-то не так сделал. - person SirKnigget; 19.05.2013
comment
Последний из двух моих пунктов, вероятно, более актуален для вашего вопроса. Первый момент (как я уже сказал) касается точности, а не производительности. В некоторых случаях это также может помочь повысить производительность, но это зависит от вашего количества вершин и т. д. - person kusma; 19.05.2013
comment
Спасибо, я посмотрю на это. Упомянутое вами расширение применимо к большинству графических процессоров или только к серии Mali? - person SirKnigget; 20.05.2013
comment
Немного погуглил - разве расширение, о котором вы упомянули, не просто способ использовать FBO в более старых версиях API? - person SirKnigget; 20.05.2013
comment
Нет, расширение является недокументированным расширением, зависящим от поставщика, для чтения предыдущего содержимого текущего фрагмента. Это применимо только к серии Mali, но у некоторых других поставщиков есть аналогичные расширения. - person kusma; 20.05.2013
comment
Я не нашел никакой документации через Google. Вы можете связать? - person SirKnigget; 20.05.2013
comment
Я не понимаю, что мне с этим делать. Это считается документацией? - person SirKnigget; 21.05.2013
comment
Нет, я не считаю это задокументированным, поэтому я все время говорил о недокументированном. Я сказал, что вы можете узнать, как его использовать через Google, и я имел в виду ссылку выше, которая действительно показывает шейдер, который его использует. - person kusma; 21.05.2013

Верхняя граница производительности фрагментного шейдера (нижняя граница времени выполнения) определяется 21 загрузкой текстуры и одной записью в буфер кадра (gl_FragColor =). Было бы целесообразно создать фрагментный шейдер, который просто выполняет 21 загрузку, накапливая результат каждой загрузки в один vec4 и затем записывая его. Если вы запустите этот шейдер на своем проблемном целевом оборудовании, вы будете знать разницу между тем, где находится ваш более сложный шейдер, и его максимальной потенциальной производительностью на этих конкретных версиях графического процессора/драйвера/платформы. Ваш реальный шейдер может быть только медленнее, поэтому, если этот простой тестовый шейдер сам по себе слишком медленный, вам придется искать решение в другом месте.

После того, как эта базовая линия будет установлена, у меня будет только расплывчатый совет по улучшению шейдера, который вас действительно интересует, но, возможно, мои рассуждения представляют интерес. Я вижу, что в вашем коде все загрузки текстур сгруппированы вверху. На аппаратном уровне загрузка текстур имеет очень большую задержку, но шейдерные процессоры графического процессора могут выполнять другие действия, пока они находятся в процессе выполнения, в том числе запускать другие потоки в том же блоке работы. В широком смысле это означает, что окончательный двоичный файл шейдера, в котором много арифметической работы распределено между нагрузками, будет выполнять арифметическую работу бесплатно в тени нагрузок, а также что шейдерная программа, использующая небольшое количество регистров, позволит запускать множество потоков в одном месте. В то же время каждый поток потенциально выполняет свою арифметическую работу, в то время как другие потоки блокируют загрузку текселей. Будем надеяться, что любой компилятор шейдеров переместит ваш код для достижения необходимого чередования. Тем не менее, помочь ему не помешает, и поэтому:

  • Попробуйте переместить каждое арифметическое выражение вверх (лексически) в файле настолько высоко, насколько это возможно без нарушения. Это может помочь распределить нагрузку, если компилятор упустил какую-то хитрость.
  • Старайтесь использовать все промежуточные результаты как можно раньше, чтобы компилятор мог распознать, что их переменные мертвы, и тем самым освободить регистры. Это может сократить использование регистров и тем самым увеличить загруженность вашей программы. Одна из идей для достижения этого эффекта, если у вас есть набор частичных результатов, которые суммируются в конце, состоит в том, чтобы преобразовать окончательное суммирование многих переменных, содержащих частичные результаты, в накопление в одну переменную по мере создания каждого частичного результата.

Как всегда с производительностью, YMMV

person ahcox    schedule 20.05.2013
comment
Что касается операторов арифметики перемещения - большая часть арифметики там зависит от выбранных текселей (за исключением нескольких). Посоветуете распределить равномерно? (например: чтение текселей 7, 11, 17, 133, вычисления, чтение текселей 8, 6, 16, 18, вычисления и т. д.) - person SirKnigget; 21.05.2013