mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 12:48:32 +09:00
[Perf] (DirectVulkan): keep the render pass alive for steady-state storage-image draws and skip storage-image collection for programs without them
This commit is contained in:
@@ -1808,6 +1808,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
} else if (kind == DescriptorBindingKind::StorageImage) {
|
||||
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
entry.hasStorageImages = true;
|
||||
} else {
|
||||
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Vector<Bool> storageImageUsesBindingFormatByBinding;
|
||||
Vector<String> storageBlockNameByBinding;
|
||||
Vector<Int> storageBlockIndexByBinding;
|
||||
// Set once during ReflectLayout so the per-draw path can skip the whole
|
||||
// storage-image preparation for the overwhelming majority of programs.
|
||||
Bool hasStorageImages = false;
|
||||
Int globalUboBinding = -1;
|
||||
Uint32 activeVertexInputLocationMask = 0;
|
||||
Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{};
|
||||
@@ -99,6 +102,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
std::move(other.storageImageUsesBindingFormatByBinding);
|
||||
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
||||
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||
hasStorageImages = other.hasStorageImages;
|
||||
globalUboBinding = other.globalUboBinding;
|
||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||
vertexInputTypes = other.vertexInputTypes;
|
||||
@@ -110,6 +114,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
other.hash = 0;
|
||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||
other.pipelineLayout = VK_NULL_HANDLE;
|
||||
other.hasStorageImages = false;
|
||||
other.globalUboBinding = -1;
|
||||
other.activeVertexInputLocationMask = 0;
|
||||
other.activeFragmentOutputLocationMask = 0;
|
||||
@@ -139,6 +144,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
std::move(other.storageImageUsesBindingFormatByBinding);
|
||||
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
||||
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||
hasStorageImages = other.hasStorageImages;
|
||||
globalUboBinding = other.globalUboBinding;
|
||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||
vertexInputTypes = other.vertexInputTypes;
|
||||
@@ -150,6 +156,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
other.hash = 0;
|
||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||
other.pipelineLayout = VK_NULL_HANDLE;
|
||||
other.hasStorageImages = false;
|
||||
other.globalUboBinding = -1;
|
||||
other.activeVertexInputLocationMask = 0;
|
||||
other.activeFragmentOutputLocationMask = 0;
|
||||
|
||||
@@ -1140,6 +1140,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return ok;
|
||||
}
|
||||
|
||||
Bool VkTextureManager::NeedsStorageImagePreparation(MG_State::GLState::ITextureObject& texture) const {
|
||||
const auto it = m_textureResources.find(MakeTextureIdentity(&texture));
|
||||
if (it == m_textureResources.end()) {
|
||||
return true;
|
||||
}
|
||||
const TextureResource& resource = it->second;
|
||||
if (resource.image == VK_NULL_HANDLE || resource.layout != VK_IMAGE_LAYOUT_GENERAL) {
|
||||
return true;
|
||||
}
|
||||
// Mirror SyncTexture's cross-draw skip condition: any version drift means the sync
|
||||
// path may upload or rebuild, both of which need the render pass ended first.
|
||||
const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture);
|
||||
const Uint32 mipLevelCount = mipTexture != nullptr ? mipTexture->GetMipmapLevelCount() : 0u;
|
||||
return resource.syncedContentVersion != texture.GetContentVersion() ||
|
||||
resource.syncedTextureParamsVersion != texture.GetTextureParamsVersion() ||
|
||||
resource.syncedMipLevelCount != mipLevelCount;
|
||||
}
|
||||
|
||||
Bool VkTextureManager::TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image,
|
||||
VkImageLayout& trackedLayout, VkImageLayout newLayout,
|
||||
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
|
||||
|
||||
@@ -284,6 +284,11 @@ public:
|
||||
VkImageLayout newLayout);
|
||||
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||
Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||
// Non-mutating probe for the per-draw storage-image fast path: true when preparing this
|
||||
// texture as a storage image may need work that is illegal inside a render pass (resource
|
||||
// creation, dirty-content upload, or a layout transition to GENERAL). Unknown state reports
|
||||
// true - a false positive merely ends the render pass, a false negative would skip a barrier.
|
||||
Bool NeedsStorageImagePreparation(MG_State::GLState::ITextureObject& texture) const;
|
||||
|
||||
static VkImageAspectFlags ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect);
|
||||
static VkFormat ResolveSampledImageViewFormat(VkFormat imageFormat, SamplerNumericDomain numericDomain);
|
||||
|
||||
@@ -3552,6 +3552,9 @@ void main() {
|
||||
VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj) {
|
||||
if (!programObj.hasStorageImages) {
|
||||
return true;
|
||||
}
|
||||
auto& storageTextures = m_storageImageTexturesScratch;
|
||||
if (!m_uniformManager->CollectStorageImageTextures(program, programObj, storageTextures)) {
|
||||
MGLOG_E("%s: failed to collect storage images for program=%u",
|
||||
@@ -3562,6 +3565,24 @@ void main() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Steady-state fast path: when every collected texture is already resident in GENERAL
|
||||
// with no pending clear and no dirty content, the loop below has nothing to record, so
|
||||
// keep the render pass alive instead of splitting it on every storage-image draw (on
|
||||
// tiled GPUs each split is a full tile load/store). GL makes cross-draw image-store
|
||||
// coherence the app's job (glMemoryBarrier), so no implicit barrier is owed here.
|
||||
Bool anyNeedsPreparation = false;
|
||||
for (auto* texture : storageTextures) {
|
||||
MOBILEGL_ASSERT(texture != nullptr, "%s: collected a null storage texture", __func__);
|
||||
if (m_textureManager->NeedsStorageImagePreparation(*texture) ||
|
||||
m_clearManager->HasPendingClear(texture)) {
|
||||
anyNeedsPreparation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!anyNeedsPreparation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Image uploads, deferred-clear materialization, and layout barriers are illegal inside
|
||||
// a classic render pass. Do this before sampler preparation as well: a texture used by
|
||||
// both a sampler and an image must stay in GENERAL, and both descriptors must name that
|
||||
@@ -3571,7 +3592,6 @@ void main() {
|
||||
}
|
||||
|
||||
for (auto* texture : storageTextures) {
|
||||
MOBILEGL_ASSERT(texture != nullptr, "%s: collected a null storage texture", __func__);
|
||||
if (!MaterializePendingClearForTexture(commandBuffer, *texture)) {
|
||||
MGLOG_E("%s: failed to materialize pending clear for storage textureId=%d",
|
||||
__func__, texture->GetExternalIndex());
|
||||
|
||||
Reference in New Issue
Block a user