From 0295dc5fff9b2977e753e7c126cc870abb07ee3f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 25 Aug 2026 20:03:18 +0200 Subject: [PATCH] [vulkan] Removal of QCOM sampler limiters + CustomBorderColor and ColorBorderSwizzle adjustments (#4301) This PR removes the artificial limit added for QCOM drivers based on a sampler budget limit (based on #3280 work), removes the ban on CustomBorderColor/BorderColorSwizzle for also QCOM driver, reduce the amount of combination required to created a sampler with custom border color/ color border swizzle; adds cases on R16 formats not able to swizzle on BGR565 formats; degrades samplers and color combinations when there's no color border swizzle available; fixes the srgb (LUT) values from legacy and now all the color variations are cached in the same key, instead of having 7 different handlers for them. In resume, rather than performance (due to the reduced amount of process on duplicated/ synced code on pipeline/texture cache), it's a graphica accuracy work that will improve image quality at the cost of almost 0 performance hit, rather than certain hitch if the color combination wasn't cached before. This applies to all platform. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4301 Reviewed-by: Samuel Reviewed-by: MaranBr --- .../renderer_vulkan/pipeline_helper.h | 17 +- .../renderer_vulkan/vk_texture_cache.cpp | 302 +++++++++++++----- .../renderer_vulkan/vk_texture_cache.h | 117 +++++-- src/video_core/texture_cache/texture_cache.h | 57 ---- .../texture_cache/texture_cache_base.h | 4 - src/video_core/textures/texture.cpp | 53 +-- src/video_core/textures/texture.h | 5 + .../vulkan_common/vulkan_device.cpp | 53 ++- src/video_core/vulkan_common/vulkan_device.h | 32 +- 9 files changed, 376 insertions(+), 264 deletions(-) diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 051305ec42..07de25f18f 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -370,20 +370,9 @@ 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)}; - const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && - !image_view.SupportsAnisotropy()}; - VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() - : sampler.Handle()}; - if (sampler.HasLinearFiltering() && - VideoCore::Surface::IsPixelFormatInteger(image_view.format)) { - vk_sampler = sampler.HandleWithNearestFilter(); - } - if (desc.is_depth && sampler.HasDepthComparison() && - !image_view.SupportsDepthComparison()) { - vk_sampler = sampler.HandleWithoutDepthComparison(); - } - guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); + Sampler& sampler{texture_cache.GetSampler(sampler_id)}; + guest_descriptor_queue.AddSampledImage(vk_image_view, + sampler.HandleFor(image_view, desc.is_depth)); const bool element_rescaled{texture_cache.IsRescaling(image_view)}; is_rescaled |= element_rescaled; } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 0a80d03ef5..c3f56408b5 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -51,6 +51,7 @@ using VideoCore::Surface::BytesPerBlock; using VideoCore::Surface::HasAlpha; using VideoCore::Surface::IsPixelFormatASTC; using VideoCore::Surface::IsPixelFormatInteger; +using VideoCore::Surface::IsPixelFormatSRGB; using VideoCore::Surface::SurfaceType; namespace { @@ -59,6 +60,17 @@ constexpr bool ENABLE_MSAA_RESOLVE_CONSUME = true; constexpr bool ENABLE_MSAA_COLOR_DISCARD = true; constexpr bool ENABLE_MSAA_DEPTH_STENCIL_DISCARD = true; +[[nodiscard]] constexpr bool NeedsExplicitBorderColorFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: + case VK_FORMAT_B5G6R5_UNORM_PACK16: + case VK_FORMAT_B5G5R5A1_UNORM_PACK16: + return true; + default: + return false; + } +} + constexpr VkBorderColor ConvertBorderColor(const std::array& color) { if (color == std::array{0, 0, 0, 0}) { return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; @@ -1802,10 +1814,6 @@ bool TextureCacheRuntime::CanReportMemoryUsage() const { return device.CanReportMemoryUsage(); } -std::optional TextureCacheRuntime::GetSamplerHeapBudget() const { - return device.GetSamplerHeapBudget(); -} - void TextureCacheRuntime::FlushDeferredClear() { scheduler.FlushDeferredClear(); } @@ -2517,9 +2525,23 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI supports_depth_comparison = (properties3.optimalTilingFeatures & VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT) != 0; + supports_minmax_filter = (properties3.optimalTilingFeatures & + VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_MINMAX_BIT) != 0; } else { supports_depth_comparison = true; + supports_minmax_filter = + (device->GetPhysical().GetFormatProperties(format_info.format).optimalTilingFeatures & + 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; @@ -2544,12 +2566,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) { @@ -2718,92 +2735,223 @@ vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_ }); } +CustomBorderColorBudget::~CustomBorderColorBudget() { + Release(); +} + +CustomBorderColorBudget::CustomBorderColorBudget(CustomBorderColorBudget&& rhs) noexcept + : device_ptr{std::exchange(rhs.device_ptr, nullptr)}, held{std::exchange(rhs.held, 0)} {} + +CustomBorderColorBudget& CustomBorderColorBudget::operator=( + CustomBorderColorBudget&& rhs) noexcept { + if (this != &rhs) { + Release(); + device_ptr = std::exchange(rhs.device_ptr, nullptr); + held = std::exchange(rhs.held, 0); + } + return *this; +} + +bool CustomBorderColorBudget::TryAcquire(const Device& device, size_t count) { + if (!device.TryReserveCustomBorderColorSamplers(count)) { + return false; + } + device_ptr = &device; + held += count; + return true; +} + +void CustomBorderColorBudget::Release() noexcept { + if (device_ptr != nullptr) { + device_ptr->ReleaseCustomBorderColorSamplers(held); + } + device_ptr = nullptr; + held = 0; +} + Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& tsc) { const auto& device = runtime.device; - const bool has_custom_border_extension = runtime.device.IsExtCustomBorderColorSupported(); - const bool has_format_undefined = - has_custom_border_extension && runtime.device.IsCustomBorderColorWithoutFormatSupported(); - const bool has_custom_border_colors = - has_format_undefined && runtime.device.IsCustomBorderColorsSupported(); - const auto color = tsc.BorderColor(); + device_ptr = &device; + border_color = tsc.BorderColor(); + srgb_border_color = tsc.SrgbBorderColor(); + const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); + default_anisotropy = static_cast(1U << tsc.max_anisotropy); + + const VkFilter mag_filter{MaxwellToVK::Sampler::Filter(tsc.mag_filter)}; + const VkFilter min_filter{MaxwellToVK::Sampler::Filter(tsc.min_filter)}; + const VkSamplerMipmapMode mipmap_mode{MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter)}; + + const VkSamplerAddressMode wrap_u{ + MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter)}; + const VkSamplerAddressMode wrap_v{ + MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter)}; + const VkSamplerAddressMode wrap_p{ + MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter)}; + const bool samples_border = wrap_u == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || + wrap_v == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || + wrap_p == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + + reduction_mode = MaxwellToVK::SamplerReduction(tsc.reduction_filter); + + has_added_anisotropy = max_anisotropy > default_anisotropy; + has_linear_filtering = mag_filter == VK_FILTER_LINEAR || min_filter == VK_FILTER_LINEAR || + mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR; + has_depth_comparison = tsc.depth_compare_enabled != 0; + has_minmax_reduction = reduction_mode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT; + has_srgb_border_color = tsc.srgb_conversion != 0 && srgb_border_color != border_color; + if (has_minmax_reduction && !device.IsExtSamplerFilterMinmaxSupported()) { + LOG_WARNING(Render_Vulkan, "VK_EXT_sampler_filter_minmax is required"); + has_minmax_reduction = false; + } + has_custom_border_colors = samples_border && device.IsCustomBorderColorUsable(); + needs_swizzle_mapping = has_custom_border_colors && device.NeedsBorderColorSwizzleMapping(); + + if (has_custom_border_colors && GPU::Logging::IsActive()) { + GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( + "VK_EXT_custom_border_color", "Sampler::Sampler"); + } + if (device.IsExtBorderColorSwizzleSupported() && GPU::Logging::IsActive()) { + GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( + "VK_EXT_border_color_swizzle", "Sampler::Sampler"); + } + + f32 min_lod = 0.0f; + f32 max_lod = 0.25f; + if (tsc.mipmap_filter != TextureMipmapFilter::None) { + min_lod = tsc.MinLod(); + max_lod = tsc.MaxLod(); + } + base_ci = VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = mag_filter, + .minFilter = min_filter, + .mipmapMode = mipmap_mode, + .addressModeU = wrap_u, + .addressModeV = wrap_v, + .addressModeW = wrap_p, + .mipLodBias = tsc.LodBias(), + .anisotropyEnable = static_cast(max_anisotropy > 1.0f), + .maxAnisotropy = max_anisotropy, + .compareEnable = static_cast(tsc.depth_compare_enabled), + .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), + .minLod = min_lod, + .maxLod = max_lod, + .borderColor = VK_BORDER_COLOR_FLOAT_CUSTOM_EXT, + .unnormalizedCoordinates = VK_FALSE, + }; + variants.reserve(1); + Emplace(VariantKey{}); +} + +Sampler::VariantKey Sampler::MakeKey(const ImageView& image_view, bool is_depth) const noexcept { + VariantKey key{}; + key.reduce_anisotropy = has_added_anisotropy && !image_view.SupportsAnisotropy(); + key.force_nearest = has_linear_filtering && IsPixelFormatInteger(image_view.format); + key.drop_depth_comparison = + is_depth && has_depth_comparison && !image_view.SupportsDepthComparison(); + key.drop_reduction = has_minmax_reduction && !image_view.SupportsMinmaxFilter(); + key.drop_custom_border = has_custom_border_colors && image_view.RequiresBorderColorFormat(); + key.srgb_border = has_srgb_border_color && IsPixelFormatSRGB(image_view.format); + if (needs_swizzle_mapping && !key.drop_custom_border && !image_view.HasIdentitySwizzle()) { + const VkComponentMapping& mapping = image_view.Swizzle(); + key.swizzle = {mapping.r, mapping.g, mapping.b, mapping.a}; + } + return key; +} + +VkSampler Sampler::Find(const VariantKey& key) const noexcept { + const auto it = std::ranges::find(variants, key, &Variant::key); + if (it == variants.end()) { + return VK_NULL_HANDLE; + } + return *it->sampler; +} + +VkSampler Sampler::Emplace(VariantKey key) { + bool custom_border = has_custom_border_colors && !key.drop_custom_border; + if (custom_border && !custom_border_color_budget.TryAcquire(*device_ptr, 1)) { + static bool warned_budget = false; + if (!warned_budget) { + warned_budget = true; + } + custom_border = false; + key.drop_custom_border = true; + key.swizzle = {}; + if (const VkSampler existing = Find(key); existing != VK_NULL_HANDLE) { + return existing; + } + } + + std::array color = border_color; + if (key.srgb_border) { + color = srgb_border_color; + } const VkSamplerCustomBorderColorCreateInfoEXT border_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT, .pNext = nullptr, .customBorderColor = std::bit_cast(color), .format = VK_FORMAT_UNDEFINED, }; - const void* pnext = nullptr; - if (has_custom_border_colors) { - pnext = &border_ci; - if (GPU::Logging::IsActive()) { - GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( - "VK_EXT_custom_border_color", "Sampler::Sampler"); + const VkSamplerBorderColorComponentMappingCreateInfoEXT mapping_ci{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_BORDER_COLOR_COMPONENT_MAPPING_CREATE_INFO_EXT, + .pNext = &border_ci, + .components{key.swizzle[0], key.swizzle[1], key.swizzle[2], key.swizzle[3]}, + .srgb = VK_FALSE, + }; + const void* chain = nullptr; + if (custom_border) { + chain = &border_ci; + if (key.HasSwizzle()) { + chain = &mapping_ci; } } - if (device.IsExtBorderColorSwizzleSupported() && GPU::Logging::IsActive()) { - GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( - "VK_EXT_border_color_swizzle", "Sampler::Sampler"); - } const VkSamplerReductionModeCreateInfoEXT reduction_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, - .pNext = pnext, - .reductionMode = MaxwellToVK::SamplerReduction(tsc.reduction_filter), + .pNext = chain, + .reductionMode = reduction_mode, }; - if (runtime.device.IsExtSamplerFilterMinmaxSupported()) { - pnext = &reduction_ci; - } else if (reduction_ci.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT) { - LOG_WARNING(Render_Vulkan, "VK_EXT_sampler_filter_minmax is required"); + if (has_minmax_reduction && !key.drop_reduction) { + chain = &reduction_ci; } - // Some games have samplers with garbage. Sanitize them here. - const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); - const VkFilter mag_filter{MaxwellToVK::Sampler::Filter(tsc.mag_filter)}; - const VkFilter min_filter{MaxwellToVK::Sampler::Filter(tsc.min_filter)}; - const VkSamplerMipmapMode mipmap_mode{MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter)}; - const bool has_linear_filtering{mag_filter == VK_FILTER_LINEAR || - min_filter == VK_FILTER_LINEAR || - mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR}; - - const auto create_sampler = [&](const f32 anisotropy, bool force_nearest, - bool disable_compare = false) { - return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = pnext, - .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 = has_custom_border_colors ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT - : ConvertBorderColor(color), - .unnormalizedCoordinates = VK_FALSE, - }); - }; - - sampler = create_sampler(max_anisotropy, false); - - const f32 max_anisotropy_default = static_cast(1U << tsc.max_anisotropy); - if (max_anisotropy > max_anisotropy_default) { - sampler_default_anisotropy = create_sampler(max_anisotropy_default, false); + VkSamplerCreateInfo create_info = base_ci; + create_info.pNext = chain; + if (key.force_nearest) { + create_info.magFilter = VK_FILTER_NEAREST; + create_info.minFilter = VK_FILTER_NEAREST; + create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + create_info.anisotropyEnable = VK_FALSE; + create_info.maxAnisotropy = 1.0f; + } else if (key.reduce_anisotropy) { + create_info.anisotropyEnable = static_cast(default_anisotropy > 1.0f); + create_info.maxAnisotropy = default_anisotropy; } - if (has_linear_filtering) { - sampler_nearest = create_sampler(1.0f, true); + if (key.drop_depth_comparison) { + create_info.compareEnable = VK_FALSE; } - if (tsc.depth_compare_enabled) { - sampler_noncompare = create_sampler(max_anisotropy, false, true); + if (!custom_border) { + create_info.borderColor = ConvertBorderColor(color); } + variants.push_back(Variant{ + .key = key, + .sampler = device_ptr->GetLogical().CreateSampler(create_info), + }); + return *variants.back().sampler; +} + +VkSampler Sampler::HandleFor(const ImageView& image_view, bool is_depth) { + VariantKey key = MakeKey(image_view, is_depth); + if (variants.size() >= MAX_VARIANTS) { + key.srgb_border = false; + key.swizzle = {}; + } + if (const VkSampler existing = Find(key); existing != VK_NULL_HANDLE) { + return existing; + } + return Emplace(key); } 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 b2f32db1ff..1b1e35aa45 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -68,8 +68,6 @@ public: bool CanReportMemoryUsage() const; - std::optional GetSamplerHeapBudget() const; - bool CanDownloadMsaa(const VideoCommon::ImageInfo& info) const; [[nodiscard]] VkImage AcquireMsaaScratchImage(const VkImageCreateInfo& image_ci); @@ -446,6 +444,22 @@ public: return supports_depth_comparison; } + [[nodiscard]] bool RequiresBorderColorFormat() const noexcept { + return requires_border_color_format; + } + + [[nodiscard]] bool SupportsMinmaxFilter() const noexcept { + 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; } @@ -478,48 +492,91 @@ 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 {}; +class CustomBorderColorBudget { +public: + CustomBorderColorBudget() = default; + ~CustomBorderColorBudget(); + + CustomBorderColorBudget(const CustomBorderColorBudget&) = delete; + CustomBorderColorBudget& operator=(const CustomBorderColorBudget&) = delete; + + CustomBorderColorBudget(CustomBorderColorBudget&& rhs) noexcept; + CustomBorderColorBudget& operator=(CustomBorderColorBudget&& rhs) noexcept; + + bool TryAcquire(const Device& device, size_t count); + +private: + void Release() noexcept; + + const Device* device_ptr = nullptr; + size_t held = 0; +}; + class Sampler { public: explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); [[nodiscard]] VkSampler Handle() const noexcept { - return *sampler; + return *variants.front().sampler; } - [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept { - return *sampler_default_anisotropy; - } - - [[nodiscard]] bool HasAddedAnisotropy() const noexcept { - return static_cast(sampler_default_anisotropy); - } - - [[nodiscard]] VkSampler HandleWithNearestFilter() const noexcept { - return *sampler_nearest; - } - - [[nodiscard]] bool HasLinearFiltering() const noexcept { - return static_cast(sampler_nearest); - } - - [[nodiscard]] VkSampler HandleWithoutDepthComparison() const noexcept { - return *sampler_noncompare; - } - - [[nodiscard]] bool HasDepthComparison() const noexcept { - return static_cast(sampler_noncompare); - } + [[nodiscard]] VkSampler HandleFor(const ImageView& image_view, bool is_depth); private: - vk::Sampler sampler; - vk::Sampler sampler_default_anisotropy; - vk::Sampler sampler_nearest; - vk::Sampler sampler_noncompare; + struct VariantKey { + bool reduce_anisotropy; + bool force_nearest; + bool drop_depth_comparison; + bool drop_reduction; + bool drop_custom_border; + bool srgb_border; + std::array swizzle; + + bool operator==(const VariantKey&) const noexcept = default; + + [[nodiscard]] bool HasSwizzle() const noexcept { + return swizzle != std::array{}; + } + }; + + struct Variant { + VariantKey key; + vk::Sampler sampler; + }; + + static constexpr size_t MAX_VARIANTS = 32; + + [[nodiscard]] VariantKey MakeKey(const ImageView& image_view, bool is_depth) const noexcept; + [[nodiscard]] VkSampler Find(const VariantKey& key) const noexcept; + VkSampler Emplace(VariantKey key); + + CustomBorderColorBudget custom_border_color_budget; + std::vector variants; + + const Device* device_ptr{nullptr}; + VkSamplerCreateInfo base_ci{}; + VkSamplerReductionModeEXT reduction_mode{VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT}; + std::array border_color{}; + std::array srgb_border_color{}; + f32 default_anisotropy{1.0f}; + + bool has_added_anisotropy{}; + bool has_linear_filtering{}; + bool has_depth_comparison{}; + bool has_minmax_reduction{}; + bool has_custom_border_colors{}; + bool has_srgb_border_color{}; + bool needs_swizzle_mapping{}; }; struct TextureCacheParams { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index bcc40afde0..b6a37286a0 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1896,67 +1896,10 @@ SamplerId TextureCache

::FindSampler(const TSCEntry& config, bool compute) { const auto [pair, is_new] = channel_state->samplers.try_emplace(config); if (is_new) { pair->second = slot_samplers.insert(runtime, config); - EnforceSamplerBudget(); } return pair->second; } -template -std::optional TextureCache

::QuerySamplerBudget() const { - if constexpr (requires { runtime.GetSamplerHeapBudget(); }) { - return runtime.GetSamplerHeapBudget(); - } else { - return std::nullopt; - } -} - -template -void TextureCache

::EnforceSamplerBudget() { - if (auto const budget = QuerySamplerBudget(); budget) { - if (slot_samplers.size() < *budget) { - return; - } - if (!channel_state) { - return; - } - if (last_sampler_gc_frame == frame_tick) { - return; - } - last_sampler_gc_frame = frame_tick; - TrimInactiveSamplers(*budget); - } -} - -template -void TextureCache

::TrimInactiveSamplers(size_t budget) { - if (channel_state->samplers.size() > 0) { - constexpr size_t SAMPLER_GC_SLACK = 1024; - ankerl::unordered_dense::set active_sampler_ids; - for (auto const& e : channel_state->sampler_ids) - active_sampler_ids.insert(e.second); - // Elements in the map must be necesarily valid - size_t removed = 0; - for (auto it = channel_state->samplers.begin(); it != channel_state->samplers.end();) { - const SamplerId sampler_id = it->second; - if (!sampler_id || sampler_id == CORRUPT_ID) { - it = channel_state->samplers.erase(it); - } else if (std::ranges::find(active_sampler_ids, sampler_id) != active_sampler_ids.end()) { - ++it; - } else { - slot_samplers.erase(sampler_id); - it = channel_state->samplers.erase(it); - ++removed; - if (slot_samplers.size() + SAMPLER_GC_SLACK <= budget) { - break; - } - } - } - if (removed != 0) { - LOG_WARNING(HW_GPU, "Sampler cache exceeded {} entries on this driver; reclaimed {} inactive samplers", budget, removed); - } - } -} - template ImageViewId TextureCache

::FindColorBuffer(size_t index) { const auto& regs = maxwell3d->regs; diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 77947e83c7..d143c1ef72 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -420,9 +420,6 @@ private: void QueueAsyncDecode(Image& image, ImageId image_id); void TickAsyncDecode(); - void EnforceSamplerBudget(); - void TrimInactiveSamplers(size_t budget); - std::optional QuerySamplerBudget() const; void QueueAsyncUnswizzle(Image& image, ImageId image_id); void TickAsyncUnswizzle(); @@ -509,7 +506,6 @@ private: u64 modification_tick = 0; u64 frame_tick = 0; - u64 last_sampler_gc_frame = (std::numeric_limits::max)(); Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {}, Common::ThreadPlacement::Efficiency}; diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp index db8a4e69af..dc3b0b8244 100644 --- a/src/video_core/textures/texture.cpp +++ b/src/video_core/textures/texture.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/cityhash.h" #include "common/settings.h" @@ -17,53 +18,25 @@ namespace Tegra::Texture { namespace { -[[maybe_unused]] constexpr std::array SRGB_CONVERSION_LUT = { - 0.000000f, 0.000000f, 0.000000f, 0.000012f, 0.000021f, 0.000033f, 0.000046f, 0.000062f, - 0.000081f, 0.000102f, 0.000125f, 0.000151f, 0.000181f, 0.000214f, 0.000251f, 0.000293f, - 0.000338f, 0.000388f, 0.000443f, 0.000503f, 0.000568f, 0.000639f, 0.000715f, 0.000798f, - 0.000887f, 0.000983f, 0.001085f, 0.001195f, 0.001312f, 0.001437f, 0.001569f, 0.001710f, - 0.001860f, 0.002019f, 0.002186f, 0.002364f, 0.002551f, 0.002748f, 0.002955f, 0.003174f, - 0.003403f, 0.003643f, 0.003896f, 0.004160f, 0.004436f, 0.004725f, 0.005028f, 0.005343f, - 0.005672f, 0.006015f, 0.006372f, 0.006744f, 0.007130f, 0.007533f, 0.007950f, 0.008384f, - 0.008834f, 0.009301f, 0.009785f, 0.010286f, 0.010805f, 0.011342f, 0.011898f, 0.012472f, - 0.013066f, 0.013680f, 0.014313f, 0.014967f, 0.015641f, 0.016337f, 0.017054f, 0.017793f, - 0.018554f, 0.019337f, 0.020144f, 0.020974f, 0.021828f, 0.022706f, 0.023609f, 0.024536f, - 0.025489f, 0.026468f, 0.027473f, 0.028504f, 0.029563f, 0.030649f, 0.031762f, 0.032904f, - 0.034074f, 0.035274f, 0.036503f, 0.037762f, 0.039050f, 0.040370f, 0.041721f, 0.043103f, - 0.044518f, 0.045964f, 0.047444f, 0.048956f, 0.050503f, 0.052083f, 0.053699f, 0.055349f, - 0.057034f, 0.058755f, 0.060513f, 0.062307f, 0.064139f, 0.066008f, 0.067915f, 0.069861f, - 0.071845f, 0.073869f, 0.075933f, 0.078037f, 0.080182f, 0.082369f, 0.084597f, 0.086867f, - 0.089180f, 0.091535f, 0.093935f, 0.096378f, 0.098866f, 0.101398f, 0.103977f, 0.106601f, - 0.109271f, 0.111988f, 0.114753f, 0.117565f, 0.120426f, 0.123335f, 0.126293f, 0.129301f, - 0.132360f, 0.135469f, 0.138629f, 0.141841f, 0.145105f, 0.148421f, 0.151791f, 0.155214f, - 0.158691f, 0.162224f, 0.165810f, 0.169453f, 0.173152f, 0.176907f, 0.180720f, 0.184589f, - 0.188517f, 0.192504f, 0.196549f, 0.200655f, 0.204820f, 0.209046f, 0.213334f, 0.217682f, - 0.222093f, 0.226567f, 0.231104f, 0.235704f, 0.240369f, 0.245099f, 0.249894f, 0.254754f, - 0.259681f, 0.264674f, 0.269736f, 0.274864f, 0.280062f, 0.285328f, 0.290664f, 0.296070f, - 0.301546f, 0.307094f, 0.312713f, 0.318404f, 0.324168f, 0.330006f, 0.335916f, 0.341902f, - 0.347962f, 0.354097f, 0.360309f, 0.366597f, 0.372961f, 0.379403f, 0.385924f, 0.392524f, - 0.399202f, 0.405960f, 0.412798f, 0.419718f, 0.426719f, 0.433802f, 0.440967f, 0.448216f, - 0.455548f, 0.462965f, 0.470465f, 0.478052f, 0.485725f, 0.493484f, 0.501329f, 0.509263f, - 0.517285f, 0.525396f, 0.533595f, 0.541885f, 0.550265f, 0.558736f, 0.567299f, 0.575954f, - 0.584702f, 0.593542f, 0.602477f, 0.611507f, 0.620632f, 0.629852f, 0.639168f, 0.648581f, - 0.658092f, 0.667700f, 0.677408f, 0.687214f, 0.697120f, 0.707127f, 0.717234f, 0.727443f, - 0.737753f, 0.748167f, 0.758685f, 0.769305f, 0.780031f, 0.790861f, 0.801798f, 0.812839f, - 0.823989f, 0.835246f, 0.846611f, 0.858085f, 0.869668f, 0.881360f, 0.893164f, 0.905078f, - 0.917104f, 0.929242f, 0.941493f, 0.953859f, 0.966338f, 1.000000f, 1.000000f, 1.000000f, -}; +float SrgbToLinear(u32 value) { + const float encoded = static_cast(value) / 255.0f; + if (encoded <= 0.04045f) { + return encoded / 12.92f; + } + return std::pow((encoded + 0.055f) / 1.055f, 2.4f); +} } // Anonymous namespace std::array TSCEntry::BorderColor() const noexcept { - // TODO: Handle SRGB correctly. Using this breaks shadows in some games (Xenoblade). - // if (!srgb_conversion) { - // return border_color; - //} - // return {SRGB_CONVERSION_LUT[srgb_border_color_r], SRGB_CONVERSION_LUT[srgb_border_color_g], - // SRGB_CONVERSION_LUT[srgb_border_color_b], border_color[3]}; return border_color; } +std::array TSCEntry::SrgbBorderColor() const noexcept { + return {SrgbToLinear(srgb_border_color_r), SrgbToLinear(srgb_border_color_g), + SrgbToLinear(srgb_border_color_b), border_color[3]}; +} + float TSCEntry::MaxAnisotropy() const noexcept { const bool is_suitable_mipmap_filter = mipmap_filter != TextureMipmapFilter::None; const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256; diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 7e5837b206..9fc6a631bd 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -378,6 +381,8 @@ struct TSCEntry { std::array BorderColor() const noexcept; + std::array SrgbBorderColor() const noexcept; + float MaxAnisotropy() const noexcept; float MinLod() const { diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 8c18e01f97..1e46ca951c 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -508,12 +508,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation."); has_broken_descriptor_aliasing = true; LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken descriptor aliasing."); - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken custom border color."); - RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, - VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken border color swizzle."); - RemoveExtensionFeature(extensions.border_color_swizzle, features.border_color_swizzle, - VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken color write enable."); RemoveExtensionFeature(extensions.color_write_enable, features.color_write_enable, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); @@ -617,21 +611,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } } - if (is_qualcomm) { - const size_t sampler_limit = properties.properties.limits.maxSamplerAllocationCount; - if (sampler_limit > 0) { - constexpr size_t MIN_SAMPLER_BUDGET = 1024U; - const size_t reserved = sampler_limit / 4U; - const size_t derived_budget = - (std::max)(MIN_SAMPLER_BUDGET, sampler_limit - reserved); - sampler_heap_budget = derived_budget; - LOG_WARNING(Render_Vulkan, - "Qualcomm driver reports max {} samplers; reserving {} (25%) and " - "allowing Eden to use {} (75%) to avoid heap exhaustion", - sampler_limit, reserved, sampler_heap_budget); - } - } - if (extensions.sampler_filter_minmax && is_amd) { // Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken. if (!features.shader_float16_int8.shaderFloat16) { @@ -1144,6 +1123,11 @@ bool Device::GetSuitability(bool requires_swapchain) { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR; SetNext(next, properties.maintenance5); } + if (extensions.custom_border_color) { + properties.custom_border_color.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT; + SetNext(next, properties.custom_border_color); + } // Perform the property fetch. physical.GetProperties2(properties2); @@ -1240,9 +1224,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, @@ -1488,11 +1470,26 @@ void Device::SetupFamilies(VkSurfaceKHR surface) { } } -std::optional Device::GetSamplerHeapBudget() const { - if (sampler_heap_budget == 0) { - return std::nullopt; +bool Device::TryReserveCustomBorderColorSamplers(size_t count) const { + const size_t limit = properties.custom_border_color.maxCustomBorderColorSamplers; + if (limit == 0) { + return true; } - return sampler_heap_budget; + size_t used = custom_border_color_samplers_used.load(std::memory_order_relaxed); + while (used + count <= limit) { + if (custom_border_color_samplers_used.compare_exchange_weak( + used, used + count, std::memory_order_relaxed, std::memory_order_relaxed)) { + return true; + } + } + return false; +} + +void Device::ReleaseCustomBorderColorSamplers(size_t count) const { + if (count == 0) { + return; + } + custom_border_color_samplers_used.fetch_sub(count, std::memory_order_relaxed); } u64 Device::GetDeviceMemoryUsage() const { diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index a0aabea1ad..a33ff5cb26 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -699,20 +700,18 @@ FN_MAX_LIMIT_LIST return features.transform_feedback.geometryStreams; } - /// Returns true if the device supports VK_EXT_custom_border_color. - bool IsExtCustomBorderColorSupported() const { - return extensions.custom_border_color; + /// Returns true if custom border colors can be created without a format. + bool IsCustomBorderColorUsable() const { + return extensions.custom_border_color && + features.custom_border_color.customBorderColors && + features.custom_border_color.customBorderColorWithoutFormat; } - /// Returns true if customBorderColors feature is available. - bool IsCustomBorderColorsSupported() const { - return features.custom_border_color.customBorderColors; - } + /// Takes budget for samplers carrying a custom border color, false when exhausted. + bool TryReserveCustomBorderColorSamplers(size_t count) const; - /// Returns true if customBorderColorWithoutFormat feature is available. - bool IsCustomBorderColorWithoutFormatSupported() const { - return features.custom_border_color.customBorderColorWithoutFormat; - } + /// Gives back budget taken by TryReserveCustomBorderColorSamplers. + void ReleaseCustomBorderColorSamplers(size_t count) const; /// Returns true if the device supports VK_EXT_color_write_enable. bool IsExtColorWriteEnableSupported() const { @@ -724,6 +723,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; @@ -919,8 +924,6 @@ FN_MAX_LIMIT_LIST return has_broken_parallel_compiling; } - std::optional GetSamplerHeapBudget() const; - /// Returns the vendor name reported from Vulkan. std::string_view GetVendorName() const { return properties.driver.driverName; @@ -1173,6 +1176,7 @@ private: VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{}; VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{}; VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{}; + VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{}; VkPhysicalDeviceProperties properties{}; }; @@ -1211,7 +1215,7 @@ private: bool dynamic_state3_alpha_to_coverage{}; bool dynamic_state3_alpha_to_one{}; bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow. - size_t sampler_heap_budget{}; ///< Sampler budget for buggy drivers (0 = unlimited). + mutable std::atomic custom_border_color_samplers_used{}; u64 device_access_memory{}; ///< Total size of device local memory in bytes. u32 sets_per_pool{}; ///< Sets per Description Pool NvidiaArchitecture nvidia_arch{NvidiaArchitecture::Arch_AmpereOrNewer};