2016-05-05 12 views
3

Gibt es eine elegante Möglichkeit, mit Array-Rändern in Compute Shadern umzugehen? (Wenn man bedenkt, dass die Dimension der Arbeitsgruppe im Shader fest codiert sein soll)Jeder elegante Weg mit Array-Margen in OpenGL Compute Shaders?

Betrachten Sie den folgenden Shader-Code, der eine Präfix-Summe für ein 2048-Array berechnet, wenn er mit glDispatchCompute (1,1,1) aufgerufen wird:

#version 430 core 

layout (local_size_x = 1024) in; 

layout (binding = 0) coherent readonly buffer block1 
{ 
    float input_data[gl_WorkGroupSize.x]; 
}; 

layout (binding = 1) coherent writeonly buffer block2 
{ 
    float output_data[gl_WorkGroupSize.x]; 
}; 

shared float shared_data[gl_WorkGroupSize.x * 2]; 

void main(void) 
{ 
uint id = gl_LocalInvocationID.x; 
uint rd_id; 
uint wr_id; 
uint mask; 

const uint steps = uint(log2(gl_WorkGroupSize.x)) + 1; 
uint step = 0; 

shared_data[id * 2] = input_data[id * 2]; 
shared_data[id * 2 + 1] = input_data[id * 2 + 1]; 

barrier(); 

for (step = 0; step < steps; step++) 
{ 
    mask = (1 << step) - 1; 
    rd_id = ((id >> step) << (step + 1)) + mask; 
    wr_id = rd_id + 1 + (id & mask); 

    shared_data[wr_id] += shared_data[rd_id]; 

    barrier(); 
} 

output_data[id * 2] = shared_data[id * 2]; 
output_data[id * 2 + 1] = shared_data[id * 2 + 1]; 
} 

Aber was, wenn ich eine Präfixsumme für ein Array von 3000 Elementen berechnen möchte?

Antwort

2

Für den Umgang mit den zusätzlichen, unüblichen Daten ist das ganz einfach: mehr Platz zuweisen. Dispatch Calls arbeiten auf ganzen Vielfachen von Arbeitsgruppen. Sie müssen also sicherstellen, dass für das, was Sie versenden, ausreichend Speicherplatz vorhanden ist.

lassen Sie es einfach nicht initialisierten für den Eingangspuffer und ignoriert es, wenn Sie die Ausgabe in lesen

Aber es gibt andere Probleme mit dem Shader, der sie von der Arbeit mit einem Abfertigungs Anruf verhindern.


Sie haben Ihren Shader explizit so entworfen, dass er nur für einen einzelnen Arbeitsgruppenversand funktioniert. Das heißt, egal wie viele Arbeitsgruppen Sie versenden, sie werden alle die gleichen Daten lesen und schreiben.

Zuerst, as previously discussed, hören Sie auf, den Pufferdaten eine absolute Länge zu geben. Sie wissen nicht, wie viele Arbeitsgruppen zum Zeitpunkt der Kompilierung aufgerufen werden. das ist eine Laufzeitentscheidung. Legen Sie also die Größe der Array-Laufzeit fest.

layout (binding = 0) readonly buffer block1 
{ 
    float input_data[]; 
}; 

layout (binding = 1) writeonly buffer block2 
{ 
    float output_data[]; 
}; 

Beachten Sie auch, den Mangel an coherent. Sie sind nicht mit diesen Puffern in irgendeiner Weise, die dieses Qualifikationsmerkmal erfordern würde.

Ihre shared Daten müssen immer noch eine Größe haben.

Zweitens ist jedes Arbeitselement für das Lesen eines bestimmten Werts von input_data und das Schreiben eines bestimmten Werts auf output_data verantwortlich. In Ihrem aktuellen Code ist dieser Index id, aber Ihr aktueller Code berechnet ihn nur basierend auf dem Arbeitsaufgabenindex innerhalb einer Arbeitsgruppe. Um es für alle Arbeitsaufgaben in allen Arbeitsgruppen zu berechnen, dies zu tun:

const uint id = dot(gl_GlobalInvocationID, 
        vec3(1, gl_NumWorkGroups.x, gl_NumWorkGroups.y * gl_NumWorkGroups.x) 

Das Punktprodukt ist nur eine andere Art Multiplikationen zu tun, und dann die Komponenten summiert werden. gl_GlobalInvocationID ist die 3D-Position jedes Arbeitselements global. Jedes Arbeitselement wird eine eindeutige gl_GlobalInvocationId; Das Dot-Produkt verwandelt den 3D-Standort in einen 1D-Index.

Drittens, verwenden Sie in Ihrer tatsächlichen Logik gidnur für den Zugriff auf Daten in Ihren Puffern. Wenn Daten in Ihrem gemeinsam genutzten Speicher zugreifen, müssen Sie gl_LocalInvocationIndex verwenden (was im Wesentlichen ist, was id verwendet werden soll):

const uint lid = gl_LocalInvocationIndex; 
shared_data[lid * 2] = input_data[id * 2]; 
shared_data[lid * 2 + 1] = input_data[id * 2 + 1]; 

for (step = 0; step < steps; step++) 
{ 
    mask = (1 << step) - 1; 
    rd_id = ((lid >> step) << (step + 1)) + mask; 
    wr_id = rd_id + 1 + (lid & mask); 

    shared_data[wr_id] += shared_data[rd_id]; 

    barrier(); 
} 

output_data[id * 2] = shared_data[lid * 2]; 
output_data[id * 2 + 1] = shared_data[lid * 2 + 1]; 

Es ist besser gl_LocalInvocationIndex statt gl_LocalInvocationID.x zu verwenden, weil Sie einen Tag in einem weiteren Arbeitseinheiten benötigen Arbeitsgruppe als Sie mit nur einer Dimension der lokalen Größe erhalten können. Mit gl_LocalInvocationIndex berücksichtigt der Index immer alle Dimensionen der lokalen Größe.

+0

Danke für Ihre Beobachtungen. Aber ich bin mir nicht sicher, ob ich die Antwort auf meine Frage verstehe. Ich habe ein Array mit 3000 Elementen, für das ich die Präfixsumme berechnen möchte. Das Beste, was ich tun kann, ist, es so aufzufüllen, dass es 4096 Elemente enthält und die Ergebnisse ignoriert, die mich nicht interessieren (vorausgesetzt, ich behalte die lokale_size_x = 1024)? – markwalberg

+0

@ BogdanPetcu: Das ist die allgemeine Idee. Ich bin mir nicht sicher, wie viel Nebensprechen in Ihren Arbeitselementen vorhanden ist, daher können kleinere lokale Größen besser sein. –