金属视图(MTKView)绘制尺寸问题

7

我这里有一个MTKView,在摄像头输入的简单CIFilter下运行。这个很正常。

问题

在旧设备(例如iPhone 5、iPad Air)的自拍摄像头上,输入的画面被缩小了。 更新:当这种情况发生时,传递给MTKView的CMSampleBuffer大小会变小。 我猜每次更新中的纹理需要放大?


 import UIKit
 import MetalPerformanceShaders
 import MetalKit
 import AVFoundation

    final class MetalObject: NSObject, MTKViewDelegate {

        private var metalBufferView            : MTKView?
        private var metalDevice                = MTLCreateSystemDefaultDevice()
        private var metalCommandQueue          : MTLCommandQueue!
        private var metalSourceTexture         : MTLTexture?
        private var context                    : CIContext?
        private var filter                     : CIFilter?


        init(with frame: CGRect, filterType: Int, scaledUp: Bool) {
            super.init()

            self.metalCommandQueue = self.metalDevice!.makeCommandQueue()
            self.metalBufferView = MTKView(frame: frame, device: self.metalDevice)
            self.metalBufferView!.framebufferOnly = false
            self.metalBufferView!.isPaused = true

            self.metalBufferView!.contentScaleFactor = UIScreen.main.nativeScale
            self.metalBufferView!.delegate = self
            self.context = CIContext()


        }


        final func update (sampleBuffer: CMSampleBuffer) {

            var textureCache : CVMetalTextureCache?
            CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, self.metalDevice!,  nil, &textureCache)
            var cameraTexture: CVMetalTexture?

            guard
                let cameraTextureCache = textureCache,
                let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
                    return
            }

            let cameraTextureWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0)
            let cameraTextureHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0)
            CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                      cameraTextureCache,
                                                      pixelBuffer,
                                                      nil,
                                                      MTLPixelFormat.bgra8Unorm,
                                                      cameraTextureWidth,
                                                      cameraTextureHeight,
                                                      0,
                                                      &cameraTexture)

            if let cameraTexture = cameraTexture,
                let metalTexture = CVMetalTextureGetTexture(cameraTexture) {
                self.metalSourceTexture = metalTexture
                self.metalBufferView!.draw()
            }


        }

        //MARK: - Metal View Delegate
        final func draw(in view: MTKView) {

            guard let currentDrawable = self.metalBufferView!.currentDrawable,
                let sourceTexture = self.metalSourceTexture
                else {  return  }

            let commandBuffer = self.metalCommandQueue!.makeCommandBuffer()
            var inputImage = CIImage(mtlTexture: sourceTexture)!.applyingOrientation(self.orientationNumber)

            if self.showFilter {
                self.filter!.setValue(inputImage, forKey: kCIInputImageKey)
                inputImage = filter!.outputImage!
            }


            self.context!.render(inputImage, to: currentDrawable.texture, commandBuffer: commandBuffer, bounds: inputImage.extent, colorSpace: self.colorSpace!)

            commandBuffer.present(currentDrawable)
            commandBuffer.commit()

        }


        final func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {

        }
    }

观察结果

  • 仅发生在旧设备的自拍摄像头上
  • 新设备的自拍摄像头没有问题,当出现问题时,新内容被绘制在一个较小的区域(向左上方倾斜),而来自后置摄像头的旧内容仍然保留在新内容之外。
  • 约束和Metal视图的大小/位置都很好。
  • self.metalBufferView!.contentScaleFactor = UIScreen.main.nativeScale可以解决Plus设备上的奇怪缩放问题。

1
旧设备的前置摄像头分辨率可能较低,这种情况是否有可能? - Devin Lane
@DevinLane 确切无误! - Gizmodo
1
很好,那么只需将其绘制到您想要的任何显示边界上,它就会自动缩放。这就是渲染调用中的“bounds”参数。目前,您正在指定输入纹理的边界,但您需要指定目标(屏幕尺寸)纹理的边界。 - Devin Lane
你能否在下面的回答中详细说明一下这些内容?我对Metal还很陌生,有些方面感到困惑。此外,我可以给予悬赏奖金。 - Gizmodo
1个回答

3
似乎老设备上的前置摄像头(自拍)分辨率较低,因此如果您希望使用完整的宽度或高度,则需要将视频进行缩放。由于您已经在使用CIContext和Metal,因此您可以简单地指示渲染调用将图像绘制到任何您想要的矩形中。
在您的draw方法中,执行:
self.context!.render(inputImage,
                     to: currentDrawable.texture,
                     commandBuffer: commandBuffer,
                     bounds: inputImage.extent,
                     colorSpace: self.colorSpace!)
bounds参数是图像将被渲染的目标矩形。目前,您正在使用图像范围,这意味着图像不会被缩放。
要将视频放大,使用显示矩形。您可以直接使用metalBufferView.bounds,因为这将是您的显示视图的大小。最终你会得到...
 self.context!.render(inputImage,
                      to: currentDrawable.texture,
                      commandBuffer: commandBuffer,
                      bounds: self.metalBufferView.bounds,
                      colorSpace: self.colorSpace!)

如果图像和视图的宽高比不同(宽度/高度为宽高比),那么您将需要计算正确的尺寸,以保持图像的宽高比。为此,您最终会得到以下代码:
CGRect dest = self.metalBufferView.bounds;
CGSize imageSize = inputImage.extent.size;
CGSize viewSize = dest.size; 
double imageAspect = imageSize.width / imageSize.height;
double viewAspect = viewSize.width / viewSize.height;
if (imageAspect > viewAspect) {
    // the image is wider than the view, adjust height
    dest.size.height = 1/imageAspect * dest.size.width;
} else {
    // the image is taller than the view, adjust the width
    dest.size.width = imageAspect * dest.size.height;

    // center the tall image
    dest.origin.x = (viewSize.width - dest.size.width) / 2;
}

希望这对你有所帮助,如果有任何问题或需要澄清,请告诉我。

更改为self.metalBufferView.bounds后,视图在屏幕的1/4处绘制。即使我将尺寸加倍,它在普通相机上也可以正常绘制,但对于那些旧的自拍相机,它仍然像以前一样绘制得更小。 - Gizmodo
嗯,那么缓冲区视图边界、输入图像、当前可绘制纹理以及自拍相机规格所指示的尺寸是什么呢?除非其中一个或多个内容有误,否则所有信息都应该在那里以进行正确的缩放...也许可绘制纹理的尺寸与视图不同? - Devin Lane
1
啊,我明白了,currentDrawable的纹理大小与相机图像相同。这需要与视图的大小相同,这样当绘制相机图像时它将被正确缩放,当视图绘制纹理时,它将是屏幕的大小。很抱歉我第一次没有注意到这个问题! - Devin Lane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接