2013-07-02 40 views
13

Trình đổ bóng tính toán GLSL sau đây chỉ cần sao chép inImage đến outImage. Nó có nguồn gốc từ một quá trình xử lý hậu xử lý phức tạp hơn.Tại sao hàng rào đồng bộ hóa bộ nhớ chia sẻ khi memoryBarrier không?

Trong một số dòng đầu tiên của main(), một chuỗi đơn tải 64 pixel dữ liệu vào mảng được chia sẻ. Sau đó, sau khi đồng bộ hóa, mỗi 64 luồng ghi một điểm ảnh vào hình ảnh đầu ra.

Tùy thuộc vào cách tôi đồng bộ hóa, tôi nhận được các kết quả khác nhau. Tôi ban đầu nghĩ memoryBarrierShared() sẽ là cuộc gọi chính xác, nhưng nó tạo ra kết quả sau:

Unsynchronized result

đó là kết quả tương tự như không có đồng bộ hoặc sử dụng memoryBarrier() để thay thế.

Nếu tôi sử dụng barrier(), tôi nhận được như sau (mong muốn) Kết quả:

enter image description here

Các vằn rộng 32 pixel, và nếu tôi thay đổi kích thước workgroup để bất cứ điều gì ít hơn hoặc bằng 32, Tôi nhận được kết quả chính xác.

Điều gì đang xảy ra ở đây? Tôi có hiểu nhầm mục đích của memoryBarrierShared() không? Tại sao nên barrier() hoạt động?

#version 430 

#define SIZE 64 

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

layout(rgba32f) uniform readonly image2D inImage; 
uniform writeonly image2D outImage; 

shared vec4 shared_data[SIZE]; 

void main() { 
    ivec2 base = ivec2(gl_WorkGroupID.xy * gl_WorkGroupSize.xy); 
    ivec2 my_index = base + ivec2(gl_LocalInvocationID.x,0); 

    if (gl_LocalInvocationID.x == 0) { 
     for (int i = 0; i < SIZE; i++) { 
      shared_data[i] = imageLoad(inImage, base + ivec2(i,0)); 
     } 
    } 

    // with no synchronization: stripes 
    // memoryBarrier();  // stripes 
    // memoryBarrierShared(); // stripes 
    // barrier();    // works 

    imageStore(outImage, my_index, shared_data[gl_LocalInvocationID.x]); 
} 
+0

Làm cách nào để hiển thị nó? Có lẽ trình đổ bóng tính toán hoạt động chính xác, nhưng bản thân hình ảnh không được đồng bộ hóa giữa lời gọi trình đổ bóng tính toán và hiển thị sau, nghĩa là 'glMemoryBarrier' thích hợp ở phía máy chủ (không chắc chắn về điều đó). –

+0

Tôi đang sử dụng thư viện kết thúc OpenGL và đang xử lý tất cả các liên lạc với GPU. Do đó rất khó để kết hợp mã hiển thị vào MWE của tôi. Vì tôi cũng nhận được kết quả chính xác khi cho phép mỗi luồng thực hiện một 'imageLoad' duy nhất theo sau là một' imageStore', tôi không nghĩ rằng đồng bộ hóa thiết bị chủ là vấn đề. –

+0

* "Vì tôi cũng nhận được kết quả chính xác khi cho phép mỗi luồng thực hiện một imageLoad duy nhất theo sau là một imageStore, tôi không nghĩ rằng đồng bộ hóa thiết bị chủ là vấn đề." * - Vâng, nếu nó thực sự là đồng bộ liên thông thất bại, sau đó một tác dụng phụ của hành vi không xác định là, nó có thể làm việc rất tốt cho các cấu hình nhất định. 'glMemoryBarrier' vẫn là đồng bộ hóa thiết bị-thiết bị chứ không phải thiết bị lưu trữ. –

Trả lời

20

Vấn đề với cửa hàng tải hình ảnh và bạn bè là, việc thực hiện không thể chắc chắn nữa rằng một Shader chỉ thay đổi dữ liệu của nó được dành riêng giá trị sản lượng (ví dụ framebuffer sau một Shader fragment). Điều này được áp dụng nhiều hơn để tính toán các trình đổ bóng, không có đầu ra chuyên dụng, nhưng chỉ xuất các dữ liệu bằng cách ghi dữ liệu vào kho lưu trữ, như hình ảnh, bộ đệm lưu trữ hoặc bộ đếm nguyên tử. Điều này có thể yêu cầu đồng bộ hóa thủ công giữa các lần truyền riêng lẻ, nếu không, trình đổ bóng phân đoạn cố gắng truy cập kết cấu có thể không có dữ liệu gần đây nhất được ghi vào kết cấu đó bằng thao tác lưu trữ hình ảnh bằng lần truyền trước, như trình đổ bóng tính toán của bạn.

Vì vậy, nó có thể là bóng đổ tính toán của bạn hoạt động hoàn hảo, nhưng đó là đồng bộ hóa với màn hình sau (hoặc bất kỳ) nào (cần phải đọc dữ liệu hình ảnh này bằng cách nào đó). Vì mục đích này, có tồn tại hàm glMemoryBarrier. Tùy thuộc vào cách bạn đọc dữ liệu hình ảnh đó trên màn hình hiển thị (hoặc chính xác hơn là đường truyền đọc hình ảnh sau khi thẻ đổ bóng tính toán), bạn cần đưa cờ khác cho hàm này. Nếu bạn đọc nó sử dụng một kết cấu, sử dụng GL_TEXTURE_FETCH_BARRIER_BIT​, nếu bạn sử dụng một tải hình ảnh một lần nữa, sử dụng GL_SHADER_IMAGE_ACCESS_BARRIER_BIT​, nếu sử dụng glBlitFramebuffer để trưng bày, sử dụng GL_FRAMEBUFFER_BARRIER_BIT​ ...

