blob: 8357c9760e6dbe99a66f59b405d6b8063f5d23c0 [file] [log] [blame]
/*
* MVKBuffer.mm
*
* Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MVKBuffer.h"
#include "MVKCommandBuffer.h"
#include "MVKFoundation.h"
#include "mvk_datatypes.h"
using namespace std;
#pragma mark -
#pragma mark MVKBuffer
#pragma mark Resource memory
VkResult MVKBuffer::getMemoryRequirements(VkMemoryRequirements* pMemoryRequirements) {
pMemoryRequirements->size = getByteCount();
pMemoryRequirements->alignment = getByteAlignment();
pMemoryRequirements->memoryTypeBits = _device->getPhysicalDevice()->getAllMemoryTypes();
return VK_SUCCESS;
}
void MVKBuffer::applyMemoryBarrier(VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
VkMemoryBarrier* pMemoryBarrier,
MVKCommandEncoder* cmdEncoder,
MVKCommandUse cmdUse) {
#if MVK_MACOS
if ( needsHostReadSync(srcStageMask, dstStageMask, pMemoryBarrier) ) {
[cmdEncoder->getMTLBlitEncoder(cmdUse) synchronizeResource: getMTLBuffer()];
}
#endif
}
void MVKBuffer::applyBufferMemoryBarrier(VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
VkBufferMemoryBarrier* pBufferMemoryBarrier,
MVKCommandEncoder* cmdEncoder,
MVKCommandUse cmdUse) {
#if MVK_MACOS
if ( needsHostReadSync(srcStageMask, dstStageMask, pBufferMemoryBarrier) ) {
[cmdEncoder->getMTLBlitEncoder(cmdUse) synchronizeResource: getMTLBuffer()];
}
#endif
}
/**
* Returns whether the specified buffer memory barrier requires a sync between this
* buffer and host memory for the purpose of the host reading texture memory.
*/
bool MVKBuffer::needsHostReadSync(VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
VkBufferMemoryBarrier* pBufferMemoryBarrier) {
#if MVK_IOS
return false;
#endif
#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)];
}
#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<MTLBuffer> MVKBuffer::getMTLBuffer() {
if (_mtlBuffer) { return _mtlBuffer; }
id<MTLBuffer> devMemMTLBuff = _deviceMemory->getMTLBuffer();
if (devMemMTLBuff) { return devMemMTLBuff; }
// Lock and check again in case another thread has created the buffer.
lock_guard<mutex> 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);
}
#pragma mark Construction
MVKBuffer::MVKBuffer(MVKDevice* device, const VkBufferCreateInfo* pCreateInfo) : MVKResource(device) {
_byteAlignment = _device->_pMetalFeatures->mtlBufferAlignment;
_byteCount = pCreateInfo->size;
_mtlBuffer = nil;
}
MVKBuffer::~MVKBuffer() {
[_mtlBuffer release];
_mtlBuffer = nil;
}
#pragma mark -
#pragma mark MVKBufferView
#pragma mark Metal
id<MTLTexture> MVKBufferView::getMTLTexture() {
if ( !_mtlTexture && _mtlPixelFormat && _device->_pMetalFeatures->texelBuffers) {
// Lock and check again in case another thread has created the texture.
lock_guard<mutex> lock(_lock);
if (_mtlTexture) { return _mtlTexture; }
VkDeviceSize byteAlign = _device->_pProperties->limits.minTexelBufferOffsetAlignment;
NSUInteger mtlByteCnt = mvkAlignByteOffset(_byteCount, byteAlign);
MTLTextureDescriptor* mtlTexDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: _mtlPixelFormat
width: _textureSize.width
height: _textureSize.height
mipmapped: NO];
_mtlTexture = [getMTLBuffer() newTextureWithDescriptor: mtlTexDesc
offset: _mtlBufferOffset
bytesPerRow: mtlByteCnt];
}
return _mtlTexture;
}
#pragma mark Construction
MVKBufferView::MVKBufferView(MVKDevice* device, const VkBufferViewCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
_buffer = (MVKBuffer*)pCreateInfo->buffer;
_mtlBufferOffset = _buffer->getMTLBufferOffset() + pCreateInfo->offset;
_mtlPixelFormat = mtlPixelFormatFromVkFormat(pCreateInfo->format);
_mtlTexture = nil;
VkExtent2D fmtBlockSize = mvkVkFormatBlockTexelSize(pCreateInfo->format); // Pixel size of format
size_t bytesPerBlock = mvkVkFormatBytesPerBlock(pCreateInfo->format);
// Layout texture as a 1D array of texel blocks (which are texels for non-compressed textures) that covers the bytes
_byteCount = pCreateInfo->range;
if (_byteCount == VK_WHOLE_SIZE) { _byteCount = _buffer->getByteCount() - _mtlBufferOffset; } // Remaining bytes in buffer
size_t blockCount = _byteCount / bytesPerBlock;
_byteCount = blockCount * bytesPerBlock; // Round down
_textureSize.width = (uint32_t)blockCount * fmtBlockSize.width;
_textureSize.height = fmtBlockSize.height;
if ( !_device->_pMetalFeatures->texelBuffers ) {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "Texel buffers are not supported on this device."));
}
}
MVKBufferView::~MVKBufferView() {
[_mtlTexture release];
_mtlTexture = nil;
}