mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-25 08:30:13 +00:00
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:
parent
1556559211
commit
a152023e5e
10 changed files with 194 additions and 40 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -40,10 +40,7 @@ void LoadingScreen::shutdown() {
|
|||
// ImGui manages descriptor set lifetime
|
||||
bgDescriptorSet = VK_NULL_HANDLE;
|
||||
}
|
||||
if (bgSampler) {
|
||||
vkDestroySampler(device, bgSampler, nullptr);
|
||||
bgSampler = VK_NULL_HANDLE;
|
||||
}
|
||||
bgSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
if (bgImageView) {
|
||||
vkDestroyImageView(device, bgImageView, nullptr);
|
||||
bgImageView = VK_NULL_HANDLE;
|
||||
|
|
@ -94,7 +91,7 @@ bool LoadingScreen::loadImage(const std::string& path) {
|
|||
if (bgImage) {
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
vkDeviceWaitIdle(device);
|
||||
if (bgSampler) { vkDestroySampler(device, bgSampler, nullptr); bgSampler = VK_NULL_HANDLE; }
|
||||
bgSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
if (bgImageView) { vkDestroyImageView(device, bgImageView, nullptr); bgImageView = VK_NULL_HANDLE; }
|
||||
if (bgImage) { vkDestroyImage(device, bgImage, nullptr); bgImage = VK_NULL_HANDLE; }
|
||||
if (bgMemory) { vkFreeMemory(device, bgMemory, nullptr); bgMemory = VK_NULL_HANDLE; }
|
||||
|
|
@ -230,7 +227,7 @@ bool LoadingScreen::loadImage(const std::string& path) {
|
|||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
vkCreateSampler(device, &samplerInfo, nullptr, &bgSampler);
|
||||
bgSampler = vkCtx->getOrCreateSampler(samplerInfo);
|
||||
}
|
||||
|
||||
// Register with ImGui as a texture
|
||||
|
|
|
|||
|
|
@ -343,7 +343,8 @@ bool Renderer::createPerFrameResources() {
|
|||
sampCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
sampCI.compareEnable = VK_TRUE;
|
||||
sampCI.compareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||
if (vkCreateSampler(device, &sampCI, nullptr, &shadowSampler) != VK_SUCCESS) {
|
||||
shadowSampler = vkCtx->getOrCreateSampler(sampCI);
|
||||
if (shadowSampler == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Failed to create shadow sampler");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -597,7 +598,7 @@ void Renderer::destroyPerFrameResources() {
|
|||
shadowDepthLayout_[i] = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
}
|
||||
if (shadowRenderPass) { vkDestroyRenderPass(device, shadowRenderPass, nullptr); shadowRenderPass = VK_NULL_HANDLE; }
|
||||
if (shadowSampler) { vkDestroySampler(device, shadowSampler, nullptr); shadowSampler = VK_NULL_HANDLE; }
|
||||
shadowSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
}
|
||||
|
||||
void Renderer::updatePerFrameUBO() {
|
||||
|
|
@ -4057,7 +4058,8 @@ bool Renderer::initFSRResources() {
|
|||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
if (vkCreateSampler(device, &samplerInfo, nullptr, &fsr_.sceneSampler) != VK_SUCCESS) {
|
||||
fsr_.sceneSampler = vkCtx->getOrCreateSampler(samplerInfo);
|
||||
if (fsr_.sceneSampler == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("FSR: failed to create sampler");
|
||||
destroyFSRResources();
|
||||
return false;
|
||||
|
|
@ -4171,7 +4173,7 @@ void Renderer::destroyFSRResources() {
|
|||
if (fsr_.descPool) { vkDestroyDescriptorPool(device, fsr_.descPool, nullptr); fsr_.descPool = VK_NULL_HANDLE; fsr_.descSet = VK_NULL_HANDLE; }
|
||||
if (fsr_.descSetLayout) { vkDestroyDescriptorSetLayout(device, fsr_.descSetLayout, nullptr); fsr_.descSetLayout = VK_NULL_HANDLE; }
|
||||
if (fsr_.sceneFramebuffer) { vkDestroyFramebuffer(device, fsr_.sceneFramebuffer, nullptr); fsr_.sceneFramebuffer = VK_NULL_HANDLE; }
|
||||
if (fsr_.sceneSampler) { vkDestroySampler(device, fsr_.sceneSampler, nullptr); fsr_.sceneSampler = VK_NULL_HANDLE; }
|
||||
fsr_.sceneSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
destroyImage(device, alloc, fsr_.sceneDepthResolve);
|
||||
destroyImage(device, alloc, fsr_.sceneMsaaColor);
|
||||
destroyImage(device, alloc, fsr_.sceneDepth);
|
||||
|
|
@ -4350,11 +4352,11 @@ bool Renderer::initFSR2Resources() {
|
|||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
vkCreateSampler(device, &samplerInfo, nullptr, &fsr2_.linearSampler);
|
||||
fsr2_.linearSampler = vkCtx->getOrCreateSampler(samplerInfo);
|
||||
|
||||
samplerInfo.minFilter = VK_FILTER_NEAREST;
|
||||
samplerInfo.magFilter = VK_FILTER_NEAREST;
|
||||
vkCreateSampler(device, &samplerInfo, nullptr, &fsr2_.nearestSampler);
|
||||
fsr2_.nearestSampler = vkCtx->getOrCreateSampler(samplerInfo);
|
||||
|
||||
#if WOWEE_HAS_AMD_FSR2
|
||||
// Initialize AMD FSR2 context; fall back to internal path on any failure.
|
||||
|
|
@ -4753,8 +4755,8 @@ void Renderer::destroyFSR2Resources() {
|
|||
if (fsr2_.motionVecDescSetLayout) { vkDestroyDescriptorSetLayout(device, fsr2_.motionVecDescSetLayout, nullptr); fsr2_.motionVecDescSetLayout = VK_NULL_HANDLE; }
|
||||
|
||||
if (fsr2_.sceneFramebuffer) { vkDestroyFramebuffer(device, fsr2_.sceneFramebuffer, nullptr); fsr2_.sceneFramebuffer = VK_NULL_HANDLE; }
|
||||
if (fsr2_.linearSampler) { vkDestroySampler(device, fsr2_.linearSampler, nullptr); fsr2_.linearSampler = VK_NULL_HANDLE; }
|
||||
if (fsr2_.nearestSampler) { vkDestroySampler(device, fsr2_.nearestSampler, nullptr); fsr2_.nearestSampler = VK_NULL_HANDLE; }
|
||||
fsr2_.linearSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
fsr2_.nearestSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
|
||||
destroyImage(device, alloc, fsr2_.motionVectors);
|
||||
for (int i = 0; i < 2; i++) destroyImage(device, alloc, fsr2_.history[i]);
|
||||
|
|
@ -5273,7 +5275,8 @@ bool Renderer::initFXAAResources() {
|
|||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
if (vkCreateSampler(device, &samplerInfo, nullptr, &fxaa_.sceneSampler) != VK_SUCCESS) {
|
||||
fxaa_.sceneSampler = vkCtx->getOrCreateSampler(samplerInfo);
|
||||
if (fxaa_.sceneSampler == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("FXAA: failed to create sampler");
|
||||
destroyFXAAResources();
|
||||
return false;
|
||||
|
|
@ -5383,7 +5386,7 @@ void Renderer::destroyFXAAResources() {
|
|||
if (fxaa_.descPool) { vkDestroyDescriptorPool(device, fxaa_.descPool, nullptr); fxaa_.descPool = VK_NULL_HANDLE; fxaa_.descSet = VK_NULL_HANDLE; }
|
||||
if (fxaa_.descSetLayout) { vkDestroyDescriptorSetLayout(device, fxaa_.descSetLayout, nullptr); fxaa_.descSetLayout = VK_NULL_HANDLE; }
|
||||
if (fxaa_.sceneFramebuffer) { vkDestroyFramebuffer(device, fxaa_.sceneFramebuffer, nullptr); fxaa_.sceneFramebuffer = VK_NULL_HANDLE; }
|
||||
if (fxaa_.sceneSampler) { vkDestroySampler(device, fxaa_.sceneSampler, nullptr); fxaa_.sceneSampler = VK_NULL_HANDLE; }
|
||||
fxaa_.sceneSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
destroyImage(device, alloc, fxaa_.sceneDepthResolve);
|
||||
destroyImage(device, alloc, fxaa_.sceneMsaaColor);
|
||||
destroyImage(device, alloc, fxaa_.sceneDepth);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,44 @@
|
|||
namespace wowee {
|
||||
namespace rendering {
|
||||
|
||||
VkContext* VkContext::sInstance_ = nullptr;
|
||||
|
||||
// Hash a VkSamplerCreateInfo into a 64-bit key for the sampler cache.
|
||||
static uint64_t hashSamplerCreateInfo(const VkSamplerCreateInfo& s) {
|
||||
// Pack the relevant fields into a deterministic hash.
|
||||
// FNV-1a 64-bit on the raw config values.
|
||||
uint64_t h = 14695981039346656037ULL;
|
||||
auto mix = [&](uint64_t v) {
|
||||
h ^= v;
|
||||
h *= 1099511628211ULL;
|
||||
};
|
||||
mix(static_cast<uint64_t>(s.minFilter));
|
||||
mix(static_cast<uint64_t>(s.magFilter));
|
||||
mix(static_cast<uint64_t>(s.mipmapMode));
|
||||
mix(static_cast<uint64_t>(s.addressModeU));
|
||||
mix(static_cast<uint64_t>(s.addressModeV));
|
||||
mix(static_cast<uint64_t>(s.addressModeW));
|
||||
mix(static_cast<uint64_t>(s.anisotropyEnable));
|
||||
// Bit-cast floats to uint32_t for hashing
|
||||
uint32_t aniso;
|
||||
std::memcpy(&aniso, &s.maxAnisotropy, sizeof(aniso));
|
||||
mix(static_cast<uint64_t>(aniso));
|
||||
uint32_t maxLodBits;
|
||||
std::memcpy(&maxLodBits, &s.maxLod, sizeof(maxLodBits));
|
||||
mix(static_cast<uint64_t>(maxLodBits));
|
||||
uint32_t minLodBits;
|
||||
std::memcpy(&minLodBits, &s.minLod, sizeof(minLodBits));
|
||||
mix(static_cast<uint64_t>(minLodBits));
|
||||
mix(static_cast<uint64_t>(s.compareEnable));
|
||||
mix(static_cast<uint64_t>(s.compareOp));
|
||||
mix(static_cast<uint64_t>(s.borderColor));
|
||||
uint32_t biasBits;
|
||||
std::memcpy(&biasBits, &s.mipLodBias, sizeof(biasBits));
|
||||
mix(static_cast<uint64_t>(biasBits));
|
||||
mix(static_cast<uint64_t>(s.unnormalizedCoordinates));
|
||||
return h;
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
[[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT type,
|
||||
|
|
@ -52,6 +90,14 @@ bool VkContext::initialize(SDL_Window* window) {
|
|||
if (!createSyncObjects()) return false;
|
||||
if (!createImGuiResources()) return false;
|
||||
|
||||
// Query anisotropy support from the physical device.
|
||||
VkPhysicalDeviceFeatures supportedFeatures{};
|
||||
vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
|
||||
samplerAnisotropySupported_ = (supportedFeatures.samplerAnisotropy == VK_TRUE);
|
||||
LOG_INFO("Sampler anisotropy supported: ", samplerAnisotropySupported_ ? "YES" : "NO");
|
||||
|
||||
sInstance_ = this;
|
||||
|
||||
LOG_INFO("Vulkan context initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
|
@ -97,6 +143,15 @@ void VkContext::shutdown() {
|
|||
pipelineCache_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Destroy all cached samplers.
|
||||
for (auto& [key, sampler] : samplerCache_) {
|
||||
if (sampler) vkDestroySampler(device, sampler, nullptr);
|
||||
}
|
||||
samplerCache_.clear();
|
||||
LOG_INFO("Sampler cache cleared");
|
||||
|
||||
sInstance_ = nullptr;
|
||||
|
||||
LOG_WARNING("VkContext::shutdown - destroySwapchain...");
|
||||
destroySwapchain();
|
||||
|
||||
|
|
@ -135,6 +190,46 @@ void VkContext::runDeferredCleanup(uint32_t frameIndex) {
|
|||
q.clear();
|
||||
}
|
||||
|
||||
VkSampler VkContext::getOrCreateSampler(const VkSamplerCreateInfo& info) {
|
||||
// Clamp anisotropy if the device doesn't support the feature.
|
||||
VkSamplerCreateInfo adjusted = info;
|
||||
if (!samplerAnisotropySupported_) {
|
||||
adjusted.anisotropyEnable = VK_FALSE;
|
||||
adjusted.maxAnisotropy = 1.0f;
|
||||
}
|
||||
|
||||
uint64_t key = hashSamplerCreateInfo(adjusted);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(samplerCacheMutex_);
|
||||
auto it = samplerCache_.find(key);
|
||||
if (it != samplerCache_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new sampler outside the lock (vkCreateSampler is thread-safe
|
||||
// for distinct create infos, but we re-lock to insert).
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
if (vkCreateSampler(device, &adjusted, nullptr, &sampler) != VK_SUCCESS) {
|
||||
LOG_ERROR("getOrCreateSampler: vkCreateSampler failed");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(samplerCacheMutex_);
|
||||
// Double-check: another thread may have inserted while we were creating.
|
||||
auto [it, inserted] = samplerCache_.emplace(key, sampler);
|
||||
if (!inserted) {
|
||||
// Another thread won the race — destroy our duplicate and use theirs.
|
||||
vkDestroySampler(device, sampler, nullptr);
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
return sampler;
|
||||
}
|
||||
|
||||
bool VkContext::createInstance(SDL_Window* window) {
|
||||
// Get required SDL extensions
|
||||
unsigned int sdlExtCount = 0;
|
||||
|
|
@ -980,10 +1075,7 @@ void VkContext::destroyImGuiResources() {
|
|||
if (tex.memory) vkFreeMemory(device, tex.memory, nullptr);
|
||||
}
|
||||
uiTextures_.clear();
|
||||
if (uiTextureSampler_) {
|
||||
vkDestroySampler(device, uiTextureSampler_, nullptr);
|
||||
uiTextureSampler_ = VK_NULL_HANDLE;
|
||||
}
|
||||
uiTextureSampler_ = VK_NULL_HANDLE; // Owned by sampler cache
|
||||
|
||||
if (imguiDescriptorPool) {
|
||||
vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
|
||||
|
|
@ -1015,7 +1107,7 @@ VkDescriptorSet VkContext::uploadImGuiTexture(const uint8_t* rgba, int width, in
|
|||
|
||||
VkDeviceSize imageSize = static_cast<VkDeviceSize>(width) * height * 4;
|
||||
|
||||
// Create shared sampler on first call
|
||||
// Create shared sampler on first call (via sampler cache)
|
||||
if (!uiTextureSampler_) {
|
||||
VkSamplerCreateInfo si{};
|
||||
si.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
|
|
@ -1024,7 +1116,8 @@ VkDescriptorSet VkContext::uploadImGuiTexture(const uint8_t* rgba, int width, in
|
|||
si.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
si.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
if (vkCreateSampler(device, &si, nullptr, &uiTextureSampler_) != VK_SUCCESS) {
|
||||
uiTextureSampler_ = getOrCreateSampler(si);
|
||||
if (!uiTextureSampler_) {
|
||||
LOG_ERROR("Failed to create UI texture sampler");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ bool VkRenderTarget::create(VkContext& ctx, uint32_t width, uint32_t height,
|
|||
}
|
||||
}
|
||||
|
||||
// Create sampler (linear filtering, clamp to edge)
|
||||
// Create sampler (linear filtering, clamp to edge) via cache
|
||||
VkSamplerCreateInfo samplerInfo{};
|
||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
samplerInfo.minFilter = VK_FILTER_LINEAR;
|
||||
|
|
@ -61,11 +61,13 @@ bool VkRenderTarget::create(VkContext& ctx, uint32_t width, uint32_t height,
|
|||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = 0.0f;
|
||||
|
||||
if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler_) != VK_SUCCESS) {
|
||||
sampler_ = ctx.getOrCreateSampler(samplerInfo);
|
||||
if (sampler_ == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("VkRenderTarget: failed to create sampler");
|
||||
destroy(device, allocator);
|
||||
return false;
|
||||
}
|
||||
ownsSampler_ = false;
|
||||
|
||||
// Create render pass
|
||||
if (useMSAA) {
|
||||
|
|
@ -259,10 +261,11 @@ void VkRenderTarget::destroy(VkDevice device, VmaAllocator allocator) {
|
|||
vkDestroyRenderPass(device, renderPass_, nullptr);
|
||||
renderPass_ = VK_NULL_HANDLE;
|
||||
}
|
||||
if (sampler_) {
|
||||
if (sampler_ && ownsSampler_) {
|
||||
vkDestroySampler(device, sampler_, nullptr);
|
||||
sampler_ = VK_NULL_HANDLE;
|
||||
}
|
||||
sampler_ = VK_NULL_HANDLE;
|
||||
ownsSampler_ = true;
|
||||
destroyImage(device, allocator, resolveImage_);
|
||||
destroyImage(device, allocator, depthImage_);
|
||||
destroyImage(device, allocator, colorImage_);
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ VkTexture::~VkTexture() {
|
|||
}
|
||||
|
||||
VkTexture::VkTexture(VkTexture&& other) noexcept
|
||||
: image_(other.image_), sampler_(other.sampler_), mipLevels_(other.mipLevels_) {
|
||||
: image_(other.image_), sampler_(other.sampler_), mipLevels_(other.mipLevels_),
|
||||
ownsSampler_(other.ownsSampler_) {
|
||||
other.image_ = {};
|
||||
other.sampler_ = VK_NULL_HANDLE;
|
||||
other.ownsSampler_ = true;
|
||||
}
|
||||
|
||||
VkTexture& VkTexture::operator=(VkTexture&& other) noexcept {
|
||||
|
|
@ -23,8 +25,10 @@ VkTexture& VkTexture::operator=(VkTexture&& other) noexcept {
|
|||
image_ = other.image_;
|
||||
sampler_ = other.sampler_;
|
||||
mipLevels_ = other.mipLevels_;
|
||||
ownsSampler_ = other.ownsSampler_;
|
||||
other.image_ = {};
|
||||
other.sampler_ = VK_NULL_HANDLE;
|
||||
other.ownsSampler_ = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -214,11 +218,20 @@ bool VkTexture::createSampler(VkDevice device,
|
|||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = static_cast<float>(mipLevels_);
|
||||
|
||||
// Use sampler cache if VkContext is available.
|
||||
auto* ctx = VkContext::globalInstance();
|
||||
if (ctx) {
|
||||
sampler_ = ctx->getOrCreateSampler(samplerInfo);
|
||||
ownsSampler_ = false;
|
||||
return sampler_ != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Fallback: no VkContext (shouldn't happen in normal use).
|
||||
if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler_) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create texture sampler");
|
||||
return false;
|
||||
}
|
||||
|
||||
ownsSampler_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -246,11 +259,20 @@ bool VkTexture::createSampler(VkDevice device,
|
|||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = static_cast<float>(mipLevels_);
|
||||
|
||||
// Use sampler cache if VkContext is available.
|
||||
auto* ctx = VkContext::globalInstance();
|
||||
if (ctx) {
|
||||
sampler_ = ctx->getOrCreateSampler(samplerInfo);
|
||||
ownsSampler_ = false;
|
||||
return sampler_ != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Fallback: no VkContext (shouldn't happen in normal use).
|
||||
if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler_) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create texture sampler");
|
||||
return false;
|
||||
}
|
||||
|
||||
ownsSampler_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -269,19 +291,29 @@ bool VkTexture::createShadowSampler(VkDevice device) {
|
|||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = 1.0f;
|
||||
|
||||
// Use sampler cache if VkContext is available.
|
||||
auto* ctx = VkContext::globalInstance();
|
||||
if (ctx) {
|
||||
sampler_ = ctx->getOrCreateSampler(samplerInfo);
|
||||
ownsSampler_ = false;
|
||||
return sampler_ != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Fallback: no VkContext (shouldn't happen in normal use).
|
||||
if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler_) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create shadow sampler");
|
||||
return false;
|
||||
}
|
||||
|
||||
ownsSampler_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkTexture::destroy(VkDevice device, VmaAllocator allocator) {
|
||||
if (sampler_ != VK_NULL_HANDLE) {
|
||||
if (sampler_ != VK_NULL_HANDLE && ownsSampler_) {
|
||||
vkDestroySampler(device, sampler_, nullptr);
|
||||
sampler_ = VK_NULL_HANDLE;
|
||||
}
|
||||
sampler_ = VK_NULL_HANDLE;
|
||||
ownsSampler_ = true;
|
||||
destroyImage(device, allocator, image_);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -352,8 +352,8 @@ void WaterRenderer::destroySceneHistoryResources() {
|
|||
if (sh.depthImage) { vmaDestroyImage(vkCtx->getAllocator(), sh.depthImage, sh.depthAlloc); sh.depthImage = VK_NULL_HANDLE; sh.depthAlloc = VK_NULL_HANDLE; }
|
||||
sh.sceneSet = VK_NULL_HANDLE;
|
||||
}
|
||||
if (sceneColorSampler) { vkDestroySampler(device, sceneColorSampler, nullptr); sceneColorSampler = VK_NULL_HANDLE; }
|
||||
if (sceneDepthSampler) { vkDestroySampler(device, sceneDepthSampler, nullptr); sceneDepthSampler = VK_NULL_HANDLE; }
|
||||
sceneColorSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
sceneDepthSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
sceneHistoryExtent = {0, 0};
|
||||
sceneHistoryReady = false;
|
||||
}
|
||||
|
|
@ -374,13 +374,15 @@ void WaterRenderer::createSceneHistoryResources(VkExtent2D extent, VkFormat colo
|
|||
sampCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
if (vkCreateSampler(device, &sampCI, nullptr, &sceneColorSampler) != VK_SUCCESS) {
|
||||
sceneColorSampler = vkCtx->getOrCreateSampler(sampCI);
|
||||
if (sceneColorSampler == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("WaterRenderer: failed to create scene color sampler");
|
||||
return;
|
||||
}
|
||||
sampCI.magFilter = VK_FILTER_NEAREST;
|
||||
sampCI.minFilter = VK_FILTER_NEAREST;
|
||||
if (vkCreateSampler(device, &sampCI, nullptr, &sceneDepthSampler) != VK_SUCCESS) {
|
||||
sceneDepthSampler = vkCtx->getOrCreateSampler(sampCI);
|
||||
if (sceneDepthSampler == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("WaterRenderer: failed to create scene depth sampler");
|
||||
return;
|
||||
}
|
||||
|
|
@ -1718,7 +1720,8 @@ void WaterRenderer::createReflectionResources() {
|
|||
sampCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
if (vkCreateSampler(device, &sampCI, nullptr, &reflectionSampler) != VK_SUCCESS) {
|
||||
reflectionSampler = vkCtx->getOrCreateSampler(sampCI);
|
||||
if (reflectionSampler == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("WaterRenderer: failed to create reflection sampler");
|
||||
return;
|
||||
}
|
||||
|
|
@ -1848,7 +1851,7 @@ void WaterRenderer::destroyReflectionResources() {
|
|||
if (reflectionDepthView) { vkDestroyImageView(device, reflectionDepthView, nullptr); reflectionDepthView = VK_NULL_HANDLE; }
|
||||
if (reflectionColorImage) { vmaDestroyImage(allocator, reflectionColorImage, reflectionColorAlloc); reflectionColorImage = VK_NULL_HANDLE; }
|
||||
if (reflectionDepthImage) { vmaDestroyImage(allocator, reflectionDepthImage, reflectionDepthAlloc); reflectionDepthImage = VK_NULL_HANDLE; }
|
||||
if (reflectionSampler) { vkDestroySampler(device, reflectionSampler, nullptr); reflectionSampler = VK_NULL_HANDLE; }
|
||||
reflectionSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
if (reflectionUBO) {
|
||||
AllocatedBuffer ab{}; ab.buffer = reflectionUBO; ab.allocation = reflectionUBOAlloc;
|
||||
destroyBuffer(allocator, ab);
|
||||
|
|
|
|||
|
|
@ -915,7 +915,7 @@ bool AuthScreen::loadBackgroundImage() {
|
|||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
vkCreateSampler(device, &samplerInfo, nullptr, &bgSampler);
|
||||
bgSampler = bgVkCtx->getOrCreateSampler(samplerInfo);
|
||||
}
|
||||
|
||||
bgDescriptorSet = ImGui_ImplVulkan_AddTexture(bgSampler, bgImageView,
|
||||
|
|
@ -930,7 +930,7 @@ void AuthScreen::destroyBackgroundImage() {
|
|||
VkDevice device = bgVkCtx->getDevice();
|
||||
vkDeviceWaitIdle(device);
|
||||
if (bgDescriptorSet) { ImGui_ImplVulkan_RemoveTexture(bgDescriptorSet); bgDescriptorSet = VK_NULL_HANDLE; }
|
||||
if (bgSampler) { vkDestroySampler(device, bgSampler, nullptr); bgSampler = VK_NULL_HANDLE; }
|
||||
bgSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
||||
if (bgImageView) { vkDestroyImageView(device, bgImageView, nullptr); bgImageView = VK_NULL_HANDLE; }
|
||||
if (bgImage) { vkDestroyImage(device, bgImage, nullptr); bgImage = VK_NULL_HANDLE; }
|
||||
if (bgMemory) { vkFreeMemory(device, bgMemory, nullptr); bgMemory = VK_NULL_HANDLE; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue