| @@ -7,8 +7,8 @@ | |||
| #include <string> | |||
| #include <vector> | |||
| const uint32_t kWidth = 128; | |||
| const uint32_t kHeight = 128; | |||
| const uint32_t kWidth = 256; | |||
| const uint32_t kHeight = 256; | |||
| const VkFormat kVulkanFormat = VK_FORMAT_A8B8G8R8_UNORM_PACK32; | |||
| const std::string kVertexShaderPath = "triangle.vert.spv"; | |||
| const std::string kFragmentShaderPath = "triangle.frag.spv"; | |||
| @@ -135,6 +135,21 @@ VkCommandPool CreateVkCommandPool(VkDevice device, uint32_t device_queue_family_ | |||
| return command_pool; | |||
| } | |||
| VkCommandBuffer CreateVkCommandBuffer(VkDevice device, VkCommandPool command_pool) { | |||
| VkCommandBufferAllocateInfo allocate_info = {}; | |||
| allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; | |||
| allocate_info.commandPool = command_pool; | |||
| allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | |||
| allocate_info.commandBufferCount = 1; | |||
| VkCommandBuffer command_buffer; | |||
| VkResult result = vkAllocateCommandBuffers( device, &allocate_info, &command_buffer); | |||
| if (result != VK_SUCCESS) { | |||
| ERROR("Unable to create VkCommandBuffer: " << result); | |||
| } | |||
| return command_buffer; | |||
| } | |||
| VkRenderPass CreateVkRenderPass(VkDevice device) { | |||
| VkAttachmentDescription attachment_description = {}; | |||
| attachment_description.format = kVulkanFormat; | |||
| @@ -144,7 +159,7 @@ VkRenderPass CreateVkRenderPass(VkDevice device) { | |||
| attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; | |||
| attachment_description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | |||
| attachment_description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |||
| attachment_description.finalLayout = VK_IMAGE_LAYOUT_GENERAL; | |||
| attachment_description.finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; | |||
| VkAttachmentReference attachment_reference = {}; | |||
| attachment_reference.attachment = 0; | |||
| @@ -319,7 +334,7 @@ VkPipeline CreateVkPipeline(VkDevice device, VkRenderPass render_pass) { | |||
| return pipeline; | |||
| } | |||
| VkImage CreateVkImage(VkDevice device) { | |||
| VkImage CreateVkImage(VkDevice device, VkImageTiling image_tiling, VkImageUsageFlags usage) { | |||
| VkImageCreateInfo create_info = {}; | |||
| create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; | |||
| create_info.pNext = nullptr; | |||
| @@ -334,9 +349,8 @@ VkImage CreateVkImage(VkDevice device) { | |||
| create_info.arrayLayers = 1; | |||
| create_info.samples = VK_SAMPLE_COUNT_1_BIT; | |||
| create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |||
| // create_info.tiling = VK_IMAGE_TILING_OPTIMAL; | |||
| create_info.tiling = VK_IMAGE_TILING_LINEAR; | |||
| create_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; | |||
| create_info.tiling = image_tiling; | |||
| create_info.usage = usage; | |||
| create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |||
| VkImage image; | |||
| @@ -347,13 +361,24 @@ VkImage CreateVkImage(VkDevice device) { | |||
| return image; | |||
| } | |||
| inline VkImage CreateRenderVkImage(VkDevice device) { | |||
| return CreateVkImage(device, VK_IMAGE_TILING_OPTIMAL, | |||
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT); | |||
| } | |||
| inline VkImage CreateScanoutVkImage(VkDevice device) { | |||
| return CreateVkImage(device, VK_IMAGE_TILING_LINEAR, | |||
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); | |||
| } | |||
| uint32_t FindMemoryType(uint32_t valid_image_memory_types, | |||
| VkPhysicalDeviceMemoryProperties device_memory_properties) { | |||
| VkPhysicalDeviceMemoryProperties device_memory_properties, | |||
| VkMemoryPropertyFlags memory_property_flags) { | |||
| for (uint32_t i = 0; i < device_memory_properties.memoryTypeCount; i++) { | |||
| // We don't care about performance, so just choose the first mappable memory type. | |||
| if ((valid_image_memory_types % (1 << i)) && | |||
| (device_memory_properties.memoryTypes[i].propertyFlags & | |||
| VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) { | |||
| ((device_memory_properties.memoryTypes[i].propertyFlags & memory_property_flags) == | |||
| memory_property_flags)) { | |||
| return i; | |||
| } | |||
| } | |||
| @@ -361,7 +386,7 @@ uint32_t FindMemoryType(uint32_t valid_image_memory_types, | |||
| } | |||
| VkDeviceMemory AllocateAndBindMemory(VkPhysicalDevice physical_device, VkDevice device, | |||
| VkImage image) { | |||
| VkImage image, VkMemoryPropertyFlags memory_property_flags) { | |||
| VkMemoryRequirements image_memory_requirements; | |||
| vkGetImageMemoryRequirements(device, image, &image_memory_requirements); | |||
| @@ -372,7 +397,8 @@ VkDeviceMemory AllocateAndBindMemory(VkPhysicalDevice physical_device, VkDevice | |||
| memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; | |||
| memory_allocate_info.allocationSize = image_memory_requirements.size; | |||
| memory_allocate_info.memoryTypeIndex = FindMemoryType( | |||
| image_memory_requirements.memoryTypeBits, device_memory_properties); | |||
| image_memory_requirements.memoryTypeBits, device_memory_properties, | |||
| memory_property_flags); | |||
| VkDeviceMemory image_memory; | |||
| VkResult result = vkAllocateMemory(device, &memory_allocate_info, nullptr, &image_memory); | |||
| @@ -387,6 +413,16 @@ VkDeviceMemory AllocateAndBindMemory(VkPhysicalDevice physical_device, VkDevice | |||
| return image_memory; | |||
| } | |||
| inline VkDeviceMemory AllocateAndBindRenderMemory(VkPhysicalDevice physical_device, | |||
| VkDevice device, VkImage image) { | |||
| return AllocateAndBindMemory(physical_device, device, image, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); | |||
| } | |||
| inline VkDeviceMemory AllocateAndBindScanoutMemory(VkPhysicalDevice physical_device, | |||
| VkDevice device, VkImage image) { | |||
| return AllocateAndBindMemory(physical_device, device, image, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); | |||
| } | |||
| VkImageView CreateVkImageView(VkDevice device, VkImage image) { | |||
| VkImageViewCreateInfo create_info = {}; | |||
| create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; | |||
| @@ -436,23 +472,7 @@ VkFramebuffer CreateVkFramebuffer(VkDevice device, VkRenderPass render_pass, | |||
| return framebuffer; | |||
| } | |||
| VkCommandBuffer CreateVkCommandBuffer(VkDevice device, VkCommandPool command_pool) { | |||
| VkCommandBufferAllocateInfo allocate_info = {}; | |||
| allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; | |||
| allocate_info.commandPool = command_pool; | |||
| allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | |||
| allocate_info.commandBufferCount = 1; | |||
| VkCommandBuffer command_buffer; | |||
| VkResult result = vkAllocateCommandBuffers( device, &allocate_info, &command_buffer); | |||
| if (result != VK_SUCCESS) { | |||
| ERROR("Unable to create VkCommandBuffer: " << result); | |||
| } | |||
| return command_buffer; | |||
| } | |||
| void Draw(VkDevice device, VkQueue queue, VkRenderPass render_pass, VkPipeline pipeline, | |||
| VkFramebuffer framebuffer, VkCommandBuffer command_buffer) { | |||
| void BeginCommandBuffer(VkCommandBuffer command_buffer) { | |||
| VkCommandBufferBeginInfo command_buffer_begin_info = {}; | |||
| command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; | |||
| command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; | |||
| @@ -460,7 +480,37 @@ void Draw(VkDevice device, VkQueue queue, VkRenderPass render_pass, VkPipeline p | |||
| if (result != VK_SUCCESS) { | |||
| ERROR("Unable to begin command buffer recording: " << result); | |||
| } | |||
| } | |||
| void EndCommandBufferAndSubmit(VkCommandBuffer command_buffer, VkQueue queue) { | |||
| VkResult result = vkEndCommandBuffer(command_buffer); | |||
| if (result != VK_SUCCESS) { | |||
| ERROR("Unable to end command buffer recording: " << result); | |||
| } | |||
| VkSubmitInfo submit_info = {}; | |||
| submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | |||
| submit_info.commandBufferCount = 1; | |||
| submit_info.pCommandBuffers = &command_buffer; | |||
| result = vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE); | |||
| if (result != VK_SUCCESS) { | |||
| ERROR("Error in submitting command buffer to queue: " << result); | |||
| } | |||
| } | |||
| void WaitForIdle(VkQueue queue) { | |||
| // In a real application, we should use real synchronization primitives to figure out when the | |||
| // command buffers have been executed. Waiting on an idle queue is simple, but it can cause | |||
| // deadlock if we continue to submit to the queue while we wait for idle. | |||
| VkResult result = vkQueueWaitIdle(queue); | |||
| if (result != VK_SUCCESS) { | |||
| ERROR("Error in waiting for graphics queue to reach idle state: " << result); | |||
| } | |||
| } | |||
| void Draw(VkDevice device, VkQueue queue, VkRenderPass render_pass, VkPipeline pipeline, | |||
| VkFramebuffer framebuffer, VkCommandBuffer command_buffer) { | |||
| BeginCommandBuffer(command_buffer); | |||
| VkRenderPassBeginInfo render_pass_begin_info = {}; | |||
| render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; | |||
| render_pass_begin_info.renderPass = render_pass; | |||
| @@ -481,25 +531,58 @@ void Draw(VkDevice device, VkQueue queue, VkRenderPass render_pass, VkPipeline p | |||
| vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | |||
| vkCmdDraw(command_buffer, 3, 1, 0, 0); | |||
| vkCmdEndRenderPass(command_buffer); | |||
| } | |||
| result = vkEndCommandBuffer(command_buffer); | |||
| if (result != VK_SUCCESS) { | |||
| ERROR("Unable to end command buffer recording: " << result); | |||
| } | |||
| VkSubmitInfo submit_info = {}; | |||
| submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | |||
| submit_info.commandBufferCount = 1; | |||
| submit_info.pCommandBuffers = &command_buffer; | |||
| result = vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE); | |||
| if (result != VK_SUCCESS) { | |||
| ERROR("Error in submitting command buffer to queue: " << result); | |||
| } | |||
| result = vkQueueWaitIdle(queue); | |||
| if (result != VK_SUCCESS) { | |||
| ERROR("Error in waiting for graphics queue to reach idle state: " << result); | |||
| } | |||
| void BlitToScanoutImage(VkQueue queue, VkCommandBuffer command_buffer, VkImage source_image, | |||
| VkImage dest_image) { | |||
| // We need to make sure the writes of the render pass have executed before actually scanning out | |||
| // the rendered image. Also, we need to transition the layout of the scanout image to | |||
| // VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL. | |||
| VkImageMemoryBarrier image_memory_barrier = {}; | |||
| image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, | |||
| image_memory_barrier.pNext = nullptr; | |||
| image_memory_barrier.srcAccessMask = 0; | |||
| image_memory_barrier.dstAccessMask = 0; | |||
| image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |||
| image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; | |||
| image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | |||
| image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | |||
| image_memory_barrier.image = dest_image; | |||
| VkImageSubresourceRange subresource_range = {}; | |||
| subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |||
| subresource_range.baseMipLevel = 0; | |||
| subresource_range.levelCount = 1; | |||
| subresource_range.baseArrayLayer = 0; | |||
| subresource_range.layerCount = 1; | |||
| image_memory_barrier.subresourceRange = subresource_range; | |||
| vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, | |||
| VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier); | |||
| VkOffset3D blit_start = {}; | |||
| blit_start.x = 0; | |||
| blit_start.y = 0; | |||
| blit_start.z = 0; | |||
| VkOffset3D blit_end = {}; | |||
| blit_end.x = kWidth; | |||
| blit_end.y = kHeight; | |||
| blit_end.z = 1; | |||
| VkImageSubresourceLayers subresource_layers = {}; | |||
| subresource_layers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |||
| subresource_layers.mipLevel = 0; | |||
| subresource_layers.baseArrayLayer = 0; | |||
| subresource_layers.layerCount = 1; | |||
| VkImageBlit image_blit = {}; | |||
| image_blit.srcSubresource = subresource_layers; | |||
| image_blit.srcOffsets[0] = blit_start; | |||
| image_blit.srcOffsets[1] = blit_end; | |||
| image_blit.dstSubresource = subresource_layers; | |||
| image_blit.dstOffsets[0] = blit_start; | |||
| image_blit.dstOffsets[1] = blit_end; | |||
| // TODO(brkho): Support multi-planar formats. | |||
| vkCmdBlitImage(command_buffer, source_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dest_image, | |||
| VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_blit, VK_FILTER_NEAREST); | |||
| EndCommandBufferAndSubmit(command_buffer, queue); | |||
| } | |||
| uint8_t* MapVkDeviceMemory(VkDevice device, VkDeviceMemory image_memory) { | |||
| @@ -533,17 +616,27 @@ int main() { | |||
| VkDevice device = CreateVkDevice(physical_device, device_queue_family_index); | |||
| VkQueue queue = GetVkQueue(device, device_queue_family_index); | |||
| VkCommandPool command_pool = CreateVkCommandPool(device, device_queue_family_index); | |||
| VkCommandBuffer command_buffer = CreateVkCommandBuffer(device, command_pool); | |||
| VkRenderPass render_pass = CreateVkRenderPass(device); | |||
| VkPipeline pipeline = CreateVkPipeline(device, render_pass); | |||
| VkImage image = CreateVkImage(device); | |||
| VkDeviceMemory image_memory = AllocateAndBindMemory(physical_device, device, image); | |||
| VkImageView image_view = CreateVkImageView(device, image); | |||
| VkFramebuffer framebuffer = CreateVkFramebuffer(device, render_pass, image_view); | |||
| VkCommandBuffer command_buffer = CreateVkCommandBuffer(device, command_pool); | |||
| VkImage render_image = CreateRenderVkImage(device); | |||
| AllocateAndBindRenderMemory(physical_device, device, render_image); | |||
| VkImageView render_image_view = CreateVkImageView(device, render_image); | |||
| VkFramebuffer framebuffer = CreateVkFramebuffer(device, render_pass, render_image_view); | |||
| VkImage scanout_image = CreateScanoutVkImage(device); | |||
| VkDeviceMemory scanout_image_memory = | |||
| AllocateAndBindScanoutMemory(physical_device, device, scanout_image); | |||
| Draw(device, queue, render_pass, pipeline, framebuffer, command_buffer); | |||
| // Since the render target is created with VK_IMAGE_TILING_OPTIMAL, we need to copy it to a linear | |||
| // format before scanning out. | |||
| BlitToScanoutImage(queue, command_buffer, render_image, scanout_image); | |||
| WaitForIdle(queue); | |||
| uint8_t* pixels = MapVkDeviceMemory(device, image_memory); | |||
| uint8_t* pixels = MapVkDeviceMemory(device, scanout_image_memory); | |||
| WriteImage(pixels); | |||
| return EXIT_SUCCESS; | |||