vulakn教程--Drawing a Triangle--Set up--Physical Device and Queue Family
原文链接:Vulkan-tutorial
Physical Device and Queue
好了,我们已经用VkInstance初始化了Vulkan API,是时候选择一个具有我们需要的特性的显卡了(graphics card),事实上,我们可以同时使用多个显卡,为了简单起见,我们只选择第一个满足我们要求的显卡。
1 VkPhysicalDevice physicalDevice=Vk_NULL_HANDLE;
//声明
VkPhysicalDevice 将同Instance一同销毁,这里不必使用VDeleter。
首先我们要考虑两个问题:
- 如何获取Physical Devices。
- 如何从Physical Devices 中挑选我们想要的那个Physical Device.
如何获取Physical Devices
Vulkan 提供了枚举(enumerate)出当前平台(platform)可用的所有显卡(graphics card or Physical Device)的简便方法:
1234 VkResult vkEnumeratePhysicalDevices(
VkInstance instance,
uint32_t* pPhysicalDeviceCount,
VkPhysicalDevice* pPhysicalDevices);
这种模式你绝对不会陌生,在前一章节我们寻找Validation layers时就已经领略过,当时是这样的: vkEnumerateInstanceLayerProperties(..)
。 现在我们用相似的方法来搜集所有的Physical Devices:
12345678 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
到底是个啥:
1234567 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
的方法感到陌生:
1234 void
vkGetPhysicalDeviceProperties(
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceProperties* pProperties
);
(2) VkPhysicalDeviceFeatures
( 特性支持 ):
12345678910111213141516 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的方法:
1234 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 进行筛选。
123456 typedef
struct
VkQueueFamilyProperties {
VkQueueFlags queueFlags;
// or VkQueueFlagBits
uint32_t queueCount;
uint32_t timestampValidBits;
VkExtent3D minImageTransferGranularity;
} VkQueueFamilyProperties;
同样为了简单,我们只考虑queueFlags
和queueCount
这两个字段。需要注意的是queueFlags
的类型在VkQueueFamilyProperties
的定义中是VkQueueFlags
,它的值属于VkQueueFlagBits
,结构如下:
1234567 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
:
1234 void
vkGetPhysicalDeviceQueueFamilyProperties(
VkPhysicalDevice physicalDevice,
uint32_t* pQueueFamilyPropertyCount,
VkQueueFamilyProperties* pQueueFamilyProperties);
挑选Physical Device
所有的准备工作都完成的差不多了,现在开始挑选满足我们需求的Physical Device 流程。
模拟需求:
我们需要 deviceType
为VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
类型的显卡,并且支持geometry shaders(后续会讲到) 特性,即VkPhysicalDeviceFeatures. geometryShader
为Vk_TRUE
,此外我们想队列支持图形处理命令,即VkQueueFamilyProperties . queueFlags
为VK_QUEUE_GRAPHICS_BIT
。
总结如下:
1. 显卡类型为VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
。
2. 特性支持geometry shaders。
3. 队列支持图形处理命令。
举个例子,我们有一个挑选函数,它满足了我们对显卡类型和对geometryShader
特性的支持:
123456789 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进行打分,然后取得最高分的那个:
12345678910111213141516171819202122232425262728293031323334 #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;
}
当然,我们并不打算在这个教程中使用这个方式,我们的目的仅在于为你提供另一条挑选显卡的思路,使你明确条条大路皆通罗马。
下面我们来添加另一条限制条件:队列支持图形处理命令。
为了更好的说明问题,我们来添加一个便利的结构:
1234567 struct
QueueFamilyIndices {
int
graphicsFamily = -1;
bool
isComplete() {
return
graphicsFamily >= 0;
}
};
各个字段的含义都非常明确,如果找到这样的队列graphicsFamily
就为这种队列的索引(还记得vkGetPhysicalDeviceQueueFamilyProperties(…)
传入的pQueueFamilyPropertyCount
参数吗,graphicsFamily
与此参数关联),否则为-1.
我们添加findQueueFamilies(…)
方法,用来寻找片特定命令的队列:
12345678910111213141516171819 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
(…):
12345 bool
isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
return
indices.isComplete();
}
把它们组合在一起看起来是这样的:
123456789101112131415161718 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!"
);
}
}
源码:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 #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;
}