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
.
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:
viewport
parameter is ignored.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.SKView
; you'll need to do some manual translation.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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With