Reorganize coherent texture flushing on memory map and unmap.

MVKDeviceMemory track original Vulkan memory coherency request, not just Metal
coherency status.

MVKDeviceMemory map() and unmap() only triggers host flush if coherent Vulkan memory
was requested, not just when Metal memory is coherent, because this can be changed
for dedicated image resources.

Redesign MVKDeviceMemory flushToDevice() and pullFromDevice()
to be consistent operation with each other.

Don't trigger automatic texture flush during vkCmdCopyImage(), as it can
cause an unexpected update from mapped memory, resulting in a race condition.
This commit is contained in:
Bill Hollings 2021-06-09 10:53:10 -04:00
parent b74040a93d
commit a2d8cf1710
4 changed files with 43 additions and 44 deletions

View File

@ -88,8 +88,6 @@ void MVKCmdCopyImage<N>::encode(MVKCommandEncoder* cmdEncoder, MVKCommandUse com
VkBufferImageCopy vkDstCopies[copyCnt];
size_t tmpBuffSize = 0;
_srcImage->flushToDevice(0, VK_WHOLE_SIZE);
for (uint32_t copyIdx = 0; copyIdx < copyCnt; copyIdx++) {
auto& vkIC = _vkImageCopies[copyIdx];

View File

@ -3616,7 +3616,7 @@ VkResult MVKDevice::invalidateMappedMemoryRanges(uint32_t memRangeCount, const V
for (uint32_t i = 0; i < memRangeCount; i++) {
const VkMappedMemoryRange* pMem = &pMemRanges[i];
MVKDeviceMemory* mvkMem = (MVKDeviceMemory*)pMem->memory;
VkResult r = mvkMem->pullFromDevice(pMem->offset, pMem->size, false, &mvkBlitEnc);
VkResult r = mvkMem->pullFromDevice(pMem->offset, pMem->size, &mvkBlitEnc);
if (rslt == VK_SUCCESS) { rslt = r; }
}
if (mvkBlitEnc.mtlBlitEncoder) { [mvkBlitEnc.mtlBlitEncoder endEncoding]; }

View File

@ -93,17 +93,11 @@ public:
/** Returns whether this device memory is currently mapped to host memory. */
bool isMapped() { return _mappedRange.size > 0; }
/**
* If this memory is host-visible, the specified memory range is flushed to the device.
* Normally, flushing will only occur if the device memory is non-coherent, but flushing
* to coherent memory can be forced by setting evenIfCoherent to true.
*/
VkResult flushToDevice(VkDeviceSize offset, VkDeviceSize size, bool evenIfCoherent = false);
/** If this memory is host-visible, the specified memory range is flushed to the device. */
VkResult flushToDevice(VkDeviceSize offset, VkDeviceSize size);
/**
* If this memory is host-visible, pulls the specified memory range from the device.
* Normally, pulling will only occur if the device memory is non-coherent, but pulling
* to coherent memory can be forced by setting evenIfCoherent to true.
*
* If pBlitEnc is not null, it points to a holder for a MTLBlitCommandEncoder and its
* associated MTLCommandBuffer. If this instance has a MTLBuffer using managed memory,
@ -114,7 +108,6 @@ public:
*/
VkResult pullFromDevice(VkDeviceSize offset,
VkDeviceSize size,
bool evenIfCoherent = false,
MVKMTLBlitEncoder* pBlitEnc = nullptr);
@ -172,8 +165,10 @@ protected:
id<MTLHeap> _mtlHeap = nil;
void* _pMemory = nullptr;
void* _pHostMemory = nullptr;
bool _isDedicated = false;
VkMemoryPropertyFlags _vkMemProps;
MTLStorageMode _mtlStorageMode;
MTLCPUCacheMode _mtlCPUCacheMode;
bool _isDedicated = false;
};

View File

@ -55,8 +55,11 @@ VkResult MVKDeviceMemory::map(VkDeviceSize offset, VkDeviceSize size, VkMemoryMa
*ppData = (void*)((uintptr_t)_pMemory + offset);
// Coherent memory does not require flushing by app, so we must flush now, to handle any texture updates.
pullFromDevice(offset, size, isMemoryHostCoherent());
// Coherent memory does not require flushing by app, so we must flush now
// to support Metal textures that actually reside in non-coherent memory.
if (mvkIsAnyFlagEnabled(_vkMemProps, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
pullFromDevice(offset, size);
}
return VK_SUCCESS;
}
@ -68,54 +71,57 @@ void MVKDeviceMemory::unmap() {
return;
}
// Coherent memory does not require flushing by app, so we must flush now.
flushToDevice(_mappedRange.offset, _mappedRange.size, isMemoryHostCoherent());
// Coherent memory does not require flushing by app, so we must flush now
// to support Metal textures that actually reside in non-coherent memory.
if (mvkIsAnyFlagEnabled(_vkMemProps, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
flushToDevice(_mappedRange.offset, _mappedRange.size);
}
_mappedRange.offset = 0;
_mappedRange.size = 0;
}
VkResult MVKDeviceMemory::flushToDevice(VkDeviceSize offset, VkDeviceSize size, bool evenIfCoherent) {
// Coherent memory is flushed on unmap(), so it is only flushed if forced
VkResult MVKDeviceMemory::flushToDevice(VkDeviceSize offset, VkDeviceSize size) {
VkDeviceSize memSize = adjustMemorySize(size, offset);
if (memSize > 0 && isMemoryHostAccessible() && (evenIfCoherent || !isMemoryHostCoherent()) ) {
if (memSize == 0 || !isMemoryHostAccessible()) { return VK_SUCCESS; }
#if MVK_MACOS
if (_mtlBuffer && _mtlStorageMode == MTLStorageModeManaged) {
[_mtlBuffer didModifyRange: NSMakeRange(offset, memSize)];
}
if (_mtlBuffer && _mtlStorageMode == MTLStorageModeManaged) {
[_mtlBuffer didModifyRange: NSMakeRange(offset, memSize)];
}
#endif
// If we have an MTLHeap object, there's no need to sync memory manually between images and the buffer.
if (!_mtlHeap) {
lock_guard<mutex> lock(_rezLock);
for (auto& img : _imageMemoryBindings) { img->flushToDevice(offset, memSize); }
for (auto& buf : _buffers) { buf->flushToDevice(offset, memSize); }
}
// If we have an MTLHeap object, there's no need to sync memory manually between resources and the buffer.
if ( !_mtlHeap ) {
lock_guard<mutex> lock(_rezLock);
for (auto& img : _imageMemoryBindings) { img->flushToDevice(offset, memSize); }
for (auto& buf : _buffers) { buf->flushToDevice(offset, memSize); }
}
return VK_SUCCESS;
}
VkResult MVKDeviceMemory::pullFromDevice(VkDeviceSize offset,
VkDeviceSize size,
bool evenIfCoherent,
MVKMTLBlitEncoder* pBlitEnc) {
// Coherent memory is flushed on unmap(), so it is only flushed if forced
VkDeviceSize memSize = adjustMemorySize(size, offset);
if (memSize > 0 && isMemoryHostAccessible() && (evenIfCoherent || !isMemoryHostCoherent()) && !_mtlHeap) {
if (memSize == 0 || !isMemoryHostAccessible()) { return VK_SUCCESS; }
#if MVK_MACOS
if (pBlitEnc && _mtlBuffer && _mtlStorageMode == MTLStorageModeManaged) {
if ( !pBlitEnc->mtlCmdBuffer) { pBlitEnc->mtlCmdBuffer = _device->getAnyQueue()->getMTLCommandBuffer(kMVKCommandUseInvalidateMappedMemoryRanges); }
if ( !pBlitEnc->mtlBlitEncoder) { pBlitEnc->mtlBlitEncoder = [pBlitEnc->mtlCmdBuffer blitCommandEncoder]; }
[pBlitEnc->mtlBlitEncoder synchronizeResource: _mtlBuffer];
}
#endif
// If we have an MTLHeap object, there's no need to sync memory manually between resources and the buffer.
if ( !_mtlHeap ) {
lock_guard<mutex> lock(_rezLock);
for (auto& img : _imageMemoryBindings) { img->pullFromDevice(offset, memSize); }
for (auto& buf : _buffers) { buf->pullFromDevice(offset, memSize); }
#if MVK_MACOS
if (pBlitEnc && _mtlBuffer && _mtlStorageMode == MTLStorageModeManaged) {
if ( !pBlitEnc->mtlCmdBuffer) { pBlitEnc->mtlCmdBuffer = _device->getAnyQueue()->getMTLCommandBuffer(kMVKCommandUseInvalidateMappedMemoryRanges); }
if ( !pBlitEnc->mtlBlitEncoder) { pBlitEnc->mtlBlitEncoder = [pBlitEnc->mtlCmdBuffer blitCommandEncoder]; }
[pBlitEnc->mtlBlitEncoder synchronizeResource: _mtlBuffer];
}
#endif
}
return VK_SUCCESS;
}
@ -271,9 +277,9 @@ MVKDeviceMemory::MVKDeviceMemory(MVKDevice* device,
const VkMemoryAllocateInfo* pAllocateInfo,
const VkAllocationCallbacks* pAllocator) : MVKVulkanAPIDeviceObject(device) {
// Set Metal memory parameters
VkMemoryPropertyFlags vkMemProps = _device->_pMemoryProperties->memoryTypes[pAllocateInfo->memoryTypeIndex].propertyFlags;
_mtlStorageMode = mvkMTLStorageModeFromVkMemoryPropertyFlags(vkMemProps);
_mtlCPUCacheMode = mvkMTLCPUCacheModeFromVkMemoryPropertyFlags(vkMemProps);
_vkMemProps = _device->_pMemoryProperties->memoryTypes[pAllocateInfo->memoryTypeIndex].propertyFlags;
_mtlStorageMode = mvkMTLStorageModeFromVkMemoryPropertyFlags(_vkMemProps);
_mtlCPUCacheMode = mvkMTLCPUCacheModeFromVkMemoryPropertyFlags(_vkMemProps);
_allocationSize = pAllocateInfo->allocationSize;