WebGPU की मदद से ऐप्लिकेशन बनाएं

François Beaufort
François Beaufort

वेब डेवलपर के लिए, WebGPU एक वेब ग्राफ़िक्स एपीआई है. यह जीपीयू को एक साथ और तेज़ी से ऐक्सेस करने की सुविधा देता है. WebGPU, आधुनिक हार्डवेयर की सुविधाओं को दिखाता है. साथ ही, Direct3D 12, Metal, और Vulkan की तरह ही GPU पर रेंडरिंग और कंप्यूटेशन ऑपरेशन की अनुमति देता है.

यह सच है, लेकिन यह स्टोरी अधूरी है. WebGPU, कई कंपनियों के साथ मिलकर किए गए काम का नतीजा है. इनमें Apple, Google, Intel, Mozilla, और Microsoft जैसी बड़ी कंपनियां शामिल हैं. इनमें से कुछ लोगों को पता चला कि WebGPU, JavaScript API से ज़्यादा हो सकता है. यह वेब के अलावा, सभी पारिस्थितिक तंत्र के डेवलपर के लिए, क्रॉस-प्लैटफ़ॉर्म ग्राफ़िक्स एपीआई हो सकता है.

मुख्य इस्तेमाल के उदाहरण को पूरा करने के लिए, Chrome 113 में JavaScript API को पेश किया गया था. हालांकि, इसके साथ ही एक और अहम प्रोजेक्ट भी डेवलप किया गया है: webgpu.h C API. इस C हेडर फ़ाइल में, WebGPU के सभी उपलब्ध प्रोसेस और डेटा स्ट्रक्चर की सूची दी गई है. यह प्लैटफ़ॉर्म के हिसाब से हार्डवेयर एब्स्ट्रैक्शन लेयर के तौर पर काम करता है. इससे, आपको अलग-अलग प्लैटफ़ॉर्म पर एक जैसा इंटरफ़ेस उपलब्ध कराकर, प्लैटफ़ॉर्म के हिसाब से ऐप्लिकेशन बनाने में मदद मिलती है.

इस दस्तावेज़ में, आपको WebGPU का इस्तेमाल करके C++ में एक छोटा ऐप्लिकेशन लिखने का तरीका बताया गया है. यह ऐप्लिकेशन, वेब और चुनिंदा प्लैटफ़ॉर्म, दोनों पर चलता है. स्पॉइलर अलर्ट: आपको वही लाल त्रिभुज दिखेगा जो ब्राउज़र विंडो और डेस्कटॉप विंडो में दिखता है. इसके लिए, आपको अपने कोडबेस में ज़्यादा बदलाव करने की ज़रूरत नहीं होगी.

ब्राउज़र विंडो में WebGPU की मदद से काम करने वाले लाल त्रिभुज और macOS पर डेस्कटॉप विंडो का स्क्रीनशॉट.
ब्राउज़र विंडो और डेस्कटॉप विंडो में, WebGPU की मदद से काम करने वाला वही त्रिभुज जो ब्राउज़र की विंडो और डेस्कटॉप विंडो में काम करता है.

यह कैसे काम करता है?

पूरा किया गया ऐप्लिकेशन देखने के लिए, WebGPU क्रॉस-प्लैटफ़ॉर्म ऐप्लिकेशन का डेटा स्टोर करने की जगह देखें.

यह ऐप्लिकेशन, C++ का एक छोटा सा उदाहरण है. इसमें एक ही कोडबेस से डेस्कटॉप और वेब ऐप्लिकेशन बनाने के लिए, WebGPU का इस्तेमाल करने का तरीका बताया गया है. यह webgpu_cpp.h नाम के C++ रैपर के ज़रिए, प्लैटफ़ॉर्म के हिसाब से हार्डवेयर एब्स्ट्रैक्शन लेयर के तौर पर, WebGPU के webgpu.h का इस्तेमाल करता है.

