|
「迷思」是指经由人们口口相传,但又难以证明证伪的现象。由于GPU硬件实现、驱动实现是一个黑盒,我们只能通过厂商提供的API、经过抽象的架构来了解并猜测其原理。因此坊间流传着各种关于与GPU打交道时的性能迷思。比如「移动端的瓶颈是带宽」、「移动端不需要太在意Overdraw」、「植被需要做PrePass」等等。这些优化手段,有时候我们对后面的原理一知半解,有时候又会随着硬件的发展而逐渐变得不适用,逐渐会变成一种神秘主义。作者:mobiuschen我希望可以结合一些现有资料,对这些「迷思」做定性的分析,避免一不小心变成了负优化。有条件时,甚至希望可以做一些定量分析的实验。移动端架构我们先简单过一下几个厂商的GPU架构。苹果的GPU架构查不到任何文档,只能姑且以多年前PowerVR的架构文档来替代。移动端的GPU现在都是TBDR架构,分为两个Pass:BinningPass和RenderingPass。BinningPass将各个Primitive分到各个Tile,Renderingpass再在对这些tile进行渲染。讲GPU结构的资料还是比较多,这里不再赘述。我们只着重讲几点不同架构上有差异、与后面「迷思」相关的功能点。隐面剔除「隐面剔除」技术这一术语来自于PowerVR的HSR(HiddenSurfaceRemoval),通常用来指代GPU对最终被遮挡的Primitive/Fragment做剔除,避免执行其PS,以达到减少Overdraw的效果。Adreno/Mali/PowerVR三家在处理隐面剔除除的方式是不一样的。PowerVR的HSR原理是生成一个visibilitybuffer,记录了每一个pixel的深度,用于对fragment做像素级的剔除。因为是逐像素级的剔除,因此需要放到Rasterization之后,也就是说每个三角形都需要做Rasterization。根据visibilitybuffer来决定每一个像素执行哪个fragment的ps。也因此,PowerVR将自己TBR(TileBasedRendering)称为TBDR(TileBasedDeferredRendering)。而且特别提到了一点,如果当出现一个fragment的深度无法在vs阶段就确定,那么就会等到fragment的ps执行完,确定了深度,再来填充对应的visibilitybuffer。也就是说这个fragment会阻塞了visibilitybuffer的生成。这个架构来自于PowerVR2015年左右的文档,后续Apple继承了其架构,但是后面是否有做更进一步的架构优化不得而知。Adreno实现隐面剔除技术的流程称为LRZ(LowResolutionDepth),其剔除的颗粒度是Primitive而不是Fragment。在Binningpass阶段执行Position-OnlyVS时的会生成一张LRZbuffer(低分辨率的zbuffer),将三角形的最小深度与zbuffer做对比,以此判断三角形的可见性。Binningpass之后,将可见的trianglelist存入SYSMEM,在renderpass中再根据trianglelist来绘制。相比于PowerVR的HSR,LRZ由于是binningpass做的,可以减少Rasterization的开销。并且在renderpass中,也会有early-zstage来做fragment级别的剔除。对于那种需要在ps阶段才能决定深度的fragment,就会跳过LRZ,但是并不会阻塞管线。Mali实现隐面剔除的技术称为FPK(ForwardPixelKilling)。其原理是所有经过Early-Z之后的Quad,都会放入一个FIFO队列中,记录其位置与深度,等待执行。如果在执行完之前,队列中新进来一个QuadA,位置与现队列中的某个QuadB相同,但是A深度更小,那么队列中的B就会被kill掉,不再执行。Early-Z只可以根据历史数据,剔除掉当前的Quad。而FPK可以使用当前的Quad,在一定程度上剔除掉老的Quad。FPK与HSR类似,但是区别是HSR是阻塞性的,只有只有完全生成visibilitybuffer之后,才会执行PS。但FPK不会阻塞,只会kill掉还没来得及执行或者执行到一半的PS。VertexShader的执行PowerVR下,VS是在BinningPass阶段执行的,只会执行一次。Adreno和Mali都是会执行两次VS。第一次都是"PosOnlyVertexProcessing",就是只执行VS中产出position相关的指令,只算出Position信息给Binning阶段使用。第二次VS的执行,Mali是在binning阶段,称为"VaryingShading",只执行非Position的那部分逻辑。而Adreno的第二次VS的执行会放到rendering阶段,并且是执行全量的VS。为什么有这个区别呢?下个点"VSOutput"会讲到。VSOutputVS阶段产出的数据称为VSOutput,或者称为Post-VSAttributes。对这些数据的处理,不同架构也不大一样。PowerVR,BinningPass阶段就已经执行了全量的VS,然后会将VSOutput写到systemmemory。在RenderingPass再重新读回来。Adreno架构下,BinningPass之后只产出两种数据并会将其写到systemmemory:PrimitiveList和PrimitiveVisibility。在RenderingPass会重新执行一遍VS,产出VSOutput。这些数据不回写回systemmemory,而是存在On-ChipMemory(LocalBuffer),PS阶段直接可以从LocalBuffer读取。这样就节省了很多的带宽消耗。Mali在第五代GPU架构Immortalis之前,第二遍VS会在BinningPass执行完,并且把VSOutput都写入systemmemory,在renderingpass重新读出来。这样就会有带宽的消耗。于是第五代架构推出了DeferredVertexShading(DVS),就是将VaryingShading延迟到rendering阶段再执行,这样就直接可以把数据存到On-ChipMemory给后面的PS使用,达到节省带宽的目的。号称可以节省20%-40%的带宽。移动端带宽是瓶颈带宽就是单位时间内数据的传输量,其量化标准为「位宽(bitwide)*频率」。在位宽确定的情况下,只能通过增加工作频率来提高带宽。更高的频率意味着需要更高的电压,进而造成更高的功耗。功率(P)与电压(V)的关系,也就是说功耗与电压平方成正比。「在移动端带宽是瓶颈」,源自于两方面:带宽的发展与算力的发展不匹配带宽的增加带来了大功耗下面这张图对比了算力、内存带宽、内部带宽的发展,可以看到20年间,硬件算力提升了60000倍,内存带宽提升了100倍,而内部带宽只提升了30倍。公路宽度提升了几十倍,车流量却变成了几万倍,当然公路的宽度就变成了瓶颈了。这个问题在移动端变得更加严重。因为在移动端,芯片面积有限,位宽无法做得很大。在手机上,内存位宽一般是64-bitwide或者128-bitwide;而桌面端可以是1024-bitwide,甚至搭配了HBM(HighBandwidthMemory)的显卡可以到4096-bitwide。更高的位宽意味更高的面积和成本。因此,手机上只能通过提高电压来增加带宽。按照前面的公式,功率(P)与电压(V)的平方成正比。电压的增加指数级地增加了手机的功耗。所以我们经常说「手机功耗的大头就是带宽」。不要在Shader中使用分支这是一个经典的迷思:「不要在Shader中使用分支,使用了之后相当于Shader需要在分支两边各执行一遍」。这个说法是基于GPUSP并行执行的原理。SP在执行时,是使用lockstep的方式执行同一个wave(或者warp)中的所有fiber(或者thread)的。也就是说,使用SIMD的指令,在一个cycle内同时执行所有的fiber相同指令。每一个fiber都是同时往前步进的。SP有个Mask的功能,可以在执行时屏蔽某些fiber。这个功能就是用来处理分支的。当遇到分支时,先mask掉应该走false的fiber,执行true的分支;再mask掉应该走true的fiber,执行false的分支。这也就是前面说到的,需要shader在分支两边各执行一遍。这个功能称为"Divergency",不止用在了处理逻辑分支,还用于处理很多其他功能。比如后面会说到的quadoverdraw。但是,分支条件分为几种情况:常量条件:这种在编译时就会被优化掉,是不会产生分支的。Uniform作为条件:如果同个wave中的所有fiber都是走同一个条件分支,理应也是不会产生分支的。运行时的变量决定:这种产生对于性能的影响是最大的第二点是容易被忽略的另外,不同的GPU架构对于divergency的性能敏感程度是不同的。从原理上我们可以猜到,每个wave中的fiber数量越多,divergency的代价就越大。Adreno中每个wave中fiber数量较多,因此收Divergency影响会更大。在SnapdragonProfiler中,通过这两个指标来观察Divergency的情况:%ShaderALUCapacityUtilized、%TimeALUsWorking。下面是这俩指标的含义:%ShaderALUCapacityUtilized:重要指标。当存在Divergence时,此Metrics会小于"%TimeALUsWorking".比如%TimeALUsWorking"为50%,"%ShaderALUCapacityUtilized"为25%,那么意味着一半的fibers不工作(maskedduetodivergence,ortrianglecoverage)。%TimeALUsWorking.SPbusy的Cycles里,多少比例的CycleALUEngineisWorking。一个Wave即使只有一个fiberactive,这个Metrics也加一。这点与FragmentALUInstructionsFull/Half不同。也就是说,Divergence的比例=%TimeALUsWorking-%ShaderALUCapacityUtilized移动端不需要太关注Overdraw"Overdraw"字面意思就是「画多了」,做多了不必要的工作。通常我们说的overdraw是指多个三角形重叠,先画远处再画近处,远的像素就会被抛弃掉,那么远的像素这部分的PS就被浪费了。在目前的TBDR架构中,在binning阶段之后,由于通过vs有了深度信息,可以利用这个深度信息来剔除远处的三角形(或像素)的绘制,减少后面ps的overdraw。这个机制,AdrenoGPU称为LRZ(LowResolutionDepth),PowerVRGPU称为HSR(HiddenSurfaceRemoval),Mali称为FPK(ForwardPixelKilling)。因此,在这个层面上,这个迷思是正确的。但是有另外一种overdraw原理完全不同,称为"QuadOverdraw"。SP在做PS绘制时,并不是以pixel为单位,而是以quad为单位的,每个quad是4个像素。如下图一个quad中画多了的那些pixel,就是quadoverdraw。一个三角形的面积越大,quadoverdraw的比例就会越小。极端情况下,如果三角形只占据了1pixel,那么就会有3pixel的绘制是浪费的,75%的quadoverdraw。Quadoverdraw在GPU的实现逻辑,就是Divergency。因此,在Adreno这种对于divergency更敏感的架构中,这个问题会变得更严重。Adreno架构一个wave最多只能处理4个三角形,但却有128个fiber。极端情况下,每个三角形只占据1个像素,那么128fiber只画了4个有效像素,浪费了96.9%的算力。因此,我们需要尽量减少微小的三角形的出现,最直接的办法就是使用合适的LOD。这不仅会让绘制的三角数量更少,减少带宽使用,还会让绘制效率更高。Arm对此的建议是,建议每个三角形至少覆盖10-20个fragment。-Usemodelsthatcreateatleast10-20fragmentsperprimitive.验证QuadOverdraw,一样还是用%ShaderALUCapacityUtilized、%TimeALUsWorking这俩指标。Divergency比例=%TimeALUsWorking-%ShaderALUCapacityUtilized为了排除shader分支造成的divergency的干扰,可以将shader都替换为没有任何分支的简单shader。Shader复杂度当我们在说「shader复杂度」的时候,我们是在说:Shader静态指令数Shader动态指令数Shader使用的寄存器数量分别讨论一下这几个指标过多过大之后的问题指令数量「指令数量」分为:静态指令数(staticinstructioncount)和动态指令数(dynamicinstructioncount)。静态指令数是指编译后的shader程序里的指令数量;动态指令数则是被执行的指令数量。比如,使用[unroll]展开for循环,那么编译出来的程序的静态指令数就会比没有展开的多。但无论是否展开,运行两个程序的动态指令数应该是一样的。Offlinecompiler统计出来的都是静态指令数,但是可能会也会统计出的最短执行路径的动态指令数。当静态指令数量过多时,cache就会装不下。SnapdragonProfiler中有对应的指标:%InstructionCacheMiss:不要让Shader(编译后机器)指令超过2000条(VS+PS),用SDP的OfflineCompiler可以看到指令数寄存器数量ShaderProcessor(SP)中使用寄存器来保存shader的上下文。如果shader的寄存器使用过多,当需要通过切换执行的shader来hidelatency时,寄存器数量不足而无法切换,进而导致执行效率下降。什么是"HideLatency"呢?就是当SP当前执行的shader发生longlatency时(比如贴图采样),为了提高利用率,SP会切换到另一个shader来执行而不是干等。这就称为"HideLatency"。是否可以切换成功的条件是,寄存器在保留原shader上下文的基础上,是否足以可以容纳新shader的上下文。如果某个shader需要多寄存器较多,当前的寄存器已经放不下了,就会导致切换失败。贴图采样、StorageBuffer访问都会造成latency,寄存器使用的数量会影响hidelatency的效率。因此,我们要避免同时贴图采样数量多而又计算过程复杂(寄存器使用数量多)的shader。当占用寄存器数量继续增大,大于on-chipmemory的尺寸时,会出现"RegisterSpilling"。也就是寄存器存不下来,只能放到systemmemory里了,那么性能就会出现断崖式下降。寄存器数量是移动端GPU和桌面端一个大的差异点。骁龙888是64KB每64ALU,而nVidia/AMD是256KB每64ALU。这直接决定了两端shader的复杂程度。桌面端的shader拿到移动端来跑,性能的下降并不是和指令数量不是成线性关系,而会因为寄存器容量不足,导致shader无法充分地切换、甚至出现spilling而执行效率非常低下。使用OfflineCompiler中的:registerfootprintpershaderinstance来看shader寄存器的使用数量。在SnapdragonProfiler中可以通过%ShaderStalled来判断shader的执行效率。当SP无法切换到其他shader去执行时,就会出现stall。%ShaderStalled:指没有任何executionunits(主要是指alu,texture&load/store)在工作的cycles占总Cycle数的比例。Memoryfetchstalled不一定意味着ShaderStalled,因为如果shader还能找个某个wave执行ALU,那么不算stall。%ShaderStalled意味着IPC(instructionpercycle)下降。移动端VertexShader是性能敏感的移动端,vertexshader性能敏感有几层原因:首先,在TBR架构中,跨tile的三角形在每个tile中都会被执行。如果开启了MSAA,那么tilesize就会变小,那么跨tile的三角形数量就会变多,vs压力会变得更大。其次,就是前面架构部分说到的,在Adreno/Mali架构中,VS都会被执行2次。在iOS中只在binningpass阶段执行一次VS,renderpass只执行PS。Adreno/Mali在binningpass中,并不是执行全量的VS,而是只执行与position相关的指令。一般里说,position的计算只需要做座标系转换。但如果涉及复杂计算或者贴图采样,那么这部分开销就被放大。比如,在地形绘制时通过采样高度图来计算顶点位置。第三,VertexOutput也会影响管线执行效率。这是跟硬件实现相关的。在Adreno架构中,vertexoutput是不需要resolve到SYSMEM的。每个SP中的LocalBuffer里有一小块区域是用来存vsoutput的,可以在ps阶段使用。但是这部分的区域是有限的,在8Gen2中只有8KB。如果SP中这块区域满了,就会出现vsstall。如果一个vsoutput是12个float4attribute,那么8KB可以装64个fragment。在Mali的第五代GPU架构Immortalis之前,在renderpass中执行的vs产生的vsoutput是会resolve到SYSMEM,在ps中再load回来。这样就产生了带宽开销。第五代GPU架构引入了Deferredvertexshading(DVS)pipeline,可以省去vsoutputresolve/unresolve的过程,对带宽有较大的改善。官方的说法是20%-40%的带宽。但可以想象,这部分肯定也是有存储上限的。总结来说,就是启用MSAA会加重VS的压力VS中position计算部分不要用过重的逻辑,尤其不要使用datafetch、贴图采样。要控制VSOutput的数据量,数据量过大,可能会造成vsstall;对于mali旧机型、iOS,会增加带宽使用。是否要做PrePass这个问题会比较复杂。我们重新看看前面Adreno的架构图:在Binningpass阶段会做LRZ(LowResolutionZ,也就是低分辨率的深度图)剔除。接着在Renderingpass阶段会做early-z和late-z。LRZTest是做低分辨率、primitive颗粒度的深度剔除;Early-Z、Late-Z是做全分辨率、quad颗粒度的深度剔除。对于Opaque物体,在LRZ/Early-Z阶段,会去做不同颗粒度的Test&Write;在Late-Z就无需再做测试了。对于Translucent(AlphaBlend)物体,不可以Write。但是因为有可能会被更近的深度剔除掉,所以可以Test。对于Mask(AlphaTest)物体,深度其实在VS阶段就可以确定了,但是因为可能在ps阶段被discard掉,所以在LRZ、Early-Z阶段都只能Test不能Write,需要到Late-Z才能Write。对于CustomDepth(oDepth)的物体,只会在Late-Z写深度,且不会被深度剔除。oDepth是pixelshaderoutputdepthregister,专门指ps阶段写深度的寄存器。LRZEarly-ZLate-ZOpaqueTest&WriteTest&WriteOffTranslucent(AlphaBlend)TestTestOffMasked(AlphaTest)TestTestWriteCustomDepth(oDepth)OffOffWrite有了上面的梳理,我们再来看看这个迷思。PrePass,在桌面端的传统做法,是针对Opaque先跑一遍PositionOnly的较为简单的Shader,生成深度图给后面的BasePass使用。以达到减少overdraw的目的。但是对于移动端来说,由于binningpass的LRZ已经做了类似的事情,因此移动端的Opaque物体是没必要做这个事情的。对于「移动端是否要做PrePass」的讨论,一般主要集中在Mask物体。Mask物体无法在LRZ/Early-Z阶段写深度,那么对于大面积的Mask物体就会造成overdraw。如果大量的mask物体穿插在一起(比如植被),开销就跟画一堆透明物体一样。并且对于不同的架构,Mask的冲击是不一样的。对于PowerVR的架构下,"OverdrawReducing"是使用了HSR(HiddenSurfaceRemoval)。这是一个Fragment颗粒度的可见性剔除,在binningpass阶段会输出一张全分辨率的visibilitymap,在renderpass中只需要执行ps而无需再执行vs。因此可以无视drawcall提交顺序。WithPowerVRTBDR,HiddenSurfaceRemoval(HSR)willcompletelyremoveoverdrawregardlessofdrawcallsubmissionorder.但如果遇到alphatest,那就很难受了。需要等到ps执行完得到真实深度了,再feedback到HSR。这个feedback是否会stall后面的drawcall呢?资料中没有明确地提及。由于需要根据HSR产生的visibilitymap去做renderpass,所以可以猜测就算不会stall后面的drawcall,也会stall整个HSR。对于Adreno/Mali架构,虽然Mask不会造成管线stall,但是混杂了Opaque和Mask的渲染pass,对管线的执行效率也是有影响。因此也是建议将Opaque/Mask作为两个pass来渲染。默认的处理方案,是将Opaque和Mask分成两个pass来渲染,避免Mask对渲染管线的阻塞。在默认方案之上,可以考虑针使用专门的简化的vs/ps对Mask物体先做一遍PrePass,然后在BasePass阶段Mask物体就无需标记为AlphaTest了,只需要将DepthTest设成Equal。因为Mask物体需要执行ps才能得到深度,因此需要一个简化到只做采样alpha的专门ps。总结来说,Mask的PrePass的意义在于用一批简单的drawcall来换取BasePass的管线执行效率、减少overdraw。PowerVR架构下,不会让alphatest的late-z成为HSR的瓶颈;Adreno/Mali架构下,不存在stallpass的问题,PrePass更大的意义在于可以参与去剔除BasePass中的物体,无论是Opaque/Translucent/Masked。但这批「简单的drawcall」当然也有开销,PrePass是否一个正优化,需要根据场景类型、机型来做perftuning。运行CS可以更高效地利用GPU现在都ShaderProcessor(SP)都是unifieddesign,一个SP可以执行VS/PS/CS,不会有专门用于跑CS的SP。因此单独地跑rasterpipeline并不会造成硬件利用率不高。并且在SP工作在VS/PS下,和工作在CS下是两种不同「工作模式」。切换成本根据硬件而有所不同。如果在VS/PS和CS可以「同时」跑在同一个SP上,这个功能称为AsyncCompute。这个「同时」是打引号的,是指SP上可以有graphicswave和computewave互相切换,用来hidelatency。在这个层面上讲,确实是可以更高效地利用GPU。但是对于结对同时运行的graphics和compute最好在资源使用上最好是匹配互补的。比如,好的匹配:Graphics:ShadowRendering(Geometrylimited)ComputeightCulling(ALUHeavy)坏的匹配:Graphics:G-Buffer(Bandwidthlimited)Compute:SSAO(Bandwidthlimited)目前iOS是移动端支持AsyncCompute支持最好的。Mali也是支持的,但更多的细节需要测试。但Adreno到目前为止还是不支持的。GPUDriven在移动端的技术限制GPUDriven是在桌面端一种非常细颗粒度的三角面剔除+合批方案,但是在移动端鲜有应用。虽然其目的是「GPU性能换取CPU性能」,但在移动端对于GPU的性能冲击跟CPU的性能优化是不匹配的。究其原因,主要有两个:低效的StorageBuffer随机访问大量的smalldrawcall,以及instanceCount=0的invaliddrawcallGPUDriven中,"PerInstance"的vertexstream里存放的是指向各个storagebuffer的index。这些storagebuffer存的是InstanceTransform、MaterialData、PrimitiveData等信息。在VS中通过index索引这些storagebuffer获取有效数据。原先这些数据都是通过instancebuffer(vertxstream)或者uniformbuffer来获取的,在on-chipmemory上就可以很快的获取到。改为storagebuffer之后,这些buffer一般都比较大,on-chipmemory和L1/L2都是存不住的,大概率都要到SYSMEM去拿数据,因此效率很低。尤其是instancetransform,是会参与vs的position计算的。在移动端就会导致这部分计算要跑两遍。地道的GPUDriven会做cluster级别的剔除,一个cluster可能是64/128个三角形。每个cluster作为一个indirectdrawcall的sub-drawcall来绘制。如果这个cluster被剔除,那么这个sub-drawcall的instancecount就会被写为0。这种处理方式,一方面产生非常多的smalldrawcall,另一方面产生了很多的instanceCount=0的invaliddrawcallGPU有专门的用来生成wave的组件,Adreno的叫HLSQ(HighLevelSequencer),Mali的叫WarpManager。这些组件会预读取若干个drawcall来生成wave,以便可以互相切换。但移动端这块能预读取的数量是比较有限的,比如Adreno8Gen1的HLSQ就只能预读取4个drawcall。如果这些是smalldrawcall或者invaliddrawcall,那就导致喂不饱后端的管线。最后破除迷思的办法,就是摒弃神秘主义,知其然知其所以然。希望这些可以为我们后面的优化带来更清晰的思路和方法,而不是盲人摸象。贯彻费曼学习法,这也算是我自己个人在这方面所了解的东西的一个总结。但作为应用层,终究只能在厂商构筑的黑盒之外摸索和猜测。所以难免会有很多不正确、不详尽、过时的东西。望见谅。ReferenceAIandMemoryWallArmGPUBestPracticesDeveloperGuide|TriangledensityArmGPUsbuiltonnew5thGenerationGPUarchitecturetoredefinevisualcomputingAcloselookattheArmImmortalis-G720andits5thGengraphicsArmCommunity(2013)KillingPixels-ANewOptimizationforShadingonARMMaliGPUs.community.arm.com/ar...ArmCommunity(2019)ImmersiveexperienceswithmainstreamArmMali-G57GPU.community.arm.com/ar...ArmDeveloperDocumentation(nodatea)ArmMaliOfflineCompilerUserGuide.developer.arm.com/do...ArmDeveloperDocumentation(nodateb)ArmMaliOfflineCompilerUserGuide-MaliGPUpipelines.developer.arm.com/do...ARMTechForum(2016)Bifrost-TheGPUarchitecturefornextfivebillion.docplayer.net/907458...(Accessed:October26,2023).Beets,K.(2013)UnderstandingPowerVRSeries5XTowerVR,TBDRandarchitectureefficiency-Imagination.blog.imaginationtech...Davies,J.(nodate)TheBifrostGPUarchitectureandtheARMMali-G71GPU.community.arm.com/cf...Frumusanu,A.(2019)Arm’snewMali-G77&ValhallGPUarchitecture:amajorleap.www.anandtech.com/sh...GPUFramebufferMemory:UnderstandingTiling|SamsungDevelopers(nodate).developer.samsung.co...Hiddensurfaceremovalefficiency(nodate).docs.imgtec.com/star...(Accessed:October7,2023).ImprovedcullingfortiledandclusteredrenderinginCallofDuty:InfiniteWarfare(2017).research.activision....Lifeofatriangle-NVIDIA’slogicalpipeline(2021).developer.nvidia.com...Mei,X.,Wang,Q.andChu,X.(2017)'AsurveyandmeasurementstudyofGPUDVFSonenergyconservation,'DigitalCommunicationsandNetworks,3(2),pp.89–100.doi.org/10.1016/j.dc...PowerVRDeveloperCommunityForums(2015)AlphaTestVSAlphaBlend.forums.imgtec.com/t/...Qualcomm®AdrenoTMGPUOverview(2021).developer.qualcomm.c...(Accessed:October7,2023).Qualcomm®SnapdragonTMMobilePlatformOpenCLGeneralProgrammingandOptimization(2023).developer.qualcomm.c...(Accessed:September16,2023).UnderstandingandresolvingGraphicsMemoryLoads(2021).developer.qualcomm.c...(Accessed:October7,2023).Wu,O.(nodate)MaliGPUArchitectureandMobileStudio.admin.jlb.kr/upload/...移动GPU架构|wingstone’sblog(2020).wingstone.github.io/...
|
|