[Optimaization] (MG_Backend/DirectVulkan): optimize perf

- Properly reuse descriptor sets rather than always allocating
- Add program lookup cache
This commit is contained in:
2026-05-11 23:20:55 +08:00
parent 4972ebf914
commit 9e0e03d4c0
11 changed files with 117 additions and 30 deletions
@@ -1530,7 +1530,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) {
auto hash = ComputeHash(program, flags);
const Uint32 backendStateVersion = program.GetBackendStateVersion();
HashType hash = 0;
if (m_lastLookup.program == &program && m_lastLookup.backendStateVersion == backendStateVersion &&
m_lastLookup.flags == flags) {
hash = m_lastLookup.hash;
} else {
hash = ComputeHash(program, flags);
m_lastLookup = {.program = &program, .backendStateVersion = backendStateVersion, .flags = flags, .hash = hash};
}
auto it = m_cache.find(hash);
if (it != m_cache.end()) {
return it->second;
@@ -172,6 +172,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
private:
struct ProgramLookupCache {
const MG_State::GLState::ProgramObject* program = nullptr;
Uint32 backendStateVersion = 0;
CompileOptionFlags flags{};
HashType hash = 0;
};
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
void ReflectVertexInputs(const Vector<SharedPtr<MG_State::GLState::ShaderObject>>& shaders,
const Vector<Vector<Uint>>& spirv,
@@ -186,6 +193,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 m_maxBindings = 0;
UnorderedMap<HashType, VkProgramObject> m_cache;
const VulkanRendererConfig& m_config;
mutable ProgramLookupCache m_lastLookup;
static inline XXH64_state_t* m_hashState = XXH64_createState();
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -135,6 +135,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
frame.texelBufferViews.clear();
frame.descriptorSetCacheByLayout.clear();
for (auto& bucket : frame.descriptorPools) {
if (bucket.handle != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(m_device, bucket.handle, nullptr);
@@ -180,13 +181,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
frame.activeDescriptorPoolIndex = 0;
frame.allocatedSetsThisFrame = 0;
frame.peakAllocatedSetsThisFrame = 0;
for (auto& bucket : frame.descriptorPools) {
bucket.allocatedSets = 0;
if (bucket.handle == VK_NULL_HANDLE) {
continue;
}
VK_VERIFY(vkResetDescriptorPool(m_device, bucket.handle, 0),
"UniformDescriptorBinder::BeginFrame, vkResetDescriptorPool");
for (auto& cacheEntryPair : frame.descriptorSetCacheByLayout) {
cacheEntryPair.second.cursor = 0;
}
}
@@ -584,7 +580,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet) {
auto& frame = m_frames[frameIndex];
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) {
frame.activeDescriptorPoolIndex = 0;
}
if (frame.descriptorPools[frame.activeDescriptorPoolIndex].allocatedSets >=
frame.descriptorPools[frame.activeDescriptorPoolIndex].maxSets) {
const auto availableBucket = std::find_if(
frame.descriptorPools.begin(), frame.descriptorPools.end(),
[](const DescriptorPoolBucket& candidate) { return candidate.allocatedSets < candidate.maxSets; });
if (availableBucket == frame.descriptorPools.end()) {
outDescriptorSet = VK_NULL_HANDLE;
return VK_ERROR_OUT_OF_POOL_MEMORY;
}
frame.activeDescriptorPoolIndex =
static_cast<Uint32>(std::distance(frame.descriptorPools.begin(), availableBucket));
}
auto& bucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
if (bucket.allocatedSets >= bucket.maxSets) {
outDescriptorSet = VK_NULL_HANDLE;
return VK_ERROR_OUT_OF_POOL_MEMORY;
}
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorSetCount = 1;
@@ -594,13 +609,42 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkResult result = vkAllocateDescriptorSets(m_device, &allocInfo, &outDescriptorSet);
if (result == VK_SUCCESS) {
++bucket.allocatedSets;
++frame.allocatedSetsThisFrame;
frame.peakAllocatedSetsThisFrame =
std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame);
}
return result;
}
VkResult UniformManager::AcquireDescriptorSet(Uint32 frameIndex,
const ProgramFactory::VkProgramObject& programObj,
VkDescriptorSet& outDescriptorSet) {
auto& frame = m_frames[frameIndex];
auto& cache = frame.descriptorSetCacheByLayout[programObj.descriptorSetLayout];
if (cache.cursor < cache.sets.size()) {
outDescriptorSet = cache.sets[cache.cursor++];
} else {
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet);
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
if (!GrowFrameDescriptorPool(frame, frameIndex)) {
MGLOG_E("UniformDescriptorBinder::AcquireDescriptorSet failed: descriptor pool growth failed");
return allocResult;
}
allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet);
}
if (allocResult != VK_SUCCESS || outDescriptorSet == VK_NULL_HANDLE) {
return allocResult;
}
cache.sets.push_back(outDescriptorSet);
++cache.cursor;
MGLOG_D("UniformDescriptorBinder: cached descriptor set count for frame=%u grew to %zu", frameIndex,
cache.sets.size());
}
++frame.allocatedSetsThisFrame;
frame.peakAllocatedSetsThisFrame =
std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame);
return VK_SUCCESS;
}
Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
@@ -616,16 +660,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, descriptorSet);
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
if (!GrowFrameDescriptorPool(frame, frameIndex)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor pool growth failed");
return false;
}
allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, descriptorSet);
}
VkResult allocResult = AcquireDescriptorSet(frameIndex, programObj, descriptorSet);
if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: vkAllocateDescriptorSets returned %d",
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor set acquire returned %d",
allocResult);
return false;
}
@@ -55,8 +55,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 allocatedSets = 0;
};
struct DescriptorSetCacheEntry {
Vector<VkDescriptorSet> sets;
Uint32 cursor = 0;
};
struct FrameResources {
Vector<DescriptorPoolBucket> descriptorPools;
UnorderedMap<VkDescriptorSetLayout, DescriptorSetCacheEntry> descriptorSetCacheByLayout;
Vector<VkBufferView> texelBufferViews;
Uint32 activeDescriptorPoolIndex = 0;
Uint32 allocatedSetsThisFrame = 0;
@@ -82,6 +88,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
VkResult AllocateDescriptorSetsFromActivePool(
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
VkResult AcquireDescriptorSet(Uint32 frameIndex,
const ProgramFactory::VkProgramObject& programObj,
VkDescriptorSet& outDescriptorSet);
VkDevice m_device = VK_NULL_HANDLE;
VkBufferManager* m_bufferManager = nullptr;
@@ -41,6 +41,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
const MG_State::GLState::VertexArrayObject& vao) {
const HashType hash = ComputeHash(vao);
return GetOrCreateVertexInputState(vao, hash);
}
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
const MG_State::GLState::VertexArrayObject& vao, HashType hash) {
auto it = m_cache.find(hash);
if (it != m_cache.end()) {
return it->second;
@@ -35,6 +35,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
HashType ComputeHash(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);
private:
@@ -18,6 +18,7 @@
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
#include "MG_Util/Converters/GLToMG/TextureEnumConverter.h"
#include "MG_Util/Converters/MGToVk/RenderStateEnumConverter.h"
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
#include "MG_Util/Metrics/TextureMetrics.h"
#include <vulkan/vulkan_core.h>
@@ -2027,11 +2028,11 @@ void main() {
VkPipeline VulkanRenderer::GetOrCreatePipeline(
GLenum mode,
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
ProgramFactory::CompileOptionFlags transformFlags,
const MG_State::GLState::VertexArrayObject& vao,
const RenderPassEntry& renderPassEntry) {
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
Bool invertClockwise = transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip;
const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
if (programObj.stages.empty()) {
MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages");
return VK_NULL_HANDLE;
@@ -2076,7 +2077,7 @@ void main() {
#endif
auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao);
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao, vertexInputHash);
const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vis.attributes);
const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask;
const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask;
@@ -2219,6 +2220,7 @@ void main() {
MOBILEGL_ASSERT(texture != nullptr,
"GetOrCreatePipeline: color attachment %u texture is null",
i);
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
const auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
MOBILEGL_ASSERT(textureResource != nullptr,
"GetOrCreatePipeline: failed to sync color attachment textureId=%d",
@@ -2235,7 +2237,9 @@ void main() {
static_cast<Int>(textureResource->format),
texture->GetExternalIndex(),
program.GetExternalIndex());
#endif
const SizeT componentCount = MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat());
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
const NumericDomain attachmentNumericDomain =
GetNumericDomainForTextureInternalFormat(texture->GetFormat());
for (Uint32 outputLocation = 0;
@@ -2263,6 +2267,7 @@ void main() {
texture->GetExternalIndex(),
program.GetExternalIndex());
}
#endif
const VkColorComponentFlags supportedColorWriteMask =
GetSupportedColorWriteMaskForComponentCount(componentCount);
if ((attachmentColorWriteMask & ~supportedColorWriteMask) != 0) {
@@ -2303,13 +2308,10 @@ void main() {
"GetOrCreatePipeline: color attachment %u texture is null",
i);
textureExternalIndex = texture->GetExternalIndex();
const auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
MOBILEGL_ASSERT(textureResource != nullptr,
"GetOrCreatePipeline: failed to sync color attachment textureId=%d",
textureExternalIndex);
colorAttachmentFormat = textureResource->format;
colorAttachmentFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(texture->GetFormat());
}
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
VkFormatProperties formatProperties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, colorAttachmentFormat, &formatProperties);
MOBILEGL_ASSERT(
@@ -2319,6 +2321,7 @@ void main() {
static_cast<Int>(colorAttachmentFormat),
textureExternalIndex,
program.GetExternalIndex());
#endif
}
payload.colorBlendAttachments[i] = MakeColorBlendAttachmentState(
effectiveBlendEnabled,
@@ -2429,7 +2432,7 @@ void main() {
renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired);
}
auto pipeline = GetOrCreatePipeline(mode, program, vao, *renderPassEntry);
auto pipeline = GetOrCreatePipeline(mode, program, programObj, transformFlags, vao, *renderPassEntry);
activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
// Begin render pass, and handle clear
@@ -237,6 +237,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkPipeline GetOrCreatePipeline(
GLenum mode,
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
ProgramFactory::CompileOptionFlags transformFlags,
const MG_State::GLState::VertexArrayObject& vao,
const RenderPassEntry& renderPassEntry);
@@ -15,6 +15,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
String AppName = "MobileGL-VulkanRenderer";
MobileGL::Version Version = MG_Config::CoreVersion;
Uint64 CacheVersion = MG_Config::CacheVersion;
#if defined(__ANDROID__)
Bool EnableValidationLayers = false;
#else
Bool EnableValidationLayers = true;
#endif
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -133,6 +133,7 @@ namespace MobileGL::MG_State::GLState {
void ProgramObject::Link(Bool addDefaultFSIfMissingForRenderingPipelineProgram) {
MGLOG_D("ProgramObject %u: Link start, shaders to link: %zu", m_externalIndex, m_shaders.size());
++m_backendStateVersion;
// Remove detached shaders first
for (const auto& detachedShader : m_detachedShaders) {
RemoveShader(detachedShader);
@@ -110,6 +110,7 @@ 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()); }
Uint32 GetBackendStateVersion() const { return m_backendStateVersion; }
void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
m_uniformSamplerOrImageUnitIndex[location] = unit;
@@ -147,7 +148,13 @@ namespace MobileGL::MG_State::GLState {
}
// Set by glUniformBlockBinding
void SetUniformBlockBinding(Uint index, Uint binding) { m_uniformBlockBinding[index] = (Int)binding; }
void SetUniformBlockBinding(Uint index, Uint binding) {
if (index >= m_uniformBlockBinding.size() || m_uniformBlockBinding[index] == static_cast<Int>(binding)) {
return;
}
m_uniformBlockBinding[index] = static_cast<Int>(binding);
++m_backendStateVersion;
}
Uint GetUniformBlockBinding(Uint index) const { return m_uniformBlockBinding[index]; }
@@ -219,5 +226,6 @@ namespace MobileGL::MG_State::GLState {
Bool m_deleteStatus = false;
Bool m_linkStatus = false;
Bool m_validateStatus = true;
Uint32 m_backendStateVersion = 0;
};
} // namespace MobileGL::MG_State::GLState