text: #define GLFW_INCLUDE_VULKAN
  #include <GLFW/glfw3.h>
  
  #include <iostream>
  #include <fstream>
  #include <stdexcept>
  #include <algorithm>
  #include <vector>
  #include <cstring>
  #include <set>
  
  const int WIDTH = 800;
  const int HEIGHT = 600;
  
  const std::vector<const char*> validationLayers = {
  	"VK_LAYER_LUNARG_standard_validation"
  };
  
  const std::vector<const char*> deviceExtensions = {
  	VK_KHR_SWAPCHAIN_EXTENSION_NAME
  };
  
  #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);
  	}
  }
  
  struct QueueFamilyIndices {
  	int graphicsFamily = -1;
  	int presentFamily = -1;
  
  	bool isComplete() {
  		return graphicsFamily >= 0 && presentFamily >= 0;
  	}
  };
  
  struct SwapChainSupportDetails {
  	VkSurfaceCapabilitiesKHR capabilities;
  	std::vector<VkSurfaceFormatKHR> formats;
  	std::vector<VkPresentModeKHR> presentModes;
  };
  
  class HelloTriangleApplication {
  public:
  	void run() {
  		initWindow();
  		initVulkan();
  		mainLoop();
  		cleanup();
  	}
  
  private:
  	GLFWwindow* window;
  
  	VkInstance instance;
  	VkDebugReportCallbackEXT callback;
  	VkSurfaceKHR surface;
  
  	VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
  	VkDevice device;
  
  	VkQueue graphicsQueue;
  	VkQueue presentQueue;
  
  	VkSwapchainKHR swapChain;
  	std::vector<VkImage> swapChainImages;
  	VkFormat swapChainImageFormat;
  	VkExtent2D swapChainExtent;
  	std::vector<VkImageView> swapChainImageViews;
  
  	VkPipelineLayout pipelineLayout;
  
  	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();
  		createSurface();
  		pickPhysicalDevice();
  		createLogicalDevice();
  		createSwapChain();
  		createImageViews();
  		createGraphicsPipeline();
  	}
  
  	void mainLoop() {
  		while (!glfwWindowShouldClose(window)) {
  			glfwPollEvents();
  		}
  	}
  
  	void cleanup() {
  		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
  
  		for (size_t i = 0; i < swapChainImageViews.size(); i++) {
  			vkDestroyImageView(device, swapChainImageViews[i], nullptr);
  		}
  
  		vkDestroySwapchainKHR(device, swapChain, nullptr);
  		vkDestroyDevice(device, nullptr);
  		DestroyDebugReportCallbackEXT(instance, callback, nullptr);
  		vkDestroySurfaceKHR(instance, surface, nullptr);
  		vkDestroyInstance(instance, nullptr);
  
  		glfwDestroyWindow(window);
  
  		glfwTerminate();
  	}
  
  	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 = static_cast<uint32_t>(extensions.size());
  		createInfo.ppEnabledExtensionNames = extensions.data();
  
  		if (enableValidationLayers) {
  			createInfo.enabledLayerCount = static_cast<uint32_t>(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 createSurface() {
  		if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
  			throw std::runtime_error("failed to create window surface!");
  		}
  	}
  
  	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<VkPhysicalDevice> 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!");
  		}
  	}
  
  	void createLogicalDevice() {
  		QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
  
  		std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
  		std::set<int> uniqueQueueFamilies = { indices.graphicsFamily, indices.presentFamily };
  
  		float queuePriority = 1.0f;
  		for (int queueFamily : uniqueQueueFamilies) {
  			VkDeviceQueueCreateInfo queueCreateInfo = {};
  			queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
  			queueCreateInfo.queueFamilyIndex = queueFamily;
  			queueCreateInfo.queueCount = 1;
  			queueCreateInfo.pQueuePriorities = &queuePriority;
  			queueCreateInfos.push_back(queueCreateInfo);
  		}
  
  		VkPhysicalDeviceFeatures deviceFeatures = {};
  
  		VkDeviceCreateInfo createInfo = {};
  		createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
  
  		createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
  		createInfo.pQueueCreateInfos = queueCreateInfos.data();
  
  		createInfo.pEnabledFeatures = &deviceFeatures;
  
  		createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
  		createInfo.ppEnabledExtensionNames = deviceExtensions.data();
  
  		if (enableValidationLayers) {
  			createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
  			createInfo.ppEnabledLayerNames = validationLayers.data();
  		}
  		else {
  			createInfo.enabledLayerCount = 0;
  		}
  
  		if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
  			throw std::runtime_error("failed to create logical device!");
  		}
  
  		vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue);
  		vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue);
  	}
  
  	void createSwapChain() {
  		SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);
  
  		VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
  		VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
  		VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
  
  		uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
  		if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
  			imageCount = swapChainSupport.capabilities.maxImageCount;
  		}
  
  		VkSwapchainCreateInfoKHR createInfo = {};
  		createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
  		createInfo.surface = surface;
  
  		createInfo.minImageCount = imageCount;
  		createInfo.imageFormat = surfaceFormat.format;
  		createInfo.imageColorSpace = surfaceFormat.colorSpace;
  		createInfo.imageExtent = extent;
  		createInfo.imageArrayLayers = 1;
  		createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
  
  		QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
  		uint32_t queueFamilyIndices[] = { (uint32_t)indices.graphicsFamily, (uint32_t)indices.presentFamily };
  
  		if (indices.graphicsFamily != indices.presentFamily) {
  			createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
  			createInfo.queueFamilyIndexCount = 2;
  			createInfo.pQueueFamilyIndices = queueFamilyIndices;
  		}
  		else {
  			createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
  		}
  
  		createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
  		createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
  		createInfo.presentMode = presentMode;
  		createInfo.clipped = VK_TRUE;
  
  		createInfo.oldSwapchain = VK_NULL_HANDLE;
  
  		if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
  			throw std::runtime_error("failed to create swap chain!");
  		}
  
  		vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
  		swapChainImages.resize(imageCount);
  		vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
  
  		swapChainImageFormat = surfaceFormat.format;
  		swapChainExtent = extent;
  	}
  
  	void createImageViews() {
  		swapChainImageViews.resize(swapChainImages.size());
  
  		for (size_t i = 0; i < swapChainImages.size(); i++) {
  			VkImageViewCreateInfo createInfo = {};
  			createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
  			createInfo.image = swapChainImages[i];
  			createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
  			createInfo.format = swapChainImageFormat;
  			createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
  			createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
  			createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
  			createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
  			createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  			createInfo.subresourceRange.baseMipLevel = 0;
  			createInfo.subresourceRange.levelCount = 1;
  			createInfo.subresourceRange.baseArrayLayer = 0;
  			createInfo.subresourceRange.layerCount = 1;
  
  			if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) {
  				throw std::runtime_error("failed to create image views!");
  			}
  		}
  	}
  
  	void createGraphicsPipeline() {
  		auto vertShaderCode = readFile("shaders/vert.spv");
  		auto fragShaderCode = readFile("shaders/frag.spv");
  
  		VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
  		VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);
  
  		VkPipelineShaderStageCreateInfo vertShaderStageInfo = {};
  		vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
  		vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
  		vertShaderStageInfo.module = vertShaderModule;
  		vertShaderStageInfo.pName = "main";
  
  		VkPipelineShaderStageCreateInfo fragShaderStageInfo = {};
  		fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
  		fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
  		fragShaderStageInfo.module = fragShaderModule;
  		fragShaderStageInfo.pName = "main";
  
  		VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo };
  
  		VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
  		vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
  		vertexInputInfo.vertexBindingDescriptionCount = 0;
  		vertexInputInfo.vertexAttributeDescriptionCount = 0;
  
  		VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
  		inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
  		inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
  		inputAssembly.primitiveRestartEnable = VK_FALSE;
  
  		VkViewport viewport = {};
  		viewport.x = 0.0f;
  		viewport.y = 0.0f;
  		viewport.width = (float)swapChainExtent.width;
  		viewport.height = (float)swapChainExtent.height;
  		viewport.minDepth = 0.0f;
  		viewport.maxDepth = 1.0f;
  
  		VkRect2D scissor = {};
  		scissor.offset = { 0, 0 };
  		scissor.extent = swapChainExtent;
  
  		VkPipelineViewportStateCreateInfo viewportState = {};
  		viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
  		viewportState.viewportCount = 1;
  		viewportState.pViewports = &viewport;
  		viewportState.scissorCount = 1;
  		viewportState.pScissors = &scissor;
  
  		VkPipelineRasterizationStateCreateInfo rasterizer = {};
  		rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
  		rasterizer.depthClampEnable = VK_FALSE;
  		rasterizer.rasterizerDiscardEnable = VK_FALSE;
  		rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
  		rasterizer.lineWidth = 1.0f;
  		rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
  		rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
  		rasterizer.depthBiasEnable = VK_FALSE;
  
  		VkPipelineMultisampleStateCreateInfo multisampling = {};
  		multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
  		multisampling.sampleShadingEnable = VK_FALSE;
  		multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
  
  		VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
  		colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
  		colorBlendAttachment.blendEnable = VK_FALSE;
  
  		VkPipelineColorBlendStateCreateInfo colorBlending = {};
  		colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
  		colorBlending.logicOpEnable = VK_FALSE;
  		colorBlending.logicOp = VK_LOGIC_OP_COPY;
  		colorBlending.attachmentCount = 1;
  		colorBlending.pAttachments = &colorBlendAttachment;
  		colorBlending.blendConstants[0] = 0.0f;
  		colorBlending.blendConstants[1] = 0.0f;
  		colorBlending.blendConstants[2] = 0.0f;
  		colorBlending.blendConstants[3] = 0.0f;
  
  		VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
  		pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
  		pipelineLayoutInfo.setLayoutCount = 0;
  		pipelineLayoutInfo.pushConstantRangeCount = 0;
  
  		if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
  			throw std::runtime_error("failed to create pipeline layout!");
  		}
  
  		vkDestroyShaderModule(device, fragShaderModule, nullptr);
  		vkDestroyShaderModule(device, vertShaderModule, nullptr);
  	}
  
  	VkShaderModule createShaderModule(const std::vector<char>& code) {
  		VkShaderModuleCreateInfo createInfo = {};
  		createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
  		createInfo.codeSize = code.size();
  
  		createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
  
  		VkShaderModule shaderModule;
  		if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
  			throw std::runtime_error("failed to create shader module!");
  		}
  
  		return shaderModule;
  	}
  
  	VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
  		if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
  			return { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
  		}
  
  		for (const auto& availableFormat : availableFormats) {
  			if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
  				return availableFormat;
  			}
  		}
  
  		return availableFormats[0];
  	}
  
  	VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR> availablePresentModes) {
  		VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR;
  
  		for (const auto& availablePresentMode : availablePresentModes) {
  			if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
  				return availablePresentMode;
  			}
  			else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
  				bestMode = availablePresentMode;
  			}
  		}
  
  		return bestMode;
  	}
  
  	VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
  		if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
  			return capabilities.currentExtent;
  		}
  		else {
  			VkExtent2D actualExtent = { WIDTH, HEIGHT };
  
  			actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width));
  			actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height));
  
  			return actualExtent;
  		}
  	}
  
  	SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
  		SwapChainSupportDetails details;
  
  		vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
  
  		uint32_t formatCount;
  		vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
  
  		if (formatCount != 0) {
  			details.formats.resize(formatCount);
  			vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
  		}
  
  		uint32_t presentModeCount;
  		vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
  
  		if (presentModeCount != 0) {
  			details.presentModes.resize(presentModeCount);
  			vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
  		}
  
  		return details;
  	}
  
  	bool isDeviceSuitable(VkPhysicalDevice device) {
  		QueueFamilyIndices indices = findQueueFamilies(device);
  
  		bool extensionsSupported = checkDeviceExtensionSupport(device);
  
  		bool swapChainAdequate = false;
  		if (extensionsSupported) {
  			SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
  			swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
  		}
  
  		return indices.isComplete() && extensionsSupported && swapChainAdequate;
  	}
  
  	bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
  		uint32_t extensionCount;
  		vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
  
  		std::vector<VkExtensionProperties> availableExtensions(extensionCount);
  		vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
  
  		std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
  
  		for (const auto& extension : availableExtensions) {
  			requiredExtensions.erase(extension.extensionName);
  		}
  
  		return requiredExtensions.empty();
  	}
  
  	QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
  		QueueFamilyIndices indices;
  
  		uint32_t queueFamilyCount = 0;
  		vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
  
  		std::vector<VkQueueFamilyProperties> 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;
  			}
  
  			VkBool32 presentSupport = false;
  			vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
  
  			if (queueFamily.queueCount > 0 && presentSupport) {
  				indices.presentFamily = 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<VkLayerProperties> 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 std::vector<char> readFile(const std::string& filename) {
  		std::ifstream file(filename, std::ios::ate | std::ios::binary);
  
  		if (!file.is_open()) {
  			throw std::runtime_error("failed to open file!");
  		}
  
  		size_t fileSize = (size_t)file.tellg();
  		std::vector<char> buffer(fileSize);
  
  		file.seekg(0);
  		file.read(buffer.data(), fileSize);
  
  		file.close();
  
  		return buffer;
  	}
  
  	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;
  }