Skip to content

Commit

Permalink
Vulkan: Use cache for sampler objects
Browse files Browse the repository at this point in the history
  • Loading branch information
Exzap committed Dec 16, 2024
1 parent 5823aee commit 2140e18
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ typedef struct
LattePerfStatCounter numGraphicPipelines;
LattePerfStatCounter numImages;
LattePerfStatCounter numImageViews;
LattePerfStatCounter numSamplers;
LattePerfStatCounter numRenderPass;
LattePerfStatCounter numFramebuffer;

Expand Down
50 changes: 39 additions & 11 deletions src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,20 @@ class VKRMoveableRefCounter

virtual ~VKRMoveableRefCounter()
{
cemu_assert_debug(refCount == 0);
cemu_assert_debug(m_refCount == 0);

// remove references
#ifdef CEMU_DEBUG_ASSERT
for (auto itr : refs)
{
auto& rev = itr->ref->reverseRefs;
rev.erase(std::remove(rev.begin(), rev.end(), this->selfRef), rev.end());
}
#endif
for (auto itr : refs)
itr->ref->refCount--;
{
itr->ref->m_refCount--;
if (itr->ref->m_refCount == 0)
itr->ref->RefCountReachedZero();
}
refs.clear();
delete selfRef;
selfRef = nullptr;
Expand All @@ -41,8 +43,8 @@ class VKRMoveableRefCounter
VKRMoveableRefCounter(VKRMoveableRefCounter&& rhs) noexcept
{
this->refs = std::move(rhs.refs);
this->refCount = rhs.refCount;
rhs.refCount = 0;
this->m_refCount = rhs.m_refCount;
rhs.m_refCount = 0;
this->selfRef = rhs.selfRef;
rhs.selfRef = nullptr;
this->selfRef->ref = this;
Expand All @@ -57,7 +59,7 @@ class VKRMoveableRefCounter
void addRef(VKRMoveableRefCounter* refTarget)
{
this->refs.emplace_back(refTarget->selfRef);
refTarget->refCount++;
refTarget->m_refCount++;

#ifdef CEMU_DEBUG_ASSERT
// add reverse ref
Expand All @@ -68,16 +70,23 @@ class VKRMoveableRefCounter
// methods to directly increment/decrement ref counter (for situations where no external object is available)
void incRef()
{
this->refCount++;
m_refCount++;
}

void decRef()
{
this->refCount--;
m_refCount--;
if (m_refCount == 0)
RefCountReachedZero();
}

protected:
int refCount{};
virtual void RefCountReachedZero()
{
// does nothing by default
}

int m_refCount{};
private:
VKRMoveableRefCounterRef* selfRef;
std::vector<VKRMoveableRefCounterRef*> refs;
Expand All @@ -88,7 +97,7 @@ class VKRMoveableRefCounter
void moveObj(VKRMoveableRefCounter&& rhs)
{
this->refs = std::move(rhs.refs);
this->refCount = rhs.refCount;
this->m_refCount = rhs.m_refCount;
this->selfRef = rhs.selfRef;
this->selfRef->ref = this;
}
Expand Down Expand Up @@ -131,6 +140,25 @@ class VKRObjectTextureView : public VKRDestructibleObject
VkSampler m_textureDefaultSampler[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; // relict from LatteTextureViewVk, get rid of it eventually
};


class VKRObjectSampler : public VKRDestructibleObject
{
public:
VKRObjectSampler(VkSamplerCreateInfo* samplerInfo);
~VKRObjectSampler() override;

static VKRObjectSampler* GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo);
static void DestroyCache();

void RefCountReachedZero() override; // sampler objects are destroyed when not referenced anymore

VkSampler GetSampler() const { return m_sampler; }
private:
static std::unordered_map<uint64, VKRObjectSampler*> s_samplerCache;
VkSampler m_sampler{ VK_NULL_HANDLE };
uint64 m_hash;
};

class VKRObjectRenderPass : public VKRDestructibleObject
{
public:
Expand Down
110 changes: 109 additions & 1 deletion src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,8 @@ VulkanRenderer::~VulkanRenderer()
if (m_commandPool != VK_NULL_HANDLE)
vkDestroyCommandPool(m_logicalDevice, m_commandPool, nullptr);

VKRObjectSampler::DestroyCache();

// destroy debug callback
if (m_debugCallback)
{
Expand Down Expand Up @@ -3707,6 +3709,7 @@ void VulkanRenderer::AppendOverlayDebugInfo()
ImGui::Text("DS StorageBuf %u", performanceMonitor.vk.numDescriptorStorageBuffers.get());
ImGui::Text("Images %u", performanceMonitor.vk.numImages.get());
ImGui::Text("ImageView %u", performanceMonitor.vk.numImageViews.get());
ImGui::Text("ImageSampler %u", performanceMonitor.vk.numSamplers.get());
ImGui::Text("RenderPass %u", performanceMonitor.vk.numRenderPass.get());
ImGui::Text("Framebuffer %u", performanceMonitor.vk.numFramebuffer.get());
m_spinlockDestructionQueue.lock();
Expand Down Expand Up @@ -3752,7 +3755,7 @@ void VKRDestructibleObject::flagForCurrentCommandBuffer()

bool VKRDestructibleObject::canDestroy()
{
if (refCount > 0)
if (m_refCount > 0)
return false;
return VulkanRenderer::GetInstance()->HasCommandBufferFinished(m_lastCmdBufferId);
}
Expand Down Expand Up @@ -3793,6 +3796,111 @@ VKRObjectTextureView::~VKRObjectTextureView()
performanceMonitor.vk.numImageViews.decrement();
}

static uint64 CalcHashSamplerCreateInfo(const VkSamplerCreateInfo& info)
{
uint64 h = 0xcbf29ce484222325ULL;
auto fnvHashCombine = [](uint64_t &h, auto val) {
using T = decltype(val);
static_assert(sizeof(T) <= 8);
uint64_t val64 = 0;
std::memcpy(&val64, &val, sizeof(val));
h ^= val64;
h *= 0x100000001b3ULL;
};
cemu_assert_debug(info.sType == VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO);
fnvHashCombine(h, info.flags);
fnvHashCombine(h, info.magFilter);
fnvHashCombine(h, info.minFilter);
fnvHashCombine(h, info.mipmapMode);
fnvHashCombine(h, info.addressModeU);
fnvHashCombine(h, info.addressModeV);
fnvHashCombine(h, info.addressModeW);
fnvHashCombine(h, info.mipLodBias);
fnvHashCombine(h, info.anisotropyEnable);
if(info.anisotropyEnable == VK_TRUE)
fnvHashCombine(h, info.maxAnisotropy);
fnvHashCombine(h, info.compareEnable);
if(info.compareEnable == VK_TRUE)
fnvHashCombine(h, info.compareOp);
fnvHashCombine(h, info.minLod);
fnvHashCombine(h, info.maxLod);
fnvHashCombine(h, info.borderColor);
fnvHashCombine(h, info.unnormalizedCoordinates);
// handle custom border color
VkBaseOutStructure* ext = (VkBaseOutStructure*)info.pNext;
while(ext)
{
if(ext->sType == VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT)
{
auto* extInfo = (VkSamplerCustomBorderColorCreateInfoEXT*)ext;
fnvHashCombine(h, extInfo->customBorderColor.uint32[0]);
fnvHashCombine(h, extInfo->customBorderColor.uint32[1]);
fnvHashCombine(h, extInfo->customBorderColor.uint32[2]);
fnvHashCombine(h, extInfo->customBorderColor.uint32[3]);
}
else
{
cemu_assert_unimplemented();
}
ext = ext->pNext;
}
return h;
}

std::unordered_map<uint64, VKRObjectSampler*> VKRObjectSampler::s_samplerCache;

VKRObjectSampler::VKRObjectSampler(VkSamplerCreateInfo* samplerInfo)
{
auto* vulkanRenderer = VulkanRenderer::GetInstance();
if (vkCreateSampler(vulkanRenderer->GetLogicalDevice(), samplerInfo, nullptr, &m_sampler) != VK_SUCCESS)
vulkanRenderer->UnrecoverableError("Failed to create texture sampler");
performanceMonitor.vk.numSamplers.increment();
m_hash = CalcHashSamplerCreateInfo(*samplerInfo);
}

VKRObjectSampler::~VKRObjectSampler()
{
vkDestroySampler(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_sampler, nullptr);
performanceMonitor.vk.numSamplers.decrement();
// remove from cache
auto it = s_samplerCache.find(m_hash);
if(it != s_samplerCache.end())
s_samplerCache.erase(it);
}

void VKRObjectSampler::RefCountReachedZero()
{
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(this);
}

VKRObjectSampler* VKRObjectSampler::GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo)
{
auto* vulkanRenderer = VulkanRenderer::GetInstance();
uint64 hash = CalcHashSamplerCreateInfo(*samplerInfo);
auto it = s_samplerCache.find(hash);
if (it != s_samplerCache.end())
{
auto* sampler = it->second;
return sampler;
}
auto* sampler = new VKRObjectSampler(samplerInfo);
s_samplerCache[hash] = sampler;
return sampler;
}

void VKRObjectSampler::DestroyCache()
{
// assuming all other objects which depend on vkSampler are destroyed, this cache should also have been emptied already
// but just to be sure lets still clear the cache
cemu_assert_debug(s_samplerCache.empty());
for(auto& sampler : s_samplerCache)
{
cemu_assert_debug(sampler.second->m_refCount == 0);
delete sampler.second;
}
s_samplerCache.clear();
}

VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount)
{
// generate helper hash for pipeline state
Expand Down
8 changes: 3 additions & 5 deletions src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,6 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*

VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{};

VkSampler sampler;
VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;

Expand Down Expand Up @@ -899,10 +898,9 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
}
}
}

if (vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &sampler) != VK_SUCCESS)
UnrecoverableError("Failed to create texture sampler");
info.sampler = sampler;
VKRObjectSampler* samplerObj = VKRObjectSampler::GetOrCreateSampler(&samplerInfo);
vkObjDS->addRef(samplerObj);
info.sampler = samplerObj->GetSampler();
textureArray.emplace_back(info);
}

Expand Down

0 comments on commit 2140e18

Please sign in to comment.