mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +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_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
|
||||||
m_frameCount = frameCount;
|
m_frameCount = frameCount;
|
||||||
m_maxBindings = maxBindings;
|
m_maxBindings = maxBindings;
|
||||||
|
m_samplerResolveMemo.assign(m_maxBindings, SamplerResolveMemo{});
|
||||||
m_setsPerFrame = setsPerFrame;
|
m_setsPerFrame = setsPerFrame;
|
||||||
m_peakDescriptorSetsObserved = 0;
|
m_peakDescriptorSetsObserved = 0;
|
||||||
m_textureManager = textureManager;
|
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
|
// 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.
|
// is gone: start the per-draw descriptor-reuse cache fresh this frame.
|
||||||
m_hasLastDescriptor = false;
|
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,
|
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",
|
"ResolveSamplerDescriptor: invalid sampled image layout=%d for textureId=%d, binding=%u",
|
||||||
static_cast<Int>(resource->layout), texture->GetExternalIndex(), binding);
|
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 = {
|
outImageInfo = {
|
||||||
.sampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture),
|
.sampler = resolvedSampler,
|
||||||
.imageView = resource->sampledView != VK_NULL_HANDLE ? resource->sampledView : resource->fullView,
|
.imageView = resource->sampledView != VK_NULL_HANDLE ? resource->sampledView : resource->fullView,
|
||||||
.imageLayout = resource->layout,
|
.imageLayout = resource->layout,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -146,6 +146,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkDescriptorSet m_lastBoundDescriptorSet = VK_NULL_HANDLE;
|
VkDescriptorSet m_lastBoundDescriptorSet = VK_NULL_HANDLE;
|
||||||
Uint64 m_lastDescriptorSignature = 0;
|
Uint64 m_lastDescriptorSignature = 0;
|
||||||
Bool m_hasLastDescriptor = false;
|
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
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,19 @@
|
|||||||
|
|
||||||
#include "SamplerObject.h"
|
#include "SamplerObject.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
namespace MG_State {
|
namespace MG_State {
|
||||||
namespace GLState {
|
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) {
|
void SamplerObject::SetWrapS(SamplerWrapMode mode) {
|
||||||
if (mode == m_samplerParameters.wrapS) return;
|
if (mode == m_samplerParameters.wrapS) return;
|
||||||
@@ -138,6 +147,10 @@ namespace MobileGL {
|
|||||||
Uint16 SamplerObject::GetVersion() const {
|
Uint16 SamplerObject::GetVersion() const {
|
||||||
return m_version;
|
return m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uint64 SamplerObject::GetLifetimeId() const {
|
||||||
|
return m_lifetimeId;
|
||||||
|
}
|
||||||
} // namespace GLState
|
} // namespace GLState
|
||||||
} // namespace MG_State
|
} // namespace MG_State
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
@@ -99,10 +99,18 @@ namespace MobileGL {
|
|||||||
SamplerCompareFunc GetSamplerCompareFunc() const;
|
SamplerCompareFunc GetSamplerCompareFunc() const;
|
||||||
Uint GetExternalIndex() const;
|
Uint GetExternalIndex() const;
|
||||||
Uint16 GetVersion() 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;
|
const SamplerParameters& GetAllSamplerParameters() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static Uint64 AllocateLifetimeId();
|
||||||
|
|
||||||
const Uint m_externalIndex;
|
const Uint m_externalIndex;
|
||||||
|
const Uint64 m_lifetimeId;
|
||||||
Uint16 m_version = 0;
|
Uint16 m_version = 0;
|
||||||
SamplerParameters m_samplerParameters;
|
SamplerParameters m_samplerParameters;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user