#include #include #include #include #include #include #include #include const uint32_t kWidth = 256; const uint32_t kHeight = 256; const VkFormat kVulkanFormat = VK_FORMAT_A8B8G8R8_UNORM_PACK32; const VkFormat kVulkanDepthFormat = VK_FORMAT_D32_SFLOAT; const std::string kVertexShaderPath = "subpass.vert.spv"; const std::string kFragmentShaderPath = "subpass.frag.spv"; #define ERROR(message) \ std::cerr << message << std::endl; \ std::exit(EXIT_FAILURE) VkInstance CreateVkInstance() { VkApplicationInfo app_info = {}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; create_info.pApplicationInfo = &app_info; create_info.enabledExtensionCount = 0; create_info.ppEnabledExtensionNames = nullptr; create_info.enabledLayerCount = 0; create_info.ppEnabledLayerNames = nullptr; VkInstance instance; VkResult result = vkCreateInstance(&create_info, nullptr, &instance); if (result != VK_SUCCESS) { ERROR("Error creating VkInstance: " << result); } return instance; } VkPhysicalDevice ChooseVkPhysicalDevice(VkInstance instance) { uint32_t device_count = 0; VkResult result = vkEnumeratePhysicalDevices(instance, &device_count, nullptr); if (result != VK_SUCCESS) { ERROR("Error enumerating VkPhysicalDevices: " << result); } if (device_count == 0) { ERROR("No available VkPhysicalDevices"); } std::vector devices(device_count); result = vkEnumeratePhysicalDevices(instance, &device_count, devices.data()); if (result != VK_SUCCESS) { ERROR("Error fetching VkPhysicalDevices: " << result); } std::cout << "Found " << device_count << " device(s):" << std::endl; VkPhysicalDevice chosen_device = VK_NULL_HANDLE; for (VkPhysicalDevice device : devices) { VkPhysicalDeviceProperties device_props; vkGetPhysicalDeviceProperties(device, &device_props); std::cout << "\t- " << device_props.deviceName << " [V: " << VK_VERSION_MAJOR(device_props.apiVersion) << "." << VK_VERSION_MINOR(device_props.apiVersion) << "." << VK_VERSION_PATCH(device_props.apiVersion) << "]" << std::endl; // Currently, any device with Vulkan API version 1.1 is fine. if (chosen_device == VK_NULL_HANDLE && device_props.apiVersion >= VK_API_VERSION_1_1) { chosen_device = device; } } if (chosen_device == VK_NULL_HANDLE) { ERROR("Unable to find suitable VkPhysicalDevice"); } return chosen_device; } uint32_t ChooseDeviceQueueFamilyIndex(VkPhysicalDevice physical_device) { uint32_t props_count; vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &props_count, nullptr); std::vector props(props_count); vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &props_count, props.data()); // Simply choose the first graphics queue. for (size_t i = 0; i < props_count; i++) { const VkQueueFamilyProperties& prop = props[i]; if ((prop.queueFlags & VK_QUEUE_GRAPHICS_BIT) && prop.queueCount > 0) { return i; } } ERROR("Unable to find suitable queue family"); } VkDevice CreateVkDevice(VkPhysicalDevice physical_device, uint32_t device_queue_family_index) { VkDeviceQueueCreateInfo queue_create_info = {}; queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queue_create_info.queueFamilyIndex = device_queue_family_index; queue_create_info.queueCount = 1; float queue_priority = 1.0f; queue_create_info.pQueuePriorities = &queue_priority; VkDeviceCreateInfo device_create_info = {}; device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; device_create_info.queueCreateInfoCount = 1; device_create_info.pQueueCreateInfos = &queue_create_info; // Let's not use any device extensions for now. device_create_info.enabledExtensionCount = 0; device_create_info.ppEnabledExtensionNames = nullptr; VkDevice device; VkResult result = vkCreateDevice(physical_device, &device_create_info, nullptr, &device); if (result != VK_SUCCESS) { ERROR("Unable to create logical device: " << result); } return device; } VkQueue GetVkQueue(VkDevice device, uint32_t device_queue_family_index) { VkQueue queue; vkGetDeviceQueue(device, device_queue_family_index, /*queueIndex*/ 0, &queue); return queue; } VkCommandPool CreateVkCommandPool(VkDevice device, uint32_t device_queue_family_index) { VkCommandPool command_pool; VkCommandPoolCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; create_info.flags = 0; create_info.queueFamilyIndex = device_queue_family_index; VkResult result = vkCreateCommandPool(device, &create_info, nullptr, &command_pool); if (result != VK_SUCCESS) { ERROR("Unable to create command pool: " << result); } 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) { std::array attachment_descriptions = {}; VkAttachmentDescription* color_attachment_description = &attachment_descriptions[0]; color_attachment_description->format = kVulkanFormat; color_attachment_description->samples = VK_SAMPLE_COUNT_1_BIT; color_attachment_description->loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; color_attachment_description->storeOp = VK_ATTACHMENT_STORE_OP_STORE; color_attachment_description->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; color_attachment_description->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; color_attachment_description->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; color_attachment_description->finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; VkAttachmentReference color_attachment_reference = {}; color_attachment_reference.attachment = 0; color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentDescription* depth_attachment_description = &attachment_descriptions[1]; depth_attachment_description->format = kVulkanDepthFormat; depth_attachment_description->samples = VK_SAMPLE_COUNT_1_BIT; depth_attachment_description->loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; depth_attachment_description->storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; depth_attachment_description->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; depth_attachment_description->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; depth_attachment_description->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; depth_attachment_description->finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkAttachmentReference depth_attachment_reference = {}; depth_attachment_reference.attachment = 1; depth_attachment_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass_description = {}; subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass_description.colorAttachmentCount = 1; subpass_description.pColorAttachments = &color_attachment_reference; subpass_description.pDepthStencilAttachment = &depth_attachment_reference; VkSubpassDependency subpass_dependency = {}; subpass_dependency.srcSubpass = VK_SUBPASS_EXTERNAL; subpass_dependency.dstSubpass = 0; subpass_dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; subpass_dependency.srcAccessMask = 0; subpass_dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; subpass_dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkRenderPassCreateInfo render_pass_info = {}; render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; render_pass_info.attachmentCount = attachment_descriptions.size(); render_pass_info.pAttachments = attachment_descriptions.data(); render_pass_info.subpassCount = 1; render_pass_info.pSubpasses = &subpass_description; render_pass_info.dependencyCount = 1; render_pass_info.pDependencies = &subpass_dependency; VkRenderPass render_pass; VkResult result = vkCreateRenderPass(device, &render_pass_info, nullptr, &render_pass); if (result != VK_SUCCESS) { ERROR("Unable to create render pass: " << result); } return render_pass; } VkShaderModule CreateVkShaderModule(VkDevice device, std::string path) { std::ifstream fstream(path); if (!fstream) { ERROR("Unable to open: " << path); } std::stringstream buffer; buffer << fstream.rdbuf(); std::string spirv_source = buffer.str(); VkShaderModuleCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; create_info.codeSize = spirv_source.length(); create_info.pCode = (const uint32_t*)spirv_source.c_str(); VkShaderModule shader_module; VkResult result = vkCreateShaderModule(device, &create_info, nullptr, &shader_module); if (result != VK_SUCCESS) { ERROR("Unable to create shader module for " << path << ": "); } return shader_module; } VkPipeline CreateVkPipeline(VkDevice device, VkRenderPass render_pass) { VkShaderModule vertex_shader_module = CreateVkShaderModule(device, kVertexShaderPath); VkShaderModule fragment_shader_module = CreateVkShaderModule(device, kFragmentShaderPath); VkPipelineShaderStageCreateInfo vertex_shader_stage_info = {}; vertex_shader_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertex_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; vertex_shader_stage_info.module = vertex_shader_module; vertex_shader_stage_info.pName = "main"; VkPipelineShaderStageCreateInfo fragment_shader_stage_info = {}; fragment_shader_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragment_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragment_shader_stage_info.module = fragment_shader_module; fragment_shader_stage_info.pName = "main"; std::vector shader_stages = {vertex_shader_stage_info, fragment_shader_stage_info}; VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertex_input_info.vertexBindingDescriptionCount = 0; vertex_input_info.pVertexBindingDescriptions = nullptr; vertex_input_info.vertexAttributeDescriptionCount = 0; vertex_input_info.pVertexAttributeDescriptions = nullptr; VkPipelineInputAssemblyStateCreateInfo input_assembly_info = {}; input_assembly_info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; input_assembly_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; input_assembly_info.primitiveRestartEnable = VK_FALSE; VkViewport viewport = {}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = (float)kWidth; viewport.height = (float)kHeight; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkExtent2D extent = {}; extent.width = kWidth; extent.height = kHeight; VkRect2D scissor = {}; scissor.offset = {0, 0}; scissor.extent = extent; VkPipelineViewportStateCreateInfo viewport_state_info = {}; viewport_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewport_state_info.viewportCount = 1; viewport_state_info.pViewports = &viewport; viewport_state_info.scissorCount = 1; viewport_state_info.pScissors = &scissor; VkPipelineRasterizationStateCreateInfo rasterization_state_info = {}; rasterization_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterization_state_info.depthClampEnable = VK_FALSE; rasterization_state_info.rasterizerDiscardEnable = VK_FALSE; rasterization_state_info.polygonMode = VK_POLYGON_MODE_FILL; rasterization_state_info.lineWidth = 1.0f; rasterization_state_info.cullMode = VK_CULL_MODE_BACK_BIT; rasterization_state_info.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterization_state_info.depthBiasEnable = VK_FALSE; VkPipelineMultisampleStateCreateInfo multisampling_state_info = {}; multisampling_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling_state_info.sampleShadingEnable = VK_FALSE; multisampling_state_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; VkPipelineDepthStencilStateCreateInfo depth_stencil_create_info = {}; depth_stencil_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depth_stencil_create_info.depthTestEnable = VK_TRUE; depth_stencil_create_info.depthWriteEnable = VK_TRUE; depth_stencil_create_info.depthCompareOp = VK_COMPARE_OP_LESS; // TODO(brkho): Test depth bounds functionality. depth_stencil_create_info.depthBoundsTestEnable = VK_FALSE; depth_stencil_create_info.minDepthBounds = 0.0f; depth_stencil_create_info.maxDepthBounds = 1.0f; depth_stencil_create_info.stencilTestEnable = VK_FALSE; VkPipelineColorBlendAttachmentState color_blend_attachment_state = {}; color_blend_attachment_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; color_blend_attachment_state.blendEnable = VK_FALSE; VkPipelineColorBlendStateCreateInfo color_blend_state = {}; color_blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; color_blend_state.logicOpEnable = VK_FALSE; color_blend_state.attachmentCount = 1; color_blend_state.pAttachments = &color_blend_attachment_state; VkPipelineLayoutCreateInfo pipeline_layout_create_info = {}; pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; VkPipelineLayout pipeline_layout; VkResult result = vkCreatePipelineLayout(device, &pipeline_layout_create_info, nullptr, &pipeline_layout); if (result != VK_SUCCESS) { ERROR("Unable to create VkPipelineLayout: " << result); } VkGraphicsPipelineCreateInfo pipeline_create_info = {}; pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipeline_create_info.stageCount = 2; pipeline_create_info.pStages = shader_stages.data(); pipeline_create_info.pVertexInputState = &vertex_input_info; pipeline_create_info.pInputAssemblyState = &input_assembly_info; pipeline_create_info.pViewportState = &viewport_state_info; pipeline_create_info.pRasterizationState = &rasterization_state_info; pipeline_create_info.pMultisampleState = &multisampling_state_info; pipeline_create_info.pDepthStencilState = &depth_stencil_create_info; pipeline_create_info.pColorBlendState = &color_blend_state; pipeline_create_info.pDynamicState = nullptr; pipeline_create_info.layout = pipeline_layout; pipeline_create_info.renderPass = render_pass; pipeline_create_info.subpass = 0; pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE; pipeline_create_info.basePipelineIndex = -1; VkPipeline pipeline; result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_create_info, nullptr, &pipeline); if (result != VK_SUCCESS) { ERROR("Unable to create VkPipeline: " << result); } return pipeline; } VkImage CreateVkImage(VkDevice device, VkFormat format, VkImageTiling image_tiling, VkImageUsageFlags usage) { VkImageCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; create_info.pNext = nullptr; create_info.imageType = VK_IMAGE_TYPE_2D; create_info.format = format; VkExtent3D extent = {}; extent.width = kWidth; extent.height = kHeight; extent.depth = 1; create_info.extent = extent; create_info.mipLevels = 1; create_info.arrayLayers = 1; create_info.samples = VK_SAMPLE_COUNT_1_BIT; create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; create_info.tiling = image_tiling; create_info.usage = usage; create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkImage image; VkResult result = vkCreateImage(device, &create_info, nullptr, &image); if (result != VK_SUCCESS) { ERROR("Unable to create VkImage: " << result); } return image; } inline VkImage CreateRenderVkImage(VkDevice device) { return CreateVkImage(device, kVulkanFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT); } inline VkImage CreateDepthVkImage(VkDevice device) { return CreateVkImage(device, kVulkanDepthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); } inline VkImage CreateScanoutVkImage(VkDevice device) { return CreateVkImage(device, kVulkanFormat, 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, 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 & memory_property_flags) == memory_property_flags)) { return i; } } ERROR("Unable to find suitable memory type index"); } VkDeviceMemory AllocateAndBindMemory(VkPhysicalDevice physical_device, VkDevice device, VkImage image, VkMemoryPropertyFlags memory_property_flags) { VkMemoryRequirements image_memory_requirements; vkGetImageMemoryRequirements(device, image, &image_memory_requirements); VkPhysicalDeviceMemoryProperties device_memory_properties; vkGetPhysicalDeviceMemoryProperties(physical_device, &device_memory_properties); VkMemoryAllocateInfo memory_allocate_info = {}; 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, memory_property_flags); VkDeviceMemory image_memory; VkResult result = vkAllocateMemory(device, &memory_allocate_info, nullptr, &image_memory); if (result != VK_SUCCESS) { ERROR("Unable to allocate image memory: " << result); } result = vkBindImageMemory(device, image, image_memory, 0); if (result != VK_SUCCESS) { ERROR("Unable to bind image memory: " << result); } return image_memory; } inline VkDeviceMemory AllocateAndBindRenderMemory(VkPhysicalDevice physical_device, VkDevice device, VkImage image) { return AllocateAndBindMemory(physical_device, device, image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); } inline VkDeviceMemory AllocateAndBindDepthMemory(VkPhysicalDevice physical_device, VkDevice device, VkImage image) { return AllocateAndBindMemory(physical_device, device, image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_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, VkFormat format, VkImageAspectFlags aspect) { VkImageViewCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; create_info.image = image; create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; create_info.format = format; VkComponentMapping component_mapping = {}; component_mapping.r = VK_COMPONENT_SWIZZLE_IDENTITY; component_mapping.b = VK_COMPONENT_SWIZZLE_IDENTITY; component_mapping.g = VK_COMPONENT_SWIZZLE_IDENTITY; component_mapping.a = VK_COMPONENT_SWIZZLE_IDENTITY; create_info.components = component_mapping; VkImageSubresourceRange subresource_range = {}; subresource_range.aspectMask = aspect; subresource_range.baseMipLevel = 0; subresource_range.levelCount = 1; subresource_range.baseArrayLayer = 0; subresource_range.layerCount = 1; create_info.subresourceRange = subresource_range; VkImageView image_view; VkResult result = vkCreateImageView(device, &create_info, nullptr, &image_view); if (result != VK_SUCCESS) { ERROR("Unable to create VkImageView: " << result); } return image_view; } VkImageView CreateRenderVkImageView(VkDevice device, VkImage image) { return CreateVkImageView(device, image, kVulkanFormat, VK_IMAGE_ASPECT_COLOR_BIT); } VkImageView CreateDepthVkImageView(VkDevice device, VkImage image) { return CreateVkImageView(device, image, kVulkanDepthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); } VkFramebuffer CreateVkFramebuffer(VkDevice device, VkRenderPass render_pass, VkImageView render_image_view, VkImageView depth_image_view) { std::array attachments = { render_image_view, depth_image_view }; VkFramebufferCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; create_info.renderPass = render_pass; create_info.attachmentCount = attachments.size(); create_info.pAttachments = attachments.data(); create_info.width = kWidth; create_info.height = kHeight; create_info.layers = 1; VkFramebuffer framebuffer; VkResult result = vkCreateFramebuffer(device, &create_info, nullptr, &framebuffer); if (result != VK_SUCCESS) { ERROR("Unable to create VkFramebuffer: " << result); } return framebuffer; } 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; VkResult result = vkBeginCommandBuffer(command_buffer, &command_buffer_begin_info); 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; render_pass_begin_info.framebuffer = framebuffer; VkRect2D render_area = {}; render_area.offset = { 0, 0 }; render_area.extent = { kWidth, kHeight }; render_pass_begin_info.renderArea = render_area; std::array clear_values = {}; clear_values[0].color.float32[0] = 1.0f; clear_values[0].color.float32[1] = 1.0f; clear_values[0].color.float32[2] = 1.0f; clear_values[0].color.float32[3] = 1.0f; clear_values[1].depthStencil.depth = 1.0f; clear_values[1].depthStencil.stencil = 0.0f; render_pass_begin_info.clearValueCount = clear_values.size(); render_pass_begin_info.pClearValues = clear_values.data(); vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdDraw(command_buffer, 3, 1, 0, 0); vkCmdDraw(command_buffer, 3, 1, 3, 0); vkCmdEndRenderPass(command_buffer); } 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) { void* mapped_memory = nullptr; VkResult result = vkMapMemory(device, image_memory, 0, kWidth * kHeight * 4, 0, &mapped_memory); if (result != VK_SUCCESS) { ERROR("Unable to map device memory: " << result); } return static_cast(mapped_memory); } void WriteImage(uint8_t* pixels) { FILE *f = fopen("subpass.png", "wb"); png_image image_info = {}; image_info.version = PNG_IMAGE_VERSION; image_info.width = kWidth; image_info.height = kHeight; image_info.format = PNG_FORMAT_RGBA; if (png_image_write_to_stdio(&image_info, f, 0, pixels, kWidth * 4, nullptr) == 0) { ERROR("Error writing PNG: " << image_info.message); } fclose(f); } int main() { VkInstance instance = CreateVkInstance(); VkPhysicalDevice physical_device = ChooseVkPhysicalDevice(instance); uint32_t device_queue_family_index = ChooseDeviceQueueFamilyIndex(physical_device); 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 render_image = CreateRenderVkImage(device); AllocateAndBindRenderMemory(physical_device, device, render_image); VkImageView render_image_view = CreateRenderVkImageView(device, render_image); VkImage depth_image = CreateDepthVkImage(device); AllocateAndBindDepthMemory(physical_device, device, depth_image); VkImageView depth_image_view = CreateDepthVkImageView(device, depth_image); VkFramebuffer framebuffer = CreateVkFramebuffer(device, render_pass, render_image_view, depth_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, scanout_image_memory); WriteImage(pixels); return EXIT_SUCCESS; }