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 का इस्तेमाल किया जा सकता है. यह 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)

विंडो खोलना

डॉन अब उपलब्ध है, इसलिए स्क्रीन पर चीज़ें ड्रॉ करने के लिए 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 को असाइन करें. इसके बाद, Start() को कॉल करने से पहले, GetDevice() को कॉल करें और उसके नतीजे के कॉलबैक को 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::glfw::CreateSurfaceForWindow() फ़ंक्शन को कॉल करके wgpu::Surface बनाएं. यह एचटीएमएल कैनवस की तरह ही होता है. इसके बाद, 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() फ़ंक्शन में, GPU को रेंडरिंग के निर्देश भेजें.

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() को कॉल करें और Emscripten से जनरेट किए गए एचटीएमएल पेज में सही एचटीएमएल कैनवस एलिमेंट से मैच करने के लिए, #canvas सिलेक्टर तय करें.

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 API के स्टेबल होने से जुड़े सुधार.
  • Android और iOS के लिए डॉन शुरुआती सहायता.

इस बीच, कृपया Emscripten के लिए WebGPU से जुड़ी समस्याओं और Dawn से जुड़ी समस्याओं के बारे में सुझाव और सवाल सबमिट करें.

संसाधन

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

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

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

लोगों का आभार

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

Unsplash पर Marc-Olivier Jodoin की फ़ोटो.