vulakn教程--Drawing a Triangle--Set up--Physical Device and Queue Family

发表于2016-12-14
评论0 578浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

原文链接:Vulkan-tutorial


Physical Device and Queue

好了,我们已经用VkInstance初始化了Vulkan API,是时候选择一个具有我们需要的特性的显卡了(graphics card),事实上,我们可以同时使用多个显卡,为了简单起见,我们只选择第一个满足我们要求的显卡。

1
VkPhysicalDevice physicalDevice=Vk_NULL_HANDLE;  //声明

VkPhysicalDevice 将同Instance一同销毁,这里不必使用VDeleter。


首先我们要考虑两个问题:

  1. 如何获取Physical Devices。
  2. 如何从Physical Devices 中挑选我们想要的那个Physical Device.

如何获取Physical Devices

Vulkan 提供了枚举(enumerate)出当前平台(platform)可用的所有显卡(graphics card or Physical Device)的简便方法:

1
2
3
4
VkResult    vkEnumeratePhysicalDevices(
    VkInstance                                 instance,
    uint32_t*                                   pPhysicalDeviceCount,
    VkPhysicalDevice*                           pPhysicalDevices);

这种模式你绝对不会陌生,在前一章节我们寻找Validation layers时就已经领略过,当时是这样的: vkEnumerateInstanceLayerProperties(..)。 现在我们用相似的方法来搜集所有的Physical Devices:

1
2
3
4
5
6
7
8
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
 
if (deviceCount == 0) {
    throw std::runtime_error("failed to find GPUs with Vulkan support!");
}
std::vector devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

现在我们已经得到了所有Physical Devices, 接下来我们挑选一个满足我们具体需求的显卡。

如何从Physical Devices 中挑选我们想要的那个Physical Device

首先我们需要引入几个重要的概念: 
(1) VkPhysicalDeviceProperties (显卡的属性)

1
2
3
4
5
6
7
8
9
10
11
typedef struct VkPhysicalDeviceProperties {
    uint32_t apiVersion;
    uint32_t driverVersion;
    uint32_t vendorID;
    uint32_t deviceID;
    VkPhysicalDeviceType  deviceType;
    char  deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];
    uint8_t  pipelineCacheUUID[VK_UUID_SIZE];
    VkPhysicalDeviceLimits  limits;
    VkPhysicalDeviceSparseProperties  sparseProperties;
} VkPhysicalDeviceProperties;

好复杂的结构,还好目前我们只对它的VkPhysicalDeviceType deviceType字段感兴趣。现在让我们看看VkPhysicalDeviceType 到底是个啥:

1
2
3
4
5
6
7
typedef enum VkPhysicalDeviceType {
    VK_PHYSICAL_DEVICE_TYPE_OTHER = 0, //other
    VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU = 1, //集成
    VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU = 2,  //独立
    VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU = 3, //虚拟
    VK_PHYSICAL_DEVICE_TYPE_CPU = 4,  //running on cpu
} VkPhysicalDeviceType

你一定不会对获取vkGetPhysicalDeviceProperties的方法感到陌生:

1
2
3
4
void vkGetPhysicalDeviceProperties(
    VkPhysicalDevice            physicalDevice,
    VkPhysicalDeviceProperties*       pProperties
);

