/** * Copyright (c) 2006-2023 LOVE Development Team * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. **/ #include "Buffer.h" #include "Graphics.h" namespace love { namespace graphics { namespace metal { static MTLPixelFormat getMTLPixelFormat(DataFormat format) { switch (format) { case DATAFORMAT_FLOAT: return MTLPixelFormatR32Float; case DATAFORMAT_FLOAT_VEC2: return MTLPixelFormatRG32Float; case DATAFORMAT_FLOAT_VEC4: return MTLPixelFormatRGBA32Float; case DATAFORMAT_INT32: return MTLPixelFormatR32Sint; case DATAFORMAT_INT32_VEC2: return MTLPixelFormatRG32Sint; case DATAFORMAT_INT32_VEC4: return MTLPixelFormatRGBA32Sint; case DATAFORMAT_UINT32: return MTLPixelFormatR32Uint; case DATAFORMAT_UINT32_VEC2: return MTLPixelFormatRG32Uint; case DATAFORMAT_UINT32_VEC4: return MTLPixelFormatRGBA32Uint; case DATAFORMAT_UNORM8_VEC4: return MTLPixelFormatRGBA8Unorm; case DATAFORMAT_SNORM8_VEC4: return MTLPixelFormatRGBA8Snorm; case DATAFORMAT_INT8_VEC4: return MTLPixelFormatRGBA8Sint; case DATAFORMAT_UINT8_VEC4: return MTLPixelFormatRGBA8Uint; case DATAFORMAT_UNORM16_VEC2: return MTLPixelFormatRG16Unorm; case DATAFORMAT_UNORM16_VEC4: return MTLPixelFormatRGBA16Unorm; case DATAFORMAT_INT16_VEC2: return MTLPixelFormatRG16Sint; case DATAFORMAT_INT16_VEC4: return MTLPixelFormatRGBA16Sint; case DATAFORMAT_UINT16: return MTLPixelFormatR16Uint; case DATAFORMAT_UINT16_VEC2: return MTLPixelFormatRG16Uint; case DATAFORMAT_UINT16_VEC4: return MTLPixelFormatRGBA16Uint; default: return MTLPixelFormatInvalid; } } Buffer::Buffer(love::graphics::Graphics *gfx, id device, const Settings &settings, const std::vector &format, const void *data, size_t size, size_t arraylength) : love::graphics::Buffer(gfx, settings, format, size, arraylength) , texture(nil) , mapBuffer(nil) , mappedRange() { @autoreleasepool { size = getSize(); arraylength = getArrayLength(); MTLResourceOptions opts = 0; if (settings.dataUsage == BUFFERDATAUSAGE_READBACK) opts |= MTLResourceStorageModeShared; else opts |= MTLResourceStorageModePrivate; buffer = [device newBufferWithLength:size options:opts]; if (buffer == nil) throw love::Exception("Could not create buffer with %d bytes (out of VRAM?)", size); if (usageFlags & BUFFERUSAGEFLAG_TEXEL) { if (@available(iOS 12, macOS 10.14, *)) { // TODO: minimumTextureBufferAlignmentForPixelFormat MTLPixelFormat pixformat = getMTLPixelFormat(getDataMember(0).decl.format); if (pixformat == MTLPixelFormatInvalid) throw love::Exception("Could not create Metal texel buffer: invalid format."); size_t width = arraylength * getDataMembers().size(); auto desc = [MTLTextureDescriptor textureBufferDescriptorWithPixelFormat:pixformat width:width resourceOptions:opts usage:MTLTextureUsageShaderRead]; texture = [buffer newTextureWithDescriptor:desc offset:0 bytesPerRow:size]; } if (texture == nil) throw love::Exception("Could not create Metal texel buffer."); } if (data != nullptr) fill(0, size, data); else if (settings.zeroInitialize) { auto *mgfx = (Graphics *) gfx; auto encoder = mgfx->useBlitEncoder(); size_t clearsize = size; #ifdef LOVE_MACOS // Metal limitation on macOS. clearsize -= (clearsize % 4); #endif if (clearsize > 0) [encoder fillBuffer:buffer range:NSMakeRange(0, clearsize) value:0]; } }} Buffer::~Buffer() { @autoreleasepool { buffer = nil; texture = nil; }} void *Buffer::map(MapType map, size_t offset, size_t size) { @autoreleasepool { if (size == 0) return nullptr; if (map == MAP_WRITE_INVALIDATE && (isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK)) return nullptr; if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK) return nullptr; Range r(offset, size); if (!Range(0, getSize()).contains(r)) return nullptr; if (map == MAP_READ_ONLY) { mappedRange = r; mapped = true; mappedType = map; return (char *) buffer.contents + offset; } auto gfx = Graphics::getInstance(); // TODO: Don't create a new buffer every time, also do something for stream // buffers. mapBuffer = [gfx->device newBufferWithLength:size options:MTLResourceStorageModeShared]; if (mapBuffer != nil) { mappedRange = r; mapped = true; mappedType = map; return mapBuffer.contents; } return nullptr; }} void Buffer::unmap(size_t usedoffset, size_t usedsize) { @autoreleasepool { if (mappedType == MAP_READ_ONLY) { mapped = false; return; } if (mapBuffer == nil) return; Range r(usedoffset, usedsize); if (!mapped || !mappedRange.contains(r)) return; auto gfx = Graphics::getInstance(); auto encoder = gfx->useBlitEncoder(); [encoder copyFromBuffer:mapBuffer sourceOffset:(usedoffset - mappedRange.getOffset()) toBuffer:buffer destinationOffset:usedoffset size:usedsize]; mapBuffer = nil; mapped = false; }} bool Buffer::fill(size_t offset, size_t size, const void *data) { @autoreleasepool { void *dest = map(MAP_WRITE_INVALIDATE, offset, size); if (dest == nullptr) return false; memcpy(dest, data, size); unmap(offset, size); return true; }} void Buffer::clear(size_t offset, size_t size) { @autoreleasepool { if (isImmutable()) throw love::Exception("Cannot clear an immutable Buffer."); else if (isMapped()) throw love::Exception("Cannot clear a mapped Buffer."); else if (offset + size > getSize()) throw love::Exception("The given offset and size parameters to clear() are not within the Buffer's size."); else if (offset % 4 != 0 || size % 4 != 0) throw love::Exception("clear() must be used with offset and size parameters that are multiples of 4 bytes."); auto gfx = Graphics::getInstance(); auto encoder = gfx->useBlitEncoder(); [encoder fillBuffer:buffer range:NSMakeRange(offset, size) value:0]; }} void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) { @autoreleasepool { auto gfx = Graphics::getInstance(); auto encoder = gfx->useBlitEncoder(); [encoder copyFromBuffer:buffer sourceOffset:sourceoffset toBuffer:((Buffer *) dest)->buffer destinationOffset:destoffset size:size]; }} } // metal } // graphics } // love