3 poin oleh GN⁺ 2024-06-07 | 1 komentar | Bagikan ke WhatsApp
  • Seorang developer yang berpengalaman dengan OpenGL mempelajari Vulkan untuk pertama kalinya dan dalam sekitar 3 bulan membuat dua demo game kecil serta engine EDBR yang dapat digunakan ulang
  • Alih-alih merancang engine serbaguna sejak awal, ia terlebih dahulu membuat game kecil lalu menjadikan hanya bagian yang diperlukan sebagai engine, sehingga mengurangi desain berlebihan dan bike-shedding
  • Engine berukuran 19k LoC, terdiri dari 6,7k LoC kode grafis dan 2k LoC abstraksi ringan Vulkan, serta mencakup compute skinning, CSM, PBR shading, MSAA, post FX, dan rendering UI
  • vk-bootstrap, Vulkan Memory Allocator, volk, VK_KHR_dynamic_rendering, push constants, buffer device address, dan bindless descriptor digunakan untuk mengurangi boilerplate Vulkan dan penggunaan descriptor set
  • Vulkan menyediakan penghapusan global state, error validasi yang lebih baik, debugging shader RenderDoc, serta konsistensi antar-GPU dan OS, tetapi sinkronisasi eksplisit tetap harus ditangani sendiri

Engine EDBR Berbasis Vulkan yang Dibuat Selama 3 Bulan

  • EDBR (Elias Daler’s Bikeshed Engine) berawal dari proyek belajar Vulkan dan kemudian berkembang menjadi engine kecil yang dapat digunakan ulang pada proyek berikutnya
  • Kode engine dan game dipublikasikan di repositori GitHub
  • Ukuran kode pada saat penulisan adalah sebagai berikut
    • Engine itu sendiri: 19k LoC
      • Kode terkait grafis: 6,7k LoC
      • Abstraksi ringan Vulkan: 2k LoC
    • Game kucing 3D: 4,6k LoC
    • Game platformer 2D: 1,2k LoC
  • Sebagian kode non-grafis seperti pemrosesan input dan sistem audio diambil dari engine sebelumnya, sementara grafis dan beberapa sistem inti ditulis baru
  • Ia menilai bahwa menulis ulang lebih cocok daripada menyisipkan Vulkan ke dalam abstraksi OpenGL yang sudah ada

Urutan Belajar dari Pemrograman Grafis hingga Vulkan

  • Jika baru mulai belajar pemrograman grafis, memulai dengan OpenGL lebih disarankan daripada Vulkan agar tidak mudah kewalahan oleh kompleksitasnya
  • Sebagai target minimum, disarankan mencapai level menampilkan model bertekstur di layar dan mengimplementasikan pencahayaan Blinn-Phong sederhana
  • Shadow mapping dasar membantu mempelajari cara merender scene dari sudut pandang lain ke render target lain, serta cara melakukan sampling depth texture
  • Materi belajar OpenGL yang direkomendasikan adalah sebagai berikut
  • Untuk belajar Vulkan, vkguide adalah yang paling membantu; jika baru memulai, sebaiknya ikuti semuanya, tetapi untuk game sederhana kompleksitas setingkat “GPU driven rendering” mungkin belum diperlukan saat itu juga
  • Vulkan Lecture Series by TU Wien membahas dasar-dasar Vulkan, dan kuliah tentang sinkronisasi khususnya sangat membantu
  • Dari hasil belajar pada bulan pertama, fitur berikut diimplementasikan
    • Loading model glTF
    • Compute skinning
    • Frustum culling
    • Shadow mapping dan cascaded shadow maps

Alasan Memilih Vulkan dan Perbandingannya dengan WebGPU

  • Targetnya adalah game 3D kecil untuk desktop yang berfokus pada Windows dan Linux, dan karena lebih menyukai teknologi open-source serta standar terbuka, pilihannya berada di antara OpenGL dan Vulkan
  • OpenGL cukup untuk game kecil, tetapi kecil kemungkinan ada versi baru dan di macOS statusnya deprecated, sehingga masa depannya dinilai tidak pasti
  • WebGPU juga sempat dipelajari, tetapi memiliki batasan berikut
    • Belum stabil dan belum banyak tutorial maupun contoh
    • Sintaks WGSL terasa kurang disukai dibanding GLSL
    • Di desktop, WebGPU lebih mirip wrapper di atas DirectX, Vulkan, dan Metal, sehingga capture RenderDoc berbeda per platform dan pemanggilan WebGPU tidak berkorespondensi 1:1 dengan pemanggilan API native
    • Tidak ada bindless textures dan push constants
  • WebGPU juga memiliki keunggulan yang jelas
    • Error validasinya lebih baik daripada OpenGL/WebGL dan tidak ada global state
    • Ada bagian yang mirip Vulkan, sehingga membantu sebelum mempelajari Vulkan
    • Boilerplate yang diperlukan untuk menampilkan sesuatu di layar lebih sedikit daripada Vulkan
    • Tidak perlu menangani sinkronisasi eksplisit sendiri
    • Game dapat dijalankan di browser

Alur Rendering Frame

  • Satu frame dibagi menjadi beberapa tahap, dan tiap tahap diimplementasikan dalam bentuk pipeline atau pass
  • Pada tahap skinning, model yang memiliki skeletal animation diproses dengan compute shader
    • Inputnya adalah mesh yang belum di-skinning dan joint matrices
    • Outputnya adalah vertex buffer yang akan digunakan pada tahap rendering berikutnya
    • Pada tahap berikutnya, mesh statis dan skinned mesh dapat diperlakukan secara serupa
  • Pada tahap CSM, cascaded shadow mapping dilakukan menggunakan depth texture 4096x4096 dan 3 slice
  • Pada tahap geometry + shading, model digambar dan shading dilakukan menggunakan shadow map serta informasi light
    • Model PBR hampir sama dengan metode yang dijelaskan dalam Physically Based Rendering in Filament
    • Fragment shader melakukan semua perhitungan light yang memengaruhi mesh tersebut dalam satu draw call
  • Semuanya digambar ke multi-sampled texture lalu di-resolve
  • Depth resolve ditangani secara manual dengan fragment shader
    • Semua fragment pada multi-sample depth texture diiterasi dan nilai minimum dicatat ke non-MS depth texture
  • Tahap post FX saat ini hanya menerapkan depth fog, dan tone mapping serta bloom juga direncanakan akan ditangani pada tahap ini nantinya
  • Pada tahap UI, dialogue UI digambar dan diproses dalam satu draw call

Library untuk Mengurangi Boilerplate Vulkan

  • vk-bootstrap mengurangi boilerplate inisialisasi Vulkan seperti pemilihan physical device dan pembuatan swapchain
    • Ini bukan wrapper untuk seluruh fungsi Vulkan, melainkan terutama memengaruhi tahap inisialisasi
  • Vulkan Memory Allocator membuat alokasi memori Vulkan tidak perlu ditangani secara langsung
  • volk menyederhanakan extension function loading
    • Misalnya, fungsi extension seperti vkSetDebugUtilsObjectNameEXT dapat digunakan tanpa menyimpannya sendiri sebagai pointer
  • Kelas GfxDevice mengelompokkan fitur dan objek Vulkan yang sering dipakai
    • Inisialisasi Vulkan context
    • Pembuatan dan pengelolaan swapchain
    • beginFrame dan endFrame
    • Pembuatan image dan loading texture
    • Pembuatan buffer
    • Pengelolaan bindless descriptor set
  • GfxDevice.cpp berjumlah 714 baris pada saat penulisan, dan memberikan satu objek lebih praktis daripada meneruskan VkDevice, VkQueue, VmaAllocator, dan lainnya ke banyak tempat

Build shader dan strategi menghindari descriptor set

  • Bahasa shader yang dipilih adalah GLSL karena pengalaman dengan OpenGL
  • Shader dikompilasi menjadi SPIR-V pada tahap build, bukan saat runtime
    • Kode pemuatan shader saat runtime menjadi lebih sederhana
    • Tidak ada dependensi pada shader compiler saat runtime
    • Error shader ditemukan pada tahap build
  • glslc dapat menetapkan DEPFILE di CMake, sehingga ketika shader include berubah, file terkait dapat direkompilasi otomatis
  • Di Vulkan, uniform harus dikelompokkan ke dalam descriptor set, sehingga pengiriman data menjadi lebih rumit dibanding OpenGL
  • Dalam implementasi ini, penggunaan descriptor set sangat dikurangi
    • Hanya menggunakan satu descriptor set global untuk bindless texture dan sampler
    • Sisanya sebagian besar dikirim lewat push constants
    • Menggunakan buffer device address untuk meneruskan alamat buffer melalui push constants

Pipeline class dan dynamic rendering

  • Tahap rendering dipisahkan menjadi pipeline class seperti PostFXPipeline
  • Setiap pipeline biasanya memiliki peran berikut
    • init: memuat shader, menginisialisasi VkPipeline dan VkPipelineLayout
    • cleanup: membersihkan pipeline dan layout
    • draw: menerima input yang diperlukan setiap frame dan menjalankan draw call
  • draw diasumsikan dipanggil di antara vkCmdBeginRendering dan vkCmdEndRendering
  • Pipeline tidak memperhatikan texture mana yang menjadi target render dari render pass; pemanggil yang menentukan render target
  • VK_KHR_dynamic_rendering digunakan secara menyeluruh, dan Vulkan render pass serta subpass tidak digunakan
    • Pernah mendengar bahwa pada GPU tile-based, render pass dan subpass lebih efisien, tetapi dukungan mobile saat ini tidak dipertimbangkan
    • Dynamic rendering membuat implementasi jauh lebih mudah

Pemanfaatan PVP, BDA, dan bindless descriptor

  • Semua mesh menggunakan satu tipe vertex
  • Dengan programmable vertex pulling, definisi vertex format seperti VAO di OpenGL atau VkVertexInputBindingDescription, VkVertexInputAttributeDescription di Vulkan dapat dihindari
  • Dengan buffer device address, vertex buffer tidak perlu di-bind ke descriptor set; alamat buffer dapat diteruskan melalui push constants
  • Layout scalar digunakan untuk push constants dan buffer
    • Penanganan alignment lebih mudah dibanding std430, sehingga dapat diperlakukan hampir sama seperti struct C++
    • Kebutuhan anggota padding pada struct C++ berkurang
  • Bindless descriptor digunakan dengan menempatkan array texture dan sampler dalam descriptor set besar
    • Ketika texture baru dimuat, texture dimasukkan ke array textures dan index-nya digunakan sebagai bindless texture id
    • Texture id diteruskan ke shader melalui push constants
  • Sampler dipisahkan dari image; common sampler dibuat saat startup dan dimasukkan ke array samplers
  • Material buffer juga menggunakan bindless texture id
    • Hanya material ID yang diteruskan melalui push constants, lalu fragment shader melakukan lookup ke material buffer
    • Akses texture per material dapat dilakukan hanya dengan satu integer tanpa descriptor set yang besar dan berat
  • Sebagai referensi terkait bindless texture, disarankan membaca Vulkan Bindless Texture

Data dinamis yang diunggah setiap frame

  • Untuk data yang harus dikirim dari CPU ke GPU setiap frame, sebuah array besar dialokasikan di awal, lalu diisi dari index 0 pada setiap frame
  • Misalnya, semua joint matrix disimpan dalam satu array mat4 besar, dan untuk setiap skinned mesh, index awal diteruskan melalui push constants
  • Ada dua pendekatan
    • Menyediakan N buffer di GPU dan menggantinya untuk setiap frame-in-flight
    • Hanya menyediakan satu buffer di GPU dan menyediakan N staging buffer di sisi CPU
  • Dalam sebagian besar kasus, pendekatan pertama direkomendasikan
    • Memakai lebih banyak memori GPU, tetapi tidak membutuhkan sinkronisasi manual
  • Jika perlu menghemat memori GPU, pendekatan kedua bisa berguna
    • Tidak ditemukan perbedaan performa yang mencolok di antara keduanya, tetapi perbedaan dapat muncul jika mengunggah data yang sangat besar setiap frame

Cleanup dan sinkronisasi

  • Pola deletion queue dari vkguide tidak terlalu berguna di engine penulis
    • Karena tidak mengalokasikan atau menghancurkan objek Vulkan baru setiap frame
  • Cleanup berbasis destructor C++ juga tidak nyaman
    • Kompleksitas meningkat karena membutuhkan wrapper class, move constructor, dan move assignment
    • Ada risiko objek yang sedang digunakan di tengah frame tidak sengaja dihapus akibat wrapper dihancurkan
  • Saat ini, objek Vulkan dibersihkan di satu tempat dengan memanggil fungsi cleanup secara eksplisit
    • Pemanggilannya mudah terlupa, tetapi saat program ditutup, Vulkan validation error dan VMA assert memberi tahu cleanup yang terlewat
  • Synchronization di Vulkan sulit dan harus dikelola secara eksplisit
    • OpenGL dan WebGPU menangani sinkronisasi yang diperlukan untuk membaca texture atau buffer
    • Di Vulkan, barrier harus dipasang sendiri untuk menghindari data race
  • Saat ini, draw dibagi menjadi pass dan pipeline, lalu barrier disisipkan manual di antaranya
    • Misalnya, setelah skinning pass di compute shader menulis vertex data, barrier dipasang sebelum shadow mapping pass membacanya
  • Ini dapat diotomatisasi dengan render graph, tetapi saat ini sinkronisasi manual sudah dirasa memadai
  • Synchronization validation layer di vkconfig membantu menemukan error sinkronisasi

Sprite, skinning, pemisahan game/renderer

  • Dengan bindless texture, banyak sprite mudah digambar dalam satu draw call tanpa vertex buffer
  • Vertex shader sprite menghasilkan vertex coordinate dan UV quad menggunakan gl_VertexIndex
  • Semua draw call sprite dikumpulkan sebagai SpriteDrawCommand di SpriteDrawBuffer
    • transform
    • rentang UV
    • color
    • texture ID
    • shader ID
  • Draw call sebenarnya berbentuk vkCmdDraw(cmd, 6, spriteDrawCommands.size(), 0, 0)
    • 6 vertex per sprite
    • instance sebanyak jumlah sprite
  • Sprite renderer dapat menggambar 10 ribu sprite dalam 315 mikrodetik
  • Compute skinning menerima input vertex dan joint matrices dari mesh yang memiliki skeletal animation, lalu menghasilkan skinned vertex buffer
    • Tiga kucing dengan mesh yang sama pun dapat memiliki animation yang berbeda
    • Output vertex buffer diperlukan untuk setiap instance mesh
  • Game logic dan renderer dipisahkan melalui draw command
    • Game logic menggunakan entt
    • Renderer tidak mengetahui entity atau game object, dan hanya menangani light, scene parameter, serta mesh draw command
  • MeshDrawCommand mencakup meshId, transform matrix, bounding sphere, pointer skinned mesh, index awal joint matrix, dan apakah ia melakukan shadow casting

loading scene, UI, Dear ImGui

  • Level editor tidak dibuat sendiri, melainkan menggunakan Blender dan mengekspornya sebagai glTF
    • Membuat level editor sendiri bisa memakan waktu berbulan-bulan atau bertahun-tahun, jadi ini menghemat waktu
  • Pembuatan prefab dan physics shape ditentukan lewat nama node
    • Contoh: pada Interact.Sphere.Diary, Interact sebelum titik pertama adalah nama prefab
    • Sphere digunakan di physics system saat membuat sphere physics body
    • Capsule dan Box juga bisa digunakan; jika tidak ada, physics shape dibuat dari vertex mesh
  • Model yang kompleks tidak dimasukkan langsung ke level glTF, tetapi ditempatkan sebagai object Empty->Arrows lalu diberi nama seperti Cat.NearStore
    • Membuat prefab Cat dan menambahkan tag NearStore
  • Prefab ditulis dalam JSON dan dapat berisi glTF eksternal serta informasi movement dan physics
  • Sistem UI terinspirasi dari Roblox UI API
    • origin
    • relative size
    • relative position
    • offsetPosition, offsetSize
    • fixed size
    • size berbasis konten label/image
  • Setelah size elemen UI dihitung secara rekursif, position dihitung, lalu digambar berurutan dari parent ke children
  • Dear ImGui digunakan untuk tool pengembangan dan debug
  • Ada masalah Dear ImGui terlihat salah pada sRGB framebuffer, sehingga dibuat backend Dear ImGui sendiri
    • Hanya bagian rendering yang ditulis sendiri, sementara logic/interaksi OS seperti pemrosesan input event dan clipboard ditangani oleh backend SDL bawaan Dear ImGui
  • Keunggulan backend sendiri adalah sebagai berikut
    • Mendukung bindless texture id sehingga image bisa digambar dengan ImGui::Image(bindlessTextureId, ...)
    • Dapat menggambar linear image dan non-linear image dengan benar melalui penyampaian format
    • Dapat diinisialisasi dan ditangani dengan cara yang sama seperti kode Vulkan lain di engine