Mặc dù tôi không có nhiều kinh nghiệm với hình ảnh load/store và snynchronization bộ nhớ thủ công và đây chỉ là những gì tôi đưa ra về mặt lý thuyết. Vì vậy, nếu có ai biết rõ hơn hoặc bạn đã sử dụng một số glMemoryBarrier thích hợp, thì hãy sửa tôi. Tương tự như vậy, điều này không cần phải là lỗi duy nhất của bạn (nếu có). Tuy nhiên, hai điểm cuối cùng từ bài viết Wiki liên kết thực sự giải quyết trường hợp sử dụng của bạn và IMHO làm cho nó rõ ràng rằng bạn cần một số loại glMemoryBarrier:

  • dữ liệu ghi vào biến hình ảnh trong một render vượt qua và đọc bởi trình đổ bóng trong lần truyền sau không cần sử dụng các biến số coherent hoặc memoryBarrier().Gọi số glMemoryBarrier bằng số SHADER_IMAGE_ACCESS_BARRIER_BIT​ đặt trong các rào cản giữa các đường chuyền là cần thiết.

  • dữ liệu được viết bởi các shader trong một render vượt qua và đọc bởi một cơ chế khác (ví dụ, đỉnh hoặc đệm index kéo) trong một đường chuyền sau cần không sử dụng coherent biến hoặc memoryBarrier(). Gọi số glMemoryBarrier với các bit thích hợp được đặt trong các rào cản giữa các số là cần thiết.


EDIT: Trên thực tế các Wiki article on compute shaders nói

Shared truy cập biến sử dụng các quy tắc cho việc truy cập bộ nhớ rời rạc. Điều này có nghĩa là người dùng phải thực hiện đồng bộ hóa nhất định theo thứ tự để đảm bảo rằng các biến được chia sẻ hiển thị.

Biến được chia sẻ đều được khai báo hoàn toàn coherent​, vì vậy bạn không cần cần (và không thể sử dụng) vòng loại đó. Tuy nhiên, bạn vẫn cần phải cung cấp một rào cản bộ nhớ thích hợp.

Bộ rào cản bộ nhớ thông thường có sẵn để tính toán trình đổ bóng, nhưng chúng cũng có quyền truy cập memoryBarrierShared()​; rào cản này là dành riêng cho thứ tự biến được chia sẻ. groupMemoryBarrier() hoạt động như memoryBarrier()​, đặt hàng bộ nhớ ghi cho tất cả các loại biến, nhưng nó chỉ đặt hàng đọc/ghi cho nhóm công việc hiện tại.

Trong khi tất cả các lời gọi trong nhóm công việc được cho là thực thi "song song", điều đó không có nghĩa là bạn có thể giả định rằng tất cả chúng là thực hiện trong bước khóa. Nếu bạn cần đảm bảo rằng lời gọi có số được ghi vào một số biến để bạn có thể đọc nó, bạn cần đồng bộ hóa thực thi với lời gọi, chứ không phải chỉ phát hành bộ nhớ rào cản (bạn vẫn cần hàng rào bộ nhớ).

Để đồng bộ hóa lần đọc và ghi giữa lời gọi trong nhóm làm việc, bạn phải sử dụng chức năng barrier(). Điều này buộc đồng bộ hóa rõ ràng giữa tất cả các yêu cầu trong nhóm làm việc. Việc thực hiện trong nhóm công việc sẽ không được tiến hành cho đến khi tất cả các yêu cầu khác đã đạt đến rào cản này. Sau khi vượt qua barrier()​, tất cả biến được chia sẻ trước đó được viết trên tất cả các lời gọi trong nhóm sẽ hiển thị.

Vì vậy, đây thực sự có vẻ như bạn cần barrier ở đó và memoryBarrierShared là không đủ (mặc dù bạn không cần cả hai, như câu cuối cùng nói). Hàng rào bộ nhớ sẽ chỉ đồng bộ hóa bộ nhớ , nhưng nó không dừng việc thực thi của chủ đề để vượt qua nó.Vì vậy, các chủ đề sẽ không đọc bất kỳ dữ liệu được lưu trữ cũ từ bộ nhớ chia sẻ nếu chủ đề đầu tiên đã viết một cái gì đó, nhưng chúng rất có thể đạt đến điểm đọc trước khi chủ đề đầu tiên đã cố gắng viết bất cứ điều gì cả.

Điều này thực sự hoàn toàn phù hợp với thực tế là đối với kích thước khối từ 32 trở xuống mà nó hoạt động và 32 pixel đầu tiên hoạt động. Ít nhất là trên phần cứng NVIDIA 32 là kích thước dọc và do đó số lượng các luồng hoạt động trong bước khóa hoàn hảo. Vì vậy, 32 chủ đề đầu tiên (tốt, mỗi khối 32 chủ đề) luôn luôn làm việc chính xác song song (tốt, khái niệm đó là) và do đó họ không thể giới thiệu bất kỳ điều kiện chủng tộc. Đây cũng là lý do tại sao bạn không thực sự cần bất kỳ đồng bộ hóa nào nếu bạn biết bạn làm việc bên trong một sợi dọc, một tối ưu hóa chung.

+4

Tuyệt vời. Điểm thưởng để giải thích ngưỡng 32 pixel. –

Các vấn đề liên quan