Chia sẻ mã giữa nhiều shader GLSL


30

Tôi thường thấy mình dán mã sao chép giữa một số shader. Điều này bao gồm cả các tính toán hoặc dữ liệu nhất định được chia sẻ giữa tất cả các shader trong một đường ống duy nhất và các tính toán chung mà tất cả các shader đỉnh của tôi cần (hoặc bất kỳ giai đoạn nào khác).

Tất nhiên, đó là một thực tế khủng khiếp: nếu tôi cần thay đổi mã ở bất cứ đâu, tôi cần đảm bảo rằng tôi thay đổi nó ở mọi nơi khác.

Có một thực hành tốt nhất được chấp nhận để giữ DRY ? Có phải mọi người chỉ cần chuẩn bị một tập tin chung cho tất cả các shader của họ? Họ có viết bộ tiền xử lý kiểu C thô sơ của riêng mình để phân tích #includecác lệnh không? Nếu có các mô hình được chấp nhận trong ngành, tôi muốn theo dõi chúng.


4
Câu hỏi này có thể gây tranh cãi một chút, bởi vì một số trang SE khác không muốn có câu hỏi về thực tiễn tốt nhất. Đây là chủ ý để xem làm thế nào cộng đồng này đứng về các câu hỏi như vậy.
Martin Ender

2
Hmm, có vẻ tốt với tôi. Tôi muốn nói rằng chúng ta ở một mức độ lớn một chút "rộng hơn" / "tổng quát hơn" trong các câu hỏi của chúng tôi hơn là, nói, StackOverflow.
Chris nói Phục hồi

2
StackOverflow đã chuyển từ bảng 'hỏi chúng tôi' sang 'không hỏi chúng tôi trừ khi bạn phải làm hài lòng'.
bên trong

Nếu nó có nghĩa là để xác định tính chủ đề, vậy còn câu hỏi Meta liên quan thì sao?
SL Barth - Phục hồi Monica

Câu trả lời:


18

Có một loạt các cách tiếp cận, nhưng không có cách nào là hoàn hảo.

Có thể chia sẻ mã bằng cách sử dụng glAttachShaderđể kết hợp các trình tạo bóng, nhưng điều này không thể chia sẻ những thứ như khai báo cấu trúc hoặc #definehằng số -d. Nó không hoạt động để chia sẻ chức năng.

Một số người thích sử dụng mảng các chuỗi được truyền vào glShaderSourcenhư một cách để bổ sung các định nghĩa phổ biến trước mã của bạn, nhưng điều này có một số nhược điểm:

  1. Việc kiểm soát những gì cần được đưa vào trong shader sẽ khó hơn (bạn cần một hệ thống riêng cho việc này.)
  2. Điều đó có nghĩa là tác giả shader không thể chỉ định GLSL #version, do tuyên bố sau trong thông số GLSL:

Lệnh #version phải xảy ra trong một shader trước bất cứ thứ gì khác, ngoại trừ các bình luận và khoảng trắng.

Do tuyên bố này, glShaderSourcekhông thể được sử dụng để thêm vào văn bản trước khi #versionkhai báo. Điều này có nghĩa là #versiondòng cần phải được bao gồm trong các glShaderSourceđối số của bạn , điều đó có nghĩa là giao diện trình biên dịch GLSL của bạn cần được thông báo bằng cách nào đó phiên bản GLSL dự kiến ​​sẽ được sử dụng. Ngoài ra, không chỉ định a #versionsẽ làm cho trình biên dịch GLSL mặc định sử dụng GLSL phiên bản 1.10. Nếu bạn muốn để các tác giả shader chỉ định #versionbên trong tập lệnh theo cách tiêu chuẩn, thì bạn cần phải chèn bằng cách nào đó #includesau #versioncâu lệnh. Điều này có thể được thực hiện bằng cách phân tích rõ ràng trình tạo bóng GLSL để tìm #versionchuỗi (nếu có) và tạo các vùi của bạn sau chuỗi đó, nhưng có quyền truy cập vào một#includeChỉ thị có thể được ưu tiên để kiểm soát dễ dàng hơn khi những vùi đó cần được thực hiện. Mặt khác, vì GLSL bỏ qua các nhận xét trước #versiondòng, bạn có thể thêm siêu dữ liệu để bao gồm trong các nhận xét ở đầu tệp của mình (yuck.)

Câu hỏi bây giờ là: Có một giải pháp tiêu chuẩn cho #include, hoặc bạn cần cuộn phần mở rộng tiền xử lý của riêng mình?

GL_ARB_shading_language_includephần mở rộng, nhưng nó có một số nhược điểm:

  1. Nó chỉ được hỗ trợ bởi NVIDIA ( http://delphigl.de/glcapsviewer/listreports2.php?listreportsbyextension=GL_ARB_shading_lingu_include )
  2. Nó hoạt động bằng cách chỉ định các chuỗi bao gồm trước thời hạn. Do đó, trước khi biên dịch, bạn cần xác định rằng chuỗi "/buffers.glsl"(như được sử dụng trong #include "/buffers.glsl") tương ứng với nội dung của tệp buffer.glsl(mà bạn đã tải trước đó).
  3. Như bạn có thể nhận thấy ở điểm (2), các đường dẫn của bạn cần bắt đầu bằng "/", như các đường dẫn tuyệt đối theo kiểu Linux. Ký hiệu này thường không quen thuộc với các lập trình viên C và có nghĩa là bạn không thể chỉ định các đường dẫn tương đối.

Một thiết kế phổ biến là thực hiện #includecơ chế của riêng bạn , nhưng điều này có thể khó khăn vì bạn cũng cần phân tích (và đánh giá) các hướng dẫn tiền xử lý khác như #ifđể xử lý việc biên dịch có điều kiện (như bộ bảo vệ tiêu đề).

Nếu bạn tự thực hiện #include, bạn cũng có một số quyền tự do trong cách bạn muốn thực hiện nó:

  • Bạn có thể vượt qua các chuỗi trước thời hạn (như GL_ARB_shading_language_include).
  • Bạn có thể chỉ định một cuộc gọi lại bao gồm (điều này được thực hiện bởi thư viện D3DCompiler của DirectX.)
  • Bạn có thể thực hiện một hệ thống luôn đọc trực tiếp từ hệ thống tệp, như được thực hiện trong các ứng dụng C điển hình.

Để đơn giản hóa, bạn có thể tự động chèn các bộ bảo vệ tiêu đề cho mỗi bao gồm trong lớp tiền xử lý của mình, vì vậy lớp bộ xử lý của bạn trông như sau:

if (#include and not_included_yet) include_file();

(Tín dụng cho Trent Reed đã cho tôi thấy kỹ thuật trên.)

Tóm lại , không tồn tại giải pháp tự động, tiêu chuẩn và đơn giản. Trong một giải pháp trong tương lai, bạn có thể sử dụng một số giao diện OpenGL SPIR-V, trong trường hợp đó, trình biên dịch GLSL sang SPIR-V có thể nằm ngoài API GL. Có trình biên dịch bên ngoài thời gian chạy OpenGL đơn giản hóa rất nhiều việc thực hiện những thứ như #includevì đây là nơi thích hợp hơn để giao tiếp với hệ thống tệp. Tôi tin rằng phương pháp phổ biến hiện nay là chỉ thực hiện một bộ tiền xử lý tùy chỉnh hoạt động theo cách mà bất kỳ lập trình viên C nào cũng nên làm quen.


Các shader cũng có thể được tách thành các mô-đun bằng cách sử dụng glslify , mặc dù nó chỉ hoạt động với node.js.
Anderson Green

9

Tôi thường chỉ sử dụng thực tế là glShaderSource (...) chấp nhận một chuỗi các chuỗi làm đầu vào.

Tôi sử dụng tệp định nghĩa shader dựa trên json, trong đó chỉ định cách tạo shader (hoặc chương trình chính xác hơn) và ở đó tôi chỉ định bộ tiền xử lý xác định tôi có thể cần, đồng phục mà nó sử dụng, tệp shader đỉnh / mảnh và tất cả các tập tin "phụ thuộc" bổ sung. Đây chỉ là tập hợp các hàm được gắn vào nguồn trước nguồn shader thực tế.

Chỉ cần thêm, AFAIK, Unreal Engine 4 sử dụng một lệnh #incoide được phân tích cú pháp và nối thêm tất cả các tệp có liên quan, trước khi biên dịch, như bạn đã đề xuất.


4

Tôi không nghĩ có một quy ước chung, nhưng nếu tôi đoán, tôi sẽ nói rằng hầu hết mọi người đều thực hiện một số hình thức bao gồm văn bản đơn giản như một bước tiền xử lý (một #includephần mở rộng), bởi vì nó rất dễ thực hiện vì thế. (Trong JavaScript / WebGL, bạn có thể làm điều đó với một biểu thức chính quy đơn giản chẳng hạn). Mặt trái của điều này là bạn có thể thực hiện quá trình tiền xử lý trong một bước ngoại tuyến để xây dựng "phát hành", khi mã shader không còn cần phải thay đổi.

Trong thực tế, một dấu hiệu cho thấy cách tiếp cận này là phổ biến là thực tế là một phần mở rộng ARB đã được giới thiệu cho điều đó : GL_ARB_shading_language_include. Tôi không chắc bây giờ nó có trở thành một tính năng cốt lõi hay không, nhưng phần mở rộng được viết dựa trên OpenGL 3.2.


2
GL_ARB_shading_lingu_include không phải là một tính năng cốt lõi. Trên thực tế, chỉ có NVIDIA hỗ trợ nó. ( delphigl.de/glcapsviewer/
Nicolas Louis Guillemot

4

Một số người đã chỉ ra rằng glShaderSourcecó thể mất một chuỗi các chuỗi.

Trên hết, trong GLSL, việc biên dịch ( glShaderSource, glCompileShader) và liên kết ( glAttachShader, glLinkProgram) của trình đổ bóng là riêng biệt.

Tôi đã sử dụng điều đó trong một số dự án để phân chia các shader giữa phần cụ thể và các phần chung cho hầu hết các shader, sau đó được biên dịch và chia sẻ với tất cả các chương trình shader. Điều này hoạt động và không khó để thực hiện: bạn chỉ cần duy trì một danh sách phụ thuộc.

Về mặt khả năng duy trì, tôi không chắc đó là một chiến thắng. Các quan sát là như nhau, chúng ta hãy cố gắng để nhân tố. Trong khi nó thực sự tránh sự lặp lại, chi phí kỹ thuật cảm thấy đáng kể. Hơn nữa, trình đổ bóng cuối cùng khó trích xuất hơn: bạn không thể nối các nguồn của trình đổ bóng, vì các khai báo kết thúc theo thứ tự mà một số trình biên dịch sẽ từ chối hoặc sẽ được sao chép. Vì vậy, nó làm cho việc kiểm tra đổ bóng nhanh trong một công cụ riêng biệt trở nên khó khăn hơn.

Cuối cùng, kỹ thuật này giải quyết một số vấn đề DRY, nhưng nó không lý tưởng lắm.

Về một chủ đề phụ, tôi không chắc cách tiếp cận này có bất kỳ tác động nào về thời gian biên dịch hay không; Tôi đã đọc được rằng một số trình điều khiển chỉ thực sự biên dịch chương trình đổ bóng khi liên kết, nhưng tôi chưa đo được.


Từ hiểu biết của tôi, tôi nghĩ rằng điều này không giải quyết được vấn đề chia sẻ định nghĩa cấu trúc.
Nicolas Louis Guillemot

@NicolasLouisGuillemot: có bạn đúng, chỉ có mã hướng dẫn được chia sẻ theo cách này, không phải khai báo.
Julien Guertault
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.