paint-brush
WebGL'den WebGPU'ya geçişby@dmitrii
6,933
6,933

WebGL'den WebGPU'ya geçiş

Bu kılavuz, temel farklılıkları, üst düzey kavramları ve pratik ipuçlarını kapsayarak WebGL'den WebGPU'ya geçişi açıklamaktadır. WebGPU, web grafiklerinin geleceği olarak ortaya çıktığından, bu makale hem yazılım mühendisleri hem de proje yöneticileri için paha biçilmez bilgiler sunmaktadır.
featured image - WebGL'den WebGPU'ya geçiş
Dmitrii Ivashchenko HackerNoon profile picture

Yaklaşan WebGPU'ya geçmek, grafik API'lerini değiştirmekten daha fazlası anlamına gelir. Bu aynı zamanda web grafiklerinin geleceğine doğru atılmış bir adımdır. Ancak bu geçiş, hazırlık ve anlayışla daha iyi sonuçlanacaktır ve bu makale sizi buna hazırlayacaktır.


Herkese merhaba, adım Dmitrii Ivashchenko ve MY.GAMES'te yazılım mühendisiyim. Bu makalede WebGL ile gelecek WebGPU arasındaki farkları tartışacağız ve projenizi geçişe nasıl hazırlayacağınızı anlatacağız.


İçeriğe Genel Bakış

  1. WebGL ve WebGPU'nun Zaman Çizelgesi

  2. WebGPU'nun mevcut durumu ve gelecek

  3. Üst Düzey Kavramsal Farklılıklar

  4. Başlatma

    • WebGL: Bağlam Modeli

    • WebGPU: Cihaz Modeli

  5. Programlar ve İşlem Hatları

    • WebGL: Program

    • WebGPU: İşlem Hattı

  6. Üniformalar

    • WebGL 1'deki üniformalar

    • WebGL 2'deki üniformalar

    • WebGPU'daki üniformalar

  7. Gölgelendiriciler

    • Gölgelendirici Dili: GLSL ve WGSL

    • Veri Türlerinin Karşılaştırılması

    • Yapılar

    • İşlev Bildirimleri

    • Yerleşik işlevler

    • Gölgelendirici Dönüşümü

  8. Konvansiyon Farklılıkları

  9. Dokular

    • Görünüm Alanı

    • Klip Alanları

  10. WebGPU İpuçları ve Püf Noktaları

    • Kullandığınız boru hattı sayısını en aza indirin.

    • Önceden ardışık düzenler oluşturun

    • RenderBundles'ı kullanın

  11. Özet


WebGL ve WebGPU'nun Zaman Çizelgesi

WebGL'nin diğer birçok web teknolojisi gibi kökleri oldukça eskilere dayanmaktadır. WebGPU'ya geçişin ardındaki dinamikleri ve motivasyonu anlamak için öncelikle WebGL geliştirme geçmişine hızlıca göz atmak faydalı olacaktır:


  • OpenGL masaüstü (1993) OpenGL'nin masaüstü sürümü çıktı.
  • WebGL 1.0 (2011) : Bu, 2007'de tanıtılan OpenGL ES 2.0'ı temel alan WebGL'nin ilk kararlı sürümüydü. Web geliştiricilerine, ek eklentilere ihtiyaç duymadan, 3D grafikleri doğrudan tarayıcılarda kullanma olanağı sağladı.
  • WebGL 2.0 (2017) : İlk sürümden altı yıl sonra tanıtılan WebGL 2.0, OpenGL ES 3.0 (2012) temel alınarak geliştirildi. Bu sürüm, web'deki 3D grafikleri daha da güçlü hale getiren bir dizi iyileştirme ve yeni özelliği beraberinde getirdi.


Son yıllarda geliştiricilere daha fazla kontrol ve esneklik sağlayan yeni grafik API'lerine olan ilgide artış yaşandı:


  • Vulkan (2016) : Khronos grubu tarafından oluşturulan bu platformlar arası API, OpenGL'nin "halefidir". Vulkan, grafik donanımı kaynaklarına daha düşük düzeyde erişim sağlayarak, grafik donanımı üzerinde daha iyi kontrole sahip yüksek performanslı uygulamalara olanak tanır.
  • D3D12 (2015) : Bu API, Microsoft tarafından oluşturulmuştur ve yalnızca Windows ve Xbox içindir. D3D12, D3D10/11'in halefidir ve geliştiricilere grafik kaynakları üzerinde daha derin kontrol sağlar.
  • Metal (2014) : Apple tarafından oluşturulan Metal, Apple cihazları için özel bir API'dir. Apple donanımında maksimum performans göz önünde bulundurularak tasarlandı.


WebGPU'nun mevcut durumu ve gelecek

Bugün WebGPU, sürüm 113'ten başlayarak Google Chrome ve Microsoft Edge tarayıcıları aracılığıyla Windows, Mac ve ChromeOS gibi birden fazla platformda mevcuttur. Yakın gelecekte Linux ve Android desteği bekleniyor.


WebGPU'yu halihazırda destekleyen (veya deneysel destek sunan) motorlardan bazıları şunlardır:


  • Babylon JS : WebGPU için tam destek.
  • ThreeJS : Şu anda deneysel destek.
  • PlayCanvas : Geliştirme aşamasında, ancak çok umut verici beklentilere sahip.
  • Unity : Çok erken ve deneysel WebGPU desteği 2023.2 alfa sürümünde duyuruldu.
  • Cocos Creator 3.6.2 : Resmi olarak WebGPU'yu destekliyor ve bu da onu bu alandaki öncülerden biri yapıyor.
  • Construct : şu anda yalnızca Windows, macOS ve ChromeOS için v113+ sürümünde desteklenmektedir.



Bunu göz önüne aldığımızda WebGPU'ya geçiş ya da en azından böyle bir geçişe yönelik projeler hazırlamak yakın gelecekte atılmış bir adım gibi görünüyor.


Üst Düzey Kavramsal Farklılıklar

Şimdi uzaklaştıralım ve başlatmadan başlayarak WebGL ile WebGPU arasındaki bazı üst düzey kavramsal farklılıklara bir göz atalım.

Başlatma

Grafik API'leriyle çalışmaya başladığınızda ilk adımlardan biri, ana nesneyi etkileşim için başlatmaktır. Bu süreç, her iki sistem için de bazı özelliklerle birlikte WebGL ve WebGPU arasında farklılık gösterir.

WebGL: Bağlam Modeli

WebGL'de bu nesne "bağlam" olarak bilinir ve esasen bir HTML5 tuval öğesi üzerinde çizim yapmaya yönelik bir arayüzü temsil eder. Bu bağlamı elde etmek oldukça basittir:

 const gl = canvas.getContext('webgl');


WebGL'nin içeriği aslında belirli bir tuvale bağlıdır. Bu, birden fazla tuval üzerinde görüntü oluşturmanız gerekiyorsa birden fazla bağlama ihtiyacınız olacağı anlamına gelir.

WebGPU: Cihaz Modeli

WebGPU "cihaz" adı verilen yeni bir konsepti tanıtıyor. Bu cihaz etkileşim kuracağınız bir GPU soyutlamasını temsil eder. Başlatma süreci WebGL'ye göre biraz daha karmaşıktır ancak daha fazla esneklik sağlar:

 const adapter = await navigator.gpu.requestAdapter(); const device = await adapter.requestDevice(); const context = canvas.getContext('webgpu'); context.configure({ device, format: 'bgra8unorm', });


Bu modelin avantajlarından biri, bir cihazın birden fazla tuval üzerinde görüntü oluşturabilmesi, hatta hiçbirinde görüntü oluşturamamasıdır. Bu, ek esneklik sağlar; örneğin, bir cihaz birden fazla pencerede veya bağlamda görüntü oluşturmayı kontrol edebilir.



Programlar ve İşlem Hatları

WebGL ve WebGPU, grafik hattını yönetmeye ve organize etmeye yönelik farklı yaklaşımları temsil eder.

WebGL: Program

WebGL'de ana odak gölgelendirici programıdır. Program köşe ve parça gölgelendiricileri birleştirerek köşelerin nasıl dönüştürülmesi gerektiğini ve her pikselin nasıl renklendirilmesi gerektiğini tanımlar.

 const program = gl.createProgram(); gl.attachShader(program, vertShader); gl.attachShader(program, fragShader); gl.bindAttribLocation(program, 'position', 0); gl.linkProgram(program);


WebGL'de program oluşturma adımları:


  1. Gölgelendirici Oluşturma : Gölgelendiricilerin kaynak kodu yazılır ve derlenir.
  2. Program Oluşturma : Derlenen gölgelendiriciler programa eklenir ve ardından bağlanır.
  3. Programın Kullanılması : Program render alınmadan önce aktif hale getirilir.
  4. Veri İletimi : Veriler etkinleştirilen programa iletilir.


Bu süreç esnek grafik kontrolüne izin verir, ancak aynı zamanda özellikle büyük ve karmaşık projelerde karmaşık olabilir ve hatalara açık olabilir.

WebGPU: Boru Hattı

WebGPU, ayrı bir program yerine "boru hattı" kavramını sunar. Bu işlem hattı yalnızca gölgelendiricileri değil aynı zamanda WebGL'de durumlar olarak oluşturulan diğer bilgileri de birleştirir. Dolayısıyla WebGPU'da bir işlem hattı oluşturmak daha karmaşık görünüyor:

 const pipeline = device.createRenderPipeline({ layout: 'auto', vertex: { module: shaderModule, entryPoint: 'vertexMain', buffers: [{ arrayStride: 12, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }] }], }, fragment: { module: shaderModule, entryPoint: 'fragmentMain', targets: [{ format, }], }, });


WebGPU'da bir işlem hattı oluşturma adımları:


  1. Gölgelendirici tanımı : Gölgelendirici kaynak kodu, WebGL'de yapıldığına benzer şekilde yazılır ve derlenir.
  2. İşlem hattı oluşturma : Gölgelendiriciler ve diğer oluşturma parametreleri bir işlem hattında birleştirilir.
  3. İşlem hattı kullanımı : İşlem hattı, oluşturma işleminden önce etkinleştirilir.


WebGL, oluşturmanın her yönünü ayırırken, WebGPU daha fazla özelliği tek bir nesnede kapsüllemeye çalışarak sistemi daha modüler ve esnek hale getirir. WebGL'de yapıldığı gibi gölgelendiricileri ve görüntü oluşturma durumlarını ayrı ayrı yönetmek yerine, WebGPU her şeyi tek bir ardışık düzen nesnesinde birleştirir. Bu, süreci daha öngörülebilir ve hatalara daha az eğilimli hale getirir:



Üniformalar

Tek tip değişkenler, tüm gölgelendirici örneklerinin kullanabileceği sabit veriler sağlar.

WebGL 1'deki üniformalar

Temel WebGL'de, uniform değişkenleri doğrudan API çağrıları yoluyla ayarlama olanağımız vardır.


GLSL :

 uniform vec3 u_LightPos; uniform vec3 u_LightDir; uniform vec3 u_LightColor;


JavaScript :

 const location = gl.getUniformLocation(p, "u_LightPos"); gl.uniform3fv(location, [100, 300, 500]);


Bu yöntem basittir ancak her uniform değişken için birden fazla API çağrısı gerektirir.

WebGL 2'deki üniformalar

WebGL 2'nin gelişiyle artık uniform değişkenleri arabellekler halinde gruplama olanağına sahibiz. Yine de ayrı tek tip gölgelendiriciler kullanabilseniz de, daha iyi bir seçenek, tek tip arabellekler kullanarak farklı tek tip gölgelendiricileri daha büyük bir yapıda gruplandırmaktır. Daha sonra tüm bu tek tip verileri, WebGL 1'de köşe arabelleği yüklemeye benzer şekilde GPU'ya aynı anda gönderirsiniz. Bunun, API çağrılarını azaltmak ve modern GPU'ların çalışma şekline daha yakın olmak gibi çeşitli performans avantajları vardır.


GLSL :

 layout(std140) uniform ub_Params { vec4 u_LightPos; vec4 u_LightDir; vec4 u_LightColor; };


JavaScript :

 gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, gl.createBuffer());


WebGL 2'de büyük bir tekdüze arabelleğin alt kümelerini bağlamak için, bindBufferRange olarak bilinen özel bir API çağrısını kullanabilirsiniz. WebGPU'da, setBindGroup API'sini çağırırken bir uzaklık listesi iletebileceğiniz, dinamik tekdüze arabellek uzaklıkları adı verilen benzer bir şey vardır.



WebGPU'daki üniformalar

WebGPU bize daha da iyi bir yöntem sunuyor. Bu bağlamda, bireysel uniform değişkenler artık desteklenmemektedir ve iş yalnızca uniform tamponlar aracılığıyla yapılmaktadır.


:

 [[block]] struct Params { u_LightPos : vec4<f32>; u_LightColor : vec4<f32>; u_LightDirection : vec4<f32>; }; [[group(0), binding(0)]] var<uniform> ub_Params : Params;


JavaScript :

 const buffer = device.createBuffer({ usage: GPUBufferUsage.UNIFORM, size: 8 });


Modern GPU'lar, verilerin birçok küçük blok yerine büyük bir blok halinde yüklenmesini tercih eder. Her seferinde küçük arabellekleri yeniden oluşturmak ve yeniden bağlamak yerine, büyük bir arabellek oluşturmayı ve farklı çizim çağrıları için bunun farklı bölümlerini kullanmayı düşünün. Bu yaklaşım performansı önemli ölçüde artırabilir.


WebGL daha zorunludur; her çağrıda küresel durum sıfırlanır ve mümkün olduğu kadar basit olmaya çabalanır. WebGPU ise daha nesne odaklı olmayı ve kaynakların yeniden kullanımına odaklanmayı hedefliyor, bu da verimliliğe yol açıyor.


WebGL'den WebGPU'ya geçiş yöntem farklılıkları nedeniyle zor görünebilir. Ancak ara adım olarak WebGL 2'ye geçişle başlamak hayatınızı kolaylaştırabilir.



Gölgelendiriciler

WebGL'den WebGPU'ya geçiş yalnızca API'de değil aynı zamanda gölgelendiricilerde de değişiklik yapılmasını gerektirir. WGSL spesifikasyonu, modern GPU'ların verimliliğini ve performansını korurken bu geçişi sorunsuz ve sezgisel hale getirmek için tasarlanmıştır.

Gölgelendirici Dili: GLSL ve WGSL

WGSL, WebGPU ile yerel grafik API'leri arasında bir köprü olacak şekilde tasarlanmıştır. GLSL ile karşılaştırıldığında WGSL biraz daha ayrıntılı görünüyor ancak yapısı tanıdık geliyor.


Doku için örnek bir gölgelendirici:


GLSL :

 sampler2D myTexture; varying vec2 vTexCoord; void main() { return texture(myTexture, vTexCoord); }


:

 [[group(0), binding(0)]] var mySampler: sampler; [[group(0), binding(1)]] var myTexture: texture_2d<f32>; [[stage(fragment)]] fn main([[location(0)]] vTexCoord: vec2<f32>) -> [[location(0)]] vec4<f32> { return textureSample(myTexture, mySampler, vTexCoord); } 



Veri Türlerinin Karşılaştırılması

Aşağıdaki tablo GLSL ve WGSL'deki temel ve matris veri türlerinin karşılaştırmasını göstermektedir:



GLSL'den WGSL'ye geçiş, kod okunabilirliğini artırabilecek ve hata olasılığını azaltabilecek veri boyutlarının daha sıkı yazılması ve açık bir şekilde tanımlanmasına yönelik isteği gösterir.



Yapılar

Yapıları bildirmenin sözdizimi de değişti:


GLSL:

 struct Light { vec3 position; vec4 color; float attenuation; vec3 direction; float innerAngle; float angle; float range; };


- WGSL:

 struct Light { position: vec3<f32>, color: vec4<f32>, attenuation: f32, direction: vec3<f32>, innerAngle: f32, angle: f32, range: f32, };


