mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +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 ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(
|
||||||
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) {
|
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);
|
auto it = m_cache.find(hash);
|
||||||
if (it != m_cache.end()) {
|
if (it != m_cache.end()) {
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|||||||
@@ -31,8 +31,19 @@ static const char* string_VkColorSpaceKHR(VkColorSpaceKHR) {
|
|||||||
return "VkColorSpaceKHR(unknown)";
|
return "VkColorSpaceKHR(unknown)";
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* string_VkPresentModeKHR(VkPresentModeKHR) {
|
static const char* string_VkPresentModeKHR(VkPresentModeKHR presentMode) {
|
||||||
return "VkPresentModeKHR(unknown)";
|
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) {
|
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_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null");
|
||||||
MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer 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);
|
writes.reserve(m_maxBindings);
|
||||||
Vector<VkDescriptorBufferInfo> bufferInfos;
|
|
||||||
Vector<VkDescriptorImageInfo> imageInfos;
|
|
||||||
Vector<VkBufferView> texelBufferViews;
|
|
||||||
Vector<Uint32> dynamicOffsets;
|
|
||||||
bufferInfos.reserve(m_maxBindings);
|
bufferInfos.reserve(m_maxBindings);
|
||||||
imageInfos.reserve(m_maxBindings);
|
imageInfos.reserve(m_maxBindings);
|
||||||
texelBufferViews.reserve(m_maxBindings);
|
texelBufferViews.reserve(m_maxBindings);
|
||||||
|
|||||||
@@ -113,6 +113,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkTextureManager* m_textureManager = nullptr;
|
VkTextureManager* m_textureManager = nullptr;
|
||||||
VkSamplerManager* m_samplerManager = nullptr;
|
VkSamplerManager* m_samplerManager = nullptr;
|
||||||
mutable SharedPtr<MG_State::GLState::ITextureObject> m_fallbackTexture2D;
|
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
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|
||||||
|
|||||||
@@ -38,10 +38,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return XXH64_digest(m_hashState);
|
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 VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||||
const MG_State::GLState::VertexArrayObject& vao) {
|
const MG_State::GLState::VertexArrayObject& vao) {
|
||||||
const HashType hash = ComputeHash(vao);
|
return GetOrCreateVertexInputState(vao, GetOrComputeHash(vao));
|
||||||
return GetOrCreateVertexInputState(vao, hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
|
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
|
||||||
|
|
||||||
HashType ComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
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 BackendVertexInputState& GetOrCreateVertexInputState(
|
||||||
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
||||||
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
||||||
|
|||||||
@@ -675,7 +675,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vector<TextureIdentity> staleAliases;
|
Vector<TextureIdentity> staleAliases;
|
||||||
staleAliases.reserve(m_aliveObjects.size());
|
|
||||||
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
|
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
|
||||||
if (it->first.texture != texture) {
|
if (it->first.texture != texture) {
|
||||||
continue;
|
continue;
|
||||||
@@ -698,12 +697,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto aliveIt = m_aliveObjects.find(identity);
|
auto aliveIt = m_aliveObjects.find(identity);
|
||||||
if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) {
|
if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) {
|
||||||
EraseTrackedTexture(aliveIt->first);
|
EraseTrackedTexture(aliveIt->first);
|
||||||
|
aliveIt = m_aliveObjects.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex());
|
// Only (re)register and prune when this (texture, lifetime) pair is new: stale
|
||||||
if (liveTexture && liveTexture.get() == &texture) {
|
// aliases can only come into existence through an address reuse, which by
|
||||||
m_aliveObjects[identity] = WeakPtr<MG_State::GLState::ITextureObject>(liveTexture);
|
// construction introduces a new identity. Doing this unconditionally made every
|
||||||
PruneStaleTextureAliases(&texture);
|
// 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);
|
auto it = m_textureResources.find(identity);
|
||||||
@@ -1033,7 +1039,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* mipTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(&texture);
|
auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture);
|
||||||
if (!mipTexture) {
|
if (!mipTexture) {
|
||||||
MGLOG_D("%s: not TextureObjectMipmap", __func__);
|
MGLOG_D("%s: not TextureObjectMipmap", __func__);
|
||||||
return false;
|
return false;
|
||||||
@@ -1579,7 +1585,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
IntVec3& outTexelSize,
|
IntVec3& outTexelSize,
|
||||||
SizeT& outByteSize,
|
SizeT& outByteSize,
|
||||||
Uint32& outMipLevelCount) {
|
Uint32& outMipLevelCount) {
|
||||||
const auto* mipTexture = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(&texture);
|
const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture);
|
||||||
if (!mipTexture) {
|
if (!mipTexture) {
|
||||||
MGLOG_D("%s: not TextureObjectMipmap", __func__);
|
MGLOG_D("%s: not TextureObjectMipmap", __func__);
|
||||||
return false;
|
return false;
|
||||||
@@ -1639,7 +1645,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MOBILEGL_ASSERT(mipLevels > 0, "ResolveViewMipRange: mipLevels must be > 0");
|
MOBILEGL_ASSERT(mipLevels > 0, "ResolveViewMipRange: mipLevels must be > 0");
|
||||||
|
|
||||||
Uint32 definedMipLevels = mipLevels;
|
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();
|
const auto& targets = texture.GetUploadTargets();
|
||||||
for (const auto target : targets) {
|
for (const auto target : targets) {
|
||||||
const Uint32 uploadMipLevels = GetUploadMipLevelCount(*mipTexture, target);
|
const Uint32 uploadMipLevels = GetUploadMipLevelCount(*mipTexture, target);
|
||||||
|
|||||||
@@ -2077,8 +2077,10 @@ void main() {
|
|||||||
|
|
||||||
const auto bindingCount = vertexInputState.bindings.size() + static_cast<SizeT>(std::popcount(missingAttribMask));
|
const auto bindingCount = vertexInputState.bindings.size() + static_cast<SizeT>(std::popcount(missingAttribMask));
|
||||||
|
|
||||||
Vector<VkBuffer> vkBuffers(bindingCount, VK_NULL_HANDLE);
|
auto& vkBuffers = m_vertexBuffersScratch;
|
||||||
Vector<VkDeviceSize> vkOffsets(bindingCount, 0);
|
auto& vkOffsets = m_vertexOffsetsScratch;
|
||||||
|
vkBuffers.assign(bindingCount, VK_NULL_HANDLE);
|
||||||
|
vkOffsets.assign(bindingCount, 0);
|
||||||
|
|
||||||
auto findBufferByKey = [&](SizeT bufferKey) -> const MG_State::GLState::BufferObject* {
|
auto findBufferByKey = [&](SizeT bufferKey) -> const MG_State::GLState::BufferObject* {
|
||||||
const auto& attrs = vao.GetAllAttributes();
|
const auto& attrs = vao.GetAllAttributes();
|
||||||
@@ -2690,6 +2692,7 @@ void main() {
|
|||||||
MOBILEGL_ASSERT(offset + size <= m_depthMipmapResources.program->GetUBOSize(),
|
MOBILEGL_ASSERT(offset + size <= m_depthMipmapResources.program->GetUBOSize(),
|
||||||
"GenerateDepthMipmapWithShader: uniform write out of bounds");
|
"GenerateDepthMipmapWithShader: uniform write out of bounds");
|
||||||
memcpy(depthProgramData + offset, data, size);
|
memcpy(depthProgramData + offset, data, size);
|
||||||
|
m_depthMipmapResources.program->MarkUBOContentDirty();
|
||||||
};
|
};
|
||||||
|
|
||||||
for (Uint32 level = baseMipLevel + 1; level < generateMipLevelCount; ++level) {
|
for (Uint32 level = baseMipLevel + 1; level < generateMipLevelCount; ++level) {
|
||||||
@@ -2843,12 +2846,13 @@ void main() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao);
|
auto vertexInputHash = m_vertexInputStateFactory->GetOrComputeHash(vao);
|
||||||
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao, vertexInputHash);
|
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao, vertexInputHash);
|
||||||
const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vis.attributes);
|
const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vis.attributes);
|
||||||
const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask;
|
const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask;
|
||||||
const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask;
|
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;
|
Bool hasPatchedVertexAttributes = false;
|
||||||
for (auto& attribute : patchedAttributes) {
|
for (auto& attribute : patchedAttributes) {
|
||||||
if (attribute.location >= 32 || (activeAttribMask & (1u << attribute.location)) == 0) {
|
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
|
// 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.
|
// without draws in between to give it a chance to materialize such clear.
|
||||||
// Deal with this situation here.
|
// 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);
|
Bool hasSampledTextures = m_uniformManager->CollectSampledTextures(program, programObj, sampledTextures);
|
||||||
MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__);
|
MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__);
|
||||||
MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s",
|
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.dstRectLocation, blitUniformData.dstRect, sizeof(blitUniformData.dstRect));
|
||||||
writeUniform(m_blitResources.surfaceTransformLocation, &blitUniformData.surfaceTransform,
|
writeUniform(m_blitResources.surfaceTransformLocation, &blitUniformData.surfaceTransform,
|
||||||
sizeof(blitUniformData.surfaceTransform));
|
sizeof(blitUniformData.surfaceTransform));
|
||||||
|
m_blitResources.program->MarkUBOContentDirty();
|
||||||
|
|
||||||
const auto samplerBindingOverride = UniformManager::SamplerBindingOverride{
|
const auto samplerBindingOverride = UniformManager::SamplerBindingOverride{
|
||||||
.binding = m_blitResources.samplerBinding,
|
.binding = m_blitResources.samplerBinding,
|
||||||
@@ -4875,7 +4880,7 @@ void main() {
|
|||||||
MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture.");
|
MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture.");
|
||||||
MOBILEGL_ASSERT(texture->IsComplete(), "GenerateMipmap requires a complete 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.");
|
MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires a mipmapped texture object.");
|
||||||
|
|
||||||
const Uint32 currentMipLevelCount = static_cast<Uint32>(mipmapTexture->GetMipmapLevelCount());
|
const Uint32 currentMipLevelCount = static_cast<Uint32>(mipmapTexture->GetMipmapLevelCount());
|
||||||
|
|||||||
@@ -272,6 +272,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
DepthMipmapResources m_depthMipmapResources;
|
DepthMipmapResources m_depthMipmapResources;
|
||||||
Vector<DeferredDepthMipmapCleanup> m_deferredDepthMipmapCleanup;
|
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();
|
void CreateInstance();
|
||||||
VkResult SetupDebugMessenger();
|
VkResult SetupDebugMessenger();
|
||||||
VkResult DestroyDebugMessenger();
|
VkResult DestroyDebugMessenger();
|
||||||
|
|||||||
@@ -105,6 +105,14 @@ namespace {
|
|||||||
|
|
||||||
namespace MobileGL::MG_State::GLState {
|
namespace MobileGL::MG_State::GLState {
|
||||||
void ProgramObject::ResetLinkArtifacts() {
|
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_program.reset();
|
||||||
m_generatedSpirv.clear();
|
m_generatedSpirv.clear();
|
||||||
m_uniformLocations.clear();
|
m_uniformLocations.clear();
|
||||||
|
|||||||
@@ -170,7 +170,33 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
void* MapUBO() { return m_globalUboScratch.data(); }
|
void* MapUBO() { return m_globalUboScratch.data(); }
|
||||||
const void* GetUBOData() const { return m_globalUboScratch.data(); }
|
const void* GetUBOData() const { return m_globalUboScratch.data(); }
|
||||||
Uint GetUBOSize() const { return static_cast<Uint>(m_globalUboScratch.size()); }
|
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; }
|
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) {
|
void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
|
||||||
if (location >= m_uniformSamplerOrImageUnitIndex.size() ||
|
if (location >= m_uniformSamplerOrImageUnitIndex.size() ||
|
||||||
@@ -308,5 +334,13 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Bool m_linkStatus = false;
|
Bool m_linkStatus = false;
|
||||||
Bool m_validateStatus = true;
|
Bool m_validateStatus = true;
|
||||||
Uint32 m_backendStateVersion = 0;
|
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
|
} // namespace MobileGL::MG_State::GLState
|
||||||
|
|||||||
@@ -128,6 +128,20 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
virtual Bool IsStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel) const = 0;
|
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 {
|
class TextureObjectWithOneMipmap : public TextureObjectMipmap {
|
||||||
public:
|
public:
|
||||||
TextureObjectWithOneMipmap(TextureTarget target, Uint externalIndex)
|
TextureObjectWithOneMipmap(TextureTarget target, Uint externalIndex)
|
||||||
|
|||||||
@@ -207,16 +207,19 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
void VertexArrayObject::BumpAttributeFormatVersion(Uint index) {
|
void VertexArrayObject::BumpAttributeFormatVersion(Uint index) {
|
||||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||||
++m_attributeVersions[index].FormatVersion;
|
++m_attributeVersions[index].FormatVersion;
|
||||||
|
++m_configVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VertexArrayObject::BumpAttributeBufferVersion(Uint index) {
|
void VertexArrayObject::BumpAttributeBufferVersion(Uint index) {
|
||||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||||
++m_attributeVersions[index].BufferVersion;
|
++m_attributeVersions[index].BufferVersion;
|
||||||
|
++m_configVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VertexArrayObject::BumpAttributeSwitchVersion(Uint index) {
|
void VertexArrayObject::BumpAttributeSwitchVersion(Uint index) {
|
||||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||||
++m_attributeVersions[index].SwitchVersion;
|
++m_attributeVersions[index].SwitchVersion;
|
||||||
|
++m_configVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VertexAttributeVersion& VertexArrayObject::GetAttributeVersion(Uint index) const {
|
const VertexAttributeVersion& VertexArrayObject::GetAttributeVersion(Uint index) const {
|
||||||
|
|||||||
@@ -81,6 +81,23 @@ namespace MobileGL {
|
|||||||
const VertexAttributeVersion& GetAttributeVersion(Uint index) const;
|
const VertexAttributeVersion& GetAttributeVersion(Uint index) const;
|
||||||
const Array<VertexAttributeVersion, MAX_VERTEX_ATTRIBS>& GetAllAttributeVersions() 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:
|
private:
|
||||||
void BumpAttributeFormatVersion(Uint index);
|
void BumpAttributeFormatVersion(Uint index);
|
||||||
void BumpAttributeBufferVersion(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
|
// ARB_vertex_attrib_binding API; only such attributes are re-resolved, so the
|
||||||
// classic glVertexAttribPointer path keeps its exact historical behavior.
|
// classic glVertexAttribPointer path keeps its exact historical behavior.
|
||||||
Array<Bool, MAX_VERTEX_ATTRIBS> m_attributeUsesBindingModel = {};
|
Array<Bool, MAX_VERTEX_ATTRIBS> m_attributeUsesBindingModel = {};
|
||||||
|
|
||||||
|
Uint32 m_configVersion = 0;
|
||||||
|
mutable Uint64 m_backendHashMemo = 0;
|
||||||
|
mutable Uint32 m_backendHashMemoVersion = ~0u;
|
||||||
};
|
};
|
||||||
} // namespace GLState
|
} // namespace GLState
|
||||||
} // namespace MG_State
|
} // namespace MG_State
|
||||||
|
|||||||
@@ -336,6 +336,8 @@ namespace MobileGL {
|
|||||||
|
|
||||||
operator Bool() const { return Any(); }
|
operator Bool() const { return Any(); }
|
||||||
|
|
||||||
|
typename Underlying::type GetRaw() const { return flags; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Bool Any() const { return static_cast<typename Underlying::type>(flags) != 0; }
|
Bool Any() const { return static_cast<typename Underlying::type>(flags) != 0; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user