वेब पर, ऐप्लिकेशन को Emscripten के हिसाब से बनाया गया है, जिसमें JavaScript API के ऊपर webgpu.h को लागू करने वाली बाइंडिंग होती हैं. macOS या Windows जैसे खास प्लैटफ़ॉर्म पर, इस प्रोजेक्ट को Dawn के लिए बनाया जा सकता है. यह Chromium का क्रॉस-प्लैटफ़ॉर्म WebGPU लागू करने वाला टूल है. यह बताना ज़रूरी है कि wgpu-native भी मौजूद है. यह webgpu.h का Rust वर्शन है. हालांकि, इस दस्तावेज़ में इसका इस्तेमाल नहीं किया गया है.

अपनी प्रोफ़ाइल बनाना शुरू करें

शुरू करने के लिए, आपको C++ कंपाइलर और CMake की ज़रूरत होगी, ताकि अलग-अलग प्लैटफ़ॉर्म के लिए बने बिल्ड को स्टैंडर्ड तरीके से मैनेज किया जा सके. किसी खास फ़ोल्डर में, main.cpp सोर्स फ़ाइल और CMakeLists.txt बिल्ड फ़ाइल बनाएं.

फ़िलहाल, main.cpp फ़ाइल में खाली main() फ़ंक्शन होना चाहिए.

int main() {}

CMakeLists.txt फ़ाइल में प्रोजेक्ट के बारे में बुनियादी जानकारी होती है. आखिरी लाइन से पता चलता है कि इस फ़ाइल का नाम "app" है और इसका सोर्स कोड main.cpp है.

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

add_executable(app "main.cpp")

किसी "build/" सब फ़ोल्डर में बिल्ड फ़ाइलें बनाने के लिए cmake -B build चलाएं और ऐप्लिकेशन बनाने और एक्ज़ीक्यूटेबल फ़ाइल जनरेट करने के लिए cmake --build build चलाएं.

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

# Run the app.
$ ./build/app

ऐप्लिकेशन चलता है, लेकिन अभी तक कोई आउटपुट नहीं है. इसकी वजह यह है कि आपको स्क्रीन पर चीज़ें खींचने का तरीका चाहिए.

Dawn ऐप्लिकेशन पाना

त्रिकोण बनाने के लिए, Dawn का इस्तेमाल किया जा सकता है. यह Chromium का क्रॉस-प्लैटफ़ॉर्म WebGPU लागू करने वाला टूल है. इसमें स्क्रीन पर ड्रॉ करने के लिए, GLFW C++ लाइब्रेरी शामिल है. Dawn को डाउनलोड करने का एक तरीका यह है कि आप इसे अपनी रिपॉज़िटरी में git सबमोड्यूल के तौर पर जोड़ें. नीचे दिए गए निर्देश, इसे "dawn/" सब-फ़ोल्डर में फ़ेच करते हैं.

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

इसके बाद, CMakeLists.txt फ़ाइल में इस तरह से जोड़ें:

  • CMake DAWN_FETCH_DEPENDENCIES विकल्प, Dawn की सभी डिपेंडेंसी फ़ेच करता है.
  • टारगेट में dawn/ सब फ़ोल्डर शामिल है.
  • आपका ऐप्लिकेशन, dawn::webgpu_dawn, glfw, और webgpu_glfw टारगेट पर निर्भर करेगा, ताकि आप बाद में main.cpp फ़ाइल में उनका इस्तेमाल कर सकें.
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)

विंडो खोलना

अब Dawn उपलब्ध है. इसलिए, स्क्रीन पर चीज़ें ड्रॉ करने के लिए GLFW का इस्तेमाल करें. webgpu_glfw में शामिल इस लाइब्रेरी की मदद से, विंडो मैनेजमेंट के लिए ऐसा कोड लिखा जा सकता है जो किसी भी प्लैटफ़ॉर्म पर काम करता हो.

