diff --git a/packages/skia/apple/MetalContext.h b/packages/skia/apple/MetalContext.h index 070f14d8a3..e69ced8cef 100644 --- a/packages/skia/apple/MetalContext.h +++ b/packages/skia/apple/MetalContext.h @@ -38,6 +38,7 @@ class MetalContext { public: MetalContext(const MetalContext &) = delete; MetalContext &operator=(const MetalContext &) = delete; + ~MetalContext(); static MetalContext &getInstance() { static thread_local MetalContext instance; @@ -71,13 +72,11 @@ class MetalContext { switch (format) { case SkiaCVPixelBufferUtils::CVPixelBufferBaseFormat::rgb: { // CVPixelBuffer is in any RGB format, single-plane - return SkiaCVPixelBufferUtils::RGB::makeSkImageFromCVPixelBuffer( - _device, _directContext.get(), sampleBuffer); + return SkiaCVPixelBufferUtils::RGB::makeSkImageFromCVPixelBuffer(*this, sampleBuffer); } case SkiaCVPixelBufferUtils::CVPixelBufferBaseFormat::yuv: { // CVPixelBuffer is in any YUV format, multi-plane - return SkiaCVPixelBufferUtils::YUV::makeSkImageFromCVPixelBuffer( - _device, _directContext.get(), sampleBuffer); + return SkiaCVPixelBufferUtils::YUV::makeSkImageFromCVPixelBuffer(*this, sampleBuffer); } default: [[unlikely]] { @@ -97,12 +96,17 @@ class MetalContext { height, useP3ColorSpace); } +public: GrDirectContext *getDirectContext() { return _directContext.get(); } + id getDevice() const { return _device; } + CVMetalTextureCacheRef getMetalTextureCache(); private: id _device = nullptr; id _commandQueue = nullptr; sk_sp _directContext = nullptr; + CVMetalTextureCacheRef _metalTextureCache = nullptr; + id _memoryWarningObserver = nil; MetalContext(); }; diff --git a/packages/skia/apple/MetalContext.mm b/packages/skia/apple/MetalContext.mm index dd79e2a2b2..d6d4ea4229 100644 --- a/packages/skia/apple/MetalContext.mm +++ b/packages/skia/apple/MetalContext.mm @@ -3,6 +3,8 @@ #include "RNSkLog.h" #import +#import +#import #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdocumentation" @@ -17,6 +19,10 @@ #pragma clang diagnostic pop +MetalContext::~MetalContext() { + [[NSNotificationCenter defaultCenter] removeObserver:_memoryWarningObserver]; +} + MetalContext::MetalContext() { _device = MTLCreateSystemDefaultDevice(); if (!_device) { @@ -35,4 +41,33 @@ if (_directContext == nullptr) { RNSkia::RNSkLogger::logToConsole("Couldn't create a Skia Metal Context"); } + + // Add a memory warning listener to purge cache + _memoryWarningObserver = [ + [NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationDidReceiveMemoryWarningNotification + object:nil + queue:nil + usingBlock:^(__unused NSNotification *notification) { + // Clean unused Skia textures (cache/GPU) + _directContext->performDeferredCleanup(std::chrono::milliseconds(0)); + if (_metalTextureCache != nil) { + // Flush Metal Texture Cache pool (this can have a huge impact) + CVMetalTextureCacheFlush(_metalTextureCache, 0); + } + } + ]; +} + + +CVMetalTextureCacheRef MetalContext::getMetalTextureCache() { + if (_metalTextureCache == nil) { + // Create a new Texture Cache + auto result = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, _device, + nil, &_metalTextureCache); + if (result != kCVReturnSuccess || _metalTextureCache == nil) { + throw std::runtime_error("Failed to create Metal Texture Cache!"); + } + } + return _metalTextureCache; } diff --git a/packages/skia/apple/SkiaCVPixelBufferUtils.h b/packages/skia/apple/SkiaCVPixelBufferUtils.h index dbd110095a..6565b82e1f 100644 --- a/packages/skia/apple/SkiaCVPixelBufferUtils.h +++ b/packages/skia/apple/SkiaCVPixelBufferUtils.h @@ -21,6 +21,8 @@ #import "include/gpu/ganesh/GrYUVABackendTextures.h" #pragma clang diagnostic pop +class MetalContext; + /** Holds a Metal Texture. When the `TextureHolder` is destroyed, the underlying Metal Texture @@ -84,8 +86,7 @@ class SkiaCVPixelBufferUtils { CVPixelBuffer. */ static sk_sp - makeSkImageFromCVPixelBuffer(id device, GrDirectContext *context, - CVPixelBufferRef pixelBuffer); + makeSkImageFromCVPixelBuffer(MetalContext& context, CVPixelBufferRef pixelBuffer); private: static SkColorType getCVPixelBufferColorType(CVPixelBufferRef pixelBuffer); @@ -98,8 +99,7 @@ class SkiaCVPixelBufferUtils { CVPixelBuffer. */ static sk_sp - makeSkImageFromCVPixelBuffer(id device, GrDirectContext *context, - CVPixelBufferRef pixelBuffer); + makeSkImageFromCVPixelBuffer(MetalContext& context, CVPixelBufferRef pixelBuffer); private: static SkYUVAInfo::PlaneConfig getPlaneConfig(OSType pixelFormat); @@ -109,9 +109,7 @@ class SkiaCVPixelBufferUtils { }; private: - static CVMetalTextureCacheRef getTextureCache(id device); - static TextureHolder *getSkiaTextureForCVPixelBufferPlane( - id device, CVPixelBufferRef pixelBuffer, size_t planeIndex); + static TextureHolder *getSkiaTextureForCVPixelBufferPlane(MetalContext& context, CVPixelBufferRef pixelBuffer, size_t planeIndex); static MTLPixelFormat getMTLPixelFormatForCVPixelBufferPlane(CVPixelBufferRef pixelBuffer, size_t planeIndex); diff --git a/packages/skia/apple/SkiaCVPixelBufferUtils.mm b/packages/skia/apple/SkiaCVPixelBufferUtils.mm index 6c1e7fda96..68531dcd05 100644 --- a/packages/skia/apple/SkiaCVPixelBufferUtils.mm +++ b/packages/skia/apple/SkiaCVPixelBufferUtils.mm @@ -7,6 +7,8 @@ #import "SkiaCVPixelBufferUtils.h" +#import "MetalContext.h" + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdocumentation" @@ -133,28 +135,23 @@ } } -sk_sp SkiaCVPixelBufferUtils::RGB::makeSkImageFromCVPixelBuffer( - id device, GrDirectContext *context, - CVPixelBufferRef pixelBuffer) { +sk_sp SkiaCVPixelBufferUtils::RGB::makeSkImageFromCVPixelBuffer(MetalContext& context, CVPixelBufferRef pixelBuffer) { // 1. Get Skia color type for RGB buffer SkColorType colorType = getCVPixelBufferColorType(pixelBuffer); // 2. Get texture, RGB buffers only have one plane - TextureHolder *texture = getSkiaTextureForCVPixelBufferPlane( - device, pixelBuffer, /* planeIndex */ 0); + TextureHolder *texture = getSkiaTextureForCVPixelBufferPlane(context, pixelBuffer, /* planeIndex */ 0); // 3. Convert to image with manual memory cleanup return SkImages::BorrowTextureFrom( - context, texture->toGrBackendTexture(), kTopLeft_GrSurfaceOrigin, + context.getDirectContext(), texture->toGrBackendTexture(), kTopLeft_GrSurfaceOrigin, colorType, kOpaque_SkAlphaType, nullptr, [](void *texture) { delete (TextureHolder *)texture; }, (void *)texture); } // pragma MARK: YUV -sk_sp SkiaCVPixelBufferUtils::YUV::makeSkImageFromCVPixelBuffer( - id device, GrDirectContext *context, - CVPixelBufferRef pixelBuffer) { +sk_sp SkiaCVPixelBufferUtils::YUV::makeSkImageFromCVPixelBuffer(MetalContext& context, CVPixelBufferRef pixelBuffer) { // 1. Get all planes (YUV, Y_UV, Y_U_V or Y_U_V_A) const size_t planesCount = CVPixelBufferGetPlaneCount(pixelBuffer); if (planesCount > SkYUVAInfo::kMaxPlanes) [[unlikely]] { @@ -168,7 +165,7 @@ for (size_t planeIndex = 0; planeIndex < planesCount; planeIndex++) { TextureHolder *textureHolder = - getSkiaTextureForCVPixelBufferPlane(device, pixelBuffer, planeIndex); + getSkiaTextureForCVPixelBufferPlane(context, pixelBuffer, planeIndex); textures[planeIndex] = textureHolder->toGrBackendTexture(); texturesHolder->addTexture(textureHolder); } @@ -181,7 +178,7 @@ // 4. Wrap into SkImage type with manualy memory cleanup return SkImages::TextureFromYUVATextures( - context, yuvaTextures, nullptr, + context.getDirectContext(), yuvaTextures, nullptr, [](void *textureHolders) { delete (MultiTexturesHolder *)textureHolders; }, @@ -290,9 +287,9 @@ // pragma MARK: CVPixelBuffer -> Skia Texture TextureHolder *SkiaCVPixelBufferUtils::getSkiaTextureForCVPixelBufferPlane( - id device, CVPixelBufferRef pixelBuffer, size_t planeIndex) { + MetalContext& context, CVPixelBufferRef pixelBuffer, size_t planeIndex) { // 1. Get cache - CVMetalTextureCacheRef textureCache = getTextureCache(device); + CVMetalTextureCacheRef textureCache = context.getMetalTextureCache(); // 2. Get MetalTexture from CMSampleBuffer size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex); @@ -314,22 +311,6 @@ return new TextureHolder(textureHolder); } -// pragma MARK: getTextureCache() - -CVMetalTextureCacheRef -SkiaCVPixelBufferUtils::getTextureCache(id device) { - static CVMetalTextureCacheRef textureCache = nil; - if (textureCache == nil) { - // Create a new Texture Cache - auto result = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, - nil, &textureCache); - if (result != kCVReturnSuccess || textureCache == nil) { - throw std::runtime_error("Failed to create Metal Texture Cache!"); - } - } - return textureCache; -} - // pragma MARK: Get CVPixelBuffer MTLPixelFormat MTLPixelFormat SkiaCVPixelBufferUtils::getMTLPixelFormatForCVPixelBufferPlane(