blob: 859022bc0e38129931ad525fa0d690e3232a077c [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/ios/ios_external_texture_metal.h"
#include "flutter/fml/logging.h"
#include "third_party/skia/include/core/SkYUVAIndex.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "third_party/skia/include/gpu/mtl/GrMtlTypes.h"
namespace flutter {
IOSExternalTextureMetal::IOSExternalTextureMetal(
int64_t texture_id,
fml::CFRef<CVMetalTextureCacheRef> texture_cache,
fml::scoped_nsobject<NSObject<FlutterTexture>> external_texture)
: Texture(texture_id),
texture_cache_(std::move(texture_cache)),
external_texture_(std::move(external_texture)) {
FML_DCHECK(texture_cache_);
FML_DCHECK(external_texture_);
}
IOSExternalTextureMetal::~IOSExternalTextureMetal() = default;
void IOSExternalTextureMetal::Paint(SkCanvas& canvas,
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
SkFilterQuality filter_quality) {
const bool needs_updated_texture = (!freeze && texture_frame_available_) || !external_image_;
if (needs_updated_texture) {
auto pixel_buffer = fml::CFRef<CVPixelBufferRef>([external_texture_ copyPixelBuffer]);
if (!pixel_buffer) {
pixel_buffer = std::move(last_pixel_buffer_);
} else {
pixel_format_ = CVPixelBufferGetPixelFormatType(pixel_buffer);
}
// If the application told us there was a texture frame available but did not provide one when
// asked for it, reuse the previous texture but make sure to ask again the next time around.
if (auto wrapped_texture = WrapExternalPixelBuffer(pixel_buffer, context)) {
external_image_ = wrapped_texture;
texture_frame_available_ = false;
last_pixel_buffer_ = std::move(pixel_buffer);
}
}
if (external_image_) {
SkPaint paint;
paint.setFilterQuality(filter_quality);
canvas.drawImageRect(external_image_, // image
external_image_->bounds(), // source rect
bounds, // destination rect
&paint, // paint
SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint // constraint
);
}
}
sk_sp<SkImage> IOSExternalTextureMetal::WrapExternalPixelBuffer(
fml::CFRef<CVPixelBufferRef> pixel_buffer,
GrDirectContext* context) const {
if (!pixel_buffer) {
return nullptr;
}
sk_sp<SkImage> image = nullptr;
if (pixel_format_ == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ||
pixel_format_ == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
image = WrapNV12ExternalPixelBuffer(pixel_buffer, context);
} else {
image = WrapRGBAExternalPixelBuffer(pixel_buffer, context);
}
if (!image) {
FML_DLOG(ERROR) << "Could not wrap Metal texture as a Skia image.";
}
return image;
}
sk_sp<SkImage> IOSExternalTextureMetal::WrapNV12ExternalPixelBuffer(
fml::CFRef<CVPixelBufferRef> pixel_buffer,
GrDirectContext* context) const {
auto texture_size =
SkISize::Make(CVPixelBufferGetWidth(pixel_buffer), CVPixelBufferGetHeight(pixel_buffer));
CVMetalTextureRef y_metal_texture_raw = nullptr;
{
auto cv_return =
CVMetalTextureCacheCreateTextureFromImage(/*allocator=*/kCFAllocatorDefault,
/*textureCache=*/texture_cache_,
/*sourceImage=*/pixel_buffer,
/*textureAttributes=*/nullptr,
/*pixelFormat=*/MTLPixelFormatR8Unorm,
/*width=*/texture_size.width(),
/*height=*/texture_size.height(),
/*planeIndex=*/0u,
/*texture=*/&y_metal_texture_raw);
if (cv_return != kCVReturnSuccess) {
FML_DLOG(ERROR) << "Could not create Metal texture from pixel buffer: CVReturn " << cv_return;
return nullptr;
}
}
CVMetalTextureRef uv_metal_texture_raw = nullptr;
{
auto cv_return =
CVMetalTextureCacheCreateTextureFromImage(/*allocator=*/kCFAllocatorDefault,
/*textureCache=*/texture_cache_,
/*sourceImage=*/pixel_buffer,
/*textureAttributes=*/nullptr,
/*pixelFormat=*/MTLPixelFormatRG8Unorm,
/*width=*/texture_size.width() / 2,
/*height=*/texture_size.height() / 2,
/*planeIndex=*/1u,
/*texture=*/&uv_metal_texture_raw);
if (cv_return != kCVReturnSuccess) {
FML_DLOG(ERROR) << "Could not create Metal texture from pixel buffer: CVReturn " << cv_return;
return nullptr;
}
}
fml::CFRef<CVMetalTextureRef> y_metal_texture(y_metal_texture_raw);
GrMtlTextureInfo y_skia_texture_info;
y_skia_texture_info.fTexture = sk_cf_obj<const void*>{
[reinterpret_cast<NSObject*>(CVMetalTextureGetTexture(y_metal_texture)) retain]};
GrBackendTexture y_skia_backend_texture(/*width=*/texture_size.width(),
/*height=*/texture_size.height(),
/*mipMapped=*/GrMipMapped ::kNo,
/*textureInfo=*/y_skia_texture_info);
fml::CFRef<CVMetalTextureRef> uv_metal_texture(uv_metal_texture_raw);
GrMtlTextureInfo uv_skia_texture_info;
uv_skia_texture_info.fTexture = sk_cf_obj<const void*>{
[reinterpret_cast<NSObject*>(CVMetalTextureGetTexture(uv_metal_texture)) retain]};
GrBackendTexture uv_skia_backend_texture(/*width=*/texture_size.width(),
/*height=*/texture_size.height(),
/*mipMapped=*/GrMipMapped ::kNo,
/*textureInfo=*/uv_skia_texture_info);
GrBackendTexture nv12TextureHandles[] = {y_skia_backend_texture, uv_skia_backend_texture};
SkYUVAIndex yuvaIndices[4] = {
SkYUVAIndex{0, SkColorChannel::kR}, // Read Y data from the red channel of the first texture
SkYUVAIndex{1, SkColorChannel::kR}, // Read U data from the red channel of the second texture
SkYUVAIndex{1,
SkColorChannel::kG}, // Read V data from the green channel of the second texture
SkYUVAIndex{-1, SkColorChannel::kA}}; //-1 means to omit the alpha data of YUVA
struct ImageCaptures {
fml::CFRef<CVPixelBufferRef> buffer;
fml::CFRef<CVMetalTextureRef> y_texture;
fml::CFRef<CVMetalTextureRef> uv_texture;
};
auto captures = std::make_unique<ImageCaptures>();
captures->buffer = std::move(pixel_buffer);
captures->y_texture = std::move(y_metal_texture);
captures->uv_texture = std::move(uv_metal_texture);
SkImage::TextureReleaseProc release_proc = [](SkImage::ReleaseContext release_context) {
auto captures = reinterpret_cast<ImageCaptures*>(release_context);
delete captures;
};
sk_sp<SkImage> image = SkImage::MakeFromYUVATextures(
context, kRec601_SkYUVColorSpace, nv12TextureHandles, yuvaIndices, texture_size,
kTopLeft_GrSurfaceOrigin, /*imageColorSpace=*/nullptr, release_proc, captures.release());
return image;
}
sk_sp<SkImage> IOSExternalTextureMetal::WrapRGBAExternalPixelBuffer(
fml::CFRef<CVPixelBufferRef> pixel_buffer,
GrDirectContext* context) const {
auto texture_size =
SkISize::Make(CVPixelBufferGetWidth(pixel_buffer), CVPixelBufferGetHeight(pixel_buffer));
CVMetalTextureRef metal_texture_raw = nullptr;
auto cv_return =
CVMetalTextureCacheCreateTextureFromImage(/*allocator=*/kCFAllocatorDefault,
/*textureCache=*/texture_cache_,
/*sourceImage=*/pixel_buffer,
/*textureAttributes=*/nullptr,
/*pixelFormat=*/MTLPixelFormatBGRA8Unorm,
/*width=*/texture_size.width(),
/*height=*/texture_size.height(),
/*planeIndex=*/0u,
/*texture=*/&metal_texture_raw);
if (cv_return != kCVReturnSuccess) {
FML_DLOG(ERROR) << "Could not create Metal texture from pixel buffer: CVReturn " << cv_return;
return nullptr;
}
fml::CFRef<CVMetalTextureRef> metal_texture(metal_texture_raw);
GrMtlTextureInfo skia_texture_info;
skia_texture_info.fTexture = sk_cf_obj<const void*>{
[reinterpret_cast<NSObject*>(CVMetalTextureGetTexture(metal_texture)) retain]};
GrBackendTexture skia_backend_texture(/*width=*/texture_size.width(),
/*height=*/texture_size.height(),
/*mipMapped=*/GrMipMapped ::kNo,
/*textureInfo=*/skia_texture_info);
struct ImageCaptures {
fml::CFRef<CVPixelBufferRef> buffer;
fml::CFRef<CVMetalTextureRef> texture;
};
auto captures = std::make_unique<ImageCaptures>();
captures->buffer = std::move(pixel_buffer);
captures->texture = std::move(metal_texture);
SkImage::TextureReleaseProc release_proc = [](SkImage::ReleaseContext release_context) {
auto captures = reinterpret_cast<ImageCaptures*>(release_context);
delete captures;
};
auto image =
SkImage::MakeFromTexture(context, skia_backend_texture, kTopLeft_GrSurfaceOrigin,
kBGRA_8888_SkColorType, kPremul_SkAlphaType,
/*imageColorSpace=*/nullptr, release_proc, captures.release()
);
return image;
}
void IOSExternalTextureMetal::OnGrContextCreated() {
// External images in this backend have no thread affinity and are not tied to the context in any
// way. Instead, they are tied to the Metal device which is associated with the cache already and
// is consistent throughout the shell run.
}
void IOSExternalTextureMetal::OnGrContextDestroyed() {
// The image must be reset because it is tied to the onscreen context. But the pixel buffer that
// created the image is still around. In case of context reacquisition, that last pixel
// buffer will be used to materialize the image in case the application fails to provide a new
// one.
external_image_.reset();
CVMetalTextureCacheFlush(texture_cache_, // cache
0 // options (must be zero)
);
}
void IOSExternalTextureMetal::MarkNewFrameAvailable() {
texture_frame_available_ = true;
}
void IOSExternalTextureMetal::OnTextureUnregistered() {
if ([external_texture_ respondsToSelector:@selector(onTextureUnregistered:)]) {
[external_texture_ onTextureUnregistered:external_texture_];
}
}
} // namespace flutter