2016-10-28 11 views
5

Моих SCNView используют металл в качестве рендеринга API, и я хотел бы знать, если есть способ, чтобы захватить обработанную сцену как MTLTexture без использования отдельного SCNRenderer? Производительность падает, когда я пытаюсь как отобразить сцену через SCNView и повторный рендеринг сцены закадровый к MTLTexture через SCNRenderer (я пытаюсь захватить выходной сигнал для каждого кадра).SceneKit - Получить обработанную сцену из SCNView как MTLTexture без использования отдельного SCNRenderer

SCNView дает мне доступ к MTLDevice, MTLRenderCommandEncoder и MTLCommandQueue, что он использует, но не к нижележащему MTLRenderPassDescriptor, что мне нужно было для того, чтобы получить MTLTexture (через renderPassDescriptor.colorAttachments[0].texture)

Некоторые альтернативы, которые я пытался, пытались использовать SCNView.snapshot(), чтобы получить UIImage и преобразовать его, но производительность была еще хуже.

+0

Основываясь на том, что он говорит здесь, https://developer.apple.com/reference/metal/mtlrenderpassattachdesdescriptor, вам необходимо создать новый MTLRenderPassDescriptor, получить MTLRenderPassAttachmentDescriptor и установить MTLTexture в качестве своей цели рендеринга. Учитывая то, что у вас есть, например MTLRenderCommandEncoder, кажется, что они сделали доступным для вас рисование, а не захватить буфер. Если вы можете создать свой собственный MTLRenderPassDescriptor и дать это SCNView, вы можете установить цель рендеринга. –

+0

О, похоже, вы можете установить свой собственный MTLRenderPassDescriptor с помощью SCNRenderer https://developer.apple.com/reference/scenekit/scnrenderer. Это, вероятно, то, что вам нужно сделать. –

+0

Настройка SCNRenderer заставляет его повторно отображать сцену (так как она находится в собственном цикле рендеринга), что приводит к снижению производительности. Я надеялся просто захватить базовую текстуру из уже визуализированной сцены (предполагая, что SCNView отображает один, что, похоже, имеет место, поскольку в качестве слоя поддержки для представления используется CAMetalLayer). –

ответ

0

** Внимание! Это может быть не подходящий метод для App Store. Но он работает.

Шаг 1: Поменяйте метод nextDrawable CAMetalLayer на новый, используя swizzling. Сохраните CAMetalDrawable для каждого цикла визуализации.

extension CAMetalLayer { 
    public static func setupSwizzling() { 
    struct Static { 
     static var token: dispatch_once_t = 0 
    } 

    dispatch_once(&Static.token) { 
     let copiedOriginalSelector = #selector(CAMetalLayer.orginalNextDrawable) 
     let originalSelector = #selector(CAMetalLayer.nextDrawable) 
     let swizzledSelector = #selector(CAMetalLayer.newNextDrawable) 

     let copiedOriginalMethod = class_getInstanceMethod(self, copiedOriginalSelector) 
     let originalMethod = class_getInstanceMethod(self, originalSelector) 
     let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) 

     let oldImp = method_getImplementation(originalMethod) 
     method_setImplementation(copiedOriginalMethod, oldImp) 
     method_exchangeImplementations(originalMethod, swizzledMethod) 
    } 
    } 


    func newNextDrawable() -> CAMetalDrawable? { 
    let drawable = orginalNextDrawable() 
    // Save the drawable to any where you want 
    AppManager.sharedInstance.currentSceneDrawable = drawable 
    return drawable 
    } 

    func orginalNextDrawable() -> CAMetalDrawable? { 
    // This is just a placeholder. Implementation will be replaced with nextDrawable. 
    return nil 
    } 
} 

Шаг 2: Настройте swizzling в AppDelegate: didFinishLaunchingWithOptions

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 
    CAMetalLayer.setupSwizzling() 
    return true 
} 

Шаг 3: Отключить framebufferOnly для CAMetalLayer вашего в SCNView (в Чтобы позвонить GetBytes для MTLTexture)

if let metalLayer = scnView.layer as? CAMetalLayer { 
    metalLayer.framebufferOnly = false 
} 

Шаг 4: В своем делегате SCNView (SCNSceneRendererDelegate), играйте с t exture

func renderer(renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: NSTimeInterval) { 
    if let texture = AppManager.sharedInstance.currentSceneDrawable?.texture where !texture.framebufferOnly { 
     AppManager.sharedInstance.currentSceneDrawable = nil 
     // Play with the texture 
    } 
} 

Шаг 5 (Дополнительно): Вы, возможно, потребуется подтвердить рисуем на CAMetalLayer вы получаете в вашей цели. (Если больше, то один CAMetalLayer в то же время)

+0

Я даже не думал об этом! Очень умно! Спасибо :) –

+0

@KevinBui Вы попробовали это в своем приложении? Если да, были ли какие-либо проблемы с App Store? – halileohalilei

+0

@halileohalilei Я выпускаю приложение в App Store без каких-либо проблем в обзоре. https://itunes.apple.com/us/app/facemous/id1213785099?mt=8 – roytornado

0

Обновление для Swift 4:

Swift 4 не поддерживает dispatch_once(), и @objc добавлены в функции замены. Вот обновленная настройка swizzle. Это хорошо работает для меня.

extension CAMetalLayer { 

    // Interface so user can grab this drawable at any time 
    private struct nextDrawableExtPropertyData { 
     static var _currentSceneDrawable : CAMetalDrawable? = nil 
    } 
    var currentSceneDrawable : CAMetalDrawable? { 
     get { 
      return nextDrawableExtPropertyData._currentSceneDrawable 
     } 
    } 

    // The rest of this is just swizzling 
    private static let doJustOnce : Any? = { 
     print ("***** Doing the doJustOnce *****") 
     CAMetalLayer.setupSwizzling() 

     return nil 
    }() 

    public static func enableNextDrawableSwizzle() { 
     _ = CAMetalLayer.doJustOnce 
    } 

    public static func setupSwizzling() { 
     print ("***** Doing the setupSwizzling *****") 

     let copiedOriginalSelector = #selector(CAMetalLayer.originalNextDrawable) 
     let originalSelector = #selector(CAMetalLayer.nextDrawable) 
     let swizzledSelector = #selector(CAMetalLayer.newNextDrawable) 

     let copiedOriginalMethod = class_getInstanceMethod(self, copiedOriginalSelector) 
     let originalMethod = class_getInstanceMethod(self, originalSelector) 
     let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) 

     let oldImp = method_getImplementation(originalMethod!) 
     method_setImplementation(copiedOriginalMethod!, oldImp) 

     let newImp = method_getImplementation(swizzledMethod!) 
     method_setImplementation(originalMethod!, newImp) 

    } 


    @objc func newNextDrawable() -> CAMetalDrawable? { 
     // After swizzling, originalNextDrawable() actually calls the real nextDrawable() 
     let drawable = originalNextDrawable() 

     // Save the drawable 
     nextDrawableExtPropertyData._currentSceneDrawable = drawable 

     return drawable 
    } 

    @objc func originalNextDrawable() -> CAMetalDrawable? { 
     // This is just a placeholder. Implementation will be replaced with nextDrawable. 
     // ***** This will never be called ***** 
     return nil 
    } 
} 

В вашем AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 
    // Override point for customization after application launch. 

    // Swizzle 
    CAMetalLayer.enableNextDrawableSwizzle() 

    return true 
} 

Обновлено добавить currentSceneDrawable свойство CAMetalLayer, так что вы можете просто использовать слой.currentSceneDrawable, чтобы получить доступ к нему, вместо того, чтобы иметь расширение, находящееся снаружи.

 Смежные вопросы

  • Нет связанных вопросов^_^