بالنسبة إلى مطوّري البرامج على الويب، WebGPU هي واجهة برمجة تطبيقات لرسومات الويب توفّر وصولاً موحدًا وسريعًا إلى وحدات معالجة الرسومات. تعرض WebGPU إمكانيات الأجهزة الحديثة وتتيح عمليات العرض والحوسبة على وحدة معالجة الرسومات، تمامًا مثل Direct3D 12 وMetal وVulkan.
رغم الحقيقة، هذه القصة غير مكتملة. وWebGPU هو نتيجة لجهود تعاونية تشمل الشركات الكبرى، مثل Apple وGoogle وIntel وMozilla وMicrosoft. من بينها، أدرك البعض أنّ WebGPU يمكن أن تكون أكثر من مجرد واجهة برمجة تطبيقات JavaScript، ولكنها واجهة برمجة تطبيقات للرسومات من عدة أنظمة أساسية مصمَّمة للمطوّرين في جميع الأنظمة الأساسية وغير الويب.
لتنفيذ حالة الاستخدام الأساسية، تم تقديم واجهة برمجة تطبيقات JavaScript في Chrome 113. ومع ذلك، تم تطوير مشروع آخر مهم إلى جانبه: واجهة برمجة التطبيقات webgpu.h. يسرد ملف العنوان C هذا جميع الإجراءات وهياكل البيانات المتاحة لوحدة معالجة الرسومات WebGPU. وهي تعمل كطبقة تجريد غير متوافقة مع النظام الأساسي للأجهزة، ما يسمح لك بإنشاء تطبيقات خاصة بالنظام الأساسي من خلال توفير واجهة متسقة على الأنظمة الأساسية المختلفة.
في هذا المستند، ستتعلم كيفية كتابة تطبيق C++ صغير باستخدام WebGPU يعمل على الويب وعلى أنظمة أساسية محدَّدة. تنبيه بحرق الأحداث، سيظهر لك المثلث الأحمر نفسه الذي يظهر في نافذة متصفّح ونافذة على سطح المكتب مع إجراء تعديلات بسيطة على قاعدة الرموز.
كيف تعمل هذه الميزة؟
للاطّلاع على التطبيق المكتمل، راجِع مستودع تطبيقات WebGPU من عدّة أنظمة أساسية.
التطبيق هو مثال مبسَّط C++ يوضح كيفية استخدام WebGPU لإنشاء تطبيقات سطح المكتب وتطبيقات الويب من قاعدة رموز واحدة. وتستخدم هذه الأنظمة webgpu.h في WebGPU كطبقة مجرّدة للأجهزة غير متوافقة مع النظام الأساسي، وذلك من خلال برنامج تضمين C++ يُسمى webgpu_cpp.h.
على الويب، تم تصميم التطبيق وفقًا لـ Emscripten، الذي يضم عمليات ربط تنفّذ webgpu.h أعلى واجهة برمجة تطبيقات JavaScript. على أنظمة أساسية معيَّنة، مثل macOS أو Windows، يمكن إنشاء هذا المشروع وفقًا لتنفيذ Dawn، وهو تطبيق WebGPU من Chromium على عدّة منصات. من الجدير الإشارة إلى wgpu-Native، وهو تنفيذ Rust لـ webgpu.h، وهو متوفّر أيضًا ولكن لم يتمّ استخدامه في هذا المستند.
البدء
للبدء، تحتاج إلى برنامج تجميع 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")
شغِّل cmake -B build
لإنشاء ملفات الإصدار في مجلد فرعي "build/" وcmake --build build
لإنشاء التطبيق وإنشاء الملف التنفيذي.
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
يعمل التطبيق ولكن لا تتوفّر أي نتيجة بعد، لأنّك تحتاج إلى طريقة لرسم العناصر على الشاشة.
الفجر
لرسم مثلث، يمكنك الاستفادة من Dawn، وهو تنفيذ WebGPU من Chromium متعدد الأنظمة الأساسية. ويتضمن ذلك مكتبة 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
وglfw
وwebgpu_glfw
حتى تتمكّن من استخدامها في ملفmain.cpp
لاحقًا.
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE webgpu_cpp webgpu_dawn glfw webgpu_glfw)
فتح نافذة
الآن وبعد أن أصبحت Dawn متاحة، يمكنك استخدام 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();
}
تؤدي إعادة إنشاء التطبيق وتشغيله كما كان في السابق إلى ظهور نافذة فارغة. أنت تحرز تقدمًا!
الحصول على جهاز وحدة معالجة الرسومات
في JavaScript، تكون navigator.gpu
هي نقطة دخولك للوصول إلى وحدة معالجة الرسومات. في C++ ، يجب إنشاء متغيّر wgpu::Instance
يدويًا يتم استخدامه للغرض نفسه. لتسهيل العملية، يُرجى الإشارة إلى الرمز instance
في أعلى ملف main.cpp
والاتصال بـ wgpu::CreateInstance()
في main()
.
…
#include <webgpu/webgpu_cpp.h>
wgpu::Instance instance;
…
int main() {
instance = wgpu::CreateInstance();
Start();
}
ويكون الوصول إلى وحدة معالجة الرسومات غير متزامن بسبب شكل واجهة برمجة تطبيقات JavaScript. في 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));
}
لتسهيل الوصول إلى البيانات، يُرجى تحديد المتغيّرَين wgpu::Adapter
وwgpu::Device
في أعلى ملف main.cpp
. يمكنك تعديل الدالة main()
لاستدعاء GetAdapter()
وتعيين معاودة الاتصال بالنتيجة إلى adapter
ثم استدعاء GetDevice()
وتعيين معاودة الاتصال بالنتيجة إلى device
قبل طلب Start()
.
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 لأنّ المتصفّح يأخذها في الاعتبار. في C++ ، يجب عليك إنشاؤه يدويًا. ولتسهيل الأمر عليك مرة أخرى، يمكنك الإشارة إلى متغيّر wgpu::Surface
في أعلى ملف main.cpp
. بعد إنشاء نافذة GLFW في Start()
مباشرةً، يمكنك استدعاء دالة wgpu::glfw::CreateSurfaceForWindow()
المفيدة لإنشاء wgpu::Surface
(مشابهة للوحة HTML) وإعدادها من خلال استدعاء دالة المساعد الجديدة ConfigureSurface()
في InitGraphics()
. عليك أيضًا طلب الرمز surface.Present()
لتقديم الزخرفة التالية في حلقة التكرار while. ليس لهذا الإجراء أي تأثير مرئي لأنّه لم يتم عرض أيّ عرض حتى الآن.
#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();
}
}
الآن هو الوقت المناسب لإنشاء مسار العرض باستخدام الرمز البرمجي أدناه. لتسهيل الوصول إلى البيانات، حدِّد متغيّر wgpu::RenderPipeline
في أعلى ملف main.cpp
واستدعِ الدالة CreateRenderPipeline()
في InitGraphics()
.
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 وتشغيله الآن إلى ظهور المثلث الأحمر الذي طال انتظاره في نافذة! حان وقت الاستراحة - فأنت تستحقها.
التجميع باستخدام WebAssembly
لنلقِ الآن نظرة على الحدّ الأدنى من التغييرات المطلوبة لضبط قاعدة الرموز الحالية لرسم هذا المثلث الأحمر في نافذة متصفّح. ومرة أخرى، تم إنشاء التطبيق مقابل Emscripten، وهي أداة لتجميع برامج C/C++ مع WebAssembly، والتي تتضمن عمليات ربط تنفذ webgpu.h أعلى واجهة برمجة تطبيقات JavaScript.
تعديل إعدادات CMake
بعد تثبيت Emscripten، يمكنك تعديل ملف إصدار CMakeLists.txt
على النحو التالي.
الرمز المميّز هو الشيء الوحيد الذي تحتاج إلى تغييره.
- يتم استخدام
set_target_properties
لإضافة امتداد ملف "html" تلقائيًا إلى الملف الهدف. بمعنى آخر، ستنشئ ملف "app.html". - يجب توفّر خيار "رابط التطبيق
USE_WEBGPU
" لتفعيل التوافق مع WebGPU في Emscripten. بدونه، لن يتمكن ملفmain.cpp
من الوصول إلى ملفwebgpu/webgpu_cpp.h
. - يكون خيار رابط التطبيق
USE_GLFW
مطلوب هنا أيضًا حتى تتمكن من إعادة استخدام رمز 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 glfw webgpu_glfw)
endif()
تعديل الرمز
في Emscripten، يتطلب إنشاء wgpu::surface
عنصر لوحة HTML. لإجراء ذلك، يمكنك استدعاء instance.CreateSurface()
وتحديد أداة الاختيار #canvas
لمطابقة عنصر لوحة HTML المناسب في صفحة HTML التي تم إنشاؤها بواسطة 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
إلى نص واجهة magical emcmake
. هذه المرّة، عليك إنشاء التطبيق في مجلد فرعي واحد (build-web
) وبدء خادم HTTP. أخيرًا، افتح المتصفح وانتقِل إلى 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
إلى ذلك الحين، يُرجى إرسال اقتراحات واستفسارات بشأن مشاكل WebGPU التي تخص Emscripten وDawn.
المراجع
يمكنك استكشاف رمز المصدر لهذا التطبيق.
إذا كنت ترغب في التعمق أكثر في إنشاء تطبيقات ثلاثية الأبعاد أصلية في C++ من الصفر باستخدام WebGPU، يمكنك الاطلاع على التعرُّف على وثائق WebGPU لوثائق C++ وأمثلة Dawn Native WebGPU.
إذا كنت مهتمًا ببرامج Rust، يمكنك أيضًا استكشاف مكتبة رسومات wgpu بناءً على WebGPU. ألقِ نظرة على العرض التوضيحي مرحبًا-triangle.
خدمات الإقرار
راجعت هذه المقالة كورنتين واليز وكاي نينوميا وراشيل أندرو.
تصوير مارك أوليفير جودين على UnLaunch.