首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >从MTLTexture生成CGImage时内存泄漏(Swift、macOS)

从MTLTexture生成CGImage时内存泄漏(Swift、macOS)
EN

Stack Overflow用户
提问于 2018-08-21 08:44:32
回答 2查看 962关注 0票数 1

我有一个金属应用程序,我正在尝试将帧导出到快速电影。我正在渲染超高分辨率的帧,然后在写之前缩小它们,以便消除场景的锯齿。

为了缩放它,我将高分辨率纹理转换为CGImage,然后调整图像大小并写出较小的版本。我在网上找到了这个用于将MTLTexture转换为CGImage的扩展:

代码语言:javascript
复制
extension MTLTexture {

func bytes() -> UnsafeMutableRawPointer {
    let width = self.width
    let height   = self.height
    let rowBytes = self.width * 4
    let p = malloc(width * height * 4)
    
    self.getBytes(p!, bytesPerRow: rowBytes, from: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0)
    
    return p!
}

func toImage() -> CGImage? {
    let p = bytes()
    
    let pColorSpace = CGColorSpaceCreateDeviceRGB()
    
    let rawBitmapInfo = CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue // noneSkipFirst
    let bitmapInfo:CGBitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo)
    
    let size = self.width * self.height * 4
    let rowBytes = self.width * 4
    
    let releaseMaskImagePixelData: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
        // https://developer.apple.com/reference/coregraphics/cgdataproviderreleasedatacallback
        // N.B. 'CGDataProviderRelease' is unavailable: Core Foundation objects are automatically memory managed
        return
    }
    if let provider = CGDataProvider(dataInfo: nil, data: p, size: size, releaseData: releaseMaskImagePixelData) {

        let cgImageRef = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes, space: pColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent)!
        p.deallocate() //this fixes the memory leak
        return cgImageRef
    }
    p.deallocate() //this fixes the memory leak
    return nil
}

}  // end extension

我不是很肯定,但似乎这个函数中的某些东西导致了内存泄漏--每一帧它都会占用巨大的纹理/ cgimage中的内存量,而不是释放它。

CGDataProvider初始化接受'releaseData‘回调参数,但我的印象是不再需要它。

我还有一个调整CGImage大小的扩展--这也可能会导致泄漏,我不知道。但是,我可以注释掉框架的大小调整和写入,内存泄漏仍然会累积,所以在我看来,转换为CGImage是主要问题。

代码语言:javascript
复制
extension CGImage {

func resize(_ scale:Float) -> CGImage? {
   
    let imageWidth = Float(width)
    let imageHeight = Float(height)
  
    let w = Int(imageWidth * scale)
    let h = Int(imageHeight * scale)
    
    guard let colorSpace = colorSpace else { return nil }
    guard let context = CGContext(data: nil, width: w, height: h, bitsPerComponent: bitsPerComponent, bytesPerRow: Int(Float(bytesPerRow)*scale), space: colorSpace, bitmapInfo: alphaInfo.rawValue) else { return nil }
    
    // draw image to context (resizing it)
    context.interpolationQuality = .high
    let r = CGRect(x: 0, y: 0, width: w, height: h)
    context.clear(r)
    context.draw(self, in:r)
    
    // extract resulting image from context
    return context.makeImage()
    
}
}

最后,这里是我在导出时调用的每个帧的大函数。我很抱歉篇幅太长,但提供太多信息可能比提供太少信息更好。因此,基本上在渲染开始时,我会分配一个巨大的MTL纹理('exportTextureBig'),即正常屏幕的大小乘以每个方向上的'zoom_subvisions‘。我以块的形式呈现场景,网格上的每个点一个块,并通过使用blitCommandEncoder.copy()将每个小块复制到大纹理上来组装大帧。填充完整个框架后,我尝试从它创建一个CGImage,将其缩小到另一个CGImage,然后将其写出来。

我在导出时调用每一帧的commandBuffer.waitUntilCompleted() --希望避免渲染器保留它仍在使用的纹理。

代码语言:javascript
复制
func exportFrame2(_ commandBuffer:MTLCommandBuffer, _ texture:MTLTexture)  {  // texture is the offscreen render target for the screen-size chunks
    
    if zoom_index < zoom_subdivisions*zoom_subdivisions {  // copy screen-size chunk to large texture
        
        if let blitCommandEncoder = commandBuffer.makeBlitCommandEncoder() {
       
            let dx = Int(BigRender.globals_L.displaySize.x) * (zoom_index%zoom_subdivisions)
            let dy = Int(BigRender.globals_L.displaySize.y) * (zoom_index/zoom_subdivisions)
            blitCommandEncoder.copy(from:texture,
                                    sourceSlice: 0,
                                    sourceLevel: 0,
                                    sourceOrigin: MTLOrigin(x:0,y:0,z:0),
                                    sourceSize: MTLSize(width:Int(BigRender.globals_L.displaySize.x),height:Int(BigRender.globals_L.displaySize.y), depth:1),
                                    to:BigVideoWriter!.exportTextureBig!,
                                    destinationSlice: 0,
                                    destinationLevel: 0,
                                    destinationOrigin: MTLOrigin(x:dx,y:dy,z:0))
            
            blitCommandEncoder.synchronize(resource: BigVideoWriter!.exportTextureBig!)
            blitCommandEncoder.endEncoding()
        }
        
    }
    
    
    commandBuffer.commit()
    commandBuffer.waitUntilCompleted() // do this instead
    
    // is big frame complete?
    if (zoom_index == zoom_subdivisions*zoom_subdivisions-1) {
        
        // shrink the big texture here
        
        if let cgImage = self.exportTextureBig!.toImage() {  // memory leak here?
            
            // this can be commented out and memory leak still happens
            if let smallImage = cgImage.resize(1.0/Float(zoom_subdivisions)) {
                writeFrame(nil, smallImage)
            }
            
        }
    
    }
   
}

除了巨大的内存泄漏之外,所有这些都是有效的。我能做些什么让它每一帧都释放cgImage数据吗?为什么它会一直持有它?

非常感谢您的建议!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-08-21 12:51:40

我认为您误解了CGDataProviderReleaseDataCallbackCGDataProviderRelease()不可用的问题。

CGDataProviderRelease() (在C中)用于释放CGDataProvider对象本身。但这与您在创建CGDataProvider时提供给它的字节缓冲区不是一回事。

在Swift中,CGDataProvider对象的生存期是为您管理的,但这无助于释放字节缓冲区。

理想情况下,CGDataProvider能够自动管理字节缓冲区的生命周期,但它不能。CGDataProvider不知道如何释放字节缓冲区,因为它不知道它是如何分配的。这就是为什么你必须提供一个回调,它可以用来释放它。您实际上是在提供有关如何释放字节缓冲区的知识。

由于您使用malloc()来分配字节缓冲区,因此您的回调需要对其执行free()操作。

也就是说,使用CFMutableData比使用UnsafeMutableRawPointer要好得多。然后,使用CGDataProvider(data:)创建数据提供程序。在这种情况下,将为您管理所有内存。

票数 2
EN

Stack Overflow用户

发布于 2021-01-31 00:26:08

我使用的是非常相似的代码,一旦我添加了解除分配P的代码,这个问题就解决了:

代码语言:javascript
复制
    func toImage() -> CGImage? {
    let p = bytes()
    
    let pColorSpace = CGColorSpaceCreateDeviceRGB()
    
    let rawBitmapInfo = CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue // noneSkipFirst
    let bitmapInfo:CGBitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo)
    
    let size = self.width * self.height * 4
    let rowBytes = self.width * 4
    
    let releaseMaskImagePixelData: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
        // https://developer.apple.com/reference/coregraphics/cgdataproviderreleasedatacallback
        // N.B. 'CGDataProviderRelease' is unavailable: Core Foundation objects are automatically memory managed
        return
    }
    if let provider = CGDataProvider(dataInfo: nil, data: p, size: size, releaseData: releaseMaskImagePixelData) {

        let cgImageRef = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes, space: pColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent)!
        p.deallocate() //this fixes the memory leak
        return cgImageRef
    }
    p.deallocate() //this fixes the memory leak, but the data provider is no longer available (you just deallocated it's backing store)
    return nil
}

在任何需要快速使用CGImage的地方

代码语言:javascript
复制
autoreleasepool {
let lastDrawableDisplayed = self.metalView?.currentDrawable?.texture
let cgImage = lastDrawableDisplayed?.toImage() // your code to convert drawable to CGImage
// do work with cgImage
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51940166

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档