We have a presentation layer!

parent 93335647
......@@ -14,7 +14,7 @@ BINDIR = bin
TARGET = learn
LIBS = -L$(VULKAN_SDK_PATH)/lib `pkg-config --static --libs glfw3` -lvulkan
CXXFLAGS = -Wall -fopenmp -I$(VULKAN_SDK_PATH)/include -std=c++14
CXXFLAGS = -Wall -fopenmp -I$(VULKAN_SDK_PATH)/include -std=c++14 -DNDEBUG
CXX = g++
LINKER = g++
......
......@@ -4,10 +4,69 @@
#include <iostream>
#include <stdexcept>
#include <functional>
#include <vector>
#include <cstring>
#include <set>
const int WINDOW_WIDTH = 800;
const int WINDOW_HEIGHT = 600;
// Required Validation Layers
const std::vector<const char*> validationLayers = {
"VK_LAYER_LUNARG_standard_validation"
};
#ifndef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif
//The extension function "vkCreateDebugReportCallbackEXT" isn't automatically loaded. We have to lookup the address with a proxy function.
VkResult CreateDebugReportCallbackEXT(
VkInstance instance,
const VkDebugReportCallbackCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDebugReportCallbackEXT* pCallback) {
// vkGetInstanceProcAddr gets the address of --> loads the function itself, vkCreateDebugReportCallbackEXT!
auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pCallback); //Call the function vkCreateDebugReportCallbackEXT.
} else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
//Same thing with the extension function "vkDestroyDebugReportCallbackEXT".
void DestroyDebugReportCallbackEXT(
VkInstance instance,
VkDebugReportCallbackEXT callback,
const VkAllocationCallbacks* pAllocator) {
// vkGetInstanceProcAddr gets the address of --> loads the function itself, vkCreateDebugReportCallbackEXT!
auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
if (func != nullptr) {
func(instance, callback, pAllocator); //Call the function vkCreateDebugReportCallbackEXT.
}
}
//Queue families generate queues that are allowed to execute a certain subset of commands.
// The indices of queue families that support certain properties are stored in a struct. -1 is not found.
struct QueueFamilyIndices {
int graphicsFamily = -1; //Graphics queue family - can execute graphics commands.
int presentFamily = -1; //Presentation queue family - can draw to surfaces.
bool isComplete() {
return graphicsFamily >= 0 && presentFamily >= 0;
}
};
class HelloTriangleApplication {
public :
void run() {
......@@ -19,8 +78,19 @@ class HelloTriangleApplication {
private :
GLFWwindow* window; //Pointer to the GLFW window.
VkInstance instance; //The Vulkan instance itself.
VkDebugReportCallbackEXT callback; //Even the debug callback is managed with a created/destroyed handle...
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; //Don't worry - this is automagically destroyed when the instance is destroyed.
VkDevice device; //Logical devices have certain features, and get it done through physical devices.
VkQueue graphicsQueue; //The handle to the graphics queue. Implicitly cleaned with the instance.
VkSurfaceKHR surface; //Platform-agnostic screen to render to. Creation of it is not. Window System Integration is handled by extensions.
VkQueue presentQueue; //The handle to the presentation queue, to draw things to the surface.
void initWindow() {
//Create a GLFW window.
glfwInit(); //Start it up.
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); //Tell it to not use OpenGL.
......@@ -29,17 +99,335 @@ class HelloTriangleApplication {
window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Vulkan Learning!", nullptr, nullptr); //Make the window!
}
void initVulkan() {
bool checkValidationLayerSupport() {
//Checks if requested validation layers are available.
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr); //List all available validation layers.
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); //Pass a pointer to the vector data.
for (const char* layerName : validationLayers) {
bool layerFound = false;
for (const auto& layerProperties : availableLayers) {
if (strcmp(layerName, layerProperties.layerName) == 0) { //Compare strings.
layerFound = true;
break;
}
}
if (!layerFound) { //If just one layer isn't found, return false.
return false;
}
}
return true;
}
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( //VKAPI_ATTR and VKAPI_CALL give it the right signature to be called by Vulkan.
VkDebugReportFlagsEXT flags, //The type of message. A combination of bit flags.
VkDebugReportObjectTypeEXT objType, //The type of object that is the subject of the message.
uint64_t obj, //Internally, all Vulkan handles are typedef'ed as uint64_t. So we need the objType flag.
size_t location,
int32_t code,
const char* layerPrefix,
const char* msg, //Pointer to the message itself.
void* userData) { //Pass your own data to the callback.
std::cerr << "Validation layer: " << msg << std::endl;
return VK_FALSE; //Return a boolean indicating if the Vulkan call triggering this message should be aborted.
}
std::vector<const char*> getRequiredExtensions() {
// Returns required list of extensions based on enabled validation layers.
// Essentially, the extensions allow the validation layers to actually output debug data.
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]); //Push the extensions required by glfw.
}
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); //Push the "debug report" extension via macros, making validation layers useful.
}
return extensions;
}
void createInstance() {
//Create a Vulkan instance.
//Check validation layer support
if (enableValidationLayers && !checkValidationLayerSupport()) {
throw std::runtime_error("Some validation layers were requested, but are not available!");
}
//Set appInfo struct
VkApplicationInfo appInfo = {}; //Pass a struct of useful info (optionally) to the new instance.
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; //Many Vulkan structs require sType.
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 = {}; //Non-optional for creating the Instance; tells which global extensions/validation layers to use.
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; //Again, the sType.
createInfo.pApplicationInfo = &appInfo; //Supply address of appInfo.
//Add VkInstanceCreateInfo validation layers to createInfo.
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 0;
}
//Now. Vulkan is platform-agnostic; it needs extensions to talk to the window system.
//See getRequiredExtensions() - stuff happens there that gets the extensions we need.
auto extensions = getRequiredExtensions();
// Pop that data into createInfo, which will tell the Instance what extensions to use.
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
//The big finish - vkCreateInstance!
//Nearly all Vulkan functions return a VkResult value; either VK_SUCCESS or an error.
//ptr to struct with creation info, custom allocator callbacks, pointer to var handling the new object.
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("Failed to create VkInstance!");
}
//Let's just print some cool details about available extensions using Vulkan's built in function and a vector.
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); //Just get the # of extensions.
std::vector<VkExtensionProperties> extensionProps(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensionProps.data()); //Read into the vector.
std::cout << "Available Extensions:" << std::endl;
for (const auto &extension : extensionProps) {
std::cout << "\t" << extension.extensionName << std::endl;
}
}
void setupDebugCallback() {
if (!enableValidationLayers) return;
// They weren't kidding when they said this shit was verbose...
// We need a "Report Callback Creation Info Struct".
VkDebugReportCallbackCreateInfoEXT createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; //This is a callback info struct for setup of debugging.
createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; //Types of messages we want to recieve.
createInfo.pfnCallback = debugCallback; //Pointer to the callback function. See private method.
//Optionally pass a pointer to createInfo.pUserData. It'll be passed to the callback function in the form of the userData void* field.
// Create the debug callback using our proxy function!
if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) {
throw std::runtime_error("Failed to setup debug callback!");
}
}
void pickPhysicalDevice() {
//Enumerating devices works a lot like enumerating extensions.
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (deviceCount == 0) {
throw std::runtime_error("Failed to find Vulkan-compatible GPUs!");
}
// Fill up a vector with VkPhysicalDevices.
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
//In this case we just pick the first GPU - one can conceivably, however, rate all available devices and choose the
// best one available (fallback to integrated GPU, for example).
for (const auto& device: devices) {
if (isDeviceSuitable(device)) { //Pick a suitable device!
physicalDevice = device;
break;
}
}
if (physicalDevice == VK_NULL_HANDLE) {
throw std::runtime_error("Failed to find a suitable GPU for graphics magic!");
}
}
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
// Find supported queue families of the device.
QueueFamilyIndices indices; //We want the indices of the available queue families.
// As usual, we enumerate over the available queue family properties of the device with a builtin function.
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
// These properties contain some info abt the queue family - like how many queues can be created, types of operations supported.
//We need to find at least one queue family that support VK_QUEUE_GRAPHICS_BIT.
int i = 0;
for (const auto& queueFamily : queueFamilies) {
if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i; //We've found the index to our graphics queue family at i.
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); //Check for presentation layer support of current queue family.
if (queueFamily.queueCount > 0 && presentSupport) {
indices.presentFamily = i;
}
// Presentation/graphics queue families might be the one and the same.
// If they are, performance is better. One could add logic to prefer a physical device that has both in the same queue.
if (indices.isComplete()) {
break; //We're done; call the struct function.
}
i++;
}
return indices;
}
bool isDeviceSuitable(VkPhysicalDevice device) {
//Not all GPUs are created equal... We only want support for:
// Geometry Shaders
// Discrete GPUs
// Graphics Command Support - through the queue family.
// Presentation Layer Support - so the device can present images to the surface through a queue family.
// Swap Chain Support - not all GPUs can swap images to the screen, due to lack of display outputs!
//Basic device properties, like name, type, supported Vulkan.
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device, &deviceProperties);
//Support for optional features like texture compresison, 64bit floats, multi-viewport rendering, etc. .
VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
// Make sure we have access to a queue family supporting graphics commands (VK_QUEUE_GRAPHICS_BIT) - or anything we want.
//~ QueueFamilyIndices indices = findQueueFamilies(device);
//~ bool extensionsSupported = checkDeviceExtensionSupport(device);
// Only allow discrete GPUs supporting geometry shaders
bool suitable = deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
deviceFeatures.geometryShader &&
indices.isComplete();// && //Make sure we have an index.
//~ extensionsSupported;
if (suitable) {
std::cout << "Found suitable device: " << deviceProperties.deviceName << std::endl;
}
return suitable;
}
void createLogicalDevice() {
QueueFamilyIndices indices = findQueueFamilies(physicalDevice); // Get indices of queue families supported on physical device.
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos; //A vector storing the createInfo structs for each queue family.
std::set<int> uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; //A set - which removes duplicates, leaving only the unique queue families.
// See, both graphicsFamily and presentFamily might be the same family. We only want to initialize it once, in that case.
// We can create command buffers on multiple threads, then submit all at once on the main thread (low-overhead). We don't really ever want more than one here.
float queuePriority = 1.0f; //We can prioritize queues for scheduling concerns. In fact, it's required, even if there's only one.
for (int queueFamily : uniqueQueueFamilies) { //For each unique queue family.
VkDeviceQueueCreateInfo queueCreateInfo = {}; //Another createInfo struct. Described the number of queues we want for a certain queue family.
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily; //Each unique queueFamily index.
queueCreateInfo.queueCount = 1; //Only one queue for now.
queueCreateInfo.pQueuePriorities = &queuePriority; //All queues will have a prio of 1, for now.
queueCreateInfos.push_back(queueCreateInfo);
}
//Now we need to specify the device features we're using - what we queried with vkGetPhysicalDeviceFeatures, like geometry shader support.
VkPhysicalDeviceFeatures deviceFeatures = {}; //Nothing special for now - leave it as VK_FALSE.
// Of course, the VkDevice has a similar init struct to VkInstance.
VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
//Pointers to the queue creation info structs and device features structs, used in the creation of the logical device.
// The logical device needs to know what queue families it supports, where they are, how many queues, what priorities, etc. .
createInfo.pQueueCreateInfos = queueCreateInfos.data(); //It can deal with multiple queueCreateInfos.
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()); //The amount of queueCreateInfos.
createInfo.pEnabledFeatures = &deviceFeatures;
//Now we need to specify device-specific extensions and validation layers. Just like VkInstance.
// We'll use the same validation layers as we did for the instance.
// No new device-specific extensions for now.
createInfo.enabledExtensionCount = 0;
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 0;
}
// Now, finally, we'll make the logical device with VkCreateDevice.
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
throw std::runtime_error("Failed to create logical device!");
}
// 0 because we're only making a single queue from the family.
vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); //Retrieve queue handles for the graphics queue family.
vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue);
}
void createSurface() {
//Pass the VkInstance, the GLFW window pointer, and address of the surface to make the surface.
// GLFW takes care of the platform-specific stuff - interfacing directly to X11 or Win32 or whatever!
// Otherwise it starts to get kinda hairy, initializing the surface... Though it can be done.
if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
throw std::runtime_error("Failed to create Vulkan window surface!");
}
}
void initVulkan() {
createInstance(); //The Vulkan instance is the link between application and Vulkan.
setupDebugCallback(); //Setup the debug callback that interfaces with the validation layer.
createSurface(); //Create the platform-agnostic "surface" Vulkan can render to, with non-platform-agnostic code...
pickPhysicalDevice(); //We need to pick a physical graphics card to use!
createLogicalDevice(); //We need to interface with the physical device using a *logical* device!
}
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwPollEvents(); //Loops and checks for events until the window should close.
glfwPollEvents(); //Loops and checks for events until the window "should close".
}
}
void cleanup() {
vkDestroyDevice(device, nullptr); //Kill the logical device.
DestroyDebugReportCallbackEXT(instance, callback, nullptr); //Cleanup the VkDebugReportCallback
vkDestroySurfaceKHR(instance, surface, nullptr); //Kill the Vulkan surface - using Vulkan, not GLFW.
vkDestroyInstance(instance, nullptr); //Kill the Vulkan instance.
glfwDestroyWindow(window); //Kill the window.
glfwTerminate(); //Shut it down!
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment