Skip to content
Snippets Groups Projects
Context.cpp 9.67 KiB
Newer Older
namespace vkcv {

	Context::Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device)
		: m_instance(instance), m_physicalDevice(physicalDevice), m_device(device)
	{}

	Context::~Context() {
		m_device.destroy();
		m_instance.destroy();
	}

	Context Context::create(const char* applicationName, uint32_t applicationVersion, uint32_t queueCount, std::vector<vk::QueueFlagBits> queueFlags, std::vector<const char*> instanceExtensions, std::vector<const char*> deviceExtensions) {
		glfwInit();
		// check for layer support
		uint32_t layerCount = 0;
		vk::enumerateInstanceLayerProperties(&layerCount, nullptr);
		std::vector<vk::LayerProperties> layerProperties(layerCount);
		vk::enumerateInstanceLayerProperties(&layerCount, layerProperties.data());
		std::vector<const char*> supportedLayers;
		for (auto& elem : layerProperties) {
			supportedLayers.push_back(elem.layerName);
// if in debug mode, check if validation layers are supported. Enable them if supported
#if _DEBUG
		std::vector<const char*> validationLayers = {
		"VK_LAYER_KHRONOS_validation"
		};
		if (!Context::checkSupport(supportedLayers, validationLayers)) {
			throw std::runtime_error("Validation layers requested but not available!");
		// check for extension support
		std::vector<vk::ExtensionProperties> instanceExtensionProperties = vk::enumerateInstanceExtensionProperties();
		std::vector<const char*> supportedExtensions;
		for (auto& elem : instanceExtensionProperties) {
			supportedExtensions.push_back(elem.extensionName);
		}
		if (!checkSupport(supportedExtensions, instanceExtensions)) {
			throw std::runtime_error("The requested instance extensions are not supported!");

		// for GLFW: get all required extensions
		std::vector<const char*> requiredExtensions = Context::getRequiredExtensions();
		instanceExtensions.insert(instanceExtensions.end(), requiredExtensions.begin(), requiredExtensions.end());

		const vk::ApplicationInfo applicationInfo(
			applicationName,
			applicationVersion,
			"vkCV",
			VK_MAKE_VERSION(0, 0, 1),
			VK_HEADER_VERSION_COMPLETE
		vk::InstanceCreateInfo instanceCreateInfo(
			vk::InstanceCreateFlags(),
			&applicationInfo,
			static_cast<uint32_t>(instanceExtensions.size()),
			instanceExtensions.data()
#if _DEBUG
		instanceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
		instanceCreateInfo.ppEnabledLayerNames = validationLayers.data();
#endif

		vk::Instance instance = vk::createInstance(instanceCreateInfo);

		std::vector<vk::PhysicalDevice> physicalDevices = instance.enumeratePhysicalDevices();
		vk::PhysicalDevice physicalDevice = pickPhysicalDevice(instance);

		// check for physical device extension support
		std::vector<vk::ExtensionProperties> deviceExtensionProperties = physicalDevice.enumerateDeviceExtensionProperties();
		supportedExtensions.clear();
		for (auto& elem : deviceExtensionProperties) {
			supportedExtensions.push_back(elem.extensionName);
		}
		if (!checkSupport(supportedExtensions, deviceExtensions)) {
			throw std::runtime_error("The requested device extensions are not supported by the physical device!");

		// create required queues
		std::vector<vk::DeviceQueueCreateInfo> qCreateInfos = getQueueCreateInfos(physicalDevice, queueCount, queueFlags);

		vk::DeviceCreateInfo deviceCreateInfo(
			vk::DeviceCreateFlags(),
			qCreateInfos.size(),
			qCreateInfos.data(),
			0,
			nullptr,
			deviceExtensions.size(),
			deviceExtensions.data(),
			nullptr		// Should our device use some features??? If yes: TODO

#if _DEBUG
		deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
		deviceCreateInfo.ppEnabledLayerNames = validationLayers.data();
#endif


		vk::Device device = physicalDevice.createDevice(deviceCreateInfo);
		// TODO: implement device.getQueue() to access the queues, if needed
		return Context(instance, physicalDevice, device);
	}

	const vk::Instance& Context::getInstance() const {
	const vk::PhysicalDevice& Context::getPhysicalDevice() const {
	const vk::Device& Context::getDevice() const {

	/// <summary>
	/// All existing physical devices will be evaluated by 
	/// </summary>
	/// <param name="instance">The instance.</param>
	/// <returns>The optimal physical device.</returns>
	/// <seealso cref="Context.deviceScore">
	vk::PhysicalDevice Context::pickPhysicalDevice(vk::Instance& instance) {
		vk::PhysicalDevice phyDevice;
		std::vector<vk::PhysicalDevice> devices = instance.enumeratePhysicalDevices();

		if (devices.size() == 0) {
			throw std::runtime_error("failed to find GPUs with Vulkan support!");
		}
		int max_score = -1;
		for (const auto& device : devices) {
			int score = deviceScore(device);
			if (score > max_score) {
				max_score = score;
				phyDevice = device;
			}
		}

		if (max_score == -1) {
			throw std::runtime_error("failed to find a suitable GPU!");
		}

		return phyDevice;
	}

	/// <summary>
	/// The physical device is evaluated by three categories: discrete GPU vs. integrated GPU, amount of queues and
	/// its abilities, and VRAM.
	/// </summary>
	/// <param name="physicalDevice"> The physical device. </param>
	/// <returns></returns>
	int Context::deviceScore(const vk::PhysicalDevice& physicalDevice) {
		int score = 0;
		vk::PhysicalDeviceProperties properties = physicalDevice.getProperties();
		std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties();

		// for every queue family compute queue flag bits and the amount of queues
		for (const auto& qFamily : qFamilyProperties) {
			uint32_t qCount = qFamily.queueCount;
			uint32_t bitCount = (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0)
				+ (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0)
				+ (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0)
				+ (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eSparseBinding) != 0);
			score += qCount * bitCount;
		}

		// compute the VRAM of the physical device
		vk::PhysicalDeviceMemoryProperties memoryProperties = physicalDevice.getMemoryProperties();
		int vram = static_cast<int>(memoryProperties.memoryHeaps[0].size / 1E9);
		score *= vram;

		if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) {
			score *= 2;
		}
		else if (properties.deviceType != vk::PhysicalDeviceType::eIntegratedGpu) {
			score = -1;
		}

		return score;
	}

	/// <summary>
	/// Creates a candidate list of queues that all meet the desired flags and then creates the maximum possible number
	/// of queues. If the number of desired queues is not sufficient, the remaining queues are created from the next
	/// candidate from the list.
	/// </summary>
	/// <param name="physicalDevice">The physical device</param>
	/// <param name="queueCount">The amount of queues to be created</param>
	/// <param name="queueFlags">The abilities which have to be supported by any created queue</param>
	/// <returns></returns>
	std::vector<vk::DeviceQueueCreateInfo> Context::getQueueCreateInfos(vk::PhysicalDevice& physicalDevice, uint32_t queueCount, std::vector<vk::QueueFlagBits>& queueFlags) {
		std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
		std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties();
		std::vector<vk::QueueFamilyProperties> qFamilyCandidates;

		// search for queue families which support the desired queue flag bits
		for (auto& qFamily : qFamilyProperties) {
			bool supported = true;
			for (auto qFlag : queueFlags) {
				supported = supported && (static_cast<uint32_t>(qFlag & qFamily.queueFlags) != 0);
			}
			if (supported) {
				qFamilyCandidates.push_back(qFamily);
			}
		}

		uint32_t create = queueCount;
		for (int i = 0; i < qFamilyCandidates.size() && create > 0; i++) {
			const int maxCreatableQueues = std::min(create, qFamilyCandidates[i].queueCount);
			float* qPriorities = new float[maxCreatableQueues];		// TO CHECK: this seems to solve validation layer errors but the array pointer will not be deleted
			std::fill_n(qPriorities, maxCreatableQueues, 1.f);		// all queues have the same priorities
			vk::DeviceQueueCreateInfo qCreateInfo(
				vk::DeviceQueueCreateFlags(),
				i,
				maxCreatableQueues,
				qPriorities
			);
			queueCreateInfos.push_back(qCreateInfo);
			create -= maxCreatableQueues;
		}

		return queueCreateInfos;
	}

	/// <summary>
	/// With the help of the reference <paramref name="supported"> all elements in <paramref name="check"/> checked,
	/// if they are supported by the physical device.
	/// </summary>
	/// <param name="supported">The reference that can be used to check <paramref name="check"/></param>
	/// <param name="check">The elements to be checked</param>
	/// <returns>True, if all elements in <param name="check"> are supported</returns>
	bool Context::checkSupport(std::vector<const char*>& supported, std::vector<const char*>& check) {
		for (auto checkElem : check) {
			bool found = false;
			for (auto supportedElem : supported) {
				if (strcmp(supportedElem, checkElem) == 0) {
					found = true;
					break;
				}
			}
			if (!found)
				return false;
		}
		return true;
	}

	/// <summary>
	/// Gets all extensions required, i.e. GLFW and advanced debug extensions.
	/// </summary>
	/// <returns>The required extensions</returns>
	std::vector<const char*> Context::getRequiredExtensions() {
		uint32_t glfwExtensionCount = 0;
		const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
		std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

#if _DEBUG
	extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
#endif