2472 lines
83 KiB
C
2472 lines
83 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#define VK_USE_PLATFORM_WAYLAND_KHR
|
|
#define GLFW_INCLUDE_VULKAN
|
|
#define GLFW_EXPOSE_NATIVE_WAYLAND
|
|
#include <GLFW/glfw3.h>
|
|
#include <GLFW/glfw3native.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <time.h>
|
|
#include <math.h>
|
|
|
|
typedef enum {
|
|
false = 0,
|
|
true = 1,
|
|
} bool;
|
|
|
|
static const uint32_t WIDTH = 800;
|
|
static const uint32_t HEIGHT = 600;
|
|
#define MAX_FRAMES_IN_FLIGHT 2
|
|
|
|
static const char* VALIDATION_LAYERS[] = { "VK_LAYER_KHRONOS_validation" };
|
|
#define VALIDATION_LAYER_COUNT 1
|
|
|
|
static const char* DEVICE_EXTENSIONS[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
|
|
#define DEVICE_EXTENSION_COUNT 1
|
|
|
|
#ifdef NDEBUG
|
|
static const bool enableValidationLayers = false;
|
|
#else
|
|
static const bool enableValidationLayers = true;
|
|
#endif
|
|
|
|
typedef struct Vec2 {
|
|
float x;
|
|
float y;
|
|
} Vec2;
|
|
|
|
typedef struct Vec3 {
|
|
float x;
|
|
float y;
|
|
float z;
|
|
} Vec3;
|
|
|
|
typedef struct Vec4 {
|
|
float x;
|
|
float y;
|
|
float z;
|
|
float w;
|
|
} Vec4;
|
|
|
|
typedef struct Mat4 {
|
|
Vec4 x;
|
|
Vec4 y;
|
|
Vec4 z;
|
|
Vec4 w;
|
|
} Mat4;
|
|
|
|
struct UniformBufferObject {
|
|
Mat4 model;
|
|
Mat4 view;
|
|
Mat4 proj;
|
|
} UniformBufferObject;
|
|
|
|
typedef struct Vertex {
|
|
Vec3 pos;
|
|
Vec3 color;
|
|
Vec2 texCoord;
|
|
} Vertex;
|
|
|
|
struct PPM {
|
|
uint32_t width;
|
|
uint32_t height;
|
|
uint32_t maxVal;
|
|
void* pixels;
|
|
};
|
|
|
|
struct VulkanData {
|
|
VkInstance instance;
|
|
VkDebugUtilsMessengerEXT debugMessenger;
|
|
VkPhysicalDevice physicalDevice;
|
|
VkDevice device;
|
|
VkQueue graphicsQueue;
|
|
VkQueue presentQueue;
|
|
VkQueue transferQueue;
|
|
VkSurfaceKHR surface;
|
|
VkSwapchainKHR swapChain;
|
|
VkImage swapChainImages[4];
|
|
VkFormat swapChainImageFormat;
|
|
VkExtent2D swapChainExtent;
|
|
VkImageView swapChainImageViews[4];
|
|
int imageCount;
|
|
VkDescriptorSetLayout descriptorSetLayout;
|
|
VkPipelineLayout pipelineLayout;
|
|
VkRenderPass renderPass;
|
|
VkPipeline graphicsPipeline;
|
|
VkFramebuffer swapChainFramebuffers[4];
|
|
VkCommandPool commandPool;
|
|
VkCommandPool transferCommandPool;
|
|
VkCommandBuffer commandBuffers[MAX_FRAMES_IN_FLIGHT];
|
|
VkSemaphore imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT];
|
|
VkSemaphore renderFinishedSemaphores[MAX_FRAMES_IN_FLIGHT];
|
|
VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
|
|
uint32_t currentFrame;
|
|
bool framebufferResized;
|
|
Vertex* vertices;
|
|
uint32_t vertexCount;
|
|
uint32_t* indices;
|
|
uint32_t indexCount;
|
|
VkBuffer vertexBuffer;
|
|
VkDeviceMemory vertexBufferMemory;
|
|
VkBuffer indexBuffer;
|
|
VkDeviceMemory indexBufferMemory;
|
|
VkBuffer uniformBuffers[MAX_FRAMES_IN_FLIGHT];
|
|
VkDeviceMemory uniformBuffersMemory[MAX_FRAMES_IN_FLIGHT];
|
|
void* uniformBuffersMapped[MAX_FRAMES_IN_FLIGHT];
|
|
VkDescriptorPool descriptorPool;
|
|
VkDescriptorSet descriptorSets[MAX_FRAMES_IN_FLIGHT];
|
|
uint32_t mipLevels;
|
|
VkImage textureImage;
|
|
VkDeviceMemory textureImageMemory;
|
|
VkImageView textureImageView;
|
|
VkSampler textureSampler;
|
|
VkImage depthImage;
|
|
VkDeviceMemory depthImageMemory;
|
|
VkImageView depthImageView;
|
|
VkSampleCountFlagBits msaaSamples;
|
|
VkImage colorImage;
|
|
VkDeviceMemory colorImageMemory;
|
|
VkImageView colorImageView;
|
|
};
|
|
|
|
struct SwapChainSupportDetails {
|
|
VkSurfaceCapabilitiesKHR capabilities;
|
|
VkPresentModeKHR presentModes[5];
|
|
VkSurfaceFormatKHR formats[8];
|
|
};
|
|
|
|
struct Optional {
|
|
uint32_t v;
|
|
bool is_some;
|
|
};
|
|
|
|
struct QueueFamilyIndices {
|
|
struct Optional graphicsFamily;
|
|
struct Optional presentFamily;
|
|
struct Optional transferFamily;
|
|
};
|
|
|
|
static Vec3 normalize(Vec3 v) {
|
|
float size = sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
|
Vec3 result = {
|
|
v.x / size,
|
|
v.y / size,
|
|
v.z / size,
|
|
};
|
|
return result;
|
|
}
|
|
|
|
static Vec3 crossProduct(Vec3 a, Vec3 b) {
|
|
Vec3 result = {
|
|
a.y * b.z - a.z * b.y,
|
|
a.z * b.x - a.x * b.z,
|
|
a.x * b.y - a.y * b.x,
|
|
};
|
|
return result;
|
|
}
|
|
|
|
static float dotProduct(Vec3 a, Vec3 b) {
|
|
return a.x * b.x + a.y * b.y + a.z * b.z;
|
|
}
|
|
|
|
static Mat4 rotate(float angle, Vec3 v) {
|
|
float c = cos(angle);
|
|
float s = sin(angle);
|
|
float x = v.x, y = v.y, z = v.z;
|
|
Mat4 result = {
|
|
{c + x * x * (1. - c), x * y * (1. - c) - z * s, x * z * (1. - c) + y * s, 0.},
|
|
{x * y * (1. - c) + z * s, c + y * y * (1. - c), y * z * (1. - c) - x * s, 0.},
|
|
{x * z * (1. - c) - y * s, y * z * (1. - c) + x * s, c + z * z * (1. - c), 0.},
|
|
{0., 0., 0., 1.}
|
|
};
|
|
return result;
|
|
}
|
|
|
|
static Mat4 lookAt(Vec3 eye, Vec3 center, Vec3 up) {
|
|
Vec3 f = {
|
|
center.x - eye.x,
|
|
center.y - eye.y,
|
|
center.z - eye.z,
|
|
};
|
|
f = normalize(f);
|
|
up = normalize(up);
|
|
Vec3 s = normalize(crossProduct(f, up));
|
|
Vec3 u = crossProduct(s, f);
|
|
|
|
Mat4 mat = {
|
|
{s.x, u.x, -f.x, 0.},
|
|
{s.y, u.y, -f.y, 0.},
|
|
{s.z, u.z, -f.z, 0.},
|
|
{-dotProduct(s, eye), -dotProduct(u, eye), dotProduct(f, eye), 1.},
|
|
};
|
|
return mat;
|
|
}
|
|
|
|
static Mat4 perspective(float angle, float aspectRatio, float near, float far) {
|
|
float f = cos(angle / 2.) / sin(angle / 2.);
|
|
Mat4 mat = {
|
|
{f / aspectRatio, 0., 0., 0.},
|
|
{0., f, 0., 0.},
|
|
{0., 0., (far + near)/(near - far), -1.},
|
|
{0., 0., (2. * far * near)/(near - far), 0.},
|
|
};
|
|
return mat;
|
|
}
|
|
|
|
static VkVertexInputBindingDescription getBindingDescription() {
|
|
VkVertexInputBindingDescription bindingDescription = {
|
|
.binding = 0,
|
|
.stride = sizeof(Vertex),
|
|
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
|
|
};
|
|
return bindingDescription;
|
|
}
|
|
|
|
static void framebufferResizeCallback(GLFWwindow* window, int width, int height) {
|
|
printf("INFO: resizing from width: %d, height: %d\n", width, height);
|
|
struct VulkanData* app = (struct VulkanData*) glfwGetWindowUserPointer(window);
|
|
app->framebufferResized = true;
|
|
}
|
|
|
|
GLFWwindow* initWindow(struct VulkanData* data) {
|
|
if (!glfwInit()) {
|
|
fprintf(stderr, "ERROR: Failed to initialize window\n");
|
|
exit(1);
|
|
}
|
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
|
|
|
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", NULL, NULL);
|
|
if (!window) {
|
|
glfwTerminate();
|
|
fprintf(stderr, "ERROR: Failed to create window\n");
|
|
exit(1);
|
|
}
|
|
|
|
glfwSetWindowUserPointer(window, data);
|
|
glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
|
|
return window;
|
|
}
|
|
|
|
static VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels, struct VulkanData* data) {
|
|
VkImageViewCreateInfo viewInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
|
.image = image,
|
|
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
|
.format = format,
|
|
.subresourceRange.aspectMask = aspectFlags,
|
|
.subresourceRange.baseMipLevel = 0,
|
|
.subresourceRange.levelCount = mipLevels,
|
|
.subresourceRange.baseArrayLayer = 0,
|
|
.subresourceRange.layerCount = 1,
|
|
};
|
|
|
|
VkImageView imageView;
|
|
if (vkCreateImageView(data->device, &viewInfo, NULL, &imageView) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create texture image view\n");
|
|
exit(1);
|
|
}
|
|
return imageView;
|
|
}
|
|
|
|
static VkCommandBuffer beginSingleTimeCommands(bool graphics, struct VulkanData* data) {
|
|
VkCommandBufferAllocateInfo allocInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
.commandBufferCount = 1,
|
|
};
|
|
|
|
if (graphics) {
|
|
allocInfo.commandPool = data->commandPool;
|
|
} else {
|
|
allocInfo.commandPool = data->transferCommandPool;
|
|
}
|
|
|
|
VkCommandBuffer commandBuffer;
|
|
vkAllocateCommandBuffers(data->device, &allocInfo, &commandBuffer);
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
|
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
|
};
|
|
|
|
vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
|
|
|
return commandBuffer;
|
|
}
|
|
|
|
static void endSingleTimeCommands(VkCommandBuffer commandBuffer, bool graphics, struct VulkanData* data) {
|
|
vkEndCommandBuffer(commandBuffer);
|
|
|
|
VkSubmitInfo submitInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
|
.commandBufferCount = 1,
|
|
.pCommandBuffers = &commandBuffer,
|
|
};
|
|
|
|
if (graphics) {
|
|
vkQueueSubmit(data->graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
|
|
vkQueueWaitIdle(data->graphicsQueue);
|
|
vkFreeCommandBuffers(data->device, data->commandPool, 1, &commandBuffer);
|
|
} else {
|
|
vkQueueSubmit(data->transferQueue, 1, &submitInfo, VK_NULL_HANDLE);
|
|
vkQueueWaitIdle(data->transferQueue);
|
|
vkFreeCommandBuffers(data->device, data->transferCommandPool, 1, &commandBuffer);
|
|
}
|
|
}
|
|
|
|
static bool checkValidationLayerSupport() {
|
|
uint32_t layerCount;
|
|
vkEnumerateInstanceLayerProperties(&layerCount, NULL);
|
|
|
|
VkLayerProperties availableLayers[layerCount];
|
|
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers);
|
|
|
|
for (int i = 0; i < VALIDATION_LAYER_COUNT; i++) {
|
|
const char* layerName = VALIDATION_LAYERS[i];
|
|
uint32_t layerFound = 0;
|
|
|
|
for (uint32_t j = 0; j < layerCount; j++) {
|
|
VkLayerProperties layerProperties = availableLayers[j];
|
|
if (strcmp(layerName, layerProperties.layerName) == 0) {
|
|
layerFound = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!layerFound) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
|
|
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
|
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
|
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
|
void* pUserData) {
|
|
|
|
if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT && messageType >= VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) {
|
|
printf("validation layer: %s, with data: %p\n", pCallbackData->pMessage, pUserData);
|
|
}
|
|
|
|
return VK_FALSE;
|
|
}
|
|
|
|
static void createInstance(struct VulkanData* data) {
|
|
if (enableValidationLayers && !checkValidationLayerSupport()) {
|
|
printf("ERROR: Validation layers requested, but not available\n");
|
|
exit(1);
|
|
}
|
|
VkApplicationInfo appInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
|
.pApplicationName = "Hello Triangle",
|
|
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
|
|
.pEngineName = "No Engine",
|
|
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
|
|
.apiVersion = VK_API_VERSION_1_3,
|
|
};
|
|
|
|
uint32_t count = 0;
|
|
vkEnumerateInstanceExtensionProperties(NULL, &count, NULL);
|
|
VkExtensionProperties availableExtensions[count];
|
|
vkEnumerateInstanceExtensionProperties(NULL, &count, availableExtensions);
|
|
printf("available extensions:\n");
|
|
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
VkExtensionProperties extension = availableExtensions[i];
|
|
printf("\t%s\n", extension.extensionName);
|
|
}
|
|
|
|
uint32_t glfwExtensionCount = 0;
|
|
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
|
|
const char* extensions[glfwExtensionCount + 1];
|
|
|
|
for (uint32_t i = 0; i < glfwExtensionCount; i++) {
|
|
extensions[i] = glfwExtensions[i];
|
|
}
|
|
|
|
if (enableValidationLayers) {
|
|
extensions[glfwExtensionCount] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
|
|
}
|
|
uint32_t extensionCount = sizeof(extensions) / sizeof(extensions[0]);
|
|
|
|
printf("required extensions:\n");
|
|
for (uint32_t i = 0; i < extensionCount; i++) {
|
|
printf("\t%s\n", extensions[i]);
|
|
}
|
|
|
|
const VkBool32 setting_validate_core = VK_TRUE;
|
|
const VkBool32 setting_validate_best_practices = VK_TRUE;
|
|
const VkBool32 setting_validate_sync = VK_TRUE;
|
|
const VkBool32 setting_thread_safety = VK_TRUE;
|
|
const char* setting_debug_action[] = {"VK_DBG_LAYER_ACTION_LOG_MSG"};
|
|
const char* setting_report_flags[] = {"info", "warn", "perf", "error", "debug"};
|
|
const VkBool32 setting_enable_message_limit = VK_TRUE;
|
|
const int32_t setting_duplicate_message_limit = 3;
|
|
const VkLayerSettingEXT settings[] = {
|
|
{VALIDATION_LAYERS[0], "validate_core", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_validate_core},
|
|
{VALIDATION_LAYERS[0], "validate_best_practices", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_validate_best_practices},
|
|
{VALIDATION_LAYERS[0], "validate_sync", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_validate_sync},
|
|
{VALIDATION_LAYERS[0], "thread_safety", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_thread_safety},
|
|
{VALIDATION_LAYERS[0], "debug_action", VK_LAYER_SETTING_TYPE_STRING_EXT, 1, setting_debug_action},
|
|
{VALIDATION_LAYERS[0], "report_flags", VK_LAYER_SETTING_TYPE_STRING_EXT, 5, setting_report_flags},
|
|
{VALIDATION_LAYERS[0], "enable_message_limit", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_enable_message_limit},
|
|
{VALIDATION_LAYERS[0], "duplicate_message_limit", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_duplicate_message_limit},
|
|
};
|
|
const VkLayerSettingsCreateInfoEXT layer_settings_create_info = {
|
|
VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT,
|
|
NULL,
|
|
8,
|
|
settings,
|
|
};
|
|
|
|
VkInstanceCreateInfo createInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
|
.pNext = &layer_settings_create_info,
|
|
.flags = 0,
|
|
.pApplicationInfo = &appInfo,
|
|
.enabledExtensionCount = extensionCount,
|
|
.ppEnabledExtensionNames = extensions,
|
|
.enabledLayerCount = 1,
|
|
};
|
|
|
|
if (enableValidationLayers) {
|
|
createInfo.enabledLayerCount = VALIDATION_LAYER_COUNT;
|
|
createInfo.ppEnabledLayerNames = VALIDATION_LAYERS;
|
|
|
|
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
|
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
|
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
|
.pfnUserCallback = debugCallback,
|
|
};
|
|
createInfo.pNext = &debugCreateInfo;
|
|
} else {
|
|
createInfo.enabledLayerCount = 0;
|
|
createInfo.pNext = NULL;
|
|
}
|
|
|
|
VkInstance instance;
|
|
if (vkCreateInstance(&createInfo, NULL, &instance) != VK_SUCCESS) {
|
|
printf("ERROR: Failed to create instance\n");
|
|
exit(1);
|
|
}
|
|
data->instance = instance;
|
|
}
|
|
|
|
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
|
|
|
|
PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
|
|
|
|
if (func == NULL) {
|
|
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
|
}
|
|
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
|
|
}
|
|
|
|
static void setupDebugMessenger(struct VulkanData* data) {
|
|
if (!enableValidationLayers) return;
|
|
|
|
VkDebugUtilsMessengerCreateInfoEXT createInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
|
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
|
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
|
.pfnUserCallback = debugCallback,
|
|
};
|
|
|
|
if (CreateDebugUtilsMessengerEXT(data->instance, &createInfo, NULL, &data->debugMessenger) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to set up debug messenger\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void createSurface(struct VulkanData* data, GLFWwindow* window) {
|
|
if (glfwCreateWindowSurface(data->instance, window, NULL, &data->surface) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create window surface\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
|
|
PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
|
|
if (func != NULL) {
|
|
func(instance, debugMessenger, pAllocator);
|
|
}
|
|
}
|
|
|
|
static uint32_t clamp(uint32_t value, uint32_t min, uint32_t max) {
|
|
if (value > max) return max;
|
|
if (value < min) return min;
|
|
return value;
|
|
}
|
|
|
|
static int checkDeviceExtensionSupport(VkPhysicalDevice device) {
|
|
uint32_t extensionCount;
|
|
vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, NULL);
|
|
|
|
VkExtensionProperties availableExtensions[extensionCount];
|
|
vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, availableExtensions);
|
|
|
|
int requiredExtensionsFound = 0;
|
|
for (uint32_t i = 0; i < extensionCount; i++) {
|
|
VkExtensionProperties extension = availableExtensions[i];
|
|
for (int j = 0; j < DEVICE_EXTENSION_COUNT; j++) {
|
|
if (strcmp(extension.extensionName, DEVICE_EXTENSIONS[j]) == 0) {
|
|
requiredExtensionsFound++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return requiredExtensionsFound == DEVICE_EXTENSION_COUNT;
|
|
}
|
|
|
|
static struct SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface) {
|
|
struct SwapChainSupportDetails details;
|
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
|
|
uint32_t formatCount;
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, NULL);
|
|
if (formatCount != 0) {
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats);
|
|
}
|
|
|
|
uint32_t presentModeCount;
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, NULL);
|
|
|
|
if (presentModeCount != 0) {
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes);
|
|
}
|
|
|
|
return details;
|
|
}
|
|
|
|
static struct QueueFamilyIndices findQueueFamilies(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) {
|
|
struct QueueFamilyIndices familyIndices = {
|
|
.graphicsFamily.v = 0,
|
|
.graphicsFamily.is_some = 0,
|
|
|
|
.presentFamily.v = 0,
|
|
.presentFamily.is_some = 0,
|
|
|
|
.transferFamily.v = 0,
|
|
.transferFamily.is_some = 0,
|
|
};
|
|
|
|
uint32_t queueFamilyCount = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, NULL);
|
|
VkQueueFamilyProperties queueFamilies[queueFamilyCount];
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies);
|
|
|
|
for (uint32_t i = 0; i < queueFamilyCount; i++) {
|
|
VkQueueFamilyProperties queueFamily = queueFamilies[i];
|
|
if (queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT
|
|
&& !(queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)) {
|
|
familyIndices.transferFamily.v = i;
|
|
familyIndices.transferFamily.is_some = true;
|
|
}
|
|
|
|
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
|
familyIndices.graphicsFamily.v = i;
|
|
familyIndices.graphicsFamily.is_some = true;
|
|
}
|
|
|
|
VkBool32 presentSupport = 0;
|
|
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &presentSupport);
|
|
|
|
if (presentSupport) {
|
|
familyIndices.presentFamily.v = i;
|
|
familyIndices.presentFamily.is_some = true;
|
|
}
|
|
|
|
if (familyIndices.presentFamily.is_some && familyIndices.graphicsFamily.is_some && familyIndices.transferFamily.is_some) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return familyIndices;
|
|
}
|
|
|
|
static bool isDeviceSuitable(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) { // One could pick the device based on a score.
|
|
struct QueueFamilyIndices familyIndices = findQueueFamilies(physicalDevice, surface);
|
|
|
|
VkPhysicalDeviceProperties deviceProperties;
|
|
vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
|
|
printf("GPU: %s\n", deviceProperties.deviceName);
|
|
VkPhysicalDeviceFeatures deviceFeatures;
|
|
int featureCount = sizeof(deviceFeatures) / sizeof(deviceFeatures.robustBufferAccess);
|
|
VkBool32* p = &deviceFeatures.robustBufferAccess;
|
|
for (int i = 0; i < featureCount; i++) {
|
|
*p = VK_FALSE;
|
|
p++;
|
|
}
|
|
vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures);
|
|
|
|
int extensionsSupported = checkDeviceExtensionSupport(physicalDevice);
|
|
|
|
int swapChainAdequate = 0;
|
|
if (extensionsSupported) {
|
|
struct SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice, surface);
|
|
swapChainAdequate = swapChainSupport.formats[0].format != 0;
|
|
}
|
|
|
|
return
|
|
deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
|
|
deviceFeatures.geometryShader && deviceFeatures.samplerAnisotropy &&
|
|
swapChainAdequate &&
|
|
familyIndices.graphicsFamily.is_some && familyIndices.presentFamily.is_some && familyIndices.transferFamily.is_some;
|
|
}
|
|
|
|
VkSampleCountFlagBits getMaxUseableSampleCount(struct VulkanData* data) {
|
|
VkPhysicalDeviceProperties physicalDeviceProperties;
|
|
vkGetPhysicalDeviceProperties(data->physicalDevice, &physicalDeviceProperties);
|
|
|
|
VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts;
|
|
if (counts & VK_SAMPLE_COUNT_64_BIT) {return VK_SAMPLE_COUNT_64_BIT;}
|
|
if (counts & VK_SAMPLE_COUNT_32_BIT) {return VK_SAMPLE_COUNT_32_BIT;}
|
|
if (counts & VK_SAMPLE_COUNT_16_BIT) {return VK_SAMPLE_COUNT_16_BIT;}
|
|
if (counts & VK_SAMPLE_COUNT_8_BIT) {return VK_SAMPLE_COUNT_8_BIT;}
|
|
if (counts & VK_SAMPLE_COUNT_4_BIT) {return VK_SAMPLE_COUNT_4_BIT;}
|
|
if (counts & VK_SAMPLE_COUNT_2_BIT) {return VK_SAMPLE_COUNT_2_BIT;}
|
|
return VK_SAMPLE_COUNT_1_BIT;
|
|
}
|
|
|
|
static void pickPhysicalDevice(struct VulkanData* data) {
|
|
data->physicalDevice = VK_NULL_HANDLE;
|
|
uint32_t deviceCount = 0;
|
|
vkEnumeratePhysicalDevices(data->instance, &deviceCount, NULL);
|
|
if (deviceCount == 0) {
|
|
printf("ERROR: Failed to find GPUs with Vulkan support\n");
|
|
exit(1);
|
|
}
|
|
VkPhysicalDevice devices[deviceCount];
|
|
vkEnumeratePhysicalDevices(data->instance, &deviceCount, devices);
|
|
|
|
for (uint32_t i = 0; i < deviceCount; i++) {
|
|
VkPhysicalDevice device = devices[i];
|
|
|
|
if (isDeviceSuitable(device, data->surface)) {
|
|
data->physicalDevice = device;
|
|
data->msaaSamples = getMaxUseableSampleCount(data);
|
|
break;
|
|
}
|
|
}
|
|
if (data->physicalDevice == VK_NULL_HANDLE) {
|
|
printf("ERROR: Failed to find a suitable GPU\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void createLogicalDevice(struct VulkanData* data) {
|
|
struct QueueFamilyIndices familyIndices = findQueueFamilies(data->physicalDevice, data->surface);
|
|
float queuePriority = 1.0f;
|
|
|
|
int queueCount = 3;
|
|
VkDeviceQueueCreateInfo queueCreateInfos[queueCount];
|
|
uint32_t uniqueQueueFamilies[] = {familyIndices.graphicsFamily.v, familyIndices.transferFamily.v, familyIndices.presentFamily.v};
|
|
|
|
for (int i = 0; i < queueCount; i++) {
|
|
uint32_t queueFamily = uniqueQueueFamilies[i];
|
|
VkDeviceQueueCreateInfo queueCreateInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
|
.queueFamilyIndex = queueFamily,
|
|
.queueCount = 1,
|
|
.pQueuePriorities = &queuePriority,
|
|
};
|
|
queueCreateInfos[i] = queueCreateInfo;
|
|
}
|
|
|
|
if (familyIndices.graphicsFamily.v == familyIndices.presentFamily.v) {
|
|
queueCount--;
|
|
}
|
|
|
|
for (int i = 0; i < queueCount; i++) {
|
|
uint32_t queueFamily = uniqueQueueFamilies[i];
|
|
VkDeviceQueueCreateInfo queueCreateInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
|
.queueFamilyIndex = queueFamily,
|
|
.queueCount = 1,
|
|
.pQueuePriorities = &queuePriority,
|
|
};
|
|
queueCreateInfos[i] = queueCreateInfo;
|
|
}
|
|
|
|
VkPhysicalDeviceFeatures deviceFeatures;
|
|
int featureCount = sizeof(deviceFeatures) / 4;
|
|
VkBool32* p = &deviceFeatures.robustBufferAccess;
|
|
for (int i = 0; i < featureCount; i++) {
|
|
*p = VK_FALSE;
|
|
p++;
|
|
}
|
|
deviceFeatures.samplerAnisotropy = VK_TRUE;
|
|
deviceFeatures.sampleRateShading = VK_TRUE;
|
|
|
|
VkDeviceCreateInfo createInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
|
.pEnabledFeatures = &deviceFeatures,
|
|
.enabledExtensionCount = DEVICE_EXTENSION_COUNT,
|
|
.queueCreateInfoCount = queueCount,
|
|
.pQueueCreateInfos = queueCreateInfos,
|
|
.ppEnabledExtensionNames = DEVICE_EXTENSIONS,
|
|
};
|
|
|
|
if (enableValidationLayers) {
|
|
createInfo.enabledLayerCount = VALIDATION_LAYER_COUNT;
|
|
createInfo.ppEnabledLayerNames = VALIDATION_LAYERS;
|
|
} else {
|
|
createInfo.enabledLayerCount = 0;
|
|
}
|
|
|
|
if (vkCreateDevice(data->physicalDevice, &createInfo, NULL, &data->device) != VK_SUCCESS) {
|
|
printf("ERROR: Failed to create logical device\n");
|
|
exit(1);
|
|
}
|
|
if (!familyIndices.transferFamily.is_some) {
|
|
printf("no graphics\n");
|
|
}
|
|
if (!familyIndices.presentFamily.is_some) {
|
|
printf("no present\n");
|
|
}
|
|
vkGetDeviceQueue(data->device, familyIndices.graphicsFamily.v, 0, &data->graphicsQueue);
|
|
vkGetDeviceQueue(data->device, familyIndices.presentFamily.v, 0, &data->presentQueue);
|
|
vkGetDeviceQueue(data->device, familyIndices.transferFamily.v, 0, &data->transferQueue);
|
|
}
|
|
|
|
static VkExtent2D chooseSwapExtent(VkSurfaceCapabilitiesKHR* capabilities, GLFWwindow* window) {
|
|
if (capabilities->currentExtent.width != UINT_MAX) {
|
|
return capabilities->currentExtent;
|
|
} else {
|
|
int width, height;
|
|
glfwGetFramebufferSize(window, &width, &height);
|
|
|
|
VkExtent2D actualExtent = {
|
|
(uint32_t)(width),
|
|
(uint32_t)(height)
|
|
};
|
|
|
|
actualExtent.width = clamp(actualExtent.width, capabilities->minImageExtent.width, capabilities->maxImageExtent.width);
|
|
actualExtent.height = clamp(actualExtent.height, capabilities->minImageExtent.height, capabilities->maxImageExtent.height);
|
|
|
|
return actualExtent;
|
|
}
|
|
}
|
|
|
|
static VkSurfaceFormatKHR chooseSwapSurfaceFormat(VkSurfaceFormatKHR availableFormats[]) {
|
|
for(int i = 0; 1; i++) {
|
|
VkSurfaceFormatKHR availableFormat = availableFormats[i];
|
|
if (availableFormat.format == VK_FORMAT_UNDEFINED) {
|
|
break;
|
|
}
|
|
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
|
|
return availableFormat;
|
|
}
|
|
}
|
|
return availableFormats[0];
|
|
}
|
|
|
|
static VkPresentModeKHR chooseSwapPresentMode(VkPresentModeKHR availablePresentModes[]) {
|
|
for(int i = 0; 1; i++) {
|
|
if (availablePresentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
|
|
return availablePresentModes[i];
|
|
}
|
|
}
|
|
return VK_PRESENT_MODE_FIFO_KHR;
|
|
}
|
|
|
|
static void createSwapChain(struct VulkanData* data, GLFWwindow* window) {
|
|
struct SwapChainSupportDetails swapChainSupport = querySwapChainSupport(data->physicalDevice, data->surface);
|
|
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
|
|
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
|
|
VkExtent2D extent = chooseSwapExtent(&swapChainSupport.capabilities, window);
|
|
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
|
|
data->imageCount = imageCount;
|
|
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
|
|
imageCount = swapChainSupport.capabilities.maxImageCount;
|
|
}
|
|
|
|
struct QueueFamilyIndices familyIndices = findQueueFamilies(data->physicalDevice, data->surface);
|
|
uint32_t queueFamilyIndices[] = {familyIndices.graphicsFamily.v, familyIndices.presentFamily.v};
|
|
uint32_t otherFamilyIndices[] = {familyIndices.transferFamily.v, familyIndices.graphicsFamily.v};
|
|
|
|
VkSwapchainCreateInfoKHR createInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
|
.surface = data->surface,
|
|
.minImageCount = imageCount,
|
|
.imageFormat = surfaceFormat.format,
|
|
.imageColorSpace = surfaceFormat.colorSpace,
|
|
.imageExtent = extent,
|
|
.imageArrayLayers = 1,
|
|
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
|
.queueFamilyIndexCount = 2,
|
|
.imageSharingMode = VK_SHARING_MODE_CONCURRENT,
|
|
.pQueueFamilyIndices = otherFamilyIndices,
|
|
.preTransform = swapChainSupport.capabilities.currentTransform,
|
|
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
|
.presentMode = presentMode,
|
|
.clipped = VK_TRUE,
|
|
.oldSwapchain = VK_NULL_HANDLE,
|
|
};
|
|
|
|
if (familyIndices.graphicsFamily.v != familyIndices.presentFamily.v) {
|
|
createInfo.pQueueFamilyIndices = queueFamilyIndices;
|
|
}
|
|
|
|
|
|
if (vkCreateSwapchainKHR(data->device, &createInfo, NULL, &data->swapChain) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create swap chain");
|
|
}
|
|
|
|
data->swapChainImageFormat = surfaceFormat.format;
|
|
data->swapChainExtent = extent;
|
|
|
|
vkGetSwapchainImagesKHR(data->device, data->swapChain, &imageCount, NULL);
|
|
vkGetSwapchainImagesKHR(data->device, data->swapChain, &imageCount, data->swapChainImages);
|
|
}
|
|
|
|
static void createImageViews(struct VulkanData* data) {
|
|
for (int i = 0; i < data->imageCount; i++) {
|
|
data->swapChainImageViews[i] = createImageView(data->swapChainImages[i], data->swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1, data);
|
|
}
|
|
}
|
|
|
|
static void readFile(const char* fileName, char buffer[], int len) {
|
|
FILE* file = fopen(fileName, "r");
|
|
if (!file) {
|
|
printf("ERROR: File \"%s\" can't be opened \n", fileName);
|
|
exit(1);
|
|
}
|
|
fread(buffer, sizeof(buffer[0]), len, file);
|
|
fclose(file);
|
|
}
|
|
|
|
static uint32_t fileSize(const char* fileName) {
|
|
FILE* file = fopen(fileName, "r");
|
|
if (!file) {
|
|
printf("ERROR: File \"%s\" can't be opened \n", fileName);
|
|
exit(1);
|
|
}
|
|
|
|
fseek(file, 0L, SEEK_END);
|
|
uint32_t size = ftell(file);
|
|
printf("INFO: File \"%s\" has size %d\n", fileName, size);
|
|
return size;
|
|
}
|
|
|
|
VkFormat findSupportedFormat(const VkFormat* candidates, int candidateCount, VkImageTiling tiling, VkFormatFeatureFlags features, struct VulkanData* data) {
|
|
for (int i = 0; i < candidateCount; i++) {
|
|
VkFormat format = candidates[i];
|
|
VkFormatProperties props;
|
|
vkGetPhysicalDeviceFormatProperties(data->physicalDevice, format, &props);
|
|
|
|
if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) {
|
|
return format;
|
|
} else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) {
|
|
return format;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "ERROR: Failed to find supported format\n");
|
|
exit(1);
|
|
}
|
|
|
|
VkFormat findDepthFormat(struct VulkanData* data) {
|
|
const VkFormat candidates[] = {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT};
|
|
return findSupportedFormat(
|
|
candidates,
|
|
3,
|
|
VK_IMAGE_TILING_OPTIMAL,
|
|
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT,
|
|
data
|
|
);
|
|
}
|
|
|
|
static VkShaderModule createShaderModule(const char* code, uint32_t size, VkDevice device) {
|
|
VkShaderModuleCreateInfo createInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
.codeSize = size,
|
|
.pCode = (uint32_t*)code,
|
|
};
|
|
VkShaderModule shaderModule;
|
|
if (vkCreateShaderModule(device, &createInfo, NULL, &shaderModule) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create shader module\n");
|
|
}
|
|
return shaderModule;
|
|
}
|
|
|
|
static void createGraphicsPipeline(struct VulkanData* data) {
|
|
const char* vertFileName = "shaders/vert.spv";
|
|
uint32_t vertLen = fileSize(vertFileName);
|
|
char vertShaderCode[vertLen];
|
|
readFile(vertFileName, vertShaderCode, vertLen);
|
|
|
|
const char* fragFileName = "shaders/frag.spv";
|
|
uint32_t fragLen = fileSize(fragFileName);
|
|
char fragShaderCode[fragLen];
|
|
readFile(fragFileName, fragShaderCode, fragLen);
|
|
|
|
VkShaderModule vertShaderModule = createShaderModule(vertShaderCode, vertLen, data->device);
|
|
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode, fragLen, data->device);
|
|
|
|
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
|
.stage = VK_SHADER_STAGE_VERTEX_BIT,
|
|
.module = vertShaderModule,
|
|
.pName = "main",
|
|
};
|
|
|
|
VkPipelineShaderStageCreateInfo fragShaderStageInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
|
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
.module = fragShaderModule,
|
|
.pName = "main",
|
|
.pSpecializationInfo = NULL,
|
|
};
|
|
|
|
VkPipelineDepthStencilStateCreateInfo depthStencil = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
|
.depthTestEnable = VK_TRUE,
|
|
.depthWriteEnable = VK_TRUE,
|
|
.depthCompareOp = VK_COMPARE_OP_LESS,
|
|
.depthBoundsTestEnable = VK_FALSE,
|
|
.minDepthBounds = 0., // Optional
|
|
.maxDepthBounds = 1., // Optional
|
|
.stencilTestEnable = VK_FALSE,
|
|
// .front = NULL, // Optional
|
|
// .back = NULL, // Optional
|
|
};
|
|
|
|
VkPipelineShaderStageCreateInfo shaderStages[2] = {vertShaderStageInfo, fragShaderStageInfo};
|
|
|
|
uint32_t dynamicStateCount = 2;
|
|
VkDynamicState dynamicStates[] = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR
|
|
};
|
|
|
|
VkPipelineDynamicStateCreateInfo dynamicState = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
|
.dynamicStateCount = dynamicStateCount,
|
|
.pDynamicStates = dynamicStates,
|
|
};
|
|
|
|
VkVertexInputBindingDescription bindingDescription = getBindingDescription();
|
|
VkVertexInputAttributeDescription attributeDescriptions[] = {
|
|
{
|
|
.binding = 0,
|
|
.location = 0,
|
|
.format = VK_FORMAT_R32G32B32_SFLOAT,
|
|
.offset = offsetof(Vertex, pos),
|
|
},
|
|
{
|
|
.binding = 0,
|
|
.location = 1,
|
|
.format = VK_FORMAT_R32G32B32_SFLOAT,
|
|
.offset = offsetof(Vertex, color),
|
|
},
|
|
{
|
|
.binding = 0,
|
|
.location = 2,
|
|
.format = VK_FORMAT_R32G32B32_SFLOAT,
|
|
.offset = offsetof(Vertex, texCoord),
|
|
}
|
|
};
|
|
|
|
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
|
.vertexBindingDescriptionCount = 1,
|
|
.vertexAttributeDescriptionCount = 3,
|
|
.pVertexBindingDescriptions = &bindingDescription,
|
|
.pVertexAttributeDescriptions = attributeDescriptions,
|
|
};
|
|
|
|
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
|
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
|
.primitiveRestartEnable = VK_FALSE,
|
|
};
|
|
|
|
VkViewport viewport = {
|
|
.x = 0.0f,
|
|
.y = 0.0f,
|
|
.width = (float) data->swapChainExtent.width,
|
|
.height = (float) data->swapChainExtent.height,
|
|
.minDepth = 0.0f,
|
|
.maxDepth = 1.0f,
|
|
};
|
|
|
|
VkRect2D scissor = {
|
|
.offset = {0, 0},
|
|
.extent = data->swapChainExtent,
|
|
};
|
|
|
|
VkPipelineViewportStateCreateInfo viewportState = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
|
.viewportCount = 1,
|
|
.pViewports = &viewport,
|
|
.scissorCount = 1,
|
|
.pScissors = &scissor,
|
|
};
|
|
|
|
VkPipelineRasterizationStateCreateInfo rasterizer = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
|
.depthClampEnable = VK_FALSE,
|
|
.rasterizerDiscardEnable = VK_FALSE,
|
|
.polygonMode = VK_POLYGON_MODE_FILL,
|
|
.lineWidth = 1.0f,
|
|
.cullMode = VK_CULL_MODE_BACK_BIT,
|
|
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
|
.depthBiasEnable = VK_FALSE,
|
|
.depthBiasConstantFactor = 0.0f, // Optional
|
|
.depthBiasClamp = 0.0f, // Optional
|
|
.depthBiasSlopeFactor = 0.0f, // Optional
|
|
};
|
|
|
|
VkPipelineMultisampleStateCreateInfo multisampling = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
|
.sampleShadingEnable = VK_TRUE,
|
|
.rasterizationSamples = data->msaaSamples,
|
|
.minSampleShading = .2, // Optional
|
|
.pSampleMask = NULL, // Optional
|
|
.alphaToCoverageEnable = VK_FALSE, // Optional
|
|
.alphaToOneEnable = VK_FALSE, // Optional
|
|
};
|
|
|
|
VkPipelineColorBlendAttachmentState colorBlendAttachment = {
|
|
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
|
.blendEnable = VK_FALSE,
|
|
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE, // Optional
|
|
.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, // Optional
|
|
.colorBlendOp = VK_BLEND_OP_ADD, // Optional
|
|
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, // Optional
|
|
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, // Optional
|
|
.alphaBlendOp = VK_BLEND_OP_ADD, // Optional
|
|
};
|
|
|
|
VkPipelineColorBlendStateCreateInfo colorBlending = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
|
.logicOpEnable = VK_FALSE,
|
|
.logicOp = VK_LOGIC_OP_COPY, // Optional
|
|
.attachmentCount = 1,
|
|
.pAttachments = &colorBlendAttachment,
|
|
.blendConstants[0] = 0.0f, // Optional
|
|
.blendConstants[1] = 0.0f, // Optional
|
|
.blendConstants[2] = 0.0f, // Optional
|
|
.blendConstants[3] = 0.0f, // Optional
|
|
};
|
|
|
|
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
|
.setLayoutCount = 1,
|
|
.pSetLayouts = &data->descriptorSetLayout,
|
|
.pushConstantRangeCount = 0, // Optional
|
|
.pPushConstantRanges = NULL, // Optional
|
|
};
|
|
|
|
if (vkCreatePipelineLayout(data->device, &pipelineLayoutInfo, NULL, &data->pipelineLayout) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create pipeline layout\n");
|
|
exit(1);
|
|
}
|
|
|
|
VkGraphicsPipelineCreateInfo pipelineInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
|
.stageCount = 2,
|
|
.pStages = shaderStages,
|
|
.pVertexInputState = &vertexInputInfo,
|
|
.pInputAssemblyState = &inputAssembly,
|
|
.pViewportState = &viewportState,
|
|
.pRasterizationState = &rasterizer,
|
|
.pMultisampleState = &multisampling,
|
|
.pDepthStencilState = &depthStencil,
|
|
.pColorBlendState = &colorBlending,
|
|
.pDynamicState = &dynamicState,
|
|
.layout = data->pipelineLayout,
|
|
.renderPass = data->renderPass,
|
|
.subpass = 0,
|
|
.basePipelineHandle = VK_NULL_HANDLE, // Optional
|
|
.basePipelineIndex = -1, // Optional
|
|
};
|
|
|
|
if (vkCreateGraphicsPipelines(data->device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &data->graphicsPipeline) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create graphics pipeline\n");
|
|
exit(1);
|
|
}
|
|
|
|
vkDestroyShaderModule(data->device, fragShaderModule, NULL);
|
|
vkDestroyShaderModule(data->device, vertShaderModule, NULL);
|
|
}
|
|
|
|
static void createRenderPass(struct VulkanData* data) {
|
|
VkAttachmentDescription colorAttachment = {
|
|
.format = data->swapChainImageFormat,
|
|
.samples = data->msaaSamples,
|
|
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
|
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
|
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
|
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
|
.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
};
|
|
VkAttachmentReference colorAttachmentRef = {
|
|
.attachment = 0,
|
|
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
};
|
|
|
|
VkAttachmentDescription depthAttachment = {
|
|
.format = findDepthFormat(data),
|
|
.samples = data->msaaSamples,
|
|
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
|
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
|
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
|
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
|
.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
|
};
|
|
VkAttachmentReference depthAttachmentRef = {
|
|
.attachment = 1,
|
|
.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
|
};
|
|
|
|
VkAttachmentDescription colorAttachmentResolve = {
|
|
.format = data->swapChainImageFormat,
|
|
.samples = VK_SAMPLE_COUNT_1_BIT,
|
|
.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
|
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
|
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
|
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
|
};
|
|
VkAttachmentReference colorAttachmentResolveRef = {
|
|
.attachment = 2,
|
|
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
};
|
|
|
|
VkSubpassDescription subpass = {
|
|
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
.colorAttachmentCount = 1,
|
|
.pColorAttachments = &colorAttachmentRef,
|
|
.pDepthStencilAttachment = &depthAttachmentRef,
|
|
.pResolveAttachments = &colorAttachmentResolveRef,
|
|
};
|
|
|
|
VkSubpassDependency dependency = {
|
|
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
|
.dstSubpass = 0,
|
|
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
|
|
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
|
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
|
|
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
|
};
|
|
|
|
VkAttachmentDescription attachments[3] = {colorAttachment, depthAttachment, colorAttachmentResolve};
|
|
|
|
VkRenderPassCreateInfo renderPassInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
|
.attachmentCount = 3,
|
|
.pAttachments = attachments,
|
|
.subpassCount = 1,
|
|
.pSubpasses = &subpass,
|
|
.dependencyCount = 1,
|
|
.pDependencies = &dependency,
|
|
};
|
|
|
|
if (vkCreateRenderPass(data->device, &renderPassInfo, NULL, &data->renderPass) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create render pass!\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void createFramebuffers(struct VulkanData* data) {
|
|
for (int i = 0; i < data->imageCount; i++) {
|
|
VkImageView attachments[] = {
|
|
data->colorImageView,
|
|
data->depthImageView,
|
|
data->swapChainImageViews[i],
|
|
};
|
|
|
|
VkFramebufferCreateInfo framebufferInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
|
.renderPass = data->renderPass,
|
|
.attachmentCount = 3,
|
|
.pAttachments = attachments,
|
|
.width = data->swapChainExtent.width,
|
|
.height = data->swapChainExtent.height,
|
|
.layers = 1,
|
|
};
|
|
if (vkCreateFramebuffer(data->device, &framebufferInfo, NULL, &data->swapChainFramebuffers[i]) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create framebuffer\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties, struct VulkanData* data) {
|
|
VkPhysicalDeviceMemoryProperties memProperties;
|
|
vkGetPhysicalDeviceMemoryProperties(data->physicalDevice, &memProperties);
|
|
|
|
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
|
if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties)) {
|
|
return i;
|
|
}
|
|
}
|
|
fprintf(stderr, "ERROR: Failed to find suitable memory type\n");
|
|
exit(1);
|
|
}
|
|
|
|
bool hasStencilComponent(VkFormat format) {
|
|
return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
|
|
}
|
|
|
|
static void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage* image, VkDeviceMemory* imageMemory, struct VulkanData* data) {
|
|
VkImageCreateInfo imageInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
|
.imageType = VK_IMAGE_TYPE_2D,
|
|
.extent.width = width,
|
|
.extent.height = height,
|
|
.extent.depth = 1,
|
|
.mipLevels = mipLevels,
|
|
.arrayLayers = 1,
|
|
.format = format,
|
|
.tiling = tiling,
|
|
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
|
.usage = usage,
|
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
|
.samples = numSamples,
|
|
.flags = 0,
|
|
};
|
|
|
|
if (vkCreateImage(data->device, &imageInfo, NULL, image) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create image\n");
|
|
exit(1);
|
|
}
|
|
|
|
VkMemoryRequirements memRequirements;
|
|
vkGetImageMemoryRequirements(data->device, *image, &memRequirements);
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
|
.allocationSize = memRequirements.size,
|
|
.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties, data),
|
|
};
|
|
|
|
if (vkAllocateMemory(data->device, &allocInfo, NULL, imageMemory) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to allocate image memory\n");
|
|
exit(1);
|
|
}
|
|
|
|
vkBindImageMemory(data->device, *image, *imageMemory, 0);
|
|
}
|
|
|
|
static void createCommandPool(struct VulkanData* data) {
|
|
struct QueueFamilyIndices queueFamilyIndices = findQueueFamilies(data->physicalDevice, data->surface);
|
|
|
|
VkCommandPoolCreateInfo poolInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
|
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
|
.queueFamilyIndex = queueFamilyIndices.graphicsFamily.v,
|
|
};
|
|
|
|
VkCommandPoolCreateInfo transferPoolInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
|
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
|
|
.queueFamilyIndex = queueFamilyIndices.transferFamily.v,
|
|
};
|
|
|
|
if (vkCreateCommandPool(data->device, &poolInfo, NULL, &data->commandPool) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: failed to create command pool\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (vkCreateCommandPool(data->device, &transferPoolInfo, NULL, &data->transferCommandPool) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: failed to create transfer command pool\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void createCommandBuffers(struct VulkanData* data) {
|
|
VkCommandBufferAllocateInfo allocInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
|
.commandPool = data->commandPool,
|
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
.commandBufferCount = MAX_FRAMES_IN_FLIGHT,
|
|
};
|
|
|
|
if (vkAllocateCommandBuffers(data->device, &allocInfo, &data->commandBuffers[data->currentFrame]) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to allocate command buffers\n");
|
|
exit(1);
|
|
}
|
|
|
|
}
|
|
|
|
static void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex, struct VulkanData* data) {
|
|
VkCommandBufferBeginInfo beginInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
|
.flags = 0,
|
|
.pInheritanceInfo = NULL,
|
|
};
|
|
|
|
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to begin recording command buffer\n");
|
|
exit(1);
|
|
}
|
|
|
|
VkClearValue clearValues[] = {
|
|
{ .color = {{0.0f, 0.0f, 0.0f, 1.0f}}, },
|
|
{ .depthStencil = {1., 0}, }
|
|
};
|
|
|
|
VkRenderPassBeginInfo renderPassInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
.renderPass = data->renderPass,
|
|
.framebuffer = data->swapChainFramebuffers[imageIndex],
|
|
.renderArea.offset = {0, 0},
|
|
.renderArea.extent = data->swapChainExtent,
|
|
.clearValueCount = 2,
|
|
.pClearValues = clearValues,
|
|
};
|
|
|
|
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
|
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, data->graphicsPipeline);
|
|
|
|
VkBuffer vertexBuffers[] = {data->vertexBuffer};
|
|
VkDeviceSize offsets[] = {0};
|
|
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
|
|
vkCmdBindIndexBuffer(commandBuffer, data->indexBuffer, 0, VK_INDEX_TYPE_UINT32);
|
|
|
|
VkViewport viewport = {
|
|
.x = 0.0f,
|
|
.y = 0.0f,
|
|
.width = (float) data->swapChainExtent.width,
|
|
.height = (float) data->swapChainExtent.height,
|
|
.minDepth = 0.0f,
|
|
.maxDepth = 1.0f,
|
|
};
|
|
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
|
|
|
|
VkRect2D scissor = {
|
|
.offset = {0, 0},
|
|
.extent = data->swapChainExtent,
|
|
};
|
|
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
|
|
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, data->pipelineLayout, 0, 1, &data->descriptorSets[data->currentFrame], 0, NULL);
|
|
vkCmdDrawIndexed(commandBuffer, data->indexCount, 1, 0, 0, 0);
|
|
vkCmdEndRenderPass(commandBuffer);
|
|
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to record command buffer\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void cleanupSwapChain(struct VulkanData* data) {
|
|
vkDestroyImageView(data->device, data->colorImageView, NULL);
|
|
vkDestroyImage(data->device, data->colorImage, NULL);
|
|
vkFreeMemory(data->device, data->colorImageMemory, NULL);
|
|
vkDestroyImageView(data->device, data->depthImageView, NULL);
|
|
vkDestroyImage(data->device, data->depthImage, NULL);
|
|
vkFreeMemory(data->device, data->depthImageMemory, NULL);
|
|
for (int i = 0; i < data->imageCount; i++) {
|
|
vkDestroyFramebuffer(data->device, data->swapChainFramebuffers[i], NULL);
|
|
}
|
|
|
|
for (int i = 0; i < data->imageCount; i++) {
|
|
vkDestroyImageView(data->device, data->swapChainImageViews[i], NULL);
|
|
}
|
|
|
|
vkDestroySwapchainKHR(data->device, data->swapChain, NULL);
|
|
}
|
|
|
|
static void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels, struct VulkanData* data) {
|
|
VkCommandBuffer commandBuffer = beginSingleTimeCommands(true, data);
|
|
|
|
VkImageMemoryBarrier barrier = {
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
.oldLayout = oldLayout,
|
|
.newLayout = newLayout,
|
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|
.image = image,
|
|
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
.subresourceRange.baseMipLevel = 0,
|
|
.subresourceRange.levelCount = mipLevels,
|
|
.subresourceRange.baseArrayLayer = 0,
|
|
.subresourceRange.layerCount = 1,
|
|
.srcAccessMask = 0,
|
|
.dstAccessMask = 0,
|
|
};
|
|
|
|
VkPipelineStageFlags sourceStage;
|
|
VkPipelineStageFlags destinationStage;
|
|
|
|
if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
|
|
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
if (hasStencilComponent(format)) {
|
|
barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
}
|
|
} else {
|
|
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
}
|
|
|
|
if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
|
|
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
} else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
|
|
barrier.srcAccessMask = 0;
|
|
barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
|
|
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
|
}
|
|
else {
|
|
fprintf(stderr, "ERROR: Unsupported layout transition\n");
|
|
exit(1);
|
|
}
|
|
|
|
vkCmdPipelineBarrier(
|
|
commandBuffer,
|
|
sourceStage, destinationStage,
|
|
0,
|
|
0, NULL,
|
|
0, NULL,
|
|
1, &barrier
|
|
);
|
|
|
|
endSingleTimeCommands(commandBuffer, true, data);
|
|
}
|
|
|
|
|
|
static void createDepthResources(struct VulkanData* data) {
|
|
VkFormat depthFormat = findDepthFormat(data);
|
|
createImage(
|
|
data->swapChainExtent.width,
|
|
data->swapChainExtent.height,
|
|
1,
|
|
data->msaaSamples,
|
|
depthFormat,
|
|
VK_IMAGE_TILING_OPTIMAL,
|
|
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
|
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
|
&data->depthImage,
|
|
&data->depthImageMemory,
|
|
data
|
|
);
|
|
data->depthImageView = createImageView(data->depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1, data);
|
|
transitionImageLayout(data->depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1, data);
|
|
}
|
|
|
|
static void createColorResources(struct VulkanData* data) {
|
|
VkFormat colorFormat = data->swapChainImageFormat;
|
|
|
|
createImage(data->swapChainExtent.width, data->swapChainExtent.height, 1, data->msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &data->colorImage, &data->colorImageMemory, data);
|
|
data->colorImageView = createImageView(data->colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1, data);
|
|
}
|
|
|
|
|
|
static void recreateSwapChain(struct VulkanData* data, GLFWwindow* window) {
|
|
int width = 0, height = 0;
|
|
glfwGetFramebufferSize(window, &width, &height);
|
|
while (width == 0 || height == 0) {
|
|
glfwGetFramebufferSize(window, &width, &height);
|
|
glfwWaitEvents();
|
|
}
|
|
vkDeviceWaitIdle(data->device);
|
|
cleanupSwapChain(data);
|
|
createSwapChain(data, window);
|
|
createImageViews(data);
|
|
createColorResources(data);
|
|
createDepthResources(data);
|
|
createFramebuffers(data);
|
|
}
|
|
|
|
static void updateUniformBuffer(uint32_t currentImage, struct VulkanData* data, struct timespec* start) {
|
|
struct timespec end;
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) {
|
|
perror("clock_gettime");
|
|
exit(1);
|
|
}
|
|
|
|
double elapsed = (end.tv_sec - start->tv_sec) + (end.tv_nsec - start->tv_nsec) / 1e9;
|
|
|
|
Vec3 v1 = { 2., 2., 2. };
|
|
Vec3 v2 = { 0., 0., 0. };
|
|
Vec3 v3 = { 0., 0., 1. };
|
|
|
|
struct UniformBufferObject ubo = {
|
|
.model = rotate(elapsed * M_PI / 2., v3),
|
|
.view = lookAt(v1, v2, v3),
|
|
.proj = perspective(M_PI / 4., data->swapChainExtent.width / (float) data->swapChainExtent.height, 0.1, 10.),
|
|
};
|
|
|
|
ubo.proj.y.y *= -1.;
|
|
memcpy(data->uniformBuffersMapped[currentImage], &ubo, sizeof(ubo));
|
|
}
|
|
|
|
static void drawFrame(struct VulkanData* data, GLFWwindow* window, struct timespec* start) {
|
|
vkWaitForFences(data->device, 1, &data->inFlightFences[data->currentFrame], VK_TRUE, UINT64_MAX);
|
|
|
|
uint32_t imageIndex;
|
|
VkResult result = vkAcquireNextImageKHR(data->device, data->swapChain, UINT64_MAX, data->imageAvailableSemaphores[data->currentFrame], VK_NULL_HANDLE, &imageIndex);
|
|
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
|
recreateSwapChain(data, window);
|
|
return;
|
|
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
|
|
fprintf(stderr, "ERROR: Failed to acquire swap chain image");
|
|
exit(1);
|
|
}
|
|
|
|
vkResetFences(data->device, 1, &data->inFlightFences[data->currentFrame]);
|
|
|
|
vkResetCommandBuffer(data->commandBuffers[data->currentFrame], 0);
|
|
recordCommandBuffer(data->commandBuffers[data->currentFrame], imageIndex, data);
|
|
|
|
VkSemaphore waitSemaphores[] = {data->imageAvailableSemaphores[data->currentFrame]};
|
|
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
|
|
VkSemaphore signalSemaphores[] = {data->renderFinishedSemaphores[data->currentFrame]};
|
|
|
|
updateUniformBuffer(data->currentFrame, data, start);
|
|
VkSubmitInfo submitInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
|
.waitSemaphoreCount = 1,
|
|
.pWaitSemaphores = waitSemaphores,
|
|
.pWaitDstStageMask = waitStages,
|
|
.commandBufferCount = 1,
|
|
.pCommandBuffers = &data->commandBuffers[data->currentFrame],
|
|
.signalSemaphoreCount = 1,
|
|
.pSignalSemaphores = signalSemaphores,
|
|
};
|
|
if (vkQueueSubmit(data->graphicsQueue, 1, &submitInfo, data->inFlightFences[data->currentFrame]) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to submit draw command buffer\n");
|
|
exit(1);
|
|
}
|
|
|
|
VkSwapchainKHR swapChains[] = {data->swapChain};
|
|
VkPresentInfoKHR presentInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
|
.waitSemaphoreCount = 1,
|
|
.pWaitSemaphores = signalSemaphores,
|
|
.swapchainCount = 1,
|
|
.pSwapchains = swapChains,
|
|
.pImageIndices = &imageIndex,
|
|
.pResults = NULL,
|
|
};
|
|
|
|
result = vkQueuePresentKHR(data->presentQueue, &presentInfo);
|
|
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || data->framebufferResized) {
|
|
data->framebufferResized = false;
|
|
recreateSwapChain(data, window);
|
|
} else if (result != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to present swap chain image");
|
|
exit(1);
|
|
}
|
|
data->currentFrame = (data->currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
|
|
|
}
|
|
|
|
static void createSyncObjects(struct VulkanData* data) {
|
|
VkSemaphoreCreateInfo semaphoreInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
|
};
|
|
VkFenceCreateInfo fenceInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
|
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
|
};
|
|
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
|
if (vkCreateSemaphore(data->device, &semaphoreInfo, NULL, &data->imageAvailableSemaphores[i]) != VK_SUCCESS ||
|
|
vkCreateSemaphore(data->device, &semaphoreInfo, NULL, &data->renderFinishedSemaphores[i]) != VK_SUCCESS ||
|
|
vkCreateFence(data->device, &fenceInfo, NULL, &data->inFlightFences[i]) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create semaphores\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, struct VulkanData* data) {
|
|
VkCommandBuffer commandBuffer = beginSingleTimeCommands(false, data);
|
|
|
|
VkBufferImageCopy region = {
|
|
.bufferOffset = 0,
|
|
.bufferRowLength = 0,
|
|
.bufferImageHeight = 0,
|
|
.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
.imageSubresource.mipLevel = 0,
|
|
.imageSubresource.baseArrayLayer = 0,
|
|
.imageSubresource.layerCount = 1,
|
|
.imageOffset = {0, 0, 0},
|
|
.imageExtent = {width, height, 1},
|
|
};
|
|
|
|
vkCmdCopyBufferToImage(
|
|
commandBuffer,
|
|
buffer,
|
|
image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
1,
|
|
®ion
|
|
);
|
|
|
|
endSingleTimeCommands(commandBuffer, false, data);
|
|
}
|
|
|
|
static void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer* buffer, VkDeviceMemory* bufferMemory, struct VulkanData* data) {
|
|
struct QueueFamilyIndices familyIndices = findQueueFamilies(data->physicalDevice, data->surface);
|
|
uint32_t otherFamilyIndices[] = {familyIndices.transferFamily.v, familyIndices.graphicsFamily.v};
|
|
VkBufferCreateInfo bufferInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
|
.size = size,
|
|
.usage = usage,
|
|
.sharingMode = VK_SHARING_MODE_CONCURRENT,
|
|
.queueFamilyIndexCount = 2,
|
|
.pQueueFamilyIndices = otherFamilyIndices,
|
|
};
|
|
|
|
if (vkCreateBuffer(data->device, &bufferInfo, NULL, buffer) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create buffer\n");
|
|
exit(1);
|
|
}
|
|
|
|
VkMemoryRequirements memRequirements;
|
|
vkGetBufferMemoryRequirements(data->device, *buffer, &memRequirements);
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
|
.allocationSize = memRequirements.size,
|
|
.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties, data),
|
|
};
|
|
|
|
if (vkAllocateMemory(data->device, &allocInfo, NULL, bufferMemory) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to allocate buffer memory\n");
|
|
exit(1);
|
|
}
|
|
|
|
vkBindBufferMemory(data->device, *buffer, *bufferMemory, 0);
|
|
}
|
|
|
|
static void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size, struct VulkanData* data) {
|
|
VkCommandBuffer commandBuffer = beginSingleTimeCommands(false, data);
|
|
|
|
VkBufferCopy copyRegion = {
|
|
.srcOffset = 0,
|
|
.dstOffset = 0,
|
|
.size = size,
|
|
};
|
|
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region);
|
|
|
|
endSingleTimeCommands(commandBuffer, false, data);
|
|
}
|
|
|
|
static void createIndexBuffer(struct VulkanData* data) {
|
|
VkDeviceSize bufferSize = sizeof(data->indices[0]) * data->indexCount;
|
|
|
|
VkBuffer stagingBuffer;
|
|
VkDeviceMemory stagingBufferMemory;
|
|
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, &stagingBufferMemory, data);
|
|
|
|
void* indexData;
|
|
vkMapMemory(data->device, stagingBufferMemory, 0, bufferSize, 0, &indexData);
|
|
memcpy(indexData, data->indices, bufferSize);
|
|
vkUnmapMemory(data->device, stagingBufferMemory);
|
|
|
|
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &data->indexBuffer, &data->indexBufferMemory, data);
|
|
|
|
copyBuffer(stagingBuffer, data->indexBuffer, bufferSize, data);
|
|
|
|
vkDestroyBuffer(data->device, stagingBuffer, NULL);
|
|
vkFreeMemory(data->device, stagingBufferMemory, NULL);
|
|
}
|
|
|
|
static void createVertexBuffer(struct VulkanData* data) {
|
|
VkDeviceSize bufferSize = sizeof(data->vertices[0]) * data->vertexCount;
|
|
|
|
VkBuffer stagingBuffer;
|
|
VkDeviceMemory stagingBufferMemory;
|
|
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, &stagingBufferMemory, data);
|
|
|
|
void* vertexData;
|
|
vkMapMemory(data->device, stagingBufferMemory, 0, bufferSize, 0, &vertexData);
|
|
memcpy(vertexData, data->vertices, bufferSize);
|
|
vkUnmapMemory(data->device, stagingBufferMemory);
|
|
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &data->vertexBuffer, &data->vertexBufferMemory, data);
|
|
copyBuffer(stagingBuffer, data->vertexBuffer, bufferSize, data);
|
|
vkDestroyBuffer(data->device, stagingBuffer, NULL);
|
|
vkFreeMemory(data->device, stagingBufferMemory, NULL);
|
|
}
|
|
|
|
static void createDescriptorSetLayout(struct VulkanData* data) {
|
|
VkDescriptorSetLayoutBinding uboLayoutBinding = {
|
|
.binding = 0,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
|
.descriptorCount = 1,
|
|
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
|
|
.pImmutableSamplers = NULL,
|
|
};
|
|
|
|
VkDescriptorSetLayoutBinding samplerLayoutBinding = {
|
|
.binding = 1,
|
|
.descriptorCount = 1,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
.pImmutableSamplers = NULL,
|
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
};
|
|
|
|
VkDescriptorSetLayoutBinding bindings[2] = {uboLayoutBinding, samplerLayoutBinding};
|
|
|
|
VkDescriptorSetLayoutCreateInfo layoutInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
|
.bindingCount = 2,
|
|
.pBindings = bindings,
|
|
};
|
|
|
|
if (vkCreateDescriptorSetLayout(data->device, &layoutInfo, NULL, &data->descriptorSetLayout) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create descriptor set layout\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void createUniformBuffers(struct VulkanData* data) {
|
|
VkDeviceSize bufferSize = sizeof(UniformBufferObject);
|
|
|
|
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
|
createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &data->uniformBuffers[i], &data->uniformBuffersMemory[i], data);
|
|
vkMapMemory(data->device, data->uniformBuffersMemory[i], 0, bufferSize, 0, &data->uniformBuffersMapped[i]);
|
|
}
|
|
}
|
|
|
|
static void createDescriptorPool(struct VulkanData* data) {
|
|
VkDescriptorPoolSize uboPoolSize = {
|
|
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
|
.descriptorCount = MAX_FRAMES_IN_FLIGHT,
|
|
};
|
|
|
|
VkDescriptorPoolSize samplerPoolSize = {
|
|
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
.descriptorCount = MAX_FRAMES_IN_FLIGHT,
|
|
};
|
|
|
|
VkDescriptorPoolSize poolSizes[2] = {uboPoolSize, samplerPoolSize};
|
|
|
|
VkDescriptorPoolCreateInfo poolInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
|
.poolSizeCount = 2,
|
|
.pPoolSizes = poolSizes,
|
|
.maxSets = MAX_FRAMES_IN_FLIGHT,
|
|
.flags = 0,
|
|
};
|
|
|
|
if (vkCreateDescriptorPool(data->device, &poolInfo, NULL, &data->descriptorPool) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create descriptor pool\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void createDescriptorSets(struct VulkanData* data) {
|
|
VkDescriptorSetLayout layouts[MAX_FRAMES_IN_FLIGHT];
|
|
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
|
layouts[i] = data->descriptorSetLayout;
|
|
}
|
|
VkDescriptorSetAllocateInfo allocInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
|
.descriptorPool = data->descriptorPool,
|
|
.descriptorSetCount = MAX_FRAMES_IN_FLIGHT,
|
|
.pSetLayouts = layouts,
|
|
};
|
|
|
|
if (vkAllocateDescriptorSets(data->device, &allocInfo, data->descriptorSets) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to allocate descriptor sets\n");
|
|
exit(1);
|
|
}
|
|
|
|
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
|
VkDescriptorBufferInfo bufferInfo = {
|
|
.buffer = data->uniformBuffers[i],
|
|
.offset = 0,
|
|
.range = sizeof(UniformBufferObject),
|
|
};
|
|
|
|
VkDescriptorImageInfo imageInfo = {
|
|
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
|
.imageView = data->textureImageView,
|
|
.sampler = data->textureSampler,
|
|
};
|
|
|
|
VkWriteDescriptorSet descriptorWrites[2] = {
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
.dstSet = data->descriptorSets[i],
|
|
.dstBinding = 0,
|
|
.dstArrayElement = 0,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
|
.descriptorCount = 1,
|
|
.pBufferInfo = &bufferInfo,
|
|
.pImageInfo = NULL,
|
|
.pTexelBufferView = NULL,
|
|
},
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
.dstSet = data->descriptorSets[i],
|
|
.dstBinding = 1,
|
|
.dstArrayElement = 0,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
.descriptorCount = 1,
|
|
.pBufferInfo = NULL,
|
|
.pImageInfo = &imageInfo,
|
|
.pTexelBufferView = NULL,
|
|
}
|
|
};
|
|
|
|
vkUpdateDescriptorSets(data->device, 2, descriptorWrites, 0, NULL);
|
|
}
|
|
}
|
|
|
|
// Because of the format support and alpha of 1 is added
|
|
static struct PPM readPPM(char* fileName) {
|
|
FILE* file = fopen(fileName, "r");
|
|
char header[2];
|
|
fread(header, sizeof(header[0]), 2, file);
|
|
if (header[0] != 'P' || header[1] != '6') {
|
|
fprintf(stderr, "ERROR: Unrecognized file format\n");
|
|
exit(1);
|
|
}
|
|
if (fseek(file, 1, SEEK_CUR)) {
|
|
fprintf(stderr, "ERROR: Failed to read image: \"%s\"\n", fileName);
|
|
exit(1);
|
|
}
|
|
|
|
int len = 0;
|
|
char c = '0';
|
|
while (true) {
|
|
if (fread(&c, 1, 1, file) != 1) {
|
|
fprintf(stderr, "ERROR: Failed to read width of image: \"%s\" with char: %c\n", fileName, c);
|
|
exit(1);
|
|
}
|
|
if (c < '0' || c > '9') {
|
|
break;
|
|
}
|
|
len++;
|
|
}
|
|
fseek(file, -len - 1, SEEK_CUR);
|
|
char widthStr[len + 1];
|
|
fread(widthStr, sizeof(widthStr[0]), len, file);
|
|
widthStr[len] = 0;
|
|
int width = atoi(widthStr);
|
|
fseek(file, 1, SEEK_CUR);
|
|
|
|
len = 0;
|
|
c = '0';
|
|
while (true) {
|
|
if (fread(&c, 1, 1, file) != 1) {
|
|
fprintf(stderr, "ERROR: Failed to read height of image: \"%s\"\n", fileName);
|
|
exit(1);
|
|
}
|
|
if (c < '0' || c > '9') {
|
|
break;
|
|
}
|
|
len++;
|
|
}
|
|
fseek(file, -len - 1, SEEK_CUR);
|
|
char heightStr[len + 1];
|
|
fread(heightStr, sizeof(heightStr[0]), len, file);
|
|
heightStr[len] = 0;
|
|
int height = atoi(heightStr);
|
|
|
|
fseek(file, 1, SEEK_CUR);
|
|
len = 0;
|
|
c = '0';
|
|
while (true) {
|
|
if (fread(&c, 1, 1, file) != 1) {
|
|
fprintf(stderr, "ERROR: Failed to read maximum value for pixels in image: \"%s\"\n", fileName);
|
|
exit(1);
|
|
}
|
|
if (c < '0' || c > '9') {
|
|
break;
|
|
}
|
|
len++;
|
|
}
|
|
fseek(file, -len - 1, SEEK_CUR);
|
|
char maxValStr[len + 1];
|
|
fread(maxValStr, sizeof(maxValStr[0]), len, file);
|
|
maxValStr[len] = 0;
|
|
int maxVal = atoi(maxValStr);
|
|
|
|
printf("INFO: Image \"%s\" has a width and height of (%d, %d) and maximum value for pixels of %d\n", fileName, width, height, maxVal);
|
|
fseek(file, 1, SEEK_CUR);
|
|
|
|
int pixelCount = width * height;
|
|
|
|
struct PPM ppm = {
|
|
.width = width,
|
|
.height = height,
|
|
.maxVal = maxVal,
|
|
.pixels = NULL,
|
|
};
|
|
|
|
if (maxVal <= UCHAR_MAX) {
|
|
uint8_t* pixels = malloc(pixelCount * 4);
|
|
uint8_t* pixel_p = pixels;
|
|
for (int i = 0; i < pixelCount; i++) {
|
|
fread(pixel_p, sizeof(uint8_t), 3, file);
|
|
pixel_p += 3;
|
|
*pixel_p = UCHAR_MAX;
|
|
pixel_p++;
|
|
}
|
|
ppm.pixels = pixels;
|
|
} else if (maxVal <= USHRT_MAX) {
|
|
uint16_t* pixels = malloc(pixelCount * sizeof(uint16_t) * 4);
|
|
uint16_t* pixel_p = pixels;
|
|
for (int i = 0; i < pixelCount; i++) {
|
|
fread(pixel_p, sizeof(uint16_t), 3, file);
|
|
pixel_p += 3;
|
|
*pixel_p = USHRT_MAX;
|
|
pixel_p++;
|
|
}
|
|
ppm.pixels = pixels;
|
|
} else {
|
|
fprintf(stderr, "ERROR: Specified maximum value for pixels is too large\n");
|
|
exit(1);
|
|
}
|
|
|
|
return ppm;
|
|
}
|
|
|
|
static void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels, struct VulkanData* data) {
|
|
VkFormatProperties formatProperties;
|
|
vkGetPhysicalDeviceFormatProperties(data->physicalDevice, imageFormat, &formatProperties);
|
|
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {
|
|
fprintf(stderr, "ERROR: Texture image format does not support linear blitting\n");
|
|
exit(1);
|
|
}
|
|
|
|
VkCommandBuffer commandBuffer = beginSingleTimeCommands(true, data);
|
|
|
|
VkImageMemoryBarrier barrier = {
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
.image = image,
|
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
.subresourceRange.baseArrayLayer = 0,
|
|
.subresourceRange.layerCount = 1,
|
|
.subresourceRange.levelCount = 1,
|
|
};
|
|
|
|
int32_t mipWidth = texWidth;
|
|
int32_t mipHeight = texHeight;
|
|
|
|
for (uint32_t i = 1; i < mipLevels; i++) {
|
|
barrier.subresourceRange.baseMipLevel = i - 1;
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
vkCmdPipelineBarrier(commandBuffer,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
|
0, NULL,
|
|
0, NULL,
|
|
1, &barrier
|
|
);
|
|
|
|
VkImageBlit blit = {
|
|
.srcOffsets[0] = {0, 0, 0},
|
|
.srcOffsets[1] = {mipWidth, mipHeight, 1},
|
|
.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
.srcSubresource.mipLevel = i - 1,
|
|
.srcSubresource.baseArrayLayer = 0,
|
|
.srcSubresource.layerCount = 1,
|
|
.dstOffsets[0] = {0, 0, 0},
|
|
.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 },
|
|
.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
.dstSubresource.mipLevel = i,
|
|
.dstSubresource.baseArrayLayer = 0,
|
|
.dstSubresource.layerCount = 1,
|
|
};
|
|
|
|
vkCmdBlitImage(commandBuffer,
|
|
image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
1, &blit,
|
|
VK_FILTER_LINEAR
|
|
);
|
|
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
vkCmdPipelineBarrier(commandBuffer,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
|
|
0, NULL,
|
|
0, NULL,
|
|
1, &barrier
|
|
);
|
|
|
|
if (mipWidth > 1) {mipWidth /= 2;}
|
|
if (mipHeight > 1) {mipHeight /= 2;}
|
|
}
|
|
|
|
barrier.subresourceRange.baseMipLevel = mipLevels - 1;
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
vkCmdPipelineBarrier(commandBuffer,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
|
|
0, NULL,
|
|
0, NULL,
|
|
1, &barrier
|
|
);
|
|
|
|
endSingleTimeCommands(commandBuffer, true, data);
|
|
}
|
|
|
|
static void createTextureImage(struct VulkanData* data) {
|
|
struct PPM ppm = readPPM("images/viking_room.ppm");
|
|
int nSize = 1;
|
|
VkFormat format = VK_FORMAT_R8G8B8A8_SRGB;
|
|
if (ppm.maxVal > 255) {
|
|
nSize = 2;
|
|
format = VK_FORMAT_R16G16B16_UINT;
|
|
}
|
|
VkDeviceSize imageSize = ppm.width * ppm.height * nSize * 4;
|
|
data->mipLevels = floor(log2(fmax(ppm.width, ppm.height))) + 1;
|
|
printf("mipLevels: %d\n", data->mipLevels);
|
|
|
|
VkBuffer stagingBuffer;
|
|
VkDeviceMemory stagingBufferMemory;
|
|
|
|
createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, &stagingBufferMemory, data);
|
|
|
|
void* imageData;
|
|
vkMapMemory(data->device, stagingBufferMemory, 0, imageSize, 0, &imageData);
|
|
memcpy(imageData, ppm.pixels, imageSize);
|
|
vkUnmapMemory(data->device, stagingBufferMemory);
|
|
|
|
free(ppm.pixels);
|
|
|
|
createImage(ppm.width, ppm.height, data->mipLevels, VK_SAMPLE_COUNT_1_BIT, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &data->textureImage, &data->textureImageMemory, data),
|
|
|
|
transitionImageLayout(data->textureImage, format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, data->mipLevels, data);
|
|
copyBufferToImage(stagingBuffer, data->textureImage, ppm.width, ppm.height, data);
|
|
// transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps
|
|
generateMipmaps(data->textureImage, format, ppm.width, ppm.height, data->mipLevels, data);
|
|
vkDestroyBuffer(data->device, stagingBuffer, NULL);
|
|
vkFreeMemory(data->device, stagingBufferMemory, NULL);
|
|
}
|
|
|
|
static void createTextureImageView(struct VulkanData* data) {
|
|
data->textureImageView = createImageView(data->textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, data->mipLevels, data);
|
|
}
|
|
|
|
static void createTextureSampler(struct VulkanData* data) {
|
|
VkPhysicalDeviceProperties properties;
|
|
vkGetPhysicalDeviceProperties(data->physicalDevice, &properties);
|
|
VkSamplerCreateInfo samplerInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
|
.magFilter = VK_FILTER_LINEAR,
|
|
.minFilter = VK_FILTER_LINEAR,
|
|
.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
|
.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
|
.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
|
.anisotropyEnable = VK_TRUE,
|
|
.maxAnisotropy = properties.limits.maxSamplerAnisotropy, // set to 1 to disable
|
|
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
|
|
.unnormalizedCoordinates = VK_FALSE,
|
|
.compareEnable = VK_FALSE,
|
|
.compareOp = VK_COMPARE_OP_ALWAYS,
|
|
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
|
.minLod = 0.,
|
|
.maxLod = VK_LOD_CLAMP_NONE,
|
|
.mipLodBias = 0.,
|
|
};
|
|
|
|
if (vkCreateSampler(data->device, &samplerInfo, NULL, &data->textureSampler) != VK_SUCCESS) {
|
|
fprintf(stderr, "ERROR: Failed to create texture sampler\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
float parseFloat(char data[], char numberBuf[], int* i) {
|
|
int j = 0;
|
|
char c = data[*i + j];
|
|
while (c != ' ' && c != '\n') {
|
|
if ((c >= '0' && c <= '9') || c == '.' || c == '-') {
|
|
numberBuf[j] = c;
|
|
} else {
|
|
printf("ERROR: sheesh at char: %c\n", c);
|
|
exit(1);
|
|
}
|
|
j++;
|
|
c = data[*i + j];
|
|
}
|
|
numberBuf[j] = 0;
|
|
*i += j + 1;
|
|
return atof(numberBuf);
|
|
}
|
|
|
|
struct ObjModel {
|
|
void* p;
|
|
int vertexCount;
|
|
int normalCount;
|
|
uint32_t faceCount;
|
|
int faceSplitCount;
|
|
Vec3* vertexPos;
|
|
Vec2* texPos;
|
|
Vec3* normalVectors;
|
|
int* faces;
|
|
int* faceSplits;
|
|
};
|
|
|
|
static struct ObjModel readObjFile(const char* fileName) {
|
|
FILE* file = fopen(fileName, "r");
|
|
int objFileSize = fileSize(fileName);
|
|
char* data = malloc(objFileSize);
|
|
fread(data, sizeof(char), objFileSize, file);
|
|
int lines = 0;
|
|
for (int i = 0; i < objFileSize; i++) {
|
|
if (data[i] == '\n') {
|
|
lines++;
|
|
}
|
|
}
|
|
|
|
int lengthOfLines[lines];
|
|
int currentLine = 0;
|
|
int lineLenCount = 0;
|
|
for (int i = 0; i < objFileSize; i++) {
|
|
lineLenCount++;
|
|
if (data[i] == '\n') {
|
|
lengthOfLines[currentLine] = lineLenCount - 1;
|
|
lineLenCount = 0;
|
|
currentLine++;
|
|
}
|
|
}
|
|
|
|
int vertexCount = 0;
|
|
int curveCount = 0;
|
|
int normalCount = 0;
|
|
int textureCount = 0;
|
|
int faceCount = 0;
|
|
int faceSplitCount = 0;
|
|
currentLine = 0;
|
|
|
|
for (int i = 0; currentLine < lines; i++) {
|
|
if (data[i] == 'v') {
|
|
switch(data[i + 1]) {
|
|
case ' ':
|
|
vertexCount++;
|
|
break;
|
|
case 'p':
|
|
curveCount++;
|
|
break;
|
|
case 'n':
|
|
normalCount++;
|
|
break;
|
|
case 't':
|
|
textureCount++;
|
|
break;
|
|
default:
|
|
printf("ERROR: Invalid char\n");
|
|
exit(1);
|
|
}
|
|
} else if (data[i] == 'f') {
|
|
faceCount++;
|
|
} else if (data[i] == 's') {
|
|
faceSplitCount++;
|
|
}
|
|
i += lengthOfLines[currentLine];
|
|
currentLine++;
|
|
}
|
|
printf("vertices: %d, curvePoints: %d, normalVectors: %d, textureVertices: %d, faces: %d\n", vertexCount, curveCount, normalCount, textureCount, faceCount);
|
|
int vertexSize = sizeof(Vec3) * vertexCount;
|
|
int textureSize = sizeof(Vec2) * textureCount;
|
|
int normalSize = sizeof(Vec3) * normalCount;
|
|
int faceSize = sizeof(int) * 9 * faceCount;
|
|
int faceSplitSize = sizeof(int) * faceSplitCount;
|
|
|
|
char* p = malloc(vertexSize + textureSize + normalSize + faceSize + faceSplitSize);
|
|
Vec3* vertexPos = (Vec3*) p;
|
|
Vec2* texPos = (Vec2*) (p + vertexSize);
|
|
Vec3* normalVectors = (Vec3*) (p + vertexSize + textureSize);
|
|
int* faces = (int*) (p + vertexSize + textureSize + normalSize);
|
|
int* faceSplits = (int*) (p + vertexSize + textureSize + normalSize + faceSize);
|
|
|
|
int vertexIndex = 0;
|
|
int textureIndex = 0;
|
|
int normalIndex = 0;
|
|
int faceIndex = 0;
|
|
int faceSplitIndex = 0;
|
|
|
|
char numberBuf[16];
|
|
currentLine = 0;
|
|
for (int i = 0; i < objFileSize; i++) {
|
|
if (data[i] == 'v') {
|
|
if (data[i + 1] == ' ') {
|
|
i += 2;
|
|
Vec3 pos = {
|
|
parseFloat(data, numberBuf, &i),
|
|
parseFloat(data, numberBuf, &i),
|
|
parseFloat(data, numberBuf, &i),
|
|
};
|
|
vertexPos[vertexIndex] = pos;
|
|
vertexIndex++;
|
|
i--;
|
|
} else if (data[i + 1] == 'p') {
|
|
} else if(data[i + 1] == 'n') {
|
|
i += 3;
|
|
Vec3 pos = {
|
|
parseFloat(data, numberBuf, &i),
|
|
parseFloat(data, numberBuf, &i),
|
|
parseFloat(data, numberBuf, &i),
|
|
};
|
|
normalVectors[normalIndex] = pos;
|
|
normalIndex++;
|
|
i--;
|
|
} else if (data[i + 1] == 't') {
|
|
i += 3;
|
|
Vec2 pos = {
|
|
parseFloat(data, numberBuf, &i),
|
|
parseFloat(data, numberBuf, &i),
|
|
};
|
|
texPos[textureIndex] = pos;
|
|
textureIndex++;
|
|
i--;
|
|
} else {
|
|
printf("sheesh\n");
|
|
exit(1);
|
|
}
|
|
} else if (data[i] == 'f') {
|
|
i += 2;
|
|
int numLen = 0;
|
|
for (int j = 0; j < 9; j++) {
|
|
while (data[i + numLen] != '/' && data[i + numLen] != ' ' && data[i + numLen] != '\n') {
|
|
numLen++;
|
|
}
|
|
for (int k = 0; k < numLen; k++) {
|
|
numberBuf[k] = data[i + k];
|
|
}
|
|
numberBuf[numLen] = 0;
|
|
faces[faceIndex * 9 + j] = atoi(numberBuf);
|
|
i += numLen + 1;
|
|
numLen = 0;
|
|
}
|
|
i--;
|
|
faceIndex++;
|
|
} else if (data[i] == 's') {
|
|
faceSplits[faceSplitIndex] = faceIndex;
|
|
i += lengthOfLines[currentLine];
|
|
} else {
|
|
i += lengthOfLines[currentLine];
|
|
}
|
|
currentLine++;
|
|
}
|
|
printf("last vert pos: (%f, %f, %f)\n", vertexPos[vertexCount - 1].x, vertexPos[vertexCount - 1].y, vertexPos[vertexCount - 1].z);
|
|
printf("last texture pos: (%f, %f)\n", texPos[textureCount - 1].x, texPos[textureCount - 1].y);
|
|
printf("last normal pos: (%f, %f, %f)\n", normalVectors[normalCount - 1].x, normalVectors[normalCount - 1].y, normalVectors[normalCount - 1].z);
|
|
printf("last face: %d, %d\n", faces[(faceCount - 1) * 9], faces[(faceCount - 1) * 9 + 8]);
|
|
printf("face split: %d\n", faceSplits[0]);
|
|
free(data);
|
|
struct ObjModel model = {
|
|
p,
|
|
vertexCount,
|
|
normalCount,
|
|
faceCount,
|
|
faceSplitCount,
|
|
vertexPos,
|
|
texPos,
|
|
normalVectors,
|
|
faces,
|
|
faceSplits,
|
|
};
|
|
return model;
|
|
}
|
|
|
|
bool isVertexEqual(Vertex* a, Vertex* b) {
|
|
return
|
|
a->pos.x == b->pos.x
|
|
&& a->pos.y == b->pos.y
|
|
&& a->pos.z == b->pos.z
|
|
&& a->texCoord.x == b->texCoord.x
|
|
&& a->texCoord.y == b->texCoord.y;
|
|
}
|
|
|
|
static void loadModel(struct VulkanData* data) {
|
|
struct ObjModel model = readObjFile("models/viking_room.obj");
|
|
data->indexCount = model.faceCount * 3;
|
|
void* p = malloc(data->indexCount * (sizeof(Vertex) + sizeof(uint32_t)));
|
|
data->vertices = p;
|
|
data->indices = (uint32_t*)((char*)p + sizeof(Vertex) * data->indexCount);
|
|
int uniqueVertexCount = 0;
|
|
|
|
printf("Vertices allocated %ld bytes\n", sizeof(Vertex) * data->indexCount);
|
|
printf("Index count: %d\n", data->indexCount);
|
|
|
|
for (uint32_t i = 0; i < data->indexCount; i++) {
|
|
uint32_t* face = ((uint32_t*) model.faces + i * 3);
|
|
Vertex vertex = {
|
|
.pos = model.vertexPos[*(face) - 1],
|
|
.texCoord = model.texPos[*(face + 1) - 1],
|
|
.color = {1., 1., 1.},
|
|
};
|
|
vertex.texCoord.y = 1. - vertex.texCoord.y;
|
|
bool seen = false;
|
|
for (int j = 0; j < uniqueVertexCount; j++) {
|
|
if (isVertexEqual(&vertex, &data->vertices[j])) {
|
|
data->indices[i] = j;
|
|
seen = true;
|
|
break;
|
|
}
|
|
}
|
|
if (seen) {
|
|
continue;
|
|
}
|
|
data->vertices[uniqueVertexCount] = vertex;
|
|
data->indices[i] = uniqueVertexCount;
|
|
uniqueVertexCount++;
|
|
}
|
|
|
|
// Realign data such that vertices are right next to indices
|
|
data->vertexCount = uniqueVertexCount;
|
|
memmove((Vertex*)p + data->vertexCount, data->indices, sizeof(uint32_t) * data->indexCount);
|
|
p = realloc(p, sizeof(Vertex) * data->vertexCount + sizeof(uint32_t) * data->indexCount);
|
|
data->vertices = p;
|
|
data->indices = (uint32_t*)((Vertex*)p + data->vertexCount);
|
|
printf("Vertices are now using %ld bytes\n", sizeof(Vertex) * data->vertexCount);
|
|
|
|
free(model.p);
|
|
printf("Unique vertices: %d\n", data->vertexCount);
|
|
}
|
|
|
|
static void initVulkan(GLFWwindow* window, struct VulkanData* data) {
|
|
data->currentFrame = 0;
|
|
data->framebufferResized = false;
|
|
data->msaaSamples = VK_SAMPLE_COUNT_1_BIT;
|
|
createInstance(data);
|
|
setupDebugMessenger(data);
|
|
createSurface(data, window);
|
|
pickPhysicalDevice(data);
|
|
createLogicalDevice(data);
|
|
createSwapChain(data, window);
|
|
createImageViews(data);
|
|
createRenderPass(data);
|
|
createDescriptorSetLayout(data);
|
|
createGraphicsPipeline(data);
|
|
createCommandPool(data);
|
|
createColorResources(data);
|
|
createDepthResources(data);
|
|
createFramebuffers(data);
|
|
createTextureImage(data);
|
|
createTextureImageView(data);
|
|
createTextureSampler(data);
|
|
loadModel(data);
|
|
createVertexBuffer(data);
|
|
createIndexBuffer(data);
|
|
createUniformBuffers(data);
|
|
createDescriptorPool(data);
|
|
createDescriptorSets(data);
|
|
createCommandBuffers(data);
|
|
createSyncObjects(data);
|
|
}
|
|
|
|
static void mainLoop(GLFWwindow* window, struct VulkanData* data, struct timespec* start) {
|
|
struct timespec frameTimeStart, frameTimeEnd;
|
|
int count = 100;
|
|
double elapsed[count];
|
|
int i = 0;
|
|
while (!glfwWindowShouldClose(window)) {
|
|
if (clock_gettime(CLOCK_MONOTONIC, &frameTimeStart) != 0) {
|
|
perror("clock_gettime");
|
|
exit(1);
|
|
}
|
|
glfwSwapBuffers(window);
|
|
glfwPollEvents();
|
|
drawFrame(data, window, start);
|
|
int state = glfwGetKey(window, GLFW_KEY_Q);
|
|
if (state == GLFW_PRESS) {
|
|
break;
|
|
}
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &frameTimeEnd) != 0) {
|
|
perror("clock_gettime");
|
|
exit(1);
|
|
}
|
|
elapsed[i] = (frameTimeEnd.tv_sec - frameTimeStart.tv_sec) + (frameTimeEnd.tv_nsec - frameTimeStart.tv_nsec) / 1e6;
|
|
i++;
|
|
i %= count;
|
|
if (i == 0) {
|
|
double worst = elapsed[0];
|
|
for(int j = 1; j < count; j++) {
|
|
worst = fmax(worst, elapsed[j]);
|
|
}
|
|
printf("INFO: worst of %d: %f ms\n", count, worst);
|
|
}
|
|
}
|
|
vkDeviceWaitIdle(data->device);
|
|
}
|
|
|
|
static void cleanup(GLFWwindow* window, struct VulkanData* data) {
|
|
cleanupSwapChain(data);
|
|
// Only one allocation for both vertices and indices
|
|
free(data->vertices);
|
|
|
|
vkDestroySampler(data->device, data->textureSampler, NULL);
|
|
vkDestroyImageView(data->device, data->textureImageView, NULL);
|
|
vkDestroyImage(data->device, data->textureImage, NULL);
|
|
vkFreeMemory(data->device, data->textureImageMemory, NULL);
|
|
|
|
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
|
vkDestroyBuffer(data->device, data->uniformBuffers[i], NULL);
|
|
vkFreeMemory(data->device, data->uniformBuffersMemory[i], NULL);
|
|
}
|
|
vkDestroyDescriptorPool(data->device, data->descriptorPool, NULL);
|
|
vkDestroyDescriptorSetLayout(data->device, data->descriptorSetLayout, NULL);
|
|
vkDestroyBuffer(data->device, data->vertexBuffer, NULL);
|
|
vkFreeMemory(data->device, data->vertexBufferMemory, NULL);
|
|
vkDestroyBuffer(data->device, data->indexBuffer, NULL);
|
|
vkFreeMemory(data->device, data->indexBufferMemory, NULL);
|
|
|
|
vkDestroyPipeline(data->device, data->graphicsPipeline, NULL);
|
|
vkDestroyPipelineLayout(data->device, data->pipelineLayout, NULL);
|
|
vkDestroyRenderPass(data->device, data->renderPass, NULL);
|
|
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
|
vkDestroySemaphore(data->device, data->imageAvailableSemaphores[i], NULL);
|
|
vkDestroySemaphore(data->device, data->renderFinishedSemaphores[i], NULL);
|
|
vkDestroyFence(data->device, data->inFlightFences[i], NULL);
|
|
}
|
|
vkDestroyCommandPool(data->device, data->commandPool, NULL);
|
|
vkDestroyCommandPool(data->device, data->transferCommandPool, NULL);
|
|
vkDestroyDevice(data->device, NULL);
|
|
if (enableValidationLayers) {
|
|
DestroyDebugUtilsMessengerEXT(data->instance, data->debugMessenger, NULL);
|
|
}
|
|
vkDestroySurfaceKHR(data->instance, data->surface, NULL);
|
|
vkDestroyInstance(data->instance, NULL);
|
|
glfwDestroyWindow(window);
|
|
glfwTerminate();
|
|
}
|
|
|
|
static void run() {
|
|
struct VulkanData data;
|
|
printf("INFO: Size of data struct: %ld bytes\n", sizeof(data));
|
|
GLFWwindow* window = initWindow(&data);
|
|
initVulkan(window, &data);
|
|
struct timespec start;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) {
|
|
perror("clock_gettime");
|
|
exit(1);
|
|
}
|
|
mainLoop(window, &data, &start);
|
|
cleanup(window, &data);
|
|
}
|
|
|
|
int main() {
|
|
run();
|
|
}
|