Skip to content

Commit

Permalink
fix(πŸ§‘πŸ»β€πŸ­): Metal NativeBuffer flickering issue (#2372)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy authored Apr 17, 2024
1 parent 676ffb5 commit 2f6ff85
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 25 deletions.
36 changes: 34 additions & 2 deletions package/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,38 @@
#import <include/gpu/GrBackendSurface.h>
#pragma clang diagnostic pop

/**
Holds a Metal Texture.
When the `TextureHolder` is destroyed, the underlying Metal Texture
is marked as invalid and might be overwritten by the Texture Cache,
so make sure to delete the `TextureHolder` only when the `SkImage`
has been deleted.
For example, use the `releaseProc` parameter in `BorrowImageFromTexture`
to delete the `TextureHolder`.
*/
class TextureHolder {
public:
/**
Create a new instance of TextureHolder which holds
the given `CVMetalTextureRef`.
The given `CVMetalTextureRef` is assumed to already be
retained with `CFRetain`, and will later be manually
released with `CFRelease`.
*/
explicit TextureHolder(CVMetalTextureRef texture);
~TextureHolder();

/**
Converts this Texture to a Skia GrBackendTexture.
*/
GrBackendTexture toGrBackendTexture();

private:
CVMetalTextureRef _texture;
};

class SkiaCVPixelBufferUtils {
public:
enum class CVPixelBufferBaseFormat { rgb };
Expand All @@ -37,13 +69,13 @@ class SkiaCVPixelBufferUtils {
/**
Gets a GPU-backed Skia Texture for the given RGB CVPixelBuffer.
*/
static GrBackendTexture
static TextureHolder *
getSkiaTextureForCVPixelBuffer(CVPixelBufferRef pixelBuffer);
};

private:
static CVMetalTextureCacheRef getTextureCache();
static GrBackendTexture
static TextureHolder *
getSkiaTextureForCVPixelBufferPlane(CVPixelBufferRef pixelBuffer,
size_t planeIndex);
static MTLPixelFormat
Expand Down
48 changes: 29 additions & 19 deletions package/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@
}
#endif

// pragma MARK: TextureHolder

TextureHolder::TextureHolder(CVMetalTextureRef texture) : _texture(texture) {}
TextureHolder::~TextureHolder() { CFRelease(_texture); }

GrBackendTexture TextureHolder::toGrBackendTexture() {
// Unwrap the underlying MTLTexture
id<MTLTexture> mtlTexture = CVMetalTextureGetTexture(_texture);
if (mtlTexture == nil) [[unlikely]] {
throw std::runtime_error(
"Failed to get MTLTexture from CVMetalTextureRef!");
}

// 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);
return texture;
}

// pragma MARK: Base

SkiaCVPixelBufferUtils::CVPixelBufferBaseFormat
Expand Down Expand Up @@ -67,54 +89,42 @@
}
}

GrBackendTexture SkiaCVPixelBufferUtils::RGB::getSkiaTextureForCVPixelBuffer(
TextureHolder *SkiaCVPixelBufferUtils::RGB::getSkiaTextureForCVPixelBuffer(
CVPixelBufferRef pixelBuffer) {
return getSkiaTextureForCVPixelBufferPlane(pixelBuffer, /* planeIndex */ 0);
}

// pragma MARK: CVPixelBuffer -> Skia Texture

GrBackendTexture SkiaCVPixelBufferUtils::getSkiaTextureForCVPixelBufferPlane(
TextureHolder *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);

CVMetalTextureRef textureHolder;
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> 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;
return new TextureHolder(textureHolder);
}

// pragma MARK: getTextureCache()

CVMetalTextureCacheRef SkiaCVPixelBufferUtils::getTextureCache() {
static thread_local CVMetalTextureCacheRef textureCache = nil;
static CVMetalTextureCacheRef textureCache = nil;
if (textureCache == nil) {
// Create a new Texture Cache
auto result = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil,
Expand Down
10 changes: 6 additions & 4 deletions package/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.mm
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,14 @@
// CVPixelBuffer is in any RGB format.
SkColorType colorType =
SkiaCVPixelBufferUtils::RGB::getCVPixelBufferColorType(pixelBuffer);
GrBackendTexture texture =
TextureHolder *texture =
SkiaCVPixelBufferUtils::RGB::getSkiaTextureForCVPixelBuffer(
pixelBuffer);
return SkImages::AdoptTextureFrom(context.skContext.get(), texture,
kTopLeft_GrSurfaceOrigin, colorType,
kOpaque_SkAlphaType);
return SkImages::BorrowTextureFrom(
context.skContext.get(), texture->toGrBackendTexture(),
kTopLeft_GrSurfaceOrigin, colorType, kOpaque_SkAlphaType, nullptr,
[](void *texture) { delete (TextureHolder *)texture; },
(void *)texture);
}
default:
[[unlikely]] {
Expand Down

0 comments on commit 2f6ff85

Please sign in to comment.