Kelsidavis-WoWee/include/rendering/vk_texture.hpp
Kelsi a152023e5e 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.
2026-03-24 11:44:54 -07:00

79 lines
2.7 KiB
C++

#pragma once
#include "rendering/vk_utils.hpp"
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <string>
#include <cstdint>
namespace wowee {
namespace rendering {
class VkContext;
class VkTexture {
public:
VkTexture() = default;
~VkTexture();
VkTexture(const VkTexture&) = delete;
VkTexture& operator=(const VkTexture&) = delete;
VkTexture(VkTexture&& other) noexcept;
VkTexture& operator=(VkTexture&& other) noexcept;
// Upload RGBA8 pixel data to GPU
bool upload(VkContext& ctx, const uint8_t* pixels, uint32_t width, uint32_t height,
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, bool generateMips = true);
// Upload with pre-existing mip data (array of mip levels)
bool uploadMips(VkContext& ctx, const uint8_t* const* mipData, const uint32_t* mipSizes,
uint32_t mipCount, uint32_t width, uint32_t height,
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM);
// Create a depth/stencil texture (no upload)
bool createDepth(VkContext& ctx, uint32_t width, uint32_t height,
VkFormat format = VK_FORMAT_D32_SFLOAT);
// Create sampler with specified filtering
bool createSampler(VkDevice device,
VkFilter minFilter = VK_FILTER_LINEAR,
VkFilter magFilter = VK_FILTER_LINEAR,
VkSamplerAddressMode addressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT,
float maxAnisotropy = 16.0f);
// Overload with separate S/T address modes
bool createSampler(VkDevice device,
VkFilter filter,
VkSamplerAddressMode addressModeU,
VkSamplerAddressMode addressModeV,
float maxAnisotropy = 16.0f);
// Create a comparison sampler (for shadow mapping)
bool createShadowSampler(VkDevice device);
void destroy(VkDevice device, VmaAllocator allocator);
VkImage getImage() const { return image_.image; }
VkImageView getImageView() const { return image_.imageView; }
VkSampler getSampler() const { return sampler_; }
uint32_t getWidth() const { return image_.extent.width; }
uint32_t getHeight() const { return image_.extent.height; }
VkFormat getFormat() const { return image_.format; }
uint32_t getMipLevels() const { return mipLevels_; }
bool isValid() const { return image_.image != VK_NULL_HANDLE; }
// Write descriptor info for binding
VkDescriptorImageInfo descriptorInfo(VkImageLayout layout =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) const;
private:
void generateMipmaps(VkContext& ctx, VkFormat format, uint32_t width, uint32_t height);
AllocatedImage image_{};
VkSampler sampler_ = VK_NULL_HANDLE;
uint32_t mipLevels_ = 1;
bool ownsSampler_ = true; // false when sampler comes from VkContext cache
};
} // namespace rendering
} // namespace wowee