WebGPU ile uygulama oluşturma

François Beaufort
François Beaufort

Yayınlanma tarihi: 20 Temmuz 2023, Son güncelleme tarihi: 27 Ağustos 2025

Web geliştiricileri için WebGPU, GPU'lara birleşik ve hızlı erişim sağlayan bir web grafikleri API'sidir. WebGPU, modern donanım özelliklerini kullanıma sunar ve Direct3D 12, Metal ve Vulkan'a benzer şekilde GPU'da oluşturma ve hesaplama işlemlerine olanak tanır.

Bu hikaye doğru olsa da eksik. WebGPU, Apple, Google, Intel, Mozilla ve Microsoft gibi büyük şirketlerin işbirliğiyle geliştirilmiştir. Bu kişiler arasında, WebGPU'nun bir JavaScript API'sinden daha fazlası olabileceğini, web dışındaki ekosistemlerdeki geliştiriciler için platformlar arası bir grafik API'si olabileceğini fark edenler de vardı.

Birincil kullanım alanını karşılamak için Chrome 113'te bir JavaScript API'si kullanıma sunuldu. Ancak bununla birlikte başka bir önemli proje daha geliştirildi: webgpu.h C API'si. Bu C başlık dosyasında, WebGPU'nun tüm kullanılabilir prosedürleri ve veri yapıları listelenir. Platformdan bağımsız bir donanım soyutlama katmanı olarak işlev görür. Farklı platformlarda tutarlı bir arayüz sağlayarak platforma özel uygulamalar oluşturmanıza olanak tanır.

Bu belgede, hem web'de hem de belirli platformlarda çalışan, WebGPU kullanan küçük bir C++ uygulaması yazmayı öğreneceksiniz. Kod tabanınızda minimum ayarlamalarla tarayıcı penceresinde ve masaüstü penceresinde görünen kırmızı üçgeni alırsınız.

macOS'te bir tarayıcı penceresinde ve masaüstü penceresinde WebGPU tarafından desteklenen kırmızı üçgenin ekran görüntüsü.
Tarayıcı penceresinde ve masaüstü penceresinde WebGPU ile desteklenen aynı üçgen.

İşleyiş şekli

Tamamlanmış uygulamayı görmek için WebGPU platformlar arası uygulama deposuna göz atın.

Bu uygulama, tek bir kod tabanından masaüstü ve web uygulamaları oluşturmak için WebGPU'nun nasıl kullanılacağını gösteren minimalist bir C++ örneğidir. Bu kitaplık, webgpu_cpp.h adlı bir C++ sarmalayıcısı aracılığıyla platformdan bağımsız bir donanım soyutlama katmanı olarak WebGPU'nun webgpu.h dosyasını kullanır.

Web'de uygulama, JavaScript API'sinin üzerinde webgpu.h'yi uygulayan bağlamalara sahip emdawnwebgpu (Emscripten Dawn WebGPU) kullanılarak oluşturulur. macOS veya Windows gibi belirli platformlarda bu proje, Chromium'un platformlar arası WebGPU uygulaması olan Dawn'a karşı oluşturulabilir. webgpu.h'nin Rust uygulaması olan wgpu-native'in de mevcut olduğunu ancak bu dokümanda kullanılmadığını belirtmek isteriz.

Başlayın

Başlamak için, platformlar arası derlemeleri standart bir şekilde işlemek üzere bir C++ derleyicisine ve CMake'e ihtiyacınız vardır. Özel bir klasörde main.cpp kaynak dosyası ve CMakeLists.txt derleme dosyası oluşturun.

main.cpp dosyası şu an için boş bir main() işlevi içermelidir.

int main() {}

CMakeLists.txt dosyası, proje hakkında temel bilgiler içerir. Son satırda, yürütülebilir dosyanın adının "app" ve kaynak kodunun main.cpp olduğu belirtiliyor.

cmake_minimum_required(VERSION 3.22) # CMake version check
project(app)                         # Create project "app"
set(CMAKE_CXX_STANDARD 20)           # Enable C++20 standard

add_executable(app "main.cpp")

"build/" alt klasöründe derleme dosyaları oluşturmak için cmake -B build, uygulamayı gerçekten derlemek ve yürütülebilir dosyayı oluşturmak için cmake --build build komutunu çalıştırın.

# Build the app with CMake.
$ cmake -B build && cmake --build build

# Run the app.
$ ./build/app

Uygulama çalışır ancak ekranda bir şeyler çizmek için bir yol bulmanız gerektiğinden henüz bir çıkış yoktur.

Get Dawn

Üçgeninizi çizmek için Chromium'un platformlar arası WebGPU uygulaması olan Dawn'dan yararlanabilirsiniz. Buna, ekrana çizim yapmak için kullanılan GLFW C++ kitaplığı da dahildir. Dawn'u indirmenin bir yolu, onu deponuza git alt modülü olarak eklemektir. Aşağıdaki komutlar, dosyayı "dawn/" alt klasörüne getirir.

$ git init
$ git submodule add https://dawn.googlesource.com/dawn

Ardından, CMakeLists.txt dosyasına aşağıdaki şekilde ekleyin:

  • CMake DAWN_FETCH_DEPENDENCIES seçeneği, tüm Dawn bağımlılarını getirir.
  • CMake DAWN_BUILD_MONOLITHIC_LIBRARY seçeneği, tüm Dawn bileşenlerini tek bir kitaplıkta toplar.
  • dawn/ alt klasörü hedefe dahil edilir.
  • Uygulamanız, webgpu_dawn, webgpu_glfw ve glfw hedeflerine bağlı olacaktır. Böylece bunları daha sonra main.cpp dosyasında kullanabilirsiniz.

set(DAWN_FETCH_DEPENDENCIES ON)
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)

target_link_libraries(app PRIVATE webgpu_dawn webgpu_glfw glfw)

Pencere açma

Dawn kullanıma sunulduğundan beri ekranda çizim yapmak için GLFW kullanılıyor. Kolaylık sağlamak için webgpu_glfw'da yer alan bu kitaplık, pencere yönetimi için platformdan bağımsız kod yazmanıza olanak tanır.

512x512 çözünürlüğe sahip "WebGPU penceresi" adlı bir pencere açmak için main.cpp dosyasını aşağıdaki gibi güncelleyin. Burada glfwWindowHint()'nın belirli bir grafik API'si başlatma isteğinde bulunmamak için kullanıldığını unutmayın.

#include <GLFW/glfw3.h>

const uint32_t kWidth = 512;
const uint32_t kHeight = 512;

void Start() {
  if (!glfwInit()) {
    return;
  }

  glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
  GLFWwindow* window =
      glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);

  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    // TODO: Render a triangle using WebGPU.
  }
}

int main() {
  Start();
}

Uygulamayı yeniden oluşturup eskisi gibi çalıştırmak artık boş bir pencereyle sonuçlanıyor. İlerleme kaydediyorsunuz.

Boş bir macOS penceresinin ekran görüntüsü.
Boş bir pencere.

GPU cihazı edinme

JavaScript'te navigator.gpu, GPU'ya erişmek için giriş noktanızdır. C++'ta aynı amaçla kullanılan bir wgpu::Instance değişkenini manuel olarak oluşturmanız gerekir. Kolaylık sağlamak için instance öğesini main.cpp dosyasının en üstünde bildirin ve wgpu::CreateInstance() öğesini Init() içinde çağırın.

#include <webgpu/webgpu_cpp.h>


wgpu::Instance instance;


void Init() {
  static const auto kTimedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny;
  wgpu::InstanceDescriptor instanceDesc{.requiredFeatureCount = 1,
                                        .requiredFeatures = &kTimedWaitAny};
  instance = wgpu::CreateInstance(&instanceDesc);
}

int main() {
  Init();
  Start();
}

main.cpp dosyasının en üstünde iki değişken wgpu::Adapter ve wgpu::Device bildirin. Init() işlevini instance.RequestAdapter() işlevini çağıracak ve sonuç geri çağırmasını adapter işlevine atayacak şekilde güncelleyin. Ardından adapter.RequestDevice() işlevini çağırın ve sonuç geri çağırmasını device işlevine atayın.

#include <iostream>

#include <dawn/webgpu_cpp_print.h>


wgpu::Adapter adapter;
wgpu::Device device;


void Init() {
  

  wgpu::Future f1 = instance.RequestAdapter(
      nullptr, wgpu::CallbackMode::WaitAnyOnly,
      [](wgpu::RequestAdapterStatus status, wgpu::Adapter a,
         wgpu::StringView message) {
        if (status != wgpu::RequestAdapterStatus::Success) {
          std::cout << "RequestAdapter: " << message << "\n";
          exit(0);
        }
        adapter = std::move(a);
      });
  instance.WaitAny(f1, UINT64_MAX);

  wgpu::DeviceDescriptor desc{};
  desc.SetUncapturedErrorCallback([](const wgpu::Device&,
                                     wgpu::ErrorType errorType,
                                     wgpu::StringView message) {
    std::cout << "Error: " << errorType << " - message: " << message << "\n";
  });

  wgpu::Future f2 = adapter.RequestDevice(
      &desc, wgpu::CallbackMode::WaitAnyOnly,
      [](wgpu::RequestDeviceStatus status, wgpu::Device d,
         wgpu::StringView message) {
        if (status != wgpu::RequestDeviceStatus::Success) {
          std::cout << "RequestDevice: " << message << "\n";
          exit(0);
        }
        device = std::move(d);
      });
  instance.WaitAny(f2, UINT64_MAX);
}

Üçgen çizme

Tarayıcı bu işlemi gerçekleştirdiğinden takas zinciri, JavaScript API'sinde kullanıma sunulmaz. C++'ta bunu manuel olarak oluşturmanız gerekir. Kolaylık sağlaması için main.cpp dosyasının en üstünde wgpu::Surface değişkenini tekrar tanımlayın. Start() içinde GLFW penceresini oluşturduktan hemen sonra, wgpu::Surface (HTML tuvaline benzer) oluşturmak için kullanışlı wgpu::glfw::CreateSurfaceForWindow() işlevini çağırın ve InitGraphics() içinde yeni yardımcı ConfigureSurface() işlevini çağırarak yapılandırın. Ayrıca, while döngüsünde bir sonraki dokuyu göstermek için surface.Present() işlevini de çağırmanız gerekir. Henüz oluşturma işlemi yapılmadığından bu durumun görünür bir etkisi yoktur.

#include <webgpu/webgpu_glfw.h>


wgpu::Surface surface;
wgpu::TextureFormat format;

void ConfigureSurface() {
  wgpu::SurfaceCapabilities capabilities;
  surface.GetCapabilities(adapter, &capabilities);
  format = capabilities.formats[0];

  wgpu::SurfaceConfiguration config{.device = device,
                                    .format = format,
                                    .width = kWidth,
                                    .height = kHeight,
                                    .presentMode = wgpu::PresentMode::Fifo};
  surface.Configure(&config);
}

void InitGraphics() {
  ConfigureSurface();
}

void Render() {
  // TODO: Render a triangle using WebGPU.
}

void Start() {
  
  surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);

  InitGraphics();

  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    Render();
    surface.Present();
    instance.ProcessEvents();
  }
}

Aşağıdaki kodu kullanarak oluşturma ardışık düzenini oluşturmak için uygun bir zaman. Daha kolay erişim için main.cpp dosyasının en üstünde bir wgpu::RenderPipeline değişkeni tanımlayın ve InitGraphics() içinde CreateRenderPipeline() yardımcı işlevini çağırın.

wgpu::RenderPipeline pipeline;


const char shaderCode[] = R"(
    @vertex fn vertexMain(@builtin(vertex_index) i : u32) ->
      @builtin(position) vec4f {
        const pos = array(vec2f(0, 1), vec2f(-1, -1), vec2f(1, -1));
        return vec4f(pos[i], 0, 1);
    }
    @fragment fn fragmentMain() -> @location(0) vec4f {
        return vec4f(1, 0, 0, 1);
    }
)";

void CreateRenderPipeline() {
  wgpu::ShaderSourceWGSL wgsl{{.code = shaderCode}};

  wgpu::ShaderModuleDescriptor shaderModuleDescriptor{.nextInChain = &wgsl};
  wgpu::ShaderModule shaderModule =
      device.CreateShaderModule(&shaderModuleDescriptor);

  wgpu::ColorTargetState colorTargetState{.format = format};

  wgpu::FragmentState fragmentState{
      .module = shaderModule, .targetCount = 1, .targets = &colorTargetState};

  wgpu::RenderPipelineDescriptor descriptor{.vertex = {.module = shaderModule},
                                            .fragment = &fragmentState};
  pipeline = device.CreateRenderPipeline(&descriptor);
}

void InitGraphics() {
  
  CreateRenderPipeline();
}

Son olarak, her karede çağrılan Render() işlevinde GPU'ya oluşturma komutları gönderin.

void Render() {
  wgpu::SurfaceTexture surfaceTexture;
  surface.GetCurrentTexture(&surfaceTexture);

  wgpu::RenderPassColorAttachment attachment{
      .view = surfaceTexture.texture.CreateView(),
      .loadOp = wgpu::LoadOp::Clear,
      .storeOp = wgpu::StoreOp::Store};

  wgpu::RenderPassDescriptor renderpass{.colorAttachmentCount = 1,
                                        .colorAttachments = &attachment};

  wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
  wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass);
  pass.SetPipeline(pipeline);
  pass.Draw(3);
  pass.End();
  wgpu::CommandBuffer commands = encoder.Finish();
  device.GetQueue().Submit(1, &commands);
}

Uygulamayı CMake ile yeniden oluşturmak ve çalıştırmak artık uzun zamandır beklenen kırmızı üçgenin bir pencerede görünmesine neden oluyor. Mola vermeyi hak ettiniz.

macOS penceresinde kırmızı üçgenin ekran görüntüsü.
Masaüstü penceresinde kırmızı üçgen.

WebAssembly'ye derleme

