// // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // mtl_resources.mm: // Implements wrapper classes for Metal's MTLTexture and MTLBuffer. // #include "libANGLE/renderer/metal/mtl_resources.h" #include #include #include "common/debug.h" #include "libANGLE/renderer/metal/ContextMtl.h" #include "libANGLE/renderer/metal/DisplayMtl.h" #include "libANGLE/renderer/metal/mtl_command_buffer.h" #include "libANGLE/renderer/metal/mtl_format_utils.h" #include "libANGLE/renderer/metal/mtl_utils.h" namespace rx { namespace mtl { namespace { inline NSUInteger GetMipSize(NSUInteger baseSize, const MipmapNativeLevel level) { return std::max(1, baseSize >> level.get()); } // Asynchronously synchronize the content of a resource between GPU memory and its CPU cache. // NOTE: This operation doesn't finish immediately upon function's return. template void InvokeCPUMemSync(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder, T *resource) { #if TARGET_OS_OSX || TARGET_OS_MACCATALYST if (blitEncoder) { blitEncoder->synchronizeResource(resource); resource->resetCPUReadMemNeedSync(); } #endif } template void EnsureCPUMemWillBeSynced(ContextMtl *context, T *resource) { #if TARGET_OS_OSX || TARGET_OS_MACCATALYST // Make sure GPU & CPU contents are synchronized. // NOTE: Only MacOS has separated storage for resource on CPU and GPU and needs explicit // synchronization if (resource->get().storageMode == MTLStorageModeManaged && resource->isCPUReadMemNeedSync()) { mtl::BlitCommandEncoder *blitEncoder = context->getBlitCommandEncoder(); InvokeCPUMemSync(context, blitEncoder, resource); } #endif } } // namespace // Resource implementation Resource::Resource() : mUsageRef(std::make_shared()) {} // Share the GPU usage ref with other resource Resource::Resource(Resource *other) : mUsageRef(other->mUsageRef) { ASSERT(mUsageRef); } void Resource::reset() { mUsageRef->cmdBufferQueueSerial = 0; resetCPUReadMemDirty(); resetCPUReadMemNeedSync(); } bool Resource::isBeingUsedByGPU(Context *context) const { return context->cmdQueue().isResourceBeingUsedByGPU(this); } bool Resource::hasPendingWorks(Context *context) const { return context->cmdQueue().resourceHasPendingWorks(this); } void Resource::setUsedByCommandBufferWithQueueSerial(uint64_t serial, bool writing) { if (writing) { mUsageRef->cpuReadMemNeedSync = true; mUsageRef->cpuReadMemDirty = true; } mUsageRef->cmdBufferQueueSerial = std::max(mUsageRef->cmdBufferQueueSerial, serial); } // Texture implemenetation /** static */ angle::Result Texture::Make2DTexture(ContextMtl *context, const Format &format, uint32_t width, uint32_t height, uint32_t mips, bool renderTargetOnly, bool allowFormatView, TextureRef *refOut) { ANGLE_MTL_OBJC_SCOPE { MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format.metalFormat width:width height:height mipmapped:mips == 0 || mips > 1]; return MakeTexture(context, format, desc, mips, renderTargetOnly, allowFormatView, refOut); } // ANGLE_MTL_OBJC_SCOPE } /** static */ angle::Result Texture::MakeMemoryLess2DTexture(ContextMtl *context, const Format &format, uint32_t width, uint32_t height, TextureRef *refOut) { ANGLE_MTL_OBJC_SCOPE { MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format.metalFormat width:width height:height mipmapped:NO]; return MakeTexture(context, format, desc, 1, true, false, true, refOut); } // ANGLE_MTL_OBJC_SCOPE } /** static */ angle::Result Texture::MakeCubeTexture(ContextMtl *context, const Format &format, uint32_t size, uint32_t mips, bool renderTargetOnly, bool allowFormatView, TextureRef *refOut) { ANGLE_MTL_OBJC_SCOPE { MTLTextureDescriptor *desc = [MTLTextureDescriptor textureCubeDescriptorWithPixelFormat:format.metalFormat size:size mipmapped:mips == 0 || mips > 1]; return MakeTexture(context, format, desc, mips, renderTargetOnly, allowFormatView, refOut); } // ANGLE_MTL_OBJC_SCOPE } /** static */ angle::Result Texture::Make2DMSTexture(ContextMtl *context, const Format &format, uint32_t width, uint32_t height, uint32_t samples, bool renderTargetOnly, bool allowFormatView, TextureRef *refOut) { ANGLE_MTL_OBJC_SCOPE { MTLTextureDescriptor *desc = [[MTLTextureDescriptor new] ANGLE_MTL_AUTORELEASE]; desc.textureType = MTLTextureType2DMultisample; desc.pixelFormat = format.metalFormat; desc.width = width; desc.height = height; desc.mipmapLevelCount = 1; desc.sampleCount = samples; return MakeTexture(context, format, desc, 1, renderTargetOnly, allowFormatView, refOut); } // ANGLE_MTL_OBJC_SCOPE } /** static */ angle::Result Texture::Make2DArrayTexture(ContextMtl *context, const Format &format, uint32_t width, uint32_t height, uint32_t mips, uint32_t arrayLength, bool renderTargetOnly, bool allowFormatView, TextureRef *refOut) { ANGLE_MTL_OBJC_SCOPE { // Use texture2DDescriptorWithPixelFormat to calculate full range mipmap range: MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format.metalFormat width:width height:height mipmapped:mips == 0 || mips > 1]; desc.textureType = MTLTextureType2DArray; desc.arrayLength = arrayLength; return MakeTexture(context, format, desc, mips, renderTargetOnly, allowFormatView, refOut); } // ANGLE_MTL_OBJC_SCOPE } /** static */ angle::Result Texture::Make3DTexture(ContextMtl *context, const Format &format, uint32_t width, uint32_t height, uint32_t depth, uint32_t mips, bool renderTargetOnly, bool allowFormatView, TextureRef *refOut) { ANGLE_MTL_OBJC_SCOPE { // Use texture2DDescriptorWithPixelFormat to calculate full range mipmap range: uint32_t maxDimen = std::max(width, height); maxDimen = std::max(maxDimen, depth); MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format.metalFormat width:maxDimen height:maxDimen mipmapped:mips == 0 || mips > 1]; desc.textureType = MTLTextureType3D; desc.width = width; desc.height = height; desc.depth = depth; return MakeTexture(context, format, desc, mips, renderTargetOnly, allowFormatView, refOut); } // ANGLE_MTL_OBJC_SCOPE } angle::Result Texture::MakeIOSurfaceTexture(ContextMtl *context, const Format &format, uint32_t width, uint32_t height, IOSurfaceRef ref, uint32_t plane, TextureRef *refOut) { MTLTextureDescriptor *desc = [[MTLTextureDescriptor new] ANGLE_MTL_AUTORELEASE]; desc.textureType = MTLTextureType2D; desc.pixelFormat = format.metalFormat; desc.width = width; desc.height = height; desc.mipmapLevelCount = 1; desc.sampleCount = 1; return MakeTexture(context, format, desc, ref, plane, NO, refOut); } /** static */ angle::Result Texture::MakeTexture(ContextMtl *context, const Format &mtlFormat, MTLTextureDescriptor *desc, uint32_t mips, bool renderTargetOnly, bool allowFormatView, TextureRef *refOut) { return MakeTexture(context, mtlFormat, desc, mips, renderTargetOnly, allowFormatView, false, refOut); } angle::Result Texture::MakeTexture(ContextMtl *context, const Format &mtlFormat, MTLTextureDescriptor *desc, uint32_t mips, bool renderTargetOnly, bool allowFormatView, bool memoryLess, TextureRef *refOut) { if (desc.pixelFormat == MTLPixelFormatInvalid) { return angle::Result::Stop; } refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowFormatView, memoryLess)); if (!refOut || !refOut->get()) { ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); } if (!mtlFormat.hasDepthAndStencilBits()) { refOut->get()->setColorWritableMask(GetEmulatedColorWriteMask(mtlFormat)); } return angle::Result::Continue; } angle::Result Texture::MakeTexture(ContextMtl *context, const Format &mtlFormat, MTLTextureDescriptor *desc, IOSurfaceRef surfaceRef, NSUInteger slice, bool renderTargetOnly, TextureRef *refOut) { refOut->reset(new Texture(context, desc, surfaceRef, slice, renderTargetOnly)); if (!(*refOut) || !(*refOut)->get()) { ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); } if (!mtlFormat.hasDepthAndStencilBits()) { refOut->get()->setColorWritableMask(GetEmulatedColorWriteMask(mtlFormat)); } return angle::Result::Continue; } /** static */ TextureRef Texture::MakeFromMetal(id metalTexture) { ANGLE_MTL_OBJC_SCOPE { return TextureRef(new Texture(metalTexture)); } } Texture::Texture(id metalTexture) : mColorWritableMask(std::make_shared(MTLColorWriteMaskAll)) { set(metalTexture); } Texture::Texture(ContextMtl *context, MTLTextureDescriptor *desc, uint32_t mips, bool renderTargetOnly, bool allowFormatView) : Texture(context, desc, mips, renderTargetOnly, allowFormatView, false) {} Texture::Texture(ContextMtl *context, MTLTextureDescriptor *desc, uint32_t mips, bool renderTargetOnly, bool allowFormatView, bool memoryLess) : mColorWritableMask(std::make_shared(MTLColorWriteMaskAll)) { ANGLE_MTL_OBJC_SCOPE { id metalDevice = context->getMetalDevice(); if (mips > 1 && mips < desc.mipmapLevelCount) { desc.mipmapLevelCount = mips; } // Every texture will support being rendered for now desc.usage = 0; if (context->getNativeFormatCaps(desc.pixelFormat).isRenderable()) { desc.usage |= MTLTextureUsageRenderTarget; } if (memoryLess) { #if (TARGET_OS_IOS || TARGET_OS_TV) && !TARGET_OS_MACCATALYST desc.resourceOptions = MTLResourceStorageModeMemoryless; #else desc.resourceOptions = MTLResourceStorageModePrivate; #endif } else if (context->getNativeFormatCaps(desc.pixelFormat).depthRenderable || desc.textureType == MTLTextureType2DMultisample) { // Metal doesn't support host access to depth stencil texture's data desc.resourceOptions = MTLResourceStorageModePrivate; } if (!renderTargetOnly) { desc.usage = desc.usage | MTLTextureUsageShaderRead; if (context->getNativeFormatCaps(desc.pixelFormat).writable) { desc.usage = desc.usage | MTLTextureUsageShaderWrite; } } if (desc.pixelFormat == MTLPixelFormatDepth32Float_Stencil8) { ASSERT(allowFormatView); } if (allowFormatView) { desc.usage = desc.usage | MTLTextureUsagePixelFormatView; } set([[metalDevice newTextureWithDescriptor:desc] ANGLE_MTL_AUTORELEASE]); mCreationDesc.retainAssign(desc); } } Texture::Texture(ContextMtl *context, MTLTextureDescriptor *desc, IOSurfaceRef iosurface, NSUInteger plane, bool renderTargetOnly) : mColorWritableMask(std::make_shared(MTLColorWriteMaskAll)) { ANGLE_MTL_OBJC_SCOPE { id metalDevice = context->getMetalDevice(); // Every texture will support being rendered for now desc.usage = MTLTextureUsagePixelFormatView; if (context->getNativeFormatCaps(desc.pixelFormat).isRenderable()) { desc.usage |= MTLTextureUsageRenderTarget; } #if (TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH) && !TARGET_OS_MACCATALYST desc.resourceOptions = MTLResourceStorageModeShared; #else desc.resourceOptions = MTLResourceStorageModeManaged; #endif if (!renderTargetOnly) { desc.usage = desc.usage | MTLTextureUsageShaderRead; if (context->getNativeFormatCaps(desc.pixelFormat).writable) { desc.usage = desc.usage | MTLTextureUsageShaderWrite; } } id iosurfTexture = [metalDevice newTextureWithDescriptor:desc iosurface:iosurface plane:plane]; set([iosurfTexture ANGLE_MTL_AUTORELEASE]); } } Texture::Texture(Texture *original, MTLPixelFormat format) : Resource(original), mColorWritableMask(original->mColorWritableMask) // Share color write mask property { ANGLE_MTL_OBJC_SCOPE { auto view = [original->get() newTextureViewWithPixelFormat:format]; set([view ANGLE_MTL_AUTORELEASE]); } } Texture::Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRange, NSRange slices) : Resource(original), mColorWritableMask(original->mColorWritableMask) // Share color write mask property { ANGLE_MTL_OBJC_SCOPE { auto view = [original->get() newTextureViewWithPixelFormat:original->pixelFormat() textureType:type levels:mipmapLevelRange slices:slices]; set([view ANGLE_MTL_AUTORELEASE]); } } Texture::Texture(Texture *original, const TextureSwizzleChannels &swizzle) : Resource(original), mColorWritableMask(original->mColorWritableMask) // Share color write mask property { #if ANGLE_MTL_SWIZZLE_AVAILABLE ANGLE_MTL_OBJC_SCOPE { auto view = [original->get() newTextureViewWithPixelFormat:original->pixelFormat() textureType:original->textureType() levels:NSMakeRange(0, original->mipmapLevels()) slices:NSMakeRange(0, original->cubeFacesOrArrayLength()) swizzle:swizzle]; set([view ANGLE_MTL_AUTORELEASE]); } #else UNREACHABLE(); #endif } void Texture::syncContent(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder) { InvokeCPUMemSync(context, blitEncoder, this); } void Texture::syncContentIfNeeded(ContextMtl *context) { EnsureCPUMemWillBeSynced(context, this); } bool Texture::isCPUAccessible() const { #if TARGET_OS_OSX || TARGET_OS_MACCATALYST if (get().storageMode == MTLStorageModeManaged) { return true; } #endif return get().storageMode == MTLStorageModeShared; } bool Texture::isShaderReadable() const { return get().usage & MTLTextureUsageShaderRead; } bool Texture::supportFormatView() const { return get().usage & MTLTextureUsagePixelFormatView; } void Texture::replace2DRegion(ContextMtl *context, const MTLRegion ®ion, const MipmapNativeLevel &mipmapLevel, uint32_t slice, const uint8_t *data, size_t bytesPerRow) { ASSERT(region.size.depth == 1); replaceRegion(context, region, mipmapLevel, slice, data, bytesPerRow, 0); } void Texture::replaceRegion(ContextMtl *context, const MTLRegion ®ion, const MipmapNativeLevel &mipmapLevel, uint32_t slice, const uint8_t *data, size_t bytesPerRow, size_t bytesPer2DImage) { if (mipmapLevel.get() >= this->mipmapLevels()) { return; } ASSERT(isCPUAccessible()); CommandQueue &cmdQueue = context->cmdQueue(); syncContentIfNeeded(context); // NOTE(hqle): what if multiple contexts on multiple threads are using this texture? if (this->isBeingUsedByGPU(context)) { context->flushCommandBufer(); } cmdQueue.ensureResourceReadyForCPU(this); if (textureType() != MTLTextureType3D) { bytesPer2DImage = 0; } [get() replaceRegion:region mipmapLevel:mipmapLevel.get() slice:slice withBytes:data bytesPerRow:bytesPerRow bytesPerImage:bytesPer2DImage]; } void Texture::getBytes(ContextMtl *context, size_t bytesPerRow, size_t bytesPer2DInage, const MTLRegion ®ion, const MipmapNativeLevel &mipmapLevel, uint32_t slice, uint8_t *dataOut) { ASSERT(isCPUAccessible()); CommandQueue &cmdQueue = context->cmdQueue(); syncContentIfNeeded(context); // NOTE(hqle): what if multiple contexts on multiple threads are using this texture? if (this->isBeingUsedByGPU(context)) { context->flushCommandBufer(); } cmdQueue.ensureResourceReadyForCPU(this); [get() getBytes:dataOut bytesPerRow:bytesPerRow bytesPerImage:bytesPer2DInage fromRegion:region mipmapLevel:mipmapLevel.get() slice:slice]; } TextureRef Texture::createCubeFaceView(uint32_t face) { ANGLE_MTL_OBJC_SCOPE { switch (textureType()) { case MTLTextureTypeCube: return TextureRef(new Texture( this, MTLTextureType2D, NSMakeRange(0, mipmapLevels()), NSMakeRange(face, 1))); default: UNREACHABLE(); return nullptr; } } } TextureRef Texture::createSliceMipView(uint32_t slice, const MipmapNativeLevel &level) { ANGLE_MTL_OBJC_SCOPE { switch (textureType()) { case MTLTextureTypeCube: case MTLTextureType2D: case MTLTextureType2DArray: return TextureRef(new Texture(this, MTLTextureType2D, NSMakeRange(level.get(), 1), NSMakeRange(slice, 1))); default: UNREACHABLE(); return nullptr; } } } TextureRef Texture::createMipView(const MipmapNativeLevel &level) { ANGLE_MTL_OBJC_SCOPE { NSUInteger slices = cubeFacesOrArrayLength(); return TextureRef( new Texture(this, textureType(), NSMakeRange(level.get(), 1), NSMakeRange(0, slices))); } } TextureRef Texture::createViewWithDifferentFormat(MTLPixelFormat format) { ASSERT(supportFormatView()); return TextureRef(new Texture(this, format)); } TextureRef Texture::createViewWithCompatibleFormat(MTLPixelFormat format) { return TextureRef(new Texture(this, format)); } TextureRef Texture::createSwizzleView(const TextureSwizzleChannels &swizzle) { #if ANGLE_MTL_SWIZZLE_AVAILABLE return TextureRef(new Texture(this, swizzle)); #else WARN() << "Texture swizzle is not supported on pre iOS 13.0 and macOS 15.0"; UNIMPLEMENTED(); return shared_from_this(); #endif } MTLPixelFormat Texture::pixelFormat() const { return get().pixelFormat; } MTLTextureType Texture::textureType() const { return get().textureType; } uint32_t Texture::mipmapLevels() const { return static_cast(get().mipmapLevelCount); } uint32_t Texture::arrayLength() const { return static_cast(get().arrayLength); } uint32_t Texture::cubeFacesOrArrayLength() const { if (textureType() == MTLTextureTypeCube) { return 6; } return arrayLength(); } uint32_t Texture::width(const MipmapNativeLevel &level) const { return static_cast(GetMipSize(get().width, level)); } uint32_t Texture::height(const MipmapNativeLevel &level) const { return static_cast(GetMipSize(get().height, level)); } uint32_t Texture::depth(const MipmapNativeLevel &level) const { return static_cast(GetMipSize(get().depth, level)); } gl::Extents Texture::size(const MipmapNativeLevel &level) const { gl::Extents re; re.width = width(level); re.height = height(level); re.depth = depth(level); return re; } gl::Extents Texture::size(const ImageNativeIndex &index) const { gl::Extents extents = size(index.getNativeLevel()); if (index.hasLayer()) { extents.depth = 1; } return extents; } uint32_t Texture::samples() const { return static_cast(get().sampleCount); } angle::Result Texture::resize(ContextMtl *context, uint32_t width, uint32_t height) { // Resizing texture view is not supported. ASSERT(mCreationDesc); ANGLE_MTL_OBJC_SCOPE { MTLTextureDescriptor *newDesc = [[mCreationDesc.get() copy] ANGLE_MTL_AUTORELEASE]; newDesc.width = width; newDesc.height = height; id newTexture = [[get().device newTextureWithDescriptor:newDesc] ANGLE_MTL_AUTORELEASE]; ANGLE_CHECK_GL_ALLOC(context, newTexture); mCreationDesc.retainAssign(newDesc); set(newTexture); // Reset reference counter Resource::reset(); } return angle::Result::Continue; } TextureRef Texture::getLinearColorView() { if (mLinearColorView) { return mLinearColorView; } switch (pixelFormat()) { case MTLPixelFormatRGBA8Unorm_sRGB: mLinearColorView = createViewWithCompatibleFormat(MTLPixelFormatRGBA8Unorm); break; case MTLPixelFormatBGRA8Unorm_sRGB: mLinearColorView = createViewWithCompatibleFormat(MTLPixelFormatBGRA8Unorm); break; default: // NOTE(hqle): Not all sRGB formats are supported yet. UNREACHABLE(); } return mLinearColorView; } TextureRef Texture::getReadableCopy(ContextMtl *context, mtl::BlitCommandEncoder *encoder, const uint32_t levelToCopy, const uint32_t sliceToCopy, const MTLRegion &areaToCopy) { gl::Extents firstLevelSize = size(kZeroNativeMipLevel); if (!mReadCopy || mReadCopy->get().width < static_cast(firstLevelSize.width) || mReadCopy->get().height < static_cast(firstLevelSize.height)) { // Create a texture that big enough to store the first level data and any smaller level ANGLE_MTL_OBJC_SCOPE { auto desc = [[MTLTextureDescriptor new] ANGLE_MTL_AUTORELEASE]; desc.textureType = get().textureType; desc.pixelFormat = get().pixelFormat; desc.width = firstLevelSize.width; desc.height = firstLevelSize.height; desc.depth = 1; desc.arrayLength = 1; desc.resourceOptions = MTLResourceStorageModePrivate; desc.sampleCount = get().sampleCount; desc.usage = MTLTextureUsageShaderRead | MTLTextureUsagePixelFormatView; id mtlTexture = [context->getMetalDevice() newTextureWithDescriptor:desc]; mReadCopy.reset(new Texture(mtlTexture)); } // ANGLE_MTL_OBJC_SCOPE } ASSERT(encoder); encoder->copyTexture(shared_from_this(), sliceToCopy, mtl::MipmapNativeLevel(levelToCopy), mReadCopy, 0, mtl::kZeroNativeMipLevel, 1, 1); return mReadCopy; } void Texture::releaseReadableCopy() { mReadCopy = nullptr; } TextureRef Texture::getStencilView() { if (mStencilView) { return mStencilView; } switch (pixelFormat()) { case MTLPixelFormatStencil8: case MTLPixelFormatX32_Stencil8: #if TARGET_OS_OSX || TARGET_OS_MACCATALYST case MTLPixelFormatX24_Stencil8: #endif return mStencilView = shared_from_this(); #if TARGET_OS_OSX || TARGET_OS_MACCATALYST case MTLPixelFormatDepth24Unorm_Stencil8: mStencilView = createViewWithDifferentFormat(MTLPixelFormatX24_Stencil8); break; #endif case MTLPixelFormatDepth32Float_Stencil8: mStencilView = createViewWithDifferentFormat(MTLPixelFormatX32_Stencil8); break; default: UNREACHABLE(); } return mStencilView; } void Texture::set(id metalTexture) { ParentClass::set(metalTexture); // Reset stencil view mStencilView = nullptr; mLinearColorView = nullptr; } // Buffer implementation angle::Result Buffer::MakeBuffer(ContextMtl *context, size_t size, const uint8_t *data, BufferRef *bufferOut) { return MakeBufferWithSharedMemOpt(context, false, size, data, bufferOut); } angle::Result Buffer::MakeBufferWithSharedMemOpt(ContextMtl *context, bool forceUseSharedMem, size_t size, const uint8_t *data, BufferRef *bufferOut) { bufferOut->reset(new Buffer(context, forceUseSharedMem, size, data)); if (!(*bufferOut) || !(*bufferOut)->get()) { ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); } return angle::Result::Continue; } angle::Result Buffer::MakeBufferWithResOpt(ContextMtl *context, MTLResourceOptions options, size_t size, const uint8_t *data, BufferRef *bufferOut) { bufferOut->reset(new Buffer(context, options, size, data)); if (!(*bufferOut) || !(*bufferOut)->get()) { ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); } return angle::Result::Continue; } Buffer::Buffer(ContextMtl *context, bool forceUseSharedMem, size_t size, const uint8_t *data) { (void)resetWithSharedMemOpt(context, forceUseSharedMem, size, data); } Buffer::Buffer(ContextMtl *context, MTLResourceOptions options, size_t size, const uint8_t *data) { (void)resetWithResOpt(context, options, size, data); } angle::Result Buffer::reset(ContextMtl *context, size_t size, const uint8_t *data) { return resetWithSharedMemOpt(context, false, size, data); } angle::Result Buffer::resetWithSharedMemOpt(ContextMtl *context, bool forceUseSharedMem, size_t size, const uint8_t *data) { MTLResourceOptions options; options = 0; #if TARGET_OS_OSX || TARGET_OS_MACCATALYST if (!forceUseSharedMem || context->getDisplay()->getFeatures().forceBufferGPUStorage.enabled) { options |= MTLResourceStorageModeManaged; } else #endif { options |= MTLResourceStorageModeShared; } return resetWithResOpt(context, options, size, data); } angle::Result Buffer::resetWithResOpt(ContextMtl *context, MTLResourceOptions options, size_t size, const uint8_t *data) { ANGLE_MTL_OBJC_SCOPE { id newBuffer; id metalDevice = context->getMetalDevice(); if (data) { newBuffer = [metalDevice newBufferWithBytes:data length:size options:options]; } else { newBuffer = [metalDevice newBufferWithLength:size options:options]; } set([newBuffer ANGLE_MTL_AUTORELEASE]); // Reset command buffer's reference serial Resource::reset(); return angle::Result::Continue; } } void Buffer::syncContent(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder) { InvokeCPUMemSync(context, blitEncoder, this); } const uint8_t *Buffer::mapReadOnly(ContextMtl *context) { return mapWithOpt(context, true, false); } uint8_t *Buffer::map(ContextMtl *context) { return mapWithOpt(context, false, false); } uint8_t *Buffer::mapWithOpt(ContextMtl *context, bool readonly, bool noSync) { mMapReadOnly = readonly; if (!noSync) { CommandQueue &cmdQueue = context->cmdQueue(); EnsureCPUMemWillBeSynced(context, this); if (this->isBeingUsedByGPU(context)) { context->flushCommandBufer(); } cmdQueue.ensureResourceReadyForCPU(this); } return reinterpret_cast([get() contents]); } void Buffer::unmap(ContextMtl *context) { flush(context, 0, size()); // Reset read only flag mMapReadOnly = true; } void Buffer::unmapNoFlush(ContextMtl *context) { mMapReadOnly = true; } void Buffer::unmapAndFlushSubset(ContextMtl *context, size_t offsetWritten, size_t sizeWritten) { #if TARGET_OS_OSX || TARGET_OS_MACCATALYST flush(context, offsetWritten, sizeWritten); #endif mMapReadOnly = true; } void Buffer::flush(ContextMtl *context, size_t offsetWritten, size_t sizeWritten) { #if TARGET_OS_OSX || TARGET_OS_MACCATALYST if (!mMapReadOnly) { if (get().storageMode == MTLStorageModeManaged) { size_t startOffset = std::min(offsetWritten, size()); size_t endOffset = std::min(offsetWritten + sizeWritten, size()); size_t clampedSize = endOffset - startOffset; if (clampedSize > 0) { [get() didModifyRange:NSMakeRange(startOffset, clampedSize)]; } } } #endif } size_t Buffer::size() const { return get().length; } bool Buffer::useSharedMem() const { return get().storageMode == MTLStorageModeShared; } } }