WGSL yapılarında alanların bildirilmesi için açık sözdiziminin getirilmesi, daha fazla netlik isteğini vurgular ve gölgelendiricilerdeki veri yapılarının anlaşılmasını basitleştirir.



İşlev Bildirimleri

GLSL :

 float saturate(float x) { return clamp(x, 0.0, 1.0); }


:

 fn saturate(x: f32) -> f32 { return clamp(x, 0.0, 1.0); }


WGSL'deki işlevlerin sözdiziminin değiştirilmesi, bildirimlere ve dönüş değerlerine yönelik yaklaşımın birleştirilmesini yansıtarak kodu daha tutarlı ve öngörülebilir hale getirir.



Yerleşik işlevler

WGSL'de birçok yerleşik GLSL işlevi yeniden adlandırıldı veya değiştirildi. Örneğin:



WGSL'deki yerleşik işlevleri yeniden adlandırmak yalnızca adlarını basitleştirmekle kalmaz, aynı zamanda onları daha sezgisel hale getirir; bu da diğer grafik API'lerine aşina olan geliştiriciler için geçiş sürecini kolaylaştırabilir.



Gölgelendirici Dönüşümü

Projelerini WebGL'den WebGPU'ya dönüştürmeyi planlayanlar için, GLSL'yi otomatik olarak WGSL'ye dönüştürmeye yönelik **[Naga](https://github.com/gfx-rs/naga gibi) araçların bulunduğunu bilmek önemlidir. /)**, GLSL'yi WGSL'ye dönüştürmek için kullanılan bir Rust kütüphanesidir. WebAssembly'ın yardımıyla doğrudan tarayıcınızda bile çalışabilir.


Naga'nın desteklediği uç noktalar şunlardır:



Konvansiyon Farklılıkları

Dokular

Geçiş sonrasında ters çevrilmiş görseller şeklinde bir sürprizle karşılaşabilirsiniz. Uygulamaları OpenGL'den Direct3D'ye (veya tersi) aktaranlar bu klasik sorunla zaten karşı karşıya kalmışlardır.


OpenGL ve WebGL bağlamında dokular genellikle başlangıç pikseli sol alt köşeye karşılık gelecek şekilde yüklenir. Ancak pratikte birçok geliştirici görselleri sol üst köşeden başlayarak yüklüyor ve bu da ters çevrilmiş görsel hatasına yol açıyor. Bununla birlikte, bu hata diğer faktörlerle telafi edilebilir ve sonuçta sorun ortadan kaldırılabilir.



OpenGL'den farklı olarak Direct3D ve Metal gibi sistemler geleneksel olarak dokuların başlangıç noktası olarak sol üst köşeyi kullanır. Bu yaklaşımın birçok geliştirici için en sezgisel yaklaşım olduğunu göz önünde bulundurarak WebGPU'nun yaratıcıları bu uygulamayı izlemeye karar verdi.

Görünüm Alanı

WebGL kodunuz çerçeve arabelleğinden piksel seçiyorsa WebGPU'nun farklı bir koordinat sistemi kullanmasına hazırlıklı olun. Koordinatları düzeltmek için basit bir "y = 1,0 - y" işlemini uygulamanız gerekebilir.



Klip Alanları

Bir geliştirici, nesnelerin beklenenden daha erken kırpılması veya kaybolması gibi bir sorunla karşılaştığında, bu genellikle derinlik alanındaki farklılıklarla ilgilidir. WebGL ile WebGPU arasında, klip alanının derinlik aralığını tanımlama şekilleri açısından bir fark vardır. WebGL -1 ile 1 arasında bir aralık kullanırken WebGPU, Direct3D, Metal ve Vulkan gibi diğer grafik API'lerine benzer şekilde 0 ile 1 arasında bir aralık kullanır. Bu karar, diğer grafik API'leriyle çalışırken belirlenen 0'dan 1'e kadar bir aralık kullanmanın çeşitli avantajları nedeniyle verildi.



Modelinizin konumlarını klip alanına dönüştürmenin ana sorumluluğu projeksiyon matrisine aittir. Kodunuzu uyarlamanın en basit yolu, projeksiyon matrisinizin çıktılarının 0 ila 1 aralığında olmasını sağlamaktır. gl-matrix gibi kitaplıkları kullananlar için basit bir çözüm var: perspective işlevini kullanmak yerine, perspectiveZO ; diğer matris işlemleri için de benzer işlevler mevcuttur.

 if (webGPU) { // Creates a matrix for a symetric perspective-view frustum // using left-handed coordinates mat4.perspectiveZO(out, Math.PI / 4, ...); } else { // Creates a matrix for a symetric perspective-view frustum // based on the default handedness and default near // and far clip planes definition. mat4.perspective(out, Math.PI / 4, …); }


Ancak bazen elinizde mevcut bir projeksiyon matrisiniz olabilir ve onun kaynağını değiştiremezsiniz. Bu durumda, bunu 0'dan 1'e kadar bir aralığa dönüştürmek için projeksiyon matrisinizi, derinlik aralığını düzelten başka bir matrisle önceden çarpabilirsiniz.



WebGPU İpuçları ve Püf Noktaları

Şimdi WebGPU ile çalışmaya yönelik bazı ipuçlarını ve püf noktalarını tartışalım.

Kullandığınız boru hattı sayısını en aza indirin.

Ne kadar çok işlem hattı kullanırsanız, o kadar çok durum değişimine sahip olursunuz ve performans da o kadar az olur; Varlıklarınızın nereden geldiğine bağlı olarak bu önemsiz olmayabilir.

İşlem hatlarını önceden oluşturun

Bir işlem hattı oluşturmak ve bunu hemen kullanmak işe yarayabilir ancak bu önerilmez. Bunun yerine hemen geri dönen işlevler oluşturun ve farklı bir iş parçacığı üzerinde çalışmaya başlayın. İşlem hattını kullandığınızda yürütme kuyruğunun, bekleyen işlem hattı oluşturma işlemlerinin tamamlanmasını beklemesi gerekir. Bu, önemli performans sorunlarına neden olabilir. Bunu önlemek için işlem hattını oluşturma ile ilk kullanma arasında biraz zaman bıraktığınızdan emin olun.


Veya daha da iyisi, create*PipelineAsync değişkenlerini kullanın! Söz, herhangi bir duraklama olmadan boru hattı kullanıma hazır olduğunda çözüme kavuşur.

 device.createComputePipelineAsync({ compute: { module: shaderModule, entryPoint: 'computeMain' } }).then((pipeline) => { const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginComputePass(); passEncoder.setPipeline(pipeline); passEncoder.setBindGroup(0, bindGroup); passEncoder.dispatchWorkgroups(128); passEncoder.end(); device.queue.submit([commandEncoder.finish()]); });

RenderBundles'ı kullanın

İşleme paketleri önceden kaydedilmiş, kısmi, yeniden kullanılabilir işleme geçişleridir. Çoğu işleme komutunu içerebilirler (görüntü alanını ayarlama gibi şeyler hariç) ve daha sonra gerçek bir işleme geçişinin parçası olarak "tekrar oynatılabilirler".


 const renderPass = encoder.beginRenderPass(descriptor); renderPass.setPipeline(renderPipeline); renderPass.draw(3); renderPass.executeBundles([renderBundle]); renderPass.setPipeline(renderPipeline); renderPass.draw(3); renderPass.end();


İşleme paketleri, normal işleme geçiş komutlarıyla birlikte yürütülebilir. İşleme geçiş durumu, her paket yürütmesinden önce ve sonra varsayılanlara sıfırlanır. Bu öncelikle çizimin JavaScript yükünü azaltmak için yapılır. Yaklaşımdan bağımsız olarak GPU performansı aynı kalır.

Özet

WebGPU'ya geçiş, grafik API'lerini değiştirmekten daha fazlası anlamına gelir. Bu aynı zamanda çeşitli grafik API'lerinin başarılı özelliklerini ve uygulamalarını birleştirerek web grafiklerinin geleceğine doğru atılmış bir adımdır. Bu geçiş, teknik ve felsefi değişikliklerin kapsamlı bir şekilde anlaşılmasını gerektirir, ancak faydaları önemlidir.

Yararlı Kaynaklar ve Bağlantılar: