diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index d4165d8e4d..40183bed25 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -353,6 +353,10 @@ public: return buffer_bits; } + // No-op in OpenGL: Vulkan uses this to adjust attachment load ops. + void UpdateLoadOps(const std::array& /*discard_colors*/, bool /*discard_depth*/, + bool /*discard_stencil*/) {} + private: OGLFramebuffer framebuffer; GLbitfield buffer_bits = GL_NONE; diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index da5d0be80f..7c494dabd3 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -525,14 +525,12 @@ void BeginRenderPass(vk::CommandBuffer& cmdbuf, const Framebuffer* framebuffer) if (dst_access == 0) { continue; } + dst_access |= VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + dst_stage |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; barriers[barrier_count++] = VkImageMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, - .srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | - VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | - VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, .dstAccessMask = dst_access, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = layout, @@ -596,7 +594,7 @@ void EndRenderPass(vk::CommandBuffer& cmdbuf, const Framebuffer* framebuffer) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = src_access, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 6ab061873d..3c246e84be 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -175,6 +175,23 @@ public: std::array words{}; }; +inline bool OverlapsSubresource(const VideoCommon::SubresourceRange& view_range, + const VkImageSubresourceRange& fb_range) { + const u32 view_level_begin = static_cast(view_range.base.level); + const u32 view_level_end = view_level_begin + static_cast(view_range.extent.levels); + const u32 fb_level_begin = fb_range.baseMipLevel; + const u32 fb_level_end = fb_level_begin + fb_range.levelCount; + + const u32 view_layer_begin = static_cast(view_range.base.layer); + const u32 view_layer_end = view_layer_begin + static_cast(view_range.extent.layers); + const u32 fb_layer_begin = fb_range.baseArrayLayer; + const u32 fb_layer_end = fb_layer_begin + fb_range.layerCount; + + const bool levels_overlap = view_level_begin < fb_level_end && fb_level_begin < view_level_end; + const bool layers_overlap = view_layer_begin < fb_layer_end && fb_layer_begin < view_layer_end; + return levels_overlap && layers_overlap; +} + inline VkImageLayout DescriptorImageLayout(TextureCache& texture_cache, const Framebuffer* framebuffer, ImageView& image_view) { @@ -191,21 +208,19 @@ inline VkImageLayout DescriptorImageLayout(TextureCache& texture_cache, if (images[index] != image_handle) { continue; } - const VkImageAspectFlags aspect = ranges[index].aspectMask; - if (aspect & VK_IMAGE_ASPECT_COLOR_BIT) { + const VkImageSubresourceRange& fb_range = ranges[index]; + + // Only return feedback-loop layout if the descriptor's view overlaps the + // subresource range actually attached in the framebuffer. This avoids + // setting a layout that doesn't match the descriptor's subresource. + static constexpr VkImageAspectFlags FeedbackAspects = + VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + if ((fb_range.aspectMask & FeedbackAspects) == 0) { + continue; + } + if (OverlapsSubresource(image_view.range, fb_range)) { return VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; } - const bool has_depth = (aspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; - const bool has_stencil = (aspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; - if (has_depth && has_stencil) { - return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; - } - if (has_depth) { - return VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; - } - if (has_stencil) { - return VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; - } } return VK_IMAGE_LAYOUT_GENERAL; } diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index f395f7ffa4..92b3d09baa 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -44,6 +44,7 @@ using Tegra::Texture::TexturePair; using VideoCore::Surface::PixelFormat; using VideoCore::Surface::PixelFormatFromDepthFormat; using VideoCore::Surface::PixelFormatFromRenderTargetFormat; +using VideoCore::Surface::GetFormatType; constexpr size_t NUM_STAGES = Maxwell::MaxShaderStage; constexpr size_t MAX_IMAGE_ELEMENTS = 64; @@ -123,13 +124,33 @@ PixelFormat DecodeFormat(u8 encoded_format) { } RenderPassKey MakeRenderPassKey(const FixedPipelineState& state) { - RenderPassKey key; + RenderPassKey key{}; std::ranges::transform(state.color_formats, key.color_formats.begin(), DecodeFormat); + for (size_t index = 0; index < key.color_formats.size(); ++index) { + if (key.color_formats[index] != PixelFormat::Invalid) { + key.color_load_ops[index] = VK_ATTACHMENT_LOAD_OP_LOAD; + key.color_store_ops[index] = VK_ATTACHMENT_STORE_OP_STORE; + } else { + key.color_load_ops[index] = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + key.color_store_ops[index] = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + } if (state.depth_enabled != 0) { const auto depth_format{static_cast(state.depth_format.Value())}; key.depth_format = PixelFormatFromDepthFormat(depth_format); + key.depth_load_op = VK_ATTACHMENT_LOAD_OP_LOAD; + key.depth_store_op = VK_ATTACHMENT_STORE_OP_STORE; + const auto depth_type = GetFormatType(key.depth_format); + const bool has_stencil = depth_type == VideoCore::Surface::SurfaceType::DepthStencil || + depth_type == VideoCore::Surface::SurfaceType::Stencil; + key.stencil_load_op = has_stencil ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE; + key.stencil_store_op = has_stencil ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; } else { key.depth_format = PixelFormat::Invalid; + key.depth_load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + key.depth_store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE; + key.stencil_load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + key.stencil_store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE; } key.samples = MaxwellToVK::MsaaMode(state.msaa_mode); return key; diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp index 5be5cbad04..8bddb455c9 100644 --- a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp @@ -14,6 +14,7 @@ #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" + namespace Vulkan { namespace { using VideoCore::Surface::PixelFormat; @@ -43,42 +44,60 @@ using VideoCore::Surface::SurfaceType; } } - VkImageLayout AttachmentLayout(const Device& device, SurfaceType surface_type) { - if (!device.SupportsAttachmentFeedbackLoopLayout()) { - return VK_IMAGE_LAYOUT_GENERAL; - } - switch (surface_type) { - case SurfaceType::ColorTexture: - return VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; - case SurfaceType::Depth: - return VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; - case SurfaceType::Stencil: - return VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; - case SurfaceType::DepthStencil: - return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; - default: - return VK_IMAGE_LAYOUT_GENERAL; - } + VkImageLayout AttachmentLayout(const Device& device, SurfaceType surface_type, bool want_feedback_loop) { + if (want_feedback_loop && device.SupportsAttachmentFeedbackLoopLayout()) { + // Single layout works for color and depth/stencil images. + return VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; + } + + // Normal (non-feedback) attachment layouts + switch (surface_type) { + case SurfaceType::ColorTexture: + return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + case SurfaceType::Depth: + return device.SupportsSeparateDepthStencilLayouts() + ? VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL + : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + case SurfaceType::Stencil: + return device.SupportsSeparateDepthStencilLayouts() + ? VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL + : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + case SurfaceType::DepthStencil: + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + default: + return VK_IMAGE_LAYOUT_GENERAL; // last-resort fallback } + } VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat format, - VkSampleCountFlagBits samples) { + VkSampleCountFlagBits samples, + VkAttachmentLoadOp load_op, + VkAttachmentStoreOp store_op, + VkAttachmentLoadOp stencil_load_op, + VkAttachmentStoreOp stencil_store_op, + bool want_feedback_loop) { using MaxwellToVK::SurfaceFormat; const SurfaceType surface_type = GetSurfaceType(format); const bool has_stencil = surface_type == SurfaceType::DepthStencil || surface_type == SurfaceType::Stencil; - const VkImageLayout layout = AttachmentLayout(device, surface_type); + const VkImageLayout layout = AttachmentLayout(device, surface_type, want_feedback_loop); + const VkAttachmentLoadOp resolved_stencil_load = + has_stencil ? stencil_load_op : VK_ATTACHMENT_LOAD_OP_DONT_CARE; + const VkAttachmentStoreOp resolved_stencil_store = + has_stencil ? stencil_store_op : VK_ATTACHMENT_STORE_OP_DONT_CARE; return { .flags = {}, .format = SurfaceFormat(device, FormatType::Optimal, true, format).format, .samples = samples, - .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, - .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .stencilLoadOp = has_stencil ? VK_ATTACHMENT_LOAD_OP_LOAD - : VK_ATTACHMENT_LOAD_OP_DONT_CARE, - .stencilStoreOp = has_stencil ? VK_ATTACHMENT_STORE_OP_STORE - : VK_ATTACHMENT_STORE_OP_DONT_CARE, + .loadOp = load_op, + .storeOp = store_op, + .stencilLoadOp = resolved_stencil_load, + .stencilStoreOp = resolved_stencil_store, .initialLayout = layout, .finalLayout = layout, }; @@ -97,6 +116,7 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { std::array references{}; u32 num_attachments{}; u32 num_colors{}; + const bool supports_feedback_loop = device->SupportsAttachmentFeedbackLoopLayout(); for (size_t index = 0; index < key.color_formats.size(); ++index) { const PixelFormat format{key.color_formats[index]}; if (format == PixelFormat::Invalid) { @@ -108,12 +128,17 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { } const SurfaceType surface_type = GetSurfaceType(format); - const VkImageLayout layout = AttachmentLayout(*device, surface_type); + const VkImageLayout layout = AttachmentLayout(*device, surface_type, supports_feedback_loop); references[index] = VkAttachmentReference{ .attachment = num_colors, .layout = layout, }; - descriptions.push_back(AttachmentDescription(*device, format, key.samples)); + const VkAttachmentLoadOp load_op = key.color_load_ops[index]; + const VkAttachmentStoreOp store_op = key.color_store_ops[index]; + descriptions.push_back(AttachmentDescription(*device, format, key.samples, load_op, store_op, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_DONT_CARE, + supports_feedback_loop)); num_attachments = static_cast(index + 1); ++num_colors; } @@ -122,18 +147,18 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { VkAttachmentReference depth_reference{}; if (has_depth) { const SurfaceType depth_type = GetSurfaceType(key.depth_format); - const VkImageLayout depth_layout = AttachmentLayout(*device, depth_type); + const VkImageLayout depth_layout = AttachmentLayout(*device, depth_type, supports_feedback_loop); depth_reference = VkAttachmentReference{ .attachment = num_colors, .layout = depth_layout, }; - descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples)); + descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples, + key.depth_load_op, key.depth_store_op, + key.stencil_load_op, key.stencil_store_op, + supports_feedback_loop)); } - const bool supports_feedback_loop = device->SupportsAttachmentFeedbackLoopLayout(); const VkSubpassDescription subpass{ - .flags = supports_feedback_loop - ? VK_SUBPASS_DESCRIPTION_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT - : 0u, + .flags = 0u, .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .inputAttachmentCount = 0, .pInputAttachments = nullptr, @@ -144,6 +169,11 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .preserveAttachmentCount = 0, .pPreserveAttachments = nullptr, }; + + VkDependencyFlags dependency_flags = VK_DEPENDENCY_BY_REGION_BIT; + if (supports_feedback_loop) { + dependency_flags |= VK_DEPENDENCY_FEEDBACK_LOOP_BIT_EXT; + } const VkSubpassDependency dependency{ .srcSubpass = 0, // Current subpass .dstSubpass = 0, // Same subpass (self-dependency) @@ -153,9 +183,11 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, + .dependencyFlags = dependency_flags }; + const VkSubpassDependency* dependency_ptr = supports_feedback_loop ? &dependency : nullptr; + const u32 dependency_count = supports_feedback_loop ? 1u : 0u; pair->second = device->GetLogical().CreateRenderPass({ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .pNext = nullptr, @@ -164,10 +196,11 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .pAttachments = descriptions.empty() ? nullptr : descriptions.data(), .subpassCount = 1, .pSubpasses = &subpass, - .dependencyCount = supports_feedback_loop ? 1u : 0u, - .pDependencies = supports_feedback_loop ? &dependency : nullptr, + .dependencyCount = dependency_count, + .pDependencies = dependency_ptr, }); return *pair->second; } } // namespace Vulkan + diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.h b/src/video_core/renderer_vulkan/vk_render_pass_cache.h index 91ad4bf577..1866353c31 100644 --- a/src/video_core/renderer_vulkan/vk_render_pass_cache.h +++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.h @@ -5,7 +5,9 @@ #include #include +#include +#include "common/container_hash.h" #include "video_core/surface.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -15,7 +17,13 @@ struct RenderPassKey { bool operator==(const RenderPassKey&) const noexcept = default; std::array color_formats; + std::array color_load_ops; + std::array color_store_ops; VideoCore::Surface::PixelFormat depth_format; + VkAttachmentLoadOp depth_load_op; + VkAttachmentStoreOp depth_store_op; + VkAttachmentLoadOp stencil_load_op; + VkAttachmentStoreOp stencil_store_op; VkSampleCountFlagBits samples; }; @@ -25,11 +33,18 @@ namespace std { template <> struct hash { [[nodiscard]] size_t operator()(const Vulkan::RenderPassKey& key) const noexcept { - size_t value = static_cast(key.depth_format) << 48; - value ^= static_cast(key.samples) << 52; + size_t value = 0; for (size_t i = 0; i < key.color_formats.size(); ++i) { - value ^= static_cast(key.color_formats[i]) << (i * 6); + Common::HashCombine(value, static_cast(key.color_formats[i])); + Common::HashCombine(value, static_cast(key.color_load_ops[i])); + Common::HashCombine(value, static_cast(key.color_store_ops[i])); } + Common::HashCombine(value, static_cast(key.depth_format)); + Common::HashCombine(value, static_cast(key.depth_load_op)); + Common::HashCombine(value, static_cast(key.depth_store_op)); + Common::HashCombine(value, static_cast(key.stencil_load_op)); + Common::HashCombine(value, static_cast(key.stencil_store_op)); + Common::HashCombine(value, static_cast(key.samples)); return value; } }; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index b7f01ecd26..32e7b96b15 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -108,62 +108,62 @@ void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) { renderpass_image_ranges = framebuffer->ImageRanges(); renderpass_image_layouts = framebuffer->ImageLayouts(); - if (device.SupportsAttachmentFeedbackLoopLayout()) { - Record([num_images = num_renderpass_images, images = renderpass_images, - ranges = renderpass_image_ranges, - layouts = renderpass_image_layouts](vk::CommandBuffer cmdbuf) { - std::array barriers{}; - u32 barrier_count = 0; - VkPipelineStageFlags dst_stages = 0; + // Transition images into their render-pass attachment layouts when needed. + Record([num_images = num_renderpass_images, images = renderpass_images, + ranges = renderpass_image_ranges, + layouts = renderpass_image_layouts](vk::CommandBuffer cmdbuf) { + std::array barriers{}; + u32 barrier_count = 0; + VkPipelineStageFlags dst_stages = 0; - for (size_t i = 0; i < num_images; ++i) { - const VkImageLayout layout = layouts[i]; - if (layout == VK_IMAGE_LAYOUT_GENERAL) { - continue; - } - const VkImageSubresourceRange& range = ranges[i]; - VkAccessFlags dst_access = 0; - VkPipelineStageFlags dst_stage = 0; - if ((range.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { - dst_access |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - dst_stage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - } - if ((range.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { - dst_access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - dst_stage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - } - if (dst_access == 0) { - continue; - } - barriers[barrier_count++] = VkImageMemoryBarrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | - VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | - VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = dst_access, - .oldLayout = layout, - .newLayout = layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = images[i], - .subresourceRange = range, - }; - dst_stages |= dst_stage; + for (size_t i = 0; i < num_images; ++i) { + const VkImageLayout layout = layouts[i]; + if (layout == VK_IMAGE_LAYOUT_GENERAL) { + continue; } - - if (barrier_count == 0) { - return; + const VkImageSubresourceRange& range = ranges[i]; + VkAccessFlags dst_access = 0; + VkPipelineStageFlags dst_stage = 0; + if ((range.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { + dst_access |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dst_stage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; } + if ((range.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { + dst_access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + dst_stage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + } + if (dst_access == 0) { + continue; + } + // If transitioning to feedback-loop layout, also make shader reads available. + if (layout == VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT) { + dst_access |= VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + dst_stage |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + barriers[barrier_count++] = VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = dst_access, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = images[i], + .subresourceRange = range, + }; + dst_stages |= dst_stage; + } - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, dst_stages, 0, {}, {}, - {barriers.data(), barrier_count}); - }); - } + if (barrier_count == 0) { + return; + } + + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, dst_stages, 0, {}, {}, + {barriers.data(), barrier_count}); + }); Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) { const VkRenderPassBeginInfo renderpass_bi{ @@ -373,7 +373,7 @@ void Scheduler::EndRenderPass() .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = src_access, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT @@ -417,5 +417,3 @@ void Scheduler::AcquireNewChunk() { } } // namespace Vulkan - - diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 584c25e570..43f12089c9 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -46,6 +46,7 @@ using VideoCore::Surface::HasAlpha; using VideoCore::Surface::IsPixelFormatASTC; using VideoCore::Surface::IsPixelFormatInteger; using VideoCore::Surface::SurfaceType; +using VideoCore::Surface::GetFormatType; namespace { constexpr VkBorderColor ConvertBorderColor(const std::array& color) { @@ -127,22 +128,32 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { [[nodiscard]] VkImageLayout AttachmentFeedbackLoopLayout(const Device& device, VkImageAspectFlags aspect_mask) { - if (!device.SupportsAttachmentFeedbackLoopLayout()) { + if (device.SupportsAttachmentFeedbackLoopLayout()) { + static constexpr VkImageAspectFlags kSupportedAspects = VK_IMAGE_ASPECT_COLOR_BIT | + VK_IMAGE_ASPECT_DEPTH_BIT | + VK_IMAGE_ASPECT_STENCIL_BIT; + if ((aspect_mask & kSupportedAspects) != 0) { + return VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; + } return VK_IMAGE_LAYOUT_GENERAL; } + // No feedback-loop extension: return optimal attachment layouts if ((aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { - return VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; + return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } + const bool sep = device.SupportsSeparateDepthStencilLayouts(); const bool has_depth = (aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; const bool has_stencil = (aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; if (has_depth && has_stencil) { - return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } if (has_depth) { - return VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; + return sep ? VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL + : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } if (has_stencil) { - return VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; + return sep ? VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL + : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } return VK_IMAGE_LAYOUT_GENERAL; } @@ -2318,11 +2329,13 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, std::span color_buffers, ImageView* depth_buffer, bool is_rescaled_) { boost::container::small_vector attachments; - RenderPassKey renderpass_key{}; s32 num_layers = 1; num_images = 0; num_color_buffers = 0; image_layouts.fill(VK_IMAGE_LAYOUT_GENERAL); + this->runtime = &runtime; + color_formats.fill(PixelFormat::Invalid); + depth_format = PixelFormat::Invalid; is_rescaled = is_rescaled_; const auto& resolution = runtime.resolution; @@ -2332,7 +2345,7 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, for (size_t index = 0; index < NUM_RT; ++index) { const ImageView* const color_buffer = color_buffers[index]; if (!color_buffer) { - renderpass_key.color_formats[index] = PixelFormat::Invalid; + color_formats[index] = PixelFormat::Invalid; continue; } width = (std::min)(width, is_rescaled ? resolution.ScaleUp(color_buffer->size.width) @@ -2340,7 +2353,7 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, height = (std::min)(height, is_rescaled ? resolution.ScaleUp(color_buffer->size.height) : color_buffer->size.height); attachments.push_back(color_buffer->RenderTarget()); - renderpass_key.color_formats[index] = color_buffer->format; + color_formats[index] = color_buffer->format; num_layers = (std::max)(num_layers, color_buffer->range.extent.layers); images[num_images] = color_buffer->ImageHandle(); const VkImageSubresourceRange subresource_range = MakeSubresourceRange(color_buffer); @@ -2358,7 +2371,7 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, height = (std::min)(height, is_rescaled ? resolution.ScaleUp(depth_buffer->size.height) : depth_buffer->size.height); attachments.push_back(depth_buffer->RenderTarget()); - renderpass_key.depth_format = depth_buffer->format; + depth_format = depth_buffer->format; num_layers = (std::max)(num_layers, depth_buffer->range.extent.layers); images[num_images] = depth_buffer->ImageHandle(); const VkImageSubresourceRange subresource_range = MakeSubresourceRange(depth_buffer); @@ -2370,11 +2383,10 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, has_depth = (subresource_range.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; has_stencil = (subresource_range.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; } else { - renderpass_key.depth_format = PixelFormat::Invalid; + depth_format = PixelFormat::Invalid; } - renderpass_key.samples = samples; - - renderpass = runtime.render_pass_cache.Get(renderpass_key); + std::array no_discard{}; + UpdateLoadOps(no_discard, false, false); render_area.width = (std::min)(render_area.width, width); render_area.height = (std::min)(render_area.height, height); @@ -2392,6 +2404,64 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, }); } +RenderPassKey Framebuffer::BuildRenderPassKey() const { + RenderPassKey key{}; + key.color_formats = color_formats; + key.color_load_ops = color_load_ops; + key.color_store_ops = color_store_ops; + key.depth_format = depth_format; + key.depth_load_op = depth_load_op; + key.depth_store_op = depth_store_op; + key.stencil_load_op = stencil_load_op; + key.stencil_store_op = stencil_store_op; + key.samples = samples; + return key; +} + +void Framebuffer::UpdateLoadOps(const std::array& discard_colors, bool discard_depth, + bool discard_stencil) { + for (size_t index = 0; index < NUM_RT; ++index) { + if (color_formats[index] != PixelFormat::Invalid) { + color_load_ops[index] = discard_colors[index] ? VK_ATTACHMENT_LOAD_OP_DONT_CARE + : VK_ATTACHMENT_LOAD_OP_LOAD; + color_store_ops[index] = VK_ATTACHMENT_STORE_OP_STORE; + } else { + color_load_ops[index] = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_store_ops[index] = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + } + + if (depth_format != PixelFormat::Invalid) { + const auto surface_type = GetFormatType(depth_format); + const bool has_depth_aspect = surface_type == SurfaceType::Depth || + surface_type == SurfaceType::DepthStencil; + const bool has_stencil_aspect = surface_type == SurfaceType::Stencil || + surface_type == SurfaceType::DepthStencil; + if (has_depth_aspect) { + depth_load_op = discard_depth ? VK_ATTACHMENT_LOAD_OP_DONT_CARE : VK_ATTACHMENT_LOAD_OP_LOAD; + depth_store_op = VK_ATTACHMENT_STORE_OP_STORE; + } else { + depth_load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depth_store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + if (has_stencil_aspect) { + stencil_load_op = discard_stencil ? VK_ATTACHMENT_LOAD_OP_DONT_CARE + : VK_ATTACHMENT_LOAD_OP_LOAD; + stencil_store_op = VK_ATTACHMENT_STORE_OP_STORE; + } else { + stencil_load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + stencil_store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + } else { + depth_load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depth_store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE; + stencil_load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + stencil_store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + + renderpass = runtime->render_pass_cache.Get(BuildRenderPassKey()); +} + void TextureCacheRuntime::AccelerateImageUpload( Image& image, const StagingBufferRef& map, std::span swizzles) { diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index d707011ce2..34fedbeec1 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -4,8 +4,10 @@ #pragma once #include +#include #include "video_core/texture_cache/texture_cache_base.h" +#include "video_core/renderer_vulkan/vk_render_pass_cache.h" #include "shader_recompiler/shader_info.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" @@ -318,6 +320,9 @@ public: ~Framebuffer(); + void UpdateLoadOps(const std::array& discard_colors, bool discard_depth, + bool discard_stencil); + Framebuffer(const Framebuffer&) = delete; Framebuffer& operator=(const Framebuffer&) = delete; @@ -391,9 +396,20 @@ private: std::array image_ranges{}; std::array image_layouts{}; std::array rt_map{}; + TextureCacheRuntime* runtime{}; + std::array color_formats{}; + PixelFormat depth_format{PixelFormat::Invalid}; + std::array color_load_ops{}; + std::array color_store_ops{}; + VkAttachmentLoadOp depth_load_op{VK_ATTACHMENT_LOAD_OP_DONT_CARE}; + VkAttachmentStoreOp depth_store_op{VK_ATTACHMENT_STORE_OP_DONT_CARE}; + VkAttachmentLoadOp stencil_load_op{VK_ATTACHMENT_LOAD_OP_DONT_CARE}; + VkAttachmentStoreOp stencil_store_op{VK_ATTACHMENT_STORE_OP_DONT_CARE}; bool has_depth{}; bool has_stencil{}; bool is_rescaled{}; + + [[nodiscard]] RenderPassKey BuildRenderPassKey() const; }; struct TextureCacheParams { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index e5d559b591..03bd49b44e 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -424,13 +424,43 @@ template void TextureCache

