diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h index 44609f96e..71d54579c 100644 --- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h +++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h @@ -48,13 +48,13 @@ extern "C" { */ #define MVK_VERSION_MAJOR 1 #define MVK_VERSION_MINOR 0 -#define MVK_VERSION_PATCH 9 +#define MVK_VERSION_PATCH 10 #define MVK_MAKE_VERSION(major, minor, patch) (((major) * 10000) + ((minor) * 100) + (patch)) #define MVK_VERSION MVK_MAKE_VERSION(MVK_VERSION_MAJOR, MVK_VERSION_MINOR, MVK_VERSION_PATCH) -#define VK_MVK_MOLTENVK_SPEC_VERSION 4 +#define VK_MVK_MOLTENVK_SPEC_VERSION 5 #define VK_MVK_MOLTENVK_EXTENSION_NAME "VK_MVK_moltenvk" /** diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h index a9d2e56c7..369ed4a67 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h @@ -36,6 +36,9 @@ class MVKBuffer : public MVKResource { /** Returns the memory requirements of this resource by populating the specified structure. */ VkResult getMemoryRequirements(VkMemoryRequirements* pMemoryRequirements) override; + /** Binds this resource to the specified offset within the specified memory allocation. */ + VkResult bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) override; + /** Applies the specified global memory barrier. */ void applyMemoryBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, @@ -54,10 +57,10 @@ class MVKBuffer : public MVKResource { #pragma mark Metal /** Returns the Metal buffer underlying this memory allocation. */ - id getMTLBuffer(); + inline id getMTLBuffer() { return _deviceMemory ? _deviceMemory->getMTLBuffer() : nullptr; } /** Returns the offset at which the contents of this instance starts within the underlying Metal buffer. */ - NSUInteger getMTLBufferOffset(); + inline NSUInteger getMTLBufferOffset() { return _deviceMemoryOffset; } #pragma mark Construction @@ -69,17 +72,9 @@ class MVKBuffer : public MVKResource { protected: using MVKResource::needsHostReadSync; - void* map(VkDeviceSize offset, VkDeviceSize size) override; - VkResult flushToDevice(VkDeviceSize offset, VkDeviceSize size) override; - VkResult pullFromDevice(VkDeviceSize offset, VkDeviceSize size) override; - VkResult copyMTLBufferContent(VkDeviceSize offset, VkDeviceSize size, bool intoMTLBuffer); - NSRange mtlBufferRange(VkDeviceSize offset, VkDeviceSize size); bool needsHostReadSync(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkBufferMemoryBarrier* pBufferMemoryBarrier); - - id _mtlBuffer; - std::mutex _lock; }; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm index 8357c9760..ed71b3f3a 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm @@ -31,11 +31,19 @@ VkResult MVKBuffer::getMemoryRequirements(VkMemoryRequirements* pMemoryRequirements) { pMemoryRequirements->size = getByteCount(); - pMemoryRequirements->alignment = getByteAlignment(); + pMemoryRequirements->alignment = _byteAlignment; pMemoryRequirements->memoryTypeBits = _device->getPhysicalDevice()->getAllMemoryTypes(); return VK_SUCCESS; } +VkResult MVKBuffer::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) { + if (_deviceMemory) { _deviceMemory->removeBuffer(this); } + + MVKResource::bindDeviceMemory(mvkMem, memOffset); + + return _deviceMemory ? _deviceMemory->addBuffer(this) : VK_SUCCESS; +} + void MVKBuffer::applyMemoryBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkMemoryBarrier* pMemoryBarrier, @@ -73,104 +81,8 @@ #if MVK_MACOS return (mvkIsAnyFlagEnabled(dstStageMask, (VK_PIPELINE_STAGE_HOST_BIT)) && mvkIsAnyFlagEnabled(pBufferMemoryBarrier->dstAccessMask, (VK_ACCESS_HOST_READ_BIT)) && - _deviceMemory->isMemoryHostAccessible() && !_deviceMemory->isMemoryHostCoherent()); -#endif -} - -/** Called when the bound device memory is updated. Flushes any associated resource memory. */ -VkResult MVKBuffer::flushToDevice(VkDeviceSize offset, VkDeviceSize size) { - VkResult rslt = copyMTLBufferContent(offset, size, true); - -#if MVK_MACOS - if (_deviceMemory->getMTLStorageMode() == MTLStorageModeManaged) { - [getMTLBuffer() didModifyRange: mtlBufferRange(offset, size)]; - } + isMemoryHostAccessible() && !isMemoryHostCoherent()); #endif - - return rslt; -} - -// Called when the bound device memory is invalidated. Pulls any associated resource memory from the device. -VkResult MVKBuffer::pullFromDevice(VkDeviceSize offset, VkDeviceSize size) { - VkResult rslt = copyMTLBufferContent(offset, size, false); - - // If we are pulling to populate a newly created device memory MTLBuffer, - // from a previously created local MTLBuffer, remove the local MTLBuffer. - // Use autorelease in case the earlier MTLBuffer was encoded. - if (_mtlBuffer && _deviceMemory->getMTLBuffer()) { - [_mtlBuffer autorelease]; - _mtlBuffer = nil; - } - - return rslt; -} - -void* MVKBuffer::map(VkDeviceSize offset, VkDeviceSize size) { - return (void*)((uintptr_t)getMTLBuffer().contents + mtlBufferRange(offset, size).location); -} - -// Copies host content into or out of the MTLBuffer. -VkResult MVKBuffer::copyMTLBufferContent(VkDeviceSize offset, VkDeviceSize size, bool intoMTLBuffer) { - - // Only copy if there is separate host memory and this buffer overlaps the host memory range - void* pMemBase = _deviceMemory->getLogicalMappedMemory(); - if (pMemBase && doesOverlap(offset, size)) { - - NSRange copyRange = mtlBufferRange(offset, size); - VkDeviceSize memOffset = max(offset, _deviceMemoryOffset); - -// MVKLogDebug("Copying contents %s buffer %p at buffer offset %d memory offset %d and length %d.", (intoMTLBuffer ? "to" : "from"), this, copyRange.location, memOffset, copyRange.length); - - void* pMemBytes = (void*)((uintptr_t)pMemBase + memOffset); - void* pMTLBuffBytes = (void*)((uintptr_t)getMTLBuffer().contents + copyRange.location); - - // Copy in the direction indicated. - // Don't copy if the source and destination are the same address, which will - // occur if the underlying MTLBuffer comes from the device memory object. - if (pMemBytes != pMTLBuffBytes) { -// MVKLogDebug("Copying buffer contents."); - if (intoMTLBuffer) { - memcpy(pMTLBuffBytes, pMemBytes, copyRange.length); - } else { - memcpy(pMemBytes, pMTLBuffBytes, copyRange.length); - } - } - } - - return VK_SUCCESS; -} - - -#pragma mark Metal - -// If a local MTLBuffer already exists, use it. -// If the device memory has a MTLBuffer, use it. -// Otherwise, create a new MTLBuffer and use it from now on. -id MVKBuffer::getMTLBuffer() { - - if (_mtlBuffer) { return _mtlBuffer; } - - id devMemMTLBuff = _deviceMemory->getMTLBuffer(); - if (devMemMTLBuff) { return devMemMTLBuff; } - - // Lock and check again in case another thread has created the buffer. - lock_guard lock(_lock); - if (_mtlBuffer) { return _mtlBuffer; } - - NSUInteger mtlBuffLen = mvkAlignByteOffset(_byteCount, _byteAlignment); - _mtlBuffer = [getMTLDevice() newBufferWithLength: mtlBuffLen - options: _deviceMemory->getMTLResourceOptions()]; // retained -// MVKLogDebug("MVKBuffer %p creating local MTLBuffer of size %d.", this, _mtlBuffer.length); - return _mtlBuffer; -} - -NSUInteger MVKBuffer::getMTLBufferOffset() { return _mtlBuffer ? 0 : _deviceMemoryOffset; } - -// Returns an NSRange that maps the specified host memory range to the MTLBuffer. -NSRange MVKBuffer::mtlBufferRange(VkDeviceSize offset, VkDeviceSize size) { - NSUInteger localRangeLoc = min((offset > _deviceMemoryOffset) ? (offset - _deviceMemoryOffset) : 0, _byteCount); - NSUInteger localRangeLen = min(size, _byteCount - localRangeLoc); - return NSMakeRange(getMTLBufferOffset() + localRangeLoc, localRangeLen); } @@ -179,12 +91,10 @@ MVKBuffer::MVKBuffer(MVKDevice* device, const VkBufferCreateInfo* pCreateInfo) : MVKResource(device) { _byteAlignment = _device->_pMetalFeatures->mtlBufferAlignment; _byteCount = pCreateInfo->size; - _mtlBuffer = nil; } MVKBuffer::~MVKBuffer() { - [_mtlBuffer release]; - _mtlBuffer = nil; + if (_deviceMemory) { _deviceMemory->removeBuffer(this); } } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.h b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.h index c09daccf3..433032beb 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.h @@ -23,7 +23,8 @@ #import -class MVKResource; +class MVKBuffer; +class MVKImage; #pragma mark MVKDeviceMemory @@ -42,18 +43,11 @@ class MVKDeviceMemory : public MVKBaseDeviceObject { /** Returns the memory already committed by this instance. */ inline VkDeviceSize getDeviceMemoryCommitment() { return _allocationSize; } - /** - * Returns the host memory address that represents what would be the beginning of the - * mapped address space if the entire device memory represented by this object were to - * be mapped to host memory. - * - * This is the address to which the offset value in the vMapMemory() call references. - * It only has physical meaning if offset is zero, otherwise it is a logical address - * used to calculate resource offsets. - * - * This function must only be called between vkMapMemory() and vkUnmapMemory() calls. - */ - inline void* getLogicalMappedMemory() { return _pLogicalMappedMemory; } + /** + * Returns the host memory address of this memory, or NULL if the memory + * is marked as device-only and cannot be mapped to a host address. + */ + inline void* getHostMemoryAddress() { return _pMemory; } /** * Maps the memory address at the specified offset from the start of this memory allocation, @@ -64,9 +58,6 @@ class MVKDeviceMemory : public MVKBaseDeviceObject { /** Unmaps a previously mapped memory range. */ void unmap(); - /** Allocates mapped host memory, and returns a pointer to it. */ - void* allocateMappedMemory(VkDeviceSize offset, VkDeviceSize size); - /** * 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 @@ -107,25 +98,30 @@ class MVKDeviceMemory : public MVKBaseDeviceObject { ~MVKDeviceMemory() override; protected: - friend MVKResource; + friend MVKBuffer; + friend MVKImage; VkDeviceSize adjustMemorySize(VkDeviceSize size, VkDeviceSize offset); - bool mapToUniqueResource(VkDeviceSize offset, VkDeviceSize size); - void addResource(MVKResource* rez); - void removeResource(MVKResource* rez); - - std::vector _resources; + VkResult addBuffer(MVKBuffer* mvkBuff); + void removeBuffer(MVKBuffer* mvkBuff); + VkResult addImage(MVKImage* mvkImg); + void removeImage(MVKImage* mvkImg); + bool ensureMTLBuffer(); + bool ensureHostMemory(); + void freeHostMemory(); + + std::vector _buffers; + std::vector _images; std::mutex _rezLock; - VkDeviceSize _allocationSize; - VkDeviceSize _mapOffset; - VkDeviceSize _mapSize; - id _mtlBuffer; - std::mutex _lock; + VkDeviceSize _allocationSize = 0; + VkDeviceSize _mapOffset = 0; + VkDeviceSize _mapSize = 0; + id _mtlBuffer = nil; + void* _pMemory = nullptr; + void* _pHostMemory = nullptr; + bool _isMapped = false; MTLResourceOptions _mtlResourceOptions; MTLStorageMode _mtlStorageMode; MTLCPUCacheMode _mtlCPUCacheMode; - void* _pMappedHostAllocation; - void* _pMappedMemory; - void* _pLogicalMappedMemory; }; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm index ee9013266..f1cbbeb75 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm @@ -17,6 +17,7 @@ */ #include "MVKDeviceMemory.h" +#include "MVKBuffer.h" #include "MVKImage.h" #include "mvk_datatypes.h" #include "MVKFoundation.h" @@ -27,134 +28,60 @@ #pragma mark MVKDeviceMemory -// Metal does not support the concept of mappable device memory separate from individual -// resources. There are a number of potentially conflicting requirements defined by Vulkan -// that make it a challenge to map device memory to Metal resources. -// 1) Memory can be mapped and populated prior to any resources being bound. -// 2) Coherent memory can be mapped forever and simply overwritten without regard for -// requiring host generated update indications. -// 3) MTLTextures are never natively coherent. -// 4) MTLBuffers are restricted to smaller sizes (eg. 256MB - 1GB) than MTLTextures. -// -// To try to deal with all of this... -// 1) If the mapped range falls within a single resource, we map it directly. This allows -// us to maximize the size of the resources (images and buffers can be larger)...and -// coherent buffers can be mapped directly. -// 2) If we can't map to a single resource, and memory must be coherent, allocate a single -// coherent MTLBuffer for the entire memory range. If any attached resources already have -// content, the subsequent coherent pullFromDevice() will populate the larger MTLBuffer. -// 3) If we can't map to a single resource, and memory is not coherent, we can allocate the -// host portion as an aligned malloc, and the individual resources will copy to and from it. -// 4) There is no way around requiring coherent memory that is used for image to be updated -// by the host, or at least unmapped, so that we have a signal to update MTLTexture content. VkResult MVKDeviceMemory::map(VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData) { - if ( !isMemoryHostAccessible() ) { - return mvkNotifyErrorWithText(VK_ERROR_MEMORY_MAP_FAILED, "Private GPU-only memory cannot be mapped to host memory."); - } - - if (_pMappedMemory) { - return mvkNotifyErrorWithText(VK_ERROR_MEMORY_MAP_FAILED, "Memory is already mapped. Call vkUnmapMemory() first."); - } - - VkDeviceSize mapSize = adjustMemorySize(size, offset); -// MVKLogDebug("Mapping device memory %p with offset %d and size %d.", this, offset, mapSize); - if ( !mapToUniqueResource(offset, mapSize) ) { - if (isMemoryHostCoherent()) { - if ( !_mtlBuffer ) { - - // Lock and check again in case another thread has created the buffer. - lock_guard lock(_lock); - if ( !_mtlBuffer ) { - NSUInteger mtlBuffLen = mvkAlignByteOffset(_allocationSize, _device->_pMetalFeatures->mtlBufferAlignment); - _mtlBuffer = [getMTLDevice() newBufferWithLength: mtlBuffLen options: _mtlResourceOptions]; // retained -// MVKLogDebug("Allocating host mapped memory %p with offset %d and size %d via underlying coherent MTLBuffer %p of size %d.", this, offset, mapSize, _mtlBuffer , _mtlBuffer.length); - } - } - _pLogicalMappedMemory = _mtlBuffer.contents; - _pMappedMemory = (void*)((uintptr_t)_pLogicalMappedMemory + offset); - } else { -// MVKLogDebug("Allocating host mapped memory %p with offset %d and size %d via host allocation.", this, offset, mapSize); - _pMappedMemory = allocateMappedMemory(offset, mapSize); - } - } - - *ppData = _pMappedMemory; - _mapOffset = offset; - _mapSize = mapSize; + if ( !isMemoryHostAccessible() ) { + return mvkNotifyErrorWithText(VK_ERROR_MEMORY_MAP_FAILED, "Private GPU-only memory cannot be mapped to host memory."); + } + + if (_isMapped) { + return mvkNotifyErrorWithText(VK_ERROR_MEMORY_MAP_FAILED, "Memory is already mapped. Call vkUnmapMemory() first."); + } + + if ( !ensureHostMemory() ) { + return mvkNotifyErrorWithText(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; + + *ppData = (void*)((uintptr_t)_pMemory + offset); // Coherent memory does not require flushing by app, so we must flush now, to handle any texture updates. - if (isMemoryHostCoherent()) { pullFromDevice(offset, size, true); } + pullFromDevice(offset, size, isMemoryHostCoherent()); return VK_SUCCESS; } void MVKDeviceMemory::unmap() { -// MVKLogDebug("Unapping device memory %p.", this); - if (!_pMappedMemory) { - mvkNotifyErrorWithText(VK_ERROR_MEMORY_MAP_FAILED, "Memory is not mapped. Call vkMapMemory() first."); - return; - } + if ( !_isMapped ) { + mvkNotifyErrorWithText(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. - if (isMemoryHostCoherent()) { flushToDevice(_mapOffset, _mapSize, true); } - - free(_pMappedHostAllocation); - _pMappedHostAllocation = VK_NULL_HANDLE; - _pMappedMemory = VK_NULL_HANDLE; - _pLogicalMappedMemory = VK_NULL_HANDLE; + flushToDevice(_mapOffset, _mapSize, isMemoryHostCoherent()); _mapOffset = 0; _mapSize = 0; -} - -// Attempts to map the memory defined by the offset and size to a unique resource, and returns -// whether such a mapping was possible. If it was, the mapped region is stored in _pMappedMemory. -bool MVKDeviceMemory::mapToUniqueResource(VkDeviceSize offset, VkDeviceSize size) { - lock_guard lock(_rezLock); - MVKResource* uniqueRez = nullptr; - for (auto& rez : _resources) { - if (rez->doesContain(offset, size)) { - if (uniqueRez) { return false; } // More than one resource mapped to the region - uniqueRez = rez; - } - } - - if (uniqueRez) { - _pMappedMemory = uniqueRez->map(offset, size); - return true; - } - - return false; -} - -void* MVKDeviceMemory::allocateMappedMemory(VkDeviceSize offset, VkDeviceSize size) { - - void* pMapAlloc = VK_NULL_HANDLE; - -// MVKLogDebug("Allocating %d bytes of device memory %p.", size, this); - - size_t mmAlign = _device->_pProperties->limits.minMemoryMapAlignment; - VkDeviceSize deltaOffset = offset % mmAlign; - int err = posix_memalign(&pMapAlloc, mmAlign, mvkAlignByteOffset(size + deltaOffset, mmAlign)); - if (err) { - mvkNotifyErrorWithText(VK_ERROR_MEMORY_MAP_FAILED, "Could not allocate host memory to map to GPU memory."); - return nullptr; - } - - _pMappedHostAllocation = pMapAlloc; - _pLogicalMappedMemory = (void*)((uintptr_t)pMapAlloc - offset); - - return (void*)((uintptr_t)pMapAlloc + deltaOffset); + _isMapped = false; } VkResult MVKDeviceMemory::flushToDevice(VkDeviceSize offset, VkDeviceSize size, bool evenIfCoherent) { // Coherent memory is flushed on unmap(), so it is only flushed if forced - if (size > 0 && isMemoryHostAccessible() && (evenIfCoherent || !isMemoryHostCoherent()) ) { + VkDeviceSize memSize = adjustMemorySize(size, offset); + if (memSize > 0 && isMemoryHostAccessible() && (evenIfCoherent || !isMemoryHostCoherent()) ) { + +#if MVK_MACOS + if (_mtlBuffer && _mtlStorageMode == MTLStorageModeManaged) { + [_mtlBuffer didModifyRange: NSMakeRange(offset, memSize)]; + } +#endif + lock_guard lock(_rezLock); - VkDeviceSize memSize = adjustMemorySize(size, offset); - for (auto& rez : _resources) { rez->flushToDevice(offset, memSize); } + for (auto& img : _images) { img->flushToDevice(offset, memSize); } } return VK_SUCCESS; } @@ -164,54 +91,118 @@ VkDeviceSize memSize = adjustMemorySize(size, offset); if (memSize > 0 && isMemoryHostAccessible() && (evenIfCoherent || !isMemoryHostCoherent()) ) { lock_guard lock(_rezLock); - for (auto& rez : _resources) { rez->pullFromDevice(offset, memSize); } + for (auto& img : _images) { img->pullFromDevice(offset, memSize); } } return VK_SUCCESS; } -/** - * If the size parameter is the special constant VK_WHOLE_SIZE, returns the size of memory - * between offset and the end of the buffer, otherwise simply returns size. - */ +// If the size parameter is the special constant VK_WHOLE_SIZE, returns the size of memory +// between offset and the end of the buffer, otherwise simply returns size. VkDeviceSize MVKDeviceMemory::adjustMemorySize(VkDeviceSize size, VkDeviceSize offset) { return (size == VK_WHOLE_SIZE) ? (_allocationSize - offset) : size; } -void MVKDeviceMemory::addResource(MVKResource* rez) { +VkResult MVKDeviceMemory::addBuffer(MVKBuffer* mvkBuff) { + lock_guard lock(_rezLock); + + if (!ensureMTLBuffer() ) { + return mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not bind a VkBuffer to a VkDeviceMemory of size %llu bytes. The maximum memory-aligned size of a VkDeviceMemory that supports a VkBuffer is %llu bytes.", _allocationSize, _device->_pMetalFeatures->maxMTLBufferSize); + } + + _buffers.push_back(mvkBuff); + + return VK_SUCCESS; +} + +void MVKDeviceMemory::removeBuffer(MVKBuffer* mvkBuff) { lock_guard lock(_rezLock); - _resources.push_back(rez); + mvkRemoveAllOccurances(_buffers, mvkBuff); } -void MVKDeviceMemory::removeResource(MVKResource* rez) { +VkResult MVKDeviceMemory::addImage(MVKImage* mvkImg) { lock_guard lock(_rezLock); - mvkRemoveAllOccurances(_resources, rez); + + _images.push_back(mvkImg); + + return VK_SUCCESS; +} + +void MVKDeviceMemory::removeImage(MVKImage* mvkImg) { + lock_guard lock(_rezLock); + mvkRemoveAllOccurances(_images, mvkImg); +} + +// Ensures that this instance is backed by a MTLBuffer object, +// creating the MTLBuffer if needed, and returns whether it was successful. +bool MVKDeviceMemory::ensureMTLBuffer() { + + if (_mtlBuffer) { return true; } + + NSUInteger memLen = mvkAlignByteOffset(_allocationSize, _device->_pMetalFeatures->mtlBufferAlignment); + + if (memLen > _device->_pMetalFeatures->maxMTLBufferSize) { return false; } + + // If host memory was already allocated, it is copied into the new MTLBuffer, and then released. + if (_pHostMemory) { + _mtlBuffer = [getMTLDevice() newBufferWithBytes: _pHostMemory length: memLen options: _mtlResourceOptions]; // retained + freeHostMemory(); + } else { + _mtlBuffer = [getMTLDevice() newBufferWithLength: memLen options: _mtlResourceOptions]; // retained + } + _pMemory = isMemoryHostAccessible() ? _mtlBuffer.contents : nullptr; + + return true; +} + +// Ensures that host-accessible memory is available, allocating it if necessary. +bool MVKDeviceMemory::ensureHostMemory() { + + if (_pMemory) { return true; } + + if ( !_pHostMemory) { + size_t memAlign = _device->_pMetalFeatures->mtlBufferAlignment; + NSUInteger memLen = mvkAlignByteOffset(_allocationSize, memAlign); + int err = posix_memalign(&_pHostMemory, memAlign, memLen); + if (err) { return false; } + } + + _pMemory = _pHostMemory; + + return true; +} + +void MVKDeviceMemory::freeHostMemory() { + free(_pHostMemory); + _pHostMemory = nullptr; } MVKDeviceMemory::MVKDeviceMemory(MVKDevice* device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator) : MVKBaseDeviceObject(device) { - _allocationSize = pAllocateInfo->allocationSize; - _mtlBuffer = nil; - _mapOffset = 0; - _mapSize = 0; - - _pMappedHostAllocation = VK_NULL_HANDLE; - _pMappedMemory = VK_NULL_HANDLE; - _pLogicalMappedMemory = VK_NULL_HANDLE; - // Set Metal memory parameters VkMemoryPropertyFlags vkMemProps = _device->_pMemoryProperties->memoryTypes[pAllocateInfo->memoryTypeIndex].propertyFlags; _mtlResourceOptions = mvkMTLResourceOptionsFromVkMemoryPropertyFlags(vkMemProps); _mtlStorageMode = mvkMTLStorageModeFromVkMemoryPropertyFlags(vkMemProps); _mtlCPUCacheMode = mvkMTLCPUCacheModeFromVkMemoryPropertyFlags(vkMemProps); + + _allocationSize = pAllocateInfo->allocationSize; + + // If memory needs to be coherent it must reside in an MTLBuffer, since an open-ended map() must work. + if (isMemoryHostCoherent() && !ensureMTLBuffer() ) { + setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not allocate a host-coherent VkDeviceMemory of size %llu bytes. The maximum memory-aligned size of a host-coherent VkDeviceMemory is %llu bytes.", _allocationSize, _device->_pMetalFeatures->maxMTLBufferSize)); + } } MVKDeviceMemory::~MVKDeviceMemory() { // Unbind any resources that are using me. Iterate a copy of the collection, // to allow the resource to callback to remove itself from the collection. - auto rezCopies = _resources; - for (auto& rez : rezCopies) { rez->bindDeviceMemory(nullptr, 0); } + auto buffCopies = _buffers; + for (auto& buf : buffCopies) { buf->bindDeviceMemory(nullptr, 0); } + auto imgCopies = _images; + for (auto& img : imgCopies) { img->bindDeviceMemory(nullptr, 0); } [_mtlBuffer release]; _mtlBuffer = nil; + + freeHostMemory(); } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h index 3e83c415a..087b5f2e3 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h @@ -107,6 +107,9 @@ class MVKImage : public MVKResource { /** Returns the memory requirements of this resource by populating the specified structure. */ VkResult getMemoryRequirements(VkMemoryRequirements* pMemoryRequirements) override; + /** Binds this resource to the specified offset within the specified memory allocation. */ + VkResult bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) override; + /** Applies the specified global memory barrier. */ void applyMemoryBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, @@ -191,11 +194,13 @@ class MVKImage : public MVKResource { ~MVKImage() override; protected: + friend class MVKDeviceMemory; friend class MVKImageView; using MVKResource::needsHostReadSync; MVKImageSubresource* getSubresource(uint32_t mipLevel, uint32_t arrayLayer); - void initMTLTextureViewSupport(); + bool validateLinear(const VkImageCreateInfo* pCreateInfo); + bool validateUseTexelBuffer(); void initSubresources(const VkImageCreateInfo* pCreateInfo); void initSubresourceLayout(MVKImageSubresource& imgSubRez); virtual id newMTLTexture(); @@ -204,9 +209,9 @@ class MVKImage : public MVKResource { MTLTextureDescriptor* getMTLTextureDescriptor(); void updateMTLTextureContent(MVKImageSubresource& subresource, VkDeviceSize offset, VkDeviceSize size); void getMTLTextureContent(MVKImageSubresource& subresource, VkDeviceSize offset, VkDeviceSize size); - void* map(VkDeviceSize offset, VkDeviceSize size) override; - VkResult flushToDevice(VkDeviceSize offset, VkDeviceSize size) override; - VkResult pullFromDevice(VkDeviceSize offset, VkDeviceSize size) override; + bool shouldFlushHostMemory(); + VkResult flushToDevice(VkDeviceSize offset, VkDeviceSize size); + VkResult pullFromDevice(VkDeviceSize offset, VkDeviceSize size); bool needsHostReadSync(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkImageMemoryBarrier* pImageMemoryBarrier); @@ -225,6 +230,8 @@ class MVKImage : public MVKResource { bool _isDepthStencilAttachment; bool _canSupportMTLTextureView; bool _hasExpectedTexelSize; + bool _usesTexelBuffer; + bool _isLinear; }; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm index 53a47dc78..552eb4efb 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm @@ -132,10 +132,8 @@ } } -/** - * Returns whether the specified image memory barrier requires a sync between this - * texture and host memory for the purpose of the host reading texture memory. - */ +// Returns whether the specified image memory barrier requires a sync between this +// texture and host memory for the purpose of the host reading texture memory. bool MVKImage::needsHostReadSync(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkImageMemoryBarrier* pImageMemoryBarrier) { @@ -146,11 +144,11 @@ return ((pImageMemoryBarrier->newLayout == VK_IMAGE_LAYOUT_GENERAL) && mvkIsAnyFlagEnabled(dstStageMask, (VK_PIPELINE_STAGE_HOST_BIT)) && mvkIsAnyFlagEnabled(pImageMemoryBarrier->dstAccessMask, (VK_ACCESS_HOST_READ_BIT)) && - _deviceMemory->isMemoryHostAccessible() && getMTLStorageMode() != MTLStorageModeShared); + isMemoryHostAccessible() && !isMemoryHostCoherent()); #endif } -/** Returns a pointer to the internal subresource for the specified MIP level layer. */ +// Returns a pointer to the internal subresource for the specified MIP level layer. MVKImageSubresource* MVKImage::getSubresource(uint32_t mipLevel, uint32_t arrayLayer) { uint32_t srIdx = (mipLevel * _arrayLayers) + arrayLayer; return (srIdx < _subresources.size()) ? &_subresources[srIdx] : NULL; @@ -165,48 +163,74 @@ return VK_SUCCESS; } -/** - * Flushes the device memory at the specified memory range into the MTLTexture. Updates - * all subresources that overlap the specified range and are in an updatable layout state. - */ +// Memory may have been mapped before image was bound, and needs to be loaded into the MTLTexture. +VkResult MVKImage::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) { + if (_deviceMemory) { _deviceMemory->removeImage(this); } + + MVKResource::bindDeviceMemory(mvkMem, memOffset); + + _usesTexelBuffer = validateUseTexelBuffer(); + + flushToDevice(getDeviceMemoryOffset(), getByteCount()); + + return _deviceMemory ? _deviceMemory->addImage(this) : VK_SUCCESS; +} + +bool MVKImage::validateUseTexelBuffer() { + VkExtent2D blockExt = mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat); + bool isUncompressed = blockExt.width == 1 && blockExt.height == 1; + + bool useTexelBuffer = _device->_pMetalFeatures->texelBuffers; // Texel buffers available + useTexelBuffer = useTexelBuffer && isMemoryHostAccessible() && _isLinear && isUncompressed; // Applicable memory layout + useTexelBuffer = useTexelBuffer && _deviceMemory && _deviceMemory->_mtlBuffer; // Buffer is available to overlay + +#if MVK_MACOS + useTexelBuffer = useTexelBuffer && !isMemoryHostCoherent(); // macOS cannot use shared memory for texel buffers +#endif + + return useTexelBuffer; +} + +bool MVKImage::shouldFlushHostMemory() { return isMemoryHostAccessible() && !_usesTexelBuffer; } + +// Flushes the device memory at the specified memory range into the MTLTexture. Updates +// all subresources that overlap the specified range and are in an updatable layout state. VkResult MVKImage::flushToDevice(VkDeviceSize offset, VkDeviceSize size) { - for (auto& subRez : _subresources) { - switch (subRez.layoutState) { - case VK_IMAGE_LAYOUT_UNDEFINED: // TODO: VK_IMAGE_LAYOUT_UNDEFINED should be illegal - case VK_IMAGE_LAYOUT_PREINITIALIZED: - case VK_IMAGE_LAYOUT_GENERAL: { - updateMTLTextureContent(subRez, offset, size); - break; + if (shouldFlushHostMemory()) { + for (auto& subRez : _subresources) { + switch (subRez.layoutState) { + case VK_IMAGE_LAYOUT_UNDEFINED: + case VK_IMAGE_LAYOUT_PREINITIALIZED: + case VK_IMAGE_LAYOUT_GENERAL: { + updateMTLTextureContent(subRez, offset, size); + break; + } + default: + break; } - default: - break; } } return VK_SUCCESS; } -/** - * Pulls content from the MTLTexture into the device memory at the specified memory range. - * Pulls from all subresources that overlap the specified range and are in an updatable layout state. - */ +// Pulls content from the MTLTexture into the device memory at the specified memory range. +// Pulls from all subresources that overlap the specified range and are in an updatable layout state. VkResult MVKImage::pullFromDevice(VkDeviceSize offset, VkDeviceSize size) { - for (auto& subRez : _subresources) { - switch (subRez.layoutState) { - case VK_IMAGE_LAYOUT_GENERAL: { - getMTLTextureContent(subRez, offset, size); - break; + if (shouldFlushHostMemory()) { + for (auto& subRez : _subresources) { + switch (subRez.layoutState) { + case VK_IMAGE_LAYOUT_GENERAL: { + getMTLTextureContent(subRez, offset, size); + break; + } + default: + break; } - default: - break; } } return VK_SUCCESS; } -void* MVKImage::map(VkDeviceSize offset, VkDeviceSize size) { - return _deviceMemory->allocateMappedMemory(offset, size); -} - #pragma mark Metal @@ -246,21 +270,22 @@ return VK_SUCCESS; } -/** - * Creates and returns a retained Metal texture suitable for use in this instance. - * - * This implementation creates a new MTLTexture from a MTLTextureDescriptor and possible IOSurface. - * Subclasses may override this function to create the MTLTexture in a different manner. - */ +// Creates and returns a retained Metal texture suitable for use in this instance. +// This implementation creates a new MTLTexture from a MTLTextureDescriptor and possible IOSurface. +// Subclasses may override this function to create the MTLTexture in a different manner. id MVKImage::newMTLTexture() { if (_ioSurface) { return [getMTLDevice() newTextureWithDescriptor: getMTLTextureDescriptor() iosurface: _ioSurface plane: 0]; + } else if (_usesTexelBuffer) { + return [_deviceMemory->_mtlBuffer newTextureWithDescriptor: getMTLTextureDescriptor() + offset: getDeviceMemoryOffset() + bytesPerRow: _subresources[0].layout.rowPitch]; } else { return [getMTLDevice() newTextureWithDescriptor: getMTLTextureDescriptor()]; } } -/** Removes and releases the MTLTexture object, so that it can be lazily created by getMTLTexture(). */ +// Removes and releases the MTLTexture object, so that it can be lazily created by getMTLTexture(). void MVKImage::resetMTLTexture() { [_mtlTexture release]; _mtlTexture = nil; @@ -309,7 +334,7 @@ return VK_SUCCESS; } -/** Returns an autoreleased Metal texture descriptor constructed from the properties of this image. */ +// Returns an autoreleased Metal texture descriptor constructed from the properties of this image. MTLTextureDescriptor* MVKImage::getMTLTextureDescriptor() { MTLTextureDescriptor* mtlTexDesc = [[MTLTextureDescriptor alloc] init]; mtlTexDesc.pixelFormat = _mtlPixelFormat; @@ -339,63 +364,63 @@ return stgMode; } -/** - * Updates the contents of the underlying MTLTexture, corresponding to the - * specified subresource definition, from the underlying memory buffer. - */ +// Updates the contents of the underlying MTLTexture, corresponding to the +//specified subresource definition, from the underlying memory buffer. void MVKImage::updateMTLTextureContent(MVKImageSubresource& subresource, VkDeviceSize offset, VkDeviceSize size) { - // Check if subresource overlaps the memory range. + + VkImageSubresource& imgSubRez = subresource.subresource; + VkSubresourceLayout& imgLayout = subresource.layout; + + // Check if subresource overlaps the memory range. VkDeviceSize memStart = offset; VkDeviceSize memEnd = offset + size; - VkDeviceSize imgStart = subresource.layout.offset; - VkDeviceSize imgEnd = subresource.layout.offset + subresource.layout.size; + VkDeviceSize imgStart = imgLayout.offset; + VkDeviceSize imgEnd = imgLayout.offset + imgLayout.size; if (imgStart >= memEnd || imgEnd <= memStart) { return; } - VkImageSubresource& imgSubRez = subresource.subresource; - VkSubresourceLayout& imgLayout = subresource.layout; - - uint32_t mipLvl = imgSubRez.mipLevel; - uint32_t layer = imgSubRez.arrayLayer; + // Don't update if host memory has not been mapped yet. + void* pHostMem = getHostMemoryAddress(); + if ( !pHostMem ) { return; } - VkExtent3D mipExtent = getExtent3D(mipLvl); + VkExtent3D mipExtent = getExtent3D(imgSubRez.mipLevel); VkImageType imgType = getImageType(); - void* pImgBytes = (void*)((uintptr_t)getLogicalMappedMemory() + imgLayout.offset); + void* pImgBytes = (void*)((uintptr_t)pHostMem + imgLayout.offset); MTLRegion mtlRegion; mtlRegion.origin = MTLOriginMake(0, 0, 0); mtlRegion.size = mvkMTLSizeFromVkExtent3D(mipExtent); [getMTLTexture() replaceRegion: mtlRegion - mipmapLevel: mipLvl - slice: layer + mipmapLevel: imgSubRez.mipLevel + slice: imgSubRez.arrayLayer withBytes: pImgBytes bytesPerRow: (imgType != VK_IMAGE_TYPE_1D ? imgLayout.rowPitch : 0) bytesPerImage: (imgType == VK_IMAGE_TYPE_3D ? imgLayout.depthPitch : 0)]; } -/** - * Updates the contents of the underlying memory buffer from the contents of - * the underlying MTLTexture, corresponding to the specified subresource definition. - */ +// Updates the contents of the underlying memory buffer from the contents of +// the underlying MTLTexture, corresponding to the specified subresource definition. void MVKImage::getMTLTextureContent(MVKImageSubresource& subresource, VkDeviceSize offset, VkDeviceSize size) { - // Check if subresource overlaps the memory range. + + VkImageSubresource& imgSubRez = subresource.subresource; + VkSubresourceLayout& imgLayout = subresource.layout; + + // Check if subresource overlaps the memory range. VkDeviceSize memStart = offset; VkDeviceSize memEnd = offset + size; - VkDeviceSize imgStart = subresource.layout.offset; - VkDeviceSize imgEnd = subresource.layout.offset + subresource.layout.size; + VkDeviceSize imgStart = imgLayout.offset; + VkDeviceSize imgEnd = imgLayout.offset + imgLayout.size; if (imgStart >= memEnd || imgEnd <= memStart) { return; } - VkImageSubresource& imgSubRez = subresource.subresource; - VkSubresourceLayout& imgLayout = subresource.layout; + // Don't update if host memory has not been mapped yet. + void* pHostMem = getHostMemoryAddress(); + if ( !pHostMem ) { return; } - uint32_t mipLvl = imgSubRez.mipLevel; - uint32_t layer = imgSubRez.arrayLayer; - - VkExtent3D mipExtent = getExtent3D(mipLvl); + VkExtent3D mipExtent = getExtent3D(imgSubRez.mipLevel); VkImageType imgType = getImageType(); - void* pImgBytes = (void*)((uintptr_t)getLogicalMappedMemory() + imgLayout.offset); + void* pImgBytes = (void*)((uintptr_t)pHostMem + imgLayout.offset); MTLRegion mtlRegion; mtlRegion.origin = MTLOriginMake(0, 0, 0); @@ -405,8 +430,8 @@ bytesPerRow: (imgType != VK_IMAGE_TYPE_1D ? imgLayout.rowPitch : 0) bytesPerImage: (imgType == VK_IMAGE_TYPE_3D ? imgLayout.depthPitch : 0) fromRegion: mtlRegion - mipmapLevel: mipLvl - slice: layer]; + mipmapLevel: imgSubRez.mipLevel + slice: imgSubRez.arrayLayer]; } @@ -446,8 +471,10 @@ _isDepthStencilAttachment = (mvkAreFlagsEnabled(pCreateInfo->usage, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) || mvkAreFlagsEnabled(mvkVkFormatProperties(pCreateInfo->format).optimalTilingFeatures, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)); - + _canSupportMTLTextureView = !_isDepthStencilAttachment; _hasExpectedTexelSize = (mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat) == mvkVkFormatBytesPerBlock(pCreateInfo->format)); + _isLinear = validateLinear(pCreateInfo); + _usesTexelBuffer = false; // Calc _byteCount after _mtlTexture & _byteAlignment for (uint32_t mipLvl = 0; mipLvl < _mipLevels; mipLvl++) { @@ -455,10 +482,46 @@ } initSubresources(pCreateInfo); - initMTLTextureViewSupport(); } -/** Initializes the subresource definitions. */ +bool MVKImage::validateLinear(const VkImageCreateInfo* pCreateInfo) { + if (pCreateInfo->tiling != VK_IMAGE_TILING_LINEAR) { return false; } + + if (pCreateInfo->imageType != VK_IMAGE_TYPE_2D) { + setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, imageType must be VK_IMAGE_TYPE_2D.")); + return false; + } + + if (_isDepthStencilAttachment) { + setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, format must not be a depth/stencil format.")); + return false; + } + + if (_mipLevels > 1) { + setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, mipLevels must be 1.")); + return false; + } + + if (_arrayLayers > 1) { + setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, arrayLayers must be 1.")); + return false; + } + + if (_samples > 1) { + setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, samples must be VK_SAMPLE_COUNT_1_BIT.")); + return false; + } + + if (mvkAreOnlyAnyFlagsEnabled(_usage, (VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT))) { + setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, usage must only include VK_IMAGE_USAGE_TRANSFER_SRC_BIT and/or VK_IMAGE_USAGE_TRANSFER_DST_BIT.")); + return false; + } + + return true; +} + + +// Initializes the subresource definitions. void MVKImage::initSubresources(const VkImageCreateInfo* pCreateInfo) { _subresources.reserve(_mipLevels * _arrayLayers); @@ -476,7 +539,7 @@ } } -/** Initializes the layout element of the specified image subresource. */ +// Initializes the layout element of the specified image subresource. void MVKImage::initSubresourceLayout(MVKImageSubresource& imgSubRez) { VkImageSubresource subresource = imgSubRez.subresource; uint32_t currMipLevel = subresource.mipLevel; @@ -500,15 +563,8 @@ layout.depthPitch = bytesPerLayerCurrLevel; } -/** - * Determines whether this image can support Metal texture views, - * and sets the _canSupportMTLTextureView variable appropriately. - */ -void MVKImage::initMTLTextureViewSupport() { - _canSupportMTLTextureView = !_isDepthStencilAttachment; -} - MVKImage::~MVKImage() { + if (_deviceMemory) { _deviceMemory->removeImage(this); } resetMTLTexture(); resetIOSurface(); } @@ -552,10 +608,8 @@ } } -/** - * Creates and returns a retained Metal texture as an - * overlay on the Metal texture of the underlying image. - */ +// Creates and returns a retained Metal texture as an +// overlay on the Metal texture of the underlying image. id MVKImageView::newMTLTexture() { return [_image->getMTLTexture() newTextureViewWithPixelFormat: _mtlPixelFormat textureType: _mtlTextureType @@ -698,10 +752,8 @@ return true; } -/** - * Determine whether this image view should use a Metal texture view, - * and set the _useMTLTextureView variable appropriately. - */ +// Determine whether this image view should use a Metal texture view, +// and set the _useMTLTextureView variable appropriately. void MVKImageView::initMTLTextureViewSupport() { _useMTLTextureView = _image->_canSupportMTLTextureView; @@ -722,7 +774,7 @@ #pragma mark - #pragma mark MVKSampler -/** Returns an autoreleased Metal sampler descriptor constructed from the properties of this image. */ +// Returns an autoreleased Metal sampler descriptor constructed from the properties of this image. MTLSamplerDescriptor* MVKSampler::getMTLSamplerDescriptor(const VkSamplerCreateInfo* pCreateInfo) { MTLSamplerDescriptor* mtlSampDesc = [[MTLSamplerDescriptor alloc] init]; @@ -746,7 +798,7 @@ return [mtlSampDesc autorelease]; } -/** Constructs an instance on the specified image. */ +// Constructs an instance on the specified image. MVKSampler::MVKSampler(MVKDevice* device, const VkSamplerCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) { _mtlSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: getMTLSamplerDescriptor(pCreateInfo)]; } @@ -819,7 +871,7 @@ // MVKLogDebug("%s swapchain image %p semaphore %p in acquire with %lu other semaphores.", (_availability.isAvailable ? "Signaling" : "Tracking"), this, semaphore, _availabilitySignalers.size()); } -/** Signal either or both of the semaphore and fence in the specified tracker pair. */ +// Signal either or both of the semaphore and fence in the specified tracker pair. void MVKSwapchainImage::signal(MVKSwapchainSignaler& signaler) { if (signaler.first) { signaler.first->signal(); } if (signaler.second) { signaler.second->signal(); } @@ -846,11 +898,8 @@ #pragma mark Metal -/** - * Creates and returns a retained Metal texture suitable for use in this instance. - * - * This implementation retrieves a MTLTexture from the CAMetalDrawable. - */ +// Creates and returns a retained Metal texture suitable for use in this instance. +// This implementation retrieves a MTLTexture from the CAMetalDrawable. id MVKSwapchainImage::newMTLTexture() { return [[getCAMetalDrawable() texture] retain]; } @@ -885,13 +934,13 @@ } } -/** Removes and releases the Metal drawable object, so that it can be lazily created by getCAMetalDrawable(). */ +// Removes and releases the Metal drawable object, so that it can be lazily created by getCAMetalDrawable(). void MVKSwapchainImage::resetCAMetalDrawable() { [_mtlDrawable release]; _mtlDrawable = nil; } -/** Resets the MTLTexture and CAMetalDrawable underlying this image. */ +// Resets the MTLTexture and CAMetalDrawable underlying this image. void MVKSwapchainImage::resetMetalSurface() { resetMTLTexture(); // Release texture first so drawable will be last to release it resetCAMetalDrawable(); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKResource.h b/MoltenVK/MoltenVK/GPUObjects/MVKResource.h index 04915893a..900f3a4a2 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKResource.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKResource.h @@ -38,36 +38,28 @@ class MVKResource : public MVKBaseDeviceObject { /** Returns the byte offset in the bound device memory. */ inline VkDeviceSize getDeviceMemoryOffset() { return _deviceMemoryOffset; } - /** Returns the byte alignment required for this resource. */ - inline VkDeviceSize getByteAlignment() { return _byteAlignment; } - /** Returns the memory requirements of this resource by populating the specified structure. */ virtual VkResult getMemoryRequirements(VkMemoryRequirements* pMemoryRequirements) = 0; /** Binds this resource to the specified offset within the specified memory allocation. */ - VkResult bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset); + virtual VkResult bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset); /** Returns the device memory underlying this resource. */ inline MVKDeviceMemory* getDeviceMemory() { return _deviceMemory; } /** Returns whether the memory is accessible from the host. */ - inline bool isMemoryHostAccessible() { - return (_deviceMemory ? _deviceMemory->isMemoryHostAccessible() : false); - } - - /** - * Returns the host memory address that represents what would be the beginning of - * the host address space that this resource is mapped to by a vkMapMemory() call. - * - * The returnd value only has physical meaning if the mapped memory overlaps the - * beginning of the memory used by this resource, otherwise it is a logical address - * used to calculate resource offsets. - * - * This function must only be called between vkMapMemory() and vkUnmapMemory() calls. - */ - inline void* getLogicalMappedMemory() { - return (_deviceMemory ? (void*)((uintptr_t)_deviceMemory->getLogicalMappedMemory() + _deviceMemoryOffset) : nullptr); - } + inline bool isMemoryHostAccessible() { return _deviceMemory && _deviceMemory->isMemoryHostAccessible(); } + + /** Returns whether the memory is automatically coherent between device and host. */ + inline bool isMemoryHostCoherent() { return _deviceMemory && _deviceMemory->isMemoryHostCoherent(); } + + /** + * Returns the host memory address of this resource, or NULL if the memory + * is marked as device-only and cannot be mapped to a host address. + */ + inline void* getHostMemoryAddress() { + return (_deviceMemory ? (void*)((uintptr_t)_deviceMemory->getHostMemoryAddress() + _deviceMemoryOffset) : nullptr); + } /** Applies the specified global memory barrier. */ virtual void applyMemoryBarrier(VkPipelineStageFlags srcStageMask, @@ -79,23 +71,12 @@ class MVKResource : public MVKBaseDeviceObject { #pragma mark Construction - /** Constructs an instance for the specified device. */ MVKResource(MVKDevice* device) : MVKBaseDeviceObject(device) {} - /** Destructor. */ - ~MVKResource() override; - protected: - friend MVKDeviceMemory; - - virtual void* map(VkDeviceSize offset, VkDeviceSize size) = 0; - virtual VkResult flushToDevice(VkDeviceSize offset, VkDeviceSize size) = 0; - virtual VkResult pullFromDevice(VkDeviceSize offset, VkDeviceSize size) = 0; virtual bool needsHostReadSync(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkMemoryBarrier* pMemoryBarrier); - bool doesOverlap(VkDeviceSize offset, VkDeviceSize size); - bool doesContain(VkDeviceSize offset, VkDeviceSize size); MVKDeviceMemory* _deviceMemory = nullptr; VkDeviceSize _deviceMemoryOffset = 0; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKResource.mm b/MoltenVK/MoltenVK/GPUObjects/MVKResource.mm index c316b5b2d..1060985f4 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKResource.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKResource.mm @@ -23,20 +23,13 @@ #pragma mark MVKResource VkResult MVKResource::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) { - if (_deviceMemory) { _deviceMemory->removeResource(this); } - _deviceMemory = mvkMem; _deviceMemoryOffset = memOffset; - - if (_deviceMemory) { _deviceMemory->addResource(this); } - return VK_SUCCESS; } -/** - * Returns whether the specified global memory barrier requires a sync between this - * texture and host memory for the purpose of the host reading texture memory. - */ +// Returns whether the specified global memory barrier requires a sync between this +// texture and host memory for the purpose of the host reading texture memory. bool MVKResource::needsHostReadSync(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkMemoryBarrier* pMemoryBarrier) { @@ -46,34 +39,7 @@ #if MVK_MACOS return (mvkIsAnyFlagEnabled(dstStageMask, (VK_PIPELINE_STAGE_HOST_BIT)) && mvkIsAnyFlagEnabled(pMemoryBarrier->dstAccessMask, (VK_ACCESS_HOST_READ_BIT)) && - _deviceMemory && _deviceMemory->isMemoryHostAccessible() && !_deviceMemory->isMemoryHostCoherent()); + isMemoryHostAccessible() && !isMemoryHostCoherent()); #endif } -// Check if this resource overlaps the device memory offset and range -bool MVKResource::doesOverlap(VkDeviceSize offset, VkDeviceSize size) { - VkDeviceSize memStart = offset; - VkDeviceSize memEnd = memStart + size; - VkDeviceSize rezStart = _deviceMemoryOffset; - VkDeviceSize rezEnd = rezStart + _byteCount; - - return (memStart < rezEnd && memEnd > rezStart); -} - -// Check if this resource completely contains the device memory offset and range -bool MVKResource::doesContain(VkDeviceSize offset, VkDeviceSize size) { - VkDeviceSize memStart = offset; - VkDeviceSize memEnd = memStart + size; - VkDeviceSize rezStart = _deviceMemoryOffset; - VkDeviceSize rezEnd = rezStart + _byteCount; - - return (memStart >= rezStart && memEnd <= rezEnd); -} - - -#pragma mark Construction - -MVKResource::~MVKResource() { - if (_deviceMemory) { _deviceMemory->removeResource(this); } -}; - diff --git a/MoltenVK/MoltenVK/Utility/MVKFoundation.h b/MoltenVK/MoltenVK/Utility/MVKFoundation.h index a08dfe95a..32b4043ab 100644 --- a/MoltenVK/MoltenVK/Utility/MVKFoundation.h +++ b/MoltenVK/MoltenVK/Utility/MVKFoundation.h @@ -363,7 +363,11 @@ bool mvkAreFlagsEnabled(T1 value, const T2 bitMask) { return ((value & bitMask) template bool mvkIsAnyFlagEnabled(T1 value, const T2 bitMask) { return !!(value & bitMask); } -/** Returns whether the specified value has ONLY of the flags specified in bitMask enabled (set to 1). */ +/** Returns whether the specified value has ONLY ALL of the flags specified in bitMask enabled (set to 1), and none others. */ template -bool mvkAreOnlyFlagsEnabled(T1 value, const T2 bitMask) { return (value == bitMask); } +bool mvkAreOnlyAllFlagsEnabled(T1 value, const T2 bitMask) { return (value == bitMask); } + +/** Returns whether the specified value has ONLY one or more of the flags specified in bitMask enabled (set to 1), and none others. */ +template +bool mvkAreOnlyAnyFlagsEnabled(T1 value, const T2 bitMask) { return (mvkIsAnyFlagEnabled(value, bitMask) && ((value | bitMask) == bitMask)); }