GPUDevice: Add support for Raster Ordered Views

This commit is contained in:
Stenzek
2024-07-22 01:01:57 +10:00
parent e743c5d1b1
commit 1006fa00da
17 changed files with 679 additions and 360 deletions

View File

@ -385,19 +385,21 @@ GPUDevice::AdapterInfoList VulkanDevice::GetAdapterList()
return ret;
}
bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface)
bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface, Error* error)
{
u32 extension_count = 0;
VkResult res = vkEnumerateDeviceExtensionProperties(m_physical_device, nullptr, &extension_count, nullptr);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkEnumerateDeviceExtensionProperties failed: ");
Vulkan::SetErrorObject(error, "vkEnumerateDeviceExtensionProperties failed: ", res);
return false;
}
if (extension_count == 0)
{
ERROR_LOG("Vulkan: No extensions supported by device.");
ERROR_LOG("No extensions supported by device.");
Error::SetStringView(error, "No extensions supported by device.");
return false;
}
@ -423,7 +425,10 @@ bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool en
}
if (required)
{
ERROR_LOG("Vulkan: Missing required extension {}.", name);
Error::SetStringFmt(error, "Missing required extension {}.", name);
}
return false;
};
@ -466,6 +471,11 @@ bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool en
m_optional_extensions.vk_ext_swapchain_maintenance1 &&
SupportsExtension(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, false);
// Dynamic rendering isn't strictly needed for FSI, but we want it with framebufferless rendering.
m_optional_extensions.vk_ext_fragment_shader_interlock =
m_optional_extensions.vk_khr_dynamic_rendering &&
SupportsExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME, false);
#ifdef _WIN32
m_optional_extensions.vk_ext_full_screen_exclusive =
enable_surface && SupportsExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME, false);
@ -480,6 +490,7 @@ bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool en
{
m_optional_extensions.vk_khr_dynamic_rendering = false;
m_optional_extensions.vk_khr_dynamic_rendering_local_read = false;
m_optional_extensions.vk_ext_fragment_shader_interlock = false;
WARNING_LOG("Disabling VK_KHR_dynamic_rendering on broken mobile driver.");
}
if (m_optional_extensions.vk_khr_push_descriptor)
@ -501,29 +512,15 @@ bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool en
return true;
}
bool VulkanDevice::SelectDeviceFeatures()
{
VkPhysicalDeviceFeatures available_features;
vkGetPhysicalDeviceFeatures(m_physical_device, &available_features);
// Enable the features we use.
m_device_features.dualSrcBlend = available_features.dualSrcBlend;
m_device_features.largePoints = available_features.largePoints;
m_device_features.wideLines = available_features.wideLines;
m_device_features.samplerAnisotropy = available_features.samplerAnisotropy;
m_device_features.sampleRateShading = available_features.sampleRateShading;
m_device_features.geometryShader = available_features.geometryShader;
return true;
}
bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer)
bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, FeatureMask disabled_features,
Error* error)
{
u32 queue_family_count;
vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, nullptr);
if (queue_family_count == 0)
{
ERROR_LOG("No queue families found on specified vulkan physical device.");
Error::SetStringView(error, "No queue families found on specified vulkan physical device.");
return false;
}
@ -554,6 +551,7 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ");
Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ", res);
return false;
}
@ -572,11 +570,13 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
if (m_graphics_queue_family_index == queue_family_count)
{
ERROR_LOG("Vulkan: Failed to find an acceptable graphics queue.");
Error::SetStringView(error, "Vulkan: Failed to find an acceptable graphics queue.");
return false;
}
if (surface != VK_NULL_HANDLE && m_present_queue_family_index == queue_family_count)
{
ERROR_LOG("Vulkan: Failed to find an acceptable present queue.");
Error::SetStringView(error, "Vulkan: Failed to find an acceptable present queue.");
return false;
}
@ -610,17 +610,26 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
device_info.pQueueCreateInfos = queue_infos.data();
ExtensionList enabled_extensions;
if (!SelectDeviceExtensions(&enabled_extensions, surface != VK_NULL_HANDLE))
if (!SelectDeviceExtensions(&enabled_extensions, surface != VK_NULL_HANDLE, error))
return false;
device_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
device_info.ppEnabledExtensionNames = enabled_extensions.data();
// Check for required features before creating.
if (!SelectDeviceFeatures())
return false;
VkPhysicalDeviceFeatures available_features;
vkGetPhysicalDeviceFeatures(m_physical_device, &available_features);
device_info.pEnabledFeatures = &m_device_features;
// Enable the features we use.
VkPhysicalDeviceFeatures enabled_features = {};
enabled_features.dualSrcBlend = available_features.dualSrcBlend;
enabled_features.largePoints = available_features.largePoints;
enabled_features.wideLines = available_features.wideLines;
enabled_features.samplerAnisotropy = available_features.samplerAnisotropy;
enabled_features.sampleRateShading = available_features.sampleRateShading;
enabled_features.geometryShader = available_features.geometryShader;
enabled_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics;
device_info.pEnabledFeatures = &enabled_features;
// Enable debug layer on debug builds
if (enable_validation_layer)
@ -639,6 +648,8 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_LOCAL_READ_FEATURES_KHR, nullptr, VK_TRUE};
VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT, nullptr, VK_TRUE};
VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT fragment_shader_interlock_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT, nullptr, VK_FALSE, VK_TRUE, VK_FALSE};
if (m_optional_extensions.vk_ext_rasterization_order_attachment_access)
Vulkan::AddPointerToChain(&device_info, &rasterization_order_access_feature);
@ -649,12 +660,15 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
Vulkan::AddPointerToChain(&device_info, &dynamic_rendering_feature);
if (m_optional_extensions.vk_khr_dynamic_rendering_local_read)
Vulkan::AddPointerToChain(&device_info, &dynamic_rendering_local_read_feature);
if (m_optional_extensions.vk_ext_fragment_shader_interlock)
Vulkan::AddPointerToChain(&device_info, &fragment_shader_interlock_feature);
}
VkResult res = vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateDevice failed: ");
Vulkan::SetErrorObject(error, "vkCreateDevice failed: ", res);
return false;
}
@ -677,6 +691,7 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
m_device_properties.limits.timestampPeriod);
ProcessDeviceExtensions();
SetFeatures(disabled_features, enabled_features);
return true;
}
@ -693,6 +708,8 @@ void VulkanDevice::ProcessDeviceExtensions()
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_LOCAL_READ_FEATURES_KHR, nullptr, VK_FALSE};
VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT, nullptr, VK_FALSE};
VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT fragment_shader_interlock_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT, nullptr, VK_FALSE, VK_FALSE, VK_FALSE};
// add in optional feature structs
if (m_optional_extensions.vk_ext_rasterization_order_attachment_access)
@ -704,6 +721,8 @@ void VulkanDevice::ProcessDeviceExtensions()
Vulkan::AddPointerToChain(&features2, &dynamic_rendering_feature);
if (m_optional_extensions.vk_khr_dynamic_rendering_local_read)
Vulkan::AddPointerToChain(&features2, &dynamic_rendering_local_read_feature);
if (m_optional_extensions.vk_ext_fragment_shader_interlock)
Vulkan::AddPointerToChain(&features2, &fragment_shader_interlock_feature);
}
// we might not have VK_KHR_get_physical_device_properties2...
@ -738,6 +757,9 @@ void VulkanDevice::ProcessDeviceExtensions()
m_optional_extensions.vk_khr_dynamic_rendering &= (dynamic_rendering_feature.dynamicRendering == VK_TRUE);
m_optional_extensions.vk_khr_dynamic_rendering_local_read &=
(dynamic_rendering_local_read_feature.dynamicRenderingLocalRead == VK_TRUE);
m_optional_extensions.vk_ext_fragment_shader_interlock &=
(m_optional_extensions.vk_khr_dynamic_rendering &&
fragment_shader_interlock_feature.fragmentShaderPixelInterlock == VK_TRUE);
VkPhysicalDeviceProperties2 properties2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, nullptr, {}};
VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor_properties = {
@ -769,6 +791,8 @@ void VulkanDevice::ProcessDeviceExtensions()
INFO_LOG("VK_EXT_external_memory_host is {}",
m_optional_extensions.vk_ext_external_memory_host ? "supported" : "NOT supported");
INFO_LOG("VK_EXT_memory_budget is {}", m_optional_extensions.vk_ext_memory_budget ? "supported" : "NOT supported");
INFO_LOG("VK_EXT_fragment_shader_interlock is {}",
m_optional_extensions.vk_ext_fragment_shader_interlock ? "supported" : "NOT supported");
INFO_LOG("VK_EXT_rasterization_order_attachment_access is {}",
m_optional_extensions.vk_ext_rasterization_order_attachment_access ? "supported" : "NOT supported");
INFO_LOG("VK_EXT_swapchain_maintenance1 is {}",
@ -2046,15 +2070,9 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
}
// Attempt to create the device.
if (!CreateDevice(surface, enable_validation_layer))
if (!CreateDevice(surface, enable_validation_layer, disabled_features, error))
return false;
if (!CheckFeatures(disabled_features))
{
Error::SetStringView(error, "Your GPU does not support the required Vulkan features.");
return false;
}
// And critical resources.
if (!CreateAllocator() || !CreatePersistentDescriptorPool() || !CreateCommandBuffers() || !CreatePipelineLayouts())
return false;
@ -2576,14 +2594,13 @@ u32 VulkanDevice::GetMaxMultisamples(VkPhysicalDevice physical_device, const VkP
return 1;
}
bool VulkanDevice::CheckFeatures(FeatureMask disabled_features)
void VulkanDevice::SetFeatures(FeatureMask disabled_features, const VkPhysicalDeviceFeatures& vk_features)
{
m_max_texture_size =
std::min(m_device_properties.limits.maxImageDimension2D, m_device_properties.limits.maxFramebufferWidth);
m_max_multisamples = GetMaxMultisamples(m_physical_device, m_device_properties);
m_features.dual_source_blend =
!(disabled_features & FEATURE_MASK_DUAL_SOURCE_BLEND) && m_device_features.dualSrcBlend;
m_features.dual_source_blend = !(disabled_features & FEATURE_MASK_DUAL_SOURCE_BLEND) && vk_features.dualSrcBlend;
m_features.framebuffer_fetch =
!(disabled_features & (FEATURE_MASK_FEEDBACK_LOOPS | FEATURE_MASK_FRAMEBUFFER_FETCH)) &&
m_optional_extensions.vk_ext_rasterization_order_attachment_access;
@ -2593,7 +2610,7 @@ bool VulkanDevice::CheckFeatures(FeatureMask disabled_features)
m_features.noperspective_interpolation = true;
m_features.texture_copy_to_self = !(disabled_features & FEATURE_MASK_TEXTURE_COPY_TO_SELF);
m_features.per_sample_shading = m_device_features.sampleRateShading;
m_features.per_sample_shading = vk_features.sampleRateShading;
m_features.supports_texture_buffers = !(disabled_features & FEATURE_MASK_TEXTURE_BUFFERS);
m_features.feedback_loops = !(disabled_features & FEATURE_MASK_FEEDBACK_LOOPS);
@ -2612,8 +2629,7 @@ bool VulkanDevice::CheckFeatures(FeatureMask disabled_features)
if (m_features.texture_buffers_emulated_with_ssbo)
WARNING_LOG("Emulating texture buffers with SSBOs.");
m_features.geometry_shaders =
!(disabled_features & FEATURE_MASK_GEOMETRY_SHADERS) && m_device_features.geometryShader;
m_features.geometry_shaders = !(disabled_features & FEATURE_MASK_GEOMETRY_SHADERS) && vk_features.geometryShader;
m_features.partial_msaa_resolve = true;
m_features.memory_import = m_optional_extensions.vk_ext_external_memory_host;
@ -2621,8 +2637,9 @@ bool VulkanDevice::CheckFeatures(FeatureMask disabled_features)
m_features.shader_cache = true;
m_features.pipeline_cache = true;
m_features.prefer_unused_textures = true;
return true;
m_features.raster_order_views =
(!(disabled_features & FEATURE_MASK_RASTER_ORDER_VIEWS) && vk_features.fragmentStoresAndAtomics &&
m_optional_extensions.vk_ext_fragment_shader_interlock);
}
void VulkanDevice::CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level,
@ -2928,7 +2945,7 @@ void VulkanDevice::UnmapUniformBuffer(u32 size)
bool VulkanDevice::CreateNullTexture()
{
m_null_texture = VulkanTexture::Create(1, 1, 1, 1, 1, GPUTexture::Type::RenderTarget, GPUTexture::Format::RGBA8,
m_null_texture = VulkanTexture::Create(1, 1, 1, 1, 1, GPUTexture::Type::RWTexture, GPUTexture::Format::RGBA8,
VK_FORMAT_R8G8B8A8_UNORM);
if (!m_null_texture)
return false;
@ -2948,10 +2965,7 @@ bool VulkanDevice::CreateNullTexture()
return false;
for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++)
{
m_current_textures[i] = m_null_texture.get();
m_current_samplers[i] = point_sampler;
}
return true;
}
@ -3005,59 +3019,89 @@ bool VulkanDevice::CreatePipelineLayouts()
Vulkan::SetObjectName(m_device, m_feedback_loop_ds_layout, "Feedback Loop Descriptor Set Layout");
}
if (m_features.raster_order_views)
{
VkPipelineLayout& pl = m_pipeline_layouts[static_cast<u8>(GPUPipeline::Layout::SingleTextureAndUBO)];
plb.AddDescriptorSet(m_ubo_ds_layout);
plb.AddDescriptorSet(m_single_texture_ds_layout);
// TODO: REMOVE ME
if (m_features.feedback_loops)
plb.AddDescriptorSet(m_feedback_loop_ds_layout);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
for (u32 i = 0; i < MAX_IMAGE_RENDER_TARGETS; i++)
dslb.AddBinding(i, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
if ((m_rov_ds_layout = dslb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Single Texture + UBO Pipeline Layout");
Vulkan::SetObjectName(m_device, m_feedback_loop_ds_layout, "ROV Descriptor Set Layout");
}
for (u32 type = 0; type < 3; type++)
{
VkPipelineLayout& pl = m_pipeline_layouts[static_cast<u8>(GPUPipeline::Layout::SingleTextureAndPushConstants)];
plb.AddDescriptorSet(m_single_texture_ds_layout);
// TODO: REMOVE ME
if (m_features.feedback_loops)
plb.AddDescriptorSet(m_feedback_loop_ds_layout);
plb.AddPushConstants(UNIFORM_PUSH_CONSTANTS_STAGES, 0, UNIFORM_PUSH_CONSTANTS_SIZE);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Single Texture Pipeline Layout");
}
const bool feedback_loop = (type == 1);
const bool rov = (type == 2);
if ((feedback_loop && !m_features.feedback_loops) || (rov && !m_features.raster_order_views))
continue;
{
VkPipelineLayout& pl =
m_pipeline_layouts[static_cast<u8>(GPUPipeline::Layout::SingleTextureBufferAndPushConstants)];
plb.AddDescriptorSet(m_single_texture_buffer_ds_layout);
// TODO: REMOVE ME
if (m_features.feedback_loops)
plb.AddDescriptorSet(m_feedback_loop_ds_layout);
plb.AddPushConstants(UNIFORM_PUSH_CONSTANTS_STAGES, 0, UNIFORM_PUSH_CONSTANTS_SIZE);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Single Texture Buffer + UBO Pipeline Layout");
}
{
VkPipelineLayout& pl = m_pipeline_layouts[type][static_cast<u8>(GPUPipeline::Layout::SingleTextureAndUBO)];
plb.AddDescriptorSet(m_ubo_ds_layout);
plb.AddDescriptorSet(m_single_texture_ds_layout);
if (feedback_loop)
plb.AddDescriptorSet(m_feedback_loop_ds_layout);
else if (rov)
plb.AddDescriptorSet(m_rov_ds_layout);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Single Texture + UBO Pipeline Layout");
}
{
VkPipelineLayout& pl = m_pipeline_layouts[static_cast<u8>(GPUPipeline::Layout::MultiTextureAndUBO)];
plb.AddDescriptorSet(m_ubo_ds_layout);
plb.AddDescriptorSet(m_multi_texture_ds_layout);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Multi Texture + UBO Pipeline Layout");
}
{
VkPipelineLayout& pl =
m_pipeline_layouts[type][static_cast<u8>(GPUPipeline::Layout::SingleTextureAndPushConstants)];
plb.AddDescriptorSet(m_single_texture_ds_layout);
if (feedback_loop)
plb.AddDescriptorSet(m_feedback_loop_ds_layout);
else if (rov)
plb.AddDescriptorSet(m_rov_ds_layout);
plb.AddPushConstants(UNIFORM_PUSH_CONSTANTS_STAGES, 0, UNIFORM_PUSH_CONSTANTS_SIZE);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Single Texture Pipeline Layout");
}
{
VkPipelineLayout& pl = m_pipeline_layouts[static_cast<u8>(GPUPipeline::Layout::MultiTextureAndPushConstants)];
plb.AddDescriptorSet(m_multi_texture_ds_layout);
plb.AddPushConstants(UNIFORM_PUSH_CONSTANTS_STAGES, 0, UNIFORM_PUSH_CONSTANTS_SIZE);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Multi Texture Pipeline Layout");
{
VkPipelineLayout& pl =
m_pipeline_layouts[type][static_cast<u8>(GPUPipeline::Layout::SingleTextureBufferAndPushConstants)];
plb.AddDescriptorSet(m_single_texture_buffer_ds_layout);
if (feedback_loop)
plb.AddDescriptorSet(m_feedback_loop_ds_layout);
else if (rov)
plb.AddDescriptorSet(m_rov_ds_layout);
plb.AddPushConstants(UNIFORM_PUSH_CONSTANTS_STAGES, 0, UNIFORM_PUSH_CONSTANTS_SIZE);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Single Texture Buffer + UBO Pipeline Layout");
}
{
VkPipelineLayout& pl = m_pipeline_layouts[type][static_cast<u8>(GPUPipeline::Layout::MultiTextureAndUBO)];
plb.AddDescriptorSet(m_ubo_ds_layout);
plb.AddDescriptorSet(m_multi_texture_ds_layout);
if (feedback_loop)
plb.AddDescriptorSet(m_feedback_loop_ds_layout);
else if (rov)
plb.AddDescriptorSet(m_rov_ds_layout);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Multi Texture + UBO Pipeline Layout");
}
{
VkPipelineLayout& pl =
m_pipeline_layouts[type][static_cast<u8>(GPUPipeline::Layout::MultiTextureAndPushConstants)];
plb.AddDescriptorSet(m_multi_texture_ds_layout);
plb.AddPushConstants(UNIFORM_PUSH_CONSTANTS_STAGES, 0, UNIFORM_PUSH_CONSTANTS_SIZE);
if (feedback_loop)
plb.AddDescriptorSet(m_feedback_loop_ds_layout);
else if (rov)
plb.AddDescriptorSet(m_rov_ds_layout);
if ((pl = plb.Create(m_device)) == VK_NULL_HANDLE)
return false;
Vulkan::SetObjectName(m_device, pl, "Multi Texture Pipeline Layout");
}
}
return true;
@ -3065,14 +3109,13 @@ bool VulkanDevice::CreatePipelineLayouts()
void VulkanDevice::DestroyPipelineLayouts()
{
for (VkPipelineLayout& pl : m_pipeline_layouts)
{
m_pipeline_layouts.enumerate([this](auto& pl) {
if (pl != VK_NULL_HANDLE)
{
vkDestroyPipelineLayout(m_device, pl, nullptr);
pl = VK_NULL_HANDLE;
}
}
});
auto destroy_dsl = [this](VkDescriptorSetLayout& l) {
if (l != VK_NULL_HANDLE)
@ -3081,6 +3124,7 @@ void VulkanDevice::DestroyPipelineLayouts()
l = VK_NULL_HANDLE;
}
};
destroy_dsl(m_rov_ds_layout);
destroy_dsl(m_feedback_loop_ds_layout);
destroy_dsl(m_multi_texture_ds_layout);
destroy_dsl(m_single_texture_buffer_ds_layout);
@ -3222,10 +3266,13 @@ bool VulkanDevice::TryImportHostMemory(void* data, size_t data_size, VkBufferUsa
}
void VulkanDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds,
GPUPipeline::RenderPassFlag feedback_loop)
GPUPipeline::RenderPassFlag flags)
{
bool changed = (m_num_current_render_targets != num_rts || m_current_depth_target != ds ||
m_current_feedback_loop != feedback_loop);
const bool changed_layout =
(m_current_render_pass_flags & (GPUPipeline::ColorFeedbackLoop | GPUPipeline::BindRenderTargetsAsImages)) !=
(flags & (GPUPipeline::ColorFeedbackLoop | GPUPipeline::BindRenderTargetsAsImages));
bool changed =
(m_num_current_render_targets != num_rts || m_current_depth_target != ds || m_current_render_pass_flags != flags);
bool needs_ds_clear = (ds && ds->IsClearedOrInvalidated());
bool needs_rt_clear = false;
@ -3240,7 +3287,7 @@ void VulkanDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUText
for (u32 i = num_rts; i < m_num_current_render_targets; i++)
m_current_render_targets[i] = nullptr;
m_num_current_render_targets = Truncate8(num_rts);
m_current_feedback_loop = feedback_loop;
m_current_render_pass_flags = flags;
if (changed)
{
@ -3253,12 +3300,12 @@ void VulkanDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUText
return;
}
if (!m_optional_extensions.vk_khr_dynamic_rendering || ((feedback_loop & GPUPipeline::ColorFeedbackLoop) &&
!m_optional_extensions.vk_khr_dynamic_rendering_local_read))
if (!m_optional_extensions.vk_khr_dynamic_rendering ||
((flags & GPUPipeline::ColorFeedbackLoop) && !m_optional_extensions.vk_khr_dynamic_rendering_local_read))
{
m_current_framebuffer = m_framebuffer_manager.Lookup(
(m_num_current_render_targets > 0) ? reinterpret_cast<GPUTexture**>(m_current_render_targets.data()) : nullptr,
m_num_current_render_targets, m_current_depth_target, feedback_loop);
m_num_current_render_targets, m_current_depth_target, flags);
if (m_current_framebuffer == VK_NULL_HANDLE)
{
ERROR_LOG("Failed to create framebuffer");
@ -3266,8 +3313,10 @@ void VulkanDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUText
}
}
m_dirty_flags = (m_dirty_flags & ~DIRTY_FLAG_INPUT_ATTACHMENT) |
((feedback_loop & GPUPipeline::ColorFeedbackLoop) ? DIRTY_FLAG_INPUT_ATTACHMENT : 0);
m_dirty_flags = (m_dirty_flags & ~DIRTY_FLAG_INPUT_ATTACHMENT) | (changed_layout ? DIRTY_FLAG_PIPELINE_LAYOUT : 0) |
((flags & (GPUPipeline::ColorFeedbackLoop | GPUPipeline::BindRenderTargetsAsImages)) ?
DIRTY_FLAG_INPUT_ATTACHMENT :
0);
}
// TODO: This could use vkCmdClearAttachments() instead.
@ -3285,11 +3334,14 @@ void VulkanDevice::BeginRenderPass()
// All textures should be in shader read only optimal already, but just in case..
const u32 num_textures = GetActiveTexturesForLayout(m_current_pipeline_layout);
for (u32 i = 0; i < num_textures; i++)
m_current_textures[i]->TransitionToLayout(VulkanTexture::Layout::ShaderReadOnly);
{
if (m_current_textures[i])
m_current_textures[i]->TransitionToLayout(VulkanTexture::Layout::ShaderReadOnly);
}
// NVIDIA drivers appear to return random garbage when sampling the RT via a feedback loop, if the load op for
// the render pass is CLEAR. Using vkCmdClearAttachments() doesn't work, so we have to clear the image instead.
if (m_current_feedback_loop & GPUPipeline::ColorFeedbackLoop)
if (m_current_render_pass_flags & GPUPipeline::ColorFeedbackLoop && IsDeviceNVIDIA())
{
for (u32 i = 0; i < m_num_current_render_targets; i++)
{
@ -3298,8 +3350,9 @@ void VulkanDevice::BeginRenderPass()
}
}
if (m_optional_extensions.vk_khr_dynamic_rendering && (m_optional_extensions.vk_khr_dynamic_rendering_local_read ||
!(m_current_feedback_loop & GPUPipeline::ColorFeedbackLoop)))
if (m_optional_extensions.vk_khr_dynamic_rendering &&
(m_optional_extensions.vk_khr_dynamic_rendering_local_read ||
!(m_current_render_pass_flags & GPUPipeline::ColorFeedbackLoop)))
{
VkRenderingInfoKHR ri = {
VK_STRUCTURE_TYPE_RENDERING_INFO_KHR, nullptr, 0u, {}, 1u, 0u, 0u, nullptr, nullptr, nullptr};
@ -3309,35 +3362,51 @@ void VulkanDevice::BeginRenderPass()
if (m_num_current_render_targets > 0 || m_current_depth_target)
{
ri.colorAttachmentCount = m_num_current_render_targets;
ri.pColorAttachments = (m_num_current_render_targets > 0) ? attachments.data() : nullptr;
// set up clear values and transition targets
for (u32 i = 0; i < m_num_current_render_targets; i++)
if (!(m_current_render_pass_flags & GPUPipeline::BindRenderTargetsAsImages))
{
VulkanTexture* const rt = static_cast<VulkanTexture*>(m_current_render_targets[i]);
rt->TransitionToLayout((m_current_feedback_loop & GPUPipeline::ColorFeedbackLoop) ?
VulkanTexture::Layout::FeedbackLoop :
VulkanTexture::Layout::ColorAttachment);
rt->SetUseFenceCounter(GetCurrentFenceCounter());
ri.colorAttachmentCount = m_num_current_render_targets;
ri.pColorAttachments = (m_num_current_render_targets > 0) ? attachments.data() : nullptr;
VkRenderingAttachmentInfo& ai = attachments[i];
ai.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
ai.pNext = nullptr;
ai.imageView = rt->GetView();
ai.imageLayout = rt->GetVkLayout();
ai.resolveMode = VK_RESOLVE_MODE_NONE_KHR;
ai.resolveImageView = VK_NULL_HANDLE;
ai.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
ai.loadOp = GetLoadOpForTexture(rt);
ai.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
if (rt->GetState() == GPUTexture::State::Cleared)
// set up clear values and transition targets
for (u32 i = 0; i < m_num_current_render_targets; i++)
{
std::memcpy(ai.clearValue.color.float32, rt->GetUNormClearColor().data(),
sizeof(ai.clearValue.color.float32));
VulkanTexture* const rt = static_cast<VulkanTexture*>(m_current_render_targets[i]);
rt->TransitionToLayout((m_current_render_pass_flags & GPUPipeline::ColorFeedbackLoop) ?
VulkanTexture::Layout::FeedbackLoop :
VulkanTexture::Layout::ColorAttachment);
rt->SetUseFenceCounter(GetCurrentFenceCounter());
VkRenderingAttachmentInfo& ai = attachments[i];
ai.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
ai.pNext = nullptr;
ai.imageView = rt->GetView();
ai.imageLayout = rt->GetVkLayout();
ai.resolveMode = VK_RESOLVE_MODE_NONE_KHR;
ai.resolveImageView = VK_NULL_HANDLE;
ai.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
ai.loadOp = GetLoadOpForTexture(rt);
ai.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
if (rt->GetState() == GPUTexture::State::Cleared)
{
std::memcpy(ai.clearValue.color.float32, rt->GetUNormClearColor().data(),
sizeof(ai.clearValue.color.float32));
}
rt->SetState(GPUTexture::State::Dirty);
}
}
else
{
// Binding as image, but we still need to clear it.
for (u32 i = 0; i < m_num_current_render_targets; i++)
{
VulkanTexture* rt = m_current_render_targets[i];
if (rt->GetState() == GPUTexture::State::Cleared)
rt->CommitClear(m_current_command_buffer);
rt->SetState(GPUTexture::State::Dirty);
rt->TransitionToLayout(VulkanTexture::Layout::ReadWriteImage);
rt->SetUseFenceCounter(GetCurrentFenceCounter());
}
rt->SetState(GPUTexture::State::Dirty);
}
if (VulkanTexture* const ds = m_current_depth_target)
@ -3396,8 +3465,9 @@ void VulkanDevice::BeginRenderPass()
if (m_current_framebuffer != VK_NULL_HANDLE)
{
bi.framebuffer = m_current_framebuffer;
bi.renderPass = m_current_render_pass = GetRenderPass(
m_current_render_targets.data(), m_num_current_render_targets, m_current_depth_target, m_current_feedback_loop);
bi.renderPass = m_current_render_pass =
GetRenderPass(m_current_render_targets.data(), m_num_current_render_targets, m_current_depth_target,
m_current_render_pass_flags);
if (bi.renderPass == VK_NULL_HANDLE)
{
ERROR_LOG("Failed to create render pass");
@ -3416,7 +3486,7 @@ void VulkanDevice::BeginRenderPass()
bi.clearValueCount = i + 1;
}
rt->SetState(GPUTexture::State::Dirty);
rt->TransitionToLayout((m_current_feedback_loop & GPUPipeline::ColorFeedbackLoop) ?
rt->TransitionToLayout((m_current_render_pass_flags & GPUPipeline::ColorFeedbackLoop) ?
VulkanTexture::Layout::FeedbackLoop :
VulkanTexture::Layout::ColorAttachment);
rt->SetUseFenceCounter(GetCurrentFenceCounter());
@ -3473,7 +3543,10 @@ void VulkanDevice::BeginSwapChainRenderPass()
// All textures should be in shader read only optimal already, but just in case..
const u32 num_textures = GetActiveTexturesForLayout(m_current_pipeline_layout);
for (u32 i = 0; i < num_textures; i++)
m_current_textures[i]->TransitionToLayout(VulkanTexture::Layout::ShaderReadOnly);
{
if (m_current_textures[i])
m_current_textures[i]->TransitionToLayout(VulkanTexture::Layout::ShaderReadOnly);
}
if (m_optional_extensions.vk_khr_dynamic_rendering)
{
@ -3518,15 +3591,16 @@ void VulkanDevice::BeginSwapChainRenderPass()
vkCmdBeginRenderPass(GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
}
m_dirty_flags |=
(m_current_render_pass_flags & (GPUPipeline::ColorFeedbackLoop | GPUPipeline::BindRenderTargetsAsImages)) ?
DIRTY_FLAG_PIPELINE_LAYOUT :
0;
s_stats.num_render_passes++;
m_num_current_render_targets = 0;
m_current_feedback_loop = GPUPipeline::NoRenderPassFlags;
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_current_depth_target = nullptr;
m_current_framebuffer = VK_NULL_HANDLE;
// Clear pipeline, it's likely incompatible.
m_current_pipeline = nullptr;
}
bool VulkanDevice::InRenderPass()
@ -3584,8 +3658,8 @@ void VulkanDevice::UnbindPipeline(VulkanPipeline* pl)
void VulkanDevice::InvalidateCachedState()
{
m_dirty_flags =
ALL_DIRTY_STATE | ((m_current_feedback_loop & GPUPipeline::ColorFeedbackLoop) ? DIRTY_FLAG_INPUT_ATTACHMENT : 0);
m_dirty_flags = ALL_DIRTY_STATE |
((m_current_render_pass_flags & GPUPipeline::ColorFeedbackLoop) ? DIRTY_FLAG_INPUT_ATTACHMENT : 0);
m_current_render_pass = VK_NULL_HANDLE;
m_current_pipeline = nullptr;
}
@ -3601,9 +3675,18 @@ s32 VulkanDevice::IsRenderTargetBoundIndex(const GPUTexture* tex) const
return -1;
}
VulkanDevice::PipelineLayoutType VulkanDevice::GetPipelineLayoutType(GPUPipeline::RenderPassFlag flags)
{
return (flags & GPUPipeline::BindRenderTargetsAsImages) ?
PipelineLayoutType::BindRenderTargetsAsImages :
((flags & GPUPipeline::ColorFeedbackLoop) ? PipelineLayoutType::ColorFeedbackLoop :
PipelineLayoutType::Normal);
}
VkPipelineLayout VulkanDevice::GetCurrentVkPipelineLayout() const
{
return m_pipeline_layouts[static_cast<u8>(m_current_pipeline_layout)];
return m_pipeline_layouts[static_cast<size_t>(GetPipelineLayoutType(m_current_render_pass_flags))]
[static_cast<size_t>(m_current_pipeline_layout)];
}
void VulkanDevice::SetInitialPipelineState()
@ -3634,7 +3717,7 @@ void VulkanDevice::SetInitialPipelineState()
void VulkanDevice::SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler* sampler)
{
VulkanTexture* T = texture ? static_cast<VulkanTexture*>(texture) : m_null_texture.get();
VulkanTexture* T = static_cast<VulkanTexture*>(texture);
const VkSampler vsampler = static_cast<VulkanSampler*>(sampler ? sampler : m_nearest_sampler.get())->GetSampler();
if (m_current_textures[slot] != T || m_current_samplers[slot] != vsampler)
{
@ -3643,7 +3726,7 @@ void VulkanDevice::SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler*
m_dirty_flags |= DIRTY_FLAG_TEXTURES_OR_SAMPLERS;
}
if (texture)
if (T)
{
T->CommitClear();
T->SetUseFenceCounter(GetCurrentFenceCounter());
@ -3673,7 +3756,7 @@ void VulkanDevice::UnbindTexture(VulkanTexture* tex)
{
if (m_current_textures[i] == tex)
{
m_current_textures[i] = m_null_texture.get();
m_current_textures[i] = nullptr;
m_dirty_flags |= DIRTY_FLAG_TEXTURES_OR_SAMPLERS;
}
}
@ -3754,7 +3837,7 @@ void VulkanDevice::PreDrawCheck()
BeginRenderPass();
DebugAssert(!(m_dirty_flags & DIRTY_FLAG_INITIAL));
const u32 update_mask = (m_current_feedback_loop ? ~0u : ~DIRTY_FLAG_INPUT_ATTACHMENT);
const u32 update_mask = (m_current_render_pass_flags ? ~0u : ~DIRTY_FLAG_INPUT_ATTACHMENT);
const u32 dirty = m_dirty_flags & update_mask;
m_dirty_flags = m_dirty_flags & ~update_mask;
@ -3774,6 +3857,7 @@ bool VulkanDevice::UpdateDescriptorSetsForLayout(u32 dirty)
{
[[maybe_unused]] bool new_dynamic_offsets = false;
VkPipelineLayout const vk_pipeline_layout = GetCurrentVkPipelineLayout();
std::array<VkDescriptorSet, 3> ds;
u32 first_ds = 0;
u32 num_ds = 0;
@ -3796,8 +3880,9 @@ bool VulkanDevice::UpdateDescriptorSetsForLayout(u32 dirty)
if constexpr (layout == GPUPipeline::Layout::SingleTextureAndUBO ||
layout == GPUPipeline::Layout::SingleTextureAndPushConstants)
{
DebugAssert(m_current_textures[0] && m_current_samplers[0] != VK_NULL_HANDLE);
ds[num_ds++] = m_current_textures[0]->GetDescriptorSetWithSampler(m_current_samplers[0]);
VulkanTexture* const tex = m_current_textures[0] ? m_current_textures[0] : m_null_texture.get();
DebugAssert(tex && m_current_samplers[0] != VK_NULL_HANDLE);
ds[num_ds++] = tex->GetDescriptorSetWithSampler(m_current_samplers[0]);
}
else if constexpr (layout == GPUPipeline::Layout::SingleTextureBufferAndPushConstants)
{
@ -3813,14 +3898,14 @@ bool VulkanDevice::UpdateDescriptorSetsForLayout(u32 dirty)
{
for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++)
{
DebugAssert(m_current_textures[i] && m_current_samplers[i] != VK_NULL_HANDLE);
dsub.AddCombinedImageSamplerDescriptorWrite(VK_NULL_HANDLE, i, m_current_textures[i]->GetView(),
m_current_samplers[i], m_current_textures[i]->GetVkLayout());
VulkanTexture* const tex = m_current_textures[i] ? m_current_textures[i] : m_null_texture.get();
DebugAssert(tex && m_current_samplers[i] != VK_NULL_HANDLE);
dsub.AddCombinedImageSamplerDescriptorWrite(VK_NULL_HANDLE, i, tex->GetView(), m_current_samplers[i],
tex->GetVkLayout());
}
const u32 set = (layout == GPUPipeline::Layout::MultiTextureAndUBO) ? 1 : 0;
dsub.PushUpdate(GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS,
m_pipeline_layouts[static_cast<u8>(m_current_pipeline_layout)], set);
dsub.PushUpdate(GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, vk_pipeline_layout, set);
if (num_ds == 0)
return true;
}
@ -3834,21 +3919,42 @@ bool VulkanDevice::UpdateDescriptorSetsForLayout(u32 dirty)
for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++)
{
DebugAssert(m_current_textures[i] && m_current_samplers[i] != VK_NULL_HANDLE);
dsub.AddCombinedImageSamplerDescriptorWrite(tds, i, m_current_textures[i]->GetView(), m_current_samplers[i],
m_current_textures[i]->GetVkLayout());
VulkanTexture* const tex = m_current_textures[i] ? m_current_textures[i] : m_null_texture.get();
DebugAssert(tex && m_current_samplers[i] != VK_NULL_HANDLE);
dsub.AddCombinedImageSamplerDescriptorWrite(tds, i, tex->GetView(), m_current_samplers[i], tex->GetVkLayout());
}
dsub.Update(m_device, false);
}
}
if constexpr (layout == GPUPipeline::Layout::SingleTextureAndUBO ||
layout == GPUPipeline::Layout::SingleTextureAndPushConstants ||
layout == GPUPipeline::Layout::SingleTextureBufferAndPushConstants)
if (m_num_current_render_targets > 0 &&
((dirty & DIRTY_FLAG_INPUT_ATTACHMENT) ||
(dirty & DIRTY_FLAG_PIPELINE_LAYOUT &&
(m_current_render_pass_flags & (GPUPipeline::ColorFeedbackLoop | GPUPipeline::BindRenderTargetsAsImages)))))
{
if ((dirty & DIRTY_FLAG_INPUT_ATTACHMENT) ||
(dirty & DIRTY_FLAG_PIPELINE_LAYOUT && (m_current_feedback_loop & GPUPipeline::ColorFeedbackLoop)))
if (m_current_render_pass_flags & GPUPipeline::BindRenderTargetsAsImages)
{
VkDescriptorSet ids = AllocateDescriptorSet(m_rov_ds_layout);
if (ids == VK_NULL_HANDLE)
return false;
ds[num_ds++] = ids;
Vulkan::DescriptorSetUpdateBuilder dsub;
for (u32 i = 0; i < m_num_current_render_targets; i++)
{
dsub.AddStorageImageDescriptorWrite(ids, i, m_current_render_targets[i]->GetView(),
m_current_render_targets[i]->GetVkLayout());
}
// Annoyingly, have to update all slots...
for (u32 i = m_num_current_render_targets; i < MAX_IMAGE_RENDER_TARGETS; i++)
dsub.AddStorageImageDescriptorWrite(ids, i, m_null_texture->GetView(), m_null_texture->GetVkLayout());
dsub.Update(m_device, false);
}
else
{
VkDescriptorSet ids = AllocateDescriptorSet(m_feedback_loop_ds_layout);
if (ids == VK_NULL_HANDLE)
@ -3864,9 +3970,8 @@ bool VulkanDevice::UpdateDescriptorSetsForLayout(u32 dirty)
}
DebugAssert(num_ds > 0);
vkCmdBindDescriptorSets(GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS,
m_pipeline_layouts[static_cast<u8>(m_current_pipeline_layout)], first_ds, num_ds, ds.data(),
static_cast<u32>(new_dynamic_offsets),
vkCmdBindDescriptorSets(GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, vk_pipeline_layout, first_ds,
num_ds, ds.data(), static_cast<u32>(new_dynamic_offsets),
new_dynamic_offsets ? &m_uniform_buffer_position : nullptr);
return true;