ใช้บัฟเฟอร์อย่างถูกต้องใน OpenGL compute Shaders

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 ก่อนที่จะคอมไพล์ shader มีประโยชน์อย่างยิ่งสำหรับขอบเขต for-loop
  • bufferMat ซึ่งมีขนาดเล็กมากเมื่อเทียบกับบัฟเฟอร์อื่น ๆ ถูกใส่ไว้ใน UBO ซึ่งควรมีประสิทธิภาพดีกว่า SSBO ฉันจะทำให้เหตุการณ์ดีขึ้นโดยการทำให้เป็นค่าคงที่เวลาคอมไพล์ได้หรือไม่ มันเล็ก แต่ยังไม่กี่ร้อย mat4
  • บัฟเฟอร์อื่น ๆ ทั้งอ่านและเขียนหลาย ๆ ครั้งน่าจะดีกว่าในฐานะ SSBO
  • ฉันมีปัญหาในการทำความเข้าใจว่าอะไรคือค่าที่ดีที่สุดสำหรับพารามิเตอร์ 'การใช้งาน' ของบัฟเฟอร์ บัฟเฟอร์ทั้งหมดถูกเขียนและอ่านหลายครั้งฉันไม่แน่ใจว่าจะใส่อะไรที่นี่
  • ถ้าฉันเข้าใจถูกต้อง local_size จะมีประโยชน์ก็ต่อเมื่อแชร์ข้อมูลระหว่างการเรียกใช้ดังนั้นฉันควรเก็บไว้ที่เดียว?

ฉันยินดีที่จะรับคำแนะนำหรือคำแนะนำเกี่ยวกับตำแหน่งที่จะเพิ่มประสิทธิภาพเมล็ดของเมล็ด!

คำตอบ

1 NicolBolas Aug 19 2020 at 21:51

ฉันจะทำให้เหตุการณ์ดีขึ้นโดยการทำให้เป็นค่าคงที่เวลาคอมไพล์ได้หรือไม่

คุณจะต้องทำโปรไฟล์ ที่ถูกกล่าวว่า "ไม่กี่ร้อย mat4" คือไม่ได้ "เล็ก"

ฉันมีปัญหาในการทำความเข้าใจว่าอะไรคือค่าที่ดีที่สุดสำหรับพารามิเตอร์ 'การใช้งาน' ของบัฟเฟอร์ บัฟเฟอร์ทั้งหมดถูกเขียนและอ่านหลายครั้งฉันไม่แน่ใจว่าจะใส่อะไรที่นี่

ครั้งแรกพารามิเตอร์การใช้งานเกี่ยวกับของคุณการใช้งานของวัตถุบัฟเฟอร์ที่ไม่ได้ใช้งาน OpenGL ของหน่วยความจำที่อยู่เบื้องหลังพวกเขา นั่นคือพวกเขากำลังพูดคุยเกี่ยวกับการทำงานเช่นglBufferSubData, glMapBufferRangeและอื่น ๆ READหมายความว่า CPU จะอ่านจากบัฟเฟอร์ แต่ไม่เขียนลงไป DRAWหมายความว่า CPU จะเขียนไปยังบัฟเฟอร์ แต่จะไม่อ่านจากมัน

อย่างที่สอง ... คุณไม่ควรสนใจจริงๆ คำแนะนำการใช้งานมีความน่ากลัวที่ระบุไว้ไม่ดีและได้รับในทางที่ผิดเพื่อให้การใช้งานหลายแบนออกไม่สนใจพวกเขา การใช้ GL ของ NVIDIA น่าจะเป็นสิ่งที่ให้ความสำคัญกับพวกเขามากที่สุด

ให้ใช้บัฟเฟอร์การจัดเก็บที่ไม่เปลี่ยนรูปแทน บรรดา "คำแนะนำการใช้งาน" ไม่ได้คำแนะนำ ; เป็นข้อกำหนดของ API หากคุณไม่ได้ใช้GL_DYNAMIC_STORAGE_BITแล้วคุณไม่สามารถglBufferSubDataเขียนไปยังบัฟเฟอร์ผ่าน และอื่น ๆ

ถ้าฉันเข้าใจถูกต้อง local_size จะมีประโยชน์ก็ต่อเมื่อแชร์ข้อมูลระหว่างการเรียกใช้ดังนั้นฉันควรเก็บไว้ที่เดียว?

ไม่อันที่จริงห้ามใช้ 1. หากการเรียกใช้ทั้งหมดทำในสิ่งของตัวเองโดยไม่มีอุปสรรคในการดำเนินการหรือสิ่งที่คล้ายกันคุณควรเลือกขนาดโลคัลที่เทียบเท่ากับขนาด wavefront ของฮาร์ดแวร์ที่คุณกำลังทำงานอยู่ . เห็นได้ชัดว่าขึ้นอยู่กับการใช้งาน แต่ 32 เป็นค่าเริ่มต้นที่ดีกว่า 1 อย่างแน่นอน