3 puan yazan GN⁺ 2024-06-07 | 1 yorum | WhatsApp'ta paylaş
  • 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
  • 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 vkSetDebugUtilsObjectNameEXT gibi extension fonksiyonlarını ayrı ayrı pointer olarak saklamadan kullanmayı sağlıyor
  • GfxDevice sınıfı, sık kullanılan Vulkan özellikleri ve nesnelerini bir araya topluyor
    • Vulkan context başlatma
    • swapchain oluşturma ve yönetimi
    • beginFrame ve endFrame
    • 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, VmaAllocator gibi 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’in DEPFILE ö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ı, PostFXPipeline gibi pipeline class’lara ayrılmış
  • Her pipeline genelde şu rolleri üstleniyor
    • init: shader yükleme, VkPipeline, VkPipelineLayout başlatma
    • cleanup: pipeline ve layout temizliği
    • draw: her frame gerekli girdileri alıp draw call çalıştırma
  • draw fonksiyonunun vkCmdBeginRendering ile vkCmdEndRendering arası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_rendering kullanı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, VkVertexInputAttributeDescription gibi 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 scalar layout kullanılıyor
    • std430’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 textures dizisine ekleniyor ve ilgili index bindless texture ID’si olarak kullanılıyor
    • Shader’a texture ID’si push constants ile aktarılıyor
  • Sampler’lar image’dan ayrı tutuluyor; ortak sampler’lar startup sırasında oluşturulup samplers dizisine 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 mat4 dizisinde 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 cleanup fonksiyonu 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
  • vkconfig iç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_VertexIndex ile quad’ın vertex koordinatlarını ve UV’lerini üretiyor
  • Tüm sprite draw call’ları SpriteDrawBuffer içinde SpriteDrawCommand olarak 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
  • MeshDrawCommand içinde meshId, 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.Diary içinde ilk noktadan önceki Interact, prefab adı
    • Sphere, physics sisteminde sphere physics body oluştururken kullanılıyor
    • Capsule ve Box da kullanılabiliyor; bunlar yoksa physics shape mesh vertex'lerinden oluşturuluyor
  • karmaşık modeller doğrudan level glTF'sine konmuyor; bunun yerine Empty->Arrows object olarak yerleştirilip Cat.NearStore gibi bir ad veriliyor
    • Cat prefab'ı oluşturuluyor ve NearStore tag'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

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

 
GN⁺ 2024-06-07
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

    • WGPU, WebGPU kullanıyor ve bildiğim kadarıyla henüz hiçbir tarayıcı thread desteklemiyor
      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
    • İlk kısım doğru, ama ikinci kısım değil. Ayırma ve senkronizasyon otomatik olarak hallediliyor
    • Metal, Vulkan’dan daha eski olduğuna göre, kesin konuşursak farklı davranan Vulkan tarafı
    • WGPU’nun birden fazla thread’den GPU belleğini çakışmasız güncelleyememesi, iyi bir deneyim oluşturmak için gerçekten doğru anlamak istediğim temel nokta. WGPU’nun bu özelliği bir uzantı olarak bile eklemenin bir yolunu bulmasını isterim
    • Daha önce URLO’da paylaştığın yazıyı da çok ilgiyle okumuştum: https://users.rust-lang.org/t/game-dev-in-rust-some-notes-on...
  • 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

    • Bildiğim kadarıyla OpenGL yalnızca macOS’ta kullanımdan kaldırıldı ve uzun süre daha var olmaya devam edecek
      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
    • Tek bir dönen küpün yüzlerce satır tutması çok anlamlı değil. İkinci bir küp ya da başka bir şekil eklediğinizde satır sayısı ikiye katlanmaz
      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
    • Vulkan, OpenGL gibi kütüphaneler yazmak için hedef alınan şeydir. Avantajı, kütüphane seviyesindeki pek çok kodun opak ve hataya açık aygıt sürücülerinden kullanıcı alanı kütüphanelerine taşınmasıdır
    • OpenGL istiyorsanız ANGLE kullanabilirsiniz
      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

    • Görsel sadakatle ilgilenmeseniz bile Vulkan’ın OpenGL’e göre avantajları var
      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
    • Buradaki itici güç daha çok oyun geliştirmenin finansal gerçeklerine benziyor. Yüksek sadakatli grafikler inanılmaz pahalı ve küçük oyun stüdyolarının gerçekçi takvim ve bütçelerle üretmesi zor
      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ı
    • Orta yol WebGPU. Vulkan’dan çok daha az laf kalabalığına sahip ve tarayıcılar dahil her yerde çalışacağı garanti
      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
    • OpenGL gün batımına giriyor, Metal’i umursayan neredeyse kimse yok ve gönüllü olarak Vulkan kullananlar, hafta sonu kernel yazmaya ara verip gelen sakallı kod büyücülerinden ibaret
      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
    • Mesele yalnızca performans değil. OpenGL’den uzaklaşmanın nedenlerinden biri de yüksek seviyeli özelliklerin sürücü uygulamaları veya GPU’lar arasında tutarlı olmaması bence
      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.

    • Bu, tek kişilik ekip bağlamında geçerli. Şu tavsiyeden de açıkça anlaşılıyor: “Oyunun/motorun herhangi bir bölümünü daha sonra yeniden yazabileceğini unutma.”
      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.
    • Aşırı tasarım sürekli baş ağrısıdır. Sonuçta pratikte neredeyse hiçbir kazanç olmadan karmaşıklık ciddi ölçüde artar.
      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.
    • YAGNI’nin de bir best practice olduğunu söylemek yeterli :D
    • Bu ilkeye sıkı sıkıya bağlı kalırsan işin büyük kısmı, çeşitli API ve servisleri birbirine bağlamaya ve daha önce yazılmış yazılımları şirketin ihtiyaçlarına uyarlamaya dönebilir.
      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.
    • “Şu anda ihtiyacın yoksa uygulama” başlarken yararlı bir tavsiye. Ancak uzmanların bozabileceği bir kural da.
  • 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

    • DirectX 12 ve DirectX 11 için de aynı şeyi duydum. Bir kitapta, ne yaptığını bilmiyorsan DirectX 12’nin DirectX 11’den daha kötü performans verme ihtimalinin yüksek olduğu yazıyordu.
    • Benim için en büyük konu shader compiler. OpenGL’de yerleşik geliyor, ama Vulkan’da bir bağımlılık daha getirmen gerekiyor.
      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.
    • Vulkan’ı hedefleyen ama daha yüksek bir soyutlama seviyesinde kullanılabilecek bir OpenGL-to-Vulkan katmanı kütüphane olarak yapılamaz mı?
      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?

    • Oyun geliştirmede genelde başta büyük bir bellek bloğu ayırıp onun içinde bump allocator kullanma pratiği vardır.
      Ç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.
    • Vulkan en başından beri son derece düşük seviyeli bir API olarak tasarlandı ve onu OpenGL/DX11 seviyesine çıkarmak için kütüphanelere ihtiyaç duyulacağı düşüncesi vardı.
      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.
    • Evet, bu çok fazla şey istemek. Belgelerdeki “resmî” örnekler bile bunu yapmıyor. Vulkan spesifikasyonunu okumak, teknik saçmalığa dayanma antrenmanına daha yakın.
      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ı?

    • Vulkan, DirectX 12’ye oldukça benzer; bu yüzden bazı kavramlar doğrudan taşınır. Bellek tahsisi için VMA adlı kütüphaneyi kullanmak yardımcı olur. Standardın yıllar içinde biriktirdiği bazı can sıkıcı istisna durumlarını ele alır ve oldukça güçlüdür.
      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.
    • Gerçek bir motor görmek için vkQuake gibi bir şey iyi bir referanstır: https://github.com/Novum/vkQuake
  • 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...

    • Evet. Yine de vk-bootstrap kullanırsanız 600 satır kodla biraz daha iyi oluyor: https://github.com/charles-lunarg/vk-bootstrap/blob/main/exa...
      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.