// // 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" namespace rx { namespace mtl { namespace { inline NSUInteger GetMipSize(NSUInteger baseSize, NSUInteger level) { return std::max(1, baseSize >> level); } void SetTextureSwizzle(ContextMtl *context, const Format &format, MTLTextureDescriptor *textureDescOut) { // Texture swizzle functions's declarations are only available if macos 10.15 sdk is present #if defined(__MAC_10_15) if (context->getDisplay()->getFeatures().hasTextureSwizzle.enabled) { // Work around Metal doesn't have native support for DXT1 without alpha. switch (format.intendedFormatId) { case angle::FormatID::BC1_RGB_UNORM_BLOCK: case angle::FormatID::BC1_RGB_UNORM_SRGB_BLOCK: textureDescOut.swizzle = MTLTextureSwizzleChannelsMake(MTLTextureSwizzleRed, MTLTextureSwizzleGreen, MTLTextureSwizzleBlue, MTLTextureSwizzleOne); break; default: break; } } #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); } bool Resource::isBeingUsedByGPU(Context *context) const { return context->cmdQueue().isResourceBeingUsedByGPU(this); } void Resource::setUsedByCommandBufferWithQueueSerial(uint64_t serial, bool writing) { auto curSerial = mUsageRef->cmdBufferQueueSerial.load(std::memory_order_relaxed); do { if (curSerial >= serial) { return; } } while (!mUsageRef->cmdBufferQueueSerial.compare_exchange_weak( curSerial, serial, std::memory_order_release, std::memory_order_relaxed)); // NOTE(hqle): This is not thread safe, if multiple command buffers on multiple threads // are writing to it. if (writing) { mUsageRef->cpuReadMemDirty = true; } } // Texture implemenetation /** static */ angle::Result Texture::Make2DTexture(ContextMtl *context, const Format &format, uint32_t width, uint32_t height, uint32_t mips, bool renderTargetOnly, bool allowTextureView, TextureRef *refOut) { ANGLE_MTL_OBJC_SCOPE { MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format.metalFormat width:width height:height mipmapped:mips == 0 || mips > 1]; SetTextureSwizzle(context, format, desc); refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowTextureView)); } // ANGLE_MTL_OBJC_SCOPE if (!refOut || !refOut->get()) { ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); } return angle::Result::Continue; } /** static */ angle::Result Texture::MakeCubeTexture(ContextMtl *context, const Format &format, uint32_t size, uint32_t mips, bool renderTargetOnly, bool allowTextureView, TextureRef *refOut) { ANGLE_MTL_OBJC_SCOPE { MTLTextureDescriptor *desc = [MTLTextureDescriptor textureCubeDescriptorWithPixelFormat:format.metalFormat size:size mipmapped:mips == 0 || mips > 1]; SetTextureSwizzle(context, format, desc); refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowTextureView)); } // ANGLE_MTL_OBJC_SCOPE if (!refOut || !refOut->get()) { ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); } 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 supportTextureView) : 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 (Format::FormatRenderable(desc.pixelFormat)) { desc.usage |= MTLTextureUsageRenderTarget; } if (!Format::FormatCPUReadable(desc.pixelFormat)) { desc.resourceOptions = MTLResourceStorageModePrivate; } if (!renderTargetOnly) { desc.usage = desc.usage | MTLTextureUsageShaderRead; } if (supportTextureView) { desc.usage = desc.usage | MTLTextureUsagePixelFormatView; } set([[metalDevice newTextureWithDescriptor:desc] 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, uint32_t slice) : 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:NSMakeRange(slice, 1)]; set([view ANGLE_MTL_AUTORELEASE]); } } void Texture::syncContent(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder) { #if TARGET_OS_OSX || TARGET_OS_MACCATALYST if (blitEncoder) { blitEncoder->synchronizeResource(shared_from_this()); } #endif } void Texture::syncContent(ContextMtl *context) { #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 (this->isCPUReadMemDirty()) { mtl::BlitCommandEncoder *blitEncoder = context->getBlitCommandEncoder(); syncContent(context, blitEncoder); this->resetCPUReadMemDirty(); } #endif } bool Texture::isCPUAccessible() const { #if TARGET_OS_OSX || TARGET_OS_MACCATALYST if (get().storageMode == MTLStorageModeManaged) { return true; } #endif return get().storageMode == MTLStorageModeShared; } void Texture::replaceRegion(ContextMtl *context, MTLRegion region, uint32_t mipmapLevel, uint32_t slice, const uint8_t *data, size_t bytesPerRow) { if (mipmapLevel >= this->mipmapLevels()) { return; } ASSERT(isCPUAccessible()); CommandQueue &cmdQueue = context->cmdQueue(); syncContent(context); // NOTE(hqle): what if multiple contexts on multiple threads are using this texture? if (this->isBeingUsedByGPU(context)) { context->flushCommandBufer(); } cmdQueue.ensureResourceReadyForCPU(this); [get() replaceRegion:region mipmapLevel:mipmapLevel slice:slice withBytes:data bytesPerRow:bytesPerRow bytesPerImage:0]; } void Texture::getBytes(ContextMtl *context, size_t bytesPerRow, MTLRegion region, uint32_t mipmapLevel, uint8_t *dataOut) { ASSERT(isCPUAccessible()); CommandQueue &cmdQueue = context->cmdQueue(); syncContent(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 fromRegion:region mipmapLevel:mipmapLevel]; } TextureRef Texture::createCubeFaceView(uint32_t face) { ANGLE_MTL_OBJC_SCOPE { switch (textureType()) { case MTLTextureTypeCube: return TextureRef( new Texture(this, MTLTextureType2D, NSMakeRange(0, mipmapLevels()), face)); default: UNREACHABLE(); return nullptr; } } } TextureRef Texture::createSliceMipView(uint32_t slice, uint32_t level) { ANGLE_MTL_OBJC_SCOPE { switch (textureType()) { case MTLTextureTypeCube: case MTLTextureType2D: return TextureRef( new Texture(this, MTLTextureType2D, NSMakeRange(level, 1), slice)); default: UNREACHABLE(); return nullptr; } } } TextureRef Texture::createViewWithDifferentFormat(MTLPixelFormat format) { return TextureRef(new Texture(this, format)); } 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::width(uint32_t level) const { return static_cast(GetMipSize(get().width, level)); } uint32_t Texture::height(uint32_t level) const { return static_cast(GetMipSize(get().height, level)); } gl::Extents Texture::size(uint32_t level) const { gl::Extents re; re.width = width(level); re.height = height(level); re.depth = static_cast(GetMipSize(get().depth, level)); return re; } gl::Extents Texture::size(const gl::ImageIndex &index) const { // Only support these texture types for now ASSERT(!get() || textureType() == MTLTextureType2D || textureType() == MTLTextureTypeCube); return size(index.getLevelIndex()); } void Texture::set(id metalTexture) { ParentClass::set(metalTexture); } // Buffer implementation angle::Result Buffer::MakeBuffer(ContextMtl *context, size_t size, const uint8_t *data, BufferRef *bufferOut) { bufferOut->reset(new Buffer(context, size, data)); if (!bufferOut || !bufferOut->get()) { ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); } return angle::Result::Continue; } Buffer::Buffer(ContextMtl *context, size_t size, const uint8_t *data) { (void)reset(context, size, data); } angle::Result Buffer::reset(ContextMtl *context, size_t size, const uint8_t *data) { ANGLE_MTL_OBJC_SCOPE { MTLResourceOptions options; id newBuffer; id metalDevice = context->getMetalDevice(); options = 0; #if TARGET_OS_OSX || TARGET_OS_MACCATALYST options |= MTLResourceStorageModeManaged; #endif if (data) { newBuffer = [metalDevice newBufferWithBytes:data length:size options:options]; } else { newBuffer = [metalDevice newBufferWithLength:size options:options]; } set([newBuffer ANGLE_MTL_AUTORELEASE]); return angle::Result::Continue; } } uint8_t *Buffer::map(ContextMtl *context) { CommandQueue &cmdQueue = context->cmdQueue(); // NOTE(hqle): what if multiple contexts on multiple threads are using this buffer? if (this->isBeingUsedByGPU(context)) { context->flushCommandBufer(); } // NOTE(hqle): currently not support reading data written by GPU cmdQueue.ensureResourceReadyForCPU(this); return reinterpret_cast([get() contents]); } void Buffer::unmap(ContextMtl *context) { #if TARGET_OS_OSX || TARGET_OS_MACCATALYST [get() didModifyRange:NSMakeRange(0, size())]; #endif } size_t Buffer::size() const { return get().length; } } }