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

François Beaufort
François Beaufort

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

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

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

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

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

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

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

यह ऐप्लिकेशन, C++ का एक सामान्य उदाहरण है. इसमें दिखाया गया है कि एक ही कोड बेस से डेस्कटॉप और वेब ऐप्लिकेशन बनाने के लिए, WebGPU का इस्तेमाल कैसे किया जाता है. हुड के तहत, यह C++ रैपर के ज़रिए, WebGPU के webgpu.h का इस्तेमाल प्लैटफ़ॉर्म-एग्नॉस्टिक हार्डवेयर ऐब्स्ट्रक्शन लेयर के तौर पर करता है. इस लेयर को 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 फ़ाइल में प्रोजेक्ट के बारे में बुनियादी जानकारी होती है. आखिरी लाइन से पता चलता है कि एक्ज़ीक्यूटेबल नाम "ऐप्लिकेशन" है और इसका सोर्स कोड 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++ लाइब्रेरी भी शामिल है. डॉन को डाउनलोड करने का एक तरीका यह है कि आप इसे अपने रिपॉज़िटरी में git सबमॉड्यूल के तौर पर जोड़ें. ये निर्देश इसे "dawn/" में फ़ेच करते हैं सब फ़ोल्डर में कॉपी हो जाएगा.

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

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

  • CMake DAWN_FETCH_DEPENDENCIES विकल्प सभी डॉन डिपेंडेंसी को फ़ेच करता है.
  • टारगेट में 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 में शामिल यह लाइब्रेरी, आपको ऐसा कोड लिखने की सुविधा देती है जो विंडो मैनेजमेंट के लिए प्लैटफ़ॉर्म-एग्नोस्टिक हो.

"WebGPU विंडो" नाम की विंडो खोलने के लिए 512x512 के रिज़ॉल्यूशन के साथ, 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() फ़ंक्शन को कॉल करके इसे कॉन्फ़िगर करें. लूप में अगली बनावट दिखाने के लिए, आपको 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 के आधार पर बनाया गया है. यह एक ऐसा टूल है जो WebAssembly में C/C++ प्रोग्राम कंपाइल करने का टूल है. इसमें JavaScript API के ऊपर, webgpu.h को लागू करने के लिए बाइंडिंग होती हैं.

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

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 dawn::webgpu_dawn glfw webgpu_glfw)
endif()

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

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

लूप का इस्तेमाल करने के बजाय, 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 के लिए डॉन शुरुआती सहायता.

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

संसाधन

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

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

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

लोगों का आभार

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

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