Oct 2

小记:关于CALayer的filters属性在iOS上无法使用的问题的判断

Lrdcq , 2021/10/02 17:19 , 程序 , 閱讀(1896) , Via 本站原創
CALayer的filters属性能为Layer渲染时添加CoreImage滤镜来做特殊效果。然而因为某些原因,根据苹果文档https://developer.apple.com/documentation/quartzcore/calayer/1410901-filters
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功能,确实是相对保险的方案了。
关键词:ios , filter , quartzcore
logo