/* * Vulkan Example - Basic indexed triangle rendering * * Note : * This is a "pedal to the metal" example to show off how to get Vulkan up an displaying something * Contrary to the other examples, this one won't make use of helper functions or initializers * Except in a few cases (swap chain setup e.g.) * * Copyright (C) 2016 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #include #include #include #include #include #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include #include #include "vulkanexamplebase.h" #define VERTEX_BUFFER_BIND_ID 0 // Set to "true" to enable Vulkan's validation layers // See vulkandebug.cpp for details #define ENABLE_VALIDATION false // Set to "true" to use staging buffers for uploading // vertex and index data to device local memory // See "prepareVertices" for details on what's staging // and on why to use it #define USE_STAGING true class VulkanExample : public VulkanExampleBase { public: struct { VkBuffer buf; VkDeviceMemory mem; VkPipelineVertexInputStateCreateInfo vi; std::vector bindingDescriptions; std::vector attributeDescriptions; } vertices; struct { int count; VkBuffer buf; VkDeviceMemory mem; } indices; struct { VkBuffer buffer; VkDeviceMemory memory; VkDescriptorBufferInfo descriptor; } uniformDataVS; struct { glm::mat4 projectionMatrix; glm::mat4 modelMatrix; glm::mat4 viewMatrix; } uboVS; struct { VkPipeline solid; } pipelines; VkPipelineLayout pipelineLayout; VkDescriptorSet descriptorSet; VkDescriptorSetLayout descriptorSetLayout; // Synchronization semaphores struct { VkSemaphore presentComplete; VkSemaphore renderComplete; } semaphores; VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) { width = 1280; height = 720; zoom = -2.5f; title = "Vulkan Example - Basic indexed triangle"; // Values not set here are initialized in the base class constructor } ~VulkanExample() { // Clean up used Vulkan resources // Note : Inherited destructor cleans up resources stored in base class vkDestroyPipeline(device, pipelines.solid, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); vkDestroyBuffer(device, vertices.buf, nullptr); vkFreeMemory(device, vertices.mem, nullptr); vkDestroyBuffer(device, indices.buf, nullptr); vkFreeMemory(device, indices.mem, nullptr); vkDestroySemaphore(device, semaphores.presentComplete, nullptr); vkDestroySemaphore(device, semaphores.renderComplete, nullptr); vkDestroyBuffer(device, uniformDataVS.buffer, nullptr); vkFreeMemory(device, uniformDataVS.memory, nullptr); } // Build separate command buffers for every framebuffer image // Unlike in OpenGL all rendering commands are recorded once // into command buffers that are then resubmitted to the queue void buildCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = {}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufInfo.pNext = NULL; VkClearValue clearValues[2]; clearValues[0].color = defaultClearColor; clearValues[1].depthStencil = { 1.0f, 0 }; VkRenderPassBeginInfo renderPassBeginInfo = {}; renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassBeginInfo.pNext = NULL; renderPassBeginInfo.renderPass = renderPass; renderPassBeginInfo.renderArea.offset.x = 0; renderPassBeginInfo.renderArea.offset.y = 0; renderPassBeginInfo.renderArea.extent.width = width; renderPassBeginInfo.renderArea.extent.height = height; renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValues; VkResult err; for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) { // Set target frame buffer renderPassBeginInfo.framebuffer = frameBuffers[i]; err = vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo); assert(!err); vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); // Update dynamic viewport state VkViewport viewport = {}; viewport.height = (float)height; viewport.width = (float)width; viewport.minDepth = (float) 0.0f; viewport.maxDepth = (float) 1.0f; vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); // Update dynamic scissor state VkRect2D scissor = {}; scissor.extent.width = width; scissor.extent.height = height; scissor.offset.x = 0; scissor.offset.y = 0; vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); // Bind descriptor sets describing shader binding points vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); // Bind the rendering pipeline (including the shaders) vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid); // Bind triangle vertices VkDeviceSize offsets[1] = { 0 }; vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &vertices.buf, offsets); // Bind triangle indices vkCmdBindIndexBuffer(drawCmdBuffers[i], indices.buf, 0, VK_INDEX_TYPE_UINT32); // Draw indexed triangle vkCmdDrawIndexed(drawCmdBuffers[i], indices.count, 1, 0, 0, 1); vkCmdEndRenderPass(drawCmdBuffers[i]); // Add a present memory barrier to the end of the command buffer // This will transform the frame buffer color attachment to a // new layout for presenting it to the windowing system integration VkImageMemoryBarrier prePresentBarrier = {}; prePresentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; prePresentBarrier.pNext = NULL; prePresentBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; prePresentBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; prePresentBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; prePresentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; prePresentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; prePresentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; prePresentBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; prePresentBarrier.image = swapChain.buffers[i].image; VkImageMemoryBarrier *pMemoryBarrier = &prePresentBarrier; vkCmdPipelineBarrier( drawCmdBuffers[i], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_FLAGS_NONE, 0, nullptr, 0, nullptr, 1, &prePresentBarrier); err = vkEndCommandBuffer(drawCmdBuffers[i]); assert(!err); } } void draw() { VkResult err; // Get next image in the swap chain (back/front buffer) err = swapChain.acquireNextImage(semaphores.presentComplete, ¤tBuffer); assert(!err); // Add a post present image memory barrier // This will transform the frame buffer color attachment back // to it's initial layout after it has been presented to the // windowing system VkImageMemoryBarrier postPresentBarrier = {}; postPresentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; postPresentBarrier.pNext = NULL; postPresentBarrier.srcAccessMask = 0; postPresentBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; postPresentBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; postPresentBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; postPresentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; postPresentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; postPresentBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; postPresentBarrier.image = swapChain.buffers[currentBuffer].image; // Use dedicated command buffer from example base class for submitting the post present barrier VkCommandBufferBeginInfo cmdBufInfo = {}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; err = vkBeginCommandBuffer(postPresentCmdBuffer, &cmdBufInfo); assert(!err); // Put post present barrier into command buffer vkCmdPipelineBarrier( postPresentCmdBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_FLAGS_NONE, 0, nullptr, 0, nullptr, 1, &postPresentBarrier); err = vkEndCommandBuffer(postPresentCmdBuffer); assert(!err); // Submit to the queue submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &postPresentCmdBuffer; err = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); assert(!err); err = vkQueueWaitIdle(queue); assert(!err); // The submit infor strcuture contains a list of // command buffers and semaphores to be submitted to a queue // If you want to submit multiple command buffers, pass an array VkPipelineStageFlags pipelineStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pWaitDstStageMask = &pipelineStages; // The wait semaphore ensures that the image is presented // before we start submitting command buffers agein submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = &semaphores.presentComplete; // Submit the currently active command buffer submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; // The signal semaphore is used during queue presentation // to ensure that the image is not rendered before all // commands have been submitted submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = &semaphores.renderComplete; // Submit to the graphics queue err = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); assert(!err); // Present the current buffer to the swap chain // We pass the signal semaphore from the submit info // to ensure that the image is not rendered until // all commands have been submitted err = swapChain.queuePresent(queue, currentBuffer, semaphores.renderComplete); assert(!err); } // Create synchronzation semaphores void prepareSemaphore() { VkSemaphoreCreateInfo semaphoreCreateInfo = {}; semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; semaphoreCreateInfo.pNext = NULL; // This semaphore ensures that the image is complete // before starting to submit again VkResult err = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.presentComplete); assert(!err); // This semaphore ensures that all commands submitted // have been finished before submitting the image to the queue err = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.renderComplete); assert(!err); } // Setups vertex and index buffers for an indexed triangle, // uploads them to the VRAM and sets binding points and attribute // descriptions to match locations inside the shaders void prepareVertices(bool useStagingBuffers) { struct Vertex { float pos[3]; float col[3]; }; // Setup vertices std::vector vertexBuffer = { { { 1.0f, 1.0f, 0.0f },{ 1.0f, 0.0f, 0.0f } }, { { -1.0f, 1.0f, 0.0f },{ 0.0f, 1.0f, 0.0f } }, { { 0.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0f } } }; int vertexBufferSize = vertexBuffer.size() * sizeof(Vertex); // Setup indices std::vector indexBuffer = { 0, 1, 2 }; uint32_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); indices.count = indexBuffer.size(); VkMemoryAllocateInfo memAlloc = {}; memAlloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; VkMemoryRequirements memReqs; void *data; if (useStagingBuffers) { // Static data like vertex and index buffer should be stored on the device memory // for optimal (and fastest) access by the GPU // // To achieve this we use so-called "staging buffers" : // - Create a buffer that's visible to the host (and can be mapped) // - Copy the data to this buffer // - Create another buffer that's local on the device (VRAM) with the same size // - Copy the data from the host to the device using a command buffer struct StagingBuffer { VkDeviceMemory memory; VkBuffer buffer; }; struct { StagingBuffer vertices; StagingBuffer indices; } stagingBuffers; // Buffer copies are done on the queue, so we need a command buffer for them VkCommandBufferAllocateInfo cmdBufInfo = {}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufInfo.commandPool = cmdPool; cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdBufInfo.commandBufferCount = 1; VkCommandBuffer copyCommandBuffer; vkTools::checkResult(vkAllocateCommandBuffers(device, &cmdBufInfo, ©CommandBuffer)); // Vertex buffer VkBufferCreateInfo vertexBufferInfo = {}; vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; vertexBufferInfo.size = vertexBufferSize; // Buffer is used as the copy source vertexBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; // Create a host-visible buffer to copy the vertex data to (staging buffer) vkTools::checkResult(vkCreateBuffer(device, &vertexBufferInfo, nullptr, &stagingBuffers.vertices.buffer)); vkGetBufferMemoryRequirements(device, stagingBuffers.vertices.buffer, &memReqs); memAlloc.allocationSize = memReqs.size; getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex); vkTools::checkResult(vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.vertices.memory)); // Map and copy vkTools::checkResult(vkMapMemory(device, stagingBuffers.vertices.memory, 0, memAlloc.allocationSize, 0, &data)); memcpy(data, vertexBuffer.data(), vertexBufferSize); vkUnmapMemory(device, stagingBuffers.vertices.memory); vkTools::checkResult(vkBindBufferMemory(device, stagingBuffers.vertices.buffer, stagingBuffers.vertices.memory, 0)); // Create the destination buffer with device only visibility // Buffer will be used as a vertex buffer and is the copy destination vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; vkTools::checkResult(vkCreateBuffer(device, &vertexBufferInfo, nullptr, &vertices.buf)); vkGetBufferMemoryRequirements(device, vertices.buf, &memReqs); memAlloc.allocationSize = memReqs.size; getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAlloc.memoryTypeIndex); vkTools::checkResult(vkAllocateMemory(device, &memAlloc, nullptr, &vertices.mem)); vkTools::checkResult(vkBindBufferMemory(device, vertices.buf, vertices.mem, 0)); // Index buffer // todo : comment VkBufferCreateInfo indexbufferInfo = {}; indexbufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; indexbufferInfo.size = indexBufferSize; indexbufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; // Copy index data to a buffer visible to the host (staging buffer) vkTools::checkResult(vkCreateBuffer(device, &indexbufferInfo, nullptr, &stagingBuffers.indices.buffer)); vkGetBufferMemoryRequirements(device, stagingBuffers.indices.buffer, &memReqs); memAlloc.allocationSize = memReqs.size; getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex); vkTools::checkResult(vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.indices.memory)); vkTools::checkResult(vkMapMemory(device, stagingBuffers.indices.memory, 0, indexBufferSize, 0, &data)); memcpy(data, indexBuffer.data(), indexBufferSize); vkUnmapMemory(device, stagingBuffers.indices.memory); vkTools::checkResult(vkBindBufferMemory(device, stagingBuffers.indices.buffer, stagingBuffers.indices.memory, 0)); // Create destination buffer with device only visibility indexbufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; vkTools::checkResult(vkCreateBuffer(device, &indexbufferInfo, nullptr, &indices.buf)); vkGetBufferMemoryRequirements(device, indices.buf, &memReqs); memAlloc.allocationSize = memReqs.size; getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAlloc.memoryTypeIndex); vkTools::checkResult(vkAllocateMemory(device, &memAlloc, nullptr, &indices.mem)); vkTools::checkResult(vkBindBufferMemory(device, indices.buf, indices.mem, 0)); indices.count = indexBuffer.size(); VkCommandBufferBeginInfo cmdBufferBeginInfo = {}; cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufferBeginInfo.pNext = NULL; VkBufferCopy copyRegion = {}; // Put buffer region copies into command buffer // Note that the staging buffer must not be deleted before the copies // have been submitted and executed vkTools::checkResult(vkBeginCommandBuffer(copyCommandBuffer, &cmdBufferBeginInfo)); // Vertex buffer copyRegion.size = vertexBufferSize; vkCmdCopyBuffer( copyCommandBuffer, stagingBuffers.vertices.buffer, vertices.buf, 1, ©Region); // Index buffer copyRegion.size = indexBufferSize; vkCmdCopyBuffer( copyCommandBuffer, stagingBuffers.indices.buffer, indices.buf, 1, ©Region); vkTools::checkResult(vkEndCommandBuffer(copyCommandBuffer)); // Submit copies to the queue VkSubmitInfo copySubmitInfo = {}; copySubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; copySubmitInfo.commandBufferCount = 1; copySubmitInfo.pCommandBuffers = ©CommandBuffer; vkTools::checkResult(vkQueueSubmit(queue, 1, ©SubmitInfo, VK_NULL_HANDLE)); vkTools::checkResult(vkQueueWaitIdle(queue)); vkFreeCommandBuffers(device, cmdPool, 1, ©CommandBuffer); // Destroy staging buffers vkDestroyBuffer(device, stagingBuffers.vertices.buffer, nullptr); vkFreeMemory(device, stagingBuffers.vertices.memory, nullptr); vkDestroyBuffer(device, stagingBuffers.indices.buffer, nullptr); vkFreeMemory(device, stagingBuffers.indices.memory, nullptr); } else { // Don't use staging // Create host-visible buffers only and use these for rendering // This is not advised for real world applications and will // result in lower performances // Vertex buffer VkBufferCreateInfo vertexBufferInfo = {}; vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; vertexBufferInfo.size = vertexBufferSize; vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; // Copy vertex data to a buffer visible to the host vkTools::checkResult(vkCreateBuffer(device, &vertexBufferInfo, nullptr, &vertices.buf)); vkGetBufferMemoryRequirements(device, vertices.buf, &memReqs); memAlloc.allocationSize = memReqs.size; getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex); vkTools::checkResult(vkAllocateMemory(device, &memAlloc, nullptr, &vertices.mem)); vkTools::checkResult(vkMapMemory(device, vertices.mem, 0, memAlloc.allocationSize, 0, &data)); memcpy(data, vertexBuffer.data(), vertexBufferSize); vkUnmapMemory(device, vertices.mem); vkTools::checkResult(vkBindBufferMemory(device, vertices.buf, vertices.mem, 0)); // Index buffer VkBufferCreateInfo indexbufferInfo = {}; indexbufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; indexbufferInfo.size = indexBufferSize; indexbufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; // Copy index data to a buffer visible to the host memset(&indices, 0, sizeof(indices)); vkTools::checkResult(vkCreateBuffer(device, &indexbufferInfo, nullptr, &indices.buf)); vkGetBufferMemoryRequirements(device, indices.buf, &memReqs); memAlloc.allocationSize = memReqs.size; getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex); vkTools::checkResult(vkAllocateMemory(device, &memAlloc, nullptr, &indices.mem)); vkTools::checkResult(vkMapMemory(device, indices.mem, 0, indexBufferSize, 0, &data)); memcpy(data, indexBuffer.data(), indexBufferSize); vkUnmapMemory(device, indices.mem); vkTools::checkResult(vkBindBufferMemory(device, indices.buf, indices.mem, 0)); indices.count = indexBuffer.size(); } // Binding description vertices.bindingDescriptions.resize(1); vertices.bindingDescriptions[0].binding = VERTEX_BUFFER_BIND_ID; vertices.bindingDescriptions[0].stride = sizeof(Vertex); vertices.bindingDescriptions[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; // Attribute descriptions // Describes memory layout and shader attribute locations vertices.attributeDescriptions.resize(2); // Location 0 : Position vertices.attributeDescriptions[0].binding = VERTEX_BUFFER_BIND_ID; vertices.attributeDescriptions[0].location = 0; vertices.attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; vertices.attributeDescriptions[0].offset = 0; vertices.attributeDescriptions[0].binding = 0; // Location 1 : Color vertices.attributeDescriptions[1].binding = VERTEX_BUFFER_BIND_ID; vertices.attributeDescriptions[1].location = 1; vertices.attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; vertices.attributeDescriptions[1].offset = sizeof(float) * 3; vertices.attributeDescriptions[1].binding = 0; // Assign to vertex buffer vertices.vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertices.vi.pNext = NULL; vertices.vi.vertexBindingDescriptionCount = vertices.bindingDescriptions.size(); vertices.vi.pVertexBindingDescriptions = vertices.bindingDescriptions.data(); vertices.vi.vertexAttributeDescriptionCount = vertices.attributeDescriptions.size(); vertices.vi.pVertexAttributeDescriptions = vertices.attributeDescriptions.data(); } void setupDescriptorPool() { // We need to tell the API the number of max. requested descriptors per type VkDescriptorPoolSize typeCounts[1]; // This example only uses one descriptor type (uniform buffer) and only // requests one descriptor of this type typeCounts[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; typeCounts[0].descriptorCount = 1; // For additional types you need to add new entries in the type count list // E.g. for two combined image samplers : // typeCounts[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; // typeCounts[1].descriptorCount = 2; // Create the global descriptor pool // All descriptors used in this example are allocated from this pool VkDescriptorPoolCreateInfo descriptorPoolInfo = {}; descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; descriptorPoolInfo.pNext = NULL; descriptorPoolInfo.poolSizeCount = 1; descriptorPoolInfo.pPoolSizes = typeCounts; // Set the max. number of sets that can be requested // Requesting descriptors beyond maxSets will result in an error descriptorPoolInfo.maxSets = 1; VkResult vkRes = vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool); assert(!vkRes); } void setupDescriptorSetLayout() { // Setup layout of descriptors used in this example // Basically connects the different shader stages to descriptors // for binding uniform buffers, image samplers, etc. // So every shader binding should map to one descriptor set layout // binding // Binding 0 : Uniform buffer (Vertex shader) VkDescriptorSetLayoutBinding layoutBinding = {}; layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; layoutBinding.descriptorCount = 1; layoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; layoutBinding.pImmutableSamplers = NULL; VkDescriptorSetLayoutCreateInfo descriptorLayout = {}; descriptorLayout.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; descriptorLayout.pNext = NULL; descriptorLayout.bindingCount = 1; descriptorLayout.pBindings = &layoutBinding; VkResult err = vkCreateDescriptorSetLayout(device, &descriptorLayout, NULL, &descriptorSetLayout); assert(!err); // Create the pipeline layout that is used to generate the rendering pipelines that // are based on this descriptor set layout // In a more complex scenario you would have different pipeline layouts for different // descriptor set layouts that could be reused VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {}; pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pPipelineLayoutCreateInfo.pNext = NULL; pPipelineLayoutCreateInfo.setLayoutCount = 1; pPipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayout; err = vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout); assert(!err); } void setupDescriptorSet() { // Update descriptor sets determining the shader binding points // For every binding point used in a shader there needs to be one // descriptor set matching that binding point VkWriteDescriptorSet writeDescriptorSet = {}; VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; allocInfo.descriptorSetCount = 1; allocInfo.pSetLayouts = &descriptorSetLayout; VkResult vkRes = vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet); assert(!vkRes); // Binding 0 : Uniform buffer writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSet.dstSet = descriptorSet; writeDescriptorSet.descriptorCount = 1; writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; writeDescriptorSet.pBufferInfo = &uniformDataVS.descriptor; // Binds this uniform buffer to binding point 0 writeDescriptorSet.dstBinding = 0; vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, NULL); } void preparePipelines() { // Create our rendering pipeline used in this example // Vulkan uses the concept of rendering pipelines to encapsulate // fixed states // This replaces OpenGL's huge (and cumbersome) state machine // A pipeline is then stored and hashed on the GPU making // pipeline changes much faster than having to set dozens of // states // In a real world application you'd have dozens of pipelines // for every shader set used in a scene // Note that there are a few states that are not stored with // the pipeline. These are called dynamic states and the // pipeline only stores that they are used with this pipeline, // but not their states VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; VkResult err; pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; // The layout used for this pipeline pipelineCreateInfo.layout = pipelineLayout; // Renderpass this pipeline is attached to pipelineCreateInfo.renderPass = renderPass; // Vertex input state // Describes the topoloy used with this pipeline VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {}; inputAssemblyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; // This pipeline renders vertex data as triangle lists inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // Rasterization state VkPipelineRasterizationStateCreateInfo rasterizationState = {}; rasterizationState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; // Solid polygon mode rasterizationState.polygonMode = VK_POLYGON_MODE_FILL; // No culling rasterizationState.cullMode = VK_CULL_MODE_NONE; rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizationState.depthClampEnable = VK_FALSE; rasterizationState.rasterizerDiscardEnable = VK_FALSE; rasterizationState.depthBiasEnable = VK_FALSE; rasterizationState.lineWidth = 1.0f; // Color blend state // Describes blend modes and color masks VkPipelineColorBlendStateCreateInfo colorBlendState = {}; colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; // One blend attachment state // Blending is not used in this example VkPipelineColorBlendAttachmentState blendAttachmentState[1] = {}; blendAttachmentState[0].colorWriteMask = 0xf; blendAttachmentState[0].blendEnable = VK_FALSE; colorBlendState.attachmentCount = 1; colorBlendState.pAttachments = blendAttachmentState; // Viewport state VkPipelineViewportStateCreateInfo viewportState = {}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; // One viewport viewportState.viewportCount = 1; // One scissor rectangle viewportState.scissorCount = 1; // Enable dynamic states // Describes the dynamic states to be used with this pipeline // Dynamic states can be set even after the pipeline has been created // So there is no need to create new pipelines just for changing // a viewport's dimensions or a scissor box VkPipelineDynamicStateCreateInfo dynamicState = {}; // The dynamic state properties themselves are stored in the command buffer std::vector dynamicStateEnables; dynamicStateEnables.push_back(VK_DYNAMIC_STATE_VIEWPORT); dynamicStateEnables.push_back(VK_DYNAMIC_STATE_SCISSOR); dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.pDynamicStates = dynamicStateEnables.data(); dynamicState.dynamicStateCount = dynamicStateEnables.size(); // Depth and stencil state // Describes depth and stenctil test and compare ops VkPipelineDepthStencilStateCreateInfo depthStencilState = {}; // Basic depth compare setup with depth writes and depth test enabled // No stencil used depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencilState.depthTestEnable = VK_TRUE; depthStencilState.depthWriteEnable = VK_TRUE; depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; depthStencilState.depthBoundsTestEnable = VK_FALSE; depthStencilState.back.failOp = VK_STENCIL_OP_KEEP; depthStencilState.back.passOp = VK_STENCIL_OP_KEEP; depthStencilState.back.compareOp = VK_COMPARE_OP_ALWAYS; depthStencilState.stencilTestEnable = VK_FALSE; depthStencilState.front = depthStencilState.back; // Multi sampling state VkPipelineMultisampleStateCreateInfo multisampleState = {}; multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampleState.pSampleMask = NULL; // No multi sampling used in this example multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; // Load shaders // Shaders are loaded from the SPIR-V format, which can be generated from glsl std::array shaderStages; shaderStages[0] = loadShader(getAssetPath() + "shaders/triangle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getAssetPath() + "shaders/triangle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); // Assign states // Assign pipeline state create information pipelineCreateInfo.stageCount = shaderStages.size(); pipelineCreateInfo.pStages = shaderStages.data(); pipelineCreateInfo.pVertexInputState = &vertices.vi; pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; pipelineCreateInfo.pRasterizationState = &rasterizationState; pipelineCreateInfo.pColorBlendState = &colorBlendState; pipelineCreateInfo.pMultisampleState = &multisampleState; pipelineCreateInfo.pViewportState = &viewportState; pipelineCreateInfo.pDepthStencilState = &depthStencilState; pipelineCreateInfo.renderPass = renderPass; pipelineCreateInfo.pDynamicState = &dynamicState; // Create rendering pipeline err = vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.solid); assert(!err); } void prepareUniformBuffers() { // Prepare and initialize uniform buffer containing shader uniforms VkMemoryRequirements memReqs; // Vertex shader uniform buffer block VkBufferCreateInfo bufferInfo = {}; VkMemoryAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.pNext = NULL; allocInfo.allocationSize = 0; allocInfo.memoryTypeIndex = 0; VkResult err; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = sizeof(uboVS); bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; // Create a new buffer err = vkCreateBuffer(device, &bufferInfo, nullptr, &uniformDataVS.buffer); assert(!err); // Get memory requirements including size, alignment and memory type vkGetBufferMemoryRequirements(device, uniformDataVS.buffer, &memReqs); allocInfo.allocationSize = memReqs.size; // Gets the appropriate memory type for this type of buffer allocation // Only memory types that are visible to the host getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &allocInfo.memoryTypeIndex); // Allocate memory for the uniform buffer err = vkAllocateMemory(device, &allocInfo, nullptr, &(uniformDataVS.memory)); assert(!err); // Bind memory to buffer err = vkBindBufferMemory(device, uniformDataVS.buffer, uniformDataVS.memory, 0); assert(!err); // Store information in the uniform's descriptor uniformDataVS.descriptor.buffer = uniformDataVS.buffer; uniformDataVS.descriptor.offset = 0; uniformDataVS.descriptor.range = sizeof(uboVS); updateUniformBuffers(); } void updateUniformBuffers() { // Update matrices uboVS.projectionMatrix = glm::perspective(glm::radians(60.0f), (float)width / (float)height, 0.1f, 256.0f); uboVS.viewMatrix = glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, zoom)); uboVS.modelMatrix = glm::mat4(); uboVS.modelMatrix = glm::rotate(uboVS.modelMatrix, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); uboVS.modelMatrix = glm::rotate(uboVS.modelMatrix, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); uboVS.modelMatrix = glm::rotate(uboVS.modelMatrix, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); // Map uniform buffer and update it uint8_t *pData; VkResult err = vkMapMemory(device, uniformDataVS.memory, 0, sizeof(uboVS), 0, (void **)&pData); assert(!err); memcpy(pData, &uboVS, sizeof(uboVS)); vkUnmapMemory(device, uniformDataVS.memory); assert(!err); } void prepare() { VulkanExampleBase::prepare(); prepareSemaphore(); prepareVertices(USE_STAGING); prepareUniformBuffers(); setupDescriptorSetLayout(); preparePipelines(); setupDescriptorPool(); setupDescriptorSet(); buildCommandBuffers(); prepared = true; } virtual void render() { if (!prepared) return; vkDeviceWaitIdle(device); draw(); vkDeviceWaitIdle(device); } virtual void viewChanged() { // This function is called by the base example class // each time the view is changed by user input updateUniformBuffers(); } }; VulkanExample *vulkanExample; #if defined(_WIN32) LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (vulkanExample != NULL) { vulkanExample->handleMessages(hWnd, uMsg, wParam, lParam); } return (DefWindowProc(hWnd, uMsg, wParam, lParam)); } #elif defined(__linux__) && !defined(__ANDROID__) static void handleEvent(const xcb_generic_event_t *event) { if (vulkanExample != NULL) { vulkanExample->handleEvent(event); } } #endif // Main entry point #if defined(_WIN32) // Windows entry point int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow) #elif defined(__ANDROID__) // Android entry point void android_main(android_app* state) #elif defined(__linux__) // Linux entry point int main(const int argc, const char *argv[]) #endif { #if defined(__ANDROID__) // Removing this may cause the compiler to omit the main entry point // which would make the application crash at start app_dummy(); #endif vulkanExample = new VulkanExample(); #if defined(_WIN32) vulkanExample->setupWindow(hInstance, WndProc); #elif defined(__ANDROID__) // Attach vulkan example to global android application state state->userData = vulkanExample; state->onAppCmd = VulkanExample::handleAppCommand; state->onInputEvent = VulkanExample::handleAppInput; vulkanExample->androidApp = state; #elif defined(__linux__) vulkanExample->setupWindow(); #endif #if !defined(__ANDROID__) vulkanExample->initSwapchain(); vulkanExample->prepare(); #endif vulkanExample->renderLoop(); delete(vulkanExample); #if !defined(__ANDROID__) return 0; #endif }