Web'de GPU Compute'u kullanmaya başlayın

Bu gönderi, deneysel WebGPU API'yi örneklerle keşfeder ve GPU'yu kullanarak veri paralel hesaplamalar yapmaya başlamanıza yardımcı olur.

François Beaufort
François Beaufort

Arka plan

Grafik işlem birimi (GPU), bildiğiniz gibi bilgisayardaki grafikleri işlemek için özel olarak tasarlanmış elektronik bir alt sistemdir. Ancak son 10 yılda, GPU'nun benzersiz mimarisinden yararlanırken geliştiricilerin yalnızca 3D grafikleri oluşturmakla kalmayıp birçok algoritma türünü de uygulamasını sağlayan daha esnek bir mimariye doğru gelişti. Bu özelliklere GPU Compute denir ve GPU'yu genel amaçlı bilimsel hesaplama için yardımcı işlemci olarak kullanmak genel amaçlı GPU (GPGPU) programlama olarak adlandırılır.

GPU Compute, son zamanlardaki makine öğrenimi patlamasına önemli ölçüde katkıda bulundu. Çünkü, konvolüsyonel sinir ağları ve diğer modeller, GPU'larda daha verimli çalışabilmek için mimariden yararlanabilir. Mevcut Web Platformu'nda GPU bilgi işlem özellikleri bulunmadığından W3C'nin "Web için GPU" Topluluk Grubu, mevcut cihazların çoğunda bulunan modern GPU API'lerini kullanıma sunmak için bir API tasarlıyor. Bu API'nin adı WebGPU'dur.

WebGPU, WebGL gibi düşük düzey bir API'dir. Göreceğiniz gibi, çok güçlü ve oldukça ayrıntılı bir yöntemdir. Ama sorun değil. Amacımız performanstır.

Bu makalede, WebGPU'nun GPU Compute kısmına odaklanacağım. Dürüst olmak gerekirse, kendi başınıza oynamaya başlayabilmeniz için konuyu sadece yüzeysel olarak ele alacağım. Gelecekteki makalelerde WebGPU oluşturma (tuval, doku vb.) hakkında daha ayrıntılı bilgi vereceğim.

GPU'ya erişme

WebGPU'da GPU'ya erişmek kolaydır. navigator.gpu.requestAdapter() çağrısı, bir GPU bağdaştırıcısı ile eşzamansız olarak çözülecek bir JavaScript promise döndürür. Bu adaptörü grafik kartı olarak düşünebilirsiniz. Entegre (CPU ile aynı çipte) veya ayrı (genellikle daha yüksek performanslı ancak daha fazla güç kullanan bir PCIe kartı) olabilir.

GPU adaptörünü aldıktan sonra, bazı GPU hesaplamaları yapmak için kullanacağınız GPU cihazıyla çözülecek bir promise almak üzere adapter.requestDevice() işlevini çağırın.

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();

Her iki işlev de istediğiniz adaptör türü (güç tercihi) ve cihaz (uzantılar, sınırlar) hakkında net olmanıza olanak tanıyan seçenekler sunar. Basitlik açısından bu makalede varsayılan seçenekleri kullanacağız.

Yazma arabellek belleği

GPU'nun belleğine veri yazmak için JavaScript'in nasıl kullanılacağını görelim. Modern web tarayıcılarında kullanılan korumalı alan modeli nedeniyle bu işlem basit değildir.

Aşağıdaki örnekte, GPU'dan erişilebilen belleği arabelleğe almak için dört baytın nasıl yazılacağını görebilirsiniz. Tamponun boyutunu ve kullanımını alan device.createBuffer() işlevini çağırır. Kullanım işareti GPUBufferUsage.MAP_WRITE bu çağrı için gerekli olmasa da bu arabelleğe yazmak istediğimizi açıkça belirtelim. mappedAtCreation doğru olarak ayarlandığından, oluşturulurken eşlenen bir GPU arabellek nesnesi oluşturulur. Ardından, ilişkili ham ikili veri arabelleği, GPU arabellek yöntemi getMappedRange() çağrılarak alınabilir.

ArrayBuffer ile daha önce oynadıysanız bayt yazma işlemine aşinasınızdır. TypedArray kullanın ve değerleri buraya kopyalayın.

// Get a GPU buffer in a mapped state and an arrayBuffer for writing.
const gpuBuffer = device.createBuffer({
  mappedAtCreation: true,
  size: 4,
  usage: GPUBufferUsage.MAP_WRITE
});
const arrayBuffer = gpuBuffer.getMappedRange();

// Write bytes to buffer.
new Uint8Array(arrayBuffer).set([0, 1, 2, 3]);

Bu noktada GPU arabelleği eşlenir. Yani arabellek, CPU'ya aittir ve JavaScript'ten okuma/yazma şeklinde erişilebilir. GPU'nun erişebilmesi için haritanın kaldırılması gerekir. Bu işlem, gpuBuffer.unmap() çağrısı yapmak kadar basittir.

GPU ve CPU'nun belleğe aynı anda eriştiği yarış koşullarını önlemek için eşlenen/eşlenmemiş kavramı gerekir.

Arabellek belleğini okuma

Şimdi bir GPU arabelleğinin başka bir GPU arabelleğine nasıl kopyalanacağını ve nasıl geri okunacağını görelim.

İlk GPU arabelleğine yazdığımız ve bunu ikinci bir GPU arabelleğine kopyalamak istediğimiz için yeni bir kullanım işareti GPUBufferUsage.COPY_SRC gerekir. İkinci GPU arabelleği bu kez device.createBuffer() ile eşlenmemiş durumda oluşturulur. İlk GPU arabelleğinin hedefi olarak kullanılacağı ve GPU kopyalama komutları çalıştırıldıktan sonra JavaScript'te okunacağı için kullanım işareti GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ'tür.

// Get a GPU buffer in a mapped state and an arrayBuffer for writing.
const gpuWriteBuffer = device.createBuffer({
  mappedAtCreation: true,
  size: 4,
  usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC
});
const arrayBuffer = gpuWriteBuffer.getMappedRange();

// Write bytes to buffer.
new Uint8Array(arrayBuffer).set([0, 1, 2, 3]);

// Unmap buffer so that it can be used later for copy.
gpuWriteBuffer.unmap();

// Get a GPU buffer for reading in an unmapped state.
const gpuReadBuffer = device.createBuffer({
  size: 4,
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});

GPU bağımsız bir yardımcı işlemci olduğundan tüm GPU komutları eşzamansız olarak yürütülür. Bu nedenle, GPU komutlarının bir listesi oluşturulur ve gerektiğinde toplu olarak gönderilir. WebGPU'da, device.createCommandEncoder() tarafından döndürülen GPU komut kodlayıcı, bir noktada GPU'ya gönderilecek "arabelleğe alınmış" komutlardan oluşan bir grup oluşturan JavaScript nesnesi olur. Öte yandan GPUBuffer üzerindeki yöntemler "arabelleğe alınmaz". Yani çağrıldıkları anda atomik olarak yürütülürler.

GPU komut kodlayıcıyı aldıktan sonra, bu komutu daha sonra yürütülmek üzere komut sırasına eklemek için aşağıda gösterildiği gibi copyEncoder.copyBufferToBuffer() işlevini çağırın. Son olarak, copyEncoder.finish() çağrısını yaparak kodlama komutlarını tamamlayın ve bu komutları GPU cihaz komut sırasına gönderin. GPU komutlarını bağımsız değişken olarak kullanarak device.queue.submit() aracılığıyla yapılan gönderimlerin işlenmesi sıranın sorumluluğundadır. Bu işlem, dizgede depolanan tüm komutları sırayla atomik olarak yürütür.

// Encode commands for copying buffer to buffer.
const copyEncoder = device.createCommandEncoder();
copyEncoder.copyBufferToBuffer(
  gpuWriteBuffer /* source buffer */,
  0 /* source offset */,
  gpuReadBuffer /* destination buffer */,
  0 /* destination offset */,
  4 /* size */
);

// Submit copy commands.
const copyCommands = copyEncoder.finish();
device.queue.submit([copyCommands]);

Bu noktada, GPU sırası komutları gönderilmiştir ancak mutlaka uygulanmamıştır. İkinci GPU arabelleğini okumak için GPUMapMode.READ ile gpuReadBuffer.mapAsync() işlevini çağırın. GPU arabelleği eşlendiğinde çözülecek bir promise döndürür. Ardından, tüm sıralanmış GPU komutları yürütüldükten sonra ilk GPU arabelleğiyle aynı değerleri içeren gpuReadBuffer.getMappedRange() ile eşlenen aralığı alın.

// Read buffer.
await gpuReadBuffer.mapAsync(GPUMapMode.READ);
const copyArrayBuffer = gpuReadBuffer.getMappedRange();
console.log(new Uint8Array(copyArrayBuffer));

Bu örneği deneyebilirsiniz.

Kısacası, arabellek belleği işlemleriyle ilgili olarak hatırlamanız gerekenler şunlardır:

  • GPU arabelleklerinin, cihaz kuyruğu gönderiminde kullanılabilmesi için haritalarının kaldırılması gerekir.
  • Haritalandığında GPU arabellekleri JavaScript'te okunabilir ve yazılabilir.
  • mappedAtCreation doğru olarak ayarlanmışken mapAsync() ve createBuffer() çağrıldığında GPU arabellekleri eşlenir.

Gölgelendirici programlama

GPU'da çalışan ve yalnızca hesaplama yapan (üçgen çizmeyen) programlara hesaplama gölgelendiricileri denir. Bunlar, verileri işlemek için birlikte çalışan yüzlerce GPU çekirdeği (CPU çekirdeklerinden daha küçüktür) tarafından paralel olarak yürütülür. Giriş ve çıkışları WebGPU'da arabelleklerdir.

WebGPU'da hesaplama gölgelendiricilerinin kullanımını açıklamak için aşağıda gösterilen makine öğrenimindeki yaygın bir algoritma olan matris çarpımıyla oynayacağız.

Matris çarpma şeması
Matris çarpma şeması

Özetlemek gerekirse şunları yapacağız:

  1. Üç GPU arabelleği oluşturun (çarpılacak matrisler için iki tane ve sonuç matrisi için bir tane)
  2. Bilgi işleme gölgelendiricinin girişini ve çıkışını açıklama
  3. Hesaplama gölge kodunu derleme
  4. İşlem ardışık düzeni oluşturma
  5. Kodlanmış komutları GPU'ya toplu olarak gönderme
  6. Sonuç matrisi GPU arabelleğini okuma

GPU arabelleklerinin oluşturulması

Kolaylık sağlamak için matrisler, kayan nokta sayılarının listesi olarak gösterilir. İlk öğe satır sayısı, ikinci öğe sütun sayısı, geri kalanı ise matrisin gerçek sayılarıdır.

JavaScript'te bir matrisin basit gösterimi ve matematiksel gösterimdeki eşdeğeri
JavaScript'te bir matrisin basit gösterimi ve matematiksel gösterimdeki eşdeğeri

Bilgi işlem gölgelendiricisinde veri depolayıp almamız gerektiğinden, üç GPU tamponu depolama tamponudur. GPU arabellek kullanımı işaretlerinin tümü için GPUBufferUsage.STORAGE içermesinin nedeni budur. Tüm GPU sıra komutları yürütüldükten sonra okunmak üzere başka bir arabelleğe kopyalanacağı için sonuç matrisi kullanım işaretinde de GPUBufferUsage.COPY_SRC vardır.

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();


// First Matrix

const firstMatrix = new Float32Array([
  2 /* rows */, 4 /* columns */,
  1, 2, 3, 4,
  5, 6, 7, 8
]);

const gpuBufferFirstMatrix = device.createBuffer({
  mappedAtCreation: true,
  size: firstMatrix.byteLength,
  usage: GPUBufferUsage.STORAGE,
});
const arrayBufferFirstMatrix = gpuBufferFirstMatrix.getMappedRange();
new Float32Array(arrayBufferFirstMatrix).set(firstMatrix);
gpuBufferFirstMatrix.unmap();


// Second Matrix

const secondMatrix = new Float32Array([
  4 /* rows */, 2 /* columns */,
  1, 2,
  3, 4,
  5, 6,
  7, 8
]);

const gpuBufferSecondMatrix = device.createBuffer({
  mappedAtCreation: true,
  size: secondMatrix.byteLength,
  usage: GPUBufferUsage.STORAGE,
});
const arrayBufferSecondMatrix = gpuBufferSecondMatrix.getMappedRange();
new Float32Array(arrayBufferSecondMatrix).set(secondMatrix);
gpuBufferSecondMatrix.unmap();


// Result Matrix

const resultMatrixBufferSize = Float32Array.BYTES_PER_ELEMENT * (2 + firstMatrix[0] * secondMatrix[1]);
const resultMatrixBuffer = device.createBuffer({
  size: resultMatrixBufferSize,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
});

Grup düzenini bağlama ve grup bağlama

Bağlama grubu düzeni ve bağlama grubu kavramları WebGPU'ya özgüdür. Bağlama grubu düzeni, bir gölgelendirici tarafından beklenen giriş/çıkış arayüzünü tanımlar. Bağlama grubu ise bir gölgelendiricinin gerçek giriş/çıkış verilerini temsil eder.

Aşağıdaki örnekte, bağlama grubu düzeni, 0 ve 1 numaralı giriş bağlamalarında iki salt okunur depolama tamponu ve bilgi işlem gölgelendirici için 2 adresinde bir depolama tamponu bekler. Öte yandan, bu bağlama grubu düzeni için tanımlanan bağlama grubu, GPU arabelleklerini girişlerle ilişkilendirir: gpuBufferFirstMatrix, 0 bağlamasıyla, gpuBufferSecondMatrix, 1 bağlamasıyla ve resultMatrixBuffer, 2 bağlamasıyla ilişkilendirilir.

const bindGroupLayout = device.createBindGroupLayout({
  entries: [
    {
      binding: 0,
      visibility: GPUShaderStage.COMPUTE,
      buffer: {
        type: "read-only-storage"
      }
    },
    {
      binding: 1,
      visibility: GPUShaderStage.COMPUTE,
      buffer: {
        type: "read-only-storage"
      }
    },
    {
      binding: 2,
      visibility: GPUShaderStage.COMPUTE,
      buffer: {
        type: "storage"
      }
    }
  ]
});

const bindGroup = device.createBindGroup({
  layout: bindGroupLayout,
  entries: [
    {
      binding: 0,
      resource: {
        buffer: gpuBufferFirstMatrix
      }
    },
    {
      binding: 1,
      resource: {
        buffer: gpuBufferSecondMatrix
      }
    },
    {
      binding: 2,
      resource: {
        buffer: resultMatrixBuffer
      }
    }
  ]
});

Compute gölge kodu

Matrisleri çarpma işlemini yapan hesaplama gölge kodu, WGSL (WebGPU Gölge Kodu Dili) ile yazılmıştır. Bu kod, SPIR-V'ye kolayca çevrilebilir. Ayrıntılara girmeden, var<storage> ile tanımlanan üç depolama arabelleğinin aşağıda gösterildiğini görebilirsiniz. Program, giriş olarak firstMatrix ve secondMatrix'ı, çıkış olarak ise resultMatrix'yi kullanır.

Her depolama arabelleğinin, yukarıda açıklanan bağlama grubu düzenlerinde ve bağlama gruplarında tanımlanan aynı dizeye karşılık gelen bir binding süslemesi olduğunu unutmayın.

const shaderModule = device.createShaderModule({
  code: `
    struct Matrix {
      size : vec2f,
      numbers: array<f32>,
    }

    @group(0) @binding(0) var<storage, read> firstMatrix : Matrix;
    @group(0) @binding(1) var<storage, read> secondMatrix : Matrix;
    @group(0) @binding(2) var<storage, read_write> resultMatrix : Matrix;

    @compute @workgroup_size(8, 8)
    fn main(@builtin(global_invocation_id) global_id : vec3u) {
      // Guard against out-of-bounds work group sizes
      if (global_id.x >= u32(firstMatrix.size.x) || global_id.y >= u32(secondMatrix.size.y)) {
        return;
      }

      resultMatrix.size = vec2(firstMatrix.size.x, secondMatrix.size.y);

      let resultCell = vec2(global_id.x, global_id.y);
      var result = 0.0;
      for (var i = 0u; i < u32(firstMatrix.size.y); i = i + 1u) {
        let a = i + resultCell.x * u32(firstMatrix.size.y);
        let b = resultCell.y + i * u32(secondMatrix.size.y);
        result = result + firstMatrix.numbers[a] * secondMatrix.numbers[b];
      }

      let index = resultCell.y + resultCell.x * u32(secondMatrix.size.y);
      resultMatrix.numbers[index] = result;
    }
  `
});

Ardışık düzen kurulumu

Hesaplama ardışık düzeni, gerçekleştireceğimiz hesaplama işlemini açıklayan nesnedir. device.createComputePipeline() numaralı telefonu arayarak hesap oluşturun. İki bağımsız değişken alır: Daha önce oluşturduğumuz bağlama grubu düzeni ve bilgi işlem gölgelendiricimizin giriş noktasını (main WGSL işlevi) ve device.createShaderModule() ile oluşturulan gerçek bilgi işlem gölgelendirici modülünü tanımlayan bir bilgi işlem aşaması.

const computePipeline = device.createComputePipeline({
  layout: device.createPipelineLayout({
    bindGroupLayouts: [bindGroupLayout]
  }),
  compute: {
    module: shaderModule,
    entryPoint: "main"
  }
});

Komut gönderme

Üç GPU arabelleğimizle bir bağlama grubu ve bağlama grubu düzeniyle bir hesaplama ardışık düzeni oluşturduktan sonra bunları kullanma zamanı gelmiştir.

commandEncoder.beginComputePass() ile programlanabilir bir hesaplama geçişi kodlayıcısı başlatalım. Bu matrisi, matris çarpımını gerçekleştirecek GPU komutlarını kodlamak için kullanırız. passEncoder.setPipeline(computePipeline) ile ardışık düzenini ve passEncoder.setBindGroup(0, bindGroup) ile 0 dizinindeki bağlama grubunu ayarlayın. 0 dizini, WGSL kodundaki group(0) süslemesine karşılık gelir.

Şimdi bu hesaplama gölgelendiricinin GPU'da nasıl çalışacağından bahsedelim. Amacımız, bu programı sonuç matrisinin her hücresi için paralel olarak adım adım yürütmektir. Örneğin, 16x32 boyutunda bir sonuç matrisinde yürütme komutunu kodlamak için @workgroup_size(8, 8) üzerinde passEncoder.dispatchWorkgroups(2, 4) veya passEncoder.dispatchWorkgroups(16 / 8, 32 / 8) çağrıları yaparız. İlk bağımsız değişken "x" ilk boyuttur, ikinci bağımsız değişken "y" ikinci boyuttur ve son bağımsız değişken "z", burada ihtiyaç duymadığımız için varsayılan olarak 1 olan üçüncü boyuttur. GPU'da hesaplama dünyasında, bir çekirdek işlevini bir veri kümesinde yürütmek için komut kodlamaya dağıtım denir.

Her sonuç matrisi hücresi için paralel yürütme
Her sonuç matrisi hücresi için paralel yürütme

WGSL kodumuzda, hesaplama gölgelendiricimiz için iş grubu ızgarasının boyutu (8, 8)'tür. Bu nedenle, sırasıyla ilk matrisin satır sayısı ve ikinci matrisin sütun sayısı olan "x" ve "y" 8'e bölünür. Böylece, passEncoder.dispatchWorkgroups(firstMatrix[0] / 8, secondMatrix[1] / 8) ile bir hesaplama çağrısı gönderebiliriz. Çalıştırılacak çalışma grubu ızgaralarının sayısı dispatchWorkgroups() bağımsız değişkenleridir.

Yukarıdaki çizimde görüldüğü gibi, her gölgelendirici, hangi sonuç matris hücresinin hesaplanacağını bilmek için kullanılacak benzersiz bir builtin(global_invocation_id) nesnesine erişebilir.

const commandEncoder = device.createCommandEncoder();

const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
const workgroupCountX = Math.ceil(firstMatrix[0] / 8);
const workgroupCountY = Math.ceil(secondMatrix[1] / 8);
passEncoder.dispatchWorkgroups(workgroupCountX, workgroupCountY);
passEncoder.end();

