- OpenGL deneyimi olan bir geliştirici, Vulkan’ı ilk kez öğrenip yaklaşık 3 ay içinde iki küçük oyun demosu ve yeniden kullanılabilir EDBR motoru geliştirdi
- Başından itibaren genel amaçlı bir motor tasarlamak yerine önce küçük oyunlar yapıp ardından yalnızca gerekli kısımları motora dönüştürerek aşırı tasarım ve bike-shedding azaltıldı
- Motor 19k LoC ölçeğinde; 6.7k LoC grafik kodu ve 2k LoC hafif Vulkan soyutlamasından oluşuyor; compute skinning, CSM, PBR shading, MSAA, post FX ve UI rendering içeriyor
vk-bootstrap, Vulkan Memory Allocator,volk,VK_KHR_dynamic_rendering, push constants, buffer device address ve bindless descriptor kullanılarak Vulkan boilerplate’i ve descriptor set kullanımı azaltıldı- Vulkan; global state’in kaldırılması, daha iyi doğrulama hataları, RenderDoc shader debugging ve GPU ile OS arasında tutarlılık sağladı, ancak explicit synchronization hâlâ doğrudan ele alınmak zorunda
3 ayda geliştirilen Vulkan tabanlı EDBR motoru
- EDBR (Elias Daler’s Bikeshed Engine), bir Vulkan öğrenme projesi olarak başlayıp daha sonra sonraki projelerde yeniden kullanılabilecek küçük bir motora dönüştü
- Motor ve oyun kodu GitHub deposunda açık olarak yayımlanıyor
- Yazının kaleme alındığı sırada kod boyutu şöyleydi
- Motorun kendisi: 19k LoC
- Grafik ile ilgili kod: 6.7k LoC
- Hafif Vulkan soyutlaması: 2k LoC
- 3D kedi oyunu: 4.6k LoC
- 2D platform oyunu: 1.2k LoC
- Motorun kendisi: 19k LoC
- Girdi işleme ve ses sistemi gibi bazı grafik dışı kodlar önceki motordan alındı; grafik ve çeşitli çekirdek sistemler ise yeniden yazıldı
- Vulkan’ı mevcut OpenGL soyutlamasına sıkıştırmaya çalışmak yerine sıfırdan yazmanın daha uygun olduğu değerlendirildi
Grafik programlamadan Vulkan’a uzanan öğrenme sırası
- Grafik programlamaya yeni başlıyorsanız, karmaşıklık altında ezilmemek için Vulkan yerine OpenGL ile başlamak daha kolay olabilir
- Asgari hedef olarak, doku uygulanmış bir modeli ekranda göstermek ve basit Blinn-Phong aydınlatması uygulamak öneriliyor
- Temel shadow mapping, sahneyi farklı bir bakış açısından ve farklı bir render target’a çizme ile depth texture örneklemeyi öğrenmeye yardımcı oluyor
- OpenGL öğrenme kaynağı olarak şunlar öneriliyor
- Vulkan öğrenirken en faydalı kaynak vkguide oldu; ilk kez başlıyorsanız tamamını takip etmek iyi olabilir, ancak basit bir oyun için “GPU driven rendering” düzeyindeki karmaşıklık hemen gerekli olmayabilir
- Vulkan Lecture Series by TU Wien, Vulkan temellerini ele alıyor; özellikle senkronizasyon dersi faydalı bulunuyor
- İlk ayın öğrenme çıktısı olarak şu özellikler uygulandı
- glTF model yükleme
- compute skinning
- frustum culling
- shadow mapping ve cascaded shadow maps
Vulkan’ı seçme nedeni ve WebGPU ile karşılaştırma
- Hedef, Windows ve Linux odaklı masaüstü için küçük 3D oyunlardı; açık kaynak teknolojiler ve açık standartlar tercih edildiği için seçim OpenGL ile Vulkan arasında yapıldı
- OpenGL küçük oyunlar için yeterli olsa da yeni sürümlerinin gelme olasılığı düşük görülüyor ve macOS’ta deprecated durumda olduğu için geleceği belirsiz kabul ediliyor
- WebGPU da bir miktar öğrenildi, ancak şu kısıtlar vardı
- Henüz yeterince olgun değil ve çok sayıda eğitim ile örnek yok
- WGSL sözdizimi, GLSL kadar beğenilmedi
- Masaüstünde DirectX, Vulkan ve Metal üzerinde bir wrapper’a daha yakın olduğu için platforma göre RenderDoc capture’ları değişiyor ve WebGPU çağrıları ile native API çağrıları bire bir eşleşmiyor
- bindless textures ve push constants yok
- WebGPU’nun belirgin avantajları da var
- OpenGL/WebGL’e göre daha iyi doğrulama hataları var ve global state bulunmuyor
- Vulkan’a benzer yönleri olduğu için Vulkan öğrenmeden önce faydalı olabiliyor
- Ekrana bir şey göstermek için Vulkan’a göre daha az boilerplate gerekiyor
- explicit synchronization ile doğrudan uğraşmak gerekmiyor
- Oyunlar tarayıcıda çalıştırılabiliyor
Kare render akışı
- Bir kare birden fazla aşamaya ayrılıyor ve her aşama bir pipeline veya pass olarak uygulanıyor
- Skinning aşamasında, skeletal animation içeren modeller compute shader ile işleniyor
- Girdi olarak skinning uygulanmamış mesh ve joint matrices alınıyor
- Çıktı olarak sonraki render aşamalarında kullanılacak vertex buffer üretiliyor
- Sonraki aşamalarda statik mesh ile skinned mesh benzer şekilde ele alınabiliyor
- CSM aşamasında, 4096x4096 depth texture ve 3 slice kullanılarak cascaded shadow mapping yapılıyor
- geometry + shading aşamasında modeller çiziliyor ve shadow map ile ışık bilgileri kullanılarak shading uygulanıyor
- PBR modeli, Physically Based Rendering in Filament içinde açıklanan yaklaşımla neredeyse aynı
- fragment shader, tek bir draw call içinde ilgili mesh’i etkileyen tüm ışıkların hesabını yapıyor
- Her şey önce multi-sampled texture’a çiziliyor, ardından resolve ediliyor
- Depth resolve işlemi fragment shader ile manuel olarak yapılıyor
- multi-sample depth texture içindeki tüm fragment’ler dolaşılıp minimum değer non-MS depth texture’a yazılıyor
- post FX aşamasında şu anda yalnızca depth fog uygulanıyor; ileride tone mapping ve bloom da bu aşamada ele alınacak
- UI aşamasında dialogue UI çiziliyor ve bu tek bir draw call ile yapılıyor
Vulkan boilerplate’ini azaltan kütüphaneler
vk-bootstrap, physical device seçimi ve swapchain oluşturma gibi Vulkan başlatma boilerplate’ini azaltıyor- Tam kapsamlı bir Vulkan function wrapper’ı değil; daha çok başlatma aşamasını etkiliyor
- Vulkan Memory Allocator, Vulkan bellek tahsislerini doğrudan yönetme ihtiyacını ortadan kaldırıyor
volk, extension function loading sürecini basitleştiriyor- Örneğin
vkSetDebugUtilsObjectNameEXTgibi extension fonksiyonlarını ayrı ayrı pointer olarak saklamadan kullanmayı sağlıyor
- Örneğin
GfxDevicesınıfı, sık kullanılan Vulkan özellikleri ve nesnelerini bir araya topluyor- Vulkan context başlatma
- swapchain oluşturma ve yönetimi
beginFrameveendFrame- image oluşturma ve texture yükleme
- buffer oluşturma
- bindless descriptor set yönetimi
GfxDevice.cpp, yazı yazıldığı sırada 714 satır uzunluğundaydı;VkDevice,VkQueue,VmaAllocatorgibi nesneleri birçok yere taşımak yerine tek bir nesne geçirmek daha kullanışlı bulunuyor
Shader derleme ve descriptor set’ten kaçınma stratejisi
- Shader dili olarak, OpenGL deneyimi nedeniyle GLSL seçildi
- Shader’lar runtime’da değil, build aşamasında SPIR-V’ye derleniyor
- Runtime shader yükleme kodu sadeleşiyor
- Runtime shader compiler bağımlılığı yok
- Shader hataları build aşamasında yakalanıyor
glslc, CMake’inDEPFILEözelliğini desteklediği için shader include değiştiğinde ilgili dosyalar otomatik olarak yeniden derlenebiliyor- Vulkan’da uniform’ların descriptor set içinde gruplanması gerektiğinden, veri aktarımı OpenGL’e göre daha karmaşık hale geliyor
- Bu implementasyonda descriptor set kullanımı büyük ölçüde azaltılmış
- Bindless texture ve sampler için yalnızca tek bir global descriptor set kullanılıyor
- Geri kalanların çoğu push constants ile aktarılıyor
- Buffer device address kullanılarak buffer adresleri push constants ile iletiliyor
Pipeline class ve dynamic rendering
- Render aşamaları,
PostFXPipelinegibi pipeline class’lara ayrılmış - Her pipeline genelde şu rolleri üstleniyor
init: shader yükleme,VkPipeline,VkPipelineLayoutbaşlatmacleanup: pipeline ve layout temizliğidraw: her frame gerekli girdileri alıp draw call çalıştırma
drawfonksiyonununvkCmdBeginRenderingilevkCmdEndRenderingarasında çağrıldığı varsayılıyor- Hangi texture’a render edildiği pipeline içinde ele alınmıyor; render target’ı çağıran taraf belirliyor
- Genel olarak
VK_KHR_dynamic_renderingkullanılıyor, Vulkan render pass ve subpass ise kullanılmıyor- Tile-based GPU’larda render pass ve subpass’in daha verimli olduğuna dair şeyler duyulmuş olsa da, şu anda mobil destek dikkate alınmıyor
- Dynamic rendering implementasyonu çok daha kolay hale getiriyor
PVP, BDA ve bindless descriptor kullanımı
- Tüm mesh’lerde tek bir vertex tipi kullanılıyor
- Programmable vertex pulling kullanıldığında, OpenGL’deki VAO ya da Vulkan’daki
VkVertexInputBindingDescription,VkVertexInputAttributeDescriptiongibi vertex format tanımlarından kaçınılabiliyor - Buffer device address sayesinde vertex buffer’ı descriptor set’e bind etmeden, buffer adresi push constants ile aktarılabiliyor
- Push constants ve buffer’larda
scalarlayout kullanılıyorstd430’a göre hizalama işlemlerini yönetmek daha kolay olduğundan, C++ struct’larıyla neredeyse aynı şekilde ele alınabiliyor- C++ struct’larında padding üyelerine duyulan ihtiyaç azalıyor
- Bindless descriptor, büyük bir descriptor set içinde texture ve sampler dizileri tutularak kullanılıyor
- Yeni bir texture yüklendiğinde
texturesdizisine ekleniyor ve ilgili index bindless texture ID’si olarak kullanılıyor - Shader’a texture ID’si push constants ile aktarılıyor
- Yeni bir texture yüklendiğinde
- Sampler’lar image’dan ayrı tutuluyor; ortak sampler’lar startup sırasında oluşturulup
samplersdizisine ekleniyor - Material buffer’da da bindless texture ID kullanılıyor
- Yalnızca material ID’si push constants ile aktarılıyor ve fragment shader material buffer’a bakıyor
- Hantal descriptor set’ler olmadan, material başına tek bir tamsayıyla texture erişimi mümkün oluyor
- Bindless texture hakkında ek kaynak olarak Vulkan Bindless Texture öneriliyor
Her frame yüklenen dinamik veri
- Her frame CPU’dan GPU’ya aktarılması gereken veriler için büyük diziler önceden ayrılıyor ve her frame index 0’dan başlayarak dolduruluyor
- Örneğin tüm joint matrix’ler tek bir büyük
mat4dizisinde tutuluyor, her skinned mesh için başlangıç index’i push constants ile aktarılıyor - İki yöntem var
- GPU’da N adet buffer tutulup frame-in-flight başına dönüşümlü kullanılıyor
- GPU’da tek bir buffer tutulup CPU tarafında N adet staging buffer kullanılıyor
- Çoğu durumda ilk yöntem öneriliyor
- Daha fazla GPU belleği kullanıyor ama manuel senkronizasyon gerektirmiyor
- GPU belleğini korumak gerekiyorsa ikinci yöntem faydalı olabilir
- İki yaklaşım arasında belirgin bir performans farkı gözlenmemiş, ancak her frame çok büyük veri yüklenirse fark ortaya çıkabilir
Cleanup ve senkronizasyon
- vkguide’daki deletion queue pattern, yazarın motorunda çok faydalı olmamış
- Çünkü her frame yeni Vulkan nesneleri allocate ya da destroy edilmiyor
- C++ destructor tabanlı cleanup da rahat bulunmamış
- Wrapper class, move constructor ve move assignment gerektirdiği için karmaşıklık artıyor
- Frame ortasında kullanımda olan nesnelerin wrapper yok edilmesiyle yanlışlıkla kaldırılması riski var
- Şu anda Vulkan nesneleri tek bir yerde toplamak için
cleanupfonksiyonu açıkça çağrılıyor- Bunu unutmak kolay olsa da, kapanışta Vulkan validation error ve VMA assert eksik cleanup’ı bildiriyor
- Vulkan synchronization zordur ve açık şekilde yönetilmelidir
- OpenGL ve WebGPU, texture ya da buffer okumaları için gerekli senkronizasyonu sizin yerinize halleder
- Vulkan’da data race’ten kaçınmak için barrier’ları doğrudan eklemek gerekir
- Şu anda draw işlemleri pass ve pipeline’lara ayrılıyor, aralarına da barrier’lar manuel olarak ekleniyor
- Örneğin compute shader ile çalışan skinning pass vertex verisini yazdıktan sonra, shadow mapping pass bunu okumadan önce bir barrier ekleniyor
- Bu süreç render graph ile otomatikleştirilebilir, ancak şu an manuel senkronizasyon yeterli görülüyor
vkconfigiçindeki synchronization validation layer, senkronizasyon hatalarını bulmada yardımcı oluyor
Sprite, skinning, game/renderer ayrımı
- Bindless texture kullanıldığında çok sayıda sprite’ı vertex buffer olmadan tek bir draw call ile çizmek kolaylaşıyor
- Sprite vertex shader,
gl_VertexIndexile quad’ın vertex koordinatlarını ve UV’lerini üretiyor - Tüm sprite draw call’ları
SpriteDrawBufferiçindeSpriteDrawCommandolarak toplanıyor- transform
- UV aralığı
- renk
- texture ID
- shader ID
- Gerçek draw call şu şekilde:
vkCmdDraw(cmd, 6, spriteDrawCommands.size(), 0, 0)- Sprite başına 6 vertex
- Sprite sayısı kadar instance
- Sprite renderer, 10 bin sprite’ı 315 mikrosaniyede çizebiliyor
- Compute skinning, skeletal animation içeren mesh’lerde giriş vertex’leri ve joint matrix’leri alıp skinned vertex buffer üretiyor
- Aynı mesh’e sahip 3 kedi bile birbirinden farklı animasyonlara sahip olabiliyor
- Çıktı vertex buffer’ı her mesh instance için gerekiyor
- Game logic ile renderer, draw command’lar üzerinden ayrılıyor
- Game logic, entt kullanıyor
- Renderer entity ya da game object’leri bilmiyor; yalnızca light, scene parameter ve mesh draw command’larıyla ilgileniyor
MeshDrawCommandiçindemeshId, transform matrix, bounding sphere, skinned mesh pointer’ı, joint matrix başlangıç index’i ve shadow casting bilgisi yer alıyor
sahne yükleme, UI, Dear ImGui
- level editor'ü kendisi yapmak yerine Blender kullanıp glTF olarak export ediyor
- level editor'ü doğrudan yazmak aylar hatta yıllar sürebileceği için zamandan tasarruf sağlıyor
- node adlarıyla prefab oluşturma ve physics shape belirleme yapılıyor
- örnek:
Interact.Sphere.Diaryiçinde ilk noktadan öncekiInteract, prefab adı Sphere, physics sisteminde sphere physics body oluştururken kullanılıyorCapsuleveBoxda kullanılabiliyor; bunlar yoksa physics shape mesh vertex'lerinden oluşturuluyor
- örnek:
- karmaşık modeller doğrudan level glTF'sine konmuyor; bunun yerine
Empty->Arrowsobject olarak yerleştirilipCat.NearStoregibi bir ad veriliyorCatprefab'ı oluşturuluyor veNearStoretag'i ekleniyor
- prefab'lar JSON ile yazılıyor ve harici glTF ile movement, physics bilgilerini içerebiliyor
- UI sistemi, Roblox UI API'sinden ilham alıyor
- origin
- relative size
- relative position
offsetPosition,offsetSize- fixed size
- label/image içeriğine dayalı size
- UI element boyutu özyinelemeli olarak hesaplandıktan sonra position hesaplanıyor ve parent'tan children'a doğru çiziliyor
- Dear ImGui, geliştirme ve debug araçları için kullanılıyor
- sRGB framebuffer üzerinde Dear ImGui'nin yanlış görünmesi sorunu olduğu için özel bir Dear ImGui backend'i yazılmış
- yalnızca rendering kısmı yazılmış; input event işleme, clipboard gibi mantık/OS etkileşimleri ise varsayılan Dear ImGui SDL backend'i tarafından ele alınıyor
- özel backend'in avantajları şöyle
- bindless texture id desteklediği için
ImGui::Image(bindlessTextureId, ...)ile image çizilebiliyor - linear image ve non-linear image, format bilgisi verilerek doğru şekilde çizilebiliyor
- motorun diğer Vulkan kodlarıyla aynı şekilde initialize edilip yönetilebiliyor
- bindless texture id desteklediği için
kullanılan kütüphaneler ve Vulkan'a geçişin etkileri
- physics için Jolt Physics kullanılıyor
- ağırlıklı olarak collision resolution ve temel character movement için kullanılıyor
JPH::CharacterVirtual, temel character movement'i iyi şekilde ele alıyor
- ECS için entt kullanılıyor
- elde yazılmış bir ECS yerine harici bir kütüphane kullanarak bakım yapılacak kod miktarı azaltılıyor
- audio için openal-soft, libogg, libvorbis kullanılıyor
- profiling için Tracy kullanılıyor
- hangi kodun gerçekte ne kadar az zaman harcadığını görmeyi sağlayarak gereksiz bike-shedding'den kaçınmaya yardımcı oluyor
- Vulkan'a geçişle elde edilenler şöyle
- OpenGL'in global state yapısı ortadan kalktığı için abstraction daha kolay hale geliyor
- OpenGL tarzı
shader.bind(), state tracker ve sihirli RAII'ye daha az ihtiyaç duyuluyor - validation error'lar OpenGL'e kıyasla daha zengin
- RenderDoc içinde vertex shader ve fragment shader doğrudan debug edilebiliyor
- GPU ile OS arasındaki davranış farkları OpenGL'e göre daha az belirgin
- gelecekte Slang ve Shady gibi başka shading language seçenekleri keşfedilebiliyor
- grafik pipeline'ının her yönü üzerinde daha fazla kontrol sağlanıyor
bundan sonra yapılacaklar
- gelecek planları şöyle
- signed-distance field font desteği
- çok sayıda image yükleme ve mipmap'leri paralel oluşturma
- bloom
- volumetric fog
- animation blending
- render graph
- ambient occlusion
- oyunu tamamlama
- Vulkan öğrenmek zordu ama beklendiği kadar zor değildi; ayrıca grafik programlama ve modern API'leri daha derinlemesine anlamak için bir fırsat oldu
1 yorum
Hacker News yorumları
Minimalizm çok etkili
Ben ters yöne gittim ve bunun yüzünden epey acı çekiyorum. Rust ile bir metaverse istemcisi geliştiriyorum; şu anda da diğer ekranda avatarın devasa bir steampunk şehrin içinden tramvayla geçtiği bir sahneyi gösteriyor. Yeni bir ön sürüm çıkarmadan önce 12 saatlik çalıştırma testleri yapıyorum
Vulkan’ın üstüne WGPU ve Rend3 koydum; Rend3 de mesh, 2D texture ve nesne oluşturduğunuzda nesnenin mesh ile texture’a referans verdiği ve ekranda göründüğü temiz bir API sunuyor. Rust’ın referans sayımı bağlantıları doğru tuttuğu için kullanımı sezgisel
Ama katmanlar arttıkça sorunlar çıkıyor. WGPU; web tarayıcıları, Vulkan, Metal, DX11 (yakın zamanda kaldırıldı), DX12, Android ve OpenGL’e kadar desteklemeye çalıştığından büyük bir geliştirme ekibi gerektiriyor ve değişiklik yapmak zorlaşıyor. WGPU API’sinin kendisi de genel olarak Vulkan’a benzediği için GPU bellek ayırma ve senkronizasyonla doğrudan ilgilenmeniz gerekiyor
WGPU’da en küçük ortak payda sorunu var. Bazı platformlar belirli özellikleri destekleyemiyor; Vulkan’ın desteklediği, birden fazla thread’den GPU belleğinin eşzamanlı güncellenmesini WGPU çakışmasız biçimde yönetemiyor. Büyük dünyalarla uğraşan oyunlar veya istemciler için, kare hızını öldürmeden içeriği GPU’ya aktarmak adına bu özellik gerekli. Platforma göre eşzamanlılık kısıtları değiştiğinden kilit çakışmaları performansı ciddi biçimde düşürebilir
Rend3, senkronizasyon ve ayırmayı yöneten makul bir yapıştırıcı kod olmaya çalıştı; ama özellikle senkronizasyonu genel bir şekilde çözmek zor. Frustum culling büyük performans kazancı sağlıyor, fakat occlusion culling hesaplama maliyeti yüzünden zarara dönüştü. Yarı saydamlık işleme de derinliğe göre sıralama gerektirdiği için baş ağrıtıyor. Çok pencereli dünyalarla çalıştığım için dışarıyı görebilmeyi ve içeriye bakabilmeyi sağlayan yarı saydam nesneler kesinlikle gerekli
Rust 3D stack’indeki insanlar, 3 yıl boyunca stack’i düzeltmeleri için sürekli baskı yapmamdan rahatsız olmuş görünüyor. Herkes gönüllü; Vulkan ise parası ve kullanıcı tabanı olduğu için ayakta kalıyor. Rend3’ün yaratıcısı yakın zamanda pes etti, bu yüzden artık benim içeri girip düzeltmem gerekiyor. WGPU ile karmaşık şeyler yapan çok az kişi var; çoğu, Flash ile bile yapılabilecek 2D oyunlar ya da basit statik 3D sahneler. Ticari projeler hâlâ Unity veya UE5 kullanıyor
Doğrudan Vulkan’a geçmek de senkronizasyonu, ayırmayı, frustum culling’i ve yarı saydamlık işlemeyi kendiniz yazmanız gerektiği için büyük bir geçiş
Ayrıca Vulkan ve Metal üzerinde bir wrapper olan Vulkano’da da en küçük ortak payda sorunu var. Vulkan ve Metal’in ikisi de GPU varlıklarının eşzamanlı güncellenmesini destekliyor, ancak Vulkano buna izin vermiyor. Elbette Apple işleri yine farklı ele alıyor
https://gpuweb.github.io/gpuweb/explainer/#multithreading
https://github.com/gpuweb/gpuweb/issues/354
OpenGL de hiçbir zaman thread desteklemedi, yani OpenGL kullanarak bu mümkün olamaz
Bir yıldan biraz daha uzun süre önce Vulkan öğrenmeyi denedim ama bir daha dokunmak istemiyorum. OpenGL’i kullanımdan kaldırıp basit işleri bile akıl almaz derecede zorlaştıran bir şeyle değiştirmek gerçekten rahatsız edici. Örneğin tek bir dönen küp için bile yüzlerce satır kod gerekiyor
OpenGL de kolay değildi ama sıradan biri nispeten kısa sürede temellerini öğrenebiliyordu. Büyük bir kitapçıdan grafik programlamaya giriş kitabı alıp bir iki öğleden sonra içinde temel render’ı ekrana getirebiliyordunuz. Vulkan bazı açılardan daha iyi olabilir ama hızlıca öğrenmeyi beklemek gerçekçi değil
Yeni Intel/ARM/AMD çipleri çıktığında C veya C++ kullanamadığınızı ve “yüksek seviyeli dil desteğini bırakıyoruz, artık yalnızca assembly kullanın; daha fazla kontrol sahibi olacağınız için daha hızlı” dendiğini hayal edin; elbette buna saçmalık denirdi
Vulkan’dan sık sık yüksek seviyeli bir grafik API’sinden çok bir “GPU API’si” olarak söz ediliyor. Böyle bakınca karmaşıklığı şaşırtıcı değil; alanın kendisi zor
Modern CPU’larda yüksek seviyeli dilleri kaldırıp yalnızca assembly kullanın benzetmesinden ziyade, tek iş parçacıklı C/C++ ile çok iş parçacıklı C/C++ arasındaki farka daha yakın. Karmaşıklık ciddi ölçüde artıyor; bilmiyorsanız patlıyor ya da performans daha da kötüleşiyor, ama ileriye dönük pratik yol da bu
OpenGL genel olarak Vulkan’ın üzerinde uygulanabilir. OpenGL standardının artık aktif biçimde geliştirilmiyor olması üzücü ama hiçbir şey sonsuza dek sürmez
Sorun, OpenGL’in artık güncel donanımla pek iyi örtüşmemesi ve naif şekilde kullanıldığında performansının çok verimsiz olması. OpenGL’de de sürücü yükünü ortadan kaldıran tekniklere kadar giderseniz Vulkan o kadar da daha zor değil
Günümüzdeki Intel/ARM/AMD çipleri de C veya C++’ı doğrudan desteklemiyor. Zaten assembly kullanıyoruz ya da C/C++’tan dönüştüren üçüncü taraf araçlar kullanıyoruz. Vulkan’ın amacı da GPU’ya düşük seviyeli standart bir arayüz sağlamak ve bunun üzerine kullanımı daha iyi soyutlamalar koymayı mümkün kılmak
https://github.com/google/angle
Pek çok telefonda artık Vulkan sürücüsü üzerindeki tek OpenGL desteği olarak ANGLE geliyor
Nispeten kolay ve taşınabilir modern bir API istiyorsanız Rust’taki wgpu’yu ya da C++’taki dawn üzerinden WebGPU’yu kullanabilirsiniz
Vulkan’ın harika olduğunu düşünüyorum ama amacı, gelişmiş GPU özelliklerinden azami ölçüde yararlanmak. Gelişmiş GPU özellikleri kullanıldığında OpenGL’den daha iyi performans da verebilir
Gelişmiş render tekniklerini hedeflemiyorsanız genelde önerilen yolun OpenGL olduğu hissindeyim
Bugün hâlâ çok sayıda 2D, low-poly ve PS1 tarzı grafiklere sahip oyun var; bu oyunların Vulkan kullanması gerekmiyor
Vulkan, AAA oyun endüstrisinin render kalitesi ve görsellik tarafına kaymasının bir örneği. AAA stüdyolar bütçelerini çok gelişmiş motorlar ve içerikle gerekçelendiriyor ama oyuncuların bıkıp grafiklerden çok oynanış istediğini fark ettikçe 2D/low-poly oyun pazarı büyüyor
Oyun geliştiricisiyseniz render kalitesinden çok oynanışa ve özelliklere odaklanmak istersiniz
Küresel durum yok, çalışma zamanında kullanılacak GPU’yu seçebiliyorsunuz ve OpenGL’in hata yönetimi berbat. Doğrulama katmanları var, resmî görselleri de hoş, belgeleri de mükemmel. İkinci bir iş parçacığında GPU’ya asenkron veri yüklemek de daha mantıklı biçimde mümkün; mesh shader veya RTX gibi gelişmiş GPU özellikleri de var
Tüketici AAA düzeyi grafiklere sahip indie oyunları reddeder mi? Muhtemelen hayır. Böyle oyunların az olmasının nedeni finansal olarak tutmasının zor olması ve daha stilize, daha düşük sadakatli grafikleri kabul eden yeterince büyük bir pazar bulunması
Aynı zamanda WebGL’de kullanılamayan compute shader gibi modern özelliklere erişim sağlıyor ve OpenGL’deki gibi aynı işi yapmanın birden çok yolu etrafında birikmiş fazla legacy de yok. Başlıca avantajı yeni olması; ama bu yüzden eğitim materyali çok daha az ve bu oldukça ciddi bir dezavantaj
Framework ve motor sayısı sonsuz ama çoğu tamamlanmamış durumda ve hepsinin kod yapısı hakkında güçlü fikirleri var. “Callback’leri tutan bir sahne ağacı oluştur. Hayır, entity ve component sınıfları ile nesneler yaz. Dur, artık her şey immediate mode, fonksiyonel ve stateless” gibi
Bunun üstüne bir de platform karmaşası biniyor. Mobil oyunlardaki push bildirimleri, uygulama içi satın almalar ve Xcode üzerinden zorunlu imzalama da eklenince tam bir keşmekeş oluyor. Unity’nin pazar payına sahip olmasının bir nedeni var ve bu, harika bir yazılım olması değil. Süslü bir webview’den fazlası olan cross-platform hâlâ muazzam bir acı olduğu için
Zaten GPU/sürücüye özel kod yazmaya devam etmek gerekiyorsa soyutlama katmanının anlamı kalmıyor. Düşük seviyeli sürücü ile yüksek seviyeli kütüphaneyi ayırmak daha iyi
Bu yazıda çok iyi tavsiyeler var. Özellikle “şu anda ihtiyacın yoksa uygulama” sözü dikkatimi çekti.
Bu, birkaç yıllık deneyimi olan ama hâlâ gelişmekte olan junior programcılarla sürekli mücadele ettiğim bir konu. Çoğu zaman “best practice”lere ve trend olan gösterişli yeni araçlara takılıyorlar; ama çözülmesi gereken problemden başlayıp, o problemi çözmek için gereken asgari şeye odaklanmakta zorlanıyorlar.
Orta ve büyük ölçekli organizasyonlarda genelde böyle olmaz. Çoğunlukla bir sonraki işe geçilir ve tekrar dönüp bakmaya neredeyse hiç zaman kalmaz. Ne yazık ki bu yüzden en baştan düzgün yapmak, bug ya da yan etki oluşturma ihtimalini azaltmak gerekir.
Çok fazla kod tabanında yeni özellikler aceleye getirildiği için kod bir mayın tarlasına dönüştü; küçücük bir değişiklik için bile tüm özellikleri elle kontrol etmek ve tüm uygulama bağlamını kafada tutmak gerekti.
Deneyimime göre sonradan yeni gereksinimler geldiğinde, önceden yaptığın genel çözüm tamamen uygunsuz kalır ve zaten yeniden yapmak gerekir. Önündeki problemi çözmelisin; bilinmeyen gelecekteki problemleri değil.
Bu yüzden insanların her fırsatta oraya buraya kendi bir şeylerini yazmaya çalışması şaşırtıcı değil. Böyle dijital tesisatçılık angaryaları insanı tüketiyor; akıl sağlığını biraz daha uzun korumak için araya ara sıra daha keyifli işler sıkıştırmak istiyorsun.
Site yoğun trafikten çökmüş gibi, bu yüzden önbellek bağlantısını bırakıyorum: https://web.archive.org/web/20240606103630/https://edw.is/le...
Harika bir yazı. Bilimsel veri görselleştirme motoru yapmak için Vulkan’ı kendi başıma öğrendim: https://datoviz.org/ Hâlâ epey deneysel ve yakında yeni bir sürüm çıkaracağım.
Daha önce OpenGL bilgim vardı ama Vulkan öğrenmek gerçekten zordu. 5 yıl önce öğrenme kaynakları da pek iyi değildi. Yine de denedim ve çok eğlenceliydi.
Onlarca soyutlamanın rolünü anlamak aylar sürdü; bu süreçte Vulkan’ı biraz daha az acılı kullanmak için küçük bir wrapper da yazdım: https://datoviz.org/api/vklite/
Bu wrapper, bilimsel görselleştirme için en gerekli özelliklerin yalnızca bir kısmını destekliyor.
Vulkan ile OpenGL’den daha iyi performans almak basit değil. Vulkan sürücüsünde, OpenGL sürücüsünün render pipeline ve render target ayarlarını senin yerine yaptığı yaklaşık 20 bin satırlık kod eksik.
O kod zaten OpenGL sürücüsünün içinde var ve sektörün en iyi insanları tarafından 20 yılı aşkın süredir optimize ediliyor.
Bu yüzden OpenGL’in varsayılan olarak sağladığı özelliklere denk şeyleri Vulkan üzerinde safça birleştirince sihirli biçimde iyi performans çıkmıyor. Daha fazla iş gerekiyor; doğru fence’leri ve senkronizasyon primitive’lerini koymak gibi gerçek problemler birikmeye başlıyor.
Ancak gerçekten ne yaptığını bildiğinde, render işlemini iyi paralellik ve doğru senkronizasyonla çalıştırabildiğinde Vulkan’ın performans avantajlarını hayal edebilirsin.
Ben bir hobi geliştiricisi olarak sadeliği nedeniyle OpenGL ES3 kullanıyorum. Benim için zaten yeterince iyi; can sıkıcı Vulkan vertex descriptor descriptor descriptor’larını yazmaktan daha acil işlerim var.
Bu arada motorum burada: https://github.com/ensisoft/detonator
Vulkan’ın artık bindless texture’a izin verdiğini duydum; bu yüzden descriptor karmaşası eskisine göre daha az korkunç olabilir.
Vulkan çekici, ama ödemek istemediğim yüksek bir başlangıç maliyeti var.
Sonrasında bu kütüphaneyi, kullanım senaryona göre optimize edilmiş rutinlerle kademeli olarak değiştirebilirsin.
Vulkan kaynaklarının artması iyi, ama bu da ekrana bir şey çizdiren tüm Vulkan kaynaklarında gördüğüm aynı sorunu yaşıyor.
Daha basit bir durumu bile göstermeden Vulkan’ın üstüne başka bir soyutlama katmanı getiriyorlar. Hep vk-bootstrap, volk, vma ya da başka kütüphaneler kullanmamızı söylüyorlar.
Bellek yönetimini elle yapan bir örnek gösteren tek bir kaynak var mı, bilmiyorum. Görünüşe göre seçenekler ya vma kullanmak ya da spesifikasyonu doğrudan kazmak. Vulkan SDK’nın kendisi dışında hiçbir kütüphane eklemeyen en temel örneği istemek bu kadar aşırı bir talep mi?
Çoğu oyunda kabaca üç yaşam süresi bulunur: kalıcı/başlangıç anı, seviye bazlı ve kare bazlı.
Bu yaşam süreleri iç içe geçtiği için tek bir stack allocator ile epey yol alınabilir. Kare ya da seviye bittiğinde başlangıç konumuna geri sarmak yeterlidir.
Daha karmaşık desenler de var, ama tek başına bu yaklaşım bile oldukça kullanışlıdır ve hem CPU hem GPU tarafında kullanılabilir.
Bu açıdan yukarıdaki kütüphanelerin yaygın biçimde kullanılması gayet normal. Bugün yazılımları doğrudan sistem çağrılarına karşı yazmamamızla aynı şey.
Birinin Vulkan başlatma yönergelerini Khronos Group deposundaki kodla karşılaştırıp Vulkan 1.3 spesifikasyonunu okurken, bir şeyi yapabilmek için spesifikasyonu sırayla değil, karmakarışık okumak gerektiğini fark ettiğiniz anda başarısız oldukları açık hale geliyor.
Başarısız oldular. Başka ölçütlerle bakınca da kötü bir iş. Sadece bir kez yapıp çoğunu unutabildiğiniz için uzmanlar pek şikâyet etmiyor.
Bu başlıktaki başka bir yorumda, spesifikasyonun bölüm ve alt bölümlerini yorum olarak eklediğim kaynak kodun bir kısmını bırakmıştım. SDL vb. ile birlikte kullanılabilecek genel bir uygulama.
Yazıldığı an itibarıyla standart yaklaşım, resmî Vulkan SDK’ya dahil olan VMA ve Volk’u kullanmak. Bu bile mevcut durumu yeterince anlatıyor.
Birkaç yıl boyunca aralıklı olarak Vulkan öğrenmeye çalıştım. Eskiden OpenGL ES 2 ve 3’ü oldukça iyi biliyordum.
Zorlandığım şeylerden biri, örneklerde değil gerçek bir motorda nasıl kullanılması gerektiğini anlamaktı. Birçok örnek tam gerektiği kadar ayırıyor ya da asla yetmeyecek duruma düşmemek için yüzlercesini ayırıyor.
DirectX öğrenirken Microsoft’un MiniEngine’i yardımcı olmuştu. Çünkü fazla karmaşık olmadan descriptor tahsisini yöneten DescriptorAllocator gibi şeyler vardı. Vulkan’da da benzeri var mı merak ediyorum.
Bir başka zor nokta da material, mesh, render sırası gibi iyi soyutlamaları nasıl oluşturacağını bilmek. Eğitimlerin ötesine geçmek için incelenebilecek iyi bir motor ya da framework var mı?
Descriptor set tahsisinde bana mantıklı gelen tek bir desen var. Pool’ların kısa ömürlü ve çok sayıda olduğunu varsaymak gerekir. Mevcut pool’dan tahsis başarısız olursa yeni bir pool oluşturmak ve descriptor sayacı tutmamak. Standart, basit sayımdan farklı her tür pool davranışına izin veriyor. O pool’a referans veren son command buffer bittikten sonra eski pool’u atmak yeterli.
Pipeline barrier’lar ve image layout’lar gerçekten baş ağrıtıcı. Tüm kaynakların son kullanımını ve son biçimini izleyip gerekli barrier’ları ekleyen bir katmanla soyutlamak iyi olur. Karmaşıklaşabilir, ama isteğe bağlı pass’lerin olduğu ya da pass sırasının değiştirilebildiği daha karmaşık durumlar ortaya çıktığında buna değer.
Mesh, material ve render sırasını tek bir HN yorumunda özetlemek zor; ayrıca seçilen render algoritmasına büyük ölçüde bağlı. Çok genelleştirilmiş bir çözümü düzgün yapmak için gereken muazzam emeğin buna değeceğini sanmıyorum.
Vulkan 1.3’te “Hello, Triangle!” yazmak için neler gerektiğini merak eden sıradan bir okursanız şuna bakabilirsiniz: https://github.com/Planimeter/game-engine-3d/blob/main/src/g...
Vulkan başlatma ve temel swapchain yönetimi çok laf kalabalığına dönüşüyor, ama bir kez yapıp sonra pipeline oluşturma ve yönetimi için kullanışlı soyutlamalar kurunca çok daha iyi hale geliyor.