आगामी WebGPU पर जाने का मतलब केवल ग्राफ़िक्स एपीआई स्विच करने से कहीं अधिक है। यह वेब ग्राफ़िक्स के भविष्य की दिशा में भी एक कदम है। लेकिन यह प्रवास तैयारी और समझ के साथ बेहतर हो जाएगा - और यह लेख आपको तैयार कर देगा।
सभी को नमस्कार, मेरा नाम दिमित्री इवाशेंको है और मैं MY.GAMES में एक सॉफ्टवेयर इंजीनियर हूं। इस लेख में, हम WebGL और आगामी WebGPU के बीच अंतरों पर चर्चा करेंगे, और यह बताएंगे कि माइग्रेशन के लिए अपना प्रोजेक्ट कैसे तैयार करें।
WebGL और WebGPU की समयरेखा
WebGPU की वर्तमान स्थिति, और आगे क्या होगा
उच्च स्तरीय वैचारिक मतभेद
प्रारंभ
• वेबजीएल: संदर्भ मॉडल
• वेबजीपीयू: डिवाइस मॉडल
कार्यक्रम और पाइपलाइन
• वेबजीएल: प्रोग्राम
• वेबजीपीयू: पाइपलाइन
वर्दी
• वेबजीएल 1 में वर्दी
• WebGL 2 में वर्दी
• वेबजीपीयू में वर्दी
शेडर्स
• शेडर भाषा: जीएलएसएल बनाम डब्लूजीएसएल
• डेटा प्रकारों की तुलना
• संरचनाएं
• कार्य घोषणाएँ
• अंतर्निहित कार्य
• शेडर रूपांतरण
कन्वेंशन मतभेद
बनावट
• व्यूपोर्ट स्पेस
• क्लिप स्पेस
वेबजीपीयू टिप्स और ट्रिक्स
• आपके द्वारा उपयोग की जाने वाली पाइपलाइनों की संख्या कम करें।
• पाइपलाइन पहले से बनाएं
• रेंडरबंडल्स का उपयोग करें
सारांश
कई अन्य वेब प्रौद्योगिकियों की तरह, WebGL की जड़ें अतीत में काफी दूर तक फैली हुई हैं। WebGPU की ओर बढ़ने के पीछे की गतिशीलता और प्रेरणा को समझने के लिए, सबसे पहले WebGL विकास के इतिहास पर एक नज़र डालना सहायक होगा:
हाल के वर्षों में, नए ग्राफ़िक्स एपीआई में रुचि बढ़ी है जो डेवलपर्स को अधिक नियंत्रण और लचीलापन प्रदान करते हैं:
आज, WebGPU संस्करण 113 से शुरू होकर Google Chrome और Microsoft Edge ब्राउज़र के माध्यम से Windows, Mac और ChromeOS जैसे कई प्लेटफार्मों पर उपलब्ध है। निकट भविष्य में Linux और Android के लिए समर्थन अपेक्षित है।
यहां कुछ इंजन दिए गए हैं जो WebGPU के लिए पहले से ही समर्थन करते हैं (या प्रयोगात्मक समर्थन प्रदान करते हैं):
इसे ध्यान में रखते हुए, वेबजीपीयू में परिवर्तन करना या कम से कम ऐसे परिवर्तन के लिए परियोजनाएं तैयार करना निकट भविष्य में एक सामयिक कदम प्रतीत होता है।
आइए ज़ूम आउट करें और आरंभीकरण से शुरू करके WebGL और WebGPU के बीच कुछ उच्च-स्तरीय वैचारिक अंतरों पर एक नज़र डालें।
ग्राफ़िक्स एपीआई के साथ काम करना शुरू करते समय, पहले चरणों में से एक इंटरैक्शन के लिए मुख्य ऑब्जेक्ट को प्रारंभ करना है। यह प्रक्रिया WebGL और WebGPU के बीच भिन्न है, दोनों प्रणालियों के लिए कुछ ख़ासियतें हैं।
WebGL में, इस ऑब्जेक्ट को "संदर्भ" के रूप में जाना जाता है, जो अनिवार्य रूप से HTML5 कैनवास तत्व पर ड्राइंग के लिए एक इंटरफ़ेस का प्रतिनिधित्व करता है। इस संदर्भ को प्राप्त करना काफी सरल है:
const gl = canvas.getContext('webgl');
WebGL का संदर्भ वास्तव में एक विशिष्ट कैनवास से जुड़ा हुआ है। इसका मतलब यह है कि यदि आपको कई कैनवस पर प्रस्तुतिकरण की आवश्यकता है, तो आपको कई संदर्भों की आवश्यकता होगी।
WebGPU ने "डिवाइस" नामक एक नई अवधारणा प्रस्तुत की है। यह डिवाइस एक जीपीयू एब्स्ट्रैक्शन का प्रतिनिधित्व करता है जिसके साथ आप इंटरैक्ट करेंगे। आरंभीकरण प्रक्रिया WebGL की तुलना में थोड़ी अधिक जटिल है, लेकिन यह अधिक लचीलापन प्रदान करती है:
const adapter = await navigator.gpu.requestAdapter(); const device = await adapter.requestDevice(); const context = canvas.getContext('webgpu'); context.configure({ device, format: 'bgra8unorm', });
इस मॉडल के फायदों में से एक यह है कि एक डिवाइस कई कैनवस पर या किसी पर भी प्रस्तुत नहीं कर सकता है। यह अतिरिक्त लचीलापन प्रदान करता है; उदाहरण के लिए, एक डिवाइस कई विंडो या संदर्भों में रेंडरिंग को नियंत्रित कर सकता है।
WebGL और WebGPU ग्राफ़िक्स पाइपलाइन के प्रबंधन और आयोजन के लिए विभिन्न दृष्टिकोणों का प्रतिनिधित्व करते हैं।
WebGL में, मुख्य फोकस शेडर प्रोग्राम पर है। प्रोग्राम वर्टेक्स और फ्रैगमेंट शेडर्स को जोड़ता है, यह परिभाषित करता है कि वर्टिस को कैसे रूपांतरित किया जाना चाहिए और प्रत्येक पिक्सेल को कैसे रंगीन किया जाना चाहिए।
const program = gl.createProgram(); gl.attachShader(program, vertShader); gl.attachShader(program, fragShader); gl.bindAttribLocation(program, 'position', 0); gl.linkProgram(program);
WebGL में प्रोग्राम बनाने के चरण:
यह प्रक्रिया लचीले ग्राफिक्स नियंत्रण की अनुमति देती है, लेकिन जटिल भी हो सकती है और त्रुटियों की संभावना भी हो सकती है, खासकर बड़ी और जटिल परियोजनाओं के लिए।
WebGPU एक अलग प्रोग्राम के बजाय "पाइपलाइन" की अवधारणा पेश करता है। यह पाइपलाइन न केवल शेडर्स बल्कि अन्य जानकारी को भी जोड़ती है, जिसे वेबजीएल में राज्यों के रूप में स्थापित किया जाता है। इसलिए, WebGPU में एक पाइपलाइन बनाना अधिक जटिल लगता है:
const pipeline = device.createRenderPipeline({ layout: 'auto', vertex: { module: shaderModule, entryPoint: 'vertexMain', buffers: [{ arrayStride: 12, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }] }], }, fragment: { module: shaderModule, entryPoint: 'fragmentMain', targets: [{ format, }], }, });
WebGPU में पाइपलाइन बनाने के चरण:
जबकि WebGL रेंडरिंग के प्रत्येक पहलू को अलग करता है, WebGPU अधिक पहलुओं को एक ही ऑब्जेक्ट में समाहित करने का प्रयास करता है, जिससे सिस्टम अधिक मॉड्यूलर और लचीला हो जाता है। शेडर्स और रेंडरिंग स्टेट्स को अलग-अलग प्रबंधित करने के बजाय, जैसा कि WebGL में किया जाता है, WebGPU सब कुछ को एक पाइपलाइन ऑब्जेक्ट में जोड़ता है। इससे प्रक्रिया अधिक पूर्वानुमानित हो जाती है और त्रुटियों की संभावना कम हो जाती है:
यूनिफ़ॉर्म वैरिएबल निरंतर डेटा प्रदान करते हैं जो सभी शेडर इंस्टेंस के लिए उपलब्ध है।
बुनियादी वेबजीएल में, हमारे पास एपीआई कॉल के माध्यम से सीधे uniform
चर सेट करने की क्षमता है।
जीएलएसएल :
uniform vec3 u_LightPos; uniform vec3 u_LightDir; uniform vec3 u_LightColor;
जावास्क्रिप्ट :
const location = gl.getUniformLocation(p, "u_LightPos"); gl.uniform3fv(location, [100, 300, 500]);
यह विधि सरल है, लेकिन प्रत्येक uniform
चर के लिए एकाधिक एपीआई कॉल की आवश्यकता होती है।
WebGL 2 के आगमन के साथ, अब हमारे पास uniform
चरों को बफ़र्स में समूहित करने की क्षमता है। हालाँकि आप अभी भी अलग-अलग यूनिफ़ॉर्म शेडर्स का उपयोग कर सकते हैं, एक बेहतर विकल्प यूनिफ़ॉर्म बफ़र्स का उपयोग करके अलग-अलग यूनिफ़ॉर्म को एक बड़ी संरचना में समूहित करना है। फिर आप इस सभी समान डेटा को एक ही बार में GPU पर भेजते हैं, ठीक उसी तरह जैसे आप WebGL 1 में वर्टेक्स बफ़र को लोड कर सकते हैं। इसमें कई प्रदर्शन लाभ हैं, जैसे API कॉल को कम करना और आधुनिक GPU के काम करने के तरीके के करीब होना।
जीएलएसएल :
layout(std140) uniform ub_Params { vec4 u_LightPos; vec4 u_LightDir; vec4 u_LightColor; };
जावास्क्रिप्ट :
gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, gl.createBuffer());
WebGL 2 में एक बड़े समान बफ़र के सबसेट को बाइंड करने के लिए, आप एक विशेष एपीआई कॉल का उपयोग कर सकते हैं जिसे bindBufferRange
नाम से जाना जाता है। WebGPU में, डायनेमिक यूनिफ़ॉर्म बफ़र ऑफ़सेट जैसा कुछ होता है, जहाँ आप setBindGroup
API को कॉल करते समय ऑफ़सेट की एक सूची पास कर सकते हैं।
WebGPU हमें और भी बेहतर तरीका प्रदान करता है। इस संदर्भ में, व्यक्तिगत uniform
चर अब समर्थित नहीं हैं, और कार्य विशेष रूप से uniform
बफ़र्स के माध्यम से किया जाता है।
डब्ल्यूजीएसएल :
[[block]] struct Params { u_LightPos : vec4<f32>; u_LightColor : vec4<f32>; u_LightDirection : vec4<f32>; }; [[group(0), binding(0)]] var<uniform> ub_Params : Params;
जावास्क्रिप्ट :
const buffer = device.createBuffer({ usage: GPUBufferUsage.UNIFORM, size: 8 });
आधुनिक जीपीयू कई छोटे ब्लॉकों के बजाय डेटा को एक बड़े ब्लॉक में लोड करना पसंद करते हैं। हर बार छोटे बफ़र्स को फिर से बनाने और रिबाइंड करने के बजाय, एक बड़ा बफ़र बनाने और अलग-अलग ड्रॉ कॉल के लिए इसके विभिन्न हिस्सों का उपयोग करने पर विचार करें। यह दृष्टिकोण प्रदर्शन में उल्लेखनीय वृद्धि कर सकता है.
WebGL अधिक अनिवार्य है, प्रत्येक कॉल के साथ वैश्विक स्थिति को रीसेट करना, और जितना संभव हो उतना सरल बनाने का प्रयास करना। दूसरी ओर, वेबजीपीयू का लक्ष्य अधिक वस्तु-उन्मुख होना और संसाधन के पुन: उपयोग पर ध्यान केंद्रित करना है, जिससे दक्षता बढ़ती है।
तरीकों में अंतर के कारण WebGL से WebGPU में संक्रमण कठिन लग सकता है। हालाँकि, एक मध्यवर्ती चरण के रूप में WebGL 2 में परिवर्तन के साथ शुरुआत करना आपके जीवन को सरल बना सकता है।
WebGL से WebGPU में माइग्रेट करने के लिए न केवल API में, बल्कि शेडर्स में भी बदलाव की आवश्यकता होती है। WGSL विनिर्देश को आधुनिक GPU के लिए दक्षता और प्रदर्शन को बनाए रखते हुए, इस परिवर्तन को सुचारू और सहज बनाने के लिए डिज़ाइन किया गया है।
WGSL को WebGPU और देशी ग्राफ़िक्स API के बीच एक सेतु के रूप में डिज़ाइन किया गया है। जीएलएसएल की तुलना में, डब्ल्यूजीएसएल थोड़ा अधिक क्रियात्मक दिखता है, लेकिन संरचना परिचित रहती है।
यहां बनावट के लिए एक उदाहरण शेडर है:
जीएलएसएल :
sampler2D myTexture; varying vec2 vTexCoord; void main() { return texture(myTexture, vTexCoord); }
डब्ल्यूजीएसएल :
[[group(0), binding(0)]] var mySampler: sampler; [[group(0), binding(1)]] var myTexture: texture_2d<f32>; [[stage(fragment)]] fn main([[location(0)]] vTexCoord: vec2<f32>) -> [[location(0)]] vec4<f32> { return textureSample(myTexture, mySampler, vTexCoord); }
नीचे दी गई तालिका GLSL और WGSL में बुनियादी और मैट्रिक्स डेटा प्रकारों की तुलना दिखाती है:
जीएलएसएल से डब्ल्यूजीएसएल में संक्रमण सख्त टाइपिंग और डेटा आकार की स्पष्ट परिभाषा की इच्छा को दर्शाता है, जो कोड पठनीयता में सुधार कर सकता है और त्रुटियों की संभावना को कम कर सकता है।
संरचनाओं को घोषित करने का सिंटैक्स भी बदल गया है:
जीएलएसएल:
struct Light { vec3 position; vec4 color; float attenuation; vec3 direction; float innerAngle; float angle; float range; };
डब्ल्यूजीएसएल:
struct Light { position: vec3<f32>, color: vec4<f32>, attenuation: f32, direction: vec3<f32>, innerAngle: f32, angle: f32, range: f32, };
WGSL संरचनाओं में फ़ील्ड घोषित करने के लिए स्पष्ट वाक्यविन्यास का परिचय अधिक स्पष्टता की इच्छा पर जोर देता है और शेडर्स में डेटा संरचनाओं की समझ को सरल बनाता है।
जीएलएसएल :
float saturate(float x) { return clamp(x, 0.0, 1.0); }
डब्ल्यूजीएसएल :
fn saturate(x: f32) -> f32 { return clamp(x, 0.0, 1.0); }
WGSL में फ़ंक्शंस के सिंटैक्स को बदलना घोषणाओं और रिटर्न मानों के दृष्टिकोण के एकीकरण को दर्शाता है, जिससे कोड अधिक सुसंगत और पूर्वानुमानित हो जाता है।
WGSL में, कई अंतर्निहित GLSL फ़ंक्शंस का नाम बदल दिया गया है या बदल दिया गया है। उदाहरण के लिए:
WGSL में अंतर्निहित फ़ंक्शंस का नाम बदलना न केवल उनके नामों को सरल बनाता है, बल्कि उन्हें अधिक सहज बनाता है, जो अन्य ग्राफिक्स एपीआई से परिचित डेवलपर्स के लिए संक्रमण प्रक्रिया को सुविधाजनक बना सकता है।
जो लोग अपनी परियोजनाओं को WebGL से WebGPU में बदलने की योजना बना रहे हैं, उनके लिए यह जानना महत्वपूर्ण है कि GLSL को WGSL में स्वचालित रूप से परिवर्तित करने के लिए उपकरण हैं, जैसे **[नागा](https://github.com/gfx-rs/naga /)**, जो GLSL को WGSL में परिवर्तित करने के लिए एक रस्ट लाइब्रेरी है। यह WebAssembly की मदद से सीधे आपके ब्राउज़र में भी काम कर सकता है।
यहां नागा समर्थित अंतिम बिंदु हैं:
माइग्रेशन के बाद, आपको फ़्लिप की गई छवियों के रूप में एक आश्चर्य का सामना करना पड़ सकता है। जिन लोगों ने कभी भी OpenGL से Direct3D (या इसके विपरीत) में एप्लिकेशन पोर्ट किया है, उन्हें पहले ही इस क्लासिक समस्या का सामना करना पड़ा है।
ओपनजीएल और वेबजीएल के संदर्भ में, बनावट आमतौर पर इस तरह से लोड की जाती है कि शुरुआती पिक्सेल निचले बाएं कोने से मेल खाता है। हालाँकि, व्यवहार में, कई डेवलपर्स ऊपरी बाएँ कोने से शुरू होने वाली छवियों को लोड करते हैं, जिससे फ़्लिप छवि त्रुटि होती है। फिर भी, इस त्रुटि की भरपाई अन्य कारकों द्वारा की जा सकती है, जिससे अंततः समस्या का समाधान हो जाएगा।
OpenGL के विपरीत, Direct3D और Metal जैसे सिस्टम पारंपरिक रूप से बनावट के लिए शुरुआती बिंदु के रूप में ऊपरी-बाएँ कोने का उपयोग करते हैं। यह ध्यान में रखते हुए कि यह दृष्टिकोण कई डेवलपर्स के लिए सबसे सहज प्रतीत होता है, वेबजीपीयू के रचनाकारों ने इस अभ्यास का पालन करने का निर्णय लिया।
यदि आपका WebGL कोड फ़्रेम बफ़र से पिक्सेल का चयन करता है, तो इस तथ्य के लिए तैयार रहें कि WebGPU एक अलग समन्वय प्रणाली का उपयोग करता है। आपको निर्देशांकों को सही करने के लिए एक सरल "y = 1.0 - y" ऑपरेशन लागू करने की आवश्यकता हो सकती है।
जब किसी डेवलपर को ऐसी समस्या का सामना करना पड़ता है जहां ऑब्जेक्ट अपेक्षा से पहले क्लिप हो जाते हैं या गायब हो जाते हैं, तो यह अक्सर गहराई डोमेन में अंतर से संबंधित होता है। WebGL और WebGPU के बीच इस बात में अंतर है कि वे क्लिप स्पेस की गहराई सीमा को कैसे परिभाषित करते हैं। जबकि WebGL -1 से 1 तक की रेंज का उपयोग करता है, WebGPU अन्य ग्राफिक्स एपीआई जैसे Direct3D, मेटल और वल्कन के समान 0 से 1 तक की रेंज का उपयोग करता है। यह निर्णय 0 से 1 तक की रेंज का उपयोग करने के कई फायदों के कारण किया गया था जिन्हें अन्य ग्राफिक्स एपीआई के साथ काम करते समय पहचाना गया था।
आपके मॉडल की स्थिति को क्लिप स्पेस में बदलने की मुख्य जिम्मेदारी प्रक्षेपण मैट्रिक्स की है। अपने कोड को अनुकूलित करने का सबसे आसान तरीका यह सुनिश्चित करना है कि आपके प्रक्षेपण मैट्रिक्स आउटपुट का परिणाम 0 से 1 की सीमा में हो। जीएल-मैट्रिक्स जैसे पुस्तकालयों का उपयोग करने वालों के लिए, एक सरल समाधान है: perspective
फ़ंक्शन का उपयोग करने के बजाय, आप इसका उपयोग कर सकते हैं perspectiveZO
; अन्य मैट्रिक्स ऑपरेशनों के लिए समान फ़ंक्शन उपलब्ध हैं।
if (webGPU) { // Creates a matrix for a symetric perspective-view frustum // using left-handed coordinates mat4.perspectiveZO(out, Math.PI / 4, ...); } else { // Creates a matrix for a symetric perspective-view frustum // based on the default handedness and default near // and far clip planes definition. mat4.perspective(out, Math.PI / 4, …); }
हालाँकि, कभी-कभी आपके पास मौजूदा प्रक्षेपण मैट्रिक्स हो सकता है और आप इसका स्रोत नहीं बदल सकते। इस मामले में, इसे 0 से 1 तक की सीमा में बदलने के लिए, आप अपने प्रक्षेपण मैट्रिक्स को किसी अन्य मैट्रिक्स से पूर्व-गुणा कर सकते हैं जो गहराई सीमा को सही करता है।
अब, आइए WebGPU के साथ काम करने के लिए कुछ युक्तियों और युक्तियों पर चर्चा करें।
आप जितनी अधिक पाइपलाइनों का उपयोग करेंगे, आपके पास उतनी ही अधिक स्थिति स्विचिंग होगी, और प्रदर्शन उतना ही कम होगा; यह मामूली बात नहीं हो सकती, यह इस पर निर्भर करता है कि आपकी संपत्ति कहां से आती है।
एक पाइपलाइन बनाना और उसका तुरंत उपयोग करना काम कर सकता है, लेकिन इसकी अनुशंसा नहीं की जाती है। इसके बजाय, ऐसे फ़ंक्शंस बनाएं जो तुरंत वापस आएं और एक अलग थ्रेड पर काम करना शुरू करें। जब आप पाइपलाइन का उपयोग करते हैं, तो निष्पादन कतार को लंबित पाइपलाइन निर्माण समाप्त होने तक प्रतीक्षा करने की आवश्यकता होती है। इसके परिणामस्वरूप महत्वपूर्ण प्रदर्शन संबंधी समस्याएँ उत्पन्न हो सकती हैं. इससे बचने के लिए, पाइपलाइन बनाने और पहले इसका उपयोग करने के बीच कुछ समय छोड़ना सुनिश्चित करें।
या, और भी बेहतर, create*PipelineAsync
वेरिएंट का उपयोग करें! वादा तब पूरा होता है जब पाइपलाइन बिना किसी रुकावट के उपयोग के लिए तैयार हो जाती है।
device.createComputePipelineAsync({ compute: { module: shaderModule, entryPoint: 'computeMain' } }).then((pipeline) => { const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginComputePass(); passEncoder.setPipeline(pipeline); passEncoder.setBindGroup(0, bindGroup); passEncoder.dispatchWorkgroups(128); passEncoder.end(); device.queue.submit([commandEncoder.finish()]); });
रेंडर बंडल पूर्व-रिकॉर्ड किए गए, आंशिक, पुन: प्रयोज्य रेंडर पास हैं। उनमें अधिकांश रेंडरिंग कमांड शामिल हो सकते हैं (व्यूपोर्ट सेट करने जैसी चीजों को छोड़कर) और बाद में वास्तविक रेंडर पास के हिस्से के रूप में "रीप्ले" किया जा सकता है।
const renderPass = encoder.beginRenderPass(descriptor); renderPass.setPipeline(renderPipeline); renderPass.draw(3); renderPass.executeBundles([renderBundle]); renderPass.setPipeline(renderPipeline); renderPass.draw(3); renderPass.end();
रेंडर बंडलों को नियमित रेंडर पास कमांड के साथ निष्पादित किया जा सकता है। प्रत्येक बंडल निष्पादन से पहले और बाद में रेंडर पास स्थिति डिफ़ॉल्ट पर रीसेट हो जाती है। यह मुख्य रूप से ड्राइंग के जावास्क्रिप्ट ओवरहेड को कम करने के लिए किया जाता है। दृष्टिकोण की परवाह किए बिना GPU का प्रदर्शन समान रहता है।
WebGPU में परिवर्तन का अर्थ केवल ग्राफ़िक्स API को स्विच करने से कहीं अधिक है। यह विभिन्न ग्राफ़िक्स एपीआई से सफल सुविधाओं और प्रथाओं के संयोजन, वेब ग्राफिक्स के भविष्य की दिशा में भी एक कदम है। इस प्रवासन के लिए तकनीकी और दार्शनिक परिवर्तनों की गहन समझ की आवश्यकता होती है, लेकिन लाभ महत्वपूर्ण हैं।