MetalVideoRenderer: don't recreate pipeline state every frame
This commit is contained in:
1 parent
45895fa8bf
commit
5a2d5af887
2 files changed
+90
-130
No files matched your search
@@ -18,7 +18,7 @@
|
|||||||
- (void)renderFrame:(nonnull Frame *)frame toLayer:(nonnull CAMetalLayer *)layer;
|
- (void)renderFrame:(nonnull Frame *)frame toLayer:(nonnull CAMetalLayer *)layer;
|
||||||
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer;
|
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer;
|
||||||
- (void)drawableResize:(CGSize)drawableSize;
|
- (void)drawableResize:(CGSize)drawableSize;
|
||||||
- (void)plotFrametime:(CFTimeInterval)presentedTime withPresentTime:(CFTimeInterval)presentTime;
|
- (void)plotFrametime:(CFTimeInterval)presentedTime;
|
||||||
- (void)stop;
|
- (void)stop;
|
||||||
- (void)discardNextDrawable;
|
- (void)discardNextDrawable;
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
id<ConnectionCallbacks> _callbacks;
|
id<ConnectionCallbacks> _callbacks;
|
||||||
id<MTLCommandQueue> _commandQueue;
|
id<MTLCommandQueue> _commandQueue;
|
||||||
id<MTLLibrary> _shaderLibrary;
|
id<MTLLibrary> _shaderLibrary;
|
||||||
id<MTLRenderPipelineState> _videoPipelineState;
|
id<MTLRenderPipelineState> _videoPipelineState[MAX_VIDEO_PLANES];
|
||||||
MTLRenderPassDescriptor *_renderPassDescriptor;
|
MTLRenderPassDescriptor *_renderPassDescriptor;
|
||||||
id<MTLTexture> _videoTexture;
|
id<MTLTexture> _videoTexture;
|
||||||
CVMetalTextureCacheRef _textureCache;
|
CVMetalTextureCacheRef _textureCache;
|
||||||
@@ -365,73 +365,66 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
|
|
||||||
- (void)renderFrame:(Frame *)frame toLayer:(CAMetalLayer *)layer
|
- (void)renderFrame:(Frame *)frame toLayer:(CAMetalLayer *)layer
|
||||||
{ @autoreleasepool {
|
{ @autoreleasepool {
|
||||||
// Handle changes to the frame's colorspace from last time we rendered
|
// Handle changes to the frame's colorspace from last time we rendered
|
||||||
BOOL layerDidChange = NO;
|
BOOL layerDidChange = NO;
|
||||||
if (![self updateColorSpaceForFrame:frame toLayer:layer layerDidChange:&layerDidChange]) {
|
if (![self updateColorSpaceForFrame:frame toLayer:layer layerDidChange:&layerDidChange]) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layerDidChange && frame.frameNumber > 1) {
|
|
||||||
Log(LOG_I, @"Metal frame changed layer's colorspace and/or pixel format, returning for new drawable");
|
|
||||||
[self discardNextDrawable];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle changes to the video size or drawable size
|
|
||||||
if (![self updateVideoRegionSizeForFrame:frame toLayer:layer]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FQLog(LOG_I, @"[%d / %.3f ms] Metal frame rendering", frame.frameNumber, frame.pts);
|
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
|
||||||
// Experimental EDR handling based on frame metadata
|
|
||||||
//[self applyEDRFromFrame:frame toLayer:layer];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
size_t planes = CVPixelBufferGetPlaneCount(frame.pixelBuffer);
|
|
||||||
assert(planes <= MAX_VIDEO_PLANES);
|
|
||||||
|
|
||||||
MTLRenderPipelineDescriptor *pipelineDesc = [MTLRenderPipelineDescriptor new];
|
|
||||||
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
|
|
||||||
pipelineDesc.vertexFunction = [defaultLibrary newFunctionWithName:@"vs_draw"];
|
|
||||||
pipelineDesc.fragmentFunction = [defaultLibrary newFunctionWithName:planes == 2 ? @"ps_draw_biplanar" : @"ps_draw_triplanar"];
|
|
||||||
pipelineDesc.colorAttachments[0].pixelFormat = layer.pixelFormat;
|
|
||||||
pipelineDesc.vertexBuffers[0].mutability = MTLMutabilityImmutable;
|
|
||||||
|
|
||||||
NSError *error = nil;
|
|
||||||
_videoPipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineDesc error:&error];
|
|
||||||
if (!_videoPipelineState) {
|
|
||||||
Log(LOG_E, @"Failed to create video pipeline state: %@", error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < planes; i++) {
|
|
||||||
MTLPixelFormat fmt;
|
|
||||||
|
|
||||||
switch (CVPixelBufferGetPixelFormatType(frame.pixelBuffer)) {
|
|
||||||
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
|
|
||||||
case kCVPixelFormatType_444YpCbCr8BiPlanarVideoRange:
|
|
||||||
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
|
||||||
case kCVPixelFormatType_444YpCbCr8BiPlanarFullRange:
|
|
||||||
fmt = (i == 0) ? MTLPixelFormatR8Unorm : MTLPixelFormatRG8Unorm;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case kCVPixelFormatType_420YpCbCr10BiPlanarFullRange:
|
|
||||||
case kCVPixelFormatType_444YpCbCr10BiPlanarFullRange:
|
|
||||||
case kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange:
|
|
||||||
case kCVPixelFormatType_444YpCbCr10BiPlanarVideoRange:
|
|
||||||
fmt = (i == 0) ? MTLPixelFormatR16Unorm : MTLPixelFormatRG16Unorm;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Log(LOG_E, @"Unknown pixel format: %@", CVPixelBufferGetPixelFormatType(frame.pixelBuffer));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cvMetalTextures[i]) {
|
if (![self updateVideoRegionSizeForFrame:frame toLayer:layer]) {
|
||||||
CVBufferRelease(_cvMetalTextures[i]);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t planes = CVPixelBufferGetPlaneCount(frame.pixelBuffer);
|
||||||
|
assert(planes <= MAX_VIDEO_PLANES);
|
||||||
|
|
||||||
|
if (layerDidChange && frame.frameNumber > 1) {
|
||||||
|
Log(LOG_I, @"Metal frame changed layer's colorspace and/or pixel format, invalidating pipeline cache");
|
||||||
|
_videoPipelineState[planes] = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_videoPipelineState[planes]) {
|
||||||
|
MTLRenderPipelineDescriptor *pipelineDesc = [MTLRenderPipelineDescriptor new];
|
||||||
|
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
|
||||||
|
pipelineDesc.vertexFunction = [defaultLibrary newFunctionWithName:@"vs_draw"];
|
||||||
|
pipelineDesc.fragmentFunction = [defaultLibrary newFunctionWithName:planes == 2 ? @"ps_draw_biplanar" : @"ps_draw_triplanar"];
|
||||||
|
pipelineDesc.colorAttachments[0].pixelFormat = layer.pixelFormat;
|
||||||
|
pipelineDesc.vertexBuffers[0].mutability = MTLMutabilityImmutable;
|
||||||
|
|
||||||
|
NSError *error = nil;
|
||||||
|
_videoPipelineState[planes] = [_device newRenderPipelineStateWithDescriptor:pipelineDesc error:&error];
|
||||||
|
if (!_videoPipelineState[planes]) {
|
||||||
|
Log(LOG_E, @"Failed to create video pipeline state: %@", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < planes; i++) {
|
||||||
|
MTLPixelFormat fmt;
|
||||||
|
|
||||||
|
switch (CVPixelBufferGetPixelFormatType(frame.pixelBuffer)) {
|
||||||
|
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
|
||||||
|
case kCVPixelFormatType_444YpCbCr8BiPlanarVideoRange:
|
||||||
|
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
||||||
|
case kCVPixelFormatType_444YpCbCr8BiPlanarFullRange:
|
||||||
|
fmt = (i == 0) ? MTLPixelFormatR8Unorm : MTLPixelFormatRG8Unorm;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case kCVPixelFormatType_420YpCbCr10BiPlanarFullRange:
|
||||||
|
case kCVPixelFormatType_444YpCbCr10BiPlanarFullRange:
|
||||||
|
case kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange:
|
||||||
|
case kCVPixelFormatType_444YpCbCr10BiPlanarVideoRange:
|
||||||
|
fmt = (i == 0) ? MTLPixelFormatR16Unorm : MTLPixelFormatRG16Unorm;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Log(LOG_E, @"Unknown pixel format: %@", CVPixelBufferGetPixelFormatType(frame.pixelBuffer));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cvMetalTextures[i]) {
|
||||||
|
CVBufferRelease(_cvMetalTextures[i]);
|
||||||
|
}
|
||||||
CVReturn err = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
|
CVReturn err = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
|
||||||
_textureCache,
|
_textureCache,
|
||||||
frame.pixelBuffer,
|
frame.pixelBuffer,
|
||||||
@@ -441,71 +434,53 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
CVPixelBufferGetHeightOfPlane(frame.pixelBuffer, i),
|
CVPixelBufferGetHeightOfPlane(frame.pixelBuffer, i),
|
||||||
i,
|
i,
|
||||||
&_cvMetalTextures[i]);
|
&_cvMetalTextures[i]);
|
||||||
if (err != kCVReturnSuccess) {
|
if (err != kCVReturnSuccess) {
|
||||||
Log(LOG_E, @"CVMetalTextureCacheCreateTextureFromImage() failed: %d", err);
|
Log(LOG_E, @"CVMetalTextureCacheCreateTextureFromImage() failed: %d", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id<CAMetalDrawable> drawable = [layer nextDrawable];
|
||||||
|
if (!drawable) {
|
||||||
|
Log(LOG_E, @"Failed to get nextDrawable from layer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
_renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
|
||||||
|
|
||||||
if (!_nextDrawable) {
|
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
|
||||||
Log(LOG_E, @"Lost nextDrawable, trying to get a new one");
|
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:_renderPassDescriptor];
|
||||||
_nextDrawable = [layer nextDrawable];
|
|
||||||
if (!_nextDrawable) {
|
[renderEncoder setRenderPipelineState:_videoPipelineState[planes]];
|
||||||
Log(LOG_E, @"Failed to get nextDrawable");
|
[renderEncoder setVertexBuffer:_VideoVertexBuffer offset:0 atIndex:0];
|
||||||
return;
|
[renderEncoder setFragmentBuffer:_CscParamsBuffer offset:0 atIndex:0];
|
||||||
|
for (size_t i = 0; i < planes; i++) {
|
||||||
|
[renderEncoder setFragmentTexture:CVMetalTextureGetTexture(_cvMetalTextures[i]) atIndex:i];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_renderPassDescriptor.colorAttachments[0].texture = _nextDrawable.texture;
|
|
||||||
|
|
||||||
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
|
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
||||||
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:_renderPassDescriptor];
|
[renderEncoder endEncoding];
|
||||||
|
|
||||||
[renderEncoder setRenderPipelineState:_videoPipelineState];
|
[commandBuffer presentDrawable:drawable afterMinimumDuration:1.0f / _framerate];
|
||||||
[renderEncoder setVertexBuffer:_VideoVertexBuffer offset:0 atIndex:0];
|
[commandBuffer commit];
|
||||||
[renderEncoder setFragmentBuffer:_CscParamsBuffer offset:0 atIndex:0];
|
|
||||||
|
|
||||||
for (size_t i = 0; i < planes; i++) {
|
[commandBuffer waitUntilCompleted];
|
||||||
[renderEncoder setFragmentTexture:CVMetalTextureGetTexture(_cvMetalTextures[i]) atIndex:i];
|
|
||||||
}
|
|
||||||
|
|
||||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
const CFTimeInterval GPUTime = commandBuffer.GPUEndTime - commandBuffer.GPUStartTime;
|
||||||
[renderEncoder endEncoding];
|
const double alpha = 0.25f;
|
||||||
|
_averageGPUTime = (GPUTime * alpha) + (_averageGPUTime * (1.0 - alpha));
|
||||||
|
|
||||||
__weak typeof(self) weakSelf = self;
|
for (size_t i = 0; i < planes; i++) {
|
||||||
[_nextDrawable addPresentedHandler:^(id<MTLDrawable> d) {
|
|
||||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
||||||
if (strongSelf) {
|
|
||||||
[strongSelf plotFrametime:d.presentedTime];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
#if TARGET_OS_SIMULATOR
|
|
||||||
[commandBuffer presentDrawable:_nextDrawable];
|
|
||||||
#else
|
|
||||||
// present for a minimum duration for best frame pacing
|
|
||||||
[commandBuffer presentDrawable:_nextDrawable afterMinimumDuration:1.0f / _framerate];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[commandBuffer commit];
|
|
||||||
|
|
||||||
// Wait for the command buffer to complete and free our CVMetalTextureCache references
|
|
||||||
[commandBuffer waitUntilCompleted];
|
|
||||||
|
|
||||||
// Perform cleanup
|
|
||||||
const CFTimeInterval GPUTime = commandBuffer.GPUEndTime - commandBuffer.GPUStartTime;
|
|
||||||
const double alpha = 0.25f;
|
|
||||||
_averageGPUTime = (GPUTime * alpha) + (_averageGPUTime * (1.0 - alpha));
|
|
||||||
|
|
||||||
for (size_t i = 0; i < planes; i++) {
|
|
||||||
if (_cvMetalTextures[i]) {
|
if (_cvMetalTextures[i]) {
|
||||||
CVBufferRelease(_cvMetalTextures[i]);
|
CVBufferRelease(_cvMetalTextures[i]);
|
||||||
_cvMetalTextures[i] = nil;
|
_cvMetalTextures[i] = nil;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
CVMetalTextureCacheFlush(_textureCache, 0);
|
||||||
}
|
}
|
||||||
CVMetalTextureCacheFlush(_textureCache, 0);
|
}
|
||||||
|
|
||||||
_nextDrawable = nil;
|
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer {
|
||||||
} }
|
}
|
||||||
|
|
||||||
- (void)plotFrametime:(CFTimeInterval)presentedTime {
|
- (void)plotFrametime:(CFTimeInterval)presentedTime {
|
||||||
if (_lastPresented > 0) {
|
if (_lastPresented > 0) {
|
||||||
@@ -515,27 +490,12 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
_lastPresented = presentedTime;
|
_lastPresented = presentedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer {
|
|
||||||
if (!_nextDrawable) {
|
|
||||||
// Wait for the next available drawable
|
|
||||||
_nextDrawable = [layer nextDrawable];
|
|
||||||
if (!_nextDrawable) {
|
|
||||||
Log(LOG_E, @"Error getting nextDrawable from CAMetalLayer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait to ensure only `MaxFramesInFlight` number of frames are getting processed
|
|
||||||
// by any stage in the Metal pipeline (CPU, GPU, Metal, Drivers, etc.).
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Responds to the drawable's size or orientation changes.
|
/// Responds to the drawable's size or orientation changes.
|
||||||
- (void)drawableResize:(CGSize)drawableSize {
|
- (void)drawableResize:(CGSize)drawableSize {
|
||||||
[self resize:drawableSize];
|
[self resize:drawableSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resize:(CGSize)size {
|
- (void)resize:(CGSize)size {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)stop {
|
- (void)stop {
|
||||||
|
|||||||
Reference in new issue
Block a user