Oct
2
CALayer的filters属性能为Layer渲染时添加CoreImage滤镜来做特殊效果。然而因为某些原因,根据苹果文档https://developer.apple.com/documentation/quartzcore/calayer/1410901-filters:
原理上使用CALayer相关的渲染逻辑应该是收口在Core Animation即QuartzCore中的,而iOS与OS X的QuartzCore前端按理说基本一致。如果是软屏蔽的话,说不定有能够解开的方式。
0. 首先有一个感性的判断,是iOS的layer按理说应该能够具有filter功能。原因是:
a. 原理上渲染流程中有可以使用filter的过程,即光栅化离屏渲染的过程。CIFilter的输入输出均为图像流并且默认是在GPU中运行的,那么如果对一个父级layer(比如root layer)进行filter过程,显然需要将内部所有layer完成渲染并且合成成为一个图形,在通过filter运行,最后渲染到界面上。这个过程其实就是离屏渲染的描述,并且这个过程描述对一些layer的既有流程(比如“阴影”)也是一致的,包括在苹果文档上(“Shadow effects and any filters in the filters property are rasterized and included in the bitmap. ”)。因此我们理解filter能力本身是可行的。
b. 有操作系统的功能是利用类似特性实现的,即辅助功能中的“反色”功能。观察开启反色功能用,被反射的区域会发生离屏渲染(如下图)。显然可以判断,苹果实现反色的方式即界面渲染完成之后进行离屏合成,并通过类似于filter的shader机制来执行反色。
1. 观察对filters属性的调用。因为原理上QuartzCore在两个操作系统上调用方式一致,因此对filters方法(即filters属性的getter)进行断点观察操作系统的调用方式。断点后,OS X会在程序生命周期时调两处分别是:
这一处显然是NSVisualEffectView即OS X的window的默认背景view的渲染流程使用,看起来和QuartzCore主流程无关。另一处是:
这一处的实际方法结构相当长,观察后即可判断-[CALayer(CALayerPrivate) _copyRenderLayer:layerFlags:commitFlags:]即是QuartzCore的主流程核心方法,作用是将CALayer这个OC对象的各个参数收集起来汇总到CA::Render::Layer这个C++对象中,然后commit到渲染端,即Commit Transaction过程的核心方法。
断点实际iOS上filter的获取,也出现了两处:
第一处是在UIKit的UIAccessibilityUpdateInvertColorsFilters流程,不过实际断点下来读取后只是做了判断,并没有使用流程。第二处则是:
同样是在-[CALayer(CALayerPrivate) _copyRenderLayer:layerFlags:commitFlags:]流程中。考虑到QuartzCore跨系统流程相似并且filter原理上处理方式与O SX一致,这个方法应该确实是filter处理的核心流程了。如果filter是在这个过程中被丢弃的,应该可以在这个方法的流程中体现。
2. 汇编解读_copyRenderLayer流程。这个过程是结合iOS与OSX的流程一起看的。首先看_copyRenderLayer中获取到filter后做了什么。
_copyRenderLayer内使用filter后的逻辑非常简单并且iOS与OS X的逻辑都一样(当然汇编不一样)。即现通过CA::Render::copy_render_array将OC数组转换为内部C++数组(CA::Render::TypedArray),然后通过CA::Render::Layer::set_filters方法把数字设置到C++Layer对象上。
这一步检查结果上,C++数组确实成功转换并且有值,因此最终下一步set_filters方法。set_filters观察iOS与OS X稍微有区别:
不过大体流程还是比较清晰,对数组数据做了一次转换与持有型变更,然后save上。经过断点也确认,iOS中set_filters的执行是没问题的,也就是说特殊逻辑并不在QuartzCore在App中的前置处理部分。
3. 结论部分。经过上文,基本上得到结论,iOS的fltter功能屏蔽是在系统QuartzCore Render Server完成的。那么用户程序没有任何干预的余地与可能了。
在测试验证过程中,也能判断到系统级反色功能的逻辑应该也是在Render Server直接完成的,因此QuartzCore Client侧不做区别化,在各操作系统Render Server侧做区别化也是合理的,也保证了Client侧的通用性甚至跨平台的可能性。
另外至于苹果为什么非要屏蔽这个特性。考虑到filter渲染即会执行离屏渲染,如果滥用filter的话会明显导致离屏渲染叠加,内存与SOC缓冲区压力巨大,手机功耗与性能极限消耗。因此直接屏蔽,同时意味着用户只能使用属性化的固有filter功能,确实是相对保险的方案了。
Special Considerations
This property is not supported on layers in iOS.
这个功能在iOS上是无法使用的。而在OS X上看起来则是能够轻易的使用:原理上使用CALayer相关的渲染逻辑应该是收口在Core Animation即QuartzCore中的,而iOS与OS X的QuartzCore前端按理说基本一致。如果是软屏蔽的话,说不定有能够解开的方式。
0. 首先有一个感性的判断,是iOS的layer按理说应该能够具有filter功能。原因是:
a. 原理上渲染流程中有可以使用filter的过程,即光栅化离屏渲染的过程。CIFilter的输入输出均为图像流并且默认是在GPU中运行的,那么如果对一个父级layer(比如root layer)进行filter过程,显然需要将内部所有layer完成渲染并且合成成为一个图形,在通过filter运行,最后渲染到界面上。这个过程其实就是离屏渲染的描述,并且这个过程描述对一些layer的既有流程(比如“阴影”)也是一致的,包括在苹果文档上(“Shadow effects and any filters in the filters property are rasterized and included in the bitmap. ”)。因此我们理解filter能力本身是可行的。
b. 有操作系统的功能是利用类似特性实现的,即辅助功能中的“反色”功能。观察开启反色功能用,被反射的区域会发生离屏渲染(如下图)。显然可以判断,苹果实现反色的方式即界面渲染完成之后进行离屏合成,并通过类似于filter的shader机制来执行反色。
1. 观察对filters属性的调用。因为原理上QuartzCore在两个操作系统上调用方式一致,因此对filters方法(即filters属性的getter)进行断点观察操作系统的调用方式。断点后,OS X会在程序生命周期时调两处分别是:
#0 0x00000001030fc07f in __29-[ViewController viewDidLoad]_block_invoke at my hook
#1 0x00007fff235312ef in makeMaterialLayerWithCoreUIOptions ()
#2 0x00007fff22f4bd2c in -[NSVisualEffectView _updateMaterialLayer] ()
#3 0x00007fff22f4ba76 in -[NSVisualEffectView updateLayer] ()
#4 0x00007fff22ec1cac in _NSViewUpdateLayer ()
#5 0x00007fff22dd3e95 in +[NSAppearance _performWithCurrentAppearance:usingBlock:] ()
#6 0x00007fff23034093 in __30-[_NSViewBackingLayer display]_block_invoke ()
#7 0x00007fff22e130fd in -[NSFocusStack performWithFocusView:inWindow:usingBlock:] ()
#8 0x00007fff22ec1635 in -[_NSViewBackingLayer display] ()
#9 0x00007fff26d6f6f3 in CA::Layer::display_if_needed(CA::Transaction*) ()
#10 0x00007fff26ebabee in CA::Context::commit_transaction(CA::Transaction*, double, double*) ()
#11 0x00007fff26d50b6f in CA::Transaction::commit() ()
#12 0x00007fff22f6b86c in __62+[CATransaction(NSCATransaction) NS_setFlushesWithDisplayLink]_block_invoke ()
#13 0x00007fff236c1332 in ___NSRunLoopObserverCreateWithHandler_block_invoke ()
#14 0x00007fff205e0d01 in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ ()
#15 0x00007fff205e0b95 in __CFRunLoopDoObservers ()
#16 0x00007fff205e0028 in __CFRunLoopRun ()
#17 0x00007fff205df61c in CFRunLoopRunSpecific ()
#18 0x00007fff28825a83 in RunCurrentEventLoopInMode ()
#19 0x00007fff288256b6 in ReceiveNextEventCommon ()
#20 0x00007fff28825583 in _BlockUntilNextEventMatchingListInModeWithFilter ()
#21 0x00007fff22de7172 in _DPSNextEvent ()
#22 0x00007fff22de5945 in -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] ()
#23 0x00007fff22dd7c69 in -[NSApplication run] ()
#24 0x00007fff22dabe6c in NSApplicationMain ()
#25 0x00000001030fc1bf in main
#26 0x00007fff20503f3d in start ()
#27 0x00007fff20503f3d in start ()
这一处显然是NSVisualEffectView即OS X的window的默认背景view的渲染流程使用,看起来和QuartzCore主流程无关。另一处是:
#0 0x000000010ef5d06f in __29-[ViewController viewDidLoad]_block_invoke at my hook
#1 0x00007fff26d7b94d in -[CALayer(CALayerPrivate) _copyRenderLayer:layerFlags:commitFlags:] ()
#2 0x00007fff26d82ea2 in -[CABackdropLayer _copyRenderLayer:layerFlags:commitFlags:] ()
#3 0x00007fff26ebf6ed in invocation function for block in CA::Context::commit_transaction(CA::Transaction*, double, double*) ()
#4 0x00007fff26d7ab47 in CA::Layer::commit_if_needed(CA::Transaction*, void (CA::Layer*, unsigned int, unsigned int) block_pointer) ()
#5 0x00007fff26d7aad0 in CA::Layer::commit_if_needed(CA::Transaction*, void (CA::Layer*, unsigned int, unsigned int) block_pointer) ()
#6 0x00007fff26ebc856 in CA::Context::commit_transaction(CA::Transaction*, double, double*) ()
#7 0x00007fff26d50b6f in CA::Transaction::commit() ()
#8 0x00007fff22f6b86c in __62+[CATransaction(NSCATransaction) NS_setFlushesWithDisplayLink]_block_invoke ()
#9 0x00007fff236c1332 in ___NSRunLoopObserverCreateWithHandler_block_invoke ()
#10 0x00007fff205e0d01 in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ ()
#11 0x00007fff205e0b95 in __CFRunLoopDoObservers ()
#12 0x00007fff205e0028 in __CFRunLoopRun ()
#13 0x00007fff205df61c in CFRunLoopRunSpecific ()
#14 0x00007fff28825a83 in RunCurrentEventLoopInMode ()
#15 0x00007fff288256b6 in ReceiveNextEventCommon ()
#16 0x00007fff28825583 in _BlockUntilNextEventMatchingListInModeWithFilter ()
#17 0x00007fff22de7172 in _DPSNextEvent ()
#18 0x00007fff22de5945 in -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] ()
#19 0x00007fff22dd7c69 in -[NSApplication run] ()
#20 0x00007fff22dabe6c in NSApplicationMain ()
#21 0x000000010ef5d1af in main
#22 0x00007fff20503f3d in start ()
#23 0x00007fff20503f3d in start ()
这一处的实际方法结构相当长,观察后即可判断-[CALayer(CALayerPrivate) _copyRenderLayer:layerFlags:commitFlags:]即是QuartzCore的主流程核心方法,作用是将CALayer这个OC对象的各个参数收集起来汇总到CA::Render::Layer这个C++对象中,然后commit到渲染端,即Commit Transaction过程的核心方法。
断点实际iOS上filter的获取,也出现了两处:
#0 0x00000001005d8174 in __29-[ViewController viewDidLoad]_block_invoke hook
#1 0x0000000186291638 in UIAccessibilityUpdateInvertColorsFilters ()
#2 0x00000001863bbd48 in -[UIView(Internal) _didMoveFromWindow:toWindow:] ()
#3 0x00000001863bba30 in -[UIView(Internal) _didMoveFromWindow:toWindow:] ()
#4 0x00000001862e7134 in __45-[UIView(Hierarchy) _postMovedFromSuperview:]_block_invoke ()
#5 0x0000000186373080 in -[UIView(Hierarchy) _postMovedFromSuperview:] ()
#6 0x00000001862945e4 in -[UIView(Internal) _addSubview:positioned:relativeTo:] ()
#7 0x00000001863dc97c in -[UIViewControllerBuiltinTransitionViewAnimator animateTransition:] ()
#8 0x00000001865a9bc0 in ___UIViewControllerTransitioningRunCustomTransition_block_invoke_2 ()
#9 0x00000001863e54f8 in +[UIKeyboardSceneDelegate _pinInputViewsForKeyboardSceneDelegate:onBehalfOfResponder:duringBlock:] ()
#10 0x0000000186459a0c in ___UIViewControllerTransitioningRunCustomTransition_block_invoke.641 ()
//...
#35 0x0000000183e66220 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#36 0x0000000183e76248 in __CFRunLoopDoSource0 ()
#37 0x0000000183db964c in __CFRunLoopDoSources0 ()
#38 0x0000000183dbea18 in __CFRunLoopRun ()
#39 0x0000000183dd1d8c in CFRunLoopRunSpecific ()
#40 0x000000019dede9a0 in GSEventRunModal ()
#41 0x0000000186606fa8 in -[UIApplication _run] ()
#42 0x000000018639b22c in UIApplicationMain ()
#43 0x00000001005dcfdc in main
#44 0x0000000100a1c190 in start ()
第一处是在UIKit的UIAccessibilityUpdateInvertColorsFilters流程,不过实际断点下来读取后只是做了判断,并没有使用流程。第二处则是:
#0 0x00000001005d8174 in __29-[ViewController viewDidLoad]_block_invoke at hook
#1 0x00000001879968d4 in -[CALayer(CALayerPrivate) _copyRenderLayer:layerFlags:commitFlags:] ()
#2 0x000000018790a3cc in invocation function for block in CA::Context::commit_transaction(CA::Transaction*, double, double*) ()
#3 0x00000001878f7e68 in CA::Layer::commit_if_needed(CA::Transaction*, void (CA::Layer*, unsigned int, unsigned int) block_pointer) ()
#4 0x00000001878f7df0 in CA::Layer::commit_if_needed(CA::Transaction*, void (CA::Layer*, unsigned int, unsigned int) block_pointer) ()
#5 0x00000001878f7df0 in CA::Layer::commit_if_needed(CA::Transaction*, void (CA::Layer*, unsigned int, unsigned int) block_pointer) ()
#6 0x00000001878f7df0 in CA::Layer::commit_if_needed(CA::Transaction*, void (CA::Layer*, unsigned int, unsigned int) block_pointer) ()
#7 0x00000001878f7df0 in CA::Layer::commit_if_needed(CA::Transaction*, void (CA::Layer*, unsigned int, unsigned int) block_pointer) ()
#8 0x00000001878f7df0 in CA::Layer::commit_if_needed(CA::Transaction*, void (CA::Layer*, unsigned int, unsigned int) block_pointer) ()
#9 0x00000001878f7df0 in CA::Layer::commit_if_needed(CA::Transaction*, void (CA::Layer*, unsigned int, unsigned int) block_pointer) ()
#10 0x0000000187908aa8 in CA::Context::commit_transaction(CA::Transaction*, double, double*) ()
#11 0x000000018791008c in CA::Transaction::commit() ()
#12 0x0000000186453b1c in __34-[UIApplication _firstCommitBlock]_block_invoke_2 ()
#13 0x0000000183e20cf8 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ ()
#14 0x0000000183e21acc in __CFRunLoopDoBlocks ()
#15 0x0000000183dbea2c in __CFRunLoopRun ()
#16 0x0000000183dd1d8c in CFRunLoopRunSpecific ()
#17 0x000000019dede9a0 in GSEventRunModal ()
#18 0x0000000186606fa8 in -[UIApplication _run] ()
#19 0x000000018639b22c in UIApplicationMain ()
#20 0x00000001005dcfdc in main
#21 0x0000000100a1c190 in start ()
同样是在-[CALayer(CALayerPrivate) _copyRenderLayer:layerFlags:commitFlags:]流程中。考虑到QuartzCore跨系统流程相似并且filter原理上处理方式与O SX一致,这个方法应该确实是filter处理的核心流程了。如果filter是在这个过程中被丢弃的,应该可以在这个方法的流程中体现。
2. 汇编解读_copyRenderLayer流程。这个过程是结合iOS与OSX的流程一起看的。首先看_copyRenderLayer中获取到filter后做了什么。
_copyRenderLayer内使用filter后的逻辑非常简单并且iOS与OS X的逻辑都一样(当然汇编不一样)。即现通过CA::Render::copy_render_array将OC数组转换为内部C++数组(CA::Render::TypedArray),然后通过CA::Render::Layer::set_filters方法把数字设置到C++Layer对象上。
这一步检查结果上,C++数组确实成功转换并且有值,因此最终下一步set_filters方法。set_filters观察iOS与OS X稍微有区别:
不过大体流程还是比较清晰,对数组数据做了一次转换与持有型变更,然后save上。经过断点也确认,iOS中set_filters的执行是没问题的,也就是说特殊逻辑并不在QuartzCore在App中的前置处理部分。
3. 结论部分。经过上文,基本上得到结论,iOS的fltter功能屏蔽是在系统QuartzCore Render Server完成的。那么用户程序没有任何干预的余地与可能了。
在测试验证过程中,也能判断到系统级反色功能的逻辑应该也是在Render Server直接完成的,因此QuartzCore Client侧不做区别化,在各操作系统Render Server侧做区别化也是合理的,也保证了Client侧的通用性甚至跨平台的可能性。
另外至于苹果为什么非要屏蔽这个特性。考虑到filter渲染即会执行离屏渲染,如果滥用filter的话会明显导致离屏渲染叠加,内存与SOC缓冲区压力巨大,手机功耗与性能极限消耗。因此直接屏蔽,同时意味着用户只能使用属性化的固有filter功能,确实是相对保险的方案了。