5

次のシェーダー セットの最適化についてサポートが必要です。

バーテックス:

    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 など) では問題なく動作しますが、弱いデバイスでは非常に遅くなります。

私にとって本当に重要なデバイスは、Mali 400MP GPU を搭載した Samsung Galaxy S 2 \ 3 です。

これまでのところ、私は試しました:

  1. バリエーションの排除 (ARM の Mali ガイドからのアドバイス) - マイナーな改善が行われました。
  2. 自分の mix() 関数をオーバーライドする - うまくいきませんでした。
  3. float の精度を lowp に下げます - 何も変わりませんでした。

レンダリング時間 (eglSwapBuffers の前後) を計算することでパフォーマンスを測定します。これにより、非常に直線的で一貫したパフォーマンスの測定値が得られます。

それを超えて、どこを見ればいいのか、ここで何を最適化できるのか本当にわかりません...

これが重いアルゴリズムであることは承知しており、使用する別のスケーリング方法についてアドバイスを求めているわけではありません。多くの方法を試しましたが、このアルゴリズムが最良の視覚的結果をもたらします。最適化された方法でまったく同じアルゴリズムを使用したいと考えています。

アップデート

  1. 従属ベクトルではなく定数ベクトルを使用してすべてのテクスチャ フェッチを行うと、パフォーマンスが大幅に向上することがわかりました。これは明らかに大きなボトルネックです。おそらくキャッシュが原因です。ただし、これらのフェッチを行う必要があります。私は少なくともいくつかのフェッチを vec2 変数で (スウィズリングなしで) やってみましたが、何も改善しませんでした。21 のテクセルを効率的にポーリングするにはどうすればよいでしょうか。

  2. 出力が少なくとも x2 でスケーリングされ、GL_NEAREST でポーリングするため、計算の大部分がまったく同じ一連のテクセルで複数回行われていることがわかりました。まったく同じテクセルに該当するフラグメントが少なくとも 4 つあります。高解像度デバイスでスケーリングが x4 の場合、同じテクセルに 16 個のフラグメントが存在します。これは大きな無駄です。複数のフラグメント間で変化しないすべての値を計算する追加のシェーダー パスを実行する方法はありますか? 追加のオフスクリーン テクスチャにレンダリングすることを考えましたが、テクセルごとに 1 つだけでなく複数の値を格納する必要があります。

アップデート

  1. 既知のブール規則を使用してブール式を単純化しようとしました-いくつかの操作を節約しましたが、パフォーマンスには何の影響もありませんでした.

アップデート

  1. 計算を頂点シェーダーに渡す方法について考えました。フルスクリーンを作成する「ジオメトリ」を持っているだけですが、スケーリング前の元の各ピクセルに対応する多くの頂点があります。たとえば、元のテクスチャが 320x200 で、ターゲット スクリーンが 1280x800 の場合、320x200 の頂点が均等に広がります。次に、それらの頂点でほとんどの計算を行います。問題は、ターゲット デバイス (S2 \ S3) が頂点テクスチャ サンプリングをサポートしていないことです。

アップデート

  1. LG Nexus 4 と Samsung Galaxy S3 でパフォーマンスを測定したところ、Nexus 4 の方が 10 倍以上高速であることがわかりました。どうすればいいの?これらは、同じ世代、同じ解像度などの 2 つのデバイスです。Mali 400MP は、特定の状況では本当に悪いのでしょうか? Nexus 4 と比較して動作が非常に遅くなる特定の何かがあると確信しています (ただし、まだ何が見つかりませんでした)。
4

3 に答える 3

2

注意が必要な Mali-400 の奇妙な点がいくつかあります。

  • 実際には、テクスチャ ルックアップにスウィズリングなしで可変を使用する必要があります (つまり、頂点シェーダーで「xyp_1_2_3.xw」などを計算し、スウィズルの代わりにテクスチャ ルックアップごとに 1 つの可変を使用します)。
  • 特定の数の命令 (残念ながら NDA により、この数を明らかにすることはできません) で、パフォーマンスが大幅に低下します。オフライン コンパイラから命令数を取得できます。これを解決するには、シェーダーを複数の小さなシェーダーに分割し、文書化されていない GL_ARM_framebuffer_read-extension を使用して前のシェーダーの結果を読み取ることができる場合があります。(Google がその使用方法を教えてくれるようです。オフラインのシェーダー コンパイラのバイナリを少し grep することも役立つかもしれません)
于 2013-05-19T10:42:32.857 に答える
0

フラグメント シェーダーのパフォーマンスの上限 (実行時間の下限) は、21 回のテクスチャ読み込みとフレームバッファーへの 1 回の書き込みによって設定されます ( gl_FragColor =)。単純に 21 回のロードを実行し、各ロードの結果を 1 つの vec4 に蓄積してからそれを書き出すフラグメント シェーダーを構築することは価値があります。問題のあるターゲット ハードウェアでこのシェーダーを実行すると、より複雑なシェーダーの位置と、特定の GPU / ドライバー / プラットフォーム リビジョンでの潜在的な最大パフォーマンスとの間の差分がわかります。実際のシェーダーはそれよりも遅くなる可能性があるため、この単純なテスト シェーダー自体が遅すぎる場合は、解決策を探す必要があります。

そのベースラインが確立されると、実際に興味のあるシェーダーを改善するための漠然としたアドバイスしかありませんが、おそらく私の推論は興味深いものです. あなたのコードには、すべてのテクスチャ ロードが上部にまとめられていることがわかります。ハードウェア レベルでは、テクスチャの読み込みのレイテンシは非常に長くなりますが、GPU シェーダー プロセッサは、処理中に他の処理を実行できます (同じ作業ブロックで他のスレッドを実行するなど)。これは、大まかに言うと、多くの演算作業がロード間に分散されている最終的なシェーダー バイナリが、ロードの影で演算作業を無料で実行し、少数のレジスタを使用するシェーダー プログラムが多くのスレッドを実行できるようにすることを意味します。同時に、他のスレッドがテクセルのロードをブロックしている間に、各スレッドが算術作業を実行する可能性があります。うまくいけば、どのシェーダー コンパイラーも、必要なインターリーブを実現するためにコードを移動します。ただし、手を差し伸べても害はありません。したがって、次のようになります。

  • ファイル内の各算術ステートメントを (字句的に) 上に、壊れずにできるだけ上に移動するようにしてください。これ、コンパイラがトリックを見逃した場合に負荷を分散するのに役立ちます。
  • すべての中間結果をできるだけ早く使用して、コンパイラがそれらの変数が無効であることを認識し、レジスタを解放できるようにします。これにより、レジスターの使用量が減るため、プログラムの占有率が高くなる可能性があります。最後に合計される部分的な結果がたくさんある場合、この効果を実現するための 1 つのアイデアは、部分的な結果が生成されるたびに、部分的な結果を保持する多くの変数の最終的な合計を単一の変数に累積するように変換することです。

いつものようにパフォーマンス、YMMV

于 2013-05-20T19:01:14.070 に答える