From c7b24bad1a36eab98de1909d8472f1e5c49be7e1 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 17 Aug 2026 20:00:06 -0400 Subject: [PATCH] [TEST] Vertex, Samplers limits adjustments based on hardware support + MSAA Sample Counts degradation --- .../renderer_vulkan/vk_compute_pass.cpp | 6 + .../renderer_vulkan/vk_graphics_pipeline.cpp | 14 +- .../renderer_vulkan/vk_rasterizer.cpp | 14 +- .../renderer_vulkan/vk_texture_cache.cpp | 142 ++++++++++++++---- .../renderer_vulkan/vk_texture_cache.h | 24 ++- src/video_core/texture_cache/texture_cache.h | 77 +++------- .../texture_cache/texture_cache_base.h | 6 +- .../vulkan_common/vulkan_device.cpp | 89 ++++++++--- src/video_core/vulkan_common/vulkan_device.h | 38 ++++- 9 files changed, 287 insertions(+), 123 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 236ed51361..54f8f9520f 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -964,6 +964,9 @@ bool BlockLinearUnswizzle2DPass::IsSupported(const VideoCommon::ImageInfo& info) VideoCore::Surface::IsPixelFormatBCn(info.format)) { return false; } + if (info.format >= VideoCore::Surface::PixelFormat::MaxColorFormat) { + return false; + } const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format); if (bytes_per_block != 4 && bytes_per_block != 8 && bytes_per_block != 16) { return false; @@ -1207,6 +1210,9 @@ bool BlockLinearUnswizzle3DBufferPass::IsSupported(const Device& device, if (VideoCore::Surface::IsPixelFormatBCn(info.format) && !device.IsOptimalBcnSupported()) { return false; } + if (info.format >= VideoCore::Surface::PixelFormat::MaxColorFormat) { + return false; + } const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format); return bytes_per_block == 4 || bytes_per_block == 8 || bytes_per_block == 16; } diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 345278ab41..a2eee7f16d 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -695,23 +695,30 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { if (!key.state.dynamic_vertex_input) { const size_t num_vertex_arrays = (std::min)( Maxwell::NumVertexArrays, static_cast(device.GetMaxVertexInputBindings())); + const u32 max_stride = device.GetMaxVertexInputBindingStride(); + const u32 max_divisor = device.GetMaxVertexAttribDivisor(); for (size_t index = 0; index < num_vertex_arrays; ++index) { const bool instanced = key.state.binding_divisors[index] != 0; const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; vertex_bindings.push_back({ .binding = static_cast(index), - .stride = key.state.vertex_strides[index], + .stride = (std::min)(u32{key.state.vertex_strides[index]}, max_stride), .inputRate = rate, }); if (instanced) { vertex_binding_divisors.push_back({ .binding = static_cast(index), - .divisor = key.state.binding_divisors[index], + .divisor = (std::min)(key.state.binding_divisors[index], max_divisor), }); } } + const size_t max_attributes = static_cast(device.GetMaxVertexInputAttributes()); + const u32 max_offset = device.GetMaxVertexInputAttributeOffset(); for (size_t index = 0; index < key.state.attributes.size(); ++index) { + if (vertex_attributes.size() >= max_attributes) { + break; + } const auto& attribute = key.state.attributes[index]; if (!attribute.enabled || !stage_infos[0].loads.Generic(index)) { continue; @@ -720,11 +727,10 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .location = static_cast(index), .binding = attribute.buffer, .format = MaxwellToVK::VertexFormat(device, attribute.Type(), attribute.Size()), - .offset = attribute.offset, + .offset = (std::min)(attribute.offset.Value(), max_offset), }); } } - ASSERT(vertex_attributes.size() <= device.GetMaxVertexInputAttributes()); VkPipelineVertexInputStateCreateInfo vertex_input_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 4b064c84e4..10522958cf 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1907,7 +1907,9 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs) const u32 max_bindings = static_cast(std::min(Maxwell::NumVertexArrays, device.GetMaxVertexInputBindings())); - + const u32 max_offset = device.GetMaxVertexInputAttributeOffset(); + const u32 max_stride = device.GetMaxVertexInputBindingStride(); + const u32 max_divisor = device.GetMaxVertexAttribDivisor(); for (u32 index = 0; index < max_attributes; ++index) { const Maxwell::VertexAttribute attribute{regs.vertex_attrib_format[index]}; @@ -1921,20 +1923,24 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs) .location = index, .binding = binding, .format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size), - .offset = attribute.offset, + .offset = (std::min)(attribute.offset.Value(), max_offset), }); } for (u32 binding = 0; binding < max_bindings; ++binding) { const auto& input_binding{regs.vertex_streams[binding]}; const bool is_instanced{regs.vertex_stream_instances.IsInstancingEnabled(binding)}; + u32 divisor = 1; + if (is_instanced) { + divisor = (std::min)(input_binding.frequency, max_divisor); + } bindings.push_back({ .sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT, .pNext = nullptr, .binding = binding, - .stride = input_binding.stride, + .stride = (std::min)(input_binding.stride.Value(), max_stride), .inputRate = is_instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX, - .divisor = is_instanced ? input_binding.frequency : 1, + .divisor = divisor, }); } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 3fea36a91c..f1f5879fab 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -119,7 +120,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { } [[nodiscard]] VkImageUsageFlags ImageUsageFlags(const MaxwellToVK::FormatInfo& info, - PixelFormat format) { + PixelFormat format, bool allow_storage = true) { VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; if (info.attachable) { @@ -137,7 +138,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { break; } } - if (info.storage) { + if (info.storage && allow_storage) { usage |= VK_IMAGE_USAGE_STORAGE_BIT; } return usage; @@ -173,6 +174,8 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; } const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples); + const bool allow_storage = + info.num_samples == 1 || device.IsStorageImageMultisampleSupported(); return VkImageCreateInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = nullptr, @@ -188,7 +191,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { .arrayLayers = static_cast(info.resources.layers), .samples = ConvertSampleCount(info.num_samples), .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = ImageUsageFlags(format_info, info.format), + .usage = ImageUsageFlags(format_info, info.format, allow_storage), .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, @@ -1766,8 +1769,19 @@ bool TextureCacheRuntime::CanReportMemoryUsage() const { return device.CanReportMemoryUsage(); } -std::optional TextureCacheRuntime::GetSamplerHeapBudget() const { - return device.GetSamplerHeapBudget(); +bool TextureCacheRuntime::IsSampleCountSupported(const VideoCommon::ImageInfo& info) const { + if (info.num_samples <= 1) { + return true; + } + const auto format_info = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format); + const bool allow_storage = device.IsStorageImageMultisampleSupported(); + const VkImageUsageFlags usage = ImageUsageFlags(format_info, info.format, allow_storage); + const VkImageAspectFlags aspect = ImageAspectMask(info.format); + const bool is_integer = VideoCore::Surface::IsPixelFormatInteger(info.format); + const VkSampleCountFlags supported = + device.GetSupportedSampleCounts(usage, aspect, is_integer); + return (supported & ConvertSampleCount(info.num_samples)) != 0; } void TextureCacheRuntime::TickFrame() { @@ -2632,12 +2646,92 @@ 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_colors = runtime.device.IsCustomBorderColorUsable(); const auto color = tsc.BorderColor(); const auto srgb_color = tsc.SrgbBorderColor(); + const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); + const f32 max_anisotropy_default = 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 bool has_linear_filtering{mag_filter == VK_FILTER_LINEAR || + min_filter == VK_FILTER_LINEAR || + mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR}; + + 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; + + const VkSamplerReductionModeEXT reduction_mode = + MaxwellToVK::SamplerReduction(tsc.reduction_filter); + const bool has_minmax_reduction = + device.IsExtSamplerFilterMinmaxSupported() && + reduction_mode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT; + + size_t custom_border_color_count = 1; + if (max_anisotropy > max_anisotropy_default) { + ++custom_border_color_count; + } + if (has_linear_filtering) { + ++custom_border_color_count; + } + if (tsc.depth_compare_enabled) { + ++custom_border_color_count; + } + if (has_minmax_reduction) { + ++custom_border_color_count; + } + if (tsc.srgb_conversion && srgb_color != color) { + ++custom_border_color_count; + } + + const bool has_custom_border_colors = + samples_border && device.IsCustomBorderColorUsable() && + custom_border_color_budget.TryAcquire(device, custom_border_color_count); + const VkSamplerCustomBorderColorCreateInfoEXT border_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT, .pNext = nullptr, @@ -2667,38 +2761,26 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t const VkSamplerReductionModeCreateInfoEXT reduction_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, .pNext = pnext, - .reductionMode = MaxwellToVK::SamplerReduction(tsc.reduction_filter), + .reductionMode = reduction_mode, }; const VkSamplerReductionModeCreateInfoEXT reduction_ci_srgb{ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, .pNext = srgb_pnext, - .reductionMode = MaxwellToVK::SamplerReduction(tsc.reduction_filter), + .reductionMode = reduction_mode, }; const VkSamplerReductionModeCreateInfoEXT reduction_ci_without_border{ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, .pNext = nullptr, - .reductionMode = MaxwellToVK::SamplerReduction(tsc.reduction_filter), + .reductionMode = reduction_mode, }; const void* pnext_without_border = nullptr; - const bool has_minmax_reduction = - runtime.device.IsExtSamplerFilterMinmaxSupported() && - reduction_ci.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT; - if (runtime.device.IsExtSamplerFilterMinmaxSupported()) { + if (device.IsExtSamplerFilterMinmaxSupported()) { pnext = &reduction_ci; srgb_pnext = &reduction_ci_srgb; pnext_without_border = &reduction_ci_without_border; - } else if (reduction_ci.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT) { + } else if (reduction_mode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT) { LOG_WARNING(Render_Vulkan, "VK_EXT_sampler_filter_minmax is required"); } - // 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 make_create_info = [&](const f32 anisotropy, bool force_nearest, bool disable_compare, const void* chain, @@ -2710,9 +2792,9 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t .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), + .addressModeU = wrap_u, + .addressModeV = wrap_v, + .addressModeW = wrap_p, .mipLodBias = tsc.LodBias(), .anisotropyEnable = static_cast(!force_nearest && anisotropy > 1.0f ? VK_TRUE : VK_FALSE), @@ -2760,7 +2842,6 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t 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); } @@ -2785,7 +2866,7 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t device_ptr = &device; border_color_value = color; srgb_border_color_value = srgb_color; - swizzle_reduction_mode = reduction_ci.reductionMode; + swizzle_reduction_mode = reduction_mode; swizzle_uses_reduction = has_minmax_reduction; swizzle_base_ci = make_create_info(max_anisotropy, false, false, nullptr, VK_BORDER_COLOR_FLOAT_CUSTOM_EXT); @@ -2802,6 +2883,11 @@ VkSampler Sampler::HandleWithSwizzle(const VkComponentMapping& mapping, bool srg if (it != swizzle_variants.end()) { return *it->sampler; } + constexpr size_t MAX_SWIZZLE_VARIANTS = 8; + if (swizzle_variants.size() >= MAX_SWIZZLE_VARIANTS || + !custom_border_color_budget.TryAcquire(*device_ptr, 1)) { + return *sampler; + } std::array value = border_color_value; if (srgb) { value = srgb_border_color_value; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 68fcc1eac6..b6200bd1cd 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -66,7 +66,7 @@ public: bool CanReportMemoryUsage() const; - std::optional GetSamplerHeapBudget() const; + bool IsSampleCountSupported(const VideoCommon::ImageInfo& info) const; void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src, const Region2D& dst_region, const Region2D& src_region, @@ -472,6 +472,26 @@ private: 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&); @@ -541,6 +561,8 @@ private: vk::Sampler sampler; }; + CustomBorderColorBudget custom_border_color_budget; + vk::Sampler sampler; vk::Sampler sampler_default_anisotropy; vk::Sampler sampler_nearest; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 9ffe0cbb25..f3ff2c455e 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1161,9 +1161,21 @@ void TextureCache

::UploadImageContents(Image& image, StagingBuffer& staging) } } +template +ImageInfo TextureCache

::ClampedSampleCount(ImageInfo info) const { + if (info.num_samples > 1) { + if constexpr (requires { runtime.IsSampleCountSupported(info); }) { + if (!runtime.IsSampleCountSupported(info)) { + info.num_samples = 1; + } + } + } + return info; +} + template ImageViewId TextureCache

::CreateImageView(const TICEntry& config) { - const ImageInfo info(config); + const ImageInfo info = ClampedSampleCount(ImageInfo(config)); if (info.type == ImageType::Buffer) { const ImageViewInfo view_info(config, 0); return slot_image_views.insert(runtime, info, view_info, config.Address()); @@ -1877,67 +1889,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; @@ -1952,7 +1907,8 @@ ImageViewId TextureCache

::FindColorBuffer(size_t index) { if (rt.format == Tegra::RenderTargetFormat::NONE) { return ImageViewId{}; } - const ImageInfo info(regs.rt[index], regs.anti_alias_samples_mode); + const ImageInfo info = + ClampedSampleCount(ImageInfo(regs.rt[index], regs.anti_alias_samples_mode)); return FindRenderTargetView(info, gpu_addr); } @@ -1966,7 +1922,8 @@ ImageViewId TextureCache

::FindDepthBuffer() { if (gpu_addr == 0) { return ImageViewId{}; } - const ImageInfo info(regs.zeta, regs.zeta_size, regs.anti_alias_samples_mode); + const ImageInfo info = + ClampedSampleCount(ImageInfo(regs.zeta, regs.zeta_size, regs.anti_alias_samples_mode)); return FindRenderTargetView(info, gpu_addr); } diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index fafc331ef7..e75ce4e706 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -416,11 +416,10 @@ private: bool ScaleDown(Image& image); u64 GetScaledImageSizeBytes(const ImageBase& image); + [[nodiscard]] ImageInfo ClampedSampleCount(ImageInfo info) const; + 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(); @@ -507,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/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 4d9d9f7d28..bd833ba784 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -628,21 +628,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) { @@ -1240,6 +1225,16 @@ 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); + } + if (extensions.vertex_attribute_divisor) { + properties.vertex_attribute_divisor.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT; + SetNext(next, properties.vertex_attribute_divisor); + } // Perform the property fetch. physical.GetProperties2(properties2); @@ -1582,11 +1577,67 @@ void Device::SetupFamilies(VkSurfaceKHR surface) { } } -std::optional Device::GetSamplerHeapBudget() const { - if (sampler_heap_budget == 0) { - return std::nullopt; +VkSampleCountFlags Device::GetSupportedSampleCounts(VkImageUsageFlags usage, + VkImageAspectFlags aspect, + bool is_integer) const { + const VkPhysicalDeviceLimits& limits = properties.properties.limits; + const bool has_color = (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0; + const bool has_depth = (aspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; + const bool has_stencil = (aspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; + + VkSampleCountFlags counts = ~VkSampleCountFlags{0}; + if ((usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0) { + if (has_color) { + if (is_integer) { + counts &= limits.sampledImageIntegerSampleCounts; + } else { + counts &= limits.sampledImageColorSampleCounts; + } + } + if (has_depth) { + counts &= limits.sampledImageDepthSampleCounts; + } + if (has_stencil) { + counts &= limits.sampledImageStencilSampleCounts; + } } - return sampler_heap_budget; + if ((usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0) { + counts &= limits.framebufferColorSampleCounts; + } + if ((usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0) { + if (has_depth) { + counts &= limits.framebufferDepthSampleCounts; + } + if (has_stencil) { + counts &= limits.framebufferStencilSampleCounts; + } + } + if ((usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) { + counts &= limits.storageImageSampleCounts; + } + return counts; +} + +bool Device::TryReserveCustomBorderColorSamplers(size_t count) const { + const size_t limit = properties.custom_border_color.maxCustomBorderColorSamplers; + if (limit == 0) { + return true; + } + 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 a271c11319..02b4767095 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 @@ -458,6 +459,10 @@ FN_MAX_LIMIT_LIST return features.features.shaderStorageImageMultisample; } + /// Returns the sample counts an image with the given usage and aspect can be created with. + VkSampleCountFlags GetSupportedSampleCounts(VkImageUsageFlags usage, VkImageAspectFlags aspect, + bool is_integer) const; + /// Returns true if the device warp size can potentially be bigger than guest's warp size. bool IsWarpSizePotentiallyBiggerThanGuest() const { return is_warp_potentially_bigger; @@ -714,6 +719,17 @@ FN_MAX_LIMIT_LIST features.custom_border_color.customBorderColorWithoutFormat; } + /// Returns how many live samplers may carry a custom border color, 0 when unknown. + u32 GetMaxCustomBorderColorSamplers() const { + return properties.custom_border_color.maxCustomBorderColorSamplers; + } + + /// Takes budget for samplers carrying a custom border color, false when exhausted. + bool TryReserveCustomBorderColorSamplers(size_t count) const; + + /// 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 { return extensions.color_write_enable; @@ -925,8 +941,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; @@ -977,6 +991,22 @@ FN_MAX_LIMIT_LIST return properties.properties.limits.maxVertexInputBindings; } + u32 GetMaxVertexInputAttributeOffset() const { + return properties.properties.limits.maxVertexInputAttributeOffset; + } + + u32 GetMaxVertexInputBindingStride() const { + return properties.properties.limits.maxVertexInputBindingStride; + } + + u32 GetMaxVertexAttribDivisor() const { + const u32 limit = properties.vertex_attribute_divisor.maxVertexAttribDivisor; + if (!extensions.vertex_attribute_divisor || limit == 0) { + return 1U; + } + return limit; + } + u32 GetMaxViewports() const { return properties.properties.limits.maxViewports; } @@ -1199,6 +1229,8 @@ private: VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{}; VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{}; VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{}; + VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{}; + VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT vertex_attribute_divisor{}; VkPhysicalDeviceProperties properties{}; }; @@ -1237,7 +1269,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};