ใช้บัฟเฟอร์อย่างถูกต้องใน OpenGL compute Shaders
ฉันกำลังเขียนอัลกอริทึมใหม่ที่ฉันเขียนครั้งแรกโดยใช้การดำเนินการเมทริกซ์ / เวกเตอร์ลงในเคอร์เนล 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 จะมีประโยชน์ก็ต่อเมื่อแชร์ข้อมูลระหว่างการเรียกใช้ดังนั้นฉันควรเก็บไว้ที่เดียว?
ฉันยินดีที่จะรับคำแนะนำหรือคำแนะนำเกี่ยวกับตำแหน่งที่จะเพิ่มประสิทธิภาพเมล็ดของเมล็ด!
คำตอบ
ฉันจะทำให้เหตุการณ์ดีขึ้นโดยการทำให้เป็นค่าคงที่เวลาคอมไพล์ได้หรือไม่
คุณจะต้องทำโปรไฟล์ ที่ถูกกล่าวว่า "ไม่กี่ร้อย mat4" คือไม่ได้ "เล็ก"
ฉันมีปัญหาในการทำความเข้าใจว่าอะไรคือค่าที่ดีที่สุดสำหรับพารามิเตอร์ 'การใช้งาน' ของบัฟเฟอร์ บัฟเฟอร์ทั้งหมดถูกเขียนและอ่านหลายครั้งฉันไม่แน่ใจว่าจะใส่อะไรที่นี่
ครั้งแรกพารามิเตอร์การใช้งานเกี่ยวกับของคุณการใช้งานของวัตถุบัฟเฟอร์ที่ไม่ได้ใช้งาน OpenGL ของหน่วยความจำที่อยู่เบื้องหลังพวกเขา นั่นคือพวกเขากำลังพูดคุยเกี่ยวกับการทำงานเช่นglBufferSubData
, glMapBufferRange
และอื่น ๆ READ
หมายความว่า CPU จะอ่านจากบัฟเฟอร์ แต่ไม่เขียนลงไป DRAW
หมายความว่า CPU จะเขียนไปยังบัฟเฟอร์ แต่จะไม่อ่านจากมัน
อย่างที่สอง ... คุณไม่ควรสนใจจริงๆ คำแนะนำการใช้งานมีความน่ากลัวที่ระบุไว้ไม่ดีและได้รับในทางที่ผิดเพื่อให้การใช้งานหลายแบนออกไม่สนใจพวกเขา การใช้ GL ของ NVIDIA น่าจะเป็นสิ่งที่ให้ความสำคัญกับพวกเขามากที่สุด
ให้ใช้บัฟเฟอร์การจัดเก็บที่ไม่เปลี่ยนรูปแทน บรรดา "คำแนะนำการใช้งาน" ไม่ได้คำแนะนำ ; เป็นข้อกำหนดของ API หากคุณไม่ได้ใช้GL_DYNAMIC_STORAGE_BIT
แล้วคุณไม่สามารถglBufferSubData
เขียนไปยังบัฟเฟอร์ผ่าน และอื่น ๆ
ถ้าฉันเข้าใจถูกต้อง local_size จะมีประโยชน์ก็ต่อเมื่อแชร์ข้อมูลระหว่างการเรียกใช้ดังนั้นฉันควรเก็บไว้ที่เดียว?
ไม่อันที่จริงห้ามใช้ 1. หากการเรียกใช้ทั้งหมดทำในสิ่งของตัวเองโดยไม่มีอุปสรรคในการดำเนินการหรือสิ่งที่คล้ายกันคุณควรเลือกขนาดโลคัลที่เทียบเท่ากับขนาด wavefront ของฮาร์ดแวร์ที่คุณกำลังทำงานอยู่ . เห็นได้ชัดว่าขึ้นอยู่กับการใช้งาน แต่ 32 เป็นค่าเริ่มต้นที่ดีกว่า 1 อย่างแน่นอน