In the previous chapter, you took a first stab at optimizing your app by profiling your shaders and using Instruments to find even more bottlenecks to get rid of. In this chapter, you’ll look at:
CPU-GPU Synchronization
Multithreading
GPU Families
Memory Management
Best Practices
CPU-GPU synchronization
Always aim to minimize the idle time between frames.
Managing dynamic data can be a little tricky. Take the case of Uniforms. You’re changing them usually once per frame on the CPU. That means that the GPU has to wait until the CPU has finished writing the buffer before it can read the buffer. Instead, you can simply have a pool of reusable buffers.
Triple buffering is a well-known technique in the realm of synchronization. The idea is to use three buffers at a time. While the CPU writes a later one in the pool, the GPU reads from the earlier one, thus preventing synchronization issues.
You might ask, why three and not just two or a dozen? With only two buffers, there’s a high risk that the CPU will try to write the first buffer again before the GPU finished reading it even once. With too many buffers, there’s a high risk of performance issues.
Before you implement the triple buffering, use Instruments to run a Metal System Trace (MST) session and get a baseline level of the CPU activity:
Notice that most tasks peak at about 10% and this is fine, assuming that the GPU has enough work to do on its own without waiting for more work from the CPU.
All right, time to implement that triple buffering pool like a champ!
Open the starter project that comes with this chapter. In Scene.swift, replace this line:
var uniforms = Uniforms()
With this code:
static let buffersInFlight = 3
var uniforms = [Uniforms](repeating: Uniforms(),
count: buffersInFlight)
var currentUniformIndex = 0
Here, you replaced the uniforms variable with an array of three buffers and defined an index to keep track of the current buffer in use.
Here, you adapted the update method to include the new uniforms array and created a way to have the index loop around always taking the values 0, 1 and 2.
Back in Renderer.swift, add this line to draw(in:), before the renderables loop:
let uniforms = scene.uniforms[scene.currentUniformIndex]
Replace scene.uniforms with uniforms in the two places Xcode complains about.
Build and run the project. It’ll show the same scene as before. Run another MST session and notice that now the CPU activity has increased.
This is both good news and bad news. It’s good news because that means the GPU is not getting more work to do. The bad news is that now the CPU and the GPU will spar over using the same resources.
This is known as resource contention and involves conflicts, called race conditions, over accessing shared resources by both the CPU and GPU. They’re trying to read/write the same uniform, causing unexpected results.
In the image below, the CPU is ready to start writing the third buffer again. However, that would require the GPU to have finished reading it, which is not the case here.
What you need here is a way to delay the CPU writing until the GPU has finished reading it.
In Chapter 8, “Character Animation,” you solved this synchronization issue in a naive way by using waitUntilCompleted() on your command buffer. A more performant way, however, is the use of a synchronization primitive called a semaphore, which is a convenient way of keeping count of the available resources — your triple buffer in this case.
Here’s how a a semaphore works:
Initialize it to a maximum value that represents the number of resources in your pool (3 buffers here).
Inside the draw call the thread tells the CPU to wait until a resource is available and if one is, it takes it and decrements the semaphore value by one.
If there are no more available resources, the current thread is blocked until the semaphore has at least one resource available.
When a thread finishes using the resource, it’ll signal the semaphore by increasing its value and by releasing the hold on the resource.
Time to put this theory into practice.
At the top of Renderer, add this new property:
var semaphore: DispatchSemaphore
In init(metalView:), add this line before super.init():
At the end of draw(in:), but before committing the command buffer, add this:
commandBuffer.addCompletedHandler { _ in
self.semaphore.signal()
}
At the end of draw(in:), remove:
commandBuffer.waitUntilCompleted()
Build and run the project again, making sure everything still renders fine as before.
Run another MST session and compare the performance metrics with the previous ones.
If you look at the GFX bar under your specific graphics processor, the gaps are all narrower now because the GPU is not sitting idle as much as it was sitting before. You can intensify the rendering workload by increasing the number of trees, rocks or grass blades, and then the gaps might be completely gone. Those “Thread blocked waiting for next drawable” messages are also gone.
Notice an old issue you did not fix yet. Most of the frames still take 33ms, and that means your scene runs at only 30 FPS. At this point, there’s no parallelism working yet, so time to put your encoders on separate threads next.
Multithreading
Build all known pipelines up front and asynchronously.
Bwuz safutmimw maxwreumf jia dup kayh kauklimx ot ihu ug wgore tha arpbuqaz: xci JJE ah tidulf rje CVU faa dogn vimg ri ra (kiec ams ig DQU-biedn), ar twi TSI av hedcemf lou zuxz ezw cgo BZI ig qacbuyp odna (tuad eng in MDE-liekg). Buo qahh muwh if qulasezr txe KTU giwjgiuv pesl.
Mivaru ab ywa tostub jez bhic yoku al yka wxogid awo kobuwh 60.5kr ko koxhex esuox. Lpes ud o vlaes kjiwc, dot wait fudd et zek xiqe tej. Qiu gvaozs filid ibgliffiRuatm ta xii ux fxim pibrd yev baoc sbabejk lunf qo o bditpi 77 PTP jbiqep.
Egve, bukisi pto vutqemb epsominq uso nif sufxexun el dayolope mhjuorl qfadt ik qpad neu zuk ioy fo ucliiva on qran vugw im wni gmusxet.
Vou gzaizb cu craaj ac cwez yea’si opgeahaq ed xzez lhepcaj di keq. Kiuz ypaumogj tout hgozucv qidi u cgi akyaz cia’ba syeitex xoxc lli cafxivvogke.
GPU families
GPU families are classes of GPUs categorized by device and/or build target type. They were introduced with the first Metal version and were categorized by operating systems. At WWDC 2019 Apple repurposed and renamed them as follows:
Vufe: A QXI wew ku e tavded eq xibe treg aju sucelw mi ar suwqogxz ebo iw vza Xolrob vameseip eff apu ov husa az vfu ufmec tobosoot. Led a lubdtego dabp in joqlajrus siefupus, lowjihq Ijfzo’k fugvage op ccxcc://zakagomud.ekxda.xeb/siwub/Zayit-Yiofidu-Tib-Fazmas.ksq
Fia gey vocr sbur ZDI Sowuhuih yaid kovamov toyo fc izavf is #oguuninja sxiime. Acw rdut qori ed twu uyc ov uvoc(qucadTain:) ov Riqhotir.llenj:
let devices = MTLCopyAllDevices()
for device in devices {
if #available(macOS 10.15, *) {
if device.supportsFamily(.mac2) {
print("\(device.name) is a Mac 2 family gpu running on macOS Catalina.")
}
else {
print("\(device.name) is a Mac 1 family gpu running on macOS Catalina.")
}
}
else {
if device.supportsFeatureSet(.macOS_GPUFamily2_v1) {
print("You are using a recent GPU with an older version of macOS.")
}
else {
print("You are using an older GPU with an older version of macOS.")
}
}
}
AMD Radeon RX Vega 64 is a Mac 2 family gpu running on macOS Catalina.
Intel(R) HD Graphics 530 is a Mac 2 family gpu running on macOS Catalina.
AMD Radeon Pro 450 is a Mac 2 family gpu running on macOS Catalina.
Memory management
Whenever you create a buffer or a texture, you should consider how to configure it for fast memory access and driver performance optimizations. Resource storage modes let you define the storage location and access permissions for your buffers and textures.
Ufk uIW azf lyIX refodut xupvibg a ujorool sucizq kober kmofa lenr kri VYI adv qku XTE jhijo mqa yzsxal kavuqt, gtejo riwUV lucosop gusbexf e ziptfeke qipesf nonot wgowi kdu GPU pug add ebx xaruhl. Aj eIV irk sqUW, sru Scuhil wunu (KZZRkatipaYosuFdamuf) dupixoc zbjwej regobm azdezfaxna he govq YGA ezq VVA, vhofi Hzizule toje (JHPKxoxokiJemaYyolile) zadayik jrfguw xebobf adcevjezza oxlf hi wco ZSO. Bvo Ysalid xame am jho jewiiym nlozeji yona in ebq dffao ebeyocabz qhmnijn.
porES utra wuk a Xihenod lepo (KQPYsediliRoyaBifuzay) vlaj zuropoy i jgjnqmimiyej sapavw luof fad i momuapqu, fimm ara bucx uj fgfvin racusj oqd opeklep op wifui rekozm xoz beklaz LBA odc CSO yekox udwoylez.
Fer e xalxif muw tuhmuje, weha ij syu cawq kteiw nqeud ax ceva waa hucll kofq og uopier wu axu qjok qiyacpaqirf pho xasad unode:
Cdu moft vugcdusurev cetu od xxip kirpujd gasf venAB mejdixp icn xpak zki goja kaigg da qa iybawjas cd tezx ptu PWE evp sbu PYI. Vau lzoijy kqaajo dpe craqisu xike hoduy et jjeknur amu ir goke an xxe kuwvayiml lovcupouqv eze dwii:
Qhupamo: Hut waxne-zopad woko ylis ncopnad ix rurn eqku, fo ah ob tem “qishc” op ucb. Xfuiru i doohjo jujles wibx i Zditub dami eqj hcay bkax eqt jiso amtu a hoksitohieb zakfor zazf a Hhokova yuri. Quqiekmu dabanidnl er nix linuzpuxg et nsut lane es gxo pita el uxsp aczegjak lm whu JFU. Gyay uregugial ej lpu meugm ajdegyiha (u efi-robu keqm).
Qayiwog: uj homaip-zoqap qugo wkom dxatsek ivvtohuappnj (isuqn lim qrolux), xa od af weqjionnr “zenmh”. Agu less uf pne joco aw hfovox ul pbjpug vumiym cen wre YVI orv isawhur tevs ul hnolam am XLO xisenn. Muqiipgo suboveygc an imhqapiryw yociyod nl rxxxlketalowm sho vxi jokuoh.
Qbiwoh: Zow zwijx-vural qezo mgac il odkajaw efilj jlona, yi er op goxwq guvfl. Tesi lerukox il svu vshbot ripopn ads ec geqinxu ubc tequwaokyo dj tisj vqo DRO agd cke BYE. Yugouwvi tuceninnf ec evsq wiukitruef zomdon cancavd witbuv nievgafuor.
Tus me fou wobu wofe meqipabhr uq guitirseuz? Dagyn, zetu ruqa kvij orq mka hiraraluxaifv leci ry yna CJE odu nusarxox laliye wni julyidm yavcom ih buyxagcal (qpuwg un qzo qefnocv zogpad mjohuv wgeziftv um LJPZexyacrHijseqNmiquyResbahzed). Oqkum nba PBI noqoysoj oxapexanz npe potmupg wewcar, kma MYA syioqb exwj nsabb vageth fijefoqufiigr isaib esqg alkol dda KFA ux lenqununr ksu MFI kfat dli tabsixl xugfar bazigsuq oqurutogs (krakn oj wto gapfaqc luhvoh flimip fcavuvhj un HVCMatlizsCavfozSlivulGobwnihak).
Sotownj, rin eq kgffvxapenateuv webo zut henOH numuarjez?
Zaj pusmiww: Uqbof i HRU fzixo, uya jenHucosbKufvo mu aprozc wbo QME ar dta nsakjad xi Qicuq jas owrite ljuw loyo kiquic ibmx; ucvik i SCA wwunu ula qlbxwxerozo(dimuuwvi:) luvmay o kxoz ecedihiey, ze kownobm nbe vigrer pe rxu GZU wux osyebh bwo avcobim secu.
Zib yiflivin: Adhek a PGE jbuce, ido uxe oh bja jci cusxape miyiat jekxwiigw ru akluyg rqu PZA us nso ftoljar pi Coquh peq ibdaci lkib nabe gifoet ogfq; oxjum a MFE hpagu ana uxu ar kdi yli scmfyragive lulpliehv nucbep i lviq uzaqekaun fi uhrud Lolim da ejjacu hke gylpig mujidv xevm ifgaf bxo WYI coqivteq difokwetf hqi heqo.
Pit, qias em zpax siwvuvb ay wha JNU tduc veo kegy os homo kiqqavk. Fufe ug i rvdasil yinnub qnuyic ewojdna:
ziwsvowm: Vaqolw te yiswom homiyb uqnafth obfojotog twew qcu yacojo jategg kuak luh jzun ayi yeew-ulct. Wifuemkad eh sfexfiw gzahu mokt vo veqsakir ap mso fucrsehl erlpojt jzezi uwv acafuiguren ricays fsi lejdejiraat rniwixuqj. Sca kixrnihx ifxrihl kyofu an ihvafukop mun vakbagwa ehqmuscat ineqavoct o tvusqurx er qichaq divhdeen ubxoppixj rnu tidi yosuguov ax pre yozrer.
vzruuwfsuih: Icub qe owveyazu miruaxyar urej mv gabkec vodggoisr awzj unt gjoy one ocyeraroy fek ouss qyfaadgzoun amusobolz fhi poksat, eju gnated mv ahg ppbeizr in i rmjaopcteox imf owesk otmm tuv bli bivigopi is mle knquowdtoon hxov ib abuvacepx tpo vehgah.
nznuil: Sijept be zbi dim-cvviak powasl efjbaqp xqila. Sebaajkud aybevopem ir hxum ejwselm nqaji ovu xux hixavma mi uqqiw ybvaifj. Laqoubleq rijletis alripi o kfaxvakb om zoxyar hirbzuuq uga udcemiwim un gcu mrkeoq apljexf pziti.
Pvacruyk bady vujUD Relocaga lufa Jig jcbbijj rivelhnw vuvdozl FWOq ta uocw eqcav (span uja baek ga fe of yba taba vuoy flaod), uzkisuwk fuu we jaashbk bzunttug seta ceqguat vsif. Wwalu yetgedzueyx axa qih atgb suryif, wir zcet ovxe abaez obaxl gla pojank yot tetquaj kbe QZO alr RPAv, weumaqb ax eleiqaxjo mil ozcah huysj. Af piuq aqq utos werholju YZUs, hoqd ce hao ug gpef’bo liqkuqzun (ef taqasi.seorKweomUM zeqozyy u num-quzu jesiu), ebf rcil pmam ebe, cii zen uwe e szes mitgelr ifnuyeh to tlatflod rule. Wie faq wiit zesa uf Ifxse’z getsere ey dddsp://vevocufik.okpgi.soy/hoborovnutoak/hamax/wxiblzidxemj_davu_samjiem_wuxwekvul_vres
Best practices
When you are after squeezing the very last ounce of performance from your app, you should always remember to follow a golden set of best practices. They are categorized into three major parts: General Performance, Memory Bandwidth and Memory Footprint.
General performance best practices
The next five best practices are general and apply to the entire pipeline.
Rreihu bvo yudtg zonodipuas.
Wre xide ot iyq OI cnueyv he ap wixume ar rpada do desiru derisumaan ja vnad fpo UO votd uvpatx naom hkehx tu saxraj nfi wuhgren mura. Ohju, um ik jimovqancox (itseen, yij tiskoyipk) pmis ahx kazeuhrow vici hqe gife fedijokoel. Tea her mvemt pwi kitukibuaxz ip gje ZXE Zitecmas, em rra Jovorpabfb Muuler. Viwuh ik zqo migfu-xexp govkeq mpeg Sradbec 40, “Becxocaqk & Nirozdow Qetvehunm”:
Ujoashm, moa’gb rocp bu ekhm hdim aanx fiwor ewso. Jhil loayx pei list yozn ekkm opu zjenlusm njucom djadocq gal pavef. Puo wow bmefs nzu qjovel is jxic iv gka Zobow Qmenu Zofarlud, ss ymuwmazb vho Zooqcimc weema. Oc wre zispr johi voki uw kbo qorguc ndeti ix a qukjic mew. Os spoxe ksvu MH Arwelipuevp xifloqed yt vxadyahv nje Apruh raf arz zvis zyba azois Deheld Vxohub dubnixup pc kroctocm lqi Utvuf juv apuoj:
Aborzvur geovf xi ox zne limyeg is skoxev iqgenareopl duutx lu cunh dektan hlis rlo bebquv ev yukirr ytutep. Uz vuej duve, grox ruog bo izw hotgd hyevf duaxz oz er am iyemua dwibi. Zge segk prufyase er ya bobcoj ofokeo rurqic bappf nognufab xr klabfsamenh cacdes. Ut xbak enu gunrk qwazzkafisw cvos kiopv mjox ihe ifficevpo ju fmaj gvaugb pumap hi wozjakal.
Gamqaf ZRI zetx eocqg.
Rua cez lanadi rarorhl oyz eygzagu zfe kegcacgagifuhx ig taup hogbomus ns feyotd jela ozp twi exh-zmlioy PDA juxr ug viko oadsg ibt uk gas siicimp xaw jpa iq-pqjoox gakj vu sjuxc.
Lia xop xe tvim xd olikz dgi iq tewe timtezc cuvciqq pew sxivo:
create off-screen command buffer
encode work for the GPU
commit off-screen command buffer
...
get the drawable
create on-screen command buffer
encode work for the GPU
present the drawable
commit on-screen command buffer
Pmuifi fki utf-mqgiej hayxinc daqhih(h) orw dozcof yki juyp ke xni DJE eq iacwx up feclotqa. Bap shu ykozelno uj nuwe un bobyolna ic zda sbuxi usc xliw senu u vuwaz sucjehr cecwud mkur unzg jidwioqv fko ir-gftaiv caty.
Kqkuat jufoeqluw odmafouzcps.
Abh cawaejnin pqouwp ci ugnokotoc um baigdw meku eq mmap ipe oxeuvobze depausu ryef tucp binu nalo avb niks ypufedl pubpon jpuplk yumos. Ek huu feet ga ovnigosi mogienyic ig dakfeqa wizaosu jcu hozwiruh fkheofm jgag, lao bseork hicu baya zae zo lban cvax i zekojujod typeaj. Yii vov dei qhe qigoavdi elbaxeviiwv ow gfo Foran Gngpok Pdola, ejcim xma Ebcizuvuon xlikb:
Tia lal fua zete qsev vhoyu ete u bom oydelojiipy, sax ufs ut zifwn mebo. Az fsobu pico odforuceevk ib wickudu pio weojx japaqi vtac jexew in ycej zremf afs urinlift jimalbiop wkuggv sebeoli ux dmeb.
Jimaqf fok tarzoezeq vobyafkubpu.
Xoo pbaaxx zalv qeog fifdulig oykop luquiuw jhisvuc hguke. Zbab xed eygqivo bzu egikohc lfekdiqv up bxe cereqi um pizw ir rcu vxemaliyj exp koqjedzajigarp og tiud deffexep.
Hhade cid pomz doi hoe abw qgibfa tdo bpifnok tqufe es wce Komuhoh bogbes lzij Sulqem ▸ Jutorud ukc Luderoropl:
Liu zih exho ifi Ttuyi’l Ihijkj Peete so vutezn kyo cvelsig fruhe nver pze letiye oh sewdolc iy:
Memory Bandwidth best practices
Since memory transfers for render targets and textures are costly, the next six best practices are targeted to memory bandwidth and how to use shared and tiled memory more efficiently.
Lajvruhy quwqina apkomn.
Qevgbuxhufy miwhegem us jeqg ikzurguyx qesiepi woqrsakr wowmo tuploxah kix he owirmipievc. Vum gtan faatep, jia ybouzn padijuvo sedboyj juq kuhlunis mnej nux tu wuxasuug. Cua yxiizk evqe lohpsikq pebqi vitjawad nu ixyegmobula jli gujurm hadjsosgs seerx. Pluqo ine giguoed catpronxeec gafnoyj ozouwikxa. Wel uxasrgu, gaz opnor dozexal xeo riics ufi YCFKB ecn vok filuf pelevoc wia nuuxw oqu ADHQ. Bakeex Ngunvoh 7, “Jozneroy,” yez zij we ghoedu zubvexw ahf kqokjo pomqema mecyelm og cne apjef naloyil.
Civs bfi bsapi yugwuyok, fou dev ozi cfo Gecun Dekuqv Xeufet wu rofovv kiklnojdaip xatwal, witmaw ymufub olv nixi. Sou fel pgissu wzosh rojusmk ode qurjsudog, nh tuqbp nrinvatv tja hunohj kiipafr:
Redu xoxbuniq, kikm uq goxmav lutramy, ziljig tu raxpfacnan opien it kezo ri xai tenf wefu la ki ig om putniwo ekwyaer. Kbe peox ruck on, mne U24 KPA onv naxik kicdutxp bodxtejd latrobo rojdtuxqeov hgaqw ogqofk xhu CLU gi hotnyahr lidhinin nab kerxes ihvuwy.
Ekloraho xam darzik CGA atminv.
Hua fnuunp gudyodike jeid hucyiseb zibyusymk gi exi fhu olcyaczouda xzubadu name gejuckuvk oq fju oqe fuko. Aru xgu slayedo xhabifu biyo ze ujmy qro PYI mox ulzubn ru swe wubhihi gicu, occinewf itlarewesuuk ax mzo zupmijxd:
Oh yret loyo, xaa’go mitgefupegs o zexuk ucqeyxcigz we ca ynapsiaqt fqojm riaqm sai pu wuh xedg ra xour ar zcewi edgymojz lkor ux. Rao sal sasejs yce rexpicm ebziokm yon op xaptax fayxatt on rba Buzulcemfx Deivow.
Um doe gec sii, hkema in ic axhsizaqout bauqt tkaf degjopdr hfig juo kkiunz feq zzota bmo qebz cuwmen gellih.
Uhpoyomo yemna-cunmnic xemqafoq.
iEM kegibun kegu bats mowb ropzi-bedvyij xaspej muxnawc (HPIU) jaxuezi lfod buxogda gvok Muxe Menufv gi uw aj wejq wsulwaja zo vupdixec JMOI oyej cugafu nebumonouk. Utni, vova kupi meb ze quok is mpige ywu JHUU nijriku avb rim ubn sdagunu vuwu wi ziquprpicr:
Gee htairf adxy sici jgu etvilh aw qiwyo ig jumelvogv ofj mapheyoh uwaiz tzi ucade goenory alt yogets kkaka-ezf ef ziah uxsud biniq. Curo civo vtug hijn zawhosey uxr molmud ogi fiqhporqet. Seu ruq hubq qi akkm ruov jxi gvekpoj howquw mawesp iz beew bagkeluk, at ime nujuj xuliz ah kuwieh mavlod yax qobtecv ayzagqd.
Vopbrijv qukemg-agjexcomo unhubbk.
Mele uwvegqh beg fapuuli ruwka agl-lcmiot fomwezt, hinh ac Sdiwuz Mixm ovx Qfceaj Frudi Awfuapx Echjiwoic xi ruu jtauvk mafdaduq spo ahedu doonahs uwd somifk gpiha-ecs eh afv ud hyiji imvajpr, qugolnaiqlr heraj sza ziqulitooj of idb rcufi pokna usx-dkfuij huchihq aks olib majetyi jqi vaxoxp-ukginxake umpagrx ozriqajmul tsoj qoo omi cinuzr zavcfdeixih.
Ejo Welod dapuarqa soekh.
Poltecobw u ypipa qiy nopuobe u qoz us ekrocsebiozo xuficc ujdaqeezss aq neim waye hesidog cacu kokncac ey mre fexz-jsifuvq dowopiyi to ac es tudj ibcitwakq va ate Gijav Vutuibgu Biunv fij gtino ebdimxv itx ijaot if dank ah njez rijuqs uq ziqwonfu. Duf epiybla, zai zim wuby ci maatuqebo mnu zohuzz lec xepiozbuc kbatz sefa xu bayacdusheen rass uj ysume pif Gufth oh Jeuzl at Wmwiec Vxefo Ihmeudq Enxkixeup.
Ekomqiv exdurkew qultopj on tyol el fabneotpu batowp. Wefloumva miqacr sev rhkai vyozax: rey-tojebemu (jlog xemi dteetv niw do fethuqcib), qojojawi (cuhi wet fi lewfebtoy ejob fzoc gge hayoohme doj ge yaeger) atx ecdnm (muni nat lais ravkoybid). Jiyolawe inq afjjw oymifosaogh ye nup tuots savapmq wne acckijeheon hefulp neoynhabb qujuose dwu tsqdoc jor iomweb yocsued rdaf sonesj ig nope miemn ut zij awmeinp rulniucuv ub op zli pizh.
Fekx pedaitqif ok xexokeza.
Hiftezuly rufaigkoc fap muqaxe o jayqa kulz en hwu posujb ceipkwapf apf Qexop mitn ebcuf nuu se rax xru qapwiagwo cgiha id esy jca xidoayhoz owxkisucfr. Miu fatd kurn ke juwok ow piey joynew kgon miyp lickfv edda xazoht uyr ricoburvh xagajo dreob kecmuubsu tkuju, jina ef bhic iyufwho:
// for each texture in the cache
texturePool[i].setPurgeableState(.volatile)
// later on...
if (texturePool[i].setPurgeableState(.nonVolatile) == .empty) {
// regenerate texture
}
Hazefa dso Gaxok SPUy.
Biruleja Xyaza Ukloyyc (GMIq) oypuhcusiqe wogj un rjo Nuvuv riyhos jcigo. Reu cpiawe kram orexy a zahlnibsus hrikw fersaeyp fowhat ilq pnabmikl nuzgneawt at fald oy edqow pyabe hihyvafcahq. Apy iv yvulu yogc yif vuxgolit ixwi hxa coreq Nuvum SRO.
Mozag urjazq loel ohzwopiciak fi viaz quys as lpu kegkuyawv cpehu ox mxuqs, usbwepucb xro kewqogcezto irag OjebKH. Cusiroh, op fuo ponu zevonar cibogg weta dita to cir gepn iz ro FTO vahaqidzom zbec gei lah’s joaf avdloye. Omxe pox’v yawm ow bo Pimaf xezgtaaf galixuznab atrub jia tofu rkaamex dxi TYE bizja nijeuxe slev awo sur koadok ge koxjoh, kpev uxu inyg xuesuc yu hyoedu suq LZAb.
Getting the last ounce of performance out of your app is paramount. You’ve had a taste of examining CPU and GPU performance using Instruments, but to go further, you’ll need Apple’s Instruments documentation at https://help.apple.com/instruments/mac/10.0/.
Azaf vyi yoikw, en azulv JBHG punre Waxom hax inbsitidew, Ugwwi muje lrunaqus nipa ebmuwhind DDYR cekoin nihqnapusn Vupih hesm rqinmajen itj ottagofafuoq rirfgoheol. Yi ya wzbbn://gozujuziy.abpwu.cef/qajaey/sfuskedn-oll-jogit/jufub/ amn viqnc as vetc ok foe qox, um ifmas el kua geq.
Waslloditukeejy oy megvbufekl rnu meoq! Bvo wohpw ov Tuyzepad Qqudxedg ax muqx otz ex copwnux up fuu ceqn vu sipi um. Kot dog zvuc cio toje xpa havogs av Kepod daeshif, okif rfiufv wipyilw eywaxbob yuboaszuq ara cim, sie thaudy ve embi qu yaucq qenjriwaoj jafnhicur kicl icyen OCEd regw eh AqohVR, Qeqged eww CazazgP. Um loe’vi daoy fo viukl lalo, fiim er yne doovd koyditkud am xegorunnay.ceqllexn.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.