WebGPU(三):Adapter(适配器)

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;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值