Tối ưu hóa hiệu suất của một shader mảnh nặng


9

Tôi cần trợ giúp để tối ưu hóa bộ shader sau:

Đỉnh:

    precision mediump float;

uniform vec2 rubyTextureSize;

attribute vec4 vPosition;
attribute vec2 a_TexCoordinate;

varying vec2 tc;

void main() {
    gl_Position = vPosition;

    tc = a_TexCoordinate;
}

Miếng:

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

Các shader nhận được kết cấu 2D và có nghĩa là chia tỷ lệ đẹp trên bề mặt 2D độ phân giải cao (màn hình thiết bị). Nó là một tối ưu hóa của thuật toán mở rộng SABR trong trường hợp nó quan trọng.

Nó đã hoạt động và hoạt động tốt trên các thiết bị Android rất cao cấp (như LG Nexus 4), nhưng nó thực sự chậm trên các thiết bị yếu hơn.

Các thiết bị Android thực sự quan trọng với tôi là Samsung Galaxy S 2 \ 3, với GPU Mali 400MP - hoạt động khủng khiếp với trình đổ bóng này.

Cho đến nay tôi đã thử:

  1. Loại bỏ các thay đổi (lời khuyên từ hướng dẫn Mali của ARM) - đã cải thiện nhỏ.
  2. Ghi đè các hàm mix () với riêng tôi - không tốt.
  3. giảm độ chính xác của phao xuống mức thấp - không thay đổi gì cả.

Tôi đo hiệu suất bằng cách tính thời gian kết xuất (trước và sau eglSwapBuffers) - điều này mang lại cho tôi một phép đo hiệu suất rất tuyến tính và nhất quán.

Ngoài ra, tôi không thực sự biết nơi nào để tìm hoặc những gì có thể được tối ưu hóa ở đây ...

Tôi biết rằng đây là một thuật toán nặng và tôi không hỏi lời khuyên về việc sử dụng các phương pháp chia tỷ lệ thay thế nào - tôi đã thử nhiều và thuật toán này cho kết quả trực quan tốt nhất. Tôi muốn sử dụng cùng một thuật toán theo cách tối ưu hóa.

CẬP NHẬT

  1. Tôi thấy rằng nếu tôi thực hiện tất cả các kết cấu tìm nạp với một vectơ không đổi thay vì các vectơ phụ thuộc tôi sẽ có một sự cải thiện hiệu suất lớn, vì vậy đây rõ ràng là một nút cổ chai lớn - có thể là do bộ đệm. Tuy nhiên, tôi vẫn cần phải thực hiện những điều đó. Tôi đã chơi với việc thực hiện ít nhất một số lần tìm nạp với các biến thể vec2 (không có bất kỳ sự thay đổi nào) nhưng nó không cải thiện bất cứ điều gì. Tôi tự hỏi điều gì có thể là một cách tốt để thăm dò hiệu quả 21 texels.

  2. Tôi thấy rằng một phần chính của các phép tính đang được thực hiện nhiều lần với cùng một tập hợp chính xác - bởi vì đầu ra được chia tỷ lệ ít nhất là x2 và tôi thăm dò ý kiến ​​với GL_NEAREST. Có ít nhất 4 mảnh rơi vào chính xác cùng một texels. Nếu tỷ lệ là x4 trên thiết bị có độ phân giải cao, có 16 mảnh rơi trên cùng một texels - đó là một sự lãng phí lớn. Có cách nào để thực hiện một shader pass bổ sung sẽ tính toán tất cả các giá trị không thay đổi trên nhiều mảnh không? Tôi đã nghĩ về việc kết xuất thành một kết cấu ngoài màn hình bổ sung, nhưng tôi cần lưu trữ nhiều giá trị trên mỗi texel, không chỉ một.

CẬP NHẬT

  1. Tôi cũng nhận thấy rằng CPU gần như không được sử dụng trong khi GPU là một nút cổ chai lớn. Bạn có lời khuyên nào về cách tận dụng sức mạnh của CPU và chuyển logic từ GPU sang CPU trong tình huống này không?

2
Bạn không bao giờ nên lấy kết cấu như một tra cứu. hoặc vượt qua uv từ đỉnh để pixelshader có thời gian để lấy kết cấu.
Tordin

Bạn có thể vui lòng giải thích? Bạn có ý nghĩa gì bởi uv?
SirKnigget

3
Bạn có thể liên kết đến một mô tả về "thuật toán chia tỷ lệ SABR" không? Google không tìm thấy bất cứ điều gì hữu ích về nó. Nhân tiện, bộ lọc 21-texel (và cũng khá nặng về toán học) trên GPU di động chỉ là vấn đề rắc rối. Tôi không nghĩ rằng bạn thực sự có thể mong đợi làm cho nó chạy tốt mà không ảnh hưởng đến chất lượng ở đâu đó.
Nathan Reed

Điều này đưa ra ý tưởng chung: board.byuu.org/viewtopic.php?f=10&t=2248 , mặc dù đó không phải là triển khai chính xác mà tôi tìm thấy.
SirKnigget

2
Về kỳ vọng thực tế - nó hoạt động tuyệt vời trên các thiết bị cao cấp. Tôi hy vọng có thể tinh chỉnh những gì tôi có bằng một yếu tố 5x hoặc tương tự và làm cho nó hoạt động trên các thiết bị yếu hơn.
SirKnigget

Câu trả lời:


2

Tôi tự hỏi điều gì có thể là một cách tốt để thăm dò hiệu quả 21 texels.

Câu trả lời là cách hiệu quả là cách không bỏ phiếu 21 texels. Xin lỗi là hiển nhiên nhưng thiết bị di động có thể không có chiều rộng xe buýt cần thiết để hỗ trợ các hạt nhân như vậy. Bạn cần tối ưu hóa bằng cách giảm kích thước của kết cấu được cắm trong bộ lấy mẫu để bộ đệm sẽ bao phủ bán kính hạt nhân lớn hơn.

Ngoài ra, bạn có thể quên hạt nhân đĩa của mình và sử dụng thuật toán hai lần sử dụng hạt nhân dọc và một thuật toán khác sử dụng hoàn toàn theo chiều ngang, theo cách này bạn chuyển từ "2D" sang "1D" để nói và giảm đáng kể số lượng lấy mẫu cũng như cải thiện hiệu suất bộ đệm nhờ truy cập tuyến tính.

Tìm nạp dọc không ảnh hưởng đến hiệu suất bộ đệm nhờ kết cấu lưu trữ Z nên được sắp xếp trong bộ nhớ GPU. cf http://en.wikipedia.org/wiki/Z-order_curve

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.