Mengoptimalkan kinerja shader fragmen yang berat


9

Saya perlu bantuan mengoptimalkan rangkaian shader berikut:

Puncak:

    precision mediump float;

uniform vec2 rubyTextureSize;

attribute vec4 vPosition;
attribute vec2 a_TexCoordinate;

varying vec2 tc;

void main() {
    gl_Position = vPosition;

    tc = a_TexCoordinate;
}

Pecahan:

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

Shader menerima tekstur 2D dan dimaksudkan untuk menskalakannya dengan indah di permukaan 2D beresolusi tinggi (layar perangkat). Ini adalah optimasi dari algoritma penskalaan SABR jika itu penting.

Ini sudah berfungsi, dan berkinerja OK pada perangkat Android yang sangat canggih (seperti LG Nexus 4), tetapi sangat lambat pada perangkat yang lebih lemah.

Perangkat Android yang sangat penting bagi saya adalah Samsung Galaxy S 2 \ 3, dengan Mali 400MP GPU - yang berkinerja sangat buruk dengan shader ini.

Sejauh ini saya sudah mencoba:

  1. Menghilangkan variasi (saran dari panduan Mali ARM) - melakukan perbaikan kecil.
  2. Overriding mix () berfungsi dengan saya sendiri - tidak bagus.
  3. mengurangi presisi float ke lowp - tidak mengubah apa pun.

Saya mengukur kinerja dengan menghitung waktu render (sebelum dan sesudah eglSwapBuffers) - ini memberi saya pengukuran kinerja yang sangat linier dan konsisten.

Selain itu, saya tidak benar-benar tahu ke mana harus mencari atau apa yang dapat dioptimalkan di sini ...

Saya tahu bahwa ini adalah algoritma yang berat, dan saya tidak meminta saran tentang metode penskalaan alternatif apa yang akan digunakan - Saya sudah mencoba banyak dan algoritma ini memberikan hasil visual terbaik. Saya ingin menggunakan algoritma yang sama persis dengan cara yang dioptimalkan.

MEMPERBARUI

  1. Saya menemukan bahwa jika saya melakukan semua tekstur mengambil dengan vektor konstan, bukan vektor tergantung saya mendapatkan peningkatan kinerja besar, jadi ini jelas merupakan hambatan besar - mungkin karena cache. Namun, saya masih perlu melakukan pengambilan itu. Saya bermain dengan melakukan setidaknya beberapa pengambilan dengan vec2 yang bervariasi (tanpa bualan) tetapi tidak meningkatkan apa pun. Saya bertanya-tanya apa yang mungkin merupakan cara yang baik untuk melakukan polling 21 texels secara efisien.

  2. Saya menemukan bahwa sebagian besar perhitungan sedang dilakukan beberapa kali dengan set texels yang persis sama - karena output diskalakan oleh setidaknya x2, dan saya polling dengan GL_NEAREST. Setidaknya ada 4 fragmen yang jatuh tepat di atas texels yang sama. Jika penskalaan x4 pada perangkat beresolusi tinggi, ada 16 fragmen yang jatuh pada texels yang sama - yang merupakan pemborosan besar. Apakah ada cara untuk melakukan pass shader tambahan yang akan menghitung semua nilai yang tidak berubah di beberapa fragmen? Saya berpikir untuk merender tekstur tambahan di luar layar, tetapi saya perlu menyimpan beberapa nilai per texel, bukan hanya satu.

MEMPERBARUI

  1. Saya juga memperhatikan bahwa CPU hampir tidak digunakan sementara GPU adalah hambatan besar. Adakah saran tentang bagaimana memanfaatkan daya CPU dan mentransfer logika dari GPU ke CPU dalam situasi ini?

2
Anda seharusnya tidak pernah mengambil tekstur sebagai pencarian. baik lulus uv dari vertex sehingga pixelshader punya waktu untuk mengambil tekstur.
Tordin

Bisakah Anda jelaskan? Apa maksudmu dengan uv?
SirKnigget

3
Bisakah Anda menautkan ke deskripsi "algoritma penskalaan SABR"? Google tidak menemukan sesuatu yang berguna tentangnya. Omong-omong, filter 21-texel (dan juga cukup-sangat-matematika) pada GPU seluler hanya meminta masalah. Saya tidak berpikir Anda dapat secara realistis berharap untuk membuatnya berjalan dengan baik tanpa mengurangi kualitas di suatu tempat.
Nathan Reed

Ini memberikan gagasan umum: board.byuu.org/viewtopic.php?f=10&t=2248 , meskipun itu bukan implementasi yang tepat yang saya temukan.
SirKnigget

2
Mengenai harapan yang realistis - ini bekerja dengan baik pada perangkat kelas atas. Saya berharap dapat memperbaiki apa yang saya miliki tentang faktor 5x atau serupa dan membuatnya bekerja pada perangkat yang lebih lemah.
SirKnigget

Jawaban:


2

Saya bertanya-tanya apa yang mungkin merupakan cara yang baik untuk melakukan polling 21 texels secara efisien.

Jawabannya adalah cara efisien adalah cara yang tidak polling 21 texels. Maaf sudah jelas tetapi perangkat seluler mungkin tidak memiliki lebar bus yang diperlukan untuk mendukung kernel tersebut. Anda perlu mengoptimalkan dengan mengurangi ukuran tekstur yang terpasang pada sampler sehingga caching akan mencakup radius kernel yang lebih besar.

Juga, Anda bisa melupakan kernel disk Anda dan menggunakan algoritma dua pass menggunakan kernel vertikal, dan yang lain menggunakan horizontal murni, dengan cara ini Anda beralih dari "2D" ke "1D" sehingga untuk berbicara, dan mengurangi secara drastis jumlah sampel serta meningkatkan kinerja cache berkat akses linier.

Pengambilan vertikal tidak akan memengaruhi kinerja cache karena tekstur penyimpanan Z harus diatur dalam memori GPU. cf http://en.wikipedia.org/wiki/Z-order_curve

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.