aboutsummaryrefslogtreecommitdiff
path: root/src/video_core
diff options
context:
space:
mode:
authorCamilleLaVey <camillelavey99@gmail.com>2026-08-25 20:03:18 +0200
committercrueter <crueter@eden-emu.dev>2026-08-25 20:03:18 +0200
commit0295dc5fff9b2977e753e7c126cc870abb07ee3f (patch)
tree751496ed005803472254a4739b10f4cfb26045d7 /src/video_core
parent60a474b8df051beb1d5fb84f4363d1576fcbb3fa (diff)
[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 <lizzie@eden-emu.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Diffstat (limited to 'src/video_core')
-rw-r--r--src/video_core/renderer_vulkan/pipeline_helper.h17
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp304
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h113
-rw-r--r--src/video_core/texture_cache/texture_cache.h57
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h4
-rw-r--r--src/video_core/textures/texture.cpp53
-rw-r--r--src/video_core/textures/texture.h5
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp53
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h32
9 files changed, 375 insertions, 263 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<float, 4>& color) {
if (color == std::array<float, 4>{0, 0, 0, 0}) {
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
@@ -1802,10 +1814,6 @@ bool TextureCacheRuntime::CanReportMemoryUsage() const {
return device.CanReportMemoryUsage();
}
-std::optional<size_t> 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<u32> 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<f32>(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<VkBool32>(max_anisotropy > 1.0f),
+ .maxAnisotropy = max_anisotropy,
+ .compareEnable = static_cast<VkBool32>(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<float, 4> 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<VkClearColorValue>(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<VkBool32>(!force_nearest && anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
- .maxAnisotropy = force_nearest ? 1.0f : anisotropy,
- .compareEnable = disable_compare ? VK_FALSE
- : static_cast<VkBool32>(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<f32>(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<VkBool32>(default_anisotropy > 1.0f);
+ create_info.maxAnisotropy = default_anisotropy;
+ }
+ if (key.drop_depth_comparison) {
+ create_info.compareEnable = VK_FALSE;
}
- if (has_linear_filtering) {
- sampler_nearest = create_sampler(1.0f, 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 (tsc.depth_compare_enabled) {
- sampler_noncompare = create_sampler(max_anisotropy, false, true);
+ if (const VkSampler existing = Find(key); existing != VK_NULL_HANDLE) {
+ return existing;
}
+ return Emplace(key);
}
Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> 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<size_t> 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 Sampler {
+class CustomBorderColorBudget {
public:
- explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&);
+ CustomBorderColorBudget() = default;
+ ~CustomBorderColorBudget();
- [[nodiscard]] VkSampler Handle() const noexcept {
- return *sampler;
- }
+ CustomBorderColorBudget(const CustomBorderColorBudget&) = delete;
+ CustomBorderColorBudget& operator=(const CustomBorderColorBudget&) = delete;
- [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept {
- return *sampler_default_anisotropy;
- }
+ CustomBorderColorBudget(CustomBorderColorBudget&& rhs) noexcept;
+ CustomBorderColorBudget& operator=(CustomBorderColorBudget&& rhs) noexcept;
- [[nodiscard]] bool HasAddedAnisotropy() const noexcept {
- return static_cast<bool>(sampler_default_anisotropy);
- }
+ bool TryAcquire(const Device& device, size_t count);
- [[nodiscard]] VkSampler HandleWithNearestFilter() const noexcept {
- return *sampler_nearest;
- }
+private:
+ void Release() noexcept;
- [[nodiscard]] bool HasLinearFiltering() const noexcept {
- return static_cast<bool>(sampler_nearest);
- }
+ const Device* device_ptr = nullptr;
+ size_t held = 0;
+};
- [[nodiscard]] VkSampler HandleWithoutDepthComparison() const noexcept {
- return *sampler_noncompare;
- }
+class Sampler {
+public:
+ explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&);
- [[nodiscard]] bool HasDepthComparison() const noexcept {
- return static_cast<bool>(sampler_noncompare);
+ [[nodiscard]] VkSampler Handle() const noexcept {
+ return *variants.front().sampler;
}
+ [[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<VkComponentSwizzle, 4> swizzle;
+
+ bool operator==(const VariantKey&) const noexcept = default;
+
+ [[nodiscard]] bool HasSwizzle() const noexcept {
+ return swizzle != std::array<VkComponentSwizzle, 4>{};
+ }
+ };
+
+ 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<Variant> variants;
+
+ const Device* device_ptr{nullptr};
+ VkSamplerCreateInfo base_ci{};
+ VkSamplerReductionModeEXT reduction_mode{VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT};
+ std::array<float, 4> border_color{};
+ std::array<float, 4> 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,68 +1896,11 @@ SamplerId TextureCache<P>::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 <class P>
-std::optional<size_t> TextureCache<P>::QuerySamplerBudget() const {
- if constexpr (requires { runtime.GetSamplerHeapBudget(); }) {
- return runtime.GetSamplerHeapBudget();
- } else {
- return std::nullopt;
- }
-}
-
-template <class P>
-void TextureCache<P>::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 <class P>
-void TextureCache<P>::TrimInactiveSamplers(size_t budget) {
- if (channel_state->samplers.size() > 0) {
- constexpr size_t SAMPLER_GC_SLACK = 1024;
- ankerl::unordered_dense::set<SamplerId> 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 <class P>
ImageViewId TextureCache<P>::FindColorBuffer(size_t index) {
const auto& regs = maxwell3d->regs;
if (index >= regs.rt_control.count) {
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<size_t> 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<u64>::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 <array>
+#include <cmath>
#include "common/cityhash.h"
#include "common/settings.h"
@@ -17,53 +18,25 @@ namespace Tegra::Texture {
namespace {
-[[maybe_unused]] constexpr std::array<float, 256> 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<float>(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<float, 4> 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<float, 4> 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<float, 4> BorderColor() const noexcept;
+ std::array<float, 4> 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<size_t> 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;
+ }
+ 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;
}
- return sampler_heap_budget;
+ 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 <atomic>
#include <optional>
#include <set>
#include <span>
@@ -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<size_t> 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<size_t> 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};