Làm cách nào để gỡ lỗi trình tạo bóng GLSL?


45

Khi viết các shader không tầm thường (giống như khi viết bất kỳ đoạn mã không tầm thường nào khác), người ta mắc lỗi. [cần dẫn nguồn] Tuy nhiên, tôi không thể chỉ gỡ lỗi nó như bất kỳ mã nào khác - sau tất cả, bạn không thể đính kèm gdb hoặc trình gỡ lỗi Visual Studio. Bạn thậm chí không thể gỡ lỗi printf, vì không có dạng đầu ra của bàn điều khiển. Những gì tôi thường làm là kết xuất dữ liệu tôi muốn xem như màu sắc, nhưng đó là một giải pháp rất thô sơ và nghiệp dư. Tôi chắc rằng mọi người đã đưa ra các giải pháp tốt hơn.

Vì vậy, làm thế nào tôi thực sự có thể gỡ lỗi một shader? Có cách nào để bước qua một shader? Tôi có thể xem xét việc thực hiện shader trên một đỉnh / nguyên thủy / đoạn cụ thể không?

(Câu hỏi này cụ thể là về cách gỡ lỗi mã shader giống như cách người ta sẽ gỡ lỗi mã "bình thường", chứ không phải về việc gỡ lỗi những thứ như thay đổi trạng thái.)


Bạn đã xem qua gDEBugger chưa? Trích dẫn trang web: "gDEBugger là trình gỡ lỗi OpenGL và OpenCL, Trình phân tích bộ nhớ và trình phân tích bộ nhớ. GDEBugger thực hiện những gì không công cụ nào có thể - cho phép bạn theo dõi hoạt động ứng dụng trên API OpenGL và OpenCL và xem những gì đang xảy ra trong quá trình triển khai hệ thống. " Cấp, không có gỡ lỗi theo kiểu VS / bước qua mã, nhưng nó có thể cung cấp cho bạn một cái nhìn sâu sắc về những gì shader của bạn làm (hoặc nên làm). Crytec đã phát hành một công cụ tương tự để "gỡ lỗi" Direct shader gọi là RenderDoc (miễn phí, nhưng hoàn toàn dành cho các shader HLSL, vì vậy có thể không phù hợp với bạn).
Bert

@Bert Hừ, tôi đoán gDEBugger là OpenGL tương đương với WebGL-Inspector? Tôi đã sử dụng thứ hai. Nó vô cùng hữu ích, nhưng nó chắc chắn sẽ gỡ lỗi các cuộc gọi OpenGL và thay đổi trạng thái hơn so với thực hiện đổ bóng.
Martin Ender

1
Tôi chưa bao giờ thực hiện bất kỳ chương trình WebGL nào và do đó tôi không quen thuộc với Trình kiểm tra WebGL. Với gDEBugger, bạn ít nhất có thể kiểm tra toàn bộ trạng thái của đường dẫn đổ bóng của mình bao gồm bộ nhớ kết cấu, dữ liệu đỉnh, v.v. Tuy nhiên, không có bước thực sự nào thông qua mã afaik.
Bert

gDEBugger rất cũ và không được hỗ trợ trong một thời gian. Nếu bạn đang tìm kiếm từ phân tích trạng thái khung và GPU, thì đây là một câu hỏi khác có liên quan mạnh mẽ: computergraphics.stackexchange.com/questions/23/ trên
cifz 7/8/2015

Đây là một phương pháp sửa lỗi tôi đề xuất cho một câu hỏi liên quan: stackoverflow.com/a/29816231/758666
xóa

Câu trả lời:


26

Theo như tôi biết, không có công cụ nào cho phép bạn bước qua mã trong trình đổ bóng (ngoài ra, trong trường hợp đó bạn sẽ phải chọn chỉ một pixel / đỉnh mà bạn muốn "gỡ lỗi", việc thực thi có khả năng thay đổi tùy theo điều đó).

Những gì cá nhân tôi làm là một "gỡ lỗi đầy màu sắc" rất hacky. Vì vậy, tôi rắc một loạt các nhánh năng động với những người #if DEBUG / #endifbảo vệ mà về cơ bản là nói

#if DEBUG
if( condition ) 
    outDebugColour = aColorSignal;
#endif

.. rest of code .. 

// Last line of the pixel shader
#if DEBUG
OutColor = outDebugColour;
#endif

Vì vậy, bạn có thể "quan sát" thông tin gỡ lỗi theo cách này. Tôi thường làm nhiều thủ thuật khác nhau như lerping hoặc trộn giữa các "mã màu" khác nhau để kiểm tra các sự kiện phức tạp hơn hoặc các công cụ không nhị phân khác nhau.

Trong "khung" này, tôi cũng thấy hữu ích khi có một tập hợp các quy ước cố định cho các trường hợp phổ biến để nếu tôi không phải liên tục quay lại và kiểm tra xem tôi liên quan đến màu gì. Điều quan trọng là có một sự hỗ trợ tốt cho việc tải lại mã shader nóng, do đó bạn gần như có thể thay đổi tương tác dữ liệu / sự kiện được theo dõi của mình và dễ dàng bật / tắt trực quan gỡ lỗi.

Nếu cần gỡ lỗi một cái gì đó mà bạn không thể hiển thị trên màn hình một cách dễ dàng, bạn luôn có thể làm tương tự và sử dụng một công cụ phân tích khung để kiểm tra kết quả của bạn. Tôi đã liệt kê một vài trong số họ là câu trả lời cho câu hỏi khác này.

Obv, không cần phải nói rằng nếu tôi không "gỡ lỗi" một shader pixel hoặc tính toán shader, tôi chuyển thông tin "debugColor" này trong suốt đường ống mà không nội suy nó (trong GLSL với flat từ khóa)

Một lần nữa, điều này rất khó khăn và khác xa với việc gỡ lỗi thích hợp, nhưng là điều tôi bị mắc kẹt với việc không biết bất kỳ sự thay thế thích hợp nào.


Khi chúng có sẵn, bạn có thể sử dụng SSBO để có định dạng đầu ra linh hoạt hơn mà bạn không cần phải mã hóa màu sắc. Tuy nhiên, nhược điểm lớn của phương pháp này là nó thay đổi mã có thể ẩn / thay đổi các lỗi, đặc biệt là khi có liên quan đến UB. +1 Tuy nhiên, đây là phương pháp trực tiếp nhất hiện có.
Không ai vào

9

Ngoài ra còn có GLSL-Debugger . Nó là một trình gỡ lỗi từng được gọi là "GLSL Devil".

Bản thân trình gỡ lỗi là siêu tiện dụng không chỉ cho mã GLSL mà còn cho cả OpenGL. Bạn có khả năng nhảy giữa các cuộc gọi rút thăm và ngắt trên các công tắc Shader. Nó cũng hiển thị cho bạn các thông báo lỗi được OpenGL truyền lại cho chính ứng dụng.


2
Lưu ý rằng kể từ 2018-08-07, nó không hỗ trợ bất cứ thứ gì cao hơn GLSL 1.2 và nó không được duy trì tích cực.
Ruslan

Nhận xét đó hợp pháp làm tôi buồn :(
lần thứ

Dự án này là nguồn mở và thực sự thích giúp hiện đại hóa nó. Không có công cụ nào khác làm những gì nó đã làm.
XenonofArcticus

7

Có một số dịch vụ của các nhà cung cấp GPU như CodeXL của AMD hoặc Trình gỡ lỗi nSight / Linux GFX của NVIDIA , cho phép bước qua các shader nhưng được gắn với phần cứng của nhà cung cấp tương ứng.

Hãy để tôi lưu ý rằng, mặc dù chúng có sẵn trong Linux, tôi luôn có rất ít thành công khi sử dụng chúng ở đó. Tôi không thể bình luận về tình hình trong Windows.

Tùy chọn mà tôi đã sử dụng gần đây, là mô đun hóa mã shader của tôi thông qua #includesvà giới hạn mã được bao gồm trong một tập hợp con phổ biến của GLSL và C ++ & glm .

Khi tôi gặp sự cố, tôi cố gắng tái tạo nó trên một thiết bị khác để xem sự cố có giống với lỗi logic hay không (thay vì sự cố trình điều khiển / hành vi không xác định). Ngoài ra còn có cơ hội truyền dữ liệu sai cho GPU (ví dụ: bởi bộ đệm bị ràng buộc không chính xác, v.v.) mà tôi thường loại trừ bằng cách gỡ lỗi đầu ra như trong câu trả lời cifz hoặc bằng cách kiểm tra dữ liệu qua apitrace .

Khi đó là lỗi logic, tôi cố gắng xây dựng lại tình huống từ GPU trên CPU bằng cách gọi mã đi kèm trên CPU có cùng dữ liệu. Sau đó tôi có thể bước qua nó trên CPU.

Dựa trên tính mô-đun của mã, bạn cũng có thể cố gắng viết không đúng cho nó và so sánh kết quả giữa một lần chạy GPU và chạy CPU. Tuy nhiên, bạn phải lưu ý rằng có những trường hợp góc trong đó C ++ có thể hoạt động khác với GLSL, do đó mang lại cho bạn những kết quả sai trong các so sánh này.

Cuối cùng, khi bạn không thể tái tạo vấn đề trên một thiết bị khác, bạn chỉ có thể bắt đầu tìm hiểu xem sự khác biệt đến từ đâu. Unittests có thể giúp bạn thu hẹp nơi xảy ra nhưng cuối cùng, bạn có thể sẽ cần phải viết ra thông tin gỡ lỗi bổ sung từ trình đổ bóng như trong câu trả lời của cifz .

Và để cung cấp cho bạn một cái nhìn tổng quan ở đây là một sơ đồ quy trình sửa lỗi của tôi: Sơ đồ quy trình mô tả trong văn bản

Để làm tròn điều này ở đây là một danh sách các ưu và nhược điểm ngẫu nhiên:

Chuyên nghiệp

  • bước qua với trình gỡ lỗi thông thường
  • chẩn đoán trình biên dịch bổ sung (thường tốt hơn)

con


Đây là một ý tưởng tuyệt vời và có lẽ là gần nhất bạn có thể nhận được mã shader một bước. Tôi tự hỏi nếu chạy qua một trình kết xuất phần mềm (Mesa?) Sẽ có lợi ích tương tự?

@racarate: Tôi cũng nghĩ về điều đó nhưng chưa có thời gian để thử. Tôi không phải là chuyên gia về mesa nhưng tôi nghĩ có thể khó gỡ lỗi trình đổ bóng vì thông tin gỡ lỗi của trình đổ bóng phải bằng cách nào đó đến được trình gỡ lỗi. Sau đó, một lần nữa, có lẽ những người ở mesa đã có giao diện để gỡ lỗi cho mesa đó :)
Không ai

