SKRenderer -- the mystery class
Asked Answered
A

2

8

Here I was getting all hyped about pulling off-screen Metal textures out of SpriteKit after watching the session 609 video from WWDC2017.

This was over a year ago!

And yet there are absolutely no overview docs on SKRenderer and there is no sample code either. https://developer.apple.com/documentation/spritekit/skrenderer

I find this very odd indeed. Does anyone here have any insight on this class, its docs or sample code?

BTW, the same goes for SKTransformNode.

Audry answered 19/6, 2018 at 18:35 Comment(1)
There's some documentation for these two classes in the header files which declare them, for what that's worth. Always check the headers. :)Deandra
G
19

Basic use of SKRenderer is pretty straightforward, but there are some oddities that make it somewhat quirky in practice.

First, the fundamentals. To instantiate a renderer, use the rendererWithDevice: method. This method takes a id<MTLDevice>, such as the system default device. Pardon the Objective-C; this will translate easily to Swift:

SKRenderer *renderer = [SKRenderer rendererWithDevice:mtlDevice];

To tell the renderer what to draw, we associate it with a previously-created scene:

renderer.scene = (SKScene *)scene;

If you want actions to run, you'll need to manually un-pause the scene, something that is normally done by SKView when presenting a scene:

scene.paused = NO;

To actually draw the scene, we'll need to provide a command buffer and render pass descriptor. Supposing you're using an MTKView to handle running the display link timer and manage a CAMetalLayer, you can write a delegate method like this, which updates the scene's time (and actions) via the renderer, then draws into the MTKView's drawable:

- (void)drawInMTKView:(nonnull MTKView *)view {
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    if (renderPassDescriptor == nil) {
        return;
    }

    [self.renderer updateAtTime:CACurrentMediaTime()];

    id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
    CGRect viewport = CGRectMake(0, 0, view.drawableSize.width, view.drawableSize.height);
    [self.renderer renderWithViewport:viewport
                        commandBuffer:commandBuffer
                 renderPassDescriptor:renderPassDescriptor];

    // TODO: Add any additional Metal rendering here

    [commandBuffer presentDrawable:view.currentDrawable];
    [commandBuffer commit];
}

Remember to set the MTKView's framebufferOnly property to NO if you use this technique.

If you want to render offscreen into a texture you've created, you'll need to do a bit more manual work, but the concepts involved are the same. You can encode separate passes that render to the same texture by creating additional render pass descriptors/encoders; just remember to set the loadAction of the primary color attachment to MTLLoadActionLoad to preserve the contents of the texture across passes.

You can also use the renderWithViewport:renderCommandEncoder:renderPassDescriptor:commandQueue: to consolidate all drawing into a single pass.

Some caveats:

  • As far as I can tell, the viewport parameter is ignored.
  • If you want the SKScene to receive NSResponder actions, you'll need to manually forward them or insert the scene into the responder chain. This especially applies to key events, where the scene (or an object responsible for forwarding to it) needs to be first responder.
  • None of the convenience methods for converting touch or mouse event locations will work when the scene isn't presented by an SKView; you'll need to do some manual translation.
Gullah answered 20/6, 2018 at 12:6 Comment(3)
Were you able to make the SKScene "transparent" in a way that you could render layers of textures "behind" the SKScene? EDIT: Actually this is accomplished just by using the renderWithViewport:renderCommandEncoder:renderPassDescriptor:commandQueue: as you already mentioned.Dasyure
@Gullah how would you insert the scene into the responder chain?Cohe
Do you have a working sample code project for this solution? thxMertiemerton
C
1

swift

   func render(renderCommandEncoder: MTLRenderCommandEncoder){
    skScene.size = Engine.previewViewSize



        currentTime = 0//allows looping skaction


    //sprite kit render
    skrender.update(atTime: currentTime )

    let viewport = CGRect(x: 0, y: 0, width: (Engine.previewViewSize.width), height: (Engine.previewViewSize.height))

    skScene.isPaused = false

    skrender.scene = skScene

    skrender.render(withViewport: viewport, renderCommandEncoder: renderCommandEncoder, renderPassDescriptor: Engine.currentRenderPassDescriptor, commandQueue: Engine.CommandQueue)


}
Cohe answered 8/6, 2019 at 18:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.