Library yang digunakan dan efek transisi ke Vulkan

  • Untuk physics digunakan Jolt Physics
    • Terutama digunakan untuk collision resolution dan basic character movement
    • JPH::CharacterVirtual menangani basic character movement dengan baik
  • Untuk ECS digunakan entt
    • Menggunakan library eksternal alih-alih ECS buatan sendiri untuk mengurangi kode yang perlu dipelihara
  • Untuk audio digunakan openal-soft, libogg, dan libvorbis
  • Untuk profiling digunakan Tracy
    • Membantu menghindari bike-shedding yang tidak perlu dengan memeriksa kode mana yang sebenarnya menggunakan waktu sangat sedikit
  • Hal-hal yang diperoleh dari transisi ke Vulkan adalah sebagai berikut
    • Tidak adanya global state OpenGL membuat abstraksi lebih mudah
    • Kebutuhan akan pola ala OpenGL seperti shader.bind(), state tracker, dan magic RAII berkurang
    • Validation error lebih kaya dibanding OpenGL
    • Di RenderDoc, vertex shader dan fragment shader dapat di-debug secara langsung
    • Perbedaan perilaku antara GPU dan OS tidak semenonjol OpenGL
    • Ke depannya dapat mengeksplorasi shading language lain seperti Slang dan Shady
    • Dapat mengendalikan tiap aspek graphics pipeline secara lebih besar

Pekerjaan ke depan

  • Pekerjaan yang direncanakan ke depan adalah sebagai berikut
    • dukungan font sign-distance field
    • loading banyak image dan pembuatan mipmap secara paralel
    • bloom
    • volumetric fog
    • animation blending
    • render graph
    • ambient occlusion
    • menyelesaikan game
  • Mempelajari Vulkan memang sulit, tetapi tidak sesulit yang dibayangkan, dan menjadi kesempatan untuk memahami graphics programming serta modern API secara lebih mendalam

