mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 13:18:31 +09:00
[Fix] (DirectVulkan): bound image mutability so Adreno keeps UBWC compression
- Every storage-capable colour texture was created MUTABLE_FORMAT, and Adreno gives up bandwidth compression on an image that may be viewed as any format in its compatibility class. MC's main render target therefore ran uncompressed; in a fill-bound scene that is the whole frame budget. Measured on Adreno 650, MC 26.2, same scene and camera, device cooled to 38-40C before each run: 65.3 -> 80.9 fps (+23.9%), GPU busy ~93% in both. - VK_KHR_image_format_list (enabled when present) fixes it without giving up mutability: VkImageFormatListCreateInfo names the exact formats a view may use, so the driver can keep the image compressed. The set must be exhaustive or the result is undefined - for sampled views it is exactly what ResolveSampledImageViewFormat can return over the three numeric domains. - glBindImageTexture may name any compatible format, which cannot be enumerated ahead of time, so a texture bound to an image unit gets no format list. That is what VK_IMAGE_USAGE_STORAGE_BIT becoming on-demand is for: it makes "unmarked" mean "will never receive an arbitrary-format storage view", which is what makes the list sound. Removing STORAGE is worth nothing on its own (65.4 fps, measured) - only the mutability bound pays. - MarkStorageImageTexture runs over every collected image-unit texture before the probe loop in PrepareStorageImageTextures, because that loop stops at the first texture needing work and would leave the rest unmarked. The mark makes NeedsStorageImagePreparation report true, which is what ends the render pass, so the recreate lands outside it. - storageUsageResolved separates "not upgraded yet" from "this format can never carry STORAGE", so a format whose optimalTilingFeatures lack STORAGE_IMAGE cannot ask for a recreate that will never happen. SyncTexture's cross-draw early-out also has to break on a pending upgrade or the recreate never runs. - An upgrade recreates the image and carries its contents forward through PreserveTextureContentsOnRecreate, which submits its own command buffer and waits. Whatever the frame already recorded into the old image is still unsubmitted, so that copy would read pre-frame content and this frame's rendering into the texture would be lost - exactly the render-target-then- image-unit case. PrepareStorageImageTextures now flushes first; it takes the FrameData rather than a command buffer because the flush retires the current one, and drops the sampled-descriptor-set memo that described it.
This commit is contained in:
@@ -53,6 +53,9 @@ public:
|
||||
VkCommandPool commandPool = VK_NULL_HANDLE;
|
||||
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
||||
Uint32 frameCount = 0;
|
||||
// VK_KHR_image_format_list is enabled: MUTABLE_FORMAT images can name the exact set of
|
||||
// formats they will be viewed as, which is what lets a tiler keep them compressed.
|
||||
Bool imageFormatListSupported = false;
|
||||
};
|
||||
|
||||
struct TextureResource {
|
||||
@@ -157,6 +160,17 @@ public:
|
||||
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkImageCreateFlags imageCreateFlags = 0;
|
||||
// Usage the live image was created with. STORAGE is only requested for textures that
|
||||
// have actually been bound to a GL image unit, because on Adreno a storage-capable
|
||||
// image loses UBWC bandwidth compression; a later image binding upgrades the usage
|
||||
// and recreates the image, so the resolved usage has to be part of the compatibility
|
||||
// check that decides whether the existing image can be kept.
|
||||
VkImageUsageFlags usageFlags = 0;
|
||||
// True once this image was (re)resolved while the texture was already marked as an
|
||||
// image-unit texture. Distinguishes "not upgraded yet" from "cannot be upgraded"
|
||||
// (a format whose optimalTilingFeatures lack STORAGE_IMAGE never gains the bit), so
|
||||
// NeedsStorageImagePreparation cannot ask for a recreate that will never happen.
|
||||
Bool storageUsageResolved = false;
|
||||
Uint16 syncedTextureParamsVersion = 0;
|
||||
// Snapshot of ITextureObject::GetContentVersion() at the last successful sync;
|
||||
// lets SyncTexture skip the whole re-check/re-upload when content is unchanged.
|
||||
@@ -190,6 +204,8 @@ public:
|
||||
std::swap(this->viewType, that.viewType);
|
||||
std::swap(this->sampleCount, that.sampleCount);
|
||||
std::swap(this->imageCreateFlags, that.imageCreateFlags);
|
||||
std::swap(this->usageFlags, that.usageFlags);
|
||||
std::swap(this->storageUsageResolved, that.storageUsageResolved);
|
||||
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
|
||||
std::swap(this->syncedContentVersion, that.syncedContentVersion);
|
||||
std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount);
|
||||
@@ -251,6 +267,8 @@ public:
|
||||
viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageCreateFlags = 0;
|
||||
usageFlags = 0;
|
||||
storageUsageResolved = false;
|
||||
syncedTextureParamsVersion = 0;
|
||||
syncedContentVersion = 0;
|
||||
syncedMipLevelCount = 0;
|
||||
@@ -289,6 +307,17 @@ public:
|
||||
VkImageLayout newLayout);
|
||||
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||
Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||
// Records that this texture is bound to a GL image unit, so its image must carry
|
||||
// VK_IMAGE_USAGE_STORAGE_BIT. Must be called before NeedsStorageImagePreparation, and
|
||||
// therefore before the render pass is committed: an image that has to be upgraded is
|
||||
// recreated, which is illegal inside a render pass. Sticky for the texture's lifetime -
|
||||
// GL lets an image binding come and go, and re-creating the image every time it does
|
||||
// would cost far more than the compression it wins back.
|
||||
void MarkStorageImageTexture(MG_State::GLState::ITextureObject& texture);
|
||||
// True when this texture is marked but its live image predates the mark, i.e. the next sync
|
||||
// will recreate it with STORAGE usage and copy the old contents forward. Callers use this to
|
||||
// submit their pending recording first, so that copy cannot read pre-flush content.
|
||||
Bool NeedsStorageUsageUpgrade(MG_State::GLState::ITextureObject& texture) const;
|
||||
// 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
|
||||
@@ -375,6 +404,7 @@ private:
|
||||
VmaAllocator m_allocator = nullptr;
|
||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||
Bool m_imageFormatListSupported = false;
|
||||
Uint32 m_currentFrameIndex = 0;
|
||||
|
||||
Uint8 m_gcCounter = 0;
|
||||
@@ -398,6 +428,8 @@ private:
|
||||
std::unordered_set<VkFormat> m_mutableFormatUnsupported;
|
||||
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
||||
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
|
||||
// Textures that have been bound to a GL image unit (see MarkStorageImageTexture).
|
||||
std::unordered_set<TextureIdentity, TextureIdentityHash> m_storageImageTextures;
|
||||
Vector<Vector<TextureResource>> m_deferredReleases;
|
||||
Vector<Vector<VkImageView>> m_deferredViewReleases;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user