mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Perf] (MG_Backend/DirectVulkan, MG_State): stop re-deriving per-draw state
- Memoize the program content hash on ProgramObject (keyed by the backend state version + compile flags; relinking and binding changes invalidate it) and the vertex-input hash on VertexArrayObject (keyed by a new aggregate config version bumped by every attribute mutation). Full-SPIRV XXH64 hashing fell from 13.7% to 1.4% of the render thread. - ProgramObject also gains a link version and a global-UBO content version (bumped by uniform writes and on relink, wrap-safe around the backends' "never uploaded" sentinel) for backends to gate uploads and link caches. - Reuse member scratch vectors in SetupDraw, UploadAndBindVertexBuffers, GetOrCreatePipeline and BindProgramUniformBuffers instead of allocating per draw (~12% of render-thread time was in the allocator). - Replace hot-path dynamic_cast with AsMipmapTexture (storage-type tag + static_cast); TextureObjectMipmap is the only Mipmap-tagged branch. - Register/prune texture aliases only when a new (texture, lifetimeId) identity appears instead of scanning the entire alive map on every sampled-texture sync. - Make the fallback VkPresentModeKHR log strings report the actual mode. Vanilla render-thread share of libMobileGL dropped from 48% to 35% on DirectVulkan (simpleperf, Adreno 830). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1666,7 +1666,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(
|
||||
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) {
|
||||
const HashType hash = ComputeHash(program, flags);
|
||||
// Hashing the full SPIR-V of every stage is far too expensive to repeat per draw;
|
||||
// reuse the program's memoized hash while its backend state version is unchanged.
|
||||
HashType hash = 0;
|
||||
if (!program.GetBackendHashMemo(flags.GetRaw(), hash)) {
|
||||
hash = ComputeHash(program, flags);
|
||||
program.SetBackendHashMemo(flags.GetRaw(), hash);
|
||||
}
|
||||
auto it = m_cache.find(hash);
|
||||
if (it != m_cache.end()) {
|
||||
return it->second;
|
||||
|
||||
@@ -31,8 +31,19 @@ static const char* string_VkColorSpaceKHR(VkColorSpaceKHR) {
|
||||
return "VkColorSpaceKHR(unknown)";
|
||||
}
|
||||
|
||||
static const char* string_VkPresentModeKHR(VkPresentModeKHR) {
|
||||
return "VkPresentModeKHR(unknown)";
|
||||
static const char* string_VkPresentModeKHR(VkPresentModeKHR presentMode) {
|
||||
switch (presentMode) {
|
||||
case VK_PRESENT_MODE_IMMEDIATE_KHR:
|
||||
return "VK_PRESENT_MODE_IMMEDIATE_KHR";
|
||||
case VK_PRESENT_MODE_MAILBOX_KHR:
|
||||
return "VK_PRESENT_MODE_MAILBOX_KHR";
|
||||
case VK_PRESENT_MODE_FIFO_KHR:
|
||||
return "VK_PRESENT_MODE_FIFO_KHR";
|
||||
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
|
||||
return "VK_PRESENT_MODE_FIFO_RELAXED_KHR";
|
||||
default:
|
||||
return "VkPresentModeKHR(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* string_VkSurfaceTransformFlagBitsKHR(VkSurfaceTransformFlagBitsKHR) {
|
||||
|
||||
@@ -822,12 +822,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MOBILEGL_ASSERT(m_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null");
|
||||
MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer manager is null");
|
||||
|
||||
Vector<VkWriteDescriptorSet> writes;
|
||||
auto& writes = m_writesScratch;
|
||||
auto& bufferInfos = m_bufferInfosScratch;
|
||||
auto& imageInfos = m_imageInfosScratch;
|
||||
auto& texelBufferViews = m_texelBufferViewsScratch;
|
||||
auto& dynamicOffsets = m_dynamicOffsetsScratch;
|
||||
writes.clear();
|
||||
bufferInfos.clear();
|
||||
imageInfos.clear();
|
||||
texelBufferViews.clear();
|
||||
dynamicOffsets.clear();
|
||||
writes.reserve(m_maxBindings);
|
||||
Vector<VkDescriptorBufferInfo> bufferInfos;
|
||||
Vector<VkDescriptorImageInfo> imageInfos;
|
||||
Vector<VkBufferView> texelBufferViews;
|
||||
Vector<Uint32> dynamicOffsets;
|
||||
bufferInfos.reserve(m_maxBindings);
|
||||
imageInfos.reserve(m_maxBindings);
|
||||
texelBufferViews.reserve(m_maxBindings);
|
||||
|
||||
@@ -113,6 +113,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkTextureManager* m_textureManager = nullptr;
|
||||
VkSamplerManager* m_samplerManager = nullptr;
|
||||
mutable SharedPtr<MG_State::GLState::ITextureObject> m_fallbackTexture2D;
|
||||
|
||||
// Per-draw scratch buffers for BindProgramUniformBuffers: reused (clear keeps
|
||||
// capacity) so the descriptor-write path stops allocating on every draw.
|
||||
Vector<VkWriteDescriptorSet> m_writesScratch;
|
||||
Vector<VkDescriptorBufferInfo> m_bufferInfosScratch;
|
||||
Vector<VkDescriptorImageInfo> m_imageInfosScratch;
|
||||
Vector<VkBufferView> m_texelBufferViewsScratch;
|
||||
Vector<Uint32> m_dynamicOffsetsScratch;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
|
||||
@@ -38,10 +38,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return XXH64_digest(m_hashState);
|
||||
}
|
||||
|
||||
VertexInputStateFactory::HashType VertexInputStateFactory::GetOrComputeHash(
|
||||
const MG_State::GLState::VertexArrayObject& vao) const {
|
||||
HashType hash = 0;
|
||||
if (!vao.GetBackendHashMemo(hash)) {
|
||||
hash = ComputeHash(vao);
|
||||
vao.SetBackendHashMemo(hash);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||
const MG_State::GLState::VertexArrayObject& vao) {
|
||||
const HashType hash = ComputeHash(vao);
|
||||
return GetOrCreateVertexInputState(vao, hash);
|
||||
return GetOrCreateVertexInputState(vao, GetOrComputeHash(vao));
|
||||
}
|
||||
|
||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||
|
||||
@@ -38,6 +38,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
|
||||
|
||||
HashType ComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
||||
// Memoized ComputeHash: reuses the VAO's cached hash while its config version
|
||||
// is unchanged. Use this on per-draw paths.
|
||||
HashType GetOrComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
||||
const BackendVertexInputState& GetOrCreateVertexInputState(
|
||||
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
||||
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
||||
|
||||
@@ -675,7 +675,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
Vector<TextureIdentity> staleAliases;
|
||||
staleAliases.reserve(m_aliveObjects.size());
|
||||
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
|
||||
if (it->first.texture != texture) {
|
||||
continue;
|
||||
@@ -698,12 +697,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
auto aliveIt = m_aliveObjects.find(identity);
|
||||
if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) {
|
||||
EraseTrackedTexture(aliveIt->first);
|
||||
aliveIt = m_aliveObjects.end();
|
||||
}
|
||||
|
||||
const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex());
|
||||
if (liveTexture && liveTexture.get() == &texture) {
|
||||
m_aliveObjects[identity] = WeakPtr<MG_State::GLState::ITextureObject>(liveTexture);
|
||||
PruneStaleTextureAliases(&texture);
|
||||
// Only (re)register and prune when this (texture, lifetime) pair is new: stale
|
||||
// aliases can only come into existence through an address reuse, which by
|
||||
// construction introduces a new identity. Doing this unconditionally made every
|
||||
// sampled-texture sync scan the entire alive-texture map per draw.
|
||||
if (aliveIt == m_aliveObjects.end()) {
|
||||
const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex());
|
||||
if (liveTexture && liveTexture.get() == &texture) {
|
||||
m_aliveObjects[identity] = WeakPtr<MG_State::GLState::ITextureObject>(liveTexture);
|
||||
PruneStaleTextureAliases(&texture);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = m_textureResources.find(identity);
|
||||
@@ -1033,7 +1039,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* mipTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(&texture);
|
||||
auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture);
|
||||
if (!mipTexture) {
|
||||
MGLOG_D("%s: not TextureObjectMipmap", __func__);
|
||||
return false;
|
||||
@@ -1579,7 +1585,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
IntVec3& outTexelSize,
|
||||
SizeT& outByteSize,
|
||||
Uint32& outMipLevelCount) {
|
||||
const auto* mipTexture = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(&texture);
|
||||
const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture);
|
||||
if (!mipTexture) {
|
||||
MGLOG_D("%s: not TextureObjectMipmap", __func__);
|
||||
return false;
|
||||
@@ -1639,7 +1645,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MOBILEGL_ASSERT(mipLevels > 0, "ResolveViewMipRange: mipLevels must be > 0");
|
||||
|
||||
Uint32 definedMipLevels = mipLevels;
|
||||
if (const auto* mipTexture = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(&texture)) {
|
||||
if (const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture)) {
|
||||
const auto& targets = texture.GetUploadTargets();
|
||||
for (const auto target : targets) {
|
||||
const Uint32 uploadMipLevels = GetUploadMipLevelCount(*mipTexture, target);
|
||||
|
||||
@@ -2077,8 +2077,10 @@ void main() {
|
||||
|
||||
const auto bindingCount = vertexInputState.bindings.size() + static_cast<SizeT>(std::popcount(missingAttribMask));
|
||||
|
||||
Vector<VkBuffer> vkBuffers(bindingCount, VK_NULL_HANDLE);
|
||||
Vector<VkDeviceSize> vkOffsets(bindingCount, 0);
|
||||
auto& vkBuffers = m_vertexBuffersScratch;
|
||||
auto& vkOffsets = m_vertexOffsetsScratch;
|
||||
vkBuffers.assign(bindingCount, VK_NULL_HANDLE);
|
||||
vkOffsets.assign(bindingCount, 0);
|
||||
|
||||
auto findBufferByKey = [&](SizeT bufferKey) -> const MG_State::GLState::BufferObject* {
|
||||
const auto& attrs = vao.GetAllAttributes();
|
||||
@@ -2690,6 +2692,7 @@ void main() {
|
||||
MOBILEGL_ASSERT(offset + size <= m_depthMipmapResources.program->GetUBOSize(),
|
||||
"GenerateDepthMipmapWithShader: uniform write out of bounds");
|
||||
memcpy(depthProgramData + offset, data, size);
|
||||
m_depthMipmapResources.program->MarkUBOContentDirty();
|
||||
};
|
||||
|
||||
for (Uint32 level = baseMipLevel + 1; level < generateMipLevelCount; ++level) {
|
||||
@@ -2843,12 +2846,13 @@ void main() {
|
||||
}
|
||||
#endif
|
||||
|
||||
auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao);
|
||||
auto vertexInputHash = m_vertexInputStateFactory->GetOrComputeHash(vao);
|
||||
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao, vertexInputHash);
|
||||
const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vis.attributes);
|
||||
const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask;
|
||||
const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask;
|
||||
Vector<VkVertexInputAttributeDescription> patchedAttributes = vis.attributes;
|
||||
auto& patchedAttributes = m_patchedAttributesScratch;
|
||||
patchedAttributes.assign(vis.attributes.begin(), vis.attributes.end());
|
||||
Bool hasPatchedVertexAttributes = false;
|
||||
for (auto& attribute : patchedAttributes) {
|
||||
if (attribute.location >= 32 || (activeAttribMask & (1u << attribute.location)) == 0) {
|
||||
@@ -3188,7 +3192,7 @@ void main() {
|
||||
// which probably indicates it's been gone through codepath like `fbo attach` -> `clear` -> `fbo detach`, and
|
||||
// without draws in between to give it a chance to materialize such clear.
|
||||
// Deal with this situation here.
|
||||
Vector<MG_State::GLState::ITextureObject*> sampledTextures;
|
||||
auto& sampledTextures = m_sampledTexturesScratch; // cleared by CollectSampledTextures
|
||||
Bool hasSampledTextures = m_uniformManager->CollectSampledTextures(program, programObj, sampledTextures);
|
||||
MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__);
|
||||
MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s",
|
||||
@@ -3848,6 +3852,7 @@ void main() {
|
||||
writeUniform(m_blitResources.dstRectLocation, blitUniformData.dstRect, sizeof(blitUniformData.dstRect));
|
||||
writeUniform(m_blitResources.surfaceTransformLocation, &blitUniformData.surfaceTransform,
|
||||
sizeof(blitUniformData.surfaceTransform));
|
||||
m_blitResources.program->MarkUBOContentDirty();
|
||||
|
||||
const auto samplerBindingOverride = UniformManager::SamplerBindingOverride{
|
||||
.binding = m_blitResources.samplerBinding,
|
||||
@@ -4875,7 +4880,7 @@ void main() {
|
||||
MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture.");
|
||||
MOBILEGL_ASSERT(texture->IsComplete(), "GenerateMipmap requires a complete texture.");
|
||||
|
||||
auto* mipmapTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(texture.get());
|
||||
auto* mipmapTexture = MG_State::GLState::AsMipmapTexture(texture.get());
|
||||
MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires a mipmapped texture object.");
|
||||
|
||||
const Uint32 currentMipLevelCount = static_cast<Uint32>(mipmapTexture->GetMipmapLevelCount());
|
||||
|
||||
@@ -272,6 +272,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
DepthMipmapResources m_depthMipmapResources;
|
||||
Vector<DeferredDepthMipmapCleanup> m_deferredDepthMipmapCleanup;
|
||||
|
||||
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
||||
// draw call and must not allocate.
|
||||
Vector<MG_State::GLState::ITextureObject*> m_sampledTexturesScratch;
|
||||
Vector<VkBuffer> m_vertexBuffersScratch;
|
||||
Vector<VkDeviceSize> m_vertexOffsetsScratch;
|
||||
Vector<VkVertexInputAttributeDescription> m_patchedAttributesScratch;
|
||||
|
||||
void CreateInstance();
|
||||
VkResult SetupDebugMessenger();
|
||||
VkResult DestroyDebugMessenger();
|
||||
|
||||
@@ -105,6 +105,14 @@ namespace {
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
void ProgramObject::ResetLinkArtifacts() {
|
||||
// Relinking regenerates the SPIR-V, so any backend-cached state keyed on
|
||||
// m_backendStateVersion (e.g. the content-hash memo) must be invalidated,
|
||||
// along with every link-derived backend cache (m_linkVersion) and the
|
||||
// last-uploaded-UBO gate (a relink resets uniforms to their initial values,
|
||||
// and that reset must reach the GPU).
|
||||
++m_backendStateVersion;
|
||||
++m_linkVersion;
|
||||
MarkUBOContentDirty();
|
||||
m_program.reset();
|
||||
m_generatedSpirv.clear();
|
||||
m_uniformLocations.clear();
|
||||
|
||||
@@ -170,7 +170,33 @@ namespace MobileGL::MG_State::GLState {
|
||||
void* MapUBO() { return m_globalUboScratch.data(); }
|
||||
const void* GetUBOData() const { return m_globalUboScratch.data(); }
|
||||
Uint GetUBOSize() const { return static_cast<Uint>(m_globalUboScratch.size()); }
|
||||
// Content version of the CPU-side global-UBO shadow: writers bump it so backends
|
||||
// can skip re-uploading an unchanged UBO on every draw. ~0u is reserved as the
|
||||
// backends' "never uploaded" sentinel, so skip over it on wrap.
|
||||
Uint32 GetUBOContentVersion() const { return m_uboContentVersion; }
|
||||
void MarkUBOContentDirty() {
|
||||
if (++m_uboContentVersion == ~0u) m_uboContentVersion = 0;
|
||||
}
|
||||
Uint32 GetBackendStateVersion() const { return m_backendStateVersion; }
|
||||
// Bumped only by (re)linking — lets backends detect that every piece of
|
||||
// link-derived reflection (locations, block order, UBO layout) is stale.
|
||||
Uint32 GetLinkVersion() const { return m_linkVersion; }
|
||||
|
||||
// Content-hash memo for backends: avoids re-hashing the generated SPIR-V on every
|
||||
// draw. The memo is keyed by (backendStateVersion, flags); ResetLinkArtifacts and
|
||||
// the binding setters below invalidate it by bumping m_backendStateVersion.
|
||||
Bool GetBackendHashMemo(Uint flags, Uint64& outHash) const {
|
||||
if (m_backendHashMemoVersion != m_backendStateVersion || m_backendHashMemoFlags != flags) {
|
||||
return false;
|
||||
}
|
||||
outHash = m_backendHashMemo;
|
||||
return true;
|
||||
}
|
||||
void SetBackendHashMemo(Uint flags, Uint64 hash) const {
|
||||
m_backendHashMemo = hash;
|
||||
m_backendHashMemoVersion = m_backendStateVersion;
|
||||
m_backendHashMemoFlags = flags;
|
||||
}
|
||||
|
||||
void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
|
||||
if (location >= m_uniformSamplerOrImageUnitIndex.size() ||
|
||||
@@ -308,5 +334,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
Bool m_linkStatus = false;
|
||||
Bool m_validateStatus = true;
|
||||
Uint32 m_backendStateVersion = 0;
|
||||
|
||||
// Backend-owned content-hash memo (see GetBackendHashMemo): valid only while
|
||||
// m_backendStateVersion and the compile flags match the recorded values.
|
||||
mutable Uint64 m_backendHashMemo = 0;
|
||||
mutable Uint32 m_backendHashMemoVersion = ~0u;
|
||||
mutable Uint m_backendHashMemoFlags = 0;
|
||||
Uint32 m_uboContentVersion = 0;
|
||||
Uint32 m_linkVersion = 0;
|
||||
};
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
@@ -128,6 +128,20 @@ namespace MobileGL::MG_State::GLState {
|
||||
virtual Bool IsStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel) const = 0;
|
||||
};
|
||||
|
||||
// Cheap replacement for dynamic_cast on the hot path: TextureObjectMipmap is the
|
||||
// only hierarchy branch whose storage type reports Mipmap, so the tag check makes
|
||||
// the static_cast safe.
|
||||
inline TextureObjectMipmap* AsMipmapTexture(ITextureObject* texture) {
|
||||
return (texture && texture->GetStorageType() == TextureStorageType::Mipmap)
|
||||
? static_cast<TextureObjectMipmap*>(texture)
|
||||
: nullptr;
|
||||
}
|
||||
inline const TextureObjectMipmap* AsMipmapTexture(const ITextureObject* texture) {
|
||||
return (texture && texture->GetStorageType() == TextureStorageType::Mipmap)
|
||||
? static_cast<const TextureObjectMipmap*>(texture)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
class TextureObjectWithOneMipmap : public TextureObjectMipmap {
|
||||
public:
|
||||
TextureObjectWithOneMipmap(TextureTarget target, Uint externalIndex)
|
||||
|
||||
@@ -207,16 +207,19 @@ namespace MobileGL::MG_State::GLState {
|
||||
void VertexArrayObject::BumpAttributeFormatVersion(Uint index) {
|
||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||
++m_attributeVersions[index].FormatVersion;
|
||||
++m_configVersion;
|
||||
}
|
||||
|
||||
void VertexArrayObject::BumpAttributeBufferVersion(Uint index) {
|
||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||
++m_attributeVersions[index].BufferVersion;
|
||||
++m_configVersion;
|
||||
}
|
||||
|
||||
void VertexArrayObject::BumpAttributeSwitchVersion(Uint index) {
|
||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||
++m_attributeVersions[index].SwitchVersion;
|
||||
++m_configVersion;
|
||||
}
|
||||
|
||||
const VertexAttributeVersion& VertexArrayObject::GetAttributeVersion(Uint index) const {
|
||||
|
||||
@@ -81,6 +81,23 @@ namespace MobileGL {
|
||||
const VertexAttributeVersion& GetAttributeVersion(Uint index) const;
|
||||
const Array<VertexAttributeVersion, MAX_VERTEX_ATTRIBS>& GetAllAttributeVersions() const;
|
||||
|
||||
// Aggregate of every per-attribute version bump; lets backends detect
|
||||
// "any vertex-input state changed" with one compare.
|
||||
Uint32 GetConfigVersion() const { return m_configVersion; }
|
||||
|
||||
// Backend-owned content-hash memo, valid while the config version matches
|
||||
// (same idea as ProgramObject's hash memo — avoids re-hashing all
|
||||
// attributes on every draw).
|
||||
Bool GetBackendHashMemo(Uint64& outHash) const {
|
||||
if (m_backendHashMemoVersion != m_configVersion) return false;
|
||||
outHash = m_backendHashMemo;
|
||||
return true;
|
||||
}
|
||||
void SetBackendHashMemo(Uint64 hash) const {
|
||||
m_backendHashMemo = hash;
|
||||
m_backendHashMemoVersion = m_configVersion;
|
||||
}
|
||||
|
||||
private:
|
||||
void BumpAttributeFormatVersion(Uint index);
|
||||
void BumpAttributeBufferVersion(Uint index);
|
||||
@@ -100,6 +117,10 @@ namespace MobileGL {
|
||||
// ARB_vertex_attrib_binding API; only such attributes are re-resolved, so the
|
||||
// classic glVertexAttribPointer path keeps its exact historical behavior.
|
||||
Array<Bool, MAX_VERTEX_ATTRIBS> m_attributeUsesBindingModel = {};
|
||||
|
||||
Uint32 m_configVersion = 0;
|
||||
mutable Uint64 m_backendHashMemo = 0;
|
||||
mutable Uint32 m_backendHashMemoVersion = ~0u;
|
||||
};
|
||||
} // namespace GLState
|
||||
} // namespace MG_State
|
||||
|
||||
@@ -336,6 +336,8 @@ namespace MobileGL {
|
||||
|
||||
operator Bool() const { return Any(); }
|
||||
|
||||
typename Underlying::type GetRaw() const { return flags; }
|
||||
|
||||
private:
|
||||
Bool Any() const { return static_cast<typename Underlying::type>(flags) != 0; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user