การแชร์รหัสระหว่าง GLSL หลายตัว


30

ฉันมักจะพบว่าตัวเองคัดลอกรหัสวางระหว่างหลายเฉดสี ซึ่งรวมถึงการคำนวณหรือข้อมูลบางอย่างที่ใช้ร่วมกันระหว่างเฉดสีทั้งหมดในไปป์ไลน์เดียวและการคำนวณทั่วไปที่ยอดสุดยอดทั้งหมดของฉันต้องการ (หรือขั้นตอนอื่นใด)

แน่นอนว่าเป็นวิธีปฏิบัติที่น่ากลัว: ถ้าฉันต้องการเปลี่ยนรหัสที่ใดก็ได้ฉันต้องตรวจสอบให้แน่ใจว่าฉันเปลี่ยนทุกที่อื่น

มีวิธีปฏิบัติที่ดีที่สุดที่ได้รับการยอมรับในการรักษาDRYหรือไม่? ผู้คนแค่เติมไฟล์ร่วมกันเพียงไฟล์เดียวให้กับผู้ที่ต้องการซื้อของพวกเขาทั้งหมดหรือไม่? พวกเขาเขียน preprocessor C-style พื้นฐานของตัวเองซึ่งแยก#includeคำสั่งหรือไม่? หากมีรูปแบบที่ยอมรับในอุตสาหกรรมฉันต้องการติดตามพวกเขา


4
คำถามนี้อาจเป็นข้อโต้แย้งเล็กน้อยเนื่องจากเว็บไซต์ SE อื่น ๆ ไม่ต้องการคำถามเกี่ยวกับแนวทางปฏิบัติที่ดีที่สุด นี่เป็นความตั้งใจที่จะเห็นว่าชุมชนนี้เกี่ยวข้องกับคำถามเหล่านี้อย่างไร
Martin Ender

2
อืมมันดูดีสำหรับฉัน ฉันจะบอกว่าเราอยู่ในระดับ "บิตกว้าง" / "ทั่วไป" ในคำถามของเรามากกว่าพูด StackOverflow
คริสพูดว่า Reinstate Monica

2
StackOverflow เปลี่ยนจากการเป็น 'ขอให้เรา' เป็น 'อย่าถามเราจนกว่าคุณจะได้โปรด' คณะกรรมการ
insidesin

ถ้ามันหมายถึงการพิจารณาหัวข้อ - แล้วคำถามเกี่ยวกับ Meta ที่เกี่ยวข้อง?
SL Barth - Reinstate Monica

คำตอบ:


18

มีวิธีการมากมาย แต่ไม่มีใครสมบูรณ์แบบ

เป็นไปได้ที่จะแบ่งปันรหัสโดยใช้glAttachShaderเพื่อรวมเฉดสี แต่มันไม่สามารถแชร์สิ่งต่าง ๆ เช่น struct declarations หรือ#define-d constants มันใช้งานได้สำหรับฟังก์ชั่นการแบ่งปัน

บางคนชอบใช้อาเรย์ของสตริงที่ส่งไปเพื่อglShaderSourceเป็นวิธีในการเติมคำจำกัดความทั่วไปก่อนโค้ดของคุณ แต่สิ่งนี้มีข้อเสียบางประการ:

  1. เป็นการยากที่จะควบคุมสิ่งที่จะต้องรวมอยู่ในภายใน shader (คุณต้องมีระบบแยกต่างหากสำหรับสิ่งนี้)
  2. หมายความว่าผู้สร้าง shader ไม่สามารถระบุ GLSL #versionได้เนื่องจากคำสั่งต่อไปนี้ในข้อมูลจำเพาะ GLSL:

#versionสั่งจะต้องเกิดขึ้นใน Shader ก่อนสิ่งอื่นใดยกเว้นสำหรับความคิดเห็นและพื้นที่สีขาว

เนื่องจากคำสั่งนี้glShaderSourceไม่สามารถใช้เพื่อเติมข้อความก่อนการ#versionประกาศ หมายความว่า#versionบรรทัดจะต้องรวมอยู่ในglShaderSourceอาร์กิวเมนต์ของคุณซึ่งหมายความว่าอินเทอร์เฟซคอมไพเลอร์ GLSL ของคุณจำเป็นต้องได้รับการบอกกล่าวว่าควรใช้ GLSL เวอร์ชันใด นอกจากนี้การไม่ระบุ#versionจะทำให้คอมไพเลอร์ GLSL เป็นค่าเริ่มต้นเพื่อใช้ GLSL เวอร์ชัน 1.10 หากคุณต้องการให้ผู้เขียน shader ระบุ#versionสคริปต์ภายในด้วยวิธีมาตรฐานคุณต้องแทรก#include-s หลังจาก#versionคำสั่ง สิ่งนี้สามารถทำได้โดยการแยกวิเคราะห์ GLSL shader อย่างชัดเจนเพื่อค้นหา#versionสตริง (ถ้ามี) และทำการรวมของคุณหลังจากนั้น แต่มีการเข้าถึง#includeคำสั่งอาจจะดีกว่าที่จะควบคุมได้ง่ายขึ้นเมื่อรวมสิ่งเหล่านั้นจะต้องทำ ในทางกลับกันเนื่องจาก GLSL ละเว้นความคิดเห็นก่อน#versionบรรทัดคุณสามารถเพิ่มข้อมูลเมตาสำหรับรวมไว้ในความคิดเห็นที่ด้านบนของไฟล์ของคุณ (yuck)

ตอนนี้คำถามคือ: มีวิธีแก้ปัญหามาตรฐาน#includeหรือคุณต้องการม้วนส่วนขยายตัวประมวลผลล่วงหน้าของคุณเองหรือไม่?

มีGL_ARB_shading_language_includeส่วนขยาย แต่มีข้อเสีย:

  1. รองรับโดย NVIDIA เท่านั้น ( http://delphigl.de/glcapsviewer/listreports2.php?listreportsbyextension=GL_ARB_shading_language_include )
  2. มันทำงานได้โดยการระบุสตริงรวมก่อนเวลา ดังนั้นก่อนที่จะรวบรวมคุณจะต้องระบุว่าสตริง"/buffers.glsl"(ตามที่ใช้ใน#include "/buffers.glsl") สอดคล้องกับเนื้อหาของไฟล์buffer.glsl(ซึ่งคุณโหลดไว้ก่อนหน้านี้)
  3. ดังที่คุณอาจสังเกตเห็นในจุด (2) พา ธ ของคุณต้องเริ่มต้นด้วย"/"เช่นพา ธ สัมบูรณ์สไตล์ Linux โดยทั่วไปแล้วสัญกรณ์นี้ไม่คุ้นเคยกับโปรแกรมเมอร์ C และหมายความว่าคุณไม่สามารถระบุเส้นทางที่สัมพันธ์กันได้

การออกแบบทั่วไปคือการใช้#includeกลไกของคุณเองแต่อาจเป็นเรื่องยุ่งยากเนื่องจากคุณต้องแยกวิเคราะห์ (และประเมิน) คำสั่ง preprocessor อื่น ๆ เช่น#ifเพื่อจัดการการคอมไพล์ที่มีเงื่อนไข (เช่น guards header)

หากคุณใช้งานของคุณเอง#includeคุณยังมีเสรีภาพในวิธีที่คุณต้องการติดตั้ง:

  • คุณสามารถส่งผ่านสตริงล่วงหน้า (เช่นGL_ARB_shading_language_include)
  • คุณสามารถระบุการโทรกลับแบบรวม (ทำได้โดยไลบรารี D3DCompiler ของ DirectX)
  • คุณสามารถใช้ระบบที่อ่านได้โดยตรงจากระบบไฟล์เช่นเดียวกับที่ทำในแอพพลิเคชั่น C ทั่วไป

เพื่อให้เข้าใจง่ายคุณสามารถแทรกตัวป้องกันส่วนหัวสำหรับแต่ละรายการในเลเยอร์การประมวลผลล่วงหน้าของคุณได้ดังนั้นเลเยอร์โปรเซสเซอร์ของคุณจะมีลักษณะดังนี้:

if (#include and not_included_yet) include_file();

(Credit to Trent Reed เพื่อแสดงเทคนิคด้านบน)

โดยสรุปแล้วไม่มีวิธีการแก้ปัญหาอัตโนมัติมาตรฐานและเรียบง่าย ในการแก้ปัญหาในอนาคตคุณสามารถใช้อินเทอร์เฟซ SPIR-V OpenGL บางส่วนซึ่งในกรณีนี้คอมไพเลอร์ GLSL to SPIR-V อาจอยู่นอก GL API การมีคอมไพเลอร์นอกรันไทม์ OpenGL ช่วยลดความยุ่งยากในการนำสิ่งต่าง ๆ มาใช้เช่น#includeเนื่องจากเป็นสถานที่ที่เหมาะสมกว่าในการเชื่อมต่อกับระบบไฟล์ ฉันเชื่อว่าวิธีการที่แพร่หลายในปัจจุบันคือการใช้ตัวประมวลผลล่วงหน้าที่กำหนดเองซึ่งทำงานในลักษณะที่โปรแกรมเมอร์ C คนใดคนหนึ่งควรคุ้นเคย


Shaders ยังสามารถแยกออกเป็นโมดูลโดยใช้glslifyแม้ว่าจะใช้ได้กับ node.js เท่านั้น
Anderson Green

9

โดยทั่วไปฉันแค่ใช้ความจริงที่ว่า glShaderSource (... ) ยอมรับอาร์เรย์ของสตริงขณะที่อินพุต

ฉันใช้ไฟล์คำจำกัดความของ json ที่ใช้ shader ซึ่งระบุว่าประกอบด้วย shader (หรือโปรแกรมที่ถูกต้องมากขึ้น) และฉันระบุผู้ประมวลผลล่วงหน้าที่กำหนดว่าฉันอาจต้องการเครื่องแบบที่ใช้มันไฟล์เวอร์เท็กซ์ / ชิ้นส่วน shader และไฟล์ "การพึ่งพา" เพิ่มเติมทั้งหมด เหล่านี้เป็นเพียงชุดของฟังก์ชั่นที่ได้รับการผนวกเข้ากับแหล่งที่มาก่อนแหล่งที่มาของ shader ที่แท้จริง

เพียงเพิ่ม AFAIK, Unreal Engine 4 ใช้คำสั่ง #include ที่แยกวิเคราะห์และผนวกไฟล์ที่เกี่ยวข้องทั้งหมดก่อนการคอมไพล์ตามที่คุณแนะนำ


4

ฉันไม่คิดว่าจะมีการประชุมร่วมกัน แต่ถ้าฉันเดาฉันจะบอกว่าเกือบทุกคนใช้การรวมข้อความแบบง่ายๆเป็นขั้นตอนก่อนการประมวลผล ( #includeส่วนขยาย) เพราะมันง่ายมากที่จะทำ ดังนั้น. (ใน JavaScript / WebGL คุณสามารถทำได้ด้วยการแสดงออกปกติอย่างง่าย) ข้อเสียคือคุณสามารถดำเนินการประมวลผลล่วงหน้าในขั้นตอนออฟไลน์สำหรับบิลด์ "ปล่อย" เมื่อรหัส shader ไม่จำเป็นต้องเปลี่ยนอีกต่อไป

ในความเป็นจริงข้อบ่งชี้ว่าวิธีการนี้เป็นเรื่องธรรมดาเป็นความจริงที่เป็นส่วนขยาย ARB GL_ARB_shading_language_includeได้รับการแนะนำว่า: ฉันไม่แน่ใจว่านี่จะเป็นคุณสมบัติหลักหรือไม่ แต่ส่วนขยายนั้นเขียนจาก OpenGL 3.2


2
GL_ARB_shading_language_include ไม่ใช่คุณสมบัติหลัก ในความเป็นจริงมีเพียง NVIDIA เท่านั้นที่รองรับ ( delphigl.de/glcapsviewer/ … )
Nicolas Louis Guillemot

4

บางคนได้ชี้ให้เห็นแล้วว่าglShaderSourceสามารถใช้ชุดของสตริง

ยิ่งไปกว่านั้นใน GLSL การคอมไพล์ ( glShaderSource, glCompileShader) และการลิงก์ ( glAttachShader, glLinkProgram) ของ shader นั้นแยกจากกัน

ฉันได้ใช้มันในบางโครงการเพื่อแยกเฉดสีระหว่างส่วนเฉพาะและชิ้นส่วนที่ใช้ร่วมกันกับส่วนใหญ่ซึ่งจะรวบรวมและแบ่งปันกับโปรแกรมทั้งหมดของ Shader วิธีนี้ใช้งานได้และไม่ยากที่จะนำมาใช้: คุณเพียงแค่ต้องรักษารายการการพึ่งพา

ในแง่ของการบำรุงรักษาฉันไม่แน่ใจว่ามันเป็นชัยชนะ การสังเกตเหมือนกันเราลองแยกตัวประกอบ ในขณะที่มันหลีกเลี่ยงการซ้ำซ้อนแน่นอนค่าใช้จ่ายของเทคนิครู้สึกสำคัญ ยิ่งไปกว่านั้น shader สุดท้ายนั้นยากกว่าในการแตกไฟล์: คุณไม่สามารถต่อเชื่อมแหล่งที่มาของ shader ได้เพราะการประกาศจะจบลงในลำดับที่คอมไพเลอร์บางคนจะปฏิเสธหรือจะทำซ้ำ ดังนั้นจึงเป็นการยากที่จะทำการทดสอบ shader อย่างรวดเร็วในเครื่องมือที่แยกต่างหาก

ในที่สุดเทคนิคนี้จะแก้ไขปัญหา DRY บางอย่าง แต่ยังห่างไกลจากอุดมคติ

ในหัวข้อด้านฉันไม่แน่ใจว่าวิธีการนี้มีผลกระทบใด ๆ ในแง่ของการรวบรวมเวลา; ฉันได้อ่านแล้วว่าไดรเวอร์บางตัวรวบรวมเฉพาะโปรแกรม shader ในการเชื่อมโยง แต่ฉันไม่ได้วัด


จากความเข้าใจของฉันฉันคิดว่านี่ไม่ได้แก้ปัญหาการแบ่งปันคำจำกัดความของโครงสร้าง
Nicolas Louis Guillemot

@NicolasLouisGuillemot: ใช่คุณถูกต้องรหัสคำแนะนำจะถูกแชร์ในลักษณะนี้ไม่ใช่การประกาศ
Julien Guertault
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.