WebGPU(三):Adapter(适配器)
这篇文章是根据WebGPU 的C++教程进行学习的经验。主要讲述如何在使用C++进行WebGPU开发并且获取GPU的适配器。
获取Adapter
要想和GPU交互,首先要获取WebGPU的适配器,适配器是作为这个WebGPU这个库的入口。(根据WebGPU的后端实现,同一个系统可能提供多个Adapter,可能是高性能的,可能是低性能的)
在Javascript的实现中,这个步骤就是如下代码:
const adapter = await navigator.gpu.requestAdapter(options);
// do something with the adapter
或者如下的代码(通过回调实现异步):
function onAdapterRequestEnded(adapter) {
// do something with the adapter
}
navigator.gpu.requestAdapter(options).then(onAdapterRequestEnded);
下面的回调的逻辑更加接近C/C++的实现,其中C/C++的实现如下:
void onAdapterRequestEnded(
WGPURequestAdapterStatus status, // a success status
WGPUAdapter adapter, // the returned adapter
char const* message, // error message, or nullptr
void* userdata // custom user data, as provided when requesting the adapter
) {
// [...] Do something with the adapter
}
wgpuInstanceRequestAdapter(
instance /* equivalent of navigator.gpu */,
&options,
onAdapterRequestEnded,
nullptr // custom user data, see below
);
在
webgpu.h
中实现的GPU行为遵循以下构建原则:
wgpuSomethingSomeAction(something, ...)
^^^^^^^^^^ // What to do...
^^^^^^^^^ // ...on what type of object
^^^^ // (Common prefix to avoid naming collisions)
类似JavaScript的代码,我们可以把获取Adapter的函数封装成一个类似JS中 await requestAdapter()
的函数。
完整代码参考如下:
#include <cassert>
/**
* Utility function to get a WebGPU adapter, so that
* WGPUAdapter adapter = requestAdapter(options);
* is roughly equivalent to
* const adapter = await navigator.gpu.requestAdapter(options);
*/
WGPUAdapter requestAdapter(WGPUInstance instance, WGPURequestAdapterOptions const * options) {
// A simple structure holding the local information shared with the
// onAdapterRequestEnded callback.
struct UserData {
WGPUAdapter adapter = nullptr;
bool requestEnded = false;
};
UserData userData;
// Callback called by wgpuInstanceRequestAdapter when the request returns
// This is a C++ lambda function, but could be any function defined in the
// global scope. It must be non-capturing (the brackets [] are empty) so
// that it behaves like a regular C function pointer, which is what
// wgpuInstanceRequestAdapter expects (WebGPU being a C API). The workaround
// is to convey what we want to capture through the pUserData pointer,
// provided as the last argument of wgpuInstanceRequestAdapter and received
// by the callback as its last argument.
auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, char const * message, void * pUserData) {
UserData& userData = *reinterpret_cast<UserData*>(pUserData);
if (status == WGPURequestAdapterStatus_Success) {
userData.adapter = adapter;
} else {
std::cout << "Could not get WebGPU adapter: " << message << std::endl;
}
userData.requestEnded = true;
};
// Call to the WebGPU request adapter procedure
wgpuInstanceRequestAdapter(
instance /* equivalent of navigator.gpu */,
options,
onAdapterRequestEnded,
(void*)&userData
);
// In theory we should wait until onAdapterReady has been called, which
// could take some time (what the 'await' keyword does in the JavaScript
// code). In practice, we know that when the wgpuInstanceRequestAdapter()
// function returns its callback has been called.
assert(userData.requestEnded);
return userData.adapter;
}
然后在主函数中,我们可以查看这个Adapter的地址值:
std::cout << "Requesting adapter..." << std::endl;
WGPURequestAdapterOptions adapterOpts = {};
WGPUAdapter adapter = requestAdapter(instance, &adapterOpts);
std::cout << "Got adapter: " << adapter << std::endl;
最后还需要销毁数据:
wgpuAdapterRelease(adapter);
小结
这里用到库函数为:
wgpuInstanceRequestAdapter(
instance /* equivalent of navigator.gpu */,
options,
onAdapterRequestEnded,
(void*)&userData
);
其中该库函数可以提供回调函数参数,所以可以利用这个特性构建回调函数:
auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, char const * message, void * pUserData) {
UserData& userData = *reinterpret_cast<UserData*>(pUserData);
if (status == WGPURequestAdapterStatus_Success) {
userData.adapter = adapter;
} else {
std::cout << "Could not get WebGPU adapter: " << message << std::endl;
}
userData.requestEnded = true;
};
用到的数据结构为
WGPURequestAdapterOptions adapterOpts = {}; //一个参数,应该是决定该adapter行为参数,默认为空
WGPUInstance instance; 之前使用的,找到某个GPU实例,注意Adapter和instance,Adapter是作为逻辑上的GPU
完整测试代码为(包括GLFW的部分代码):
#include <iostream>
#include <GLFW/glfw3.h> // Add libs after the setting in CMakeLists.txt
#include <webgpu/webgpu.h>
#include <cassert> //Used for debugging
/**
* Utility function to get a WebGPU adapter, so that
* WGPUAdapter adapter = requestAdapter(options);
* is roughly equivalent to
* const adapter = await navigator.gpu.requestAdapter(options);
*/
WGPUAdapter requestAdapter(WGPUInstance instance, WGPURequestAdapterOptions const * options) {
// A simple structure holding the local information shared with the
// onAdapterRequestEnded callback.
struct UserData {
WGPUAdapter adapter = nullptr;
bool requestEnded = false;
};
UserData userData;
// Callback called by wgpuInstanceRequestAdapter when the request returns
// This is a C++ lambda function, but could be any function defined in the
// global scope. It must be non-capturing (the brackets [] are empty) so
// that it behaves like a regular C function pointer, which is what
// wgpuInstanceRequestAdapter expects (WebGPU being a C API). The workaround
// is to convey what we want to capture through the pUserData pointer,
// provided as the last argument of wgpuInstanceRequestAdapter and received
// by the callback as its last argument.
auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, char const * message, void * pUserData) {
UserData& userData = *reinterpret_cast<UserData*>(pUserData);
if (status == WGPURequestAdapterStatus_Success) {
userData.adapter = adapter;
} else {
std::cout << "Could not get WebGPU adapter: " << message << std::endl;
}
userData.requestEnded = true;
};
// Call to the WebGPU request adapter procedure
wgpuInstanceRequestAdapter(
instance /* equivalent of navigator.gpu */,
options,
onAdapterRequestEnded,
(void*)&userData
);
// In theory we should wait until onAdapterReady has been called, which
// could take some time (what the 'await' keyword does in the JavaScript
// code). In practice, we know that when the wgpuInstanceRequestAdapter()
// function returns its callback has been called.
assert(userData.requestEnded);
return userData.adapter;
}
int main (int, char**) {
std::cout << "Hello, world!" << std::endl;
// First all the call of GLFW must be defined between its initialization and termination
glfwInit();//Initialization for GLFW
if (!glfwInit()) {
std::cerr << "Could not initialize GLFW!" << std::endl;
return 1;
}else{
// Create the window
GLFWwindow* window = glfwCreateWindow(640, 480, "Learn WebGPU", NULL, NULL);
//...
//...
if (!window) {
std::cerr << "Could not open window!" << std::endl;
glfwTerminate();
return 1;
}else{
WGPUInstanceDescriptor desc = {};
desc.nextInChain = nullptr;// 某个字符的描述字段 保留为以后的自定义拓展
// 2. We create the instance using this descriptor
WGPUInstance instance = wgpuCreateInstance(&desc);
// 3. We can check whether there is actually an instance created
if (!instance) {
std::cerr << "Could not initialize WebGPU!" << std::endl;
return 1;
}
// 4. Display the object (WGPUInstance is a simple pointer, it may be
// copied around without worrying about its size).
std::cout << "WGPU instance: " << instance << std::endl;
// 5. Get the adapter via instance
std::cout << "Requesting adapter..." << std::endl;
WGPURequestAdapterOptions adapterOpts = {};
WGPUAdapter adapter = requestAdapter(instance, &adapterOpts);
std::cout << "Got adapter: " << adapter << std::endl;
wgpuAdapterRelease(adapter);// Never forget to destory it.
while (!glfwWindowShouldClose(window)) {
// Check whether the user clicked on the close button (and any other
// mouse/key event, which we don't use so far)
// 1. We create a descriptor
glfwPollEvents();
}
}
glfwDestroyWindow(window);//after all the process destory the window
}
glfwTerminate();//Termination for GLFW
return 0;
}
在我的电脑中运行过程有一个警告:
Hello, world!
WGPU instance: 0000012FD53B2410
Requesting adapter...
Warning: loader_get_json: Failed to open JSON file G:\Steam\SteamOverlayVulkanLayer64.json
Warning: loader_get_json: Failed to open JSON file G:\Steam\SteamFossilizeVulkanLayer64.json
Warning: windows_read_data_files_in_registry: Registry lookup failed to get layer manifest files.
Got adapter: 0000012FD5365510
这个应该是因为自己本来安装Steam的位置变更了,但是注册表的东西没有更新,先暂时不管。
注意cmake的编译问题
使用下面的指令生成项目树之后
cmake . -B build
仅仅是源代码中有更新,只需要运行下面的指令,下面的指令指定目录编译项目:
cmake --build build
界面
在Adapter Request 过程中,我们需要传递option参数,告诉adapter我们使用哪一个界面进行图形的描绘。
基本的代码逻辑思路如下:
{{Get the surface}}
WGPURequestAdapterOptions adapterOpts = {};
adapterOpts.nextInChain = nullptr;
adapterOpts.compatibleSurface = surface;
WGPUAdapter adapter = requestAdapter(instance, &adapterOpts);
上面的代码中获取surface的过程还没有实现出来,想要获取界面需要OS的服务,GLFW官方实现的代码还没有提供这种接口,但是根据我参考的链接作者,他实现了GLFW的一个拓展可以提供surface的接口给webGPU,即glfw3webgpu。
下载拓展源码,然后加入到项目中,编写CMakeLists.txt.
增加库目录:
add_subdirectory(glfw3webgpu)
链接库:
target_link_libraries(App PRIVATE glfw webgpu glfw3webgpu)
target_copy_webgpu_binaries(App) # 这个是为了跨平台增加的 采用Dawn的源码构建其实没有用,对于Rust的wgpu有用
**最后一步:**告诉GLFW不要关注图形APIs的设置,因为GLFW并不知道webGPU的存在,而且我们不会把它默认设置的参数用于其他APIs。代码如下:
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // NEW
GLFWwindow* window = glfwCreateWindow(640, 480, "Learn WebGPU", NULL, NULL);
NOTES: 注意
glfwWindowHint
函数可以传递一些可选的参数给glfwCreateWindow
.在这个例子里面我们告诉它默认初始化非特定的图形APIs,因为我们需要自己管理这个图形APIs. 更多的资料参考这里这个关于glfwCreateWindow
的资料。
检查Adapter
Adapter对象提供提层的实现以及硬件信息,并且告知什么可做什么不可做。
下面我们关注一个函数wgpuAdapterEnumerateFeatures
这个函数枚举了WebGPU的实现特性,并且接近原生的WebGPU。
我们调用这个函数两次,第一次我们传递一个空指针获取特性的数量,第二次我们传递一个vector变量的数据起始地址去存储其特性名字。
代码如下:
#include <vector>
std::vector<WGPUFeatureName> features;
// Call the function a first time with a null return address, just to get
// the entry count.
size_t featureCount = wgpuAdapterEnumerateFeatures(adapter, nullptr);
// Allocate memory (could be a new, or a malloc() if this were a C program)
features.resize(featureCount);
// Call the function a second time, with a non-null return address
wgpuAdapterEnumerateFeatures(adapter, features.data());
std::cout << "Adapter features:" << std::endl;
for (auto f : features) {
std::cout << " - " << f << std::endl;
}
测试结果如下:
Got adapter: 000001D738317CD0
Adapter features:
- 5
- 1
- 2
- 8
- 10
- 11
- 1002
- 1004
- 1007
- 1008
- 1009
查找位于webgpu.h 中的枚举变量WGPUFeatureName
可以得知各个数值的含义:
typedef enum WGPUFeatureName {
WGPUFeatureName_Undefined = 0x00000000,
WGPUFeatureName_DepthClipControl = 0x00000001,
WGPUFeatureName_Depth32FloatStencil8 = 0x00000002,
WGPUFeatureName_TimestampQuery = 0x00000003,
WGPUFeatureName_PipelineStatisticsQuery = 0x00000004,
WGPUFeatureName_TextureCompressionBC = 0x00000005,
WGPUFeatureName_TextureCompressionETC2 = 0x00000006,
WGPUFeatureName_TextureCompressionASTC = 0x00000007,
WGPUFeatureName_IndirectFirstInstance = 0x00000008,
WGPUFeatureName_ShaderF16 = 0x00000009,
WGPUFeatureName_RG11B10UfloatRenderable = 0x0000000A,
WGPUFeatureName_BGRA8UnormStorage = 0x0000000B,
WGPUFeatureName_Float32Filterable = 0x0000000C,
WGPUFeatureName_DawnShaderFloat16 = 0x000003E9,
WGPUFeatureName_DawnInternalUsages = 0x000003EA,
WGPUFeatureName_DawnMultiPlanarFormats = 0x000003EB,
WGPUFeatureName_DawnNative = 0x000003EC,
WGPUFeatureName_ChromiumExperimentalDp4a = 0x000003ED,
WGPUFeatureName_TimestampQueryInsidePasses = 0x000003EE,
WGPUFeatureName_ImplicitDeviceSynchronization = 0x000003EF,
WGPUFeatureName_SurfaceCapabilities = 0x000003F0,
WGPUFeatureName_TransientAttachments = 0x000003F1,
WGPUFeatureName_MSAARenderToSingleSampled = 0x000003F2,
WGPUFeatureName_Force32 = 0x7FFFFFFF
} WGPUFeatureName WGPU_ENUM_ATTRIBUTE;