#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 kDrawVertexShaderPath = "draw.vert.spv"; const std::string kDrawFragmentShaderPath = "draw.frag.spv"; const std::string kDarkenVertexShaderPath = "darken.vert.spv"; const std::string kDarkenFragmentShaderPath = "darken.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; } void CreateVkBuffer(VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer* buffer, VkDeviceMemory* bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer) != VK_SUCCESS) { ERROR("Unable to create buffer: "); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, *buffer, &memRequirements); VkMemoryAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = 0; if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory) != VK_SUCCESS) { ERROR("Unable to allocate buffer memory: "); } vkBindBufferMemory(device, *buffer, *bufferMemory, 0); } VkDescriptorSetLayout CreateVkDescriptorSetLayout(VkDevice device) { VkDescriptorSetLayoutBinding layout_binding = {}; layout_binding.binding = 0; layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; layout_binding.descriptorCount = 1; layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; layout_binding.pImmutableSamplers = nullptr; VkDescriptorSetLayoutCreateInfo layout_info = {}; layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layout_info.bindingCount = 1; layout_info.pBindings = &layout_binding; VkDescriptorSetLayout layout; VkResult result = vkCreateDescriptorSetLayout(device, &layout_info, nullptr, &layout); if (result != VK_SUCCESS) { ERROR("Unable to create descriptor set layout: " << result); } return layout; } VkDescriptorPool CreateAttachmentVkDescriptorPool(VkDevice device) { VkDescriptorPoolSize pool_size = {}; pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; pool_size.descriptorCount = 1; VkDescriptorPoolCreateInfo pool_create_info = {}; pool_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_create_info.poolSizeCount = 1; pool_create_info.pPoolSizes = &pool_size; pool_create_info.maxSets = 1; VkDescriptorPool pool; VkResult result = vkCreateDescriptorPool(device, &pool_create_info, nullptr, &pool); if (result != VK_SUCCESS) { ERROR("Unable to create descriptor pool: " << result); } return pool; } VkDescriptorSet CreateVkDescriptorSet(VkDevice device, VkDescriptorSetLayout layout, VkDescriptorPool pool) { VkDescriptorSetAllocateInfo allocate_info = {}; allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocate_info.descriptorPool = pool; allocate_info.descriptorSetCount = 1; allocate_info.pSetLayouts = &layout; VkDescriptorSet descriptor_set; VkResult result = vkAllocateDescriptorSets(device, &allocate_info, &descriptor_set); if (result != VK_SUCCESS) { ERROR("Unable to allocate descriptor set: " << result); } return descriptor_set; } void UpdateVkDescriptorSet(VkDevice device, VkImageView image_view, VkDescriptorSet set) { VkDescriptorImageInfo image_info = {}; image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; image_info.imageView = image_view; image_info.sampler = VK_NULL_HANDLE; VkWriteDescriptorSet descriptor_write = {}; descriptor_write.dstSet = set; descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; descriptor_write.descriptorCount = 1; descriptor_write.dstBinding = 0; descriptor_write.pImageInfo = &image_info; descriptor_write.pBufferInfo = nullptr; descriptor_write.pTexelBufferView = nullptr; vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, nullptr); } 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; VkAttachmentDescription* input_attachment_description = &attachment_descriptions[2]; input_attachment_description->format = kVulkanFormat; input_attachment_description->samples = VK_SAMPLE_COUNT_1_BIT; input_attachment_description->loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; input_attachment_description->storeOp = VK_ATTACHMENT_STORE_OP_STORE; input_attachment_description->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; input_attachment_description->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; input_attachment_description->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; input_attachment_description->finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; VkAttachmentReference input_attachment_reference = {}; input_attachment_reference.attachment = 2; input_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; std::array subpass_descriptions = {}; VkSubpassDescription* draw_subpass_description = &subpass_descriptions[0]; draw_subpass_description->pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; draw_subpass_description->colorAttachmentCount = 1; draw_subpass_description->pColorAttachments = &input_attachment_reference; draw_subpass_description->pDepthStencilAttachment = &depth_attachment_reference; VkSubpassDescription* darken_subpass_description = &subpass_descriptions[1]; darken_subpass_description->pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; darken_subpass_description->colorAttachmentCount = 1; darken_subpass_description->pColorAttachments = &color_attachment_reference; darken_subpass_description->pDepthStencilAttachment = &depth_attachment_reference; darken_subpass_description->inputAttachmentCount = 1; darken_subpass_description->pInputAttachments = &input_attachment_reference; std::array subpass_dependencies = {}; VkSubpassDependency* color_subpass_dependency = &subpass_dependencies[0]; color_subpass_dependency->srcSubpass = VK_SUBPASS_EXTERNAL; color_subpass_dependency->dstSubpass = 0; color_subpass_dependency->srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; color_subpass_dependency->dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; color_subpass_dependency->srcAccessMask = 0; color_subpass_dependency->dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; color_subpass_dependency->dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; VkSubpassDependency* darken_subpass_dependency = &subpass_dependencies[1]; darken_subpass_dependency->srcSubpass = 0; darken_subpass_dependency->dstSubpass = 1; darken_subpass_dependency->srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; darken_subpass_dependency->dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; darken_subpass_dependency->srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; darken_subpass_dependency->dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; darken_subpass_dependency->dependencyFlags = VK_DEPENDENCY_BY_REGION_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 = subpass_descriptions.size(); render_pass_info.pSubpasses = subpass_descriptions.data(); render_pass_info.dependencyCount = subpass_dependencies.size(); render_pass_info.pDependencies = subpass_dependencies.data(); 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; } VkPipelineLayout CreateVkPipelineLayout(VkDevice device, VkDescriptorSetLayout descriptor_set_layout) { VkPipelineLayoutCreateInfo pipeline_layout_create_info = {}; pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; if (descriptor_set_layout != VK_NULL_HANDLE) { pipeline_layout_create_info.setLayoutCount = 1; pipeline_layout_create_info.pSetLayouts = &descriptor_set_layout; } VkPipelineLayout pipeline_layout; VkResult result = vkCreatePipelineLayout(device, &pipeline_layout_create_info, nullptr, &pipeline_layout); if (result != VK_SUCCESS) { ERROR("Unable to create VkPipelineLayout: " << result); } return pipeline_layout; } VkPipeline CreateVkPipeline(VkDevice device, VkShaderModule vertex_shader_module, VkShaderModule fragment_shader_module, VkPipelineLayout pipeline_layout, VkRenderPass render_pass, uint32_t subpass) { 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; 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 = subpass; pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE; pipeline_create_info.basePipelineIndex = -1; VkPipeline pipeline; VkResult 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, VkImageView input_image_view) { std::array attachments = { render_image_view, depth_image_view, input_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 draw_pipeline, VkPipeline darken_pipeline, VkPipelineLayout darken_pipeline_layout, VkDescriptorSet descriptor_set, 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] = 0.0f; clear_values[0].color.float32[2] = 0.0f; clear_values[0].color.float32[3] = 1.0f; clear_values[1].depthStencil.depth = 1.0f; clear_values[1].depthStencil.stencil = 0.0f; clear_values[2].color.float32[0] = 1.0f; clear_values[2].color.float32[1] = 1.0f; clear_values[2].color.float32[2] = 1.0f; clear_values[2].color.float32[3] = 1.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, draw_pipeline); vkCmdDraw(command_buffer, 3, 1, 0, 0); vkCmdDraw(command_buffer, 3, 1, 3, 0); vkCmdNextSubpass(command_buffer, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, darken_pipeline); vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, darken_pipeline_layout, 0, 1, &descriptor_set, 0, nullptr); vkCmdDraw(command_buffer, 6, 1, 0, 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); 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); VkImage input_image = CreateRenderVkImage(device); AllocateAndBindRenderMemory(physical_device, device, input_image); VkImageView input_image_view = CreateRenderVkImageView(device, input_image); VkDescriptorSetLayout descriptor_set_layout = CreateVkDescriptorSetLayout(device); VkDescriptorPool descriptor_pool = CreateAttachmentVkDescriptorPool(device); VkDescriptorSet descriptor_set = CreateVkDescriptorSet(device, descriptor_set_layout, descriptor_pool); UpdateVkDescriptorSet(device, render_image_view, descriptor_set); VkRenderPass render_pass = CreateVkRenderPass(device); VkShaderModule draw_vertex_shader_module = CreateVkShaderModule(device, kDrawVertexShaderPath); VkShaderModule draw_fragment_shader_module = CreateVkShaderModule(device, kDrawFragmentShaderPath); VkPipelineLayout draw_pipeline_layout = CreateVkPipelineLayout(device, VK_NULL_HANDLE); VkPipeline draw_pipeline = CreateVkPipeline(device, draw_vertex_shader_module, draw_fragment_shader_module, draw_pipeline_layout, render_pass, 0 /* subpass */); VkShaderModule darken_vertex_shader_module = CreateVkShaderModule(device, kDarkenVertexShaderPath); VkShaderModule darken_fragment_shader_module = CreateVkShaderModule(device, kDarkenFragmentShaderPath); VkPipelineLayout darken_pipeline_layout = CreateVkPipelineLayout(device, descriptor_set_layout); VkPipeline darken_pipeline = CreateVkPipeline(device, darken_vertex_shader_module, darken_fragment_shader_module, darken_pipeline_layout, render_pass, 1 /* subpass */); VkFramebuffer framebuffer = CreateVkFramebuffer(device, render_pass, render_image_view, depth_image_view, input_image_view); VkImage scanout_image = CreateScanoutVkImage(device); VkDeviceMemory scanout_image_memory = AllocateAndBindScanoutMemory(physical_device, device, scanout_image); Draw(device, queue, render_pass, draw_pipeline, darken_pipeline, darken_pipeline_layout, descriptor_set, 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; }