mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Optimaization] (MG_Backend/DirectVulkan): optimize perf
- Properly reuse descriptor sets rather than always allocating - Add program lookup cache
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user