CAMetalLayer支持的视图

时间:2017-04-13 07:04:56

标签: ios animation core-animation calayer metal

我有一个由CAMetalLayer的子类支持的视图,它需要重绘其内容以响应捏合和平移手势。该图层具有一些自定义属性,这些属性将转换为变换矩阵,并在两种不同的绘图样式(displayMode)之间显示和转换。我没有使用基于CADisplayLink的动画循环,但我确实需要对这些自定义属性进行几种不同的显式动画。

图层子类

自定义属性代码看起来像这样,受到启发  this objc.io article

@implementation RHLayer

@dynamic origin;
@dynamic scale;
@dynamic rotation;
@dynamic reveal;
@dynamic transition;
@dynamic displayMode;

- (RHLayer *)init {
    self = [super init];
    if (self) {
        [self setUpRenderPipeline];
        self.scale = 1.0;
        self.reveal = 1.0;
        self.transition = 0.0;
        self.displayMode = RHSpokeMode;
        self.vertexData = [[NSMutableData alloc] initWithCapacity:32768];
        self.indexData = [[NSMutableData alloc] initWithCapacity:32768];
        [self.vertexData increaseLengthBy:32768];
        [self.indexData increaseLengthBy:32768];
        self.presentsWithTransaction = YES;
        self.needsDisplayOnBoundsChange = YES;
        [self setNeedsDisplay];
    }
    return self;
}

- (RHLayer *)initWithLayer: (RHLayer *)layer {
    self = [super initWithLayer:layer];
    self.origin = layer.origin;
    self.scale = layer.scale;
    self.rotation = layer.rotation;
    self.reveal = layer.reveal;
    self.transition = layer.transition;
    self.displayMode = layer.displayMode;
    return self;
}

static NSSet *_animatableKeys = nil;

+ (BOOL)needsDisplayForKey:(NSString *)key {
    if (!_animatableKeys) {
        _animatableKeys = [[NSSet alloc] initWithObjects:@"origin", @"scale", @"rotation",
                           @"reveal", @"transition", @"displayMode", nil];
    }
    if ([_animatableKeys member:key] ) {
        return YES;
    }
    return [super needsDisplayForKey:key];
}

到目前为止,我没有实施actionForKey:,因为我还没有(还)对隐式动画感兴趣,只对显式动画感兴趣。

绘图代码如下所示:

- (id<CAMetalDrawable>)drawable {
    while (_drawable == nil) {
        _drawable = [self nextDrawable];
    }
    return _drawable;
}

- (void)display {
    [self update]; // Update transform matrix based on presentationLayer's current scale, origin, rotation

    id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
    id<CAMetalDrawable> drawable = self.drawable;
    MTLRenderPassDescriptor *rpd = [self renderPassDescriptorForTexture: drawable.texture];
    id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:rpd];

    [encoder setRenderPipelineState: self.pipelineState];
    [encoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:0];
    [encoder setVertexBuffer:self.uniformBuffer offset:0 atIndex:1];

    // ...
    // Draw metal primitives based on presentationLayer's current displayMode, reveal, transition
    // ...

    [encoder endEncoding];
    [commandBuffer commit];
    [commandBuffer waitUntilScheduled];
    [drawable present];
    self.drawable = nil;
}                                            

@end

视图子类

伴侣UIView子类由上面的子类子类支持,并包含手势识别器逻辑,并根据需要将scaleoffsetrotation值传递给图层。 (它还会安装UIButton子视图,最终会在后备层顶部安装其他子文件以进行文本注释。)

@implementation RHView

+ (Class)layerClass {
    return [RHLayer class];
}

- (void)setFrame:(CGRect)frame {
    [super setFrame: frame];
    self.layer.drawableSize = CGSizeMake(self.bounds.size.width * self.contentScaleFactor,
                                         self.bounds.size.height * self.contentScaleFactor);
}

到目前为止,这么好。两种绘图模式运行良好,转换矩阵与手势完美跟踪,按钮子视图布局跟踪背板层转换。

我的问题

我的明确动画有两个明显的问题:

在动画开始之前,

1。 display始终使用模型图层的新属性值调用一次。例如,这里是视图代码中的回弹动画,它强制执行最小比例值:

if (gesture.state == UIGestureRecognizerStateEnded) {
    if (self.layer.scale < 1.0) {
        [UIView animateWithDuration:0.2 animations:^{
            CABasicAnimation *resetScale = [CABasicAnimation animationWithKeyPath:@"scale"];
            resetScale.fromValue = [NSNumber numberWithFloat: self.layer.scale];
            resetScale.toValue = @1.0;
            resetScale.duration = 0.2;
            resetScale.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
            self.layer.scale = 1.0;
            [self.layer addAnimation:resetScale forKey:@"ResetScale"];
        }];
    }
}

使用此代码,我得到一个缩放设置为1.0的帧,然后下一帧以请求的fromValue开始,动画从那里继续。这会导致恼人的视觉流行音乐。

2. 动画有时会到达toValue,但有时并不会。在上面的示例中,在动画display的最后一帧上,最终可能会使用presentationLayer值调用,例如,scale的0.993,而不是1.0。模型图层的值肯定设置为1.0,但需要手势轻推以强制使用该值进行额外的重新显示。

我尝试的事情

我已经搜索了数十篇SO文章和在线教程,但我还没有看到一个CAMetalLayer被直接子类化的例子。

  • 我尝试将presentsWithTransaction设置为YES和NO,但没有明显效果。最终,如果我能正确理解此设置的作用,我希望我需要使用YES来动态我的子图层与背景层同步。
  • 对于上面的问题1,我尝试在动画上使用kCAFillModeBackwards。没效果。
  • 对于上面的问题2,我尝试在完成块内使用带有[self setNeedsDisplay]的CATransaction,以强制进行一次display次呼叫。那里也没有运气。
  • 我的UIView动画块中的代码的各种版本和重新排序。
  • 我的图层的原始版本是标准CALayer的子类,它使用CoreGraphics调用来执行我的自定义绘图。事实证明这是太慢而不能平滑地制作动画,所以我用Metal重写了。但即使在那个版本中我也有不完整动画的问题;这让我怀疑我的问题与我如何使用自定义属性有关,而不是与使用CAMetalLayer有关。

另一个观察,可能是无关的:在一些我不太了解的偶然情况下,[self presentationLayer]返回零。所以我做的事情就像这个代码片段的第一行(由update调用),以便回退到模型层值:

- (matrix_float4x4)layerToViewTransform: (BOOL)animated {
    RadialHierarchyLayer *layer = animated && [self presentationLayer] ? [self presentationLayer] : self;

    matrix_float4x4 layerToView = matrix_float4x4_translation((vector_float3){ layer.origin.x, layer.origin.y, 0.0 });
    layerToView = matrix_multiply (layerToView, matrix_float4x4_rotateZ (layer.rotation));
    layerToView = matrix_multiply (layerToView, matrix_float4x4_uniform_scale (layer.scale));
    return layerToView;
}

0 个答案:

没有答案