diff --git a/Makefile b/Makefile index 8e1e466..55dec93 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lm -CFLAGS = -g -pedantic -Wall -Wextra -Wshadow -Wunused-macros +CFLAGS = -g -pedantic -Wall -Wextra -Wshadow -Wunused-macros VulkanApplication: main.c gcc $(CFLAGS) -o VulkanApplication main.c $(LDFLAGS) diff --git a/images/statue.ppm b/images/statue.ppm new file mode 100644 index 0000000..639dcb1 Binary files /dev/null and b/images/statue.ppm differ diff --git a/main.c b/main.c index 579eb31..2053373 100644 --- a/main.c +++ b/main.c @@ -63,21 +63,35 @@ struct UniformBufferObject { } UniformBufferObject; typedef struct Vertex { - Vec2 pos; + Vec3 pos; Vec3 color; + Vec2 texCoord; } Vertex; -#define VERTEX_COUNT 4 -static const Vertex vertices[VERTEX_COUNT] = { - {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, - {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, - {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +struct PPM { + uint32_t width; + uint32_t height; + uint32_t maxVal; + void* pixels; }; -#define INDEX_COUNT 6 +#define VERTEX_COUNT 8 +static const Vertex vertices[VERTEX_COUNT] = { + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + + {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, +}; + +#define INDEX_COUNT 12 static const uint16_t indices[INDEX_COUNT] = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 }; struct VulkanData { @@ -117,6 +131,13 @@ struct VulkanData { void* uniformBuffersMapped[MAX_FRAMES_IN_FLIGHT]; VkDescriptorPool descriptorPool; VkDescriptorSet descriptorSets[MAX_FRAMES_IN_FLIGHT]; + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + VkImage depthImage; + VkDeviceMemory depthImageMemory; + VkImageView depthImageView; }; struct SwapChainSupportDetails { @@ -127,7 +148,7 @@ struct SwapChainSupportDetails { struct Optional { uint32_t v; - uint32_t is_some; + bool is_some; }; struct QueueFamilyIndices { @@ -240,6 +261,73 @@ GLFWwindow* initWindow(struct VulkanData* data) { return window; } +static VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, 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 = 1, + .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); @@ -323,6 +411,20 @@ static void createInstance(struct VulkanData* data) { printf("\t%s\n", extensions[i]); } + // const VkBool32 setting_validate_core = VK_TRUE; + // const VkBool32 setting_validate_best_practices = VK_TRUE; + // 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} + // }; + // const VkLayerSettingsCreateInfoEXT layer_settings_create_info = { + // VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT, + // NULL, + // 1, + // settings + // }; + + VkInstanceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &appInfo, @@ -341,7 +443,7 @@ static void createInstance(struct VulkanData* data) { .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 = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + createInfo.pNext = &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; createInfo.pNext = NULL; @@ -494,7 +596,7 @@ static bool isDeviceSuitable(VkPhysicalDevice physicalDevice, VkSurfaceKHR surfa vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); printf("GPU: %s\n", deviceProperties.deviceName); VkPhysicalDeviceFeatures deviceFeatures; - int featureCount = sizeof(deviceFeatures) / 4; + int featureCount = sizeof(deviceFeatures) / sizeof(deviceFeatures.robustBufferAccess); VkBool32* p = &deviceFeatures.robustBufferAccess; for (int i = 0; i < featureCount; i++) { *p = VK_FALSE; @@ -512,7 +614,7 @@ static bool isDeviceSuitable(VkPhysicalDevice physicalDevice, VkSurfaceKHR surfa return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && - deviceFeatures.geometryShader && + deviceFeatures.geometryShader && deviceFeatures.samplerAnisotropy && swapChainAdequate && familyIndices.graphicsFamily.is_some && familyIndices.presentFamily.is_some && familyIndices.transferFamily.is_some; } @@ -583,6 +685,7 @@ static void createLogicalDevice(struct VulkanData* data) { *p = VK_FALSE; p++; } + deviceFeatures.samplerAnisotropy = VK_TRUE; VkDeviceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, @@ -708,24 +811,7 @@ static void createSwapChain(struct VulkanData* data, GLFWwindow* window) { static void createImageViews(struct VulkanData* data) { for (int i = 0; i < data->imageCount; i++) { - VkImageViewCreateInfo createInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = data->swapChainImages[i], - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = data->swapChainImageFormat, - .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, - .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, - .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, - .components.a = VK_COMPONENT_SWIZZLE_IDENTITY, - .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .subresourceRange.baseMipLevel = 0, - .subresourceRange.levelCount = 1, - .subresourceRange.baseArrayLayer = 0, - .subresourceRange.layerCount = 1, - }; - if (vkCreateImageView(data->device, &createInfo, NULL, &data->swapChainImageViews[i]) != VK_SUCCESS) { - fprintf(stderr, "failed to create image views!"); - } + data->swapChainImageViews[i] = createImageView(data->swapChainImages[i], data->swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, data); } } @@ -753,6 +839,34 @@ static uint32_t fileSize(const char* fileName) { 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, @@ -795,6 +909,19 @@ static void createGraphicsPipeline(struct VulkanData* data) { .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; @@ -810,11 +937,11 @@ static void createGraphicsPipeline(struct VulkanData* data) { }; VkVertexInputBindingDescription bindingDescription = getBindingDescription(); - VkVertexInputAttributeDescription attributeDescriptions[2] = { + VkVertexInputAttributeDescription attributeDescriptions[] = { { .binding = 0, .location = 0, - .format = VK_FORMAT_R32G32_SFLOAT, + .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, pos), }, { @@ -822,13 +949,19 @@ static void createGraphicsPipeline(struct VulkanData* data) { .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 = 2, + .vertexAttributeDescriptionCount = 3, .pVertexBindingDescriptions = &bindingDescription, .pVertexAttributeDescriptions = attributeDescriptions, }; @@ -867,8 +1000,7 @@ static void createGraphicsPipeline(struct VulkanData* data) { .rasterizerDiscardEnable = VK_FALSE, .polygonMode = VK_POLYGON_MODE_FILL, .lineWidth = 1.0f, - // .cullMode = VK_CULL_MODE_BACK_BIT, - .cullMode = 0, + .cullMode = VK_CULL_MODE_BACK_BIT, .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .depthBiasEnable = VK_FALSE, .depthBiasConstantFactor = 0.0f, // Optional @@ -931,7 +1063,7 @@ static void createGraphicsPipeline(struct VulkanData* data) { .pViewportState = &viewportState, .pRasterizationState = &rasterizer, .pMultisampleState = &multisampling, - .pDepthStencilState = NULL, // Optional + .pDepthStencilState = &depthStencil, .pColorBlendState = &colorBlending, .pDynamicState = &dynamicState, .layout = data->pipelineLayout, @@ -966,26 +1098,44 @@ static void createRenderPass(struct VulkanData* data) { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; + VkAttachmentDescription depthAttachment = { + .format = findDepthFormat(data), + .samples = VK_SAMPLE_COUNT_1_BIT, + .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, + }; VkSubpassDescription subpass = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &colorAttachmentRef, + .pDepthStencilAttachment = &depthAttachmentRef, }; VkSubpassDependency dependency = { .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, - .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - .srcAccessMask = 0, - .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .srcAccessMask = 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[2] = {colorAttachment, depthAttachment}; + VkRenderPassCreateInfo renderPassInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, - .attachmentCount = 1, - .pAttachments = &colorAttachment, + .attachmentCount = 2, + .pAttachments = attachments, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, @@ -1001,13 +1151,14 @@ static void createRenderPass(struct VulkanData* data) { static void createFramebuffers(struct VulkanData* data) { for (int i = 0; i < data->imageCount; i++) { VkImageView attachments[] = { - data->swapChainImageViews[i] + data->swapChainImageViews[i], + data->depthImageView, }; VkFramebufferCreateInfo framebufferInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = data->renderPass, - .attachmentCount = 1, + .attachmentCount = 2, .pAttachments = attachments, .width = data->swapChainExtent.width, .height = data->swapChainExtent.height, @@ -1021,6 +1172,59 @@ static void createFramebuffers(struct VulkanData* data) { } } +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); +} + +static void createImage(uint32_t width, uint32_t height, 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 = 1, + .arrayLayers = 1, + .format = format, + .tiling = tiling, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .samples = VK_SAMPLE_COUNT_1_BIT, + .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); @@ -1074,7 +1278,10 @@ static void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageInd exit(1); } - VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + VkClearValue clearValues[] = { + { .color = {{0.0f, 0.0f, 0.0f, 1.0f}}, }, + { .depthStencil = {1., 0}, } + }; VkRenderPassBeginInfo renderPassInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, @@ -1082,8 +1289,8 @@ static void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageInd .framebuffer = data->swapChainFramebuffers[imageIndex], .renderArea.offset = {0, 0}, .renderArea.extent = data->swapChainExtent, - .clearValueCount = 1, - .pClearValues = &clearColor, + .clearValueCount = 2, + .pClearValues = clearValues, }; vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); @@ -1119,6 +1326,9 @@ static void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageInd } static void cleanupSwapChain(struct VulkanData* data) { + 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); } @@ -1130,6 +1340,22 @@ static void cleanupSwapChain(struct VulkanData* data) { vkDestroySwapchainKHR(data->device, data->swapChain, NULL); } +static void createDepthResources(struct VulkanData* data) { + VkFormat depthFormat = findDepthFormat(data); + createImage( + data->swapChainExtent.width, + data->swapChainExtent.height, + 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, data); +} + static void recreateSwapChain(struct VulkanData* data, GLFWwindow* window) { int width = 0, height = 0; glfwGetFramebufferSize(window, &width, &height); @@ -1141,6 +1367,7 @@ static void recreateSwapChain(struct VulkanData* data, GLFWwindow* window) { cleanupSwapChain(data); createSwapChain(data, window); createImageViews(data); + createDepthResources(data); createFramebuffers(data); } @@ -1246,17 +1473,81 @@ static void createSyncObjects(struct VulkanData* data) { } } -static uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties, struct VulkanData* data) { - VkPhysicalDeviceMemoryProperties memProperties; - vkGetPhysicalDeviceMemoryProperties(data->physicalDevice, &memProperties); +static void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, struct VulkanData* data) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(false, data); - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties)) { - return i; - } + 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 transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, 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 = 1, + .subresourceRange.baseArrayLayer = 0, + .subresourceRange.layerCount = 1, + .srcAccessMask = 0, + .dstAccessMask = 0, + }; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + 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 { + fprintf(stderr, "ERROR: Unsupported layout transition\n"); + exit(1); } - fprintf(stderr, "ERROR: Failed to find suitable memory type\n"); - exit(1); + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, NULL, + 0, NULL, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer, true, data); } static void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer* buffer, VkDeviceMemory* bufferMemory, struct VulkanData* data) { @@ -1294,21 +1585,7 @@ static void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPr } static void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size, struct VulkanData* data) { - VkCommandBufferAllocateInfo allocInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandPool = data->transferCommandPool, - .commandBufferCount = 1, - }; - 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); + VkCommandBuffer commandBuffer = beginSingleTimeCommands(false, data); VkBufferCopy copyRegion = { .srcOffset = 0, @@ -1316,16 +1593,8 @@ static void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size .size = size, }; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); - vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .commandBufferCount = 1, - .pCommandBuffers = &commandBuffer, - }; - vkQueueSubmit(data->transferQueue, 1, &submitInfo, VK_NULL_HANDLE); - vkQueueWaitIdle(data->transferQueue); - vkFreeCommandBuffers(data->device, data->transferCommandPool, 1, &commandBuffer); + endSingleTimeCommands(commandBuffer, false, data); } static void createIndexBuffer(struct VulkanData* data) { @@ -1374,10 +1643,20 @@ static void createDescriptorSetLayout(struct VulkanData* data) { .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 = 1, - .pBindings = &uboLayoutBinding, + .bindingCount = 2, + .pBindings = bindings, }; if (vkCreateDescriptorSetLayout(data->device, &layoutInfo, NULL, &data->descriptorSetLayout) != VK_SUCCESS) { @@ -1396,19 +1675,25 @@ static void createUniformBuffers(struct VulkanData* data) { } static void createDescriptorPool(struct VulkanData* data) { - VkDescriptorPoolSize poolSize = { + 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 = 1, - .pPoolSizes = &poolSize, + .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"); @@ -1440,25 +1725,221 @@ static void createDescriptorSets(struct VulkanData* data) { .range = sizeof(UniformBufferObject), }; - VkWriteDescriptorSet descriptorWrite = { - .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, + VkDescriptorImageInfo imageInfo = { + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .imageView = data->textureImageView, + .sampler = data->textureSampler, }; - vkUpdateDescriptorSets(data->device, 1, &descriptorWrite, 0, NULL); + 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 createTextureImage(struct VulkanData* data) { + struct PPM ppm = readPPM("images/statue.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; + + 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, format, VK_IMAGE_TILING_OPTIMAL, 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, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, data); + copyBufferToImage(stagingBuffer, data->textureImage, ppm.width, ppm.height, data); + transitionImageLayout(data->textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 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); +} + +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, + .mipLodBias = 0., + .minLod = 0., + .maxLod = 0., + }; + + if (vkCreateSampler(data->device, &samplerInfo, NULL, &data->textureSampler) != VK_SUCCESS) { + fprintf(stderr, "ERROR: Failed to create texture sampler\n"); + exit(1); + } +} + +bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; +} + static void initVulkan(GLFWwindow* window, struct VulkanData* data) { data->currentFrame = 0; - data->framebufferResized = 0; + data->framebufferResized = false; createInstance(data); setupDebugMessenger(data); createSurface(data, window); @@ -1469,8 +1950,12 @@ static void initVulkan(GLFWwindow* window, struct VulkanData* data) { createRenderPass(data); createDescriptorSetLayout(data); createGraphicsPipeline(data); - createFramebuffers(data); createCommandPool(data); + createDepthResources(data); + createFramebuffers(data); + createTextureImage(data); + createTextureImageView(data); + createTextureSampler(data); createVertexBuffer(data); createIndexBuffer(data); createUniformBuffers(data); @@ -1497,6 +1982,11 @@ static void mainLoop(GLFWwindow* window, struct VulkanData* data, struct timespe static void cleanup(GLFWwindow* window, struct VulkanData* data) { cleanupSwapChain(data); + 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); diff --git a/shaders/shader.frag b/shaders/shader.frag index 7c5b0e7..ad32317 100644 --- a/shaders/shader.frag +++ b/shaders/shader.frag @@ -1,9 +1,12 @@ #version 450 layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; layout(location = 0) out vec4 outColor; +layout(binding = 1) uniform sampler2D texSampler; + void main() { - outColor = vec4(fragColor, 1.0); + outColor = texture(texSampler, fragTexCoord); } diff --git a/shaders/shader.vert b/shaders/shader.vert index 733e31f..8527cc1 100644 --- a/shaders/shader.vert +++ b/shaders/shader.vert @@ -1,9 +1,11 @@ #version 450 -layout(location = 0) in vec2 inPosition; +layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; layout(binding = 0) uniform UniformBufferObject { mat4 model; @@ -12,6 +14,7 @@ layout(binding = 0) uniform UniformBufferObject { } ubo; void main() { - gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); fragColor = inColor; + fragTexCoord = inTexCoord; }