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

François Beaufort
François Beaufort

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

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

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

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

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

यह सुविधा कैसे काम करती है?

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

यह ऐप्लिकेशन, C++ का छोटा उदाहरण है. इससे यह पता चलता है कि एक ही कोड बेस से डेस्कटॉप और वेब ऐप्लिकेशन बनाने के लिए, WebGPU का इस्तेमाल कैसे किया जा सकता है. इसमें, WebGPU के webgpu.h का इस्तेमाल, प्लैटफ़ॉर्म-एग्नोस्टिक हार्डवेयर ऐब्स्ट्रैक्शन लेयर के तौर पर, एक C++ रैपर के ज़रिए किया जाता है. इसे webgpu_cpp.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/ सब-फ़ोल्डर को टारगेट में शामिल किया गया है.
  • आपका ऐप्लिकेशन, webgpu_cpp, webgpu_dawn, और webgpu_glfw टारगेट पर निर्भर करेगा, ताकि आप बाद में main.cpp फ़ाइल में उनका इस्तेमाल कर सकें.
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE webgpu_cpp webgpu_dawn 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++ में, एक हेल्पर GetDevice() फ़ंक्शन बनाएं, जो एक कॉलबैक फ़ंक्शन तर्क लेता है और उसे नतीजे के तौर पर मिलने वाले wgpu::Device को कॉल करता है.

#include <iostream>
…

void GetDevice(void (*callback)(wgpu::Device)) {
  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);
        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);
            },
            userdata);
      },
      reinterpret_cast<void*>(callback));
}

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

wgpu::Device device;
…

int main() {
  instance = wgpu::CreateInstance();
  GetDevice([](wgpu::Device dev) {
    device = dev;
    Start();
  });
}

त्रिभुज बनाएं

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

#include <webgpu/webgpu_glfw.h>
…

wgpu::SwapChain swapChain;

void SetupSwapChain(wgpu::Surface surface) {
  wgpu::SwapChainDescriptor scDesc{
      .usage = wgpu::TextureUsage::RenderAttachment,
      .format = wgpu::TextureFormat::BGRA8Unorm,
      .width = kWidth,
      .height = kHeight,
      .presentMode = wgpu::PresentMode::Fifo};
  swapChain = device.CreateSwapChain(surface, &scDesc);
}

void InitGraphics(wgpu::Surface surface) {
  SetupSwapChain(surface);
}

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

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

  InitGraphics(surface);

  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    Render();
    swapChain.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 = wgpu::TextureFormat::BGRA8Unorm};

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

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

void InitGraphics(wgpu::Surface surface) {
  …
  CreateRenderPipeline();
}

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

void Render() {
  wgpu::RenderPassColorAttachment attachment{
      .view = swapChain.GetCurrentTextureView(),
      .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 की मदद से बनाया गया है. यह एक ऐसा टूल है जो WebAssembly में C/C++ प्रोग्राम को कंपाइल करता है. इसमें JavaScript API के सबसे ऊपर webgpu.h को लागू करने के लिए बाइंडिंग है.

C Maker की सेटिंग अपडेट करें

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

  • टारगेट फ़ाइल में "html" फ़ाइल एक्सटेंशन को अपने-आप जोड़ने के लिए, set_target_properties का इस्तेमाल किया जाता है. दूसरे शब्दों में, आप एक "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 webgpu_cpp webgpu_dawn webgpu_glfw)
endif()

कोड अपडेट करें

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

लूप में काम करने के बजाय, 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};
  wgpu::Surface surface = instance.CreateSurface(&surfaceDesc);
#else
  wgpu::Surface surface =
      wgpu::glfw::CreateSurfaceForWindow(instance, window);
#endif

  InitGraphics(surface);

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

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

Emscripten की मदद से ऐप्लिकेशन बनाने के लिए, सिर्फ़ मैजिक emcmake शेल स्क्रिप्ट के साथ cmake कमांड जोड़ना होगा. इस बार, 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-त्रिकोण का डेमो देखें.

लोगों का आभार

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

Unsplash पर मार्क-ऑलिवर जोडोइन ने फ़ोटो ली है.