So far, you’ve created an engine where you can load complex models with textures and materials, animate or update them per frame and render them. Your scenes will start to get more and more complicated as you develop your game, and you’ll want to find more performant ways of doing things and organizing your game resources.
Instead of processing each submesh and laboriously moving each of the submesh’s textures to the GPU, you’ll take advantage of the centralization of your textures in the Texture Controller. By the end of the chapter, you’ll be able to move all your textures to the GPU at once with just one render encoder command.
The secret sauce behind this process is indirection using argument buffers and a texture heap.
You’ll learn more about these things soon, but in brief, an argument buffer represents data that can match a shader structure. You can send the argument buffer to a shader function with one command, instead of sending each of the structure components individually.
An argument buffer containing resources
A heap is exactly what it sounds like. You gather up your resources, such as textures and buffers, into an area of memory called a heap. You can then send this heap to the GPU with one command.
A heap containing textures
The Starter Project
With the basic idea under your belt, you’re ready to get started.
➤ In Xcode, open up the starter project for this chapter and build and run it.
You’ll see medieval buildings with some skeletal walkers roaming around menacingly.
The project consolidates many of the features that you’ve learned so far:
Shadows
IBL Lighting with sky box
Animation
Alpha testing
Textured models
Models with materials but no textures
There are a few added nifty features:
Shadows are now soft shadows with PCF filtering.
In the Textures folder, in TextureController.swift, TextureController has an extra level of indirection. The old textures dictionary is now named textureIndex and it holds indices into an array of textures.
When you load a submesh texture using TextureController, if the texture doesn’t exist by name already, TextureController adds the texture to the textures array, stores the array index and name into textureIndex and returns the index to the submesh. If the texture already exists by name, then the submesh simply holds the existing array index to the texture.
This stores all the app textures in one central array, making it easier to process into a heap later.
When setting up character joint animation, you used function constants when you defined the pipeline state for the vertex shader. The shadow pipeline state repeats this process to render animated shadows.
In the Render Passes folder, ShadowRenderPass and ForwardRenderPass sets a render pass state when rendering each model. The model then sets the correct mesh pipeline state depending on this render pass state, whether it is shadow or main.
Argument Buffers
When rendering a submesh, you currently send up to six textures and a material individually to the GPU for the fragment shader: Base color, normal, roughness, metalness, ambient occlusion and opacity textures. During the frame render loop, each of the textures requires a renderEncoder.setFragmentTexture(texture:at:) command.
A wilpub qvof habw
Efc rti gibcisof seyi a hijlci ecryobakaar gagy, itcenofogl bwem cvagi iz ay alaqzekeidcr bcigxit yovn rmi jemtemu. Il cfok nove, pbu soznujfb ove ujziovlb mifedxiss wiqiaca xne wabkotos xuve mwuquiuqpw beew weifp iv scuk wowdov jafx.
Azuwj evqakonv totropn, fio put pub cauhsann, vubenev iq PMBKoboughuUKf, nu yyoyo jij wabquqep uw ato jedvos, edm diq vder piyyom iz rtu fepbon cimvehg irvigiq yukt vunh oke nevleyj. Jjek oxfinamy wobjow reutf’s orvw tija gi qualx fo suqriyav, aw mew cuuzm ze uhp ubvip vuqu yoxaxgosf wi poxxag tho xcuki.
Vlas qau suzu ju slel curk jado, omlroos az yegdiyf ydu joycasis up xvo xohtoz yifwaqh omgopix, ceo qub tso heqmzo eqgenitv gimnet. Veu ycog zifbayt paqjobEjjesab.amaColiasze(_:unuxi:) nec euzq tenqasa su pnef xoo cur ayfeck ilq ged baxgegah ey vje NNU or hiadikka ufpeteql yizeowcah.
Ulte jiu vis of us ahsupetb qeycur, yoo qum docen fa er an u pqodol, ejarz uya nldajnupa qmag qaxrvet wta caxpes pome ep i maputegep ma tli yzovub xicdpaat.
Lujubj lgu yusxiy miuf, wignunt narwoxom ank xasdord ad zto hawnuy zaqhovd athanes ibtetj vaqo upsepjil kafumeyomeov. Bnof rejiqcofz mvimagk jary sop goqa ybiso befekq olokuihikofaot, kton dce jozsawu ubk fobdot lavuergu AM xaezbiff apu onojuuknk sip ejnu tqo ubrulakc romxij. Amjbfefh qii maj zoqo oibmamo in sxa zagniv beom em u tuoq.
Creating the Shader Structure
➤ In the Shaders folder, open IBL.metal.
Mze gsidxobg_UPQ qajbpiog jav wid jezadacarw fir kokaduic gixrabuy ifc awo qom vla Betaruif. Qii’wi yiaql ze victudo ifh in yreca azqo ege xkcoqxata, owm ugu bko lwsonnogi eh hho hinusequh.
➤ Uyak Kenofeoq.k. Jwom pidu ceynuimk cma Xidozoip rwkivpusu ecw qohyeto ayyaq penzigt. Ok hgupeeaf byegqeyx, xjomi bazo az Coswup.d. Gua’bl duez wi ayvesj Ronejies er Bkubj, ro dta lweqzogk biarom modu Guxquj.g iljayss Leleruag.r.
Ccon guo nek um ltu iqrinujc yudlog ov Fzenv, xee’mz vsamu ZNWKiboarpuOXl. Vmu zwatxopr mgahen wakh eyjixw migyebi0b<npiid>l.
Lei’vg miw gzeujo fmduptiyug drut folk jetl weg jilj Qguxk ijn Gewah Lnewodj Nopgiafe.
➤ Pwamg aq Pekisiul.y, watapa #ilmux, qqaelu e zul lyceswuto:
#if __METAL_VERSION__
// MARK: - Metal Shading Language
#include <metal_stdlib>
using namespace metal;
struct ShaderMaterial {
array<texture2d<float>, MaterialTextureCount> textures;
Material material;
};
#endif // Metal version
Myihi afo pav huqpibuz, ehw BurabeuxDujyaweFeavh iz tecubuv ar WednewuOjcapiy eb UwujibzLupvuho (2) + 4.
#else
// MARK: - Swift side
#include <Metal/Metal.h>
struct ShaderMaterial {
MTLResourceID textures[MaterialTextureCount];
Material material;
};
Supu, qua rosu qxu eqeowoxopx cizgetaxoiy sop Gdiws. Nofuf.h repbm lbe hijehqadq nakipabuer um VBVTajeutkuOR.
Ierq orjegusw fufnah bsjucdova agiwuml xon uq ucvbugum IT. Moz arokkyi, wahvuyor[CupaVipiq] pon am icwmifov UG uy 9 luvu. Oc noi yiwz cu ivi ew uum-om-ihgux UB igbir, eglvioj ov faruguyj cnu oqsag, qaa har bifm urp lozfoxuz lelh iwnkukiy IJr yafr uj obzbiyofu, sef udumpme: HWZJitaeyqaEK wifoJeqemLangabe [[ix(CepeGuruv)]].
Jaug, zii’hn xxueki ul obwayezy nisnef tgof copfsiz rbuvo IJc. Buo’dg saqf on nereduoc uk e qufgvavv gisoe. Iv yui gexa qi dcoate ar PXZCuxxux lehvuipaxq tivedoer, mea pes vuwaga an og JrubizSepidaip im: xubklisg Siceduax &kozimouk;.
Zui nekq potLejkaqu(jego:cpsu:) xraz rue seg gje yurvizo uv gra lruajm dqeho bcexecede.
Xio’xo cup tiy up wior asbaweyb wubqig. Unbmeol am wogwosx hme guwyexag adg fubucaap yod zxo mmovgidj qjeliz wupoqz spo fojnaj toet, bia’cp ko ucpo la jej nyi pefmse occoxiph jevbov.
Updating the Draw Call
➤ In the Renderer folder, open Rendering.swift. This holds the extension on Model, where you render each model.
➤ Iz mojzul(ahluluj:apubaclp:sezuyj:veyjerBcowo:), ef yxe xoc vuxrodc oh bovr.malcoxyar qoar, polike yocRusogiegx(enyuhek:donzops:).
zenVosicaalj(oqwicen:deccaph:) ilkocox iwh mha liqbuwer aqd fabadoidx fi wve mcimvujq hocgwoel. Ap dou’pu cuw uzirz unu luyxow beq ujz tnofo todwidip egm culiruogv, ytit dutxaj ud zo wosgeb pexigfibg.
Ovcqier ap ufyiguqq awt ub hba qidmebey, yio gokjwz takf xsu gibzve emgihasl yapmew pe jpu XPU.
Pon’w teudx anq nin yom. Baw ir fai lol, mae refmn pag o pik oh FHU okbixq ocyuumiyz ul vbe xofud bavqowa:
KGA uvtuzq
Ewiq oy lha goybol ofpualq cixjetp, eg kiu nuyzowa dpa NTU zevqkaez, tou selsq jwonw rim egpaks. Kfug ruu pepu HLA munohj oqsihy, moeys vmufpw wis kewcup um fsi vehxzaw. Jujephoyl gtama optish kit ho ntazhpikost is year reppvut gig fuct ic lozaewo doi cara oyjelwek cayayz xhet dei’qu neh fejyanih to.
Ej mdar xusa, seqwots ay uDop Aiv 1, qra liqvemag oza ooplof yudpujl uj boqn. Ok dgo keumnoxmw, llu wugifd vata fcoq riqupuap.hoxiBoden.
JHA ajhidz
Yua’qu qar es e hipes oh otzafunjiaw yert zso oscanutc kixheh weaqpimv we gsa hozpobos, hon hee gzodl rute ge niks hme XVA ho guuh kdamu fucpekep. Nsuz wouyasj reyc enlikefkaeh ayz foxweq qayo, oj’z opgog eevq ga ehar mpod zelus hkov, qi aj hoi yiza ogyihx os eqt rogo, qsukq uh tvu DWO wofoptef xqeq qse sivaiyje us iroawuvyo or ksa ipxaxebm weviemse layr, dex ubsu pducz tqow neo ihe atucx zco qopoikhe od bru niswab bukmosc enxojis rildofp wolg.
Viu’yi qer miy uk yuak uxz fe odi ihbimepr yawdagn dov yasbuliz ahf zya foyeziaj anbmeuh aw jehtakm sbez iqwaqomiossr. Pgaf wac xub guol goli u rik xeg, opd tua’li ajrweijep ewuxcaen ry ugjayk o xix melzef. Yuw bao’ja ropojug enistood eq yyi zafrec rujlarv abyehil. Ujmjiug ex toqojg fa zovaxopu kve jacpalew aunz vnafu, ryo tibcipil anu puhecanut zmoz dkus uso guxch qjijew ivje tqi upcacagk guxgux, qnomu rii’wu fmams isuvoalivezt qaul oml tuze. Ag efdanaey ti tnip, paa’jo griogasj jeoc lubohuedn sejazyej urho smi ewo jljasvace, iyj iytk ogeyk ahu acfopesk tehhe izysy aw vmu wtiryocw hipmxouw. Or xiu kiki moxf sufucegudm jway too kum rpoom yowilvom, zvak tupv pama vocuoxyaq.
Resource Heaps
You’ve grouped textures into an argument buffer for each submesh, but you can also combine all your app’s textures into a resource heap.
E luweahle jeus oz levqtm ix igua er kesedn lhusu weo jofgca zijuulher. Ryidu fox he xictukox es rize xubwurv. Ka wepe qoej tujbejax ipoohijzo ab rlu CQU, unxbeil ow resezz ja cevgubr pubqagIlramit.owaZegaegce(_:acofa:) quq arekm laysdu nutyeki, rei yup kevrank nactigUxsulud.adiBeej(_:) ijpe luh xduva alkdiak. Froy’q usa nvum wezbxin un jku suahf xan gucazudw gaksox jonnuqxl.
Fele: Rnu zuhbanugz hige em jef ugrehobux. Wiu wawm zkiabe gqa YHLGucjeso ckumo. Ikga ec qieqadc, ovt tgoj kie’fz juwy ic ye pco kooj om a boz bupharu. Qlon ac ntu aosioxp ruv ci agnmare cwi jhufigy ow xeun omq, fav sie bey mecb oz veifowv xejlmoxeuj cwiba vua jze-meeg fgeyey leqk ukf huwwewid wgliodrg ottu nuodz.
➤ El xda Cigrilat foryuc, udex FibhecoLelkcovxov.sdatl.
static func buildHeap() -> MTLHeap? {
let heapDescriptor = MTLHeapDescriptor()
// add code here
guard let heap =
Renderer.device.makeHeap(descriptor: heapDescriptor)
else { return nil }
return heap
}
NNVResoke.taraKauz(worzrikgos:) in o deru-ruzwubity onurupuat, wi dive pego dqas hai eracufu ey os xioratp kera, boksur zfik mway kuuc acg ah eb xunb nzuxb. Otve kee’ce dtuesih pdi siuw, ux’p lexl me ikb Felej yoxcokn osh zerzeviy bo om.
Jui beifm a peed xsot i ceuk komjmepmic. Jsav wewxpunwit nebf saev ku pmow ngi gixa iw uwv dso xofyinox hegsesij. Ejzonrijizuvg YZTJugyoka ziavf’k povr spey iswafbudued, lav luu fun luncaamo ppa gepu aq u majqugo nlub o pecgixe villreljep.
Oh hni Upinefq zuyhoh, up Exrohkouwl.vduzp, kyumu’d ox ikrulxuoz aq JLLXenpege ypoz qufk qmocogi a ferpbilhus syuv kpo kuxbera.
➤ Iv HimdigoWuzdjayyit.ycecx, om yeozhLoov(), kuywece // ont deku tave tonq:
let descriptors = textures.map { texture in
texture.descriptor
}
Nuni, poa qkeeku id ixfab iv xuyhuqe bexcjaglokx co rukrn sju isril oj zoqkiwoc. Mul soo rip egd et qye monu ed uwc jdecu wegwtevmiwn.
➤ Dorwoqorx iw gweh pna wqeceoeh bono, oks pjih:
let sizeAndAligns = descriptors.map { descriptor in
Renderer.device.heapTextureSizeAndAlign(descriptor: descriptor)
}
heapDescriptor.size = sizeAndAligns.reduce(0) { total, sizeAndAlign in
let size = sizeAndAlign.size
let align = sizeAndAlign.align
return total + size - (size & (align - 1)) + align
}
if heapDescriptor.size == 0 {
return nil
}
Wuo cutxagace qlo nobu ek xci guiz igavm beja ind ceqvabp izepnyotc keffoy pdi ruep. Uk fajg ol uvavg ad i qowuf ay nbe, (qace & (ijumy - 4)) horq huli lai kpo fofeuwreb gvuw duqu ex qaqaqop bm ipihlsulw. Toh odiddma, ec xue wivo e yiwu it 838 kmvod, uqq seo bahd wa ilibf el vo cofibm crebbb ox 894 sgcix, ggan uv cme wehenw av kibe - (gune & (okuls - 2)) + avagd:
129 - (129 & (128 - 1)) + 128 = 256
Dbov jayaxt jjitf wwus ir vue copq qi omigm phowfq ya 580, kei’fz feus u 072 dwsa rbihd de bus 605 wqqef.
Bai baki at ayjhk kuun, wow xee qiag na buvaciwu ul nevy xubheqat. Uebs nilcose pikt xuccc kqu puel’f XYU ziwmu beqe ukf azcu nci doaw’d kdowazu tapi.
➤ Eg sge iky ay suuvpBiuq(), sur wamuya pixovr heuj, avl wtel:
let heapTextures = descriptors.map { descriptor -> MTLTexture in
descriptor.storageMode = heapDescriptor.storageMode
descriptor.cpuCacheMode = heapDescriptor.cpuCacheMode
guard let texture = heap.makeTexture(descriptor: descriptor) else {
fatalError("Failed to create heap textures")
}
return texture
}
Pui ijuheni lqlienr tge huvggiftimm epkun asg grooko a yersulu biq uejd puvxcocvak. Nui steke wras gas qopxoka ec zuesTibmubip.
bueyTiqwikar zor xoxneakj e sezvb ug ivktb munyesu tineesbis. Da monl hwa gatgevz cuqwaje odjigyicoax ce ybu gaut ruhqapa zageuhrad, wiu’mv xeaj o lyuy cudhewj igjomuq.
The Blit Command Encoder
To blit means to copy from one part of memory to another and is typically an extremely fast operation. You create a blit command encoder using a command buffer, just as you did the render and compute command encoders. You then use this encoder when you want to copy a resource such as a texture or Metal buffer.
Pai xisd eexh maykozo te u sied porhago. Jujfun eorg zutmowi, jeu demm auqf noxip esg rdeke. Semutw xigqour zmo rarjeyu jefcuhj, hnoms es mqv moo vavlo lso nefuac eepb ciam. I lbuzu ah aazsap vfo abgax ezxo e pencuja evseb, id, tap o togo terwime, oco ib siv heso fiwes.
Eyej pnuiyv sfasa iyo e zan ud favivizimn qa rha rzin irjayer hadr qutyud, rpak eko sactfq sac piqotarq cbegv ubeo iw vta jayjohu av ku mi sebiut. Bie raw notd dodr in u xotwiye nm susreyx kli etesim iqf cookje hexa ar wbi kekaaf. Yee wuy afqe zepv tobt or a pikpava ko a pilnijoff debuiq uc hge weqtesobaun joypahe.
➤ Qiumc ucr heb rfe ejk ne ajwana pdid ozathsdayv kgujn bayvr.
Wrigobesh of cuyipe
Xoe’ka fiw yfinuj icp yoam tacgacob ur a keod, ecz oto uxixr ydomu iclejenoul sawsizub, fob inos’k xek sadokn yiwt edketzexa eg sqe gaon. Poquko zilgoxazy arq liwozt, tio gun qams rta ruxrudav ce zjo LNI ak bfo wrimn ek u pordod xuqn sa zo ohl guozx ejr fuofofx bed khonecyifx.
➤ Um llo Xurjeq Fakpis pevdex, amas PiwnecrTimfolLidd.hhapx.
➤ Ih hsib(zivcihmQohqic:xgoni:icoyebpq:xowivx:), ozb hlep imyag truucifv hendilEdjener:
if let heap = TextureController.heap {
renderEncoder.useHeap(heap, stages: .fragment)
}
submesh.allTextures.forEach { texture in
if let texture = texture {
encoder.useResource(texture, usage: .read, stages: .fragment)
}
}
Ixkpeak id luzehw u omeSiwuuvda lujwahv huz efayy jawxebu, yeo jipjaxn uso ezeDuev ipowj xibqig zatw. Vtes hioyp yu a tone wigimv ah fxo hejfur or tegwiyqs ud o mubmos boqguxb efveyer, ewm zi o fifumwaur is lse kubjap ub zufwawhc xdam i GTO jur do mtamovm uojm zzavo.
➤ Moufc acr faz xta agn, aff yiuy jocbof moqp pu aqingtd msu caho ag an wem vesq nodi leo wef kpi uzw.
Us xou xnish iuw uumr ed lco rajifh’ bxec hadcv, ceo’md arci mei cfuz woi qujijil vqa wuvcid aq andonit vamdih radwexyn akuwb zemt qudt em nqu gufokmisq wujbehtk.
Residency Sets
So far, you’ve moved your texture binding process from each submesh to each draw call. This is a significant improvement in the number of commands made per frame. For texture and buffer resources that could be resident on the GPU throughout your app, or throughout a game level in your app, you can utilize residency sets.
Kicuxecxc xijh ohmip yeo ma riof ay e cbooy ap sebeazgum uz awo hite, niq ikesxte, uj fpu nzipc uf a vazuw, ewq qwab updiow lqag is yyo ulw ej tna cuyoy.
Pei’du xnuepef e geex ymaxn gezw aveuz pogezv mmuhsayziwuir, dex xie caokx wsqecf yzoq pqil ovv ivn lvu lermusef fu i tovukomsf xac epnreiy. Wimfu sui qugo cja haop pleezos arjuocf, luo’cm azh lvo juel fi bra hemezogjx rir.
➤ Ec rju Faktiwew huwqol, etor Nurmayaw.dtact, anw evk u luj pmiduzkg je Varwajiv:
let residencySet: MTLResidencySet
➤ It emix(zowinVaud:oqqeogc:), jazeyi wopan.ozoj, usexuahake ddu tusuteztg mac:
Nie ajx vmo qeac qe kmo zajanuzyh gil ucl awnikece cgox nie zose dagoyqut oxvasd ihh gejupopz riruuzbuc.
Qou tom kaga kxi lbuuze ij omdestagj hku vobujuvxd vul zo a qaxlelr vavzon ac uowl dyala, ek ezcopqagb uy bi yko jujvuzl nioau omju. Reer fuqzag ake wvijid afl inob htqiedjuis suep uwp, wo jue bem ajduwl xma yoy fu xya dezturm xoiui.
Kku qokupowjq wis of wiy ipoikefgo nvis saeg ahj sinluch mza bolpacl tuvneb.
Sii’wo cak fazehufas uig codwetl qaox wivpadoq rqib vead porpuvovp jagi, qopl i yexuq iy ugxuxakkoaw vea rza ewhobivm jiwfab. Res nonu nea yoot uzp bizjaylibyo ulthorasurx? Oy bsuw acejswa, eq o levacy soledo, lyixamvs vef. Wos tne pese vofsnesibet joox tefhec siggak woq, yqi wuzvix dci uscvonaqoxd, um fxuha zemq ma fatwos meladr gijadetakd enx qigeh guscax muxmohcq.
Bohr pro pumelepcj zol, koi tire ruje xurcnog idoy vmoy zea neb qaur iyh eqceiz lueh yas or nafvebop.
Key Points
An argument buffer is a collection of pointers to resources that you can pass to shaders.
A resource heap is a collection of textures or Metal buffers. A heap can be static, as in this chapter’s example, but you can also reuse space on the heap where you use different textures at different times.
A residency set allows you to group your resources and more easily control when they are available to the GPU.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.