1 komentar

 
GN⁺ 2024-06-07
Komentar Hacker News
  • Minimalisme sangat efektif
    Saya malah mengambil arah sebaliknya, dan karena itu sedang mengalami banyak penderitaan. Saya sedang membuat klien metaverse dengan Rust, dan bahkan sekarang di layar lain sedang ditampilkan adegan avatar melewati kota steampunk raksasa dengan trem. Saya menjalankannya selama 12 jam tiap kali sebelum merilis pre-release baru
    Di atas Vulkan saya menumpuk WGPU dan Rend3, dan Rend3 menyediakan API yang rapi: jika Anda membuat mesh, tekstur 2D, dan objek, maka objek itu mereferensikan mesh dan tekstur lalu tampil di layar. Reference counting di Rust menyambungkan semuanya dengan benar, jadi cukup intuitif untuk digunakan
    Namun, saat lapisannya makin banyak, masalah mulai muncul. WGPU berusaha mendukung web browser, Vulkan, Metal, DX11 (baru-baru ini dihapus), DX12, Android, hingga OpenGL, jadi butuh tim pengembang besar dan sulit diubah. API WGPU sendiri juga pada dasarnya mirip Vulkan, sehingga kita tetap harus memikirkan sendiri alokasi memori GPU dan sinkronisasi
    WGPU punya masalah penyebut umum terendah. Beberapa platform tidak mendukung fitur tertentu, dan pembaruan memori GPU simultan dari beberapa thread yang didukung Vulkan tidak bisa ditangani WGPU tanpa saling mengganggu. Untuk game atau klien yang menangani dunia besar, fitur ini diperlukan agar konten bisa dimasukkan ke GPU tanpa membunuh frame rate. Karena batasan konkurensi tiap platform berbeda, performa juga bisa turun tajam akibat konflik lock
    Rend3 dimaksudkan menjadi semacam glue code yang cukup baik untuk menangani sinkronisasi dan alokasi, tetapi terutama sinkronisasi sangat sulit diselesaikan secara umum. Frustum culling memberi peningkatan performa besar, tetapi occlusion culling justru merugikan karena biaya komputasinya. Penanganan semi-transparansi juga merepotkan karena perlu depth sorting. Karena saya menangani dunia yang penuh jendela, objek semi-transparan yang memungkinkan kita melihat ke dalam dari luar itu mutlak diperlukan
    Orang-orang di ekosistem Rust 3D tampaknya kesal karena selama 3 tahun saya terus menekan mereka untuk memperbaiki stack ini. Mereka semua sukarelawan, dan Vulkan tetap terawat karena punya dana dan basis pengguna. Pembuat Rend3 baru-baru ini menyerah, jadi sekarang saya harus masuk ke dalam dan memperbaikinya sendiri. Jarang ada orang yang membuat hal kompleks dengan WGPU; kebanyakan hanya game 2D yang bahkan bisa dibuat dengan Flash, atau adegan 3D statis yang sederhana. Proyek komersial tetap memakai Unity atau UE5
    Sekalipun langsung memakai Vulkan, itu tetap lompatan besar karena saya harus menulis sendiri sinkronisasi, alokasi, frustum culling, dan penanganan semi-transparansi
    Tambahan lagi, Vulkano, pembungkus di atas Vulkan dan Metal, juga punya masalah penyebut umum terendah. Vulkan dan Metal sama-sama mendukung pembaruan simultan atas aset GPU, tetapi Vulkano tidak mengizinkannya. Tentu saja Apple juga menanganinya dengan cara berbeda

    • WGPU menggunakan WebGPU, dan setahu saya belum ada browser yang mendukung thread
      https://gpuweb.github.io/gpuweb/explainer/#multithreading
      https://github.com/gpuweb/gpuweb/issues/354
      OpenGL juga tidak pernah mendukung thread, jadi kalau memakai OpenGL memang tidak mungkin
    • Bagian depan benar, tetapi bagian belakang tidak. Alokasi dan sinkronisasi ditangani secara otomatis
    • Karena Metal lebih tua daripada Vulkan, secara teknis justru Vulkan yang melakukannya dengan cara berbeda
    • Fakta bahwa WGPU tidak bisa memperbarui memori GPU dari beberapa thread tanpa saling mengganggu adalah inti yang benar-benar ingin saya pahami demi menciptakan pengalaman yang baik. Saya harap WGPU bisa menemukan cara untuk menambahkan fitur ini, setidaknya sebagai ekstensi
    • Tulisan yang dulu Anda unggah di URLO juga sangat menarik untuk dibaca: https://users.rust-lang.org/t/game-dev-in-rust-some-notes-on...
  • Sedikit lebih dari setahun lalu saya mencoba mempelajari Vulkan, tetapi rasanya tidak ingin menyentuhnya lagi. Sangat menyebalkan mengganti OpenGL yang dihentikan dengan sesuatu yang membuat tugas sederhana pun jadi sangat sulit. Misalnya, hanya untuk satu kubus berputar saja perlu ratusan baris kode
    OpenGL juga tidak mudah, tetapi setidaknya orang biasa masih bisa mempelajari dasarnya dalam waktu yang relatif singkat. Dulu kita bisa membeli buku pengantar pemrograman grafis di toko buku besar dan dalam satu-dua sore sudah bisa menampilkan rendering dasar. Mungkin Vulkan lebih baik dalam beberapa hal, tetapi tidak realistis berharap bisa mempelajarinya dengan cepat
    Bayangkan jika saat chip Intel/ARM/AMD terbaru keluar, mereka berkata kita tidak bisa lagi memakai C atau C++ dan “dukungan bahasa tingkat tinggi dihentikan, sekarang pakailah assembly saja, kontrolnya lebih besar jadi lebih cepat”, tentu itu akan dianggap omong kosong

    • Sepengetahuan saya, OpenGL hanya dihentikan di macOS dan akan tetap ada untuk waktu yang lama
      Vulkan sering disebut lebih dekat ke “API GPU” daripada API grafis tingkat tinggi. Jika dilihat seperti itu, kompleksitasnya tidak mengejutkan, dan domainnya sendiri memang sulit
      Daripada dianalogikan sebagai menghapus bahasa tingkat tinggi di CPU modern lalu menyuruh semua orang menulis assembly, ini lebih mirip perbedaan antara C/C++ single-threaded dan C/C++ multi-threaded. Kompleksitasnya naik tajam dan jika tidak paham bisa kacau atau performanya malah lebih buruk, tetapi itu juga jalur praktis ke depan
      OpenGL pada umumnya bisa diimplementasikan di atas Vulkan. Memang disayangkan standar OpenGL tidak lagi dikembangkan dengan aktif, tetapi tidak ada yang abadi
    • Fakta bahwa satu kubus berputar butuh ratusan baris tidak terlalu berarti. Menambahkan kubus kedua atau bentuk lain tidak membuat jumlah baris menjadi dua kali lipat
      Masalahnya adalah OpenGL sudah tidak terlalu cocok dengan hardware saat ini, sehingga jika dipakai secara naif performanya bisa sangat tidak efisien. Bahkan di OpenGL, kalau sudah sampai ke teknik untuk menghilangkan beban driver, Vulkan sebenarnya tidak jauh lebih sulit
      Chip Intel/ARM/AMD saat ini juga tidak mendukung C atau C++ secara langsung. Kita memang sudah memakai assembly, atau memakai alat pihak ketiga yang menerjemahkan dari C/C++. Tujuan Vulkan juga adalah menyediakan antarmuka standar tingkat rendah untuk GPU, lalu membiarkan abstraksi yang lebih nyaman dipakai dibangun di atasnya
    • Vulkan adalah target untuk menulis library seperti OpenGL. Keuntungannya adalah banyak kode tingkat library berpindah dari driver perangkat yang buram dan penuh bug ke library user-space
    • Jika ingin OpenGL, pakai saja ANGLE
      https://github.com/google/angle
      Banyak ponsel sekarang memakai ANGLE sebagai satu-satunya dukungan OpenGL di atas driver Vulkan
      Jika ingin API modern yang relatif mudah dan portabel, gunakan WebGPU melalui wgpu di Rust atau dawn di C++
  • Saya pikir Vulkan itu hebat, tetapi tujuannya adalah memanfaatkan fitur GPU tingkat lanjut semaksimal mungkin. Saat memakai fitur GPU tingkat lanjut, performanya juga bisa lebih baik daripada OpenGL
    Jika tidak menargetkan teknik rendering tingkat lanjut, biasanya OpenGL terasa sebagai jalur yang lebih direkomendasikan
    Bahkan sekarang masih banyak game dengan grafis 2D, low-poly, atau bergaya PS1, dan game seperti ini tidak perlu memakai Vulkan
    Vulkan adalah contoh bagaimana industri game AAA condong ke kualitas rendering dan tampilan visual. Studio AAA membenarkan anggaran mereka dengan engine dan konten yang sangat canggih, tetapi pemain mulai lelah dan sadar bahwa mereka menginginkan gameplay lebih dari sekadar grafis, sehingga pasar game 2D/low-poly terus tumbuh
    Jika Anda pengembang game, kemungkinan besar Anda ingin fokus pada gameplay dan fitur daripada kualitas rendering

    • Vulkan punya keunggulan dibanding OpenGL bahkan jika Anda tidak peduli pada fidelitas visual
      Tidak ada state global, GPU yang dipakai bisa dipilih saat runtime, dan penanganan error OpenGL itu mengerikan. Ada validation layers, artwork resminya juga keren, dan dokumentasinya bagus. Mengunggah data ke GPU secara asinkron dari thread kedua juga lebih masuk akal, dan ada fitur GPU tingkat lanjut seperti mesh shader atau RTX
    • Pendorongnya di sini tampaknya lebih dekat ke realitas finansial pengembangan game. Grafis fidelitas tinggi sangat mahal, dan studio game kecil sulit membuatnya dengan jadwal dan anggaran yang realistis
      Apakah konsumen akan menolak game indie dengan grafis setara AAA? Mungkin tidak. Alasan game seperti itu sedikit adalah karena secara finansial sulit diwujudkan, dan pasar yang mau menerima grafis yang lebih distilisasi dan lebih rendah fidelitasnya sudah cukup besar
    • Titik tengahnya adalah WebGPU. Ia jauh kurang bertele-tele dibanding Vulkan dan ada jaminan bisa berjalan di mana saja, termasuk di browser
      Pada saat yang sama, Anda mendapat akses ke fitur modern seperti compute shader yang tidak tersedia di WebGL, dan tidak terlalu banyak warisan lama seperti OpenGL yang menumpuk dengan banyak cara berbeda untuk melakukan hal yang sama. Keunggulan utamanya adalah karena ini baru, tetapi akibatnya tutorialnya juga jauh lebih sedikit, dan itu kekurangan yang cukup serius
    • OpenGL sedang meredup, hampir tidak ada yang benar-benar peduli pada Metal, dan orang yang secara sukarela memakai Vulkan hanyalah para penyihir kode berjanggut yang beristirahat dari menulis kernel di akhir pekan
      Framework dan engine jumlahnya tak ada habisnya, tetapi kebanyakan belum matang, dan semuanya punya opini kuat soal struktur kode. Semacam, “buat scene tree yang berisi callback. Tidak, tulis entity dan component class dan object. Tunggu, sekarang semuanya immediate mode, fungsional, dan stateless”
      Lalu di atas itu ada lagi kekacauan platform. Jika ditambah push notification game mobile, pembelian dalam aplikasi, sampai penandatanganan wajib lewat Xcode, semuanya benar-benar berantakan. Ada alasan Unity punya pangsa pasar besar, dan itu bukan karena software-nya hebat. Alasannya, pengembangan lintas platform yang lebih dari sekadar webview mewah masih sangat menyakitkan
    • Ini bukan cuma soal performa. Salah satu alasan meninggalkan OpenGL adalah karena fitur tingkat tinggi tidak konsisten antara implementasi driver atau antar-GPU
      Jika Anda tetap harus terus menulis kode khusus untuk tiap GPU/driver, maka lapisan abstraksi itu tidak ada gunanya. Lebih baik memisahkan driver tingkat rendah dan library tingkat tinggi
  • Tulisan ini punya banyak nasihat bagus. Yang paling menonjol terutama adalah “jangan implementasikan jika belum dibutuhkan saat ini”
    Ini adalah hal yang terus saya perdebatkan dengan programmer junior yang sudah punya beberapa tahun pengalaman tetapi masih dalam proses berkembang. Mereka sering terobsesi dengan “praktik terbaik” dan alat baru yang sedang tren dan terlihat mewah, tetapi kesulitan memulai dari masalah yang benar-benar harus diselesaikan lalu fokus pada hal minimum yang diperlukan untuk menyelesaikannya

    • Ini dalam konteks tim satu orang. Hal itu menjadi jelas dari nasihat berikutnya: “ingatlah bahwa bagian mana pun dari game/engine bisa ditulis ulang nanti”
      Di organisasi menengah hingga besar, biasanya tidak begitu. Umumnya kita lanjut ke pekerjaan berikutnya dan hampir tidak pernah punya waktu untuk menengoknya lagi. Sayangnya, karena itu kita harus membuatnya dengan benar sejak awal dan mengurangi ruang bagi bug atau efek samping
      Terlalu banyak codebase yang berubah menjadi ladang ranjau karena fitur baru dikebut, sampai perubahan kecil pun menuntut pengecekan manual terhadap semua fungsi dan kita harus menahan seluruh konteks aplikasi di kepala
    • Overengineering terus menjadi sumber masalah. Pada akhirnya ini menambah banyak kompleksitas tanpa manfaat yang benar-benar berarti
      Dalam pengalaman saya, ketika nanti ada kebutuhan baru, solusi generik yang dibuat sebelumnya biasanya ternyata sama sekali tidak cocok sehingga tetap harus dibuat ulang. Yang harus diselesaikan adalah masalah di depan mata, bukan masalah masa depan yang belum diketahui
    • Cukup beri tahu mereka bahwa YAGNI juga merupakan praktik terbaik :D
    • Jika prinsip ini diterapkan secara ketat, pekerjaan bisa jadi sebagian besar hanya berkutat pada menyambungkan berbagai API dan layanan, lalu menyesuaikan software yang sudah ditulis agar cocok dengan kebutuhan perusahaan
      Jadi tidak mengherankan kalau orang ingin sesekali menulis sesuatu sendiri di mana pun ada kesempatan. Pekerjaan serabutan seperti tukang ledeng digital ini melelahkan, jadi agar tetap waras sedikit lebih lama, rasanya wajar ingin sesekali menyelipkan pekerjaan yang lebih menyenangkan
    • “jangan implementasikan jika belum dibutuhkan saat ini” adalah nasihat yang berguna saat memulai. Hanya saja, ini juga aturan yang boleh dilanggar oleh ahli
  • Sepertinya situsnya tumbang karena lonjakan trafik, jadi saya simpan versi cache-nya: https://web.archive.org/web/20240606103630/https://edw.is/le...

  • Tulisan yang luar biasa. Saya belajar Vulkan sendiri untuk membuat engine visualisasi data ilmiah: https://datoviz.org/ yang masih cukup eksperimental dan versi barunya akan segera dirilis
    Saya sebelumnya sudah punya pengetahuan OpenGL, tetapi mempelajari Vulkan benar-benar sulit. Lima tahun lalu pun materi belajarnya tidak terlalu bagus. Meski begitu saya tetap mencobanya dan itu sangat menyenangkan
    Butuh beberapa bulan untuk memahami peran dari puluhan abstraksi yang ada, dan selama proses itu saya juga menulis wrapper kecil agar Vulkan sedikit lebih tidak menyakitkan untuk digunakan: https://datoviz.org/api/vklite/
    Wrapper ini hanya mendukung sebagian fitur yang paling dibutuhkan untuk visualisasi ilmiah

  • Mendapatkan performa yang lebih baik dengan Vulkan dibanding OpenGL bukan hal yang sederhana. Di driver Vulkan tidak ada sekitar 20 ribu baris kode yang pada driver OpenGL menangani pengaturan rendering pipeline dan render target untuk Anda
    Kode itu sudah ada di dalam driver OpenGL, dan telah dioptimalkan selama lebih dari 20 tahun oleh orang-orang terbaik di industri
    Jadi, jika Anda secara naif merakit sesuatu di atas Vulkan yang setara dengan fungsi bawaan OpenGL, performa bagus tidak akan muncul secara ajaib. Masih perlu lebih banyak kerja, dan masalah nyata mulai menumpuk, seperti memasang fence dan primitive sinkronisasi yang benar
    Anda baru bisa berharap pada keunggulan performa Vulkan ketika benar-benar tahu apa yang sedang dilakukan, dan bisa menjalankan rendering dengan paralelisme yang baik serta sinkronisasi yang benar
    Sebagai pengembang hobi, saya memakai OpenGL ES3 karena kesederhanaannya. Bagi saya itu sudah lebih dari cukup, dan ada banyak hal yang lebih mendesak daripada menulis descriptor descriptor descriptor vertex Vulkan yang menjengkelkan
    Sebagai referensi, engine saya ada di sini: https://github.com/ensisoft/detonator

    • Saya juga pernah mendengar hal yang sama tentang DirectX 12 dan DirectX 11. Dalam sebuah buku dikatakan bahwa jika Anda tidak benar-benar tahu apa yang Anda lakukan, besar kemungkinan performa DirectX 12 justru lebih buruk daripada DirectX 11
    • Bagian terbesar bagi saya adalah shader compiler. OpenGL sudah menyediakannya secara bawaan, tetapi Vulkan mengharuskan saya menarik dependensi tambahan lagi
      Saya dengar Vulkan sekarang sudah mengizinkan bindless texture, jadi kekacauan descriptor tampaknya tidak semenakutkan dulu
      Vulkan memang menarik, tetapi ada biaya awal yang tinggi yang tidak ingin saya bayar
    • Tidak bisakah dibuat lapisan OpenGL-ke-Vulkan sebagai library, sehingga tetap menargetkan Vulkan tetapi bisa digunakan pada tingkat abstraksi yang lebih tinggi?
      Setelah itu, library tersebut bisa diganti secara bertahap dengan rutin yang dioptimalkan sesuai use case
  • Bertambahnya materi Vulkan tentu bagus, tetapi ini juga mengalami masalah yang sama yang selalu terlihat pada semua materi Vulkan tentang menampilkan sesuatu di layar
    Semuanya memperkenalkan lapisan abstraksi lain di atas Vulkan bahkan sebelum menunjukkan kasus yang sederhana. Selalu disuruh memakai vk-bootstrap, volk, vma, atau library lain
    Saya bahkan tidak tahu apakah ada satu pun materi yang menunjukkan contoh manajemen memori secara manual. Sepertinya pilihannya hanya memakai vma atau menggali spesifikasinya sendiri. Apakah meminta contoh paling dasar tanpa menambahkan library apa pun selain Vulkan SDK itu sendiri benar-benar berlebihan?

    • Dalam pengembangan game, biasanya ada praktik mengalokasikan bongkahan memori besar di awal lalu memakai bump allocator di dalamnya
      Kebanyakan game kira-kira punya tiga jenis masa hidup: permanen/saat startup, per level, dan per frame
      Masa hidup ini saling bertumpuk, jadi dengan satu stack allocator saja kita bisa melangkah cukup jauh. Saat frame atau level selesai, cukup kembali ke posisi awal
      Ada pola yang lebih rumit, tetapi cara ini saja sudah cukup berguna, dan bisa dipakai baik di CPU maupun GPU
    • Vulkan sejak awal memang dirancang sebagai API yang sangat low-level, dan ada asumsi bahwa perlu library untuk menaikkannya hingga setara OpenGL/DX11
      Dari sudut pandang itu, pemakaian library di atasnya secara luas adalah hal yang sangat normal. Sama seperti software modern yang tidak ditulis langsung terhadap system call
    • Betul, itu tuntutan yang terlalu besar. Bahkan contoh “resmi” dalam dokumentasi pun tidak melakukannya. Membaca spesifikasi Vulkan rasanya lebih seperti latihan menahan omong kosong teknis
      Saat seseorang membandingkan panduan inisialisasi Vulkan buatan orang lain dengan kode di repositori Khronos Group, lalu membaca spesifikasi Vulkan 1.3, dan menyadari bahwa untuk melakukan sesuatu spesifikasinya harus dibaca campur aduk, bukan berurutan, saat itulah jelas mereka sudah gagal
      Mereka gagal. Dilihat dari standar lain pun itu pekerjaan yang buruk. Hanya saja, karena cukup dilakukan sekali lalu sebagian besar bisa dilupakan, para ahli tidak terlalu banyak mengeluh
      Di komentar lain dalam thread ini saya meninggalkan sebagian source code dengan bab dan pasal spesifikasi diberi anotasi sebagai komentar. Itu implementasi umum yang bisa dipakai bersama SDL dan semacamnya
      Pendekatan standar saat ini, pada saat penulisan, adalah memakai VMA dan Volk yang disertakan dalam Vulkan SDK resmi. Itu saja sudah cukup mengatakan banyak hal tentang kondisinya sekarang
  • Selama beberapa tahun saya mencoba belajar Vulkan secara selingan. Dulu saya cukup paham OpenGL ES 2 dan 3
    Salah satu hal yang sulit adalah memahami bagaimana memakainya dalam engine sungguhan, bukan sekadar sampel. Banyak sampel hanya mengalokasikan persis sebanyak yang dibutuhkan, atau mengalokasikan ratusan agar tidak akan pernah kekurangan
    Waktu belajar DirectX, MiniEngine dari Microsoft cukup membantu. Tidak terlalu rumit, tetapi punya hal-hal seperti DescriptorAllocator yang mengelola alokasi descriptor. Saya penasaran apakah ada sesuatu yang mirip untuk Vulkan
    Hal sulit lainnya adalah mengetahui cara membuat abstraksi yang baik untuk material, mesh, urutan rendering, dan sebagainya. Apakah ada engine atau framework bagus yang layak dipelajari untuk melampaui tutorial?

    • Vulkan cukup mirip dengan DirectX 12, jadi beberapa konsep bisa dipindahkan begitu saja. Untuk alokasi memori, library VMA akan membantu. Ia menangani beberapa kasus pengecualian menyebalkan yang menumpuk selama bertahun-tahun dalam standar, dan cukup kuat
      Untuk alokasi descriptor set, bagi saya hanya ada satu pola yang masuk akal. Harus diasumsikan bahwa pool berumur pendek dan jumlahnya akan banyak. Jika alokasi dari pool saat ini gagal, buat pool baru, alih-alih memelihara sendiri penghitung jumlah descriptor. Standarnya mengizinkan berbagai perilaku pool yang tidak sesederhana perhitungan biasa. Pool lama tinggal dibuang setelah command buffer terakhir yang merujuknya selesai
      Pipeline barrier dan image layout memang benar-benar merepotkan. Sebaiknya diabstraksikan sebagai lapisan yang melacak penggunaan terakhir dan format terakhir semua resource lalu menambahkan barrier yang diperlukan. Ini bisa menjadi rumit, tetapi akan sepadan ketika muncul situasi yang lebih kompleks, misalnya ada pass opsional atau urutan pass bisa diubah
      Mesh, material, dan urutan rendering terlalu sulit diringkas dalam satu komentar HN, dan sangat dipengaruhi oleh pilihan algoritme rendering. Menurut saya upaya besar yang dibutuhkan untuk membuat solusi yang sangat umum dengan benar tidak sepadan
    • Jika ingin melihat engine nyata, sesuatu seperti vkQuake adalah referensi yang bagus: https://github.com/Novum/vkQuake
  • Jika Anda pembaca santai yang penasaran apa saja yang dibutuhkan untuk menulis “Hello, Triangle!” di Vulkan 1.3, lihat ini: https://github.com/Planimeter/game-engine-3d/blob/main/src/g...

    • Betul. Meski begitu, dengan vk-bootstrap kodenya sedikit lebih baik, sekitar 600 baris: https://github.com/charles-lunarg/vk-bootstrap/blob/main/exa...
      Inisialisasi Vulkan dan pengelolaan swapchain dasar memang sangat bertele-tele, tetapi setelah dicoba sekali dan nanti dibuat abstraksi yang nyaman untuk pembuatan serta pengelolaan pipeline, semuanya akan terasa jauh lebih baik