mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Perf] (MG_Backend/DirectVulkan): cache resolved VkSampler per binding to skip the per-draw sampler key hash, keyed on a new sampler lifetime id
This commit is contained in:
@@ -107,6 +107,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
|
||||
m_frameCount = frameCount;
|
||||
m_maxBindings = maxBindings;
|
||||
m_samplerResolveMemo.assign(m_maxBindings, SamplerResolveMemo{});
|
||||
m_setsPerFrame = setsPerFrame;
|
||||
m_peakDescriptorSetsObserved = 0;
|
||||
m_textureManager = textureManager;
|
||||
@@ -196,6 +197,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// The frame's descriptor sets are recycled above, so last frame's reuse target
|
||||
// is gone: start the per-draw descriptor-reuse cache fresh this frame.
|
||||
m_hasLastDescriptor = false;
|
||||
// Re-fingerprint the bound sampler set fresh this frame so any GL object address
|
||||
// reuse cannot outlive a single frame (see SamplerResolveMemo).
|
||||
for (auto& memo : m_samplerResolveMemo) {
|
||||
memo.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
||||
@@ -274,8 +280,37 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
"ResolveSamplerDescriptor: invalid sampled image layout=%d for textureId=%d, binding=%u",
|
||||
static_cast<Int>(resource->layout), texture->GetExternalIndex(), binding);
|
||||
}
|
||||
// Skip GetOrCreateSampler's per-draw key hash + map lookup when this binding's
|
||||
// sampler object and texture (both by lifetime id + version) are unchanged from the
|
||||
// last draw that resolved it: the resulting sampler key, and therefore the VkSampler
|
||||
// handle, are guaranteed identical. Lifetime ids are never reused, so a freed-and-
|
||||
// reallocated sampler/texture at the same address gets a fresh id and misses instead
|
||||
// of false-hitting. Cached handles live until Shutdown, so the memo can never hand
|
||||
// back a destroyed sampler.
|
||||
VkSampler resolvedSampler = VK_NULL_HANDLE;
|
||||
if (binding < m_samplerResolveMemo.size()) {
|
||||
auto& memo = m_samplerResolveMemo[binding];
|
||||
const Uint64 samplerLifetimeId = samplerToUse->GetLifetimeId();
|
||||
const Uint16 samplerVersion = samplerToUse->GetVersion();
|
||||
const Uint64 textureLifetimeId = texture->GetLifetimeId();
|
||||
const Uint16 textureParamsVersion = texture->GetTextureParamsVersion();
|
||||
if (memo.valid && memo.samplerLifetimeId == samplerLifetimeId && memo.samplerVersion == samplerVersion &&
|
||||
memo.textureLifetimeId == textureLifetimeId && memo.textureParamsVersion == textureParamsVersion) {
|
||||
resolvedSampler = memo.sampler;
|
||||
} else {
|
||||
resolvedSampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture);
|
||||
memo.samplerLifetimeId = samplerLifetimeId;
|
||||
memo.samplerVersion = samplerVersion;
|
||||
memo.textureLifetimeId = textureLifetimeId;
|
||||
memo.textureParamsVersion = textureParamsVersion;
|
||||
memo.sampler = resolvedSampler;
|
||||
memo.valid = true;
|
||||
}
|
||||
} else {
|
||||
resolvedSampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture);
|
||||
}
|
||||
outImageInfo = {
|
||||
.sampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture),
|
||||
.sampler = resolvedSampler,
|
||||
.imageView = resource->sampledView != VK_NULL_HANDLE ? resource->sampledView : resource->fullView,
|
||||
.imageLayout = resource->layout,
|
||||
};
|
||||
|
||||
@@ -146,6 +146,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkDescriptorSet m_lastBoundDescriptorSet = VK_NULL_HANDLE;
|
||||
Uint64 m_lastDescriptorSignature = 0;
|
||||
Bool m_hasLastDescriptor = false;
|
||||
|
||||
// Per-binding fast path over VkSamplerManager's content-hashed sampler cache, which
|
||||
// stays the source of truth: its key hashes all sampler+texture state, so two distinct
|
||||
// sampler objects with identical state still resolve to one VkSampler. This memo only
|
||||
// skips recomputing that hash. Across a draw batch the bound sampler set is stable, so a
|
||||
// binding whose sampler (lifetime id + version, bumped on every setter) and texture
|
||||
// (lifetime id + params version, bumped on the format/border-color setters that feed the
|
||||
// key) are unchanged recycles the VkSampler it resolved last draw; a param change bumps
|
||||
// a version and forces a re-resolve. Both objects are keyed by a never-reused monotonic
|
||||
// lifetime id, so a freed-and-reallocated sampler or texture at the same heap address
|
||||
// always gets a fresh id and misses (a raw pointer would false-hit that ABA) - so a
|
||||
// stale guess can only miss and fall through to the hash, never resolve wrong. Still
|
||||
// reset each frame alongside the descriptor-set cache. Indexed by binding.
|
||||
struct SamplerResolveMemo {
|
||||
Uint64 samplerLifetimeId = 0;
|
||||
Uint64 textureLifetimeId = 0;
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
Uint16 samplerVersion = 0;
|
||||
Uint16 textureParamsVersion = 0;
|
||||
Bool valid = false;
|
||||
};
|
||||
mutable Vector<SamplerResolveMemo> m_samplerResolveMemo;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
|
||||
@@ -8,10 +8,19 @@
|
||||
|
||||
#include "SamplerObject.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State {
|
||||
namespace GLState {
|
||||
SamplerObject::SamplerObject(Uint externalIndex) : m_externalIndex(externalIndex) {}
|
||||
static std::atomic<Uint64> s_nextSamplerLifetimeId = 1;
|
||||
|
||||
Uint64 SamplerObject::AllocateLifetimeId() {
|
||||
return s_nextSamplerLifetimeId.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
SamplerObject::SamplerObject(Uint externalIndex)
|
||||
: m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()) {}
|
||||
|
||||
void SamplerObject::SetWrapS(SamplerWrapMode mode) {
|
||||
if (mode == m_samplerParameters.wrapS) return;
|
||||
@@ -138,6 +147,10 @@ namespace MobileGL {
|
||||
Uint16 SamplerObject::GetVersion() const {
|
||||
return m_version;
|
||||
}
|
||||
|
||||
Uint64 SamplerObject::GetLifetimeId() const {
|
||||
return m_lifetimeId;
|
||||
}
|
||||
} // namespace GLState
|
||||
} // namespace MG_State
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -99,10 +99,18 @@ namespace MobileGL {
|
||||
SamplerCompareFunc GetSamplerCompareFunc() const;
|
||||
Uint GetExternalIndex() const;
|
||||
Uint16 GetVersion() const;
|
||||
// Globally-unique, never-reused id for this sampler object's lifetime. Lets a
|
||||
// cache distinguish a freed-and-reallocated sampler (same heap address, GL name,
|
||||
// or version count) from the original - the sampler analogue of the texture
|
||||
// lifetime id. Used by the Vulkan backend's per-binding sampler fast path.
|
||||
Uint64 GetLifetimeId() const;
|
||||
const SamplerParameters& GetAllSamplerParameters() const;
|
||||
|
||||
private:
|
||||
static Uint64 AllocateLifetimeId();
|
||||
|
||||
const Uint m_externalIndex;
|
||||
const Uint64 m_lifetimeId;
|
||||
Uint16 m_version = 0;
|
||||
SamplerParameters m_samplerParameters;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user