fix: add VkSampler cache to prevent sampler exhaustion crash

Validation layers revealed 9965 VkSamplers allocated against a device
limit of 4000 — every VkTexture created its own sampler even when
configurations were identical. This exhausted NVIDIA's sampler pool
and caused intermittent SIGSEGV in vkCmdBeginRenderPass.

Add a thread-safe sampler cache in VkContext that deduplicates samplers
by FNV-1a hash of all 14 VkSamplerCreateInfo fields. All texture,
render target, renderer, water, and loading screen sampler creation
now goes through getOrCreateSampler(). Textures set ownsSampler_=false
so shared samplers aren't double-freed.

Also auto-disable anisotropy in the cache when the physical device
doesn't support the samplerAnisotropy feature, fixing the validation
error VUID-VkSamplerCreateInfo-anisotropyEnable-01070.
This commit is contained in:
Kelsi 2026-03-24 11:44:54 -07:00
parent 1556559211
commit a152023e5e
10 changed files with 194 additions and 40 deletions

View file

@ -8,6 +8,8 @@
#include <vector>
#include <functional>
#include <cstdint>
#include <unordered_map>
#include <mutex>
namespace wowee {
namespace rendering {
@ -119,6 +121,18 @@ public:
VkImageView getDepthResolveImageView() const { return depthResolveImageView; }
VkImageView getDepthImageView() const { return depthImageView; }
// Sampler cache: returns a shared VkSampler matching the given create info.
// Callers must NOT destroy the returned sampler — it is owned by VkContext.
// Automatically clamps anisotropy if the device doesn't support it.
VkSampler getOrCreateSampler(const VkSamplerCreateInfo& info);
// Whether the physical device supports sampler anisotropy.
bool isSamplerAnisotropySupported() const { return samplerAnisotropySupported_; }
// Global sampler cache accessor (set during VkContext::initialize, cleared on shutdown).
// Used by VkTexture and other code that only has a VkDevice handle.
static VkContext* globalInstance() { return sInstance_; }
// UI texture upload: creates a Vulkan texture from RGBA data and returns
// a VkDescriptorSet suitable for use as ImTextureID.
// The caller does NOT need to free the result — resources are tracked and
@ -239,6 +253,13 @@ private:
};
std::vector<UiTexture> uiTextures_;
// Sampler cache — deduplicates VkSamplers by configuration hash.
std::mutex samplerCacheMutex_;
std::unordered_map<uint64_t, VkSampler> samplerCache_;
bool samplerAnisotropySupported_ = false;
static VkContext* sInstance_;
#ifndef NDEBUG
bool enableValidation = true;
#else

View file

@ -73,6 +73,7 @@ private:
bool hasDepth_ = false;
VkSampleCountFlagBits msaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
VkSampler sampler_ = VK_NULL_HANDLE;
bool ownsSampler_ = true;
VkRenderPass renderPass_ = VK_NULL_HANDLE;
VkFramebuffer framebuffer_ = VK_NULL_HANDLE;
};

View file

@ -72,6 +72,7 @@ private:
AllocatedImage image_{};
VkSampler sampler_ = VK_NULL_HANDLE;
uint32_t mipLevels_ = 1;
bool ownsSampler_ = true; // false when sampler comes from VkContext cache
};
} // namespace rendering