GLSL 쉐이더를 어떻게 디버깅 할 수 있습니까?


45

사소한 셰이더를 작성할 때 (사소한 다른 코드를 작성할 때와 마찬가지로) 사람들은 실수를합니다. [citation needed] 그러나 다른 코드와 마찬가지로 디버깅 할 수는 없습니다. gdb 또는 Visual Studio 디버거를 연결할 수는 없습니다. 콘솔 출력 형식이 없으므로 printf 디버깅을 수행 할 수도 없습니다. 내가 일반적으로하는 일은 색상으로보고 싶은 데이터를 렌더링하는 것이지만 매우 초보적이고 아마추어적인 솔루션입니다. 사람들이 더 나은 솔루션을 생각해 냈습니다.

그렇다면 실제로 셰이더를 어떻게 디버깅 할 수 있습니까? 셰이더를 밟는 방법이 있습니까? 특정 버텍스 / 프리미티브 / 프래그먼트에서 셰이더 실행을 볼 수 있습니까?

(이 질문은 특히 상태 변경과 같은 것을 디버깅하는 것이 아니라 "일반적인"코드를 디버깅하는 것과 유사한 쉐이더 코드를 디버깅하는 방법에 관한 것입니다.


gDEBugger를 살펴 보셨습니까? 사이트 인용 : "gDEBugger는 고급 OpenGL 및 OpenCL 디버거, 프로파일 러 및 메모리 분석기입니다. gDEBugger는 다른 도구로는 할 수없는 기능을 수행합니다. OpenGL 및 OpenCL API에서 애플리케이션 활동을 추적하고 시스템 구현 내에서 무슨 일이 일어나고 있는지 확인할 수 있습니다. " 물론 VS 스타일의 디버깅 / 코드 단계별 스테핑은 없지만 셰이더의 기능에 대한 통찰력을 줄 수 있습니다. Crytec은 RenderDoc이라는 직접 셰이더 "디버깅"을위한 유사한 툴을 출시했습니다 (무료이지만 HLSL 셰이더에만 적용되므로 사용자와는 관련이 없을 수도 있습니다).
Bert

@Bert 흠 그래, gDEBugger는 OpenGL이 WebGL-Inspector와 동등한 것 같아? 나는 후자를 사용했다. 매우 유용하지만 셰이더 실행보다 OpenGL 호출 및 상태 변경을 디버깅하는 것이 더 많습니다.
Martin Ender

1
WebGL 프로그래밍을 한 적이 없으며 WebGL-Inspector에 익숙하지 않습니다. gDEBugger를 사용하면 최소한 텍스처 메모리, 정점 데이터 등 셰이더 파이프 라인의 전체 상태를 검사 할 수 있습니다. 그럼에도 불구하고 실제 코드 afaik을 거치지 않습니다.
Bert

gDEBugger는 매우 오래되어 한동안 지원되지 않습니다. 프레임 및 GPU 상태 분석을보고 있다면 이것은 또 다른 질문입니다. computergraphics.stackexchange.com/questions/23/…
cifz

다음은 관련 질문에 제안한 디버깅 방법입니다. stackoverflow.com/a/29816231/758666
wip

답변:


26

내가 아는 한 셰이더에서 코드를 단계별로 실행할 수있는 도구가 없습니다 (이 경우 "디버그"하려는 픽셀 / 버텍스 만 선택할 수 있어야합니다). 그에 따라 다릅니다).

내가 개인적으로하는 일은 매우 해킹 된 "다채로운 디버깅"입니다. #if DEBUG / #endif기본적으로 다음 과 같은 가드가있는 역동적 인 가지를 뿌립니다.

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

.. rest of code .. 

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

따라서이 방법으로 디버그 정보를 "관측"할 수 있습니다. 나는 일반적으로 다양한 "컬러 코드"사이에서 lerping 또는 blending과 같은 다양한 트릭을 수행하여 다양한 복잡한 이벤트 또는 이진이 아닌 것을 테스트합니다.

이 "프레임 워크"에서는 일반적인 경우에 대해 고정 된 규칙을 사용하는 것이 유용하다는 것을 알았습니다. 따라서 지속적으로 돌아가서 어떤 색상을 어떤 색상과 연관 시킬지 확인해야합니다. 중요한 것은 셰이더 코드의 핫 리로드를 잘 지원하므로 추적 된 데이터 / 이벤트를 거의 대화식으로 변경하고 디버그 시각화를 쉽게 켜고 끌 수 있습니다.

화면에 쉽게 표시 할 수없는 것을 디버그해야하는 경우 항상 동일한 작업을 수행하고 하나의 프레임 분석기 도구를 사용하여 결과를 검사 할 수 있습니다. 나는 이 다른 질문에 대한 답변으로 두 가지를 나열했습니다 .

물론, 픽셀 셰이더 나 계산 셰이더를 "디버깅"하지 않으면이 "debugColor"정보를 보간하지 않고 파이프 라인 전체에 전달합니다 ( flat 키워드 가 GLSL 인 경우).

다시 말하지만, 이것은 매우 해킹적이고 적절한 디버깅과는 거리가 멀지 만 적절한 대안을 모른 채 붙어 있습니다.


사용 가능한 경우 SSBO를 사용하여 색상으로 인코딩 할 필요가없는보다 유연한 출력 형식을 얻을 수 있습니다. 그러나이 방법의 큰 단점은 특히 UB가 관련된 경우 버그를 숨기거나 변경할 수있는 코드를 변경한다는 것입니다. 그럼에도 불구하고 사용 가능한 가장 직접적인 방법이기 때문입니다.
아무도

9

또한이 GLSL - 디버거 . "GLSL Devil"으로 알려진 디버거입니다.

디버거 자체는 GLSL 코드뿐만 아니라 OpenGL 자체에도 매우 유용합니다. 드로우 콜 사이를 이동하고 셰이더 스위치를 깰 수 있습니다. 또한 OpenGL이 다시 응용 프로그램 자체로 전달한 오류 메시지를 보여줍니다.


2
2018-08-07 현재 GLSL 1.2보다 높은 것을 지원하지 않으며 적극적으로 유지 관리되지 않습니다.
Ruslan

그 의견은 합법적으로 나를 슬프게했다 :(
rdelfin

이 프로젝트는 오픈 소스이며 현대화에 큰 도움이 될 것입니다. 그 일을 수행하는 다른 도구는 없습니다.
XenonofArcticus

7

AMD의 CodeXL 또는 NVIDIA의 nSight / Linux GFX 디버거 와 같은 GPU 공급 업체는 셰이더를 단계별로 허용하지만 각 공급 업체의 하드웨어에 묶여 있는 여러 제품을 제공 합니다.

리눅스에서 사용할 수는 있지만 항상 사용하는 데 거의 성공하지 못했습니다. Windows의 상황에 대해서는 언급 할 수 없습니다.

최근에 사용하게 된 옵션 #includes은 포함 된 코드를 GLSL 및 C ++ & glm 의 공통 하위 집합으로 제한하여 셰이더 코드를 모듈화하는 것 입니다.

문제가 발생하면 다른 장치에서 문제를 재현하여 문제가 동일한 지 확인하여 드라이버 문제 / 정의되지 않은 동작 대신 논리 오류를 암시합니다. cifz 응답 과 같은 출력 디버깅 이나 apitrace 를 통해 데이터를 검사하여 일반적으로 배제하는 잘못된 데이터를 GPU에 전달할 수도 있습니다 (예 : 잘못 바인딩 된 버퍼 등) .

논리 오류 일 때 CPU에 포함 된 코드를 동일한 데이터로 호출하여 CPU의 GPU에서 상황을 재구성하려고합니다. 그런 다음 CPU에서 단계별로 진행할 수 있습니다.

코드의 모듈성을 기반으로 코드에 대한 단위 테스트를 작성하고 GPU 실행과 CPU 실행 간의 결과를 비교할 수도 있습니다. 그러나 C ++이 GLSL과 다르게 작동하는 경우가 있으므로 이러한 비교에서 잘못된 긍정을 제공합니다.

마지막으로 다른 장치에서 문제를 재현 할 수없는 경우 차이점이 어디에서 나오는지 알아볼 수 있습니다. 단위 테스트는 발생하는 위치를 좁히는 데 도움이 될 수 있지만 결국 cifz answer 에서와 같이 셰이더에서 추가 디버그 정보를 작성해야합니다 .

그리고 여기에 개요를 제공하기 위해 디버깅 프로세스의 흐름도가 있습니다. 본문에 기술 된 절차의 흐름도

이것을 반올림하기 위해 무작위 장단점 목록이 있습니다.

찬성

  • 일반적인 디버거로 단계별
  • 추가 (종종 더 나은) 컴파일러 진단

범죄자


이것은 좋은 아이디어이며 아마도 단일 스테핑 셰이더 코드에 가장 가깝습니다. 소프트웨어 렌더러 (Mesa?)를 통해 실행할 때 비슷한 이점이 있는지 궁금합니다.

@ racarate : 나는 그것에 대해서도 생각했지만 아직 시도 할 시간이 없었습니다. 나는 mesa에 대한 전문가는 아니지만 쉐이더 디버그 정보가 어떻게 든 디버거에 도달해야하기 때문에 쉐이더를 디버깅하는 것이 어려울 수 있다고 생각합니다. 아마 다시, 아마도 메사의 사람들은 이미 메사 자체를 디버깅하기위한 인터페이스를 가지고있을 것입니다 :)
아무도

5

실제로 OpenGL 쉐이더를 단계별로 실행하는 것은 불가능하지만 컴파일 결과를 얻을 수 있습니다.
다음은 Android Cardboard 샘플 에서 가져온 것입니다 .

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

코드가 올바르게 컴파일되면 선택의 여지가 거의 없지만 프로그램 상태를 사용자에게 알리는 다른 방법을 시도해보십시오. 예를 들어 정점의 색상을 변경하거나 다른 질감을 사용하여 코드의 일부에 도달했음을 알릴 수 있습니다. 어색하지만 지금은 유일한 방법 인 것 같습니다.

편집 : WebGL의 경우, 나는 이 프로젝트를 보고 있지만 방금 그것을 발견했습니다 ... 보증 할 수 없습니다.


3
흠, 컴파일러 오류가 발생할 수 있음을 알고 있습니다. 더 나은 런타임 디버깅을 원했습니다. 나는 또한 과거에 WebGL 관리자를 사용했지만 상태 변경 만 표시하지만 셰이더 호출을 볼 수는 없다고 생각합니다. 나는 이것이 질문에서 더 분명했을 수 있다고 생각한다.
Martin Ender

2

이것은 StackOverflow에서 동일한 질문에 대한 내 답변 의 사본 붙여 넣기입니다 .


이 답변의 맨 아래에는 floatIEEE 754를 인코딩 하여 전체 값을 컬러 로 출력 할 수있는 GLSL 코드의 예가 있습니다 binary32. 다음과 같이 사용합니다 (이 코드 조각은 yymodelview 매트릭스의 구성 요소를 제공합니다 ).

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

화면에 표시 한 후에는 색상 선택기를 사용하여 색상을 HTML로 서식을 지정할 수 있습니다 (높은 정밀도가 필요하지 않은 경우 값에 추가 00하고 rgb필요한 경우 더 낮은 바이트를 얻기 위해 두 번째 패스를 수행). floatIEEE 754 의 16 진수 표현을 얻을 수 binary32있습니다.

실제 구현은 다음과 같습니다 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

나를 위해 일한 해결책은 Nobody에서 언급했듯이 셰이더 코드를 C ++로 컴파일하는 것입니다. 약간의 설정이 필요하더라도 복잡한 코드로 작업 할 때 매우 효율적입니다.

나는 주로 HLSL Compute Shaders와 협력하여 여기에 사용 가능한 개념 증명 라이브러리를 개발했습니다.

https://github.com/cezbloch/shaderator

DirectX SDK 샘플의 Compute Shader, HLSL 디버깅과 같은 C ++ 사용 방법 및 단위 테스트 설정 방법에 대해 설명합니다.

GLSL 컴퓨팅 셰이더를 C ++로 컴파일하는 것은 HLSL보다 쉽습니다. 주로 HLSL의 구문 구조로 인해 발생합니다. GLSL 광선 추적기 Compute Shader에 실행 가능한 단위 테스트의 간단한 예를 추가했습니다. 위의 링크에서 Shaderator 프로젝트의 소스를 찾을 수도 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.