[Fix] (MG_Backend/DirectVulkan): fix 26.2 texture bug.

- Fix texture object / buffer lifecycle issues
This commit is contained in:
2026-05-10 10:03:03 +08:00
parent d7e768096e
commit 4a36d63c1b
6 changed files with 72 additions and 19 deletions
@@ -142,11 +142,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
SizeT count = 0; SizeT count = 0;
for (const auto& [raw, weak]: m_aliveObjects) { for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end();) {
if (weak.expired()) { auto current = it++;
count++; if (current->second.expired()) {
m_pendingClears.erase(raw); m_pendingClears.erase(current->first);
m_aliveObjects.erase(raw); m_aliveObjects.erase(current);
++count;
} }
} }
return count; return count;
@@ -443,6 +443,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkTextureManager::TextureResource* VkTextureManager::SyncTextureAndGetDescriptor(MG_State::GLState::ITextureObject& texture) { VkTextureManager::TextureResource* VkTextureManager::SyncTextureAndGetDescriptor(MG_State::GLState::ITextureObject& texture) {
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureAndGetDescriptor: m_device == VK_NULL_HANDLE"); MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureAndGetDescriptor: m_device == VK_NULL_HANDLE");
auto aliveIt = m_aliveObjects.find(&texture);
if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) {
auto resourceIt = m_textureResources.find(&texture);
if (resourceIt != m_textureResources.end()) {
DeferResourceRelease(Move(resourceIt->second));
m_textureResources.erase(resourceIt);
}
m_aliveObjects.erase(aliveIt);
}
const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex());
if (liveTexture && liveTexture.get() == &texture) {
m_aliveObjects[&texture] = WeakPtr<MG_State::GLState::ITextureObject>(liveTexture);
}
auto it = m_textureResources.find(&texture); auto it = m_textureResources.find(&texture);
if (it == m_textureResources.end()) { if (it == m_textureResources.end()) {
TextureResource initial{}; TextureResource initial{};
@@ -668,11 +683,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return 0; return 0;
} }
SizeT count = 0; SizeT count = 0;
for (const auto& [raw, weak]: m_aliveObjects) { for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end();) {
if (weak.expired()) { auto current = it++;
count++; if (current->second.expired()) {
m_textureResources.erase(raw); auto resourceIt = m_textureResources.find(current->first);
m_aliveObjects.erase(raw); if (resourceIt != m_textureResources.end()) {
DeferResourceRelease(Move(resourceIt->second));
m_textureResources.erase(resourceIt);
}
m_aliveObjects.erase(current);
++count;
} }
} }
return count; return count;
@@ -1323,19 +1323,25 @@ void main() {
"UploadAndBindVertexStreams failed to resolve shared source buffer"); "UploadAndBindVertexStreams failed to resolve shared source buffer");
BufferSlice slice{}; BufferSlice slice{};
const Bool isDirty = (sourceBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit); const Bool isDirty = (sourceBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit);
const Uint64 changeSerial = sourceBufferShared->GetChangeSerial();
const SizeT sourceSize = sourceBufferShared->GetSize();
auto cachedTransient = m_transientVertexIndexBufferSlicesThisFrame.find(sourceBufferShared.get()); auto cachedTransient = m_transientVertexIndexBufferSlicesThisFrame.find(sourceBufferShared.get());
if (cachedTransient != m_transientVertexIndexBufferSlicesThisFrame.end() && !isDirty) { if (cachedTransient != m_transientVertexIndexBufferSlicesThisFrame.end() && !isDirty &&
slice = cachedTransient->second; cachedTransient->second.changeSerial == changeSerial && cachedTransient->second.size == sourceSize) {
slice = cachedTransient->second.slice;
} else if (ShouldUseTransientVertexIndexBuffer(*sourceBufferShared) || isDirty) { } else if (ShouldUseTransientVertexIndexBuffer(*sourceBufferShared) || isDirty) {
const auto sourceData = sourceBufferShared->GetDataReadOnly(); const auto sourceData = sourceBufferShared->GetDataReadOnly();
const SizeT sourceSize = sourceBufferShared->GetSize();
if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(),
sourceData->data(), static_cast<VkDeviceSize>(sourceSize), 16, sourceData->data(), static_cast<VkDeviceSize>(sourceSize), 16,
slice)) { slice)) {
MOBILEGL_ASSERT(false, "UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding); MOBILEGL_ASSERT(false, "UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding);
return false; return false;
} }
m_transientVertexIndexBufferSlicesThisFrame[sourceBufferShared.get()] = slice; m_transientVertexIndexBufferSlicesThisFrame[sourceBufferShared.get()] = {
.slice = slice,
.changeSerial = changeSerial,
.size = sourceSize,
};
m_bufferManager.DowngradeResidentBufferToTransient(sourceBufferShared); m_bufferManager.DowngradeResidentBufferToTransient(sourceBufferShared);
sourceBufferShared->ClearDirty(); sourceBufferShared->ClearDirty();
} else { } else {
@@ -1415,9 +1421,12 @@ void main() {
auto indexBufferShared = MG_State::pGLContext->GetBufferObject(indexBuffer->GetExternalIndex()); auto indexBufferShared = MG_State::pGLContext->GetBufferObject(indexBuffer->GetExternalIndex());
MOBILEGL_ASSERT(indexBufferShared != nullptr, "UploadAndBindIndexBuffer failed to resolve shared EBO"); MOBILEGL_ASSERT(indexBufferShared != nullptr, "UploadAndBindIndexBuffer failed to resolve shared EBO");
const Bool isDirty = (indexBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit); const Bool isDirty = (indexBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit);
const Uint64 changeSerial = indexBufferShared->GetChangeSerial();
const SizeT indexBufferSize = indexBufferShared->GetSize();
auto cachedTransient = m_transientVertexIndexBufferSlicesThisFrame.find(indexBufferShared.get()); auto cachedTransient = m_transientVertexIndexBufferSlicesThisFrame.find(indexBufferShared.get());
if (cachedTransient != m_transientVertexIndexBufferSlicesThisFrame.end() && !isDirty) { if (cachedTransient != m_transientVertexIndexBufferSlicesThisFrame.end() && !isDirty &&
slice = cachedTransient->second; cachedTransient->second.changeSerial == changeSerial && cachedTransient->second.size == indexBufferSize) {
slice = cachedTransient->second.slice;
vkCmdBindIndexBuffer(frame.commandBuffer, vkCmdBindIndexBuffer(frame.commandBuffer,
slice.buffer, slice.buffer,
slice.offset + static_cast<VkDeviceSize>(pIndexBufferView->indexByteOffset), slice.offset + static_cast<VkDeviceSize>(pIndexBufferView->indexByteOffset),
@@ -1429,12 +1438,16 @@ void main() {
MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data"); MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data");
if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(), if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(),
indexData->data(), indexData->data(),
static_cast<VkDeviceSize>(indexBufferShared->GetSize()), indexSize, static_cast<VkDeviceSize>(indexBufferSize), indexSize,
slice)) { slice)) {
MOBILEGL_ASSERT(false, "DrawElements skipped: failed to prepare transient index buffer"); MOBILEGL_ASSERT(false, "DrawElements skipped: failed to prepare transient index buffer");
return false; return false;
} }
m_transientVertexIndexBufferSlicesThisFrame[indexBufferShared.get()] = slice; m_transientVertexIndexBufferSlicesThisFrame[indexBufferShared.get()] = {
.slice = slice,
.changeSerial = changeSerial,
.size = indexBufferSize,
};
m_bufferManager.DowngradeResidentBufferToTransient(indexBufferShared); m_bufferManager.DowngradeResidentBufferToTransient(indexBufferShared);
indexBufferShared->ClearDirty(); indexBufferShared->ClearDirty();
vkCmdBindIndexBuffer(frame.commandBuffer, vkCmdBindIndexBuffer(frame.commandBuffer,
@@ -197,8 +197,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkCommandPool m_commandPool = VK_NULL_HANDLE; VkCommandPool m_commandPool = VK_NULL_HANDLE;
struct TransientBufferSliceCacheEntry {
BufferSlice slice;
Uint64 changeSerial = 0;
SizeT size = 0;
};
VkBufferManager m_bufferManager; VkBufferManager m_bufferManager;
UnorderedMap<const MG_State::GLState::BufferObject*, BufferSlice> m_transientVertexIndexBufferSlicesThisFrame; UnorderedMap<const MG_State::GLState::BufferObject*, TransientBufferSliceCacheEntry>
m_transientVertexIndexBufferSlicesThisFrame;
Uint m_imageIndexAcquired = 0; Uint m_imageIndexAcquired = 0;
FrameContext m_frameContext; FrameContext m_frameContext;
@@ -23,6 +23,7 @@ namespace MobileGL::MG_State::GLState {
m_dataPtr->resize(size); m_dataPtr->resize(size);
m_change.Bits |= BufferChangeBits::DirtyBit; m_change.Bits |= BufferChangeBits::DirtyBit;
m_change.Bits |= BufferChangeBits::PreferReallocationBit; m_change.Bits |= BufferChangeBits::PreferReallocationBit;
++m_changeSerial;
} }
void BufferObject::UploadData(DataPtr data, SizeT atOffset) { void BufferObject::UploadData(DataPtr data, SizeT atOffset) {
@@ -35,6 +36,7 @@ namespace MobileGL::MG_State::GLState {
m_change.Bits |= BufferChangeBits::DirtyBit; m_change.Bits |= BufferChangeBits::DirtyBit;
m_change.Bits |= BufferChangeBits::ForbidInvalidationBit; m_change.Bits |= BufferChangeBits::ForbidInvalidationBit;
m_change.Bits |= BufferChangeBits::ForbidUnsynchronizationBit; m_change.Bits |= BufferChangeBits::ForbidUnsynchronizationBit;
++m_changeSerial;
// This function may be called by `glBufferData`, but we still set the forbid bits above, // This function may be called by `glBufferData`, but we still set the forbid bits above,
// because when `PreferReallocationBit` is set, those bits are ignored anyway. // because when `PreferReallocationBit` is set, those bits are ignored anyway.
// The bits can fit the `glBufferSubData` semantics // The bits can fit the `glBufferSubData` semantics
@@ -54,6 +56,7 @@ namespace MobileGL::MG_State::GLState {
m_mappedRange.end - m_mappedRange.start); m_mappedRange.end - m_mappedRange.start);
m_change.DirtyRanges.Add({m_mappedRange.start, m_mappedRange.end}); m_change.DirtyRanges.Add({m_mappedRange.start, m_mappedRange.end});
m_change.Bits |= BufferChangeBits::DirtyBit; m_change.Bits |= BufferChangeBits::DirtyBit;
++m_changeSerial;
} }
m_stagingData.clear(); m_stagingData.clear();
@@ -80,6 +83,7 @@ namespace MobileGL::MG_State::GLState {
Memcpy(m_dataPtr->data() + start, m_stagingData.data() + offset, length); Memcpy(m_dataPtr->data() + start, m_stagingData.data() + offset, length);
m_change.DirtyRanges.Add({start, end}); m_change.DirtyRanges.Add({start, end});
m_change.Bits |= BufferChangeBits::DirtyBit; m_change.Bits |= BufferChangeBits::DirtyBit;
++m_changeSerial;
} }
void BufferObject::UploadSubData(DataPtr data, SizeT atOffset) { void BufferObject::UploadSubData(DataPtr data, SizeT atOffset) {
@@ -93,6 +97,7 @@ namespace MobileGL::MG_State::GLState {
m_change.Bits |= BufferChangeBits::DirtyBit; m_change.Bits |= BufferChangeBits::DirtyBit;
m_change.Bits |= BufferChangeBits::ForbidInvalidationBit; m_change.Bits |= BufferChangeBits::ForbidInvalidationBit;
m_change.Bits |= BufferChangeBits::ForbidUnsynchronizationBit; m_change.Bits |= BufferChangeBits::ForbidUnsynchronizationBit;
++m_changeSerial;
} }
void BufferObject::CopyDataFrom(const SharedPtr<BufferObject>& src, SizeT srcOffset, SizeT dstOffset, SizeT size) { void BufferObject::CopyDataFrom(const SharedPtr<BufferObject>& src, SizeT srcOffset, SizeT dstOffset, SizeT size) {
@@ -109,6 +114,7 @@ namespace MobileGL::MG_State::GLState {
Memcpy(m_dataPtr->data() + dstOffset, srcData, size); Memcpy(m_dataPtr->data() + dstOffset, srcData, size);
m_change.DirtyRanges.Add({dstOffset, dstOffset + size}); m_change.DirtyRanges.Add({dstOffset, dstOffset + size});
m_change.Bits |= BufferChangeBits::DirtyBit; m_change.Bits |= BufferChangeBits::DirtyBit;
++m_changeSerial;
} }
void* BufferObject::AcquireMemory(Bool markMapped, Bool read, Bool write) { void* BufferObject::AcquireMemory(Bool markMapped, Bool read, Bool write) {
@@ -191,6 +197,10 @@ namespace MobileGL::MG_State::GLState {
return m_change.Bits; return m_change.Bits;
} }
Uint64 BufferObject::GetChangeSerial() const {
return m_changeSerial;
}
Bool BufferObject::IsMapped() const { Bool BufferObject::IsMapped() const {
return m_isMapped; return m_isMapped;
} }
@@ -99,6 +99,7 @@ namespace MobileGL {
Uint GetExternalIndex() const; Uint GetExternalIndex() const;
const VecRange1D& GetDirtyRanges() const; const VecRange1D& GetDirtyRanges() const;
Flags<BufferChangeBits> GetChangeBits() const; Flags<BufferChangeBits> GetChangeBits() const;
Uint64 GetChangeSerial() const;
private: private:
const Uint m_externalIndex = 0; const Uint m_externalIndex = 0;
@@ -108,6 +109,7 @@ namespace MobileGL {
Bool m_isMapped; Bool m_isMapped;
Flags<BufferMappingAccessBit> m_mappingAccess; Flags<BufferMappingAccessBit> m_mappingAccess;
BufferChange m_change; BufferChange m_change;
Uint64 m_changeSerial = 0;
Range1D m_mappedRange; Range1D m_mappedRange;
Vector<Uint8> m_stagingData; Vector<Uint8> m_stagingData;
Bool m_ownsStagingData; Bool m_ownsStagingData;