Şimdi mevcut kod tabanınızı bir tarayıcı penceresinde bu kırmızı üçgeni çizecek şekilde ayarlamak için gereken minimum değişikliklere göz atalım. Uygulama, JavaScript API'sinin üzerinde webgpu.h'yi uygulayan bağlamalara sahip emdawnwebgpu (Emscripten Dawn WebGPU) kullanılarak oluşturulur. C/C++ programlarını WebAssembly'ye derlemek için kullanılan bir araç olan Emscripten'i kullanır.

CMake ayarlarını güncelleme

Emscripten yüklendikten sonra CMakeLists.txt derleme dosyasını aşağıdaki şekilde güncelleyin. Yalnızca vurgulanan kodu değiştirmeniz gerekir.

  • set_target_properties, hedef dosyaya "html" dosya uzantısını otomatik olarak eklemek için kullanılır. Başka bir deyişle, "app.html" dosyası oluşturursunuz.
  • emdawnwebgpu_cpp hedef bağlantı kitaplığı, Emscripten'de WebGPU desteğini etkinleştirir. Bu olmadan main.cpp dosyanız webgpu/webgpu_cpp.h dosyasına erişemez.
  • ASYNCIFY=1 uygulama bağlantısı seçeneği, eşzamanlı C++ kodunun eşzamansız JavaScript ile etkileşim kurmasına olanak tanır.
  • USE_GLFW=3 uygulama bağlantısı seçeneği, Emscripten'e GLFW 3 API'nin yerleşik JavaScript uygulamasını kullanmasını söyler.
cmake_minimum_required(VERSION 3.22) # CMake version check
project(app)                         # Create project "app"
set(CMAKE_CXX_STANDARD 20)           # Enable C++20 standard

add_executable(app "main.cpp")

set(DAWN_FETCH_DEPENDENCIES ON)
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)

if(EMSCRIPTEN)
  set_target_properties(app PROPERTIES SUFFIX ".html")
  target_link_libraries(app PRIVATE emdawnwebgpu_cpp webgpu_glfw)
  target_link_options(app PRIVATE "-sASYNCIFY=1" "-sUSE_GLFW=3")
else()
  target_link_libraries(app PRIVATE webgpu_dawn webgpu_glfw glfw)
endif()

Kodu güncelleme

While döngüsü kullanmak yerine, emscripten_set_main_loop(Render) işlevini çağırarak Render() işlevinin tarayıcı ve monitörle düzgün şekilde eşleşen, uygun ve sorunsuz bir hızda çağrıldığından emin olun.

#include <iostream>

#include <GLFW/glfw3.h>
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#endif
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <webgpu/webgpu_glfw.h>
void Start() {
  
#if defined(__EMSCRIPTEN__)
  emscripten_set_main_loop(Render, 0, false);
#else
  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    Render();
    surface.Present();
    instance.ProcessEvents();
  }
#endif
}

Emscripten ile uygulamayı oluşturma

Uygulamayı Emscripten ile oluşturmak için tek değişiklik olarak cmake komutlarının başına sihirli emcmake kabuk komut dosyasını eklemeniz gerekir. Bu kez uygulamayı bir build-web alt klasöründe oluşturun ve bir HTTP sunucusu başlatın. Son olarak, tarayıcınızı açıp build-web/app.html adresini ziyaret edin.

# Build the app with Emscripten.
$ emcmake cmake -B build-web && cmake --build build-web

# Start a HTTP server.
$ npx http-server
Tarayıcı penceresindeki kırmızı üçgenin ekran görüntüsü.
Tarayıcı penceresinde kırmızı üçgen.

Sırada ne var?

Gelecekte göreceğiniz değişiklikler:

  • webgpu.h ve webgpu_cpp.h API'lerinin kararlılığında iyileştirmeler yapıldı.
  • Android ve iOS için ilk Dawn desteği.

Bu süre zarfında lütfen öneri ve sorularınızla birlikte Emscripten için WebGPU sorunları ve Dawn sorunları bildirin.

Kaynaklar

Bu uygulamanın kaynak kodunu inceleyebilirsiniz.

WebGPU ile C++'ta sıfırdan yerel 3D uygulamalar oluşturma hakkında daha fazla bilgi edinmek istiyorsanız Learn WebGPU for C++ documentation (C++ için WebGPU'yu Öğrenme) ve Dawn Native WebGPU Examples (Dawn Yerel WebGPU Örnekleri) başlıklı makaleleri inceleyin.

Rust ile ilgileniyorsanız WebGPU'ya dayalı wgpu grafik kitaplığını da inceleyebilirsiniz. hello-triangle demosuna göz atın.

Teşekkür

Bu makale Corentin Wallez, Kai Ninomiya ve Rachel Andrew tarafından incelenmiştir.

Marc-Olivier Jodoin'in Unsplash'teki fotoğrafı.