::UpdateRenderTargets(bool is_clear) { using namespace VideoCommon::Dirty; auto& flags = maxwell3d->dirty.flags; + + discard_color_on_load.fill(false); + discard_depth_on_load = false; + discard_stencil_on_load = false; + + const auto& regs = maxwell3d->regs; + const bool clear_color = is_clear && (regs.clear_surface.R || regs.clear_surface.G || + regs.clear_surface.B || regs.clear_surface.A); + const u32 clear_rt = regs.clear_surface.RT; + const bool clear_depth = is_clear && regs.clear_surface.Z; + const bool clear_stencil = is_clear && regs.clear_surface.S; + + const auto prepare_color = [&](size_t index, ImageViewId& color_buffer_id) { + const bool has_attachment = static_cast(color_buffer_id); + const bool full_clear = has_attachment ? IsFullClear(color_buffer_id) : true; + const bool valid_target = clear_rt < NUM_RT; + const bool target_clear = clear_color && valid_target && clear_rt == index && has_attachment; + discard_color_on_load[index] = !has_attachment || (target_clear && full_clear); + PrepareImageView(color_buffer_id, true, target_clear && full_clear); + }; + + const auto prepare_depth = [&](ImageViewId depth_buffer_id) { + const bool has_attachment = static_cast(depth_buffer_id); + const bool full_clear = has_attachment ? IsFullClear(depth_buffer_id) : true; + discard_depth_on_load = !has_attachment || (clear_depth && full_clear); + discard_stencil_on_load = !has_attachment || (clear_stencil && full_clear); + const bool invalidate = has_attachment && full_clear && (clear_depth || clear_stencil); + PrepareImageView(depth_buffer_id, true, invalidate); + }; + if (!flags[Dirty::RenderTargets]) { for (size_t index = 0; index < NUM_RT; ++index) { ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; - PrepareImageView(color_buffer_id, true, is_clear && IsFullClear(color_buffer_id)); + prepare_color(index, color_buffer_id); } const ImageViewId depth_buffer_id = render_targets.depth_buffer_id; - PrepareImageView(depth_buffer_id, true, is_clear && IsFullClear(depth_buffer_id)); + prepare_depth(depth_buffer_id); return; } @@ -443,11 +473,11 @@ void TextureCache

::UpdateRenderTargets(bool is_clear) { for (size_t index = 0; index < NUM_RT; ++index) { ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; - PrepareImageView(color_buffer_id, true, is_clear && IsFullClear(color_buffer_id)); + prepare_color(index, color_buffer_id); } const ImageViewId depth_buffer_id = render_targets.depth_buffer_id; - PrepareImageView(depth_buffer_id, true, is_clear && IsFullClear(depth_buffer_id)); + prepare_depth(depth_buffer_id); for (size_t index = 0; index < NUM_RT; ++index) { render_targets.draw_buffers[index] = static_cast(maxwell3d->regs.rt_control.Map(index)); @@ -469,7 +499,9 @@ void TextureCache

::UpdateRenderTargets(bool is_clear) { template typename P::Framebuffer* TextureCache

::GetFramebuffer() { - return &slot_framebuffers[GetFramebufferId(render_targets)]; + auto* framebuffer = &slot_framebuffers[GetFramebufferId(render_targets)]; + framebuffer->UpdateLoadOps(discard_color_on_load, discard_depth_on_load, discard_stencil_on_load); + return framebuffer; } template diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 73900ef9d0..67b2dd86ef 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -439,6 +440,9 @@ private: std::deque gpu_page_table_storage; RenderTargets render_targets; + std::array discard_color_on_load{}; + bool discard_depth_on_load{}; + bool discard_stencil_on_load{}; std::unordered_map framebuffers; diff --git a/src/video_core/vulkan_common/vulkan.h b/src/video_core/vulkan_common/vulkan.h index 2cffb25411..62aa132915 100644 --- a/src/video_core/vulkan_common/vulkan.h +++ b/src/video_core/vulkan_common/vulkan.h @@ -17,16 +17,6 @@ #include -#ifndef VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT -#define VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT -#endif -#ifndef VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT -#define VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT -#endif -#ifndef VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT -#define VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT -#endif - // Sanitize macros #undef CreateEvent #undef CreateSemaphore diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 3cff3cfaa5..5cd9920683 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1220,6 +1220,13 @@ void Device::RemoveUnsuitableExtensions() { features.attachment_feedback_loop_layout, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME); + // VK_KHR_separate_depth_stencil_layouts + extensions.separate_depth_stencil_layouts = + features.separate_depth_stencil_layouts.separateDepthStencilLayouts; + RemoveExtensionFeatureIfUnsuitable(extensions.separate_depth_stencil_layouts, + features.separate_depth_stencil_layouts, + VK_KHR_SEPARATE_DEPTH_STENCIL_LAYOUTS_EXTENSION_NAME); + /* */ // VK_EXT_extended_dynamic_state extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d3a4bb0a96..8d44584e3d 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -34,7 +34,9 @@ VK_DEFINE_HANDLE(VmaAllocator) #define FOR_EACH_VK_FEATURE_1_2(FEATURE) \ FEATURE(EXT, HostQueryReset, HOST_QUERY_RESET, host_query_reset) \ FEATURE(KHR, 8BitStorage, 8BIT_STORAGE, bit8_storage) \ - FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore) + FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore) \ + FEATURE(KHR, SeparateDepthStencilLayouts, SEPARATE_DEPTH_STENCIL_LAYOUTS, \ + separate_depth_stencil_layouts) #define FOR_EACH_VK_FEATURE_1_3(FEATURE) \ FEATURE(EXT, ShaderDemoteToHelperInvocation, SHADER_DEMOTE_TO_HELPER_INVOCATION, \ @@ -569,6 +571,11 @@ public: features.attachment_feedback_loop_layout.attachmentFeedbackLoopLayout; } + bool SupportsSeparateDepthStencilLayouts() const { + return extensions.separate_depth_stencil_layouts && + features.separate_depth_stencil_layouts.separateDepthStencilLayouts; + } + /// Returns true if the device supports VK_EXT_vertex_input_dynamic_state. bool IsExtVertexInputDynamicStateSupported() const {