Hesaplama geçiş kodlayıcısını sonlandırmak için passEncoder.end() değerini çağırın. Ardından, sonuç matris arabelleğinin copyBufferToBuffer ile kopyalanması için hedef olarak kullanılacak bir GPU arabelleği oluşturun. Son olarak, copyEncoder.finish() ile kodlama komutlarını tamamlayın ve GPU komutlarıyla device.queue.submit()'ı çağırarak bunları GPU cihaz kuyruğuna gönderin.

// Get a GPU buffer for reading in an unmapped state.
const gpuReadBuffer = device.createBuffer({
  size: resultMatrixBufferSize,
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});

// Encode commands for copying buffer to buffer.
commandEncoder.copyBufferToBuffer(
  resultMatrixBuffer /* source buffer */,
  0 /* source offset */,
  gpuReadBuffer /* destination buffer */,
  0 /* destination offset */,
  resultMatrixBufferSize /* size */
);

// Submit GPU commands.
const gpuCommands = commandEncoder.finish();
device.queue.submit([gpuCommands]);

Sonuç matrisini okuma

Sonuç matrisini okumak, gpuReadBuffer.mapAsync() ile GPUMapMode.READ çağrısında bulunmak ve döndürülen promise'ın çözülmesini beklemek kadar kolaydır. Bu, GPU arabelleğinin artık eşlendiğini gösterir. Bu noktada, gpuReadBuffer.getMappedRange() ile eşlenen aralığı almak mümkündür.

Matris çarpımı sonucu
Matris çarpımı sonucu

Kodumuzda, DevTools JavaScript konsoluna kaydedilen sonuç "2, 2, 50, 60, 114, 140" şeklindedir.

// Read buffer.
await gpuReadBuffer.mapAsync(GPUMapMode.READ);
const arrayBuffer = gpuReadBuffer.getMappedRange();
console.log(new Float32Array(arrayBuffer));

Tebrikler! Ba. Sana Özel içeriğinizi oynatabilirsiniz.

Son bir numara

Kodunuzun okunmasını kolaylaştırmanın bir yolu, bağlantı grubu düzenini gölgelendirici modülünden tahmin etmek için hesaplama ardışık düzeninin kullanışlı getBindGroupLayout yöntemini kullanmaktır. Bu hile, aşağıda görebileceğiniz gibi özel bir bağlama grubu düzeni oluşturma ve hesaplama ardışık düzeninizde bir ardışık düzen belirtme ihtiyacını ortadan kaldırır.

Önceki örnek için getBindGroupLayout'ün bir resmi mevcuttur.

 const computePipeline = device.createComputePipeline({
-  layout: device.createPipelineLayout({
-    bindGroupLayouts: [bindGroupLayout]
-  }),
   compute: {
-// Bind group layout and bind group
- const bindGroupLayout = device.createBindGroupLayout({
-   entries: [
-     {
-       binding: 0,
-       visibility: GPUShaderStage.COMPUTE,
-       buffer: {
-         type: "read-only-storage"
-       }
-     },
-     {
-       binding: 1,
-       visibility: GPUShaderStage.COMPUTE,
-       buffer: {
-         type: "read-only-storage"
-       }
-     },
-     {
-       binding: 2,
-       visibility: GPUShaderStage.COMPUTE,
-       buffer: {
-         type: "storage"
-       }
-     }
-   ]
- });
+// Bind group
  const bindGroup = device.createBindGroup({
-  layout: bindGroupLayout,
+  layout: computePipeline.getBindGroupLayout(0 /* index */),
   entries: [

Performans bulguları

Peki matris çarpımını GPU'da çalıştırmak ile CPU'da çalıştırmak arasında nasıl bir fark var? Bunu öğrenmek için, az önce açıklanan programı bir CPU için yazdım. Aşağıdaki grafikte de görebileceğiniz gibi, matrislerin boyutu 256x256'tan büyük olduğunda GPU'nun tam gücünü kullanmak mantıklı bir seçimdir.

GPU ve CPU karşılaştırması
GPU ve CPU karşılaştırması

Bu makale, WebGPU'yu keşfetme yolculuğumun başlangıcıydı. GPU Compute ve WebGPU'da oluşturma (tuval, doku, örnekleyici) işlevinin işleyişine dair daha ayrıntılı bilgiler içeren daha fazla makaleyi yakında yayınlayacağız.