OpenGL hesaplama gölgelendiricilerinde arabellekleri doğru şekilde kullanma
Matris / vektör işlemlerini kullanarak ilk yazdığım algoritmayı, performansları en üst düzeye çıkarmaya çalışmak için OpenGL çekirdeklerine yeniden yazıyorum.
Temel bir OpenGL bilgisine sahibim, bu yüzden işleri çalıştırmayı başardım, ancak OpenGL tarafından sunulan çeşitli seçimleri yaparken çok sorun yaşıyorum, özellikle de arabellek parametrelerinin benim durumumda büyük etkisi olduğunu tahmin ediyorum çok fazla veri okuyup yazdığım yer.
Üç çekirdeği sırayla çağırıyorum:
İlk :
/* 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);
}
}
İkinci:
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)]);
}
}
Üçüncü:
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 belgesini okuyarak bulduğum şey:
- Algoritmanın tüm süresi boyunca aynı olan bazı değerler, gölgelendiriciyi derlemeden önce const olarak üretilir. Özellikle for-loop sınırı için kullanışlıdır
- Diğer tamponlara göre çok küçük olan bufferMat, SSBO'dan daha iyi performansa sahip olması gereken bir UBO'ya yerleştirilir. Derleme zamanı sabiti yaparak olayı daha iyi performans gösterebilir miyim? Küçük ama yine de birkaç yüz mat4
- Hem birkaç kez okunan hem de yazılan diğer tamponlar SSBO olarak daha iyi olmalıdır
- Tamponun 'kullanım' parametreleri için en iyi değerin ne olabileceğini anlamakta güçlük çekiyorum. Tüm tamponlar birkaç kez yazılıyor ve okunuyor, buraya ne yazacağımdan emin değilim.
- Doğru anlarsam, local_size yalnızca çağrılar arasında veri paylaşırken kullanışlıdır, bu yüzden onu birde tutmalı mıyım?
Bu çekirdekleri optimize etmek için nereye bakmam gerektiğiyle ilgili her türlü tavsiye veya ipucunu memnuniyetle alırım!
Yanıtlar
Derleme zamanı sabiti yaparak olayı daha iyi performans gösterebilir miyim?
Profilini çıkarman gerekecek. Bununla birlikte, "birkaç yüz mat4" "küçük" değildir .
Tamponun 'kullanım' parametreleri için en iyi değerin ne olabileceğini anlamakta güçlük çekiyorum. Tüm tamponlar birkaç kez yazılıyor ve okunuyor, buraya ne yazacağımdan emin değilim.
Birincisi, kullanım parametreleri üzeresiniz sizin tampon nesne, arkalarında bellek değil OpenGL'ın kullanım kullanımı. Bu da onların gibi işlevler bahsediyoruz olduğunu glBufferSubData
, glMapBufferRange
vb, vb. READ
CPU'nun arabellekten okuyacağı ancak ona yazmayacağı anlamına gelir. DRAW
CPU'nun arabelleğe yazacağı ancak ondan okumayacağı anlamına gelir.
İkincisi ... gerçekten umursamamalısın. Kullanım ipuçları korkunçtur, yetersiz belirtilmiştir ve o kadar kötüye kullanılmıştır ki, çoğu uygulama bunları tamamen görmezden gelir . NVIDIA'nın GL uygulaması muhtemelen onları en ciddiye alan uygulama.
Bunun yerine, değişmez depolama arabelleklerini kullanın . Bu "kullanım ipuçları" ipucu değildir ; API gereksinimleridir. Eğer kullanmıyorsanız GL_DYNAMIC_STORAGE_BIT
, o zaman olamaz aracılığıyla tampon yazma glBufferSubData
. Ve benzeri.
Doğru anlarsam, local_size yalnızca çağrılar arasında veri paylaşırken kullanışlıdır, bu yüzden onu birde tutmalı mıyım?
Hayır. Aslında, asla kullanmayın 1. Tüm çağrılar, yürütme engelleri veya benzerleri olmadan kendi işlerini yapıyorsa, üzerinde çalıştığınız donanımın dalga önü boyutuna eşdeğer bir yerel boyut seçmelisiniz . Açıkçası bu uygulamaya bağlıdır, ancak 32 kesinlikle 1'den daha iyi bir varsayılandır.