(2) VkPhysicalDeviceFeatures ( 特性支持 ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct VkPhysicalDeviceFeatures {
    VkBool32  robustBufferAccess;
    VkBool32     fullDrawIndexUint32;
    VkBool32     imageCubeArray;
    VkBool32     independentBlend;
    VkBool32     geometryShader;
    VkBool32     tessellationShader;
    VkBool32     sampleRateShading;
    VkBool32     dualSrcBlend;
    VkBool32     logicOp;
    VkBool32     multiDrawIndirect;
    VkBool32  drawIndirectFirstInstance;
    VkBool32     depthClamp;
    ...
    ...
} VkPhysicalDeviceFeatures;

这是个庞大(我用…表示它还有很多字段)但简单的结构,每个字段都是bool型,非真(Vk_TRUE)即假(Vk_FALSE)。表示是否对此特性的支持,如果你想了解完整的信息请参考相关文档,毕竟这里只是个栗子。

获取vkGetPhysicalDeviceFeatures的方法:

1
2
3
4
void  vkGetPhysicalDeviceFeatures(
    VkPhysicalDevice                            physicalDevice,
    VkPhysicalDeviceFeatures*                   pFeatures
);

(3) VkQueueFamilyProperties(队列家族属性) 
你会在很多地方看到队列的身影。在Vulkan中,队列有很多种类(感觉family 译成种类好理解),每种队列只支持Vulkan命令的一个子集,比如:一种队列只具有处理计算的命令(processing of compute commands) 或者只具有内存传递的命令(memory transfer related commands)。我们将从Physical Device里枚举出它所拥有的所有队列(VkQueue)的种类,并从中抽取出我们感兴的那种队列,或者说我们要通过判断Physical Device 是否支持我们感兴趣的队列来对Physical Device 进行筛选。

1
2
3
4
5
6
typedef struct VkQueueFamilyProperties {
    VkQueueFlags    queueFlags;  // or VkQueueFlagBits
    uint32_t        queueCount;
    uint32_t        timestampValidBits;
    VkExtent3D      minImageTransferGranularity;
} VkQueueFamilyProperties;

同样为了简单,我们只考虑queueFlagsqueueCount这两个字段。需要注意的是queueFlags的类型在VkQueueFamilyProperties的定义中是VkQueueFlags,它的值属于VkQueueFlagBits,结构如下:

1
2
3
4
5
6
7
typedef enum VkQueueFlagBits {
    VK_QUEUE_GRAPHICS_BIT = 0x00000001,
    VK_QUEUE_COMPUTE_BIT = 0x00000002,
    VK_QUEUE_TRANSFER_BIT = 0x00000004,
    VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
    VK_QUEUE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkQueueFlagBits;

采用同样的模式来遍历制定Physical Device的 VkQueueFamilyProperties

1
2
3
4
void vkGetPhysicalDeviceQueueFamilyProperties(
    VkPhysicalDevice                            physicalDevice,
    uint32_t*                                   pQueueFamilyPropertyCount,
    VkQueueFamilyProperties*                    pQueueFamilyProperties);

挑选Physical Device

所有的准备工作都完成的差不多了,现在开始挑选满足我们需求的Physical Device 流程。

模拟需求: 
我们需要 deviceType 为VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU 类型的显卡,并且支持geometry shaders(后续会讲到) 特性,即VkPhysicalDeviceFeatures. geometryShaderVk_TRUE,此外我们想队列支持图形处理命令,即VkQueueFamilyProperties . queueFlagsVK_QUEUE_GRAPHICS_BIT

总结如下: 
1. 显卡类型为VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU。 
2. 特性支持geometry shaders。 
3. 队列支持图形处理命令。

举个例子,我们有一个挑选函数,它满足了我们对显卡类型和对geometryShader特性的支持:

1
2
3
4
5
6
7
8
9
bool isDeviceSuitable(VkPhysicalDevice device) {
    VkPhysicalDeviceProperties deviceProperties;
    VkPhysicalDeviceFeatures deviceFeatures;
    vkGetPhysicalDeviceProperties(device, &deviceProperties);
    vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
 
    return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
           deviceFeatures.geometryShader;
}

你也可以采用下面一个比较优雅的方法,它对Physical Device进行打分,然后取得最高分的那个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include
...
void pickPhysicalDevice() {
    ...
    // Use an ordered map to automatically sort candidates by increasing score
    std::map<int, vkphysicaldevice=""> candidates;
 
    for (const auto& device : devices) {
        int score = rateDeviceSuitability(device);
        candidates[score] = device;
    }
    // Check if the best candidate is suitable at all
    if (candidates.begin()->first > 0) {
        physicalDevice = candidates.begin()->second;
    } else {
        throw std::runtime_error("failed to find a suitable GPU!");
    }
}
 
int rateDeviceSuitability(VkPhysicalDevice device) {
    ...
    int score = 0;
    // Discrete GPUs have a significant performance advantage
    if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
        score += 1000;
    }
    // Maximum possible size of textures affects graphics quality
    score += deviceProperties.limits.maxImageDimension2D;
    // Application can't function without geometry shaders
    if (!deviceFeatures.geometryShader) {
        return 0;
    }
    return score;
}

当然,我们并不打算在这个教程中使用这个方式,我们的目的仅在于为你提供另一条挑选显卡的思路,使你明确条条大路皆通罗马。

下面我们来添加另一条限制条件:队列支持图形处理命令。 
为了更好的说明问题,我们来添加一个便利的结构:

1
2
3
4
5
6
7
struct QueueFamilyIndices {
    int graphicsFamily = -1;
 
    bool isComplete() {
        return graphicsFamily >= 0;
    }
};

各个字段的含义都非常明确,如果找到这样的队列graphicsFamily就为这种队列的索引(还记得vkGetPhysicalDeviceQueueFamilyProperties(…)传入的pQueueFamilyPropertyCount参数吗,graphicsFamily与此参数关联),否则为-1. 
我们添加findQueueFamilies(…)方法,用来寻找片特定命令的队列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
        QueueFamilyIndices indices;
        uint32_t queueFamilyCount = 0;
        vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
        std::vector queueFamilies(queueFamilyCount);
        vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
 
        int i = 0;
        for (const auto& queueFamily : queueFamilies) {
            if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                indices.graphicsFamily = i;
            }
            if (indices.isComplete()) {
                break;
            }
            i++;
        }
        return indices;
    }

然后再添加一个验证方法 isDeviceSuitable(…):

1
2
3
4
5
bool isDeviceSuitable(VkPhysicalDevice device) {
    QueueFamilyIndices indices = findQueueFamilies(device);
 
    return indices.isComplete();
}

把它们组合在一起看起来是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void pickPhysicalDevice() {
        uint32_t deviceCount = 0;
        vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
        if (deviceCount == 0) {
            throw std::runtime_error("failed to find GPUs with Vulkan support!");
        }
        std::vector devices(deviceCount);
        vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
        for (const auto& device : devices) {
            if (isDeviceSuitable(device)) {
                physicalDevice = device;
                break;
            }
        }
        if (physicalDevice == VK_NULL_HANDLE) {
            throw std::runtime_error("failed to find a suitable GPU!");
        }
    }


源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#define GLFW_INCLUDE_VULKAN
#include
 
#include
#include
#include
#include
#include
 
const int WIDTH = 800;
const int HEIGHT = 600;
 
const std::vector<const char*=""> validationLayers = {
    "VK_LAYER_LUNARG_standard_validation"
};
 
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif
 
VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) {
    auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
    if (func != nullptr) {
        return func(instance, pCreateInfo, pAllocator, pCallback);
    } else {
        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }
}
 
void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) {
    auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
    if (func != nullptr) {
        func(instance, callback, pAllocator);
    }
}
 
template "">
class VDeleter {
public:
    VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {}
 
    VDeleter(std::function<void(t, vkallocationcallbacks*)=""> deletef) {
        this->deleter = [=](T obj) { deletef(obj, nullptr); };
    }
 
    VDeleter(const VDeleter& instance, std::function<void(vkinstance, t,="" vkallocationcallbacks*)=""> deletef) {
        this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); };
    }
 
    VDeleter(const VDeleter& device, std::function<void(vkdevice, t,="" vkallocationcallbacks*)=""> deletef) {
        this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); };
    }
 
    ~VDeleter() {
        cleanup();
    }
 
    T* operator &() {
        cleanup();
        return &object;
    }
 
    operator T() const {
        return object;
    }
 
private:
    T object{VK_NULL_HANDLE};
    std::function<void(t)> deleter;
 
    void cleanup() {
        if (object != VK_NULL_HANDLE) {
            deleter(object);
        }
        object = VK_NULL_HANDLE;
    }
};
 
struct QueueFamilyIndices {
    int graphicsFamily = -1;
 
    bool isComplete() {
        return graphicsFamily >= 0;
    }
};
 
class HelloTriangleApplication {
public:
    void run() {
        initWindow();
        initVulkan();
        mainLoop();
    }
 
private:
    GLFWwindow* window;
 
    VDeleter instance{vkDestroyInstance};
    VDeleter callback{instance, DestroyDebugReportCallbackEXT};
    VDeleter surface{instance, vkDestroySurfaceKHR};
 
    VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
 
    void initWindow() {
        glfwInit();
 
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
 
        window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
    }
 
    void initVulkan() {
        createInstance();
        setupDebugCallback();
        pickPhysicalDevice();
    }
 
    void mainLoop() {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }
 
    void createInstance() {
        if (enableValidationLayers && !checkValidationLayerSupport()) {
            throw std::runtime_error("validation layers requested, but not available!");
        }
 
        VkApplicationInfo appInfo = {};
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        appInfo.pApplicationName = "Hello Triangle";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.pEngineName = "No Engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.apiVersion = VK_API_VERSION_1_0;
 
        VkInstanceCreateInfo createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pApplicationInfo = &appInfo;
 
        auto extensions = getRequiredExtensions();
        createInfo.enabledExtensionCount = extensions.size();
        createInfo.ppEnabledExtensionNames = extensions.data();
 
        if (enableValidationLayers) {
            createInfo.enabledLayerCount = validationLayers.size();
            createInfo.ppEnabledLayerNames = validationLayers.data();
        } else {
            createInfo.enabledLayerCount = 0;
        }
 
        if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
            throw std::runtime_error("failed to create instance!");
        }
    }
 
    void setupDebugCallback() {
        if (!enableValidationLayers) return;
 
        VkDebugReportCallbackCreateInfoEXT createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
        createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
        createInfo.pfnCallback = debugCallback;
 
        if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) {
            throw std::runtime_error("failed to set up debug callback!");
        }
    }
 
    void pickPhysicalDevice() {
        uint32_t deviceCount = 0;
        vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
 
        if (deviceCount == 0) {
            throw std::runtime_error("failed to find GPUs with Vulkan support!");
        }
 
        std::vector devices(deviceCount);
        vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
 
        for (const auto& device : devices) {
            if (isDeviceSuitable(device)) {
                physicalDevice = device;
                break;
            }
        }
 
        if (physicalDevice == VK_NULL_HANDLE) {
            throw std::runtime_error("failed to find a suitable GPU!");
        }
    }
 
    bool isDeviceSuitable(VkPhysicalDevice device) {
        QueueFamilyIndices indices = findQueueFamilies(device);
 
        return indices.isComplete();
    }
 
    QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
        QueueFamilyIndices indices;
 
        uint32_t queueFamilyCount = 0;
        vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
 
        std::vector queueFamilies(queueFamilyCount);
        vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
 
        int i = 0;
        for (const auto& queueFamily : queueFamilies) {
            if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                indices.graphicsFamily = i;
            }
 
            if (indices.isComplete()) {
                break;
            }
 
            i++;
        }
 
        return indices;
    }
 
    std::vector<const char*=""> getRequiredExtensions() {
        std::vector<const char*=""> extensions;
 
        unsigned int glfwExtensionCount = 0;
        const char** glfwExtensions;
        glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
 
        for (unsigned int i = 0; i < glfwExtensionCount; i++) {
            extensions.push_back(glfwExtensions[i]);
        }
 
        if (enableValidationLayers) {
            extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
        }
 
        return extensions;
    }
 
    bool checkValidationLayerSupport() {
        uint32_t layerCount;
        vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
 
        std::vector availableLayers(layerCount);
        vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
 
        for (const char* layerName : validationLayers) {
            bool layerFound = false;
 
            for (const auto& layerProperties : availableLayers) {
                if (strcmp(layerName, layerProperties.layerName) == 0) {
                    layerFound = true;
                    break;
                }
            }
 
            if (!layerFound) {
                return false;
            }
        }
 
        return true;
    }
 
    static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, const char* layerPrefix, const char* msg, void* userData) {
        std::cerr << "validation layer: " << msg << std::endl;
 
        return VK_FALSE;
    }
};
 
int main() {
    HelloTriangleApplication app;
 
    try {
        app.run();
    } catch (const std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
 
    return EXIT_SUCCESS;
}

原文链接

著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台