diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 7a71c68c08..481a4d8181 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -370,7 +370,7 @@ inline void PushImageDescriptors(TextureCache& texture_cache, const VkImageView null_image_view{texture_cache.GetImageView(VideoCommon::NULL_IMAGE_VIEW_ID).Handle(desc.type)}; if (null_image_view != VK_NULL_HANDLE) vk_image_view = null_image_view; } - const Sampler& sampler{texture_cache.GetSampler(sampler_id)}; + Sampler& sampler{texture_cache.GetSampler(sampler_id)}; const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && !image_view.SupportsAnisotropy()}; VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() @@ -383,8 +383,11 @@ inline void PushImageDescriptors(TextureCache& texture_cache, !image_view.SupportsDepthComparison()) { vk_sampler = sampler.HandleWithoutDepthComparison(); } - if (sampler.HasSrgbBorderColor() && - VideoCore::Surface::IsPixelFormatSRGB(image_view.format)) { + const bool srgb_border{sampler.HasSrgbBorderColor() && + VideoCore::Surface::IsPixelFormatSRGB(image_view.format)}; + if (sampler.NeedsSwizzleMapping() && !image_view.HasIdentitySwizzle()) { + vk_sampler = sampler.HandleWithSwizzle(image_view.Swizzle(), srgb_border); + } else if (srgb_border) { vk_sampler = sampler.HandleWithSrgbBorderColor(); } if (sampler.HasMinmaxReduction() && !image_view.SupportsMinmaxFilter()) { diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 69d3f745a8..2e37a99497 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2405,6 +2405,14 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT) != 0; } requires_border_color_format = NeedsExplicitBorderColorFormat(format_info.format); + swizzle_mapping = VkComponentMapping{ + .r = ComponentSwizzle(swizzle[0]), + .g = ComponentSwizzle(swizzle[1]), + .b = ComponentSwizzle(swizzle[2]), + .a = ComponentSwizzle(swizzle[3]), + }; + has_identity_swizzle = swizzle[0] == SwizzleSource::R && swizzle[1] == SwizzleSource::G && + swizzle[2] == SwizzleSource::B && swizzle[3] == SwizzleSource::A; const VkImageUsageFlags requested_view_usage = ImageUsageFlags(format_info, format); const VkImageUsageFlags image_usage = image.UsageFlags(); const VkImageUsageFlags clamped_view_usage = requested_view_usage & image_usage; @@ -2429,12 +2437,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI .image = image.Handle(), .viewType = VkImageViewType{}, .format = format_info.format, - .components{ - .r = ComponentSwizzle(swizzle[0]), - .g = ComponentSwizzle(swizzle[1]), - .b = ComponentSwizzle(swizzle[2]), - .a = ComponentSwizzle(swizzle[3]), - }, + .components = swizzle_mapping, .subresourceRange = MakeSubresourceRange(aspect_mask, info.range), }; const auto create = [&](TextureType tex_type, std::optional num_layers) { @@ -2671,6 +2674,33 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t min_filter == VK_FILTER_LINEAR || mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR}; + const auto make_create_info = [&](const f32 anisotropy, bool force_nearest, + bool disable_compare, const void* chain, + VkBorderColor fixed_border) { + return VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = chain, + .flags = 0, + .magFilter = force_nearest ? VK_FILTER_NEAREST : mag_filter, + .minFilter = force_nearest ? VK_FILTER_NEAREST : min_filter, + .mipmapMode = force_nearest ? VK_SAMPLER_MIPMAP_MODE_NEAREST : mipmap_mode, + .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), + .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), + .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), + .mipLodBias = tsc.LodBias(), + .anisotropyEnable = + static_cast(!force_nearest && anisotropy > 1.0f ? VK_TRUE : VK_FALSE), + .maxAnisotropy = force_nearest ? 1.0f : anisotropy, + .compareEnable = disable_compare ? VK_FALSE + : static_cast(tsc.depth_compare_enabled), + .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), + .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), + .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), + .borderColor = fixed_border, + .unnormalizedCoordinates = VK_FALSE, + }; + }; + const auto create_sampler = [&](const f32 anisotropy, bool force_nearest, bool disable_compare = false, bool disable_custom_border = false, @@ -2698,28 +2728,8 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t fixed_border = ConvertBorderColor(srgb_color); } } - return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = chain, - .flags = 0, - .magFilter = force_nearest ? VK_FILTER_NEAREST : mag_filter, - .minFilter = force_nearest ? VK_FILTER_NEAREST : min_filter, - .mipmapMode = force_nearest ? VK_SAMPLER_MIPMAP_MODE_NEAREST : mipmap_mode, - .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), - .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), - .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), - .mipLodBias = tsc.LodBias(), - .anisotropyEnable = - static_cast(!force_nearest && anisotropy > 1.0f ? VK_TRUE : VK_FALSE), - .maxAnisotropy = force_nearest ? 1.0f : anisotropy, - .compareEnable = disable_compare ? VK_FALSE - : static_cast(tsc.depth_compare_enabled), - .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), - .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), - .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), - .borderColor = fixed_border, - .unnormalizedCoordinates = VK_FALSE, - }); + return device.GetLogical().CreateSampler( + make_create_info(anisotropy, force_nearest, disable_compare, chain, fixed_border)); }; sampler = create_sampler(max_anisotropy, false); @@ -2744,6 +2754,60 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t sampler_srgb_border = create_sampler(max_anisotropy, false, false, false, false, true); } + needs_swizzle_mapping = has_custom_border_colors && device.NeedsBorderColorSwizzleMapping(); + if (needs_swizzle_mapping) { + device_ptr = &device; + border_color_value = color; + srgb_border_color_value = srgb_color; + swizzle_reduction_mode = reduction_ci.reductionMode; + swizzle_uses_reduction = has_minmax_reduction; + swizzle_base_ci = make_create_info(max_anisotropy, false, false, nullptr, + VK_BORDER_COLOR_FLOAT_CUSTOM_EXT); + } +} + +VkSampler Sampler::HandleWithSwizzle(const VkComponentMapping& mapping, bool srgb) { + const auto matches = [&](const SwizzleVariant& variant) { + return variant.srgb == srgb && variant.mapping.r == mapping.r && + variant.mapping.g == mapping.g && variant.mapping.b == mapping.b && + variant.mapping.a == mapping.a; + }; + const auto it = std::ranges::find_if(swizzle_variants, matches); + if (it != swizzle_variants.end()) { + return *it->sampler; + } + std::array value = border_color_value; + if (srgb) { + value = srgb_border_color_value; + } + const VkSamplerCustomBorderColorCreateInfoEXT border_ci{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT, + .pNext = nullptr, + .customBorderColor = std::bit_cast(value), + .format = VK_FORMAT_UNDEFINED, + }; + const VkSamplerBorderColorComponentMappingCreateInfoEXT mapping_ci{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_BORDER_COLOR_COMPONENT_MAPPING_CREATE_INFO_EXT, + .pNext = &border_ci, + .components = mapping, + .srgb = VK_FALSE, + }; + const VkSamplerReductionModeCreateInfoEXT reduction_ci{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, + .pNext = &mapping_ci, + .reductionMode = swizzle_reduction_mode, + }; + VkSamplerCreateInfo create_info = swizzle_base_ci; + create_info.pNext = &mapping_ci; + if (swizzle_uses_reduction) { + create_info.pNext = &reduction_ci; + } + swizzle_variants.push_back(SwizzleVariant{ + .mapping = mapping, + .srgb = srgb, + .sampler = device_ptr->GetLogical().CreateSampler(create_info), + }); + return *swizzle_variants.back().sampler; } Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span color_buffers, diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 0dfd87292d..68fcc1eac6 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -422,6 +422,14 @@ public: return supports_minmax_filter; } + [[nodiscard]] const VkComponentMapping& Swizzle() const noexcept { + return swizzle_mapping; + } + + [[nodiscard]] bool HasIdentitySwizzle() const noexcept { + return has_identity_swizzle; + } + [[nodiscard]] GPUVAddr GpuAddr() const noexcept { return gpu_addr; } @@ -454,9 +462,12 @@ private: VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; u32 buffer_size = 0; + VkComponentMapping swizzle_mapping{}; + bool supports_depth_comparison = false; bool requires_border_color_format = false; bool supports_minmax_filter = false; + bool has_identity_swizzle = true; }; class ImageAlloc : public VideoCommon::ImageAllocBase {}; @@ -517,7 +528,19 @@ public: return static_cast(sampler_srgb_border); } + [[nodiscard]] bool NeedsSwizzleMapping() const noexcept { + return needs_swizzle_mapping; + } + + [[nodiscard]] VkSampler HandleWithSwizzle(const VkComponentMapping& mapping, bool srgb); + private: + struct SwizzleVariant { + VkComponentMapping mapping; + bool srgb; + vk::Sampler sampler; + }; + vk::Sampler sampler; vk::Sampler sampler_default_anisotropy; vk::Sampler sampler_nearest; @@ -525,6 +548,15 @@ private: vk::Sampler sampler_default_border; vk::Sampler sampler_default_reduction; vk::Sampler sampler_srgb_border; + + std::vector swizzle_variants; + const Device* device_ptr = nullptr; + VkSamplerCreateInfo swizzle_base_ci{}; + VkSamplerReductionModeEXT swizzle_reduction_mode = VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT; + std::array border_color_value{}; + std::array srgb_border_color_value{}; + bool swizzle_uses_reduction = false; + bool needs_swizzle_mapping = false; }; struct TextureCacheParams { diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 09e7f88f71..092d985f4f 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1343,9 +1343,7 @@ void Device::RemoveUnsuitableExtensions() { // VK_EXT_border_color_swizzle if (extensions.border_color_swizzle) { extensions.border_color_swizzle = - extensions.custom_border_color && - features.border_color_swizzle.borderColorSwizzle && - features.border_color_swizzle.borderColorSwizzleFromImage; + extensions.custom_border_color && features.border_color_swizzle.borderColorSwizzle; } RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle, features.border_color_swizzle, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 45dc6587f2..7e40f8eeb6 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -724,6 +724,12 @@ FN_MAX_LIMIT_LIST return extensions.border_color_swizzle; } + /// Returns true if samplers must be carried with border color swizzle mapping. + bool NeedsBorderColorSwizzleMapping() const { + return extensions.border_color_swizzle && + !features.border_color_swizzle.borderColorSwizzleFromImage; + } + /// Returns true if borderColorSwizzleFromImage is available. bool IsBorderColorSwizzleFromImageSupported() const { return features.border_color_swizzle.borderColorSwizzleFromImage;