5

Mặc dù dường như không thể thực sự bước qua trình tạo bóng OpenGL, nhưng có thể nhận được kết quả biên dịch.
Dưới đây được lấy từ Mẫu các tông Android .

while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
    Log.e(TAG, label + ": glError " + error);
    throw new RuntimeException(label + ": glError " + error);

Nếu mã của bạn biên dịch đúng, thì bạn có ít lựa chọn ngoài việc thử một cách khác để truyền đạt trạng thái của chương trình cho bạn. Bạn có thể báo hiệu rằng một phần của mã đã đạt được, ví dụ, thay đổi màu của một đỉnh hoặc sử dụng một kết cấu khác. Điều này thật khó xử, nhưng dường như là cách duy nhất cho đến bây giờ.

EDIT: Đối với WebGL, tôi đang xem dự án này , nhưng tôi chỉ tìm thấy nó ... không thể đảm bảo cho nó.


3
Hừ, tôi biết rằng tôi có thể gặp lỗi trình biên dịch. Tôi đã hy vọng để gỡ lỗi thời gian chạy tốt hơn. Trước đây tôi cũng đã sử dụng trình kiểm tra WebGL, nhưng tôi tin rằng nó chỉ hiển thị cho bạn các thay đổi trạng thái, nhưng bạn không thể xem xét một lệnh gọi đổ bóng. Tôi đoán điều này có thể đã rõ ràng hơn trong câu hỏi.
Martin Ender

2

Đây là bản sao dán câu trả lời của tôi cho cùng một câu hỏi tại StackOverflow .


Ở dưới cùng của câu trả lời này là một ví dụ về mã GLSL cho phép xuất toàn bộ floatgiá trị dưới dạng màu, mã hóa IEEE 754 binary32. Tôi sử dụng nó như sau (đoạn trích này đưa ra yythành phần của ma trận modelview):

vec4 xAsColor=toColor(gl_ModelViewMatrix[1][1]);
if(bool(1)) // put 0 here to get lowest byte instead of three highest
    gl_FrontColor=vec4(xAsColor.rgb,1);
else
    gl_FrontColor=vec4(xAsColor.a,0,0,1);

Sau khi bạn có được điều này trên màn hình, bạn chỉ có thể lấy bất kỳ chọn màu, định dạng màu dưới dạng HTML (phụ thêm 00vào rgbgiá trị nếu bạn không cần độ chính xác cao hơn, và thực hiện một đường chuyền thứ hai để có được byte thấp hơn nếu bạn làm), và bạn có được đại diện thập lục phân của floatIEEE 754 binary32.

Đây là triển khai thực tế của toColor():

const int emax=127;
// Input: x>=0
// Output: base 2 exponent of x if (x!=0 && !isnan(x) && !isinf(x))
//         -emax if x==0
//         emax+1 otherwise
int floorLog2(float x)
{
    if(x==0.) return -emax;
    // NOTE: there exist values of x, for which floor(log2(x)) will give wrong
    // (off by one) result as compared to the one calculated with infinite precision.
    // Thus we do it in a brute-force way.
    for(int e=emax;e>=1-emax;--e)
        if(x>=exp2(float(e))) return e;
    // If we are here, x must be infinity or NaN
    return emax+1;
}

// Input: any x
// Output: IEEE 754 biased exponent with bias=emax
int biasedExp(float x) { return emax+floorLog2(abs(x)); }

// Input: any x such that (!isnan(x) && !isinf(x))
// Output: significand AKA mantissa of x if !isnan(x) && !isinf(x)
//         undefined otherwise
float significand(float x)
{
    // converting int to float so that exp2(genType) gets correctly-typed value
    float expo=float(floorLog2(abs(x)));
    return abs(x)/exp2(expo);
}

// Input: x\in[0,1)
//        N>=0
// Output: Nth byte as counted from the highest byte in the fraction
int part(float x,int N)
{
    // All comments about exactness here assume that underflow and overflow don't occur
    const float byteShift=256.;
    // Multiplication is exact since it's just an increase of exponent by 8
    for(int n=0;n<N;++n)
        x*=byteShift;

    // Cut higher bits away.
    // $q \in [0,1) \cap \mathbb Q'.$
    float q=fract(x);

    // Shift and cut lower bits away. Cutting lower bits prevents potentially unexpected
    // results of rounding by the GPU later in the pipeline when transforming to TrueColor
    // the resulting subpixel value.
    // $c \in [0,255] \cap \mathbb Z.$
    // Multiplication is exact since it's just and increase of exponent by 8
    float c=floor(byteShift*q);
    return int(c);
}

// Input: any x acceptable to significand()
// Output: significand of x split to (8,8,8)-bit data vector
ivec3 significandAsIVec3(float x)
{
    ivec3 result;
    float sig=significand(x)/2.; // shift all bits to fractional part
    result.x=part(sig,0);
    result.y=part(sig,1);
    result.z=part(sig,2);
    return result;
}

// Input: any x such that !isnan(x)
// Output: IEEE 754 defined binary32 number, packed as ivec4(byte3,byte2,byte1,byte0)
ivec4 packIEEE754binary32(float x)
{
    int e = biasedExp(x);
    // sign to bit 7
    int s = x<0. ? 128 : 0;

    ivec4 binary32;
    binary32.yzw=significandAsIVec3(x);
    // clear the implicit integer bit of significand
    if(binary32.y>=128) binary32.y-=128;
    // put lowest bit of exponent into its position, replacing just cleared integer bit
    binary32.y+=128*int(mod(float(e),2.));
    // prepare high bits of exponent for fitting into their positions
    e/=2;
    // pack highest byte
    binary32.x=e+s;

    return binary32;
}

vec4 toColor(float x)
{
    ivec4 binary32=packIEEE754binary32(x);
    // Transform color components to [0,1] range.
    // Division is inexact, but works reliably for all integers from 0 to 255 if
    // the transformation to TrueColor by GPU uses rounding to nearest or upwards.
    // The result will be multiplied by 255 back when transformed
    // to TrueColor subpixel value by OpenGL.
    return vec4(binary32)/255.;
}

1

Giải pháp hiệu quả với tôi là biên dịch mã shader thành C ++ - như Nobody đã đề cập. Nó được đề xuất rất hiệu quả khi làm việc với một mã phức tạp mặc dù nó đòi hỏi một chút thiết lập.

Tôi đã làm việc chủ yếu với các Trình tạo bóng điện toán HLSL mà tôi đã phát triển một thư viện bằng chứng khái niệm có sẵn ở đây:

https://github.com/cezbloch/shaderator

Nó trình bày trên Trình tính toán Shader từ Mẫu SDK DirectX, cách bật C ++ như gỡ lỗi HLSL và cách thiết lập Kiểm tra đơn vị.

Việc biên dịch shader tính toán GLSL sang C ++ trông dễ dàng hơn so với HLSL. Chủ yếu là do các cấu trúc cú pháp trong HLSL. Tôi đã thêm một ví dụ tầm thường về Kiểm tra đơn vị thực thi trên Trình theo dõi tia GLSL Compute Shader mà bạn cũng có thể tìm thấy trong các nguồn của dự án Shaderator theo liên kết ở trên.

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.