512x512 के रिज़ॉल्यूशन वाली "WebGPU विंडो" नाम की विंडो खोलने के लिए, main.cpp फ़ाइल को यहां दिए गए तरीके से अपडेट करें. ध्यान दें कि यहां glfwWindowHint() का इस्तेमाल, किसी खास ग्राफ़िक्स एपीआई को शुरू करने का अनुरोध करने के लिए नहीं किया गया है.

#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();
}

ऐप्लिकेशन को फिर से बनाकर, पहले की तरह चलाने पर अब एक खाली विंडो दिखती है. आपकी प्रोग्रेस दिख रही है!

खाली macOS विंडो का स्क्रीनशॉट.
खाली विंडो.

जीपीयू डिवाइस पाएं

JavaScript में, जीपीयू को ऐक्सेस करने के लिए navigator.gpu आपका एंट्री पॉइंट होता है. C++ में, आपको मैन्युअल रूप से एक ऐसा wgpu::Instance वैरिएबल बनाना होगा जिसका इस्तेमाल इसी काम के लिए किया गया हो. आपकी सुविधा के लिए, main.cpp फ़ाइल के सबसे ऊपर, instance एलान करें और main() में wgpu::CreateInstance() को कॉल करें.

…
#include <webgpu/webgpu_cpp.h>

wgpu::Instance instance;
…

int main() {
  instance = wgpu::CreateInstance();
  Start();
}

JavaScript API के आकार की वजह से जीपीयू को ऐक्सेस करना एसिंक्रोनस होता है. C++ में, GetAdapter() और GetDevice() नाम के दो हेल्पर फ़ंक्शन बनाएं. ये wgpu::Adapter और wgpu::Device के साथ कॉलबैक फ़ंक्शन दिखाते हैं.

#include <iostream>
…

void GetAdapter(void (*callback)(wgpu::Adapter)) {
  instance.RequestAdapter(
      nullptr,
      [](WGPURequestAdapterStatus status, WGPUAdapter cAdapter,
         const char* message, void* userdata) {
        if (status != WGPURequestAdapterStatus_Success) {
          exit(0);
        }
        wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
        reinterpret_cast<void (*)(wgpu::Adapter)>(userdata)(adapter);
  }, reinterpret_cast<void*>(callback));
}

void GetDevice(void (*callback)(wgpu::Device)) {
  adapter.RequestDevice(
      nullptr,
      [](WGPURequestDeviceStatus status, WGPUDevice cDevice,
          const char* message, void* userdata) {
        wgpu::Device device = wgpu::Device::Acquire(cDevice);
        device.SetUncapturedErrorCallback(
            [](WGPUErrorType type, const char* message, void* userdata) {
              std::cout << "Error: " << type << " - message: " << message;
            },
            nullptr);
        reinterpret_cast<void (*)(wgpu::Device)>(userdata)(device);
  }, reinterpret_cast<void*>(callback));
}

आसानी से ऐक्सेस करने के लिए, main.cpp फ़ाइल में सबसे ऊपर दो वैरिएबल wgpu::Adapter और wgpu::Device डालें. GetAdapter() को कॉल करने के लिए, main() फ़ंक्शन को अपडेट करें और इसके नतीजे का कॉलबैक adapter को असाइन करें. इसके बाद, GetDevice() को कॉल करें और Start() को कॉल करने से पहले, इसके नतीजे का कॉलबैक device को असाइन करें.

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

int main() {
  instance = wgpu::CreateInstance();
  GetAdapter([](wgpu::Adapter a) {
    adapter = a;
    GetDevice([](wgpu::Device d) {
      device = d;
      Start();
    });
  });
}

त्रिकोण बनाना

स्वैप चेन को JavaScript API में नहीं दिखाया जाता, क्योंकि ब्राउज़र इसका ध्यान रखता है. C++ में, आपको इसे मैन्युअल रूप से बनाना होगा. एक बार फिर से, सुविधा के लिए, main.cpp फ़ाइल के सबसे ऊपर wgpu::Surface वैरिएबल का एलान करें. Start() में GLFW विंडो बनाने के बाद, wgpu::Surface (एचटीएमएल कैनवस की तरह) बनाने के लिए, हैंडी wgpu::glfw::CreateSurfaceForWindow() फ़ंक्शन को कॉल करें और InitGraphics() में नए हेल्पर ConfigureSurface() फ़ंक्शन को कॉल करके इसे कॉन्फ़िगर करें. while लूप में अगला टेक्सचर दिखाने के लिए, आपको surface.Present() को भी कॉल करना होगा. इसका कोई असर नहीं पड़ता, क्योंकि अभी तक रेंडरिंग नहीं हो रही है.

#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};
  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();
  }
}

अब नीचे दिए गए कोड के साथ रेंडर पाइपलाइन बनाने का सही समय है. आसानी से ऐक्सेस करने के लिए, main.cpp फ़ाइल के सबसे ऊपर wgpu::RenderPipeline वैरिएबल का एलान करें. साथ ही, InitGraphics() में हेल्पर फ़ंक्शन CreateRenderPipeline() को कॉल करें.

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::ShaderModuleWGSLDescriptor wgslDesc{};
  wgslDesc.code = shaderCode;

  wgpu::ShaderModuleDescriptor shaderModuleDescriptor{
      .nextInChain = &wgslDesc};
  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();
}

आखिर में, हर फ़्रेम नाम के Render() फ़ंक्शन में जीपीयू को रेंडरिंग कमांड भेजें.

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);
}

CMake की मदद से ऐप्लिकेशन को फिर से बनाकर उसे चलाने पर, अब हमें विंडो में लाल रंग का वह त्रिकोण दिख रहा है जिसका हम इंतज़ार कर रहे थे! ब्रेक लें—आपको इसकी ज़रूरत है.

macOS विंडो में लाल रंग के ट्राएंगल का स्क्रीनशॉट.
डेस्कटॉप विंडो में लाल रंग का त्रिभुज.

WebAssembly में कंपाइल करना

अब हम ब्राउज़र विंडो में यह लाल ट्राएंगल बनाने के लिए, अपने मौजूदा कोडबेस में किए जाने वाले कम से कम बदलावों के बारे में जानेंगे. फिर से, ऐप्लिकेशन को Emscripten के लिए बनाया गया है. यह C/C++ प्रोग्राम को WebAssembly में कंपाइल करने के लिए इस्तेमाल किया जाने वाला टूल है. इसमें JavaScript API के ऊपर webgpu.h को लागू करने वाली बाइंडिंग होती हैं.

CMake की सेटिंग अपडेट करना

Emscripten इंस्टॉल होने के बाद, CMakeLists.txt बिल्ड फ़ाइल को इस तरह अपडेट करें. आपको सिर्फ़ हाइलाइट किए गए कोड में बदलाव करना होगा.

  • set_target_properties का इस्तेमाल, टारगेट फ़ाइल में "html" फ़ाइल एक्सटेंशन को अपने-आप जोड़ने के लिए किया जाता है. दूसरे शब्दों में, आपको "app.html" फ़ाइल जनरेट होगी.
  • Emscripten में WebGPU की सुविधा चालू करने के लिए, USE_WEBGPU ऐप्लिकेशन लिंक का विकल्प ज़रूरी है. इसके बिना, आपकी main.cpp फ़ाइल webgpu/webgpu_cpp.h फ़ाइल को ऐक्सेस नहीं कर सकती.
  • यहां USE_GLFW ऐप्लिकेशन लिंक विकल्प भी ज़रूरी है, ताकि आप अपने जीएलएफ़डब्ल्यू कोड का फिर से इस्तेमाल कर सकें.
cmake_minimum_required(VERSION 3.13) # CMake version check
project(app)                         # Create project "app"
set(CMAKE_CXX_STANDARD 20)           # Enable C++20 standard

add_executable(app "main.cpp")

if(EMSCRIPTEN)
  set_target_properties(app PROPERTIES SUFFIX ".html")
  target_link_options(app PRIVATE "-sUSE_WEBGPU=1" "-sUSE_GLFW=3")
else()
  set(DAWN_FETCH_DEPENDENCIES ON)
  add_subdirectory("dawn" EXCLUDE_FROM_ALL)
  target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
endif()

कोड अपडेट करना

Emscripten में, wgpu::surface बनाने के लिए एचटीएमएल कैनवस एलिमेंट की ज़रूरत होती है. इसके लिए, instance.CreateSurface() को कॉल करें और #canvas सिलेक्टर की जानकारी दें, ताकि Emscripten से जनरेट किए गए एचटीएमएल पेज में सही एचटीएमएल कैनवस एलिमेंट से मिलान किया जा सके.

while लूप का इस्तेमाल करने के बजाय, emscripten_set_main_loop(Render) को कॉल करें. इससे यह पक्का किया जा सकता है कि Render() फ़ंक्शन को सही दर से कॉल किया जाए, ताकि वह ब्राउज़र और मॉनिटर के साथ सही तरीके से अलाइन हो सके.

#include <GLFW/glfw3.h>
#include <webgpu/webgpu_cpp.h>
#include <iostream>
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#else
#include <webgpu/webgpu_glfw.h>
#endif
void Start() {
  if (!glfwInit()) {
    return;
  }

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

#if defined(__EMSCRIPTEN__)
  wgpu::SurfaceDescriptorFromCanvasHTMLSelector canvasDesc{};
  canvasDesc.selector = "#canvas";

  wgpu::SurfaceDescriptor surfaceDesc{.nextInChain = &canvasDesc};
  surface = instance.CreateSurface(&surfaceDesc);
#else
  surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
#endif

  InitGraphics();

#if defined(__EMSCRIPTEN__)
  emscripten_set_main_loop(Render, 0, false);
#else
  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    Render();
    surface.Present();
    instance.ProcessEvents();
  }
#endif
}

Emscripten की मदद से ऐप्लिकेशन बनाएं

Emscripten के साथ ऐप्लिकेशन बनाने के लिए, आपको बस cmake कमांड को मैजिक emcmake शेल स्क्रिप्ट के साथ जोड़ने की ज़रूरत होती है. इस बार, build-web सब-फ़ोल्डर में ऐप्लिकेशन जनरेट करें और एचटीटीपी सर्वर शुरू करें. आखिर में, अपना ब्राउज़र खोलें और build-web/app.html पर जाएं.

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

# Start a HTTP server.
$ npx http-server
ब्राउज़र विंडो में लाल रंग के ट्राएंगल का स्क्रीनशॉट.
ब्राउज़र विंडो में लाल रंग का त्रिभुज.

आगे क्या करना है

आने वाले समय में, आपको ये बदलाव दिख सकते हैं:

  • webgpu.h और webgpu_cpp.h एपीआई के स्टेबलाइज़ेशन में सुधार.
  • Android और iOS के लिए, Dawn की शुरुआती सहायता.

इस दौरान, कृपया सुझावों और सवालों के साथ, Emscripten के लिए WebGPU से जुड़ी समस्याओं और डॉन से जुड़ी समस्याओं की शिकायत करें.

संसाधन

इस ऐप्लिकेशन के सोर्स कोड को देखें.

अगर आपको WebGPU की मदद से, C++ में शुरू से ही नेटिव 3D ऐप्लिकेशन बनाने के बारे में ज़्यादा जानना है, तो C++ दस्तावेज़ के लिए WebGPU के बारे में जानें और Dawn Native WebGPU के उदाहरण देखें.

अगर आपकी दिलचस्पी Rust में है, तो WebGPU पर आधारित wgpu ग्राफ़िक लाइब्रेरी भी देखी जा सकती है. hello-triangle का डेमो देखें.

लोगों का आभार

इस लेख की समीक्षा कॉर्नेटिन वॉलेस, काई निनोमिया, और रेचेल एंड्रयू ने की है.

Unस्प्लैश पर मार्क-ओलिवियर जोदोइन की फ़ोटो.