mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +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;
|
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||||
} else if (kind == DescriptorBindingKind::StorageImage) {
|
} else if (kind == DescriptorBindingKind::StorageImage) {
|
||||||
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||||
|
entry.hasStorageImages = true;
|
||||||
} else {
|
} else {
|
||||||
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<Bool> storageImageUsesBindingFormatByBinding;
|
Vector<Bool> storageImageUsesBindingFormatByBinding;
|
||||||
Vector<String> storageBlockNameByBinding;
|
Vector<String> storageBlockNameByBinding;
|
||||||
Vector<Int> storageBlockIndexByBinding;
|
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;
|
Int globalUboBinding = -1;
|
||||||
Uint32 activeVertexInputLocationMask = 0;
|
Uint32 activeVertexInputLocationMask = 0;
|
||||||
Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{};
|
Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{};
|
||||||
@@ -99,6 +102,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
std::move(other.storageImageUsesBindingFormatByBinding);
|
std::move(other.storageImageUsesBindingFormatByBinding);
|
||||||
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
||||||
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||||
|
hasStorageImages = other.hasStorageImages;
|
||||||
globalUboBinding = other.globalUboBinding;
|
globalUboBinding = other.globalUboBinding;
|
||||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||||
vertexInputTypes = other.vertexInputTypes;
|
vertexInputTypes = other.vertexInputTypes;
|
||||||
@@ -110,6 +114,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
other.hasStorageImages = false;
|
||||||
other.globalUboBinding = -1;
|
other.globalUboBinding = -1;
|
||||||
other.activeVertexInputLocationMask = 0;
|
other.activeVertexInputLocationMask = 0;
|
||||||
other.activeFragmentOutputLocationMask = 0;
|
other.activeFragmentOutputLocationMask = 0;
|
||||||
@@ -139,6 +144,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
std::move(other.storageImageUsesBindingFormatByBinding);
|
std::move(other.storageImageUsesBindingFormatByBinding);
|
||||||
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
||||||
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||||
|
hasStorageImages = other.hasStorageImages;
|
||||||
globalUboBinding = other.globalUboBinding;
|
globalUboBinding = other.globalUboBinding;
|
||||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||||
vertexInputTypes = other.vertexInputTypes;
|
vertexInputTypes = other.vertexInputTypes;
|
||||||
@@ -150,6 +156,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
other.hasStorageImages = false;
|
||||||
other.globalUboBinding = -1;
|
other.globalUboBinding = -1;
|
||||||
other.activeVertexInputLocationMask = 0;
|
other.activeVertexInputLocationMask = 0;
|
||||||
other.activeFragmentOutputLocationMask = 0;
|
other.activeFragmentOutputLocationMask = 0;
|
||||||
|
|||||||
@@ -1140,6 +1140,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return ok;
|
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,
|
Bool VkTextureManager::TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image,
|
||||||
VkImageLayout& trackedLayout, VkImageLayout newLayout,
|
VkImageLayout& trackedLayout, VkImageLayout newLayout,
|
||||||
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
|
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
|
||||||
|
|||||||
@@ -284,6 +284,11 @@ public:
|
|||||||
VkImageLayout newLayout);
|
VkImageLayout newLayout);
|
||||||
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||||
Bool TransitionTextureForStorageImage(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 VkImageAspectFlags ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect);
|
||||||
static VkFormat ResolveSampledImageViewFormat(VkFormat imageFormat, SamplerNumericDomain numericDomain);
|
static VkFormat ResolveSampledImageViewFormat(VkFormat imageFormat, SamplerNumericDomain numericDomain);
|
||||||
|
|||||||
@@ -3552,6 +3552,9 @@ void main() {
|
|||||||
VkCommandBuffer commandBuffer,
|
VkCommandBuffer commandBuffer,
|
||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj) {
|
const ProgramFactory::VkProgramObject& programObj) {
|
||||||
|
if (!programObj.hasStorageImages) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
auto& storageTextures = m_storageImageTexturesScratch;
|
auto& storageTextures = m_storageImageTexturesScratch;
|
||||||
if (!m_uniformManager->CollectStorageImageTextures(program, programObj, storageTextures)) {
|
if (!m_uniformManager->CollectStorageImageTextures(program, programObj, storageTextures)) {
|
||||||
MGLOG_E("%s: failed to collect storage images for program=%u",
|
MGLOG_E("%s: failed to collect storage images for program=%u",
|
||||||
@@ -3562,6 +3565,24 @@ void main() {
|
|||||||
return true;
|
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
|
// 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
|
// 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
|
// 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) {
|
for (auto* texture : storageTextures) {
|
||||||
MOBILEGL_ASSERT(texture != nullptr, "%s: collected a null storage texture", __func__);
|
|
||||||
if (!MaterializePendingClearForTexture(commandBuffer, *texture)) {
|
if (!MaterializePendingClearForTexture(commandBuffer, *texture)) {
|
||||||
MGLOG_E("%s: failed to materialize pending clear for storage textureId=%d",
|
MGLOG_E("%s: failed to materialize pending clear for storage textureId=%d",
|
||||||
__func__, texture->GetExternalIndex());
|
__func__, texture->GetExternalIndex());
|
||||||
|
|||||||
Reference in New Issue
Block a user