【问题标题】:SceneKit: too much memory persistingSceneKit:太多的内存持续存在
【发布时间】:2016-06-11 18:12:10
【问题描述】:

我在这里没有想法,SceneKit 正在积累内存,而我才刚刚开始。我正在显示SNCNodes,它们存储在数组中,因此我可以分离分子的组件以进行动画处理。这些树模拟分子,我最终可能会展示 50 个,比如每个“章节”一个。问题是当我转到另一章时,前几章中的分子会保留在内存中。

分子节点是子节点的树。大约一半的节点是用于定向的空容器。否则,几何图形为SCNPrimitives(球体、胶囊和圆柱体)。每个几何体都有一个镜面反射和一个漫反射material,由一个UIColor 组成,不使用任何纹理。

当应用首次启动时,这些分子由代码构建并归档到字典中。然后,在随后的引导中,归档字典被读入本地字典以供 VC 使用。 (为简洁起见,我删除了这篇文章中的安全功能。)

moleculeDictionary = Molecules.readFile() as! [String: [SCNNode]]

当一章想要显示一个分子时,它会调用一个特定函数,该函数将给定分子所需的组件从本地字典加载到本地 SCNNode 属性中。

// node stores (reuseable)
var atomsNode_1 = SCNNode() 
var atomsNode_2 = SCNNode()
        . . .

func lysozyme() {   // called by a chapter to display this molecule 
        . . .
    components = moleculeDictionary["lysozyme"]

    atomsNode_1 = components[0]         // protein w/CPK color
    baseNode.addChildNode(atomsNode_1)
    atomsNode_2 = components[2]         // NAG
    baseNode.addChildNode(atomsNode_2)
        . . .
 }

在显示下一个分子之前,我调用了“清理”函数:

atomsNode_1.removeFromParentNode()
atomsNode_2.removeFromParentNode()
        . . .

当我在仪器中进行调查时,大部分膨胀的内存是 C3DMeshCreateFromProfile 调用的 32 kB 块和 C3DMeshCreateCopyWithInterleavedSources 调用的 80 kB 块。

我也有需要追踪的泄漏,这些泄漏可追溯到存档的NSKeyedUnarchiver 解码。所以我也需要处理这些,但它们只是累积每个分子调用的内存使用的一小部分。

如果我返回到以前查看过的分子,内存使用量不会进一步增加,它会累积并持续存在。

我尝试将 atomsNode_1 及其亲属声明为可选项,然后在清理时将它们设置为 nil。没有帮助。我试过了,在清理功能中,

atomsNode_1.enumerateChildNodesUsingBlock({
    node, stop in
    node.removeFromParentNode()
})

嗯,内存恢复了,但节点现在似乎从加载的字典中永久消失了。该死的引用类型!

所以也许我需要一种方法来归档[SCNNode] 数组,以便单独取消归档和检索它们。在这种情况下,我会在完成后将它们从内存中清除,并在重新访问该分子时从存档中重新加载。但我还不知道如何做这些。在投入更多时间感到沮丧之前,我会很感激 cmets。

【问题讨论】:

    标签: memory scenekit


    【解决方案1】:

    球体、胶囊和圆柱体都有相当密集的网格。你需要所有这些细节吗?尝试减少各种段数属性(segmentCountradialSegmentCount 等)。作为一个快速测试,将SCNPyramid 替换为您的所有原始类型(即具有最低向量计数的原始类型)。如果这是一个因素,您应该会看到内存使用量的显着减少(它看起来很难看,但会立即反馈您是否处于可用轨道上)。可以用长的SCNBox代替圆柱吗?

    另一个优化步骤是使用SCNLevelOfDetail 来允许在对象很远时替代低顶点数几何体。这将比简单地统一减少段数更有效,但如果您有时需要更多详细信息,这会有所回报。

    不要自己在数组中管理组件,而是使用节点层次结构来做到这一点。将每个分子或分子的可动画片段创建为SCNNodes 的树。给它一个名字。制作flattenedClone。现在存档。需要时从存档中读取节点树;不用担心节点数组。

    考虑编写两个程序。一个是你的 iOS 程序,它操纵/显示分子。另一个是 Mac(或 iOS?)程序,可以生成分子节点树并将它们存档。这将为您提供一堆 SCNNode 树存档,您可以将它们作为资源嵌入到您的显示程序中,而无需即时生成。

    scene kit memory management using swift 的答案指出需要消除“纹理”(materialsfirstMaterial 属性?)以释放节点。似乎值得一看,虽然因为你只是使用 UIColor 我怀疑这是一个因素。

    这是一个创建复合节点并将其归档的示例。在实际代码中,您会将归档与创建分开。还要注意使用细长的盒子来模拟一条线。尝试将倒角半径设为 0!

    extension SCNNode {
    
    public class func gizmoNode(axisLength: CGFloat) -> SCNNode {
        let offset = CGFloat(axisLength/2.0)
        let axisSide = CGFloat(0.1)
        let chamferRadius = CGFloat(axisSide)
    
        let xBox = SCNBox(width: axisLength, height: axisSide, length: axisSide, chamferRadius: chamferRadius)
        xBox.firstMaterial?.diffuse.contents = NSColor.redColor()
        let yBox = SCNBox(width: axisSide, height: axisLength, length: axisSide, chamferRadius: chamferRadius)
        yBox.firstMaterial?.diffuse.contents = NSColor.greenColor()
        let zBox = SCNBox(width: axisSide, height: axisSide, length: axisLength, chamferRadius: chamferRadius)
        zBox.firstMaterial?.diffuse.contents = NSColor.blueColor()
        let xNode = SCNNode(geometry: xBox)
        xNode.name = "X axis"
        let yNode = SCNNode(geometry: yBox)
        yNode.name = "Y axis"
        let zNode = SCNNode(geometry: zBox)
        zNode.name = "Z axis"
    
        let result = SCNNode()
        result.name = "Gizmo"
        result.addChildNode(xNode)
        result.addChildNode(yNode)
        result.addChildNode(zNode)
        xNode.position.x = offset
        yNode.position.y = offset
        zNode.position.z = offset
    
        let data = NSKeyedArchiver.archivedDataWithRootObject(result)
        let filename = "gizmo"
    
        // Save data to file
        let DocumentDirURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
    
        // made the extension "plist" so you can easily inspect it by opening in Finder. Could just as well be "scn" or "node"
        // ".scn" can be opened in the Xcode Scene Editor
        let fileURL = DocumentDirURL.URLByAppendingPathComponent(filename).URLByAppendingPathExtension("plist")
        print("FilePath:", fileURL.path)
    
        if (!data.writeToURL(fileURL, atomically: true)) {
            print("oops")
        }
        return result
    }
    }   
    

    【讨论】:

    • 哈尔,感谢您的反馈。盒子不会削减它,对于分子显示器来说,它看起来非常不正统。在某些模型上,我可能会减少多边形数量,但是对于尽可能多的模型,这在 lon 运行中不会很有效。我真的需要防止模型积累。我认为一次阅读一本是潜在的解决方案,但到目前为止,我只设法将它们全部保存在一本字典中。节点位于数组中还有动画原因:它不需要更多内存(我已经测试过)加上动画比枚举快。
    • 所以,我真的很喜欢您关于“作为资源嵌入的一堆节点树......”的想法。事实上,我已经在这里发布了 2 1/2 的问题,关于如何做到这一点,但没有收到任何回复!欢迎提出建议!再次感谢,伯恩
    • 用一些示例代码编辑。您可能正在为 SceneKit 的内存循环发痒; stackoverflow.com/questions/32997711/… 可能是同一回事。作为一种解决方法,您可以尝试销毁/重新创建 SCNScene 或 SCNView。
    • 非常感谢。可能需要几天时间才能做到这一点。我已经看到我已经限制自己认为我必须将所有图表保存到一个文件中。我还将测试擦除场景或视图。还考虑另存为 .scn 而不是节点。
    • 仍在处理内存问题。但是:你已经解决了我的归档问题!请将上面代码的最后一部分复制到下面的链接帖子中,以便我标记为已回答。谢谢! stackoverflow.com/questions/35633854/…
    【解决方案2】:

    在我的应用程序中,我确实也经历过 SceneKit 的大量内存膨胀,与您在 Instruments 中的内存块相似(C3DGenericSourceCreateDeserializedDataWithAccessorsC3DMeshSourceCreateMutable 等)。我发现在让 Swift 取消初始化它们之前将 SCNNode 对象上的 geometry 属性设置为 nil 解决了它。

    在您的情况下,在您的清理功能中,执行以下操作:

    atomsNode_1.removeFromParentNode()
    atomsNode_1.geometry = nil
    atomsNode_2.removeFromParentNode()
    atomsNode_2.geometry = nil
    

    另一个如何实施清洁的例子:

    class ViewController: UIViewController {
        @IBOutlet weak var sceneView: SCNView!
        var scene: SCNScene!
    
        // ...
    
        override func viewDidLoad() {
            super.viewDidLoad()
            scene = SCNScene()
            sceneView.scene = scene
    
            // ...
        }
    
        deinit {
            scene.rootNode.cleanup()
        }
    
        // ...
    }
    
    extension SCNNode {
        func cleanup() {
            for child in childNodes {
                child.cleanup()
            }
            geometry = nil
        }
    }
    

    如果这不起作用,您可以通过将其纹理设置为nil 来获得更好的成功,正如scene kit memory management using swift 上所报告的那样。

    【讨论】:

    • 谢谢!已经有一段时间了,我不确定所有有帮助的改进。但到目前为止,最大的因素是将我的模型节点树存档为资源,然后根据需要调用。感谢 Hal Mueller 在另一个帖子中的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-13
    • 2015-11-08
    相关资源
    最近更新 更多