bool VulkanContext::CheckValidationLayerAvailablility() { u32 extension_count = 0; VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: "); return false; } std::vector<VkExtensionProperties> extension_list(extension_count); res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, extension_list.data()); ASSERT(res == VK_SUCCESS); u32 layer_count = 0; res = vkEnumerateInstanceLayerProperties(&layer_count, nullptr); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: "); return false; } std::vector<VkLayerProperties> layer_list(layer_count); res = vkEnumerateInstanceLayerProperties(&layer_count, layer_list.data()); ASSERT(res == VK_SUCCESS); // Check for both VK_EXT_debug_report and VK_LAYER_LUNARG_standard_validation return (std::find_if(extension_list.begin(), extension_list.end(), [](const auto& it) { return strcmp(it.extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME) == 0; }) != extension_list.end() && std::find_if(layer_list.begin(), layer_list.end(), [](const auto& it) { return strcmp(it.layerName, "VK_LAYER_LUNARG_standard_validation") == 0; }) != layer_list.end()); }
bool CommandBufferManager::CreateCommandBuffers() { VkDevice device = g_vulkan_context->GetDevice(); for (FrameResources& resources : m_frame_resources) { resources.init_command_buffer_used = false; resources.needs_fence_wait = false; VkCommandBufferAllocateInfo allocate_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, m_command_pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast<uint32_t>(resources.command_buffers.size())}; VkResult res = vkAllocateCommandBuffers(device, &allocate_info, resources.command_buffers.data()); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkAllocateCommandBuffers failed: "); return false; } VkFenceCreateInfo fence_info = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT}; res = vkCreateFence(device, &fence_info, nullptr, &resources.fence); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateFence failed: "); return false; } // TODO: A better way to choose the number of descriptors. VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 500000}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 500000}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16}, {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1024}}; VkDescriptorPoolCreateInfo pool_create_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, nullptr, 0, 100000, // tweak this static_cast<u32>(ArraySize(pool_sizes)), pool_sizes}; res = vkCreateDescriptorPool(device, &pool_create_info, nullptr, &resources.descriptor_pool); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateDescriptorPool failed: "); return false; } } // Activate the first command buffer. ActivateCommandBuffer moves forward, so start with the last m_current_frame = m_frame_resources.size() - 1; ActivateCommandBuffer(); return true; }
bool SwapChain::SetupSwapChainImages() { _assert_(m_swap_chain_images.empty()); uint32_t image_count; VkResult res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, nullptr); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: "); return false; } std::vector<VkImage> images(image_count); res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, images.data()); _assert_(res == VK_SUCCESS); m_swap_chain_images.reserve(image_count); for (uint32_t i = 0; i < image_count; i++) { SwapChainImage image; image.image = images[i]; // Create texture object, which creates a view of the backbuffer image.texture = Texture2D::CreateFromExistingImage( m_width, m_height, 1, 1, m_surface_format.format, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, image.image); VkImageView view = image.texture->GetView(); VkFramebufferCreateInfo framebuffer_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0, m_render_pass, 1, &view, m_width, m_height, 1}; res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr, &image.framebuffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: "); return false; } m_swap_chain_images.emplace_back(std::move(image)); } return true; }
void PerfQuery::ReadbackQueries(u32 query_count) { // Should be at maximum query_count queries pending. ASSERT(query_count <= m_query_count && (m_query_readback_pos + query_count) <= PERF_QUERY_BUFFER_SIZE); // Read back from the GPU. VkResult res = vkGetQueryPoolResults(g_vulkan_context->GetDevice(), m_query_pool, m_query_readback_pos, query_count, query_count * sizeof(PerfQueryDataType), m_query_result_buffer.data(), sizeof(PerfQueryDataType), 0); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkGetQueryPoolResults failed: "); // Remove pending queries. for (u32 i = 0; i < query_count; i++) { u32 index = (m_query_readback_pos + i) % PERF_QUERY_BUFFER_SIZE; ActiveQuery& entry = m_query_buffer[index]; // Should have a fence associated with it (waiting for a result). DEBUG_ASSERT(entry.fence_counter != 0); entry.fence_counter = 0; entry.has_value = false; // NOTE: Reported pixel metrics should be referenced to native resolution m_results[entry.query_type] += static_cast<u32>(static_cast<u64>(m_query_result_buffer[i]) * EFB_WIDTH / g_renderer->GetTargetWidth() * EFB_HEIGHT / g_renderer->GetTargetHeight()); } m_query_readback_pos = (m_query_readback_pos + query_count) % PERF_QUERY_BUFFER_SIZE; m_query_count -= query_count; }
bool SwapChain::SelectSurfaceFormat() { u32 format_count; VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &format_count, nullptr); if (res != VK_SUCCESS || format_count == 0) { LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); return false; } std::vector<VkSurfaceFormatKHR> surface_formats(format_count); res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &format_count, surface_formats.data()); _assert_(res == VK_SUCCESS); // If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA if (surface_formats[0].format == VK_FORMAT_UNDEFINED) { m_surface_format.format = VK_FORMAT_R8G8B8A8_UNORM; m_surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; return true; } // Use the first surface format, just use what it prefers. // Some drivers seem to return a SRGB format here (Intel Mesa). // This results in gamma correction when presenting to the screen, which we don't want. // Use a linear format instead, if this is the case. m_surface_format.format = Util::GetLinearFormat(surface_formats[0].format); m_surface_format.colorSpace = surface_formats[0].colorSpace; return true; }
bool TextureConverter::CreateEncodingTexture() { m_encoding_render_texture = Texture2D::Create( ENCODING_TEXTURE_WIDTH, ENCODING_TEXTURE_HEIGHT, 1, 1, ENCODING_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); if (!m_encoding_render_texture) return false; VkImageView framebuffer_attachments[] = {m_encoding_render_texture->GetView()}; VkFramebufferCreateInfo framebuffer_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0, m_encoding_render_pass, static_cast<u32>(ArraySize(framebuffer_attachments)), framebuffer_attachments, m_encoding_render_texture->GetWidth(), m_encoding_render_texture->GetHeight(), m_encoding_render_texture->GetLayers()}; VkResult res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr, &m_encoding_render_framebuffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: "); return false; } return true; }
bool TextureConverter::CreateEncodingRenderPass() { VkAttachmentDescription attachments[] = { {0, ENCODING_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}}; VkAttachmentReference color_attachment_references[] = { {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}}; VkSubpassDescription subpass_descriptions[] = {{0, VK_PIPELINE_BIND_POINT_GRAPHICS, 0, nullptr, 1, color_attachment_references, nullptr, nullptr, 0, nullptr}}; VkRenderPassCreateInfo pass_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(attachments)), attachments, static_cast<u32>(ArraySize(subpass_descriptions)), subpass_descriptions, 0, nullptr}; VkResult res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &pass_info, nullptr, &m_encoding_render_pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass (Encode) failed: "); return false; } return true; }
std::unique_ptr<Texture2D> Texture2D::CreateFromExistingImage(u32 width, u32 height, u32 levels, u32 layers, VkFormat format, VkSampleCountFlagBits samples, VkImageViewType view_type, VkImage existing_image) { // Only need to create the image view, this is mainly for swap chains. VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, nullptr, 0, existing_image, view_type, format, {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}, {Util::IsDepthFormat(format) ? static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT) : static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_COLOR_BIT), 0, levels, 0, layers}}; // Memory is managed by the owner of the image. VkDeviceMemory memory = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE; VkResult res = vkCreateImageView(g_vulkan_context->GetDevice(), &view_info, nullptr, &view); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateImageView failed: "); return nullptr; } return std::make_unique<Texture2D>(width, height, levels, layers, format, samples, view_type, existing_image, memory, view); }
void CommandBufferManager::WaitForCommandBufferCompletion(u32 index) { // Ensure this command buffer has been submitted. WaitForWorkerThreadIdle(); // Wait for this command buffer to be completed. VkResult res = vkWaitForFences(g_vulkan_context->GetDevice(), 1, &m_frame_resources[index].fence, VK_TRUE, UINT64_MAX); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkWaitForFences failed: "); // Clean up any resources for command buffers between the last known completed buffer and this // now-completed command buffer. If we use >2 buffers, this may be more than one buffer. const u64 now_completed_counter = m_frame_resources[index].fence_counter; u32 cleanup_index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS; while (cleanup_index != m_current_frame) { FrameResources& resources = m_frame_resources[cleanup_index]; if (resources.fence_counter > now_completed_counter) break; if (resources.fence_counter > m_completed_fence_counter) { for (auto& it : resources.cleanup_resources) it(); resources.cleanup_resources.clear(); } cleanup_index = (cleanup_index + 1) % NUM_COMMAND_BUFFERS; } m_completed_fence_counter = now_completed_counter; }
void CommandBufferManager::WaitForFence(VkFence fence) { // Find the command buffer that this fence corresponds to. size_t command_buffer_index = 0; for (; command_buffer_index < m_frame_resources.size(); command_buffer_index++) { if (m_frame_resources[command_buffer_index].fence == fence) break; } _assert_(command_buffer_index < m_frame_resources.size()); // Has this command buffer already been waited for? if (!m_frame_resources[command_buffer_index].needs_fence_wait) return; // Wait for this command buffer to be completed. VkResult res = vkWaitForFences(g_vulkan_context->GetDevice(), 1, &m_frame_resources[command_buffer_index].fence, VK_TRUE, UINT64_MAX); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkWaitForFences failed: "); // Immediately fire callbacks and cleanups, since the commands has been completed. m_frame_resources[command_buffer_index].needs_fence_wait = false; OnCommandBufferExecuted(command_buffer_index); }
bool VulkanContext::EnableDebugReports() { // Already enabled? if (m_debug_report_callback != VK_NULL_HANDLE) return true; // Check for presence of the functions before calling if (!vkCreateDebugReportCallbackEXT || !vkDestroyDebugReportCallbackEXT || !vkDebugReportMessageEXT) { return false; } VkDebugReportCallbackCreateInfoEXT callback_info = { VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, nullptr, VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT, DebugReportCallback, nullptr}; VkResult res = vkCreateDebugReportCallbackEXT(m_instance, &callback_info, nullptr, &m_debug_report_callback); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateDebugReportCallbackEXT failed: "); return false; } return true; }
void CommandBufferManager::ActivateCommandBuffer() { // Move to the next command buffer. m_current_frame = (m_current_frame + 1) % NUM_COMMAND_BUFFERS; FrameResources& resources = m_frame_resources[m_current_frame]; // Wait for the GPU to finish with all resources for this command buffer. if (resources.needs_fence_wait) { VkResult res = vkWaitForFences(g_vulkan_context->GetDevice(), 1, &resources.fence, true, UINT64_MAX); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkWaitForFences failed: "); OnCommandBufferExecuted(m_current_frame); } // Reset fence to unsignaled before starting. VkResult res = vkResetFences(g_vulkan_context->GetDevice(), 1, &resources.fence); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkResetFences failed: "); // Reset command pools to beginning since we can re-use the memory now res = vkResetCommandPool(g_vulkan_context->GetDevice(), resources.command_pool, 0); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkResetCommandPool failed: "); // Enable commands to be recorded to the two buffers again. VkCommandBufferBeginInfo begin_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr}; for (VkCommandBuffer command_buffer : resources.command_buffers) { res = vkBeginCommandBuffer(command_buffer, &begin_info); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkBeginCommandBuffer failed: "); } // Also can do the same for the descriptor pools res = vkResetDescriptorPool(g_vulkan_context->GetDevice(), resources.descriptor_pool, 0); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkResetDescriptorPool failed: "); // Reset upload command buffer state resources.init_command_buffer_used = false; }
bool ObjectCache::CreateDescriptorSetLayouts() { static const VkDescriptorSetLayoutBinding ubo_set_bindings[] = { {UBO_DESCRIPTOR_SET_BINDING_PS, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, {UBO_DESCRIPTOR_SET_BINDING_VS, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT}, {UBO_DESCRIPTOR_SET_BINDING_GS, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_GEOMETRY_BIT}}; static const VkDescriptorSetLayoutBinding sampler_set_bindings[] = { {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<u32>(NUM_PIXEL_SHADER_SAMPLERS), VK_SHADER_STAGE_FRAGMENT_BIT}}; static const VkDescriptorSetLayoutBinding ssbo_set_bindings[] = { {0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}}; static const VkDescriptorSetLayoutBinding texel_buffer_set_bindings[] = { {0, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, }; static const VkDescriptorSetLayoutBinding compute_set_bindings[] = { {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {5, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {6, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT}, {7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, }; static const VkDescriptorSetLayoutCreateInfo create_infos[NUM_DESCRIPTOR_SET_LAYOUTS] = { {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(ubo_set_bindings)), ubo_set_bindings}, {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(sampler_set_bindings)), sampler_set_bindings}, {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(ssbo_set_bindings)), ssbo_set_bindings}, {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(texel_buffer_set_bindings)), texel_buffer_set_bindings}, {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(compute_set_bindings)), compute_set_bindings}}; for (size_t i = 0; i < NUM_DESCRIPTOR_SET_LAYOUTS; i++) { VkResult res = vkCreateDescriptorSetLayout(g_vulkan_context->GetDevice(), &create_infos[i], nullptr, &m_descriptor_set_layouts[i]); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateDescriptorSetLayout failed: "); return false; } } return true; }
bool ObjectCache::CreatePipelineLayouts() { VkResult res; // Descriptor sets for each pipeline layout VkDescriptorSetLayout standard_sets[] = { m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_UNIFORM_BUFFERS], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_PIXEL_SHADER_SAMPLERS]}; VkDescriptorSetLayout bbox_sets[] = { m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_UNIFORM_BUFFERS], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_PIXEL_SHADER_SAMPLERS], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_SHADER_STORAGE_BUFFERS]}; VkDescriptorSetLayout texture_conversion_sets[] = { m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_UNIFORM_BUFFERS], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_PIXEL_SHADER_SAMPLERS], m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_TEXEL_BUFFERS]}; VkDescriptorSetLayout compute_sets[] = {m_descriptor_set_layouts[DESCRIPTOR_SET_LAYOUT_COMPUTE]}; VkPushConstantRange push_constant_range = { VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, PUSH_CONSTANT_BUFFER_SIZE}; VkPushConstantRange compute_push_constant_range = {VK_SHADER_STAGE_COMPUTE_BIT, 0, PUSH_CONSTANT_BUFFER_SIZE}; // Info for each pipeline layout VkPipelineLayoutCreateInfo pipeline_layout_info[NUM_PIPELINE_LAYOUTS] = { // Standard {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(standard_sets)), standard_sets, 0, nullptr}, // BBox {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(bbox_sets)), bbox_sets, 0, nullptr}, // Push Constant {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(standard_sets)), standard_sets, 1, &push_constant_range}, // Texture Conversion {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(texture_conversion_sets)), texture_conversion_sets, 1, &push_constant_range}, // Compute {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(compute_sets)), compute_sets, 1, &compute_push_constant_range}}; for (size_t i = 0; i < NUM_PIPELINE_LAYOUTS; i++) { if ((res = vkCreatePipelineLayout(g_vulkan_context->GetDevice(), &pipeline_layout_info[i], nullptr, &m_pipeline_layouts[i])) != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreatePipelineLayout failed: "); return false; } } return true; }
bool ObjectCache::CreateStaticSamplers() { VkSamplerCreateInfo create_info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkSamplerCreateFlags flags VK_FILTER_NEAREST, // VkFilter magFilter VK_FILTER_NEAREST, // VkFilter minFilter VK_SAMPLER_MIPMAP_MODE_NEAREST, // VkSamplerMipmapMode mipmapMode VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // VkSamplerAddressMode addressModeU VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // VkSamplerAddressMode addressModeV VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // VkSamplerAddressMode addressModeW 0.0f, // float mipLodBias VK_FALSE, // VkBool32 anisotropyEnable 1.0f, // float maxAnisotropy VK_FALSE, // VkBool32 compareEnable VK_COMPARE_OP_ALWAYS, // VkCompareOp compareOp std::numeric_limits<float>::min(), // float minLod std::numeric_limits<float>::max(), // float maxLod VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, // VkBorderColor borderColor VK_FALSE // VkBool32 unnormalizedCoordinates }; VkResult res = vkCreateSampler(g_vulkan_context->GetDevice(), &create_info, nullptr, &m_point_sampler); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateSampler failed: "); return false; } // Most fields are shared across point<->linear samplers, so only change those necessary. create_info.minFilter = VK_FILTER_LINEAR; create_info.magFilter = VK_FILTER_LINEAR; create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; res = vkCreateSampler(g_vulkan_context->GetDevice(), &create_info, nullptr, &m_linear_sampler); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateSampler failed: "); return false; } return true; }
VkResult SwapChain::AcquireNextImage(VkSemaphore available_semaphore) { VkResult res = vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX, available_semaphore, VK_NULL_HANDLE, &m_current_swap_chain_image_index); if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR failed: "); return res; }
bool VulkanContext::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface, bool enable_validation_layer) { u32 extension_count = 0; VkResult res = vkEnumerateDeviceExtensionProperties(m_physical_device, nullptr, &extension_count, nullptr); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkEnumerateDeviceExtensionProperties failed: "); return false; } if (extension_count == 0) { ERROR_LOG(VIDEO, "Vulkan: No extensions supported by device."); return false; } std::vector<VkExtensionProperties> available_extension_list(extension_count); res = vkEnumerateDeviceExtensionProperties(m_physical_device, nullptr, &extension_count, available_extension_list.data()); _assert_(res == VK_SUCCESS); for (const auto& extension_properties : available_extension_list) INFO_LOG(VIDEO, "Available extension: %s", extension_properties.extensionName); auto CheckForExtension = [&](const char* name, bool required) -> bool { if (std::find_if(available_extension_list.begin(), available_extension_list.end(), [&](const VkExtensionProperties& properties) { return !strcmp(name, properties.extensionName); }) != available_extension_list.end()) { INFO_LOG(VIDEO, "Enabling extension: %s", name); extension_list->push_back(name); return true; } if (required) { ERROR_LOG(VIDEO, "Vulkan: Missing required extension %s.", name); return false; } return true; }; if (enable_surface && !CheckForExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, true)) { return false; } return true; }
TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConfig& config) { // Determine image usage, we need to flag as an attachment if it can be used as a rendertarget. VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; if (config.rendertarget) usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; // Allocate texture object std::unique_ptr<Texture2D> texture = Texture2D::Create( config.width, config.height, config.levels, config.layers, TEXTURECACHE_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, usage); if (!texture) return nullptr; // If this is a render target (for efb copies), allocate a framebuffer VkFramebuffer framebuffer = VK_NULL_HANDLE; if (config.rendertarget) { VkImageView framebuffer_attachments[] = {texture->GetView()}; VkFramebufferCreateInfo framebuffer_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0, m_render_pass, static_cast<u32>(ArraySize(framebuffer_attachments)), framebuffer_attachments, texture->GetWidth(), texture->GetHeight(), texture->GetLayers()}; VkResult res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr, &framebuffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: "); return nullptr; } // Clear render targets before use to prevent reading uninitialized memory. VkClearColorValue clear_value = {{0.0f, 0.0f, 0.0f, 1.0f}}; VkImageSubresourceRange clear_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, config.levels, 0, config.layers}; texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); vkCmdClearColorImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), texture->GetImage(), texture->GetLayout(), &clear_value, 1, &clear_range); } return new TCacheEntry(config, std::move(texture), framebuffer); }
VkSampler ObjectCache::GetSampler(const SamplerState& info) { auto iter = m_sampler_cache.find(info); if (iter != m_sampler_cache.end()) return iter->second; static constexpr std::array<VkFilter, 4> filters = {{VK_FILTER_NEAREST, VK_FILTER_LINEAR}}; static constexpr std::array<VkSamplerMipmapMode, 2> mipmap_modes = { {VK_SAMPLER_MIPMAP_MODE_NEAREST, VK_SAMPLER_MIPMAP_MODE_LINEAR}}; static constexpr std::array<VkSamplerAddressMode, 4> address_modes = { {VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_SAMPLER_ADDRESS_MODE_REPEAT, VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT}}; VkSamplerCreateInfo create_info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkSamplerCreateFlags flags filters[static_cast<u32>(info.mag_filter.Value())], // VkFilter magFilter filters[static_cast<u32>(info.min_filter.Value())], // VkFilter minFilter mipmap_modes[static_cast<u32>(info.mipmap_filter.Value())], // VkSamplerMipmapMode mipmapMode address_modes[static_cast<u32>(info.wrap_u.Value())], // VkSamplerAddressMode addressModeU address_modes[static_cast<u32>(info.wrap_v.Value())], // VkSamplerAddressMode addressModeV VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // VkSamplerAddressMode addressModeW info.lod_bias / 256.0f, // float mipLodBias VK_FALSE, // VkBool32 anisotropyEnable 0.0f, // float maxAnisotropy VK_FALSE, // VkBool32 compareEnable VK_COMPARE_OP_ALWAYS, // VkCompareOp compareOp info.min_lod / 16.0f, // float minLod info.max_lod / 16.0f, // float maxLod VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, // VkBorderColor borderColor VK_FALSE // VkBool32 unnormalizedCoordinates }; // Can we use anisotropic filtering with this sampler? if (info.anisotropic_filtering && g_vulkan_context->SupportsAnisotropicFiltering()) { // Cap anisotropy to device limits. create_info.anisotropyEnable = VK_TRUE; create_info.maxAnisotropy = std::min(static_cast<float>(1 << g_ActiveConfig.iMaxAnisotropy), g_vulkan_context->GetMaxSamplerAnisotropy()); } VkSampler sampler = VK_NULL_HANDLE; VkResult res = vkCreateSampler(g_vulkan_context->GetDevice(), &create_info, nullptr, &sampler); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkCreateSampler failed: "); // Store it even if it failed m_sampler_cache.emplace(info, sampler); return sampler; }
VulkanContext::GPUList VulkanContext::EnumerateGPUs(VkInstance instance) { u32 gpu_count = 0; VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: "); return {}; } GPUList gpus; gpus.resize(gpu_count); res = vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data()); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: "); return {}; } return gpus; }
bool SwapChain::SelectPresentMode() { VkResult res; u32 mode_count; res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &mode_count, nullptr); if (res != VK_SUCCESS || mode_count == 0) { LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); return false; } std::vector<VkPresentModeKHR> present_modes(mode_count); res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &mode_count, present_modes.data()); _assert_(res == VK_SUCCESS); // Checks if a particular mode is supported, if it is, returns that mode. auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) { auto it = std::find_if(present_modes.begin(), present_modes.end(), [check_mode](VkPresentModeKHR mode) { return check_mode == mode; }); return it != present_modes.end(); }; // If vsync is enabled, use VK_PRESENT_MODE_FIFO_KHR. // This check should not fail with conforming drivers, as the FIFO present mode is mandated by // the specification (VK_KHR_swapchain). In case it isn't though, fall through to any other mode. if (m_vsync_enabled && CheckForMode(VK_PRESENT_MODE_FIFO_KHR)) { m_present_mode = VK_PRESENT_MODE_FIFO_KHR; return true; } // Prefer screen-tearing, if possible, for lowest latency. if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR)) { m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; return true; } // Use optimized-vsync above vsync. if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) { m_present_mode = VK_PRESENT_MODE_MAILBOX_KHR; return true; } // Fall back to whatever is available. m_present_mode = present_modes[0]; return true; }
void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread, VkSemaphore wait_semaphore, VkSemaphore signal_semaphore, VkSwapchainKHR present_swap_chain, uint32_t present_image_index) { FrameResources& resources = m_frame_resources[m_current_frame]; // Fire fence tracking callbacks. This can't happen on the worker thread. // We invoke these before submitting so that any last-minute commands can be added. for (const auto& iter : m_fence_point_callbacks) iter.second.first(resources.command_buffers[1], resources.fence); // End the current command buffer. for (VkCommandBuffer command_buffer : resources.command_buffers) { VkResult res = vkEndCommandBuffer(command_buffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkEndCommandBuffer failed: "); PanicAlert("Failed to end command buffer"); } } // This command buffer now has commands, so can't be re-used without waiting. resources.needs_fence_wait = true; // Submitting off-thread? if (m_use_threaded_submission && submit_on_worker_thread) { // Push to the pending submit queue. { std::lock_guard<std::mutex> guard(m_pending_submit_lock); m_pending_submits.push_back({m_current_frame, wait_semaphore, signal_semaphore, present_swap_chain, present_image_index}); } // Wake up the worker thread for a single iteration. m_submit_loop->Wakeup(); } else { // Pass through to normal submission path. SubmitCommandBuffer(m_current_frame, wait_semaphore, signal_semaphore, present_swap_chain, present_image_index); } }
bool CommandBufferManager::CreateCommandPool() { VkCommandPoolCreateInfo info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, g_vulkan_context->GetGraphicsQueueFamilyIndex()}; VkResult res = vkCreateCommandPool(g_vulkan_context->GetDevice(), &info, nullptr, &m_command_pool); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateCommandPool failed: "); return false; } return true; }
bool TextureCache::CreateRenderPasses() { static constexpr VkAttachmentDescription update_attachment = { 0, TEXTURECACHE_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; static constexpr VkAttachmentReference color_attachment_reference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; static constexpr VkSubpassDescription subpass_description = { 0, VK_PIPELINE_BIND_POINT_GRAPHICS, 0, nullptr, 1, &color_attachment_reference, nullptr, nullptr, 0, nullptr }; VkRenderPassCreateInfo update_info = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, nullptr, 0, 1, &update_attachment, 1, &subpass_description, 0, nullptr }; VkResult res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &update_info, nullptr, &m_render_pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass failed: "); return false; } return true; }
VkShaderModule CreateShaderModule(const u32* spv, size_t spv_word_count) { VkShaderModuleCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; info.codeSize = spv_word_count * sizeof(u32); info.pCode = spv; VkShaderModule module; VkResult res = vkCreateShaderModule(g_vulkan_context->GetDevice(), &info, nullptr, &module); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateShaderModule failed: "); return VK_NULL_HANDLE; } return module; }
void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion, VkSwapchainKHR present_swap_chain, uint32_t present_image_index) { // End the current command buffer. FrameResources& resources = m_frame_resources[m_current_frame]; for (VkCommandBuffer command_buffer : resources.command_buffers) { VkResult res = vkEndCommandBuffer(command_buffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkEndCommandBuffer failed: "); PanicAlert("Failed to end command buffer"); } } // Grab the semaphore before submitting command buffer either on-thread or off-thread. // This prevents a race from occurring where a second command buffer is executed // before the worker thread has woken and executed the first one yet. m_submit_semaphore.Wait(); // Submitting off-thread? if (m_use_threaded_submission && submit_on_worker_thread && !wait_for_completion) { // Push to the pending submit queue. { std::lock_guard<std::mutex> guard(m_pending_submit_lock); m_pending_submits.push_back({present_swap_chain, present_image_index, m_current_frame}); } // Wake up the worker thread for a single iteration. m_submit_loop->Wakeup(); } else { // Pass through to normal submission path. SubmitCommandBuffer(m_current_frame, present_swap_chain, present_image_index); if (wait_for_completion) WaitForCommandBufferCompletion(m_current_frame); } // Switch to next cmdbuffer. BeginCommandBuffer(); }
VkInstance VulkanContext::CreateVulkanInstance(WindowSystemType wstype, bool enable_debug_report, bool enable_validation_layer) { ExtensionList enabled_extensions; if (!SelectInstanceExtensions(&enabled_extensions, wstype, enable_debug_report)) return VK_NULL_HANDLE; VkApplicationInfo app_info = {}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pNext = nullptr; app_info.pApplicationName = "Dolphin Emulator"; app_info.applicationVersion = VK_MAKE_VERSION(5, 0, 0); app_info.pEngineName = "Dolphin Emulator"; app_info.engineVersion = VK_MAKE_VERSION(5, 0, 0); app_info.apiVersion = VK_MAKE_VERSION(1, 0, 0); VkInstanceCreateInfo instance_create_info = {}; instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_create_info.pNext = nullptr; instance_create_info.flags = 0; instance_create_info.pApplicationInfo = &app_info; instance_create_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size()); instance_create_info.ppEnabledExtensionNames = enabled_extensions.data(); instance_create_info.enabledLayerCount = 0; instance_create_info.ppEnabledLayerNames = nullptr; // Enable debug layer on debug builds if (enable_validation_layer) { static const char* layer_names[] = {"VK_LAYER_LUNARG_standard_validation"}; instance_create_info.enabledLayerCount = 1; instance_create_info.ppEnabledLayerNames = layer_names; } VkInstance instance; VkResult res = vkCreateInstance(&instance_create_info, nullptr, &instance); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateInstance failed: "); return nullptr; } return instance; }
bool PerfQuery::CreateQueryPool() { VkQueryPoolCreateInfo info = { VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkQueryPoolCreateFlags flags VK_QUERY_TYPE_OCCLUSION, // VkQueryType queryType PERF_QUERY_BUFFER_SIZE, // uint32_t queryCount 0 // VkQueryPipelineStatisticFlags pipelineStatistics; }; VkResult res = vkCreateQueryPool(g_vulkan_context->GetDevice(), &info, nullptr, &m_query_pool); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateQueryPool failed: "); return false; } return true; }
VkBufferView TextureConverter::CreateTexelBufferView(VkFormat format) const { // Create a view of the whole buffer, we'll offset our texel load into it VkBufferViewCreateInfo view_info = { VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkBufferViewCreateFlags flags m_texel_buffer->GetBuffer(), // VkBuffer buffer format, // VkFormat format 0, // VkDeviceSize offset m_texel_buffer_size // VkDeviceSize range }; VkBufferView view; VkResult res = vkCreateBufferView(g_vulkan_context->GetDevice(), &view_info, nullptr, &view); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateBufferView failed: "); return VK_NULL_HANDLE; } return view; }
bool SwapChain::CreateRenderPass() { // render pass for rendering to the swap chain VkAttachmentDescription present_render_pass_attachments[] = { {0, m_surface_format.format, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}}; VkAttachmentReference present_render_pass_color_attachment_references[] = { {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}}; VkSubpassDescription present_render_pass_subpass_descriptions[] = { {0, VK_PIPELINE_BIND_POINT_GRAPHICS, 0, nullptr, 1, present_render_pass_color_attachment_references, nullptr, nullptr, 0, nullptr}}; VkRenderPassCreateInfo present_render_pass_info = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, nullptr, 0, static_cast<u32>(ArraySize(present_render_pass_attachments)), present_render_pass_attachments, static_cast<u32>(ArraySize(present_render_pass_subpass_descriptions)), present_render_pass_subpass_descriptions, 0, nullptr}; VkResult res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &present_render_pass_info, nullptr, &m_render_pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass (present) failed: "); return false; } return true; }