- OpenGL अनुभव रखने वाले डेवलपर ने पहली बार Vulkan सीखा और लगभग 3 महीनों में दो छोटे game demo और फिर से इस्तेमाल हो सकने वाला EDBR engine बनाया
- शुरुआत से generic engine design करने के बजाय पहले छोटा game बनाया, फिर केवल ज़रूरी हिस्सों को engine में बदला, जिससे over-design और bike-shedding कम हुआ
- engine का आकार 19k LoC है, जिसमें graphics code 6.7k LoC और Vulkan lightweight abstraction 2k LoC है; इसमें compute skinning, CSM, PBR shading, MSAA, post FX और UI rendering शामिल हैं
vk-bootstrap, Vulkan Memory Allocator,volk,VK_KHR_dynamic_rendering, push constants, buffer device address और bindless descriptor का इस्तेमाल करके Vulkan boilerplate और descriptor set usage घटाया गया- Vulkan ने global state हटाना, बेहतर validation errors, RenderDoc shader debugging, और GPU·OS के बीच consistency दी, लेकिन explicit synchronization अब भी खुद संभालना पड़ता है
3 महीनों में बनाया गया Vulkan-based EDBR engine
- EDBR(Elias Daler’s Bikeshed Engine) एक Vulkan learning project के रूप में शुरू हुआ और बाद में ऐसे छोटे engine में विकसित हुआ जिसे अगले projects में reuse किया जा सके
- engine और game code GitHub repository पर public है
- लिखे जाने के समय code size इस प्रकार था
- engine खुद: 19k LoC
- graphics-related code: 6.7k LoC
- Vulkan lightweight abstraction: 2k LoC
- 3D cat game: 4.6k LoC
- 2D platform game: 1.2k LoC
- engine खुद: 19k LoC
- input handling और audio system जैसे कुछ non-graphics code पिछले engine से लिए गए, जबकि graphics और कई core systems नए सिरे से लिखे गए
- उनका आकलन था कि Vulkan को मौजूदा OpenGL abstraction में फिट करने के बजाय नया लिखना ज़्यादा उचित था
graphics programming से Vulkan तक सीखने का क्रम
- अगर आप graphics programming पहली बार शुरू कर रहे हैं, तो Vulkan के बजाय OpenGL से शुरू करना complexity से overwhelmed न होने के लिए आसान है
- minimum goal के तौर पर textured model को screen पर दिखाना और simple Blinn-Phong lighting implement करने के स्तर की सलाह दी गई है
- basic shadow mapping scene को अलग viewpoint और अलग render target में render करने, और depth texture sample करने का तरीका सीखने में मदद करता है
- OpenGL learning resources के रूप में ये recommended हैं
- Vulkan सीखने में vkguide सबसे मददगार रहा, और अगर पहली बार शुरू कर रहे हैं तो पूरे guide को follow करें, लेकिन simple game के लिए “GPU driven rendering” जैसी complexity तुरंत ज़रूरी नहीं हो सकती
- Vulkan Lecture Series by TU Wien Vulkan basics cover करती है, और खासकर synchronization lecture मददगार है
- पहले महीने की learning के नतीजे के रूप में ये features implement किए गए
- glTF model loading
- compute skinning
- frustum culling
- shadow mapping और cascaded shadow maps
Vulkan चुनने की वजह और WebGPU से तुलना
- लक्ष्य Windows और Linux focused desktop के लिए छोटा 3D game था, और open source technologies व open standards को पसंद करते हुए OpenGL और Vulkan के बीच चुनाव किया गया
- OpenGL छोटे games के लिए पर्याप्त है, लेकिन नया version आने की संभावना कम है और macOS पर deprecated होने के कारण इसका भविष्य अनिश्चित माना गया
- WebGPU भी कुछ हद तक सीखा गया, लेकिन इसकी ये सीमाएं थीं
- अभी stable नहीं है और tutorials व examples बहुत नहीं हैं
- WGSL syntax GLSL की तुलना में कम पसंद आया
- desktop पर यह DirectX, Vulkan, Metal के ऊपर wrapper जैसा है, जिससे platform-specific RenderDoc captures अलग हो जाते हैं और WebGPU calls व native API calls 1:1 match नहीं करते
- bindless textures और push constants नहीं हैं
- WebGPU के स्पष्ट फायदे भी हैं
- OpenGL/WebGL से बेहतर validation errors हैं और global state नहीं है
- Vulkan से मिलते-जुलते हिस्से हैं, इसलिए Vulkan सीखने से पहले मददगार है
- Vulkan की तुलना में screen पर कुछ दिखाने के लिए कम boilerplate चाहिए
- explicit synchronization खुद handle नहीं करना पड़ता
- browser में game चलाया जा सकता है
frame rendering flow
- एक frame कई stages में बंटा होता है, और हर stage pipeline या pass के रूप में implement किया गया है
- skinning stage में skeletal animation वाले models को compute shader से process किया जाता है
- input unskinned mesh और joint matrices होते हैं
- output बाद के rendering stage में इस्तेमाल होने वाला vertex buffer होता है
- अगले stages में static mesh और skinned mesh को लगभग एक जैसा handle किया जा सकता है
- CSM stage में 4096x4096 depth texture और 3 slices का इस्तेमाल करके cascaded shadow mapping की जाती है
- geometry + shading stage में models draw किए जाते हैं और shadow map व light information का इस्तेमाल करके shading की जाती है
- PBR model लगभग वैसा ही है जैसा Physically Based Rendering in Filament में बताया गया है
- fragment shader एक draw call में उस mesh को affect करने वाली सभी lights की calculation करता है
- सब कुछ multi-sampled texture पर draw होने के बाद resolve किया जाता है
- depth resolve fragment shader से manually किया जाता है
- multi-sample depth texture के सभी fragments पर iterate करके minimum value को non-MS depth texture में लिखा जाता है
- post FX stage फिलहाल केवल depth fog apply करता है, और आगे tone mapping व bloom भी इसी stage में handle करने की योजना है
- UI stage में dialogue UI draw किया जाता है और इसे एक draw call में process किया जाता है
Vulkan boilerplate कम करने वाली libraries
vk-bootstrapphysical device selection, swapchain creation आदि Vulkan initialization boilerplate कम करता है- यह पूरा Vulkan function wrapper नहीं है, बल्कि मुख्य रूप से initialization stage पर असर डालता है
- Vulkan Memory Allocator Vulkan memory allocation को सीधे handle करने की ज़रूरत नहीं रहने देता
volkextension function loading को सरल बनाता है- उदाहरण के लिए
vkSetDebugUtilsObjectNameEXTजैसी extension function को pointer के रूप में खुद store किए बिना इस्तेमाल किया जा सकता है
- उदाहरण के लिए
GfxDeviceclass अक्सर इस्तेमाल होने वाली Vulkan features और objects को एक साथ रखती है- Vulkan context initialization
- swapchain creation और management
beginFrameऔरendFrame- image creation और texture loading
- buffer creation
- bindless descriptor set management
- लिखे जाने के समय
GfxDevice.cpp714 lines का था, औरVkDevice,VkQueue,VmaAllocatorआदि को कई जगह pass करने के बजाय एक object pass करना सुविधाजनक है
shader build और descriptor set से बचने की रणनीति
- shader भाषा के लिए OpenGL अनुभव के कारण GLSL चुना
- shaders को runtime पर नहीं, बल्कि build stage में SPIR-V में compile किया जाता है
- runtime shader loading code सरल हो जाता है
- runtime shader compiler पर dependency नहीं रहती
- shader errors build stage में ही मिल जाते हैं
glslcCMake काDEPFILEspecify कर सकता है, इसलिए shader include बदलने पर संबंधित files अपने-आप recompile हो सकती हैं- Vulkan में uniforms को descriptor set में bundle करना पड़ता है, इसलिए OpenGL की तुलना में data pass करना ज्यादा जटिल हो जाता है
- implementation में descriptor set का इस्तेमाल काफी कम किया गया
- bindless texture और sampler के लिए सिर्फ एक global descriptor set इस्तेमाल किया
- बाकी ज्यादातर data push constants से pass किया
- buffer device address का उपयोग करके buffer address को push constants से pass किया
pipeline class और dynamic rendering
- rendering stages को
PostFXPipelineजैसी pipeline class में अलग किया गया - हर pipeline की आम तौर पर ये भूमिकाएं होती हैं
init: shader loading,VkPipeline,VkPipelineLayoutinitializationcleanup: pipeline और layout cleanupdraw: हर frame में जरूरी inputs लेकर draw call execute करना
- माना जाता है कि
drawकोvkCmdBeginRenderingऔरvkCmdEndRenderingके बीच call किया जाता है - render pass किस texture में rendering करता है, इसकी चिंता pipeline के अंदर नहीं की जाती; caller render target तय करता है
VK_KHR_dynamic_renderingको पूरे तौर पर इस्तेमाल किया गया है और Vulkan render pass व subpass का उपयोग नहीं किया गया- सुना है कि tile-based GPU पर render pass और subpass ज्यादा efficient होते हैं, लेकिन फिलहाल mobile support पर विचार नहीं किया गया
- dynamic rendering implementation को बहुत आसान बना देता है
PVP, BDA, bindless descriptor का उपयोग
- सभी mesh के लिए एक ही vertex type इस्तेमाल किया गया
- programmable vertex pulling का उपयोग करने से OpenGL के VAO या Vulkan के
VkVertexInputBindingDescription,VkVertexInputAttributeDescriptionजैसी vertex format definitions से बचा जा सकता है - buffer device address का उपयोग करने से vertex buffer को descriptor set में bind किए बिना buffer address को push constants से pass किया जा सकता है
- push constants और buffers में
scalarlayout का उपयोग किया गयाstd430की तुलना में alignment handling आसान है, इसलिए इसे C++ structs के लगभग समान तरीके से handle किया जा सकता है- C++ structs में padding members की जरूरत कम हो जाती है
- bindless descriptor को बड़े descriptor set में texture और sampler arrays रखकर इस्तेमाल किया गया
- नई texture load होने पर उसे
texturesarray में डालते हैं और उसके index को bindless texture id के रूप में इस्तेमाल करते हैं - shader में texture id को push constants से pass किया जाता है
- नई texture load होने पर उसे
- sampler को image से अलग करके common sampler startup के समय बनाए गए और
samplersarray में डाले गए - material buffer में भी bindless texture id का उपयोग किया गया
- केवल material ID को push constants से pass किया जाता है और fragment shader में material buffer lookup किया जाता है
- bulky descriptor set के बिना हर material के लिए सिर्फ एक integer से texture access संभव होता है
- bindless texture से जुड़ी reference material के रूप में Vulkan Bindless Texture की सलाह दी गई है
हर frame में upload होने वाला dynamic data
- हर frame CPU से GPU में डालने वाले data के लिए एक बड़ा array पहले से allocate किया जाता है और हर frame index 0 से भरने का तरीका इस्तेमाल किया जाता है
- उदाहरण के लिए, सभी joint matrices को एक बड़े
mat4array में store किया जाता है, और हर skinned mesh के लिए start index push constants से pass किया जाता है - दो तरीके हैं
- GPU में N buffers रखना और हर frame-in-flight पर switch करना
- GPU में केवल एक buffer रखना और CPU-side staging buffers N रखना
- ज्यादातर मामलों में पहला तरीका recommend किया जाता है
- GPU memory ज्यादा लगती है, लेकिन manual synchronization की जरूरत नहीं होती
- अगर GPU memory बचानी हो तो दूसरा तरीका उपयोगी हो सकता है
- दोनों तरीकों के बीच performance में कोई खास अंतर नहीं मिला, लेकिन हर frame बहुत बड़ा data upload करने पर अंतर आ सकता है
cleanup और synchronization
- vkguide का deletion queue pattern लेखक के engine में बहुत उपयोगी नहीं रहा
- क्योंकि हर frame नए Vulkan objects allocate या destroy नहीं किए जाते
- C++ destructor-based cleanup भी सुविधाजनक नहीं था
- wrapper class, move constructor, move assignment की जरूरत पड़ती है, जिससे complexity बढ़ती है
- mid-frame में इस्तेमाल हो रहे object को wrapper destruction से गलती से हटाने का risk होता है
- फिलहाल
cleanupfunction को explicitly call करके Vulkan objects को एक जगह cleanup किया जाता है- call करना भूलना आसान है, लेकिन exit पर Vulkan validation error और VMA assert छूटा हुआ cleanup बता देते हैं
- Vulkan synchronization कठिन है और इसे explicitly manage करना पड़ता है
- OpenGL और WebGPU texture या buffer reads के लिए जरूरी synchronization अपने-आप handle करते हैं
- Vulkan में data race से बचने के लिए barrier खुद डालना पड़ता है
- फिलहाल draw को pass और pipeline में बांटकर उनके बीच barriers manually insert किए जाते हैं
- उदाहरण के लिए, compute shader का skinning pass vertex data लिखने के बाद और shadow mapping pass के उसे पढ़ने से पहले barrier डाला जाता है
- इसे render graph से automate किया जा सकता है, लेकिन अभी manual synchronization से संतुष्ट हैं
vkconfigकी synchronization validation layer synchronization errors खोजने में मदद करती है
sprite, skinning, game/renderer separation
- bindless texture का उपयोग करने से बहुत सारे sprites को vertex buffer के बिना एक draw call में draw करना आसान हो जाता है
- sprite vertex shader
gl_VertexIndexसे quad के vertex coordinate और UV generate करता है - सभी sprite draw calls को
SpriteDrawBufferमेंSpriteDrawCommandके रूप में इकट्ठा किया जाता है- transform
- UV range
- color
- texture ID
- shader ID
- असली draw call
vkCmdDraw(cmd, 6, spriteDrawCommands.size(), 0, 0)के रूप में होता है- प्रति sprite 6 vertices
- sprite की संख्या जितने instances
- sprite renderer 10,000 sprites को 315 microseconds में draw कर सकता है
- compute skinning skeletal animation वाले mesh में input vertices और joint matrices लेकर skinned vertex buffer generate करता है
- एक ही mesh वाली 3 बिल्लियों के भी अलग-अलग animations हो सकते हैं
- output vertex buffer हर mesh instance के लिए चाहिए
- game logic और renderer को draw command से अलग किया गया
- game logic entt का उपयोग करता है
- renderer entity या game object को नहीं जानता, केवल light, scene parameter और mesh draw command handle करता है
MeshDrawCommandमेंmeshId, transform matrix, bounding sphere, skinned mesh pointer, joint matrix start index, और shadow casting की स्थिति शामिल होते हैं
scene loading, UI, Dear ImGui
- level editor खुद नहीं बनाया; इसके बजाय Blender का उपयोग किया और glTF में export किया
- level editor खुद लिखने में कई महीने या साल लग सकते थे, इसलिए समय बचाया
- node के नाम से prefab बनाना और physics shape तय करना किया
- उदाहरण:
Interact.Sphere.Diaryमें पहले dot से पहले वालाInteractprefab का नाम है Spherephysics system में sphere physics body बनाते समय इस्तेमाल होता हैCapsule,Boxभी इस्तेमाल किए जा सकते हैं, और अगर कुछ न हो तो mesh vertex से physics shape बनाई जाती है
- उदाहरण:
- जटिल model को level glTF में सीधे डालने के बजाय
Empty->Arrowsobject के रूप में रखकरCat.NearStoreजैसा नाम दियाCatprefab बनाता है औरNearStoretag जोड़ता है
- prefab JSON में लिखे जाते हैं और बाहरी glTF, movement, physics जानकारी शामिल कर सकते हैं
- UI system Roblox UI API से प्रेरित है
- origin
- relative size
- relative position
offsetPosition,offsetSize- fixed size
- label/image content आधारित size
- UI element size को recursively calculate करने के बाद position calculate की जाती है, और parent से children के क्रम में draw किया जाता है
- Dear ImGui का उपयोग development/debug tools के लिए किया गया
- sRGB framebuffer में Dear ImGui गलत दिखने की समस्या थी, इसलिए अपना Dear ImGui backend लिखा
- केवल rendering वाला हिस्सा लिखा, और input event handling, clipboard आदि logic/OS interaction को default Dear ImGui SDL backend संभालता है
- अपने backend के फायदे ये हैं
- bindless texture id को support करता है, जिससे
ImGui::Image(bindlessTextureId, ...)से image draw की जा सकती है - format pass करके linear image और non-linear image को सही तरीके से draw किया जा सकता है
- engine के दूसरे Vulkan code की तरह ही initialize और handle किया जा सकता है
- bindless texture id को support करता है, जिससे
इस्तेमाल की गई libraries और Vulkan पर जाने का असर
- physics के लिए Jolt Physics का उपयोग किया
- मुख्य रूप से collision resolution और basic character movement में उपयोग किया
JPH::CharacterVirtualbasic character movement को अच्छी तरह handle करता है
- ECS के लिए entt का उपयोग किया
- खुद बनाए ECS के बजाय external library इस्तेमाल करके maintain करने वाला code कम किया
- audio के लिए openal-soft, libogg, libvorbis का उपयोग किया
- profiling के लिए Tracy का उपयोग किया
- यह देखने में मदद मिली कि कौन-सा code वास्तव में कितना कम समय लेता है, ताकि unnecessary bike-shedding से बचा जा सके
- Vulkan पर switch करने से मिली चीजें ये हैं
- OpenGL का global state हट गया, जिससे abstraction आसान हो गया
- OpenGL-style
shader.bind()या state tracker, magic RAII की जरूरत कम हो गई - validation error OpenGL की तुलना में ज्यादा समृद्ध हैं
- RenderDoc में vertex shader और fragment shader को सीधे debug किया जा सकता है
- GPU और OS के बीच behavior differences OpenGL की तुलना में कम प्रमुख हैं
- आगे Slang, Shady जैसी दूसरी shading language explore की जा सकती हैं
- graphics pipeline के हर पहलू पर ज्यादा control मिल सकता है
आगे किए जाने वाले काम
- आगे की planned tasks ये हैं
- sign-distance field font support
- बहुत सारी image loading और mipmap parallel generation
- bloom
- volumetric fog
- animation blending
- render graph
- ambient occlusion
- game complete करना
- Vulkan सीखना मुश्किल था, लेकिन जितना सोचा था उतना नहीं; और इससे graphics programming और modern API को गहराई से समझने का मौका मिला
1 टिप्पणियां
Hacker News टिप्पणियाँ
मिनिमलिज़्म बहुत प्रभावी है
मैं उलटी दिशा में गया, और इसकी वजह से काफ़ी परेशानी झेल रहा हूँ। मैं Rust में एक metaverse client बना रहा हूँ, और अभी भी दूसरी स्क्रीन पर मेरा avatar एक विशाल steampunk शहर से tram में गुजरता हुआ दिख रहा है। नया prerelease निकालने से पहले मैं इसे 12-12 घंटे चलाकर देखता हूँ
Vulkan के ऊपर WGPU और Rend3 लगाया है, और Rend3 एक साफ़ API देता है: आप mesh, 2D texture और object बनाते हैं, फिर object mesh और texture को reference करता है और स्क्रीन पर दिखाई देता है। Rust की reference counting connections को सही रखती है, इसलिए इसे लिखना सहज है
लेकिन layers बढ़ने पर समस्याएँ आती हैं। WGPU web browser, Vulkan, Metal, DX11(हाल ही में हटाया गया), DX12, Android, OpenGL तक support करने की कोशिश करता है, इसलिए बड़ी development team चाहिए और बदलाव करना मुश्किल होता है। WGPU API खुद भी मोटे तौर पर Vulkan जैसा है, इसलिए GPU memory allocation और synchronization का ध्यान आपको खुद रखना पड़ता है
WGPU में lowest common denominator वाली समस्या है। कुछ platforms कुछ features support नहीं कर पाते, और Vulkan में मौजूद कई threads से GPU memory को concurrently update करने की सुविधा WGPU बिना interference के संभाल नहीं पाता। बड़े worlds संभालने वाले games या clients को frame rate खराब किए बिना content को GPU में डालने के लिए इस feature की ज़रूरत होती है। हर platform पर concurrency constraints अलग हैं, इसलिए lock contention से performance बहुत गिर सकती है
Rend3 synchronization और allocation संभालने वाले ठीक-ठाक glue code की भूमिका निभाना चाहता था, लेकिन खासकर synchronization को general तरीके से हल करना मुश्किल है। Frustum culling से performance में बड़ा फायदा होता है, लेकिन occlusion culling computation cost की वजह से घाटे का सौदा था। Translucency भी depth sorting मांगती है, इसलिए सिरदर्द है। क्योंकि world में बहुत सारी windows हैं, ऐसे transparent objects ज़रूरी हैं जिनसे बाहर देखा जा सके और अंदर झाँका जा सके
लगता है Rust 3D stack के लोग मुझसे चिढ़ गए हैं, क्योंकि मैं 3 साल से लगातार stack ठीक करने का दबाव डाल रहा हूँ। सभी volunteer हैं, और Vulkan पैसे और user base की वजह से maintain होता है। Rend3 के creator ने हाल में हार मान ली है, इसलिए अब मुझे अंदर जाकर इसे ठीक करना होगा। WGPU पर complex चीज़ें बनाने वाले लोग कम हैं; ज़्यादातर 2D games या simple static 3D scenes हैं जो Flash से भी बन सकते थे। Commercial projects अब भी Unity या UE5 ही इस्तेमाल करते हैं
सीधे Vulkan पर जाने पर भी synchronization, allocation, frustum culling और translucency handling खुद लिखनी पड़ेगी, इसलिए यह बड़ा transition है
साथ ही, Vulkan और Metal के ऊपर wrapper Vulkano में भी lowest common denominator की समस्या है। Vulkan और Metal दोनों GPU assets को concurrently update करना support करते हैं, लेकिन Vulkano इसकी अनुमति नहीं देता। बेशक Apple इसे फिर अलग तरह से handle करता है
https://gpuweb.github.io/gpuweb/explainer/#multithreading
https://github.com/gpuweb/gpuweb/issues/354
OpenGL ने भी कभी threads support नहीं किए, इसलिए OpenGL इस्तेमाल करना ऐसा नहीं कर सकता
एक साल से थोड़ा ज़्यादा पहले Vulkan सीखने की कोशिश की थी, लेकिन अब उसे फिर कभी छूना नहीं चाहता। OpenGL को हटाकर ऐसी चीज़ से बदलना जो साधारण कामों को भी बेतुका मुश्किल बना देती है, वाकई असहज है। उदाहरण के लिए, एक घूमते हुए cube के लिए भी सैकड़ों lines का code चाहिए
OpenGL भी आसान नहीं था, लेकिन आम आदमी अपेक्षाकृत कम समय में उसकी basics सीख सकता था। किसी बड़े bookstore से graphics programming की intro book खरीदकर एक-दो दोपहर में basic rendering चला सकता था। Vulkan कुछ मामलों में बेहतर होगा, लेकिन इसे जल्दी सीखने की उम्मीद करना realistic नहीं है
कल्पना करें कि नए Intel/ARM/AMD chips आने पर कहा जाए कि C या C++ नहीं लिख सकते और “हम high-level language support बंद कर रहे हैं, इसलिए अब सिर्फ assembly लिखिए; control ज़्यादा मिलेगा तो fast होगा।” जाहिर है, इसे बकवास कहा जाएगा
Vulkan को high-level graphics API से ज़्यादा “GPU API” कहा जाता है। इस तरह देखें तो इसकी complexity चौंकाने वाली नहीं है; domain ही मुश्किल है
यह analogy कि modern CPU पर high-level languages हटाकर सिर्फ assembly लिखने को कहा जाए, उतनी सही नहीं; यह single-threaded C/C++ और multi-threaded C/C++ के फर्क के ज़्यादा करीब है। complexity बहुत बढ़ जाती है और न पता हो तो चीज़ें फटती हैं या performance और खराब हो जाती है, लेकिन आगे बढ़ने का practical रास्ता भी यही है
OpenGL को आम तौर पर Vulkan के ऊपर implement किया जा सकता है। OpenGL standard का अब actively develop न होना अफसोसजनक है, लेकिन कुछ भी हमेशा के लिए नहीं होता
समस्या यह है कि OpenGL अब current hardware से अच्छी तरह match नहीं करता, इसलिए naive तरीके से इस्तेमाल करने पर performance बहुत inefficient होती है। OpenGL में भी जब driver overhead हटाने वाली techniques तक पहुँचते हैं, तो Vulkan इतना भी ज़्यादा मुश्किल नहीं रह जाता
मौजूदा Intel/ARM/AMD chips भी C या C++ को सीधे support नहीं करते। हम पहले से ही assembly इस्तेमाल करते हैं, या C/C++ से convert करने वाले third-party tools इस्तेमाल करते हैं। Vulkan का goal भी GPU के लिए low-level standard interface देना है, और उसके ऊपर इस्तेमाल में आसान abstractions बनाना है
https://github.com/google/angle
कई phones अब Vulkan driver के ऊपर अपने इकलौते OpenGL support के रूप में ANGLE ship करते हैं
अगर अपेक्षाकृत आसान और portable modern API चाहिए, तो Rust में wgpu या C++ में dawn के जरिए WebGPU इस्तेमाल कर सकते हैं
मुझे लगता है Vulkan बेहतरीन है, लेकिन इसका उद्देश्य advanced GPU features का अधिकतम लाभ उठाना है। advanced GPU features इस्तेमाल करते समय यह OpenGL से बेहतर performance भी दे सकता है
अगर लक्ष्य advanced rendering techniques नहीं हैं, तो आम तौर पर OpenGL ही recommended path लगता है
आज भी बहुत सारे 2D, low-poly, PS1-style graphics वाले games हैं, और ऐसे games को Vulkan इस्तेमाल करने की जरूरत नहीं है
Vulkan इस बात का उदाहरण है कि AAA game industry rendering quality और visual look की तरफ झुकी हुई है। AAA studios बहुत advanced engines और content से अपना budget justify करते हैं, लेकिन players थक रहे हैं और graphics से ज़्यादा gameplay चाहते हैं—यह समझ आने पर 2D/low-poly games का market बढ़ रहा है
game developer हों तो शायद rendering quality से ज़्यादा gameplay और features पर focus करना चाहेंगे
global state नहीं है, runtime पर इस्तेमाल करने वाला GPU चुन सकते हैं, और OpenGL की error handling भयानक है। validation layers हैं, official artwork भी cool है, और documentation भी शानदार है। दूसरे thread से asynchronously GPU पर data upload करना भी ज़्यादा sensible तरीके से संभव है, और mesh shaders या RTX जैसे advanced GPU features भी हैं
क्या consumers AAA-level graphics वाले indie games को reject करेंगे? शायद नहीं। ऐसे games कम होने की वजह यह है कि वे financially viable नहीं होते, और अधिक stylized, lower-fidelity graphics स्वीकार करने वाला market पर्याप्त बड़ा है
साथ ही, यह compute shaders जैसी modern capabilities तक access देता है जिन्हें WebGL में इस्तेमाल नहीं कर सकते, और OpenGL की तरह एक ही काम करने के कई तरीकों से जमा हुआ legacy भी इतना नहीं है। मुख्य advantage इसका नया होना है, लेकिन इसी वजह से tutorials बहुत कम हैं, और यह काफी गंभीर drawback है
frameworks और engines की कोई कमी नहीं, लेकिन ज्यादातर अधूरे हैं, और सभी code structure को लेकर बहुत strong opinions रखते हैं। जैसे, “callbacks रखने वाला scene tree बनाओ। नहीं, entities और component classes और objects लिखो। रुको, अब सब immediate mode, functional और stateless है”
इसके ऊपर platform की अफरातफरी भी जोड़ दें। mobile games के push notifications, in-app purchases, Xcode के जरिए mandatory signing तक मिला दें तो पूरा मामला गड़बड़ हो जाता है। Unity का market share होने की वजह है, और वह यह नहीं कि यह शानदार software है। वजह यह है कि flashy webview से ज़्यादा कुछ करने वाला cross-platform अभी भी बेहद painful है
अगर GPU/driver-specific code लगातार लिखना ही पड़े, तो abstraction layer का मतलब नहीं रह जाता। low-level driver और high-level library को अलग रखना बेहतर है
इस लेख में अच्छी सलाह बहुत है। खासकर “अभी ज़रूरत नहीं है तो implement मत करो” वाली बात ध्यान खींचती है
यही वह बात है जिस पर मैं उन junior programmers से लगातार भिड़ता हूँ जिनके पास कुछ साल का experience तो है, लेकिन वे अभी सीख रहे हैं। वे अक्सर “best practices” और trend में चल रहे चमकदार नए tools पर अटक जाते हैं, लेकिन जिस problem को solve करना है, वहीं से शुरू करके उसे solve करने के लिए ज़रूरी minimum चीज़ों पर focus करने में मुश्किल महसूस करते हैं
medium-to-large organizations में आम तौर पर ऐसा नहीं होता। ज़्यादातर अगला काम शुरू हो जाता है और वापस देखकर सुधारने का समय लगभग नहीं मिलता। अफ़सोस, इसी वजह से शुरुआत से ही ठीक बनाना पड़ता है और bugs या side effects पैदा होने की गुंजाइश कम करनी पड़ती है
बहुत सारे codebases में नए features की जल्दबाज़ी ने code को minefield जैसा बना दिया, जहाँ छोटे से change के लिए भी सभी features को manually check करना पड़ता था और पूरी application का context दिमाग़ में रखना पड़ता था
मेरे अनुभव में, बाद में जब नई requirements आती हैं, तो पहले बनाया गया generic solution पूरी तरह अनुपयुक्त निकलता है और फिर भी उसे दोबारा बनाना पड़ता है। सामने की problem solve करनी चाहिए, किसी अज्ञात future problem को नहीं
इसलिए यह हैरानी की बात नहीं कि लोग हर संभव मौके पर यहाँ-वहाँ कुछ खुद लिखने की कोशिश करते हैं। ऐसे digital plumber जैसे छोटे-मोटे काम इंसान को थका देते हैं, इसलिए होश कुछ और समय तक बचाए रखने के लिए कभी-कभी कुछ ज़्यादा मज़ेदार काम बीच में डालने का मन करता है
लगता है site traffic spike से down हो गई, इसलिए cache छोड़ रहा हूँ: https://web.archive.org/web/20240606103630/https://edw.is/le...
शानदार लेख है। scientific data visualization engine बनाने के लिए मैंने खुद Vulkan सीखा: https://datoviz.org/ यह अभी भी काफ़ी experimental है और जल्द ही नया version आने वाला है
पहले OpenGL का ज्ञान था, लेकिन Vulkan सीखना सच में मुश्किल था। 5 साल पहले learning material भी इतना अच्छा नहीं था। फिर भी मैंने कोशिश की और यह बहुत मज़ेदार रहा
दर्जनों abstractions की भूमिका समझने में कई महीने लगे, और इसी process में Vulkan को थोड़ा कम दर्दनाक तरीके से इस्तेमाल करने के लिए एक छोटा wrapper भी लिखा: https://datoviz.org/api/vklite/
यह wrapper scientific visualization के लिए सबसे ज़रूरी कुछ features ही support करता है
Vulkan से OpenGL से बेहतर performance निकालना आसान नहीं है। Vulkan driver में वे लगभग 20 हज़ार lines का code नहीं होता जो OpenGL driver rendering pipeline और render target setup के लिए आपके बदले करता था
वह code पहले से OpenGL driver के अंदर है, और industry के बेहतरीन लोगों ने उसे 20 साल से ज़्यादा समय तक optimize किया है
इसलिए OpenGL default रूप से जो capabilities देता था, उनके बराबर की चीज़ों को Vulkan के ऊपर naïvely जोड़ देने से जादू की तरह बेहतर performance नहीं मिलती। और काम करना पड़ता है, और सही fences व synchronization primitives लगाने जैसी असली समस्याएँ जमा होने लगती हैं
जब आपको सच में पता हो कि आप क्या कर रहे हैं, और अच्छी parallelism व सही synchronization के साथ rendering चला सकते हों, तभी Vulkan के performance advantages का सपना देखा जा सकता है
hobby developer होने के नाते मैं simplicity की वजह से OpenGL ES3 इस्तेमाल करता हूँ। मेरे लिए यह पहले ही काफी अच्छा है, और परेशान करने वाले Vulkan vertex descriptor descriptor descriptor लिखने से ज़्यादा urgent काम मेरे पास बहुत हैं
संदर्भ के लिए मेरा engine यहाँ है: https://github.com/ensisoft/detonator
सुना है Vulkan अब bindless textures allow करता है, इसलिए descriptor वाली अव्यवस्था पहले जितनी भयानक नहीं होगी
Vulkan आकर्षक है, लेकिन इसकी upfront cost बहुत ऊँची है जिसे मैं चुकाना नहीं चाहता
फिर धीरे-धीरे उस library को use case के हिसाब से optimized routines से replace किया जा सकता है
Vulkan से जुड़ी सामग्री बढ़ना अच्छी बात है, लेकिन इसमें भी वही समस्या है जो स्क्रीन पर कुछ दिखाने वाली हर Vulkan सामग्री में दिखी है
कोई साधारण case दिखाने से पहले ही सब Vulkan के ऊपर एक और abstraction layer ला देते हैं। हमेशा vk-bootstrap, volk, vma या कोई और library इस्तेमाल करने को कहते हैं
memory management को manually करने वाला example दिखाने वाली कोई सामग्री कहीं है भी या नहीं, पता नहीं। लगता है विकल्प बस vma इस्तेमाल करना या specification को खुद खोदना ही है। Vulkan SDK के अलावा कोई library जोड़े बिना सबसे basic example मांगना क्या इतनी ज़्यादा मांग है?
ज़्यादातर games में मोटे तौर पर तीन lifetimes होती हैं: permanent/startup time, per-level, per-frame
ये lifetimes nested होती हैं, इसलिए एक stack allocator से काफी दूर तक काम चल सकता है। frame या level खत्म होने पर बस starting position पर वापस ले आएं
और भी complex patterns हैं, लेकिन सिर्फ यह तरीका भी काफी उपयोगी है, और CPU व GPU दोनों तरफ इस्तेमाल किया जा सकता है
इस लिहाज से ऊपर libraries का बड़े पैमाने पर इस्तेमाल करना बिल्कुल normal है। यह वैसा ही है जैसे आजकल software सीधे system calls पर लिखना आम नहीं है
जब आप किसी व्यक्ति की Vulkan initialization guidelines को Khronos Group repository के code से मिलाते हैं, Vulkan 1.3 specification पढ़ते हैं, और फिर समझते हैं कि कुछ करने के लिए specification को क्रम से नहीं बल्कि उलट-पुलट कर पढ़ना पड़ता है, तब साफ हो जाता है कि यह fail हुआ है
वे fail हुए। दूसरे standards से देखें तो भी यह खराब काम है। बस एक बार कर लेने के बाद ज़्यादातर भूल सकते हैं, इसलिए experts ज्यादा शिकायत नहीं करते
इस thread की दूसरी comment में मैंने source code का एक हिस्सा छोड़ा है, जिसमें specification के chapters और sections को comments में annotate किया है। यह SDL आदि के साथ इस्तेमाल की जा सकने वाली general implementation है
लिखे जाने के समय standard approach official Vulkan SDK में शामिल VMA और Volk इस्तेमाल करना है। यही अपने-आप current state के बारे में काफी कुछ कह देता है
कई सालों तक रुक-रुककर Vulkan सीखने की कोशिश की। पहले OpenGL ES 2 और 3 को काफी अच्छी तरह जानता था
मुश्किल चीज़ों में से एक यह समझना था कि samples नहीं, बल्कि real engine में इसे कैसे इस्तेमाल करना चाहिए। कई samples बस जितनी जरूरत हो उतना allocate करते हैं, या कमी न पड़े इसलिए सैकड़ों allocate कर देते हैं
DirectX सीखते समय Microsoft का MiniEngine मददगार था, क्योंकि उसमें DescriptorAllocator जैसी चीज़ थी जो बहुत complex हुए बिना descriptor allocation manage करती थी। सोचता हूँ Vulkan में भी कुछ ऐसा है क्या
एक और मुश्किल यह जानना है कि materials, meshes, rendering order जैसी अच्छी abstractions कैसे बनाई जाएं। tutorials से आगे बढ़ने के लिए पढ़ने लायक कोई अच्छा engine या framework है?
descriptor set allocation में मेरे लिए सिर्फ एक pattern समझ में आता है। pools को short-lived और बड़ी संख्या में बनने वाला मानना चाहिए। current pool से allocation fail हो तो नया pool बनाएं, और descriptor count counters खुद maintain न करें। standard simple counting से अलग तरह-तरह के pool behavior allow करता है। उस pool को reference करने वाला आखिरी command buffer खत्म होने के बाद पुराने pool को discard कर दें
pipeline barriers और image layouts सच में सिरदर्द हैं। बेहतर है कि इन्हें ऐसी layer में abstract करें जो हर resource का last use और last format track करे और जरूरी barriers add करे। यह complex हो सकता है, लेकिन जब optional passes हों या pass order बदला जा सके जैसी ज्यादा complex स्थितियां आएं, तो इसकी कीमत वसूल होती है
meshes, materials, rendering order को HN comment में summarize करना मुश्किल है, और यह rendering algorithm के selection पर बहुत निर्भर करता है। बहुत generalized solution को सही तरीके से बनाने में लगने वाली भारी मेहनत मुझे worth it नहीं लगती
अगर कोई casual reader जानना चाहता है कि Vulkan 1.3 में “Hello, Triangle!” लिखने के लिए क्या चाहिए, तो यह देखें: https://github.com/Planimeter/game-engine-3d/blob/main/src/g...
Vulkan initialization और basic swapchain management बहुत verbose हैं, लेकिन एक बार कर लेने के बाद बाद में pipeline creation और management के लिए सुविधाजनक abstraction बना लें तो यह काफी बेहतर हो जाता है