diff --git a/package/example/package.json b/package/example/package.json index ccd368e140..5027ff2d3c 100644 --- a/package/example/package.json +++ b/package/example/package.json @@ -10,7 +10,8 @@ "pods": "cd ios && bundle install && bundle exec pod install", "lint": "eslint . --fix", "lint-ci": "yarn lint -f ../node_modules/@firmnav/eslint-github-actions-formatter/dist/formatter.js", - "typescript": "tsc --noEmit" + "typescript": "tsc --noEmit", + "postinstall": "patch-package" }, "dependencies": { "@react-native-camera-roll/camera-roll": "^5.7.2", @@ -45,6 +46,7 @@ "eslint": "^8.46.0", "eslint-plugin-prettier": "^5.0.0", "metro-react-native-babel-preset": "^0.77.0", + "patch-package": "^8.0.0", "prettier": "^3.2.4", "typescript": "^5.1.6" } diff --git a/package/example/patches/@shopify+react-native-skia+1.2.0.patch b/package/example/patches/@shopify+react-native-skia+1.2.0.patch new file mode 100644 index 0000000000..7c7df8b1fa --- /dev/null +++ b/package/example/patches/@shopify+react-native-skia+1.2.0.patch @@ -0,0 +1,565 @@ +diff --git a/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/RNSkMetalCanvasProvider.mm b/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/RNSkMetalCanvasProvider.mm +index 0c73eee..5738746 100644 +--- a/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/RNSkMetalCanvasProvider.mm ++++ b/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/RNSkMetalCanvasProvider.mm +@@ -92,9 +92,8 @@ + dContext->flushAndSubmit(); + } + +- id commandBuffer( +- [ThreadContextHolder::ThreadSkiaMetalContext +- .commandQueue commandBuffer]); ++ auto &context = SkiaMetalSurfaceFactory::getSkiaContext(); ++ id commandBuffer = context.commandQueue.commandBuffer; + [commandBuffer presentDrawable:currentDrawable]; + [commandBuffer commit]; + } +diff --git a/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm b/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm +index b3134ee..644cbf7 100644 +--- a/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm ++++ b/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm +@@ -1,11 +1,12 @@ +-#include "RNSkiOSPlatformContext.h" ++#import "RNSkiOSPlatformContext.h" + + #import + #import + #include + #include + +-#include "SkiaMetalSurfaceFactory.h" ++#import "SkiaCVPixelBufferUtils.h" ++#import "SkiaMetalSurfaceFactory.h" + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" +@@ -57,7 +58,7 @@ + std::thread(loader).detach(); + } + +-void RNSkiOSPlatformContext::releasePlatformBuffer(uint64_t pointer) { ++void RNSkiOSPlatformContext::releaseNativeBuffer(uint64_t pointer) { + CMSampleBufferRef sampleBuffer = reinterpret_cast(pointer); + CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + if (sampleBuffer) { +@@ -68,64 +69,94 @@ + } + } + +-uint64_t RNSkiOSPlatformContext::makePlatformBuffer(sk_sp image) { ++uint64_t RNSkiOSPlatformContext::makeNativeBuffer(sk_sp image) { ++ // 0. If Image is not in BGRA, convert to BGRA as only BGRA is supported. ++ if (image->colorType() != kBGRA_8888_SkColorType) { ++ // on iOS, 32_BGRA is the only supported RGB format for CVPixelBuffers. ++ image = image->makeColorTypeAndColorSpace( ++ ThreadContextHolder::ThreadSkiaMetalContext.skContext.get(), ++ kBGRA_8888_SkColorType, SkColorSpace::MakeSRGB()); ++ if (image == nullptr) { ++ throw std::runtime_error( ++ "Failed to convert image to BGRA_8888 colortype! Only BGRA_8888 " ++ "PlatformBuffers are supported."); ++ } ++ } ++ ++ // 1. Get image info + auto bytesPerPixel = image->imageInfo().bytesPerPixel(); + int bytesPerRow = image->width() * bytesPerPixel; + auto buf = SkData::MakeUninitialized(image->width() * image->height() * + bytesPerPixel); + SkImageInfo info = SkImageInfo::Make(image->width(), image->height(), + image->colorType(), image->alphaType()); ++ // 2. Copy pixels into our buffer + image->readPixels(nullptr, info, const_cast(buf->data()), bytesPerRow, + 0, 0); +- auto pixelData = const_cast(buf->data()); + +- // Create a CVPixelBuffer from the raw pixel data +- CVPixelBufferRef pixelBuffer = nullptr; +- // OSType pixelFormatType = MapSkColorTypeToOSType(image->colorType()); +- +- // You will need to fill in the details for creating the pixel buffer +- // CVPixelBufferCreateWithBytes or CVPixelBufferCreateWithPlanarBytes +- // Create the CVPixelBuffer with the image data +- void *context = static_cast( +- new sk_sp(buf)); // Create a copy for the context +- CVReturn r = CVPixelBufferCreateWithBytes( +- nullptr, // allocator +- image->width(), image->height(), kCVPixelFormatType_32BGRA, +- pixelData, // pixel data +- bytesPerRow, // bytes per row +- [](void *releaseRefCon, const void *baseAddress) { // release callback +- auto buf = static_cast *>(releaseRefCon); +- buf->reset(); // This effectively calls unref on the SkData object +- delete buf; // Cleanup the dynamically allocated context +- }, +- context, // release callback context +- nullptr, // pixel buffer attributes +- &pixelBuffer // the newly created pixel buffer +- ); +- +- if (r != kCVReturnSuccess) { +- return 0; // or handle error appropriately ++ // 3. Create an IOSurface (GPU + CPU memory) ++ CFMutableDictionaryRef dict = CFDictionaryCreateMutable( ++ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, ++ &kCFTypeDictionaryValueCallBacks); ++ int width = image->width(); ++ int height = image->height(); ++ int pitch = width * bytesPerPixel; ++ int size = width * height * bytesPerPixel; ++ OSType pixelFormat = kCVPixelFormatType_32BGRA; ++ CFDictionarySetValue( ++ dict, kIOSurfaceBytesPerRow, ++ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pitch)); ++ CFDictionarySetValue( ++ dict, kIOSurfaceBytesPerElement, ++ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bytesPerPixel)); ++ CFDictionarySetValue( ++ dict, kIOSurfaceWidth, ++ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &width)); ++ CFDictionarySetValue( ++ dict, kIOSurfaceHeight, ++ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &height)); ++ CFDictionarySetValue( ++ dict, kIOSurfacePixelFormat, ++ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pixelFormat)); ++ CFDictionarySetValue( ++ dict, kIOSurfaceAllocSize, ++ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &size)); ++ IOSurfaceRef surface = IOSurfaceCreate(dict); ++ if (surface == nil) { ++ throw std::runtime_error("Failed to create " + std::to_string(width) + "x" + ++ std::to_string(height) + " IOSurface!"); + } + +- // Wrap the CVPixelBuffer in a CMSampleBuffer +- CMSampleBufferRef sampleBuffer = nullptr; ++ // 4. Copy over the memory from the pixels into the IOSurface ++ IOSurfaceLock(surface, 0, nil); ++ void *base = IOSurfaceGetBaseAddress(surface); ++ memcpy(base, buf->data(), buf->size()); ++ IOSurfaceUnlock(surface, 0, nil); ++ ++ // 5. Create a CVPixelBuffer from the IOSurface ++ CVPixelBufferRef pixelBuffer = nullptr; ++ CVReturn result = ++ CVPixelBufferCreateWithIOSurface(nil, surface, nil, &pixelBuffer); ++ if (result != kCVReturnSuccess) { ++ throw std::runtime_error( ++ "Failed to create CVPixelBuffer from SkImage! Return value: " + ++ std::to_string(result)); ++ } + ++ // 6. Create CMSampleBuffer base information + CMFormatDescriptionRef formatDescription = nullptr; + CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, + &formatDescription); +- +- // Assuming no specific timing is required, we initialize the timing info to +- // zero. + CMSampleTimingInfo timingInfo = {0}; +- timingInfo.duration = kCMTimeInvalid; // Indicate an unknown duration. +- timingInfo.presentationTimeStamp = kCMTimeZero; // Start at time zero. +- timingInfo.decodeTimeStamp = kCMTimeInvalid; // No specific decode time. ++ timingInfo.duration = kCMTimeInvalid; ++ timingInfo.presentationTimeStamp = kCMTimeZero; ++ timingInfo.decodeTimeStamp = kCMTimeInvalid; + +- // Create the sample buffer. ++ // 7. Wrap the CVPixelBuffer in a CMSampleBuffer ++ CMSampleBufferRef sampleBuffer = nullptr; + OSStatus status = CMSampleBufferCreateReadyWithImageBuffer( + kCFAllocatorDefault, pixelBuffer, formatDescription, &timingInfo, + &sampleBuffer); +- + if (status != noErr) { + if (formatDescription) { + CFRelease(formatDescription); +@@ -133,10 +164,12 @@ + if (pixelBuffer) { + CFRelease(pixelBuffer); + } +- return 0; ++ throw std::runtime_error( ++ "Failed to wrap CVPixelBuffer in CMSampleBuffer! Return value: " + ++ std::to_string(status)); + } + +- // Return sampleBuffer casted to uint64_t ++ // 8. Return CMsampleBuffer casted to uint64_t + return reinterpret_cast(sampleBuffer); + } + +@@ -149,35 +182,9 @@ + return SkiaMetalSurfaceFactory::makeOffscreenSurface(width, height); + } + +-sk_sp +-RNSkiOSPlatformContext::makeImageFromPlatformBuffer(void *buffer) { ++sk_sp RNSkiOSPlatformContext::makeImageFromNativeBuffer(void *buffer) { + CMSampleBufferRef sampleBuffer = (CMSampleBufferRef)buffer; +- // DO the CPU transfer (debugging only) +- // Step 1: Extract the CVPixelBufferRef from the CMSampleBufferRef +- CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); +- +- // Step 2: Lock the pixel buffer to access the raw pixel data +- CVPixelBufferLockBaseAddress(pixelBuffer, 0); +- +- // Step 3: Get information about the image +- void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer); +- size_t width = CVPixelBufferGetWidth(pixelBuffer); +- size_t height = CVPixelBufferGetHeight(pixelBuffer); +- size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); +- +- // Assuming the pixel format is 32BGRA, which is common for iOS video frames. +- // You might need to adjust this based on the actual pixel format. +- SkImageInfo info = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, +- kUnpremul_SkAlphaType); +- +- // Step 4: Create an SkImage from the pixel buffer +- sk_sp data = +- SkData::MakeWithoutCopy(baseAddress, height * bytesPerRow); +- sk_sp image = SkImages::RasterFromData(info, data, bytesPerRow); +- auto texture = SkiaMetalSurfaceFactory::makeTextureFromImage(image); +- // Step 5: Unlock the pixel buffer +- CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); +- return texture; ++ return SkiaMetalSurfaceFactory::makeTextureFromCMSampleBuffer(sampleBuffer); + } + + sk_sp RNSkiOSPlatformContext::createFontMgr() { +diff --git a/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm b/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm +new file mode 100644 +index 0000000..15e7309 +--- /dev/null ++++ b/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm +@@ -0,0 +1,149 @@ ++// ++// SkiaCVPixelBufferUtils.mm ++// react-native-skia ++// ++// Created by Marc Rousavy on 10.04.24. ++// ++ ++#import "SkiaCVPixelBufferUtils.h" ++#import "SkiaMetalSurfaceFactory.h" ++ ++#pragma clang diagnostic push ++#pragma clang diagnostic ignored "-Wdocumentation" ++#import "include/core/SkColorSpace.h" ++#import ++#pragma clang diagnostic pop ++ ++#include ++#if TARGET_RT_BIG_ENDIAN ++#define FourCC2Str(fourcc) \ ++ (const char[]) { \ ++ *((char *)&fourcc), *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 2), \ ++ *(((char *)&fourcc) + 3), 0 \ ++ } ++#else ++#define FourCC2Str(fourcc) \ ++ (const char[]) { \ ++ *(((char *)&fourcc) + 3), *(((char *)&fourcc) + 2), \ ++ *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 0), 0 \ ++ } ++#endif ++ ++// pragma MARK: Base ++ ++SkiaCVPixelBufferUtils::CVPixelBufferBaseFormat ++SkiaCVPixelBufferUtils::getCVPixelBufferBaseFormat( ++ CVPixelBufferRef pixelBuffer) { ++ OSType format = CVPixelBufferGetPixelFormatType(pixelBuffer); ++ ++ switch (format) { ++ case kCVPixelFormatType_32BGRA: ++ case kCVPixelFormatType_32RGBA: ++ return CVPixelBufferBaseFormat::rgb; ++ default: ++ [[unlikely]] throw std::runtime_error( ++ "CVPixelBuffer has unsupported pixel-format! " + ++ std::string(FourCC2Str(format))); ++ } ++} ++ ++// pragma MARK: RGB ++ ++SkColorType SkiaCVPixelBufferUtils::RGB::getCVPixelBufferColorType( ++ CVPixelBufferRef pixelBuffer) { ++ OSType format = CVPixelBufferGetPixelFormatType(pixelBuffer); ++ ++ switch (format) { ++ case kCVPixelFormatType_32BGRA: ++ [[likely]] return kBGRA_8888_SkColorType; ++ case kCVPixelFormatType_32RGBA: ++ return kRGBA_8888_SkColorType; ++ // This can be extended with branches for specific RGB formats if new Apple ++ // uses new formats. ++ default: ++ [[unlikely]] throw std::runtime_error( ++ "CVPixelBuffer has unknown RGB format! " + ++ std::string(FourCC2Str(format))); ++ } ++} ++ ++GrBackendTexture SkiaCVPixelBufferUtils::RGB::getSkiaTextureForCVPixelBuffer( ++ CVPixelBufferRef pixelBuffer) { ++ return getSkiaTextureForCVPixelBufferPlane(pixelBuffer, /* planeIndex */ 0); ++} ++ ++// pragma MARK: CVPixelBuffer -> Skia Texture ++ ++GrBackendTexture SkiaCVPixelBufferUtils::getSkiaTextureForCVPixelBufferPlane( ++ CVPixelBufferRef pixelBuffer, size_t planeIndex) { ++ // 1. Get cache ++ CVMetalTextureCacheRef textureCache = getTextureCache(); ++ ++ // 2. Get MetalTexture from CMSampleBuffer ++ CVMetalTextureRef textureHolder; ++ size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex); ++ size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex); ++ MTLPixelFormat pixelFormat = ++ getMTLPixelFormatForCVPixelBufferPlane(pixelBuffer, planeIndex); ++ CVReturn result = CVMetalTextureCacheCreateTextureFromImage( ++ kCFAllocatorDefault, textureCache, pixelBuffer, nil, pixelFormat, width, ++ height, planeIndex, &textureHolder); ++ if (result != kCVReturnSuccess) [[unlikely]] { ++ throw std::runtime_error( ++ "Failed to create Metal Texture from CMSampleBuffer! Result: " + ++ std::to_string(result)); ++ } ++ ++ // 2. Unwrap the underlying MTLTexture ++ id mtlTexture = CVMetalTextureGetTexture(textureHolder); ++ if (mtlTexture == nil) [[unlikely]] { ++ throw std::runtime_error( ++ "Failed to get MTLTexture from CVMetalTextureRef!"); ++ } ++ ++ // 3. Wrap MTLTexture in Skia's GrBackendTexture ++ GrMtlTextureInfo textureInfo; ++ textureInfo.fTexture.retain((__bridge void *)mtlTexture); ++ GrBackendTexture texture = ++ GrBackendTexture((int)mtlTexture.width, (int)mtlTexture.height, ++ skgpu::Mipmapped::kNo, textureInfo); ++ CFRelease(textureHolder); ++ return texture; ++} ++ ++// pragma MARK: getTextureCache() ++ ++CVMetalTextureCacheRef SkiaCVPixelBufferUtils::getTextureCache() { ++ static thread_local CVMetalTextureCacheRef textureCache = nil; ++ if (textureCache == nil) { ++ // Create a new Texture Cache ++ auto result = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, ++ MTLCreateSystemDefaultDevice(), 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( ++ CVPixelBufferRef pixelBuffer, size_t planeIndex) { ++ size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex); ++ size_t bytesPerRow = ++ CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, planeIndex); ++ double bytesPerPixel = round(static_cast(bytesPerRow) / width); ++ if (bytesPerPixel == 1) { ++ return MTLPixelFormatR8Unorm; ++ } else if (bytesPerPixel == 2) { ++ return MTLPixelFormatRG8Unorm; ++ } else if (bytesPerPixel == 4) { ++ return MTLPixelFormatBGRA8Unorm; ++ } else [[unlikely]] { ++ throw std::runtime_error("Invalid bytes per row! Expected 1 (R), 2 (RG) or " ++ "4 (RGBA), but received " + ++ std::to_string(bytesPerPixel)); ++ } ++} +diff --git a/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.mm b/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.mm +index 0e95d2c..e62956d 100644 +--- a/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.mm ++++ b/node_modules/@shopify/react-native-skia/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.mm +@@ -1,6 +1,7 @@ + #import "RNSkLog.h" + +-#include "SkiaMetalSurfaceFactory.h" ++#import "SkiaCVPixelBufferUtils.h" ++#import "SkiaMetalSurfaceFactory.h" + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" +@@ -17,8 +18,6 @@ + + #pragma clang diagnostic pop + +-thread_local SkiaMetalContext ThreadContextHolder::ThreadSkiaMetalContext; +- + struct OffscreenRenderContext { + id texture; + +@@ -38,40 +37,59 @@ + } + }; + +-id SkiaMetalSurfaceFactory::device = MTLCreateSystemDefaultDevice(); +- +-bool SkiaMetalSurfaceFactory::createSkiaDirectContextIfNecessary( +- SkiaMetalContext *skiaMetalContext) { +- if (skiaMetalContext->skContext == nullptr) { +- skiaMetalContext->commandQueue = +- id(CFRetain((GrMTLHandle)[device newCommandQueue])); +- skiaMetalContext->skContext = GrDirectContext::MakeMetal( +- (__bridge void *)device, +- (__bridge void *)skiaMetalContext->commandQueue); +- if (skiaMetalContext->skContext == nullptr) { +- RNSkia::RNSkLogger::logToConsole("Couldn't create a Skia Metal Context"); +- return false; ++const SkiaMetalContext &SkiaMetalSurfaceFactory::getSkiaContext() { ++ // this key is just used as a pointer reference. ++ static const auto key = 1; ++ ++ void *state = dispatch_get_specific(&key); ++ if (state == nullptr) { ++ NSLog(@"Re-creating SkiaContext..."); ++ SkiaMetalContext *context = new SkiaMetalContext(); ++ context->device = MTLCreateSystemDefaultDevice(); ++ context->commandQueue = [context->device newCommandQueue]; ++ sk_sp skContext = ++ GrDirectContext::MakeMetal((__bridge void *)context->device, ++ (__bridge void *)context->commandQueue); ++ if (skContext == nullptr) { ++ throw std::runtime_error("Failed to create Metal Skia Context!"); + } ++ context->skContext = skContext; ++ ++ state = reinterpret_cast(context); ++ ++#pragma clang diagnostic push ++#pragma clang diagnostic ignored "-Wdeprecated-declarations" ++ // Instead of using thread_local, we store the current SkContext ++ // in the current dispatch queue's specific storage. ++ // The problem with thread_local is that Dispatch Queues on iOS ++ // may use multiple Threads, even when using serial (non-concurrent) ++ // attributes, which caused the SkContext to re-initialize ++ // even though it should remain the same - this caused flickering ++ // in NativeBuffer() APIs (e.g. VisionCamera or Video) ++ dispatch_queue_t currentQueue = dispatch_get_current_queue(); ++#pragma clang diagnostic pop ++ dispatch_queue_set_specific(currentQueue, &key, state, [](void *data) { ++ delete reinterpret_cast(data); ++ }); + } +- return true; ++ ++ SkiaMetalContext *currentContext = ++ reinterpret_cast(state); ++ return *currentContext; + } + + sk_sp + SkiaMetalSurfaceFactory::makeWindowedSurface(id texture, int width, + int height) { +- // Get render context for current thread +- if (!SkiaMetalSurfaceFactory::createSkiaDirectContextIfNecessary( +- &ThreadContextHolder::ThreadSkiaMetalContext)) { +- return nullptr; +- } + GrMtlTextureInfo fbInfo; + fbInfo.fTexture.retain((__bridge void *)texture); + + GrBackendRenderTarget backendRT(width, height, fbInfo); + ++ auto &context = getSkiaContext(); + auto skSurface = SkSurfaces::WrapBackendRenderTarget( +- ThreadContextHolder::ThreadSkiaMetalContext.skContext.get(), backendRT, +- kTopLeft_GrSurfaceOrigin, kBGRA_8888_SkColorType, nullptr, nullptr); ++ context.skContext.get(), backendRT, kTopLeft_GrSurfaceOrigin, ++ kBGRA_8888_SkColorType, nullptr, nullptr); + + if (skSurface == nullptr || skSurface->getCanvas() == nullptr) { + RNSkia::RNSkLogger::logToConsole( +@@ -83,13 +101,9 @@ + + sk_sp SkiaMetalSurfaceFactory::makeOffscreenSurface(int width, + int height) { +- if (!SkiaMetalSurfaceFactory::createSkiaDirectContextIfNecessary( +- &ThreadContextHolder::ThreadSkiaMetalContext)) { +- return nullptr; +- } +- auto ctx = new OffscreenRenderContext( +- device, ThreadContextHolder::ThreadSkiaMetalContext.skContext, +- ThreadContextHolder::ThreadSkiaMetalContext.commandQueue, width, height); ++ auto &context = getSkiaContext(); ++ auto ctx = new OffscreenRenderContext(context.device, context.skContext, ++ context.commandQueue, width, height); + + // Create a GrBackendTexture from the Metal texture + GrMtlTextureInfo info; +@@ -98,20 +112,49 @@ + + // Create a SkSurface from the GrBackendTexture + auto surface = SkSurfaces::WrapBackendTexture( +- ThreadContextHolder::ThreadSkiaMetalContext.skContext.get(), +- backendTexture, kTopLeft_GrSurfaceOrigin, 0, kBGRA_8888_SkColorType, +- nullptr, nullptr, ++ context.skContext.get(), backendTexture, kTopLeft_GrSurfaceOrigin, 0, ++ kBGRA_8888_SkColorType, nullptr, nullptr, + [](void *addr) { delete (OffscreenRenderContext *)addr; }, ctx); + + return surface; + } + +-sk_sp +-SkiaMetalSurfaceFactory::makeTextureFromImage(sk_sp image) { +- if (!SkiaMetalSurfaceFactory::createSkiaDirectContextIfNecessary( +- &ThreadContextHolder::ThreadSkiaMetalContext)) { +- throw std::runtime_error("Failed to create Skia Context for this Thread!"); ++sk_sp SkiaMetalSurfaceFactory::makeTextureFromCMSampleBuffer( ++ CMSampleBufferRef sampleBuffer) { ++ auto &context = getSkiaContext(); ++ ++ if (!CMSampleBufferIsValid(sampleBuffer)) [[unlikely]] { ++ throw std::runtime_error("The given CMSampleBuffer is not valid!"); ++ } ++ ++ CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); ++ ++ SkiaCVPixelBufferUtils::CVPixelBufferBaseFormat format = ++ SkiaCVPixelBufferUtils::getCVPixelBufferBaseFormat(pixelBuffer); ++ switch (format) { ++ case SkiaCVPixelBufferUtils::CVPixelBufferBaseFormat::rgb: { ++ // CVPixelBuffer is in any RGB format. ++ SkColorType colorType = ++ SkiaCVPixelBufferUtils::RGB::getCVPixelBufferColorType(pixelBuffer); ++ GrBackendTexture texture = ++ SkiaCVPixelBufferUtils::RGB::getSkiaTextureForCVPixelBuffer( ++ pixelBuffer); ++ return SkImages::AdoptTextureFrom(context.skContext.get(), texture, ++ kTopLeft_GrSurfaceOrigin, colorType, ++ kOpaque_SkAlphaType); ++ } ++ case SkiaCVPixelBufferUtils::CVPixelBufferBaseFormat::yuv: { ++ // CVPixelBuffer is in any YUV format, multi-plane ++ GrYUVABackendTextures textures = ++ SkiaCVPixelBufferUtils::YUV::getSkiaTextureForCVPixelBuffer( ++ pixelBuffer); ++ return SkImages::TextureFromYUVATextures(context.skContext.get(), textures); ++ } ++ default: ++ [[unlikely]] { ++ throw std::runtime_error("Failed to convert PlatformBuffer to SkImage - " ++ "PlatformBuffer has unsupported PixelFormat! " + ++ std::to_string(static_cast(format))); ++ } + } +- return SkImages::TextureFromImage( +- ThreadContextHolder::ThreadSkiaMetalContext.skContext.get(), image); + } diff --git a/package/example/yarn.lock b/package/example/yarn.lock index 0a77f2986e..d762be12c6 100644 --- a/package/example/yarn.lock +++ b/package/example/yarn.lock @@ -1969,6 +1969,11 @@ resolved "https://registry.yarnpkg.com/@webgpu/types/-/types-0.1.21.tgz#b181202daec30d66ccd67264de23814cfd176d3a" integrity sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow== +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -2174,6 +2179,11 @@ asynciterator.prototype@^1.0.0: dependencies: has-symbols "^1.0.3" +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -2436,7 +2446,7 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -ci-info@^3.2.0: +ci-info@^3.2.0, ci-info@^3.7.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== @@ -3291,6 +3301,13 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + flat-cache@^3.0.4: version "3.2.0" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" @@ -3341,6 +3358,16 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -3887,7 +3914,7 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== -is-wsl@^2.2.0: +is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -4112,6 +4139,16 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stable-stringify@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz#52d4361b47d49168bcc4e564189a42e5a7439454" + integrity sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg== + dependencies: + call-bind "^1.0.5" + isarray "^2.0.5" + jsonify "^0.0.1" + object-keys "^1.1.1" + json5@^2.1.1, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -4124,6 +4161,20 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== + "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.5" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" @@ -4151,6 +4202,13 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -4590,7 +4648,7 @@ metro@0.76.8: ws "^7.5.1" yargs "^17.6.2" -micromatch@^4.0.4: +micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -4871,6 +4929,14 @@ open@^6.2.0: dependencies: is-wsl "^1.1.0" +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + open@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" @@ -4908,6 +4974,11 @@ ora@^5.4.1: strip-ansi "^6.0.0" wcwidth "^1.0.1" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -4968,6 +5039,27 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +patch-package@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61" + integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^4.1.2" + ci-info "^3.7.0" + cross-spawn "^7.0.3" + find-yarn-workspace-root "^2.0.0" + fs-extra "^9.0.0" + json-stable-stringify "^1.0.2" + klaw-sync "^6.0.0" + minimist "^1.2.6" + open "^7.4.2" + rimraf "^2.6.3" + semver "^7.5.3" + slash "^2.0.0" + tmp "^0.0.33" + yaml "^2.2.2" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -5162,6 +5254,11 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-native-fast-tflite@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-native-fast-tflite/-/react-native-fast-tflite-1.2.0.tgz#8813d268402675c58d050adaa407c241aa2b6d41" + integrity sha512-1hpDXU+XDJ/fHtI7v0ThJj9/bn/utk7C7FLE2Fa60sJawHcsOAxdOlh92BJzhxpYHBMO9367Jlr5f2bKC1DZmA== + react-native-gesture-handler@^2.12.1: version "2.14.0" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.14.0.tgz#d6aec0d8b2e55c67557fd6107e828c0a1a248be8" @@ -5472,6 +5569,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -5560,6 +5664,13 @@ semver@^7.3.7, semver@^7.5.2: dependencies: lru-cache "^6.0.0" +semver@^7.5.3: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -5673,6 +5784,11 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -5942,6 +6058,13 @@ titleize@^3.0.0: resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -6103,6 +6226,11 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -6331,6 +6459,11 @@ yaml@^2.2.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== +yaml@^2.2.2: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.1.tgz#2e57e0b5e995292c25c75d2658f0664765210eed" + integrity sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg== + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"