|
|
|
@ -17,6 +17,19 @@ const char* validation_layers[] = {
@@ -17,6 +17,19 @@ const char* validation_layers[] = {
|
|
|
|
|
"VK_LAYER_KHRONOS_validation", |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const char* required_extensions[] = { |
|
|
|
|
VK_KHR_SWAPCHAIN_EXTENSION_NAME |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
typedef struct SwapChainSupportDetails { |
|
|
|
|
VkSurfaceCapabilitiesKHR capabilities; |
|
|
|
|
uint32_t num_formats; |
|
|
|
|
VkSurfaceFormatKHR* formats; |
|
|
|
|
uint32_t num_modes; |
|
|
|
|
VkPresentModeKHR* present_modes; |
|
|
|
|
} SwapChainSupportDetails; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct ApplicationState { |
|
|
|
|
GLFWwindow* window; |
|
|
|
|
//information about the connection between application and vulkan itself
|
|
|
|
@ -31,6 +44,9 @@ typedef struct ApplicationState {
@@ -31,6 +44,9 @@ typedef struct ApplicationState {
|
|
|
|
|
VkSurfaceKHR surface; |
|
|
|
|
//presentation queue
|
|
|
|
|
VkQueue present_queue; |
|
|
|
|
//swapchain details:
|
|
|
|
|
SwapChainSupportDetails details; |
|
|
|
|
VkSwapchainKHR swapchain; |
|
|
|
|
} ApplicationState; |
|
|
|
|
|
|
|
|
|
typedef struct QueueFamilyIndices { |
|
|
|
@ -164,6 +180,60 @@ QueueFamilyIndices find_queue_families(ApplicationState* state, VkPhysicalDevice
@@ -164,6 +180,60 @@ QueueFamilyIndices find_queue_families(ApplicationState* state, VkPhysicalDevice
|
|
|
|
|
return ret; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//get supported formats and modes
|
|
|
|
|
SwapChainSupportDetails get_swapchain_details(ApplicationState* state, VkPhysicalDevice device) { |
|
|
|
|
SwapChainSupportDetails ret = {0}; |
|
|
|
|
|
|
|
|
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, state->surface, &ret.capabilities); |
|
|
|
|
|
|
|
|
|
uint32_t format_count; |
|
|
|
|
|
|
|
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(device, state->surface, &format_count, NULL); |
|
|
|
|
ret.num_formats = format_count; |
|
|
|
|
ret.formats = malloc(sizeof(VkSurfaceFormatKHR) * format_count); |
|
|
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(device, state->surface, &format_count, ret.formats); |
|
|
|
|
|
|
|
|
|
uint32_t present_modes_count; |
|
|
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(device, state->surface, &present_modes_count, NULL); |
|
|
|
|
ret.num_modes = present_modes_count; |
|
|
|
|
ret.present_modes = malloc(sizeof(VkSurfaceFormatKHR) * present_modes_count); |
|
|
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(device, state->surface, &format_count, ret.present_modes); |
|
|
|
|
|
|
|
|
|
printf("Device has %x present modes and %x formats for surface\n", present_modes_count, format_count); |
|
|
|
|
|
|
|
|
|
return ret; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool extensions_supported(ApplicationState* state, VkPhysicalDevice device) { |
|
|
|
|
uint32_t num_extensions; |
|
|
|
|
vkEnumerateDeviceExtensionProperties(device, NULL, &num_extensions, NULL); |
|
|
|
|
|
|
|
|
|
VkExtensionProperties* available_extensions = malloc(sizeof(VkExtensionProperties) * num_extensions); |
|
|
|
|
vkEnumerateDeviceExtensionProperties(device, NULL, &num_extensions, available_extensions); |
|
|
|
|
|
|
|
|
|
printf("Device extensions:\n"); |
|
|
|
|
for (int i = 0; i < num_extensions; i++) { |
|
|
|
|
printf("\t%s\n", available_extensions[i].extensionName); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < ARRSIZE(required_extensions); i++) { |
|
|
|
|
bool found = false; |
|
|
|
|
for (int j = 0; j < num_extensions; j++) { |
|
|
|
|
if (!strcmp(required_extensions[i], available_extensions[j].extensionName)) { |
|
|
|
|
found = true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!found) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
free(available_extensions); |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void pick_physical_device(ApplicationState* state) { |
|
|
|
|
uint32_t device_count = 0; |
|
|
|
|
vkEnumeratePhysicalDevices(state->instance, &device_count, NULL); |
|
|
|
@ -189,13 +259,22 @@ void pick_physical_device(ApplicationState* state) {
@@ -189,13 +259,22 @@ void pick_physical_device(ApplicationState* state) {
|
|
|
|
|
|
|
|
|
|
QueueFamilyIndices families = find_queue_families(state, device); |
|
|
|
|
|
|
|
|
|
SwapChainSupportDetails details = get_swapchain_details(state, device); |
|
|
|
|
|
|
|
|
|
//for now we only require that a graphics queue is present
|
|
|
|
|
if (families.graphics_present && families.present_present) { |
|
|
|
|
if (families.graphics_present && families.present_present && extensions_supported(state, device) && |
|
|
|
|
//we only care that at least one mode and one format exist
|
|
|
|
|
details.num_modes != 0 && details.num_formats != 0) { |
|
|
|
|
state->details = details; |
|
|
|
|
printf("graphics queue at %x, present queue at %x\n", families.graphics_queue, families.present_queue); |
|
|
|
|
break; |
|
|
|
|
} else { |
|
|
|
|
free (details.present_modes); |
|
|
|
|
free (details.formats); |
|
|
|
|
printf("device does not support all necessary extensions and queues"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//TODO print unsupported message
|
|
|
|
|
state->physical_device = devices[i]; |
|
|
|
|
free(devices); |
|
|
|
|
} |
|
|
|
@ -244,7 +323,8 @@ void create_logical_device(ApplicationState* state) {
@@ -244,7 +323,8 @@ void create_logical_device(ApplicationState* state) {
|
|
|
|
|
create_info.pQueueCreateInfos = queue_create_infos; |
|
|
|
|
create_info.queueCreateInfoCount = j; |
|
|
|
|
create_info.pEnabledFeatures = &device_features; |
|
|
|
|
create_info.enabledExtensionCount = 0; |
|
|
|
|
create_info.enabledExtensionCount = ARRSIZE(required_extensions); |
|
|
|
|
create_info.ppEnabledExtensionNames = required_extensions; |
|
|
|
|
|
|
|
|
|
if (VALIDATE) { |
|
|
|
|
create_info.enabledLayerCount = ARRSIZE(validation_layers); |
|
|
|
@ -273,11 +353,131 @@ void create_surface(ApplicationState* state) {
@@ -273,11 +353,131 @@ void create_surface(ApplicationState* state) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//We prefer one format but it may not be available
|
|
|
|
|
VkSurfaceFormatKHR choose_swap_surface_format(VkSurfaceFormatKHR* formats, uint32_t num) { |
|
|
|
|
for (int i = 0; i < num; i++) { |
|
|
|
|
if (formats[i].format == VK_FORMAT_B8G8R8_SRGB && formats[i].colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR) { |
|
|
|
|
return formats[i]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return formats[0]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//presentation modes determines how images show up on screen
|
|
|
|
|
//VK_PRESENT_MODE_IMMEDIATE_KHR: Images submitted by your application are transferred to the screen right away, which may result in tearing.
|
|
|
|
|
|
|
|
|
|
//VK_PRESENT_MODE_FIFO_KHR: The swap chain is a queue where the display takes an image from the front of the queue when the display is
|
|
|
|
|
//refreshed and the program inserts rendered images at the back of the queue. If the queue is full then the program has to wait.
|
|
|
|
|
//This is most similar to vertical sync as found in modern games. The moment that the display is refreshed is known as "vertical blank".
|
|
|
|
|
|
|
|
|
|
//VK_PRESENT_MODE_FIFO_RELAXED_KHR: This mode only differs from the previous one if the application is late and the queue was empty at
|
|
|
|
|
//the last vertical blank. Instead of waiting for the next vertical blank, the image is transferred right away when it finally arrives.
|
|
|
|
|
//This may result in visible tearing.
|
|
|
|
|
|
|
|
|
|
//VK_PRESENT_MODE_MAILBOX_KHR: This is another variation of the second mode. Instead of blocking the application when the queue is full,
|
|
|
|
|
//the images that are already queued are simply replaced with the newer ones. This mode can be used to render frames as fast as possible
|
|
|
|
|
//while still avoiding tearing, resulting in fewer latency issues than standard vertical sync. This is commonly known as "triple buffering",
|
|
|
|
|
//although the existence of three buffers alone does not necessarily mean that the framerate is unlocked.
|
|
|
|
|
VkPresentModeKHR choose_swap_present_mode(VkPresentModeKHR* modes, uint32_t num) { |
|
|
|
|
for (int i = 0; i < num; i++) { |
|
|
|
|
if (modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { |
|
|
|
|
return modes[i]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//guaranteed to exist
|
|
|
|
|
return VK_PRESENT_MODE_FIFO_KHR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint32_t clamp(uint32_t val, uint32_t min, uint32_t max) { |
|
|
|
|
if (val > max) { |
|
|
|
|
return max; |
|
|
|
|
} else if (val < min) { |
|
|
|
|
return min; |
|
|
|
|
} |
|
|
|
|
return val; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//resolutoion of the swapchain images
|
|
|
|
|
VkExtent2D choose_swap_extent(ApplicationState* state, VkSurfaceCapabilitiesKHR capabilities) { |
|
|
|
|
if (capabilities.currentExtent.width != UINT32_MAX) { |
|
|
|
|
return capabilities.currentExtent; |
|
|
|
|
} else { |
|
|
|
|
int width, height; |
|
|
|
|
|
|
|
|
|
glfwGetFramebufferSize(state->window, &width, &height); |
|
|
|
|
|
|
|
|
|
VkExtent2D extent; |
|
|
|
|
extent.width = clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); |
|
|
|
|
extent.height = clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); |
|
|
|
|
|
|
|
|
|
return extent; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void create_swap_chain(ApplicationState* state) { |
|
|
|
|
VkSurfaceFormatKHR format = choose_swap_surface_format(state->details.formats, state->details.num_formats); |
|
|
|
|
VkPresentModeKHR mode = choose_swap_present_mode(state->details.present_modes, state->details.num_modes); |
|
|
|
|
VkExtent2D extent = choose_swap_extent(state, state->details.capabilities); |
|
|
|
|
|
|
|
|
|
uint32_t image_count = state->details.capabilities.minImageCount + 1; |
|
|
|
|
|
|
|
|
|
if (state->details.capabilities.maxImageCount > 0 && image_count > state->details.capabilities.maxImageCount) { |
|
|
|
|
image_count = state->details.capabilities.maxImageCount; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
VkSwapchainCreateInfoKHR create_info; |
|
|
|
|
create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
|
|
|
|
create_info.surface = state->surface; |
|
|
|
|
create_info.minImageCount = image_count; |
|
|
|
|
create_info.imageFormat = format.format; |
|
|
|
|
create_info.imageColorSpace = format.colorSpace; |
|
|
|
|
create_info.imageExtent = extent; |
|
|
|
|
//1 unless stereoscopic 3d
|
|
|
|
|
create_info.imageArrayLayers = 1; |
|
|
|
|
//we directly render to this image
|
|
|
|
|
create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
|
|
|
|
|
|
|
|
|
//next we need to determine if images will be shared by multiple queues
|
|
|
|
|
|
|
|
|
|
QueueFamilyIndices families = find_queue_families(state, state->physical_device); |
|
|
|
|
uint32_t indices[] = {families.graphics_queue, families.present_queue}; |
|
|
|
|
|
|
|
|
|
if (families.present_queue != families.graphics_queue) { |
|
|
|
|
create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; |
|
|
|
|
create_info.queueFamilyIndexCount = 2; |
|
|
|
|
create_info.pQueueFamilyIndices = indices; |
|
|
|
|
} else { |
|
|
|
|
create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; |
|
|
|
|
create_info.queueFamilyIndexCount = 0; |
|
|
|
|
create_info.pQueueFamilyIndices = NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//no transformations (such as rotations and so on)
|
|
|
|
|
create_info.preTransform = state->details.capabilities.currentTransform; |
|
|
|
|
create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; |
|
|
|
|
create_info.presentMode = mode; |
|
|
|
|
//pixels that are obscured by other windows do not matter
|
|
|
|
|
create_info.clipped = VK_TRUE; |
|
|
|
|
|
|
|
|
|
//we don't handle resize for now
|
|
|
|
|
create_info.oldSwapchain = VK_NULL_HANDLE; |
|
|
|
|
|
|
|
|
|
if (vkCreateSwapchainKHR(state->device, &create_info, NULL, &state->swapchain) != VK_SUCCESS) { |
|
|
|
|
printf("failed to create swpachain\n"); |
|
|
|
|
exit(1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
printf("swapchain created\n"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void init_vulkan(ApplicationState* state) { |
|
|
|
|
create_instance(state); |
|
|
|
|
create_surface(state); |
|
|
|
|
pick_physical_device(state); |
|
|
|
|
create_logical_device(state); |
|
|
|
|
create_swap_chain(state); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void main_loop(ApplicationState* state) { |
|
|
|
@ -287,6 +487,7 @@ void main_loop(ApplicationState* state) {
@@ -287,6 +487,7 @@ void main_loop(ApplicationState* state) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void terminate(ApplicationState* state) { |
|
|
|
|
vkDestroySwapchainKHR(state->device, state->swapchain, NULL); |
|
|
|
|
vkDestroyDevice(state->device, NULL); |
|
|
|
|
vkDestroySurfaceKHR(state->instance, state->surface, NULL); |
|
|
|
|
vkDestroyInstance(state->instance, NULL); |
|
|
|
|