OpenGL 컴퓨팅 셰이더에서 올바르게 버퍼 사용

Aug 19 2020

성능을 극대화하기 위해 행렬 / 벡터 연산을 사용하여 처음 작성한 알고리즘을 OpenGL 커널에 다시 작성하고 있습니다.

저는 OpenGL에 대한 기본적인 지식을 가지고있어서 작업을 할 수 있었지만 OpenGL이 제공하는 다양한 선택, 특히 제 경우에 큰 영향을 미칠 것으로 생각되는 버퍼의 매개 변수를 선택할 때 많은 문제가 있습니다. 많은 데이터를 읽고 쓰는 곳입니다.

세 커널을 순차적으로 호출합니다.

첫째 :

/* Generated constants (for all three shaders): 
 *   #version 430
 *   const vec3 orig
 *   const float vx
 *   const ivec2 size
 *   const uint projections
 *   const uint subIterations
 */
layout(local_size_x = 1, local_size_y = 1) in;

layout(std430, binding = 0) buffer bufferA { //GL_SHADER_STORAGE_BUFFER, GL_DYNAMIC_READ
    uint bufferProjection[]; //Written and read (AtomicAdd) by this shader, read by the second kernel
};
layout(std430, binding = 1) readonly buffer bufferB { //GL_SHADER_STORAGE_BUFFER, GL_DYNAMIC_READ
    uint layer[]; //Written and read by the third kernel, read by this shader and by glGetNamedBufferSubData
};
layout(std140) uniform bufferMat { //GL_UNIFORM_BUFFER, GL_STATIC_DRAW
    mat4 proj_mat[projections*subIterations]; //Read only by this shader and the third
};
layout(location = 0) uniform int z;
layout(location = 1) uniform int subit;

void main() {
    vec4 layer_coords = vec4(orig,1.0) + vec4(gl_GlobalInvocationID.x, z, gl_GlobalInvocationID.y, 0.0)*vx;
    uint val = layer[gl_GlobalInvocationID.y*size.x + gl_GlobalInvocationID.x];
    for(int i = 0; i < projections; ++i) {
        vec4 proj_coords = proj_mat[subit+i*subIterations]*layer_coords;
        ivec2 tex_coords = ivec2(floor((proj_coords.xy*size)/(2.0*proj_coords.w)) + size/2);
        bool valid = all(greaterThanEqual(tex_coords, ivec2(0,0))) && all(lessThan(tex_coords, size));
        atomicAdd(bufferProjection[tex_coords.y*size.x+tex_coords.x+i*(size.x*size.y)], valid?val:0);
    }
}

둘째:

layout(local_size_x = 1, local_size_y = 1) in;

layout(std430, binding = 0) buffer bufferA { //GL_SHADER_STORAGE_BUFFER, GL_DYNAMIC_READ
    float updateProjection[]; //Written by this shader, read by the third kernel
};
layout(std430, binding = 1) readonly buffer bufferB { //GL_SHADER_STORAGE_BUFFER, GL_DYNAMIC_READ
    uint bufferProjection[]; //Written by the first, read by this shader
};
layout(std430, binding = 2) readonly buffer bufferC { //GL_SHADER_STORAGE_BUFFER, GL_DYNAMIC_READ
    uint originalProjection[]; //Only modified by glBufferSubData, read by this shader
};

void main() {
    for(int i = 0; i < projections; ++i) {
        updateProjection[gl_GlobalInvocationID.x+i*(size.x*size.y)] = float(originalProjection[gl_GlobalInvocationID.x+i*(size.x*size.y)])/float(bufferProjection[gl_GlobalInvocationID.x+i*(size.x*size.y)]);
    }
}

제삼:

layout(local_size_x = 1, local_size_y = 1) in;

layout(std430, binding = 0) readonly buffer bufferA { //GL_SHADER_STORAGE_BUFFER, GL_DYNAMIC_READ
    float updateProjection[]; //Written by the second kernel, read by this shader
};
layout(std430, binding = 1) buffer bufferB { //GL_SHADER_STORAGE_BUFFER, GL_DYNAMIC_READ
    uint layer[]; //Written and read by this shader, read by the first kernel and by glGetNamedBufferSubData
};
layout(std140) uniform bufferMat { //GL_UNIFORM_BUFFER, GL_STATIC_DRAW
    mat4 proj_mat[projections*subIterations]; //Read only by this shader and and the first
};
layout(location = 0) uniform int z;
layout(location = 1) uniform int subit;
layout(location = 2) uniform float weight;

void main() {
    vec4 layer_coords = vec4(orig,1.0) + vec4(gl_GlobalInvocationID.x, z, gl_GlobalInvocationID.y, 0.0)*vx;
    float acc = 0;
    for(int i = 0; i < projections; ++i) {
        vec4 proj_coords = proj_mat[subit+i*subIterations]*layer_coords;
        ivec2 tex_coords = ivec2(floor((proj_coords.xy*size)/(2.0*proj_coords.w)) + size/2);
        bool valid = all(greaterThanEqual(tex_coords, ivec2(0,0))) && all(lessThan(tex_coords, size));
        acc += valid?updateProjection[tex_coords.y*size.x+tex_coords.x+i*(size.x*size.y)]:0;
    }
    float val = pow(float(layer[gl_GlobalInvocationID.y*size.x + gl_GlobalInvocationID.x])*(acc/projections), weight);
    layer[gl_GlobalInvocationID.y*size.x + gl_GlobalInvocationID.x] = uint(val);
}

OpenGL 문서를 읽음으로써 내가 찾은 것 :

  • 알고리즘의 모든 기간 동안 동일한 일부 값은 셰이더를 컴파일하기 전에 const로 생성됩니다. for 루프 경계에 특히 유용합니다.
  • 다른 버퍼에 비해 매우 작은 bufferMat은 UBO에 넣어 져 SSBO보다 더 나은 성능을 가져야합니다. 이벤트를 컴파일 시간 상수로 만들어 더 나은 성능을 얻을 수 있습니까? 작지만 여전히 수백 mat4
  • 여러 번 읽고 쓰는 다른 버퍼는 SSBO보다 더 좋습니다.
  • 버퍼의 '사용'매개 변수에 가장 적합한 값이 무엇인지 이해하는 데 어려움이 있습니다. 모든 버퍼에 여러 번 쓰고 읽습니다. 여기에 무엇을 넣어야할지 모르겠습니다.
  • 올바르게 이해하면 local_size는 호출간에 데이터를 공유 할 때만 유용하므로 하나에 유지해야합니까?

나는 그 커널을 최적화하기 위해 찾아야 할 곳에 대한 권장 사항이나 힌트를 기꺼이 받아 들일 것입니다!

답변

1 NicolBolas Aug 19 2020 at 21:51

이벤트를 컴파일 시간 상수로 만들어 더 나은 성능을 얻을 수 있습니까?

프로필을 작성해야합니다. 즉, "수백 mat4"는 "작은"아닙니다 .

버퍼의 '사용'매개 변수에 가장 적합한 값이 무엇인지 이해하는 데 어려움이 있습니다. 모든 버퍼에 여러 번 쓰고 읽습니다. 여기에 무엇을 넣어야할지 모르겠습니다.

첫째, 사용 매개 변수에 대해있는 당신의 버퍼 오브젝트, 그들 뒤에 메모리되지는 OpenGL의 사용의 사용. 즉, 그들은 glBufferSubData, glMapBufferRange등의 기능에 대해 이야기하고 있습니다 . READCPU가 버퍼에서 읽기는하지만 쓰기는 할 수 없음을 의미합니다. DRAWCPU가 버퍼에 쓰지만 읽지는 않음을 의미합니다.

둘째 ... 당신은 정말로 신경 쓰지 말아야합니다. 사용 힌트는 끔찍하고 잘못 지정되었으며 너무 오용되어 많은 구현에서 이를 무시 합니다. NVIDIA의 GL 구현은 아마도 가장 심각하게 여기는 것일 것입니다.

대신 변경 불가능한 스토리지 버퍼를 사용하십시오 . 이러한 "사용 힌트"는 힌트 가 아닙니다 . API 요구 사항입니다. 사용하지 않는 경우 GL_DYNAMIC_STORAGE_BIT에, 당신은 할 수없는 통해 버퍼에 기록 glBufferSubData. 기타 등등.

올바르게 이해하면 local_size는 호출간에 데이터를 공유 할 때만 유용하므로 하나에 유지해야합니까?

아니요. 실제로 1을 사용하지 마십시오. 모든 호출이 실행 장벽 등없이 자체 작업을 수행하는 경우 작업중인 하드웨어의 웨이브 프론트 크기와 동일한 로컬 크기를 선택해야합니다. . 분명히 그것은 구현에 따라 다르지만 32는 확실히 1보다 나은 기본값입니다.