Fix issue where mapped host-coherent device memory not updated from image contents on macOS.
Currently, when a mapping is initiated for host-coherent device memory, existing image contents are copied from the MTLTexture to host memory. However, if host-coherent device memory is already mapped in a long-standing memory mapping, changes to MTLTexture content are not reflected to the host memory. This change adds that capability to open memory mappings. When the image pipeline barrier is applied to an image that is attached to a host-coherent device memory that currently has an open memory mapping, the MTLTexture contents are copied to the open host memory binding. Add MVKMappedMemoryRange to retrieve the currently mapped range in an MVKDeviceMemory. MVKImage cleanup use of isHostCoherent(). Let it be same as MVKDeviceMemory and don't test for it from macOS because it is unnecessary.
This commit is contained in:
parent
c1c49def22
commit
205b859580
@ -20,8 +20,9 @@ Released TBD
|
||||
|
||||
- Add support for extensions:
|
||||
- VK_KHR_sampler_ycbcr_conversion
|
||||
- Remove use of `@available()` directive as it was causing issues in some build environments
|
||||
- Refactor **MoltenVK** *Xcode* build architectures
|
||||
- Fix issue where mapped host-coherent device memory not updated from image contents on *macOS*.
|
||||
- Remove use of `@available()` directive as it was causing issues in some build environments.
|
||||
- Refactor **MoltenVK** *Xcode* build architectures.
|
||||
- Demo `API-Samples generateSPIRVShaders` no longer builds `MoltenVKShaderController` tool.
|
||||
|
||||
|
||||
|
@ -33,6 +33,12 @@ static const VkExternalMemoryHandleTypeFlagBits VK_EXTERNAL_MEMORY_HANDLE_TYPE_M
|
||||
|
||||
#pragma mark MVKDeviceMemory
|
||||
|
||||
typedef struct {
|
||||
VkDeviceSize offset = 0;
|
||||
VkDeviceSize size = 0;
|
||||
} MVKMappedMemoryRange;
|
||||
|
||||
|
||||
/** Represents a Vulkan device-space memory allocation. */
|
||||
class MVKDeviceMemory : public MVKVulkanAPIDeviceObject {
|
||||
|
||||
@ -77,6 +83,16 @@ public:
|
||||
/** Unmaps a previously mapped memory range. */
|
||||
void unmap();
|
||||
|
||||
/**
|
||||
* If this device memory is currently mapped to host memory, returns the range within
|
||||
* this device memory that is currently mapped to host memory, or returns {0,0} if
|
||||
* this device memory is not currently mapped to host memory.
|
||||
*/
|
||||
inline const MVKMappedMemoryRange& getMappedRange() { return _mappedRange; }
|
||||
|
||||
/** 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
|
||||
@ -150,13 +166,11 @@ protected:
|
||||
MVKSmallVector<MVKImageMemoryBinding*, 4> _imageMemoryBindings;
|
||||
std::mutex _rezLock;
|
||||
VkDeviceSize _allocationSize = 0;
|
||||
VkDeviceSize _mapOffset = 0;
|
||||
VkDeviceSize _mapSize = 0;
|
||||
MVKMappedMemoryRange _mappedRange;
|
||||
id<MTLBuffer> _mtlBuffer = nil;
|
||||
id<MTLHeap> _mtlHeap = nil;
|
||||
void* _pMemory = nullptr;
|
||||
void* _pHostMemory = nullptr;
|
||||
bool _isMapped = false;
|
||||
bool _isDedicated = false;
|
||||
MTLStorageMode _mtlStorageMode;
|
||||
MTLCPUCacheMode _mtlCPUCacheMode;
|
||||
|
@ -43,7 +43,7 @@ VkResult MVKDeviceMemory::map(VkDeviceSize offset, VkDeviceSize size, VkMemoryMa
|
||||
return reportError(VK_ERROR_MEMORY_MAP_FAILED, "Private GPU-only memory cannot be mapped to host memory.");
|
||||
}
|
||||
|
||||
if (_isMapped) {
|
||||
if (isMapped()) {
|
||||
return reportError(VK_ERROR_MEMORY_MAP_FAILED, "Memory is already mapped. Call vkUnmapMemory() first.");
|
||||
}
|
||||
|
||||
@ -51,9 +51,8 @@ VkResult MVKDeviceMemory::map(VkDeviceSize offset, VkDeviceSize size, VkMemoryMa
|
||||
return reportError(VK_ERROR_OUT_OF_HOST_MEMORY, "Could not allocate %llu bytes of host-accessible device memory.", _allocationSize);
|
||||
}
|
||||
|
||||
_mapOffset = offset;
|
||||
_mapSize = adjustMemorySize(size, offset);
|
||||
_isMapped = true;
|
||||
_mappedRange.offset = offset;
|
||||
_mappedRange.size = adjustMemorySize(size, offset);
|
||||
|
||||
*ppData = (void*)((uintptr_t)_pMemory + offset);
|
||||
|
||||
@ -65,17 +64,16 @@ VkResult MVKDeviceMemory::map(VkDeviceSize offset, VkDeviceSize size, VkMemoryMa
|
||||
|
||||
void MVKDeviceMemory::unmap() {
|
||||
|
||||
if ( !_isMapped ) {
|
||||
if ( !isMapped() ) {
|
||||
reportError(VK_ERROR_MEMORY_MAP_FAILED, "Memory is not mapped. Call vkMapMemory() first.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Coherent memory does not require flushing by app, so we must flush now.
|
||||
flushToDevice(_mapOffset, _mapSize, isMemoryHostCoherent());
|
||||
flushToDevice(_mappedRange.offset, _mappedRange.size, isMemoryHostCoherent());
|
||||
|
||||
_mapOffset = 0;
|
||||
_mapSize = 0;
|
||||
_isMapped = false;
|
||||
_mappedRange.offset = 0;
|
||||
_mappedRange.size = 0;
|
||||
}
|
||||
|
||||
VkResult MVKDeviceMemory::flushToDevice(VkDeviceSize offset, VkDeviceSize size, bool evenIfCoherent) {
|
||||
|
@ -70,6 +70,7 @@ protected:
|
||||
MVKImageSubresource* getSubresource(uint32_t mipLevel, uint32_t arrayLayer);
|
||||
void updateMTLTextureContent(MVKImageSubresource& subresource, VkDeviceSize offset, VkDeviceSize size);
|
||||
void getMTLTextureContent(MVKImageSubresource& subresource, VkDeviceSize offset, VkDeviceSize size);
|
||||
bool overlaps(VkSubresourceLayout& imgLayout, VkDeviceSize offset, VkDeviceSize size);
|
||||
void propagateDebugName();
|
||||
MVKImageMemoryBinding* getMemoryBinding() const;
|
||||
void applyImageMemoryBarrier(VkPipelineStageFlags srcStageMask,
|
||||
@ -77,6 +78,9 @@ protected:
|
||||
MVKPipelineBarrier& barrier,
|
||||
MVKCommandEncoder* cmdEncoder,
|
||||
MVKCommandUse cmdUse);
|
||||
void pullFromDeviceOnCompletion(MVKCommandEncoder* cmdEncoder,
|
||||
MVKImageSubresource& subresource,
|
||||
const MVKMappedMemoryRange& mappedRange);
|
||||
|
||||
MVKImagePlane(MVKImage* image, uint8_t planeIndex);
|
||||
|
||||
@ -119,7 +123,7 @@ public:
|
||||
MVKPipelineBarrier& barrier,
|
||||
MVKCommandEncoder* cmdEncoder,
|
||||
MVKCommandUse cmdUse) override;
|
||||
|
||||
|
||||
~MVKImageMemoryBinding();
|
||||
|
||||
protected:
|
||||
@ -315,12 +319,6 @@ public:
|
||||
/** Returns the Metal CPU cache mode used by this image. */
|
||||
MTLCPUCacheMode getMTLCPUCacheMode();
|
||||
|
||||
/**
|
||||
* Returns whether the memory is automatically coherent between device and host.
|
||||
* On macOS, this always returns false because textures cannot use Shared storage mode.
|
||||
*/
|
||||
bool isMemoryHostCoherent();
|
||||
|
||||
#pragma mark Construction
|
||||
|
||||
MVKImage(MVKDevice* device, const VkImageCreateInfo* pCreateInfo);
|
||||
|
@ -177,11 +177,7 @@ void MVKImagePlane::updateMTLTextureContent(MVKImageSubresource& subresource,
|
||||
VkSubresourceLayout& imgLayout = subresource.layout;
|
||||
|
||||
// Check if subresource overlaps the memory range.
|
||||
VkDeviceSize memStart = offset;
|
||||
VkDeviceSize memEnd = offset + size;
|
||||
VkDeviceSize imgStart = imgLayout.offset;
|
||||
VkDeviceSize imgEnd = imgLayout.offset + imgLayout.size;
|
||||
if (imgStart >= memEnd || imgEnd <= memStart) { return; }
|
||||
if ( !overlaps(imgLayout, offset, size) ) { return; }
|
||||
|
||||
// Don't update if host memory has not been mapped yet.
|
||||
void* pHostMem = getMemoryBinding()->getHostMemoryAddress();
|
||||
@ -242,11 +238,7 @@ void MVKImagePlane::getMTLTextureContent(MVKImageSubresource& subresource,
|
||||
VkSubresourceLayout& imgLayout = subresource.layout;
|
||||
|
||||
// Check if subresource overlaps the memory range.
|
||||
VkDeviceSize memStart = offset;
|
||||
VkDeviceSize memEnd = offset + size;
|
||||
VkDeviceSize imgStart = imgLayout.offset;
|
||||
VkDeviceSize imgEnd = imgLayout.offset + imgLayout.size;
|
||||
if (imgStart >= memEnd || imgEnd <= memStart) { return; }
|
||||
if ( !overlaps(imgLayout, offset, size) ) { return; }
|
||||
|
||||
// Don't update if host memory has not been mapped yet.
|
||||
void* pHostMem = getMemoryBinding()->getHostMemoryAddress();
|
||||
@ -271,6 +263,15 @@ void MVKImagePlane::getMTLTextureContent(MVKImageSubresource& subresource,
|
||||
slice: imgSubRez.arrayLayer];
|
||||
}
|
||||
|
||||
// Returns whether subresource layout overlaps the memory range.
|
||||
bool MVKImagePlane::overlaps(VkSubresourceLayout& imgLayout, VkDeviceSize offset, VkDeviceSize size) {
|
||||
VkDeviceSize memStart = offset;
|
||||
VkDeviceSize memEnd = offset + size;
|
||||
VkDeviceSize imgStart = imgLayout.offset;
|
||||
VkDeviceSize imgEnd = imgLayout.offset + imgLayout.size;
|
||||
return imgStart < memEnd && imgEnd > memStart;
|
||||
}
|
||||
|
||||
void MVKImagePlane::propagateDebugName() {
|
||||
setLabelIfNotNil(_image->_planes[_planeIndex]->_mtlTexture, _image->_debugName);
|
||||
}
|
||||
@ -297,20 +298,50 @@ void MVKImagePlane::applyImageMemoryBarrier(VkPipelineStageFlags srcStageMask,
|
||||
? _image->getLayerCount()
|
||||
: (layerStart + barrier.layerCount));
|
||||
|
||||
MVKImageMemoryBinding* memBind = getMemoryBinding();
|
||||
bool needsSync = memBind->needsHostReadSync(srcStageMask, dstStageMask, barrier);
|
||||
bool needsPull = (!memBind->_usesTexelBuffer &&
|
||||
memBind->isMemoryHostCoherent() &&
|
||||
barrier.newLayout == VK_IMAGE_LAYOUT_GENERAL &&
|
||||
mvkIsAnyFlagEnabled(barrier.dstAccessMask, (VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT)));
|
||||
MVKDeviceMemory* dvcMem = memBind->getDeviceMemory();
|
||||
const MVKMappedMemoryRange& mappedRange = dvcMem ? dvcMem->getMappedRange() : MVKMappedMemoryRange();
|
||||
|
||||
// Iterate across mipmap levels and layers, and update the image layout state for each
|
||||
for (uint32_t mipLvl = mipLvlStart; mipLvl < mipLvlEnd; mipLvl++) {
|
||||
for (uint32_t layer = layerStart; layer < layerEnd; layer++) {
|
||||
MVKImageSubresource* pImgRez = getSubresource(mipLvl, layer);
|
||||
if (pImgRez) { pImgRez->layoutState = barrier.newLayout; }
|
||||
if (needsSync) {
|
||||
#if MVK_MACOS
|
||||
if (getMemoryBinding()->needsHostReadSync(srcStageMask, dstStageMask, barrier)) {
|
||||
[cmdEncoder->getMTLBlitEncoder(cmdUse) synchronizeTexture: _mtlTexture slice: layer level: mipLvl];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// Check if image content should be pulled into host-mapped device memory after
|
||||
// the GPU is done with it. This only applies if the image is intended to be
|
||||
// host-coherent, is not using a texel buffer, is transitioning to host-readable,
|
||||
// AND the device memory has an already-open memory mapping. If a memory mapping is
|
||||
// created later, it will pull the image contents in at that time, so it is not needed now.
|
||||
// The mapped range will be {0,0} if device memory is not currently mapped.
|
||||
if (needsPull && pImgRez && overlaps(pImgRez->layout, mappedRange.offset, mappedRange.size)) {
|
||||
pullFromDeviceOnCompletion(cmdEncoder, *pImgRez, mappedRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Once the command buffer completes, pull the content of the subresource into host memory.
|
||||
// This is only necessary when the image memory is intended to be host-coherent, and the
|
||||
// device memory is currently mapped to host memory
|
||||
void MVKImagePlane::pullFromDeviceOnCompletion(MVKCommandEncoder* cmdEncoder,
|
||||
MVKImageSubresource& subresource,
|
||||
const MVKMappedMemoryRange& mappedRange) {
|
||||
|
||||
[cmdEncoder->_mtlCmdBuffer addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
|
||||
getMTLTextureContent(subresource, mappedRange.offset, mappedRange.size);
|
||||
}];
|
||||
}
|
||||
|
||||
MVKImagePlane::MVKImagePlane(MVKImage* image, uint8_t planeIndex) {
|
||||
_image = image;
|
||||
_planeIndex = planeIndex;
|
||||
@ -360,8 +391,7 @@ VkResult MVKImageMemoryBinding::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDevi
|
||||
|
||||
#if MVK_MACOS
|
||||
// macOS cannot use shared memory for texel buffers.
|
||||
// Test _deviceMemory->isMemoryHostCoherent() directly because local version overrides.
|
||||
_usesTexelBuffer = _usesTexelBuffer && _deviceMemory && !_deviceMemory->isMemoryHostCoherent();
|
||||
_usesTexelBuffer = _usesTexelBuffer && !isMemoryHostCoherent();
|
||||
#endif
|
||||
|
||||
flushToDevice(getDeviceMemoryOffset(), getByteCount());
|
||||
@ -394,9 +424,10 @@ bool MVKImageMemoryBinding::needsHostReadSync(VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask,
|
||||
MVKPipelineBarrier& barrier) {
|
||||
#if MVK_MACOS
|
||||
// On macOS, texture memory is never host-coherent, so don't test for it.
|
||||
return ((barrier.newLayout == VK_IMAGE_LAYOUT_GENERAL) &&
|
||||
mvkIsAnyFlagEnabled(barrier.dstAccessMask, (VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT)) &&
|
||||
isMemoryHostAccessible() && !isMemoryHostCoherent());
|
||||
isMemoryHostAccessible());
|
||||
#endif
|
||||
#if MVK_IOS
|
||||
return false;
|
||||
@ -728,9 +759,6 @@ MTLCPUCacheMode MVKImage::getMTLCPUCacheMode() {
|
||||
return _memoryBindings[0]->_deviceMemory ? _memoryBindings[0]->_deviceMemory->getMTLCPUCacheMode() : MTLCPUCacheModeDefaultCache;
|
||||
}
|
||||
|
||||
bool MVKImage::isMemoryHostCoherent() {
|
||||
return (getMTLStorageMode() == MTLStorageModeShared);
|
||||
}
|
||||
|
||||
#pragma mark Construction
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user