| #include <string> | #include <string> | ||||
| #include <vector> | #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 VkFormat kVulkanFormat = VK_FORMAT_A8B8G8R8_UNORM_PACK32; | ||||
| const std::string kVertexShaderPath = "triangle.vert.spv"; | const std::string kVertexShaderPath = "triangle.vert.spv"; | ||||
| const std::string kFragmentShaderPath = "triangle.frag.spv"; | const std::string kFragmentShaderPath = "triangle.frag.spv"; | ||||
| return command_pool; | 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) { | VkRenderPass CreateVkRenderPass(VkDevice device) { | ||||
| VkAttachmentDescription attachment_description = {}; | VkAttachmentDescription attachment_description = {}; | ||||
| attachment_description.format = kVulkanFormat; | attachment_description.format = kVulkanFormat; | ||||
| attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; | attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; | ||||
| attachment_description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | attachment_description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | ||||
| attachment_description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | 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 = {}; | VkAttachmentReference attachment_reference = {}; | ||||
| attachment_reference.attachment = 0; | attachment_reference.attachment = 0; | ||||
| return pipeline; | return pipeline; | ||||
| } | } | ||||
| VkImage CreateVkImage(VkDevice device) { | |||||
| VkImage CreateVkImage(VkDevice device, VkImageTiling image_tiling, VkImageUsageFlags usage) { | |||||
| VkImageCreateInfo create_info = {}; | VkImageCreateInfo create_info = {}; | ||||
| create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; | create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; | ||||
| create_info.pNext = nullptr; | create_info.pNext = nullptr; | ||||
| create_info.arrayLayers = 1; | create_info.arrayLayers = 1; | ||||
| create_info.samples = VK_SAMPLE_COUNT_1_BIT; | create_info.samples = VK_SAMPLE_COUNT_1_BIT; | ||||
| create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | 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; | create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | ||||
| VkImage image; | VkImage image; | ||||
| return image; | 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, | 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++) { | 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. | // We don't care about performance, so just choose the first mappable memory type. | ||||
| if ((valid_image_memory_types % (1 << i)) && | 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; | return i; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| VkDeviceMemory AllocateAndBindMemory(VkPhysicalDevice physical_device, VkDevice device, | VkDeviceMemory AllocateAndBindMemory(VkPhysicalDevice physical_device, VkDevice device, | ||||
| VkImage image) { | |||||
| VkImage image, VkMemoryPropertyFlags memory_property_flags) { | |||||
| VkMemoryRequirements image_memory_requirements; | VkMemoryRequirements image_memory_requirements; | ||||
| vkGetImageMemoryRequirements(device, image, &image_memory_requirements); | vkGetImageMemoryRequirements(device, image, &image_memory_requirements); | ||||
| memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; | memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; | ||||
| memory_allocate_info.allocationSize = image_memory_requirements.size; | memory_allocate_info.allocationSize = image_memory_requirements.size; | ||||
| memory_allocate_info.memoryTypeIndex = FindMemoryType( | 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; | VkDeviceMemory image_memory; | ||||
| VkResult result = vkAllocateMemory(device, &memory_allocate_info, nullptr, &image_memory); | VkResult result = vkAllocateMemory(device, &memory_allocate_info, nullptr, &image_memory); | ||||
| return image_memory; | 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) { | VkImageView CreateVkImageView(VkDevice device, VkImage image) { | ||||
| VkImageViewCreateInfo create_info = {}; | VkImageViewCreateInfo create_info = {}; | ||||
| create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; | create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; | ||||
| return framebuffer; | 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 = {}; | VkCommandBufferBeginInfo command_buffer_begin_info = {}; | ||||
| command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_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; | command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; | ||||
| if (result != VK_SUCCESS) { | if (result != VK_SUCCESS) { | ||||
| ERROR("Unable to begin command buffer recording: " << result); | 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 = {}; | VkRenderPassBeginInfo render_pass_begin_info = {}; | ||||
| render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; | render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; | ||||
| render_pass_begin_info.renderPass = render_pass; | render_pass_begin_info.renderPass = render_pass; | ||||
| vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | ||||
| vkCmdDraw(command_buffer, 3, 1, 0, 0); | vkCmdDraw(command_buffer, 3, 1, 0, 0); | ||||
| vkCmdEndRenderPass(command_buffer); | 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) { | uint8_t* MapVkDeviceMemory(VkDevice device, VkDeviceMemory image_memory) { | ||||
| VkDevice device = CreateVkDevice(physical_device, device_queue_family_index); | VkDevice device = CreateVkDevice(physical_device, device_queue_family_index); | ||||
| VkQueue queue = GetVkQueue(device, device_queue_family_index); | VkQueue queue = GetVkQueue(device, device_queue_family_index); | ||||
| VkCommandPool command_pool = CreateVkCommandPool(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); | VkRenderPass render_pass = CreateVkRenderPass(device); | ||||
| VkPipeline pipeline = CreateVkPipeline(device, render_pass); | 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); | 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); | WriteImage(pixels); | ||||
| return EXIT_SUCCESS; | return EXIT_SUCCESS; |