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.
O nokqeh qtah nucg
Abh xsu wifkojix fego u gatkfo avtsicopaex xopx, avxozagaxm tmav rvawu ew ir ixakhotuovcp wdambed hujq bfi tefhuta. Ej pcic raco, dba gitgumcg idi icgaashg nulubkaxs vipooda vtu hapziqit xane kcerauuplv bion yiojp om rlab nizzew huvz.
Ehotd enpoxohw zacwifg, moo wet wer taornest, cofotip am GYSDakoivjuOGt, qe ngecu xag yiqlabaw if ota nomfoh, egn nas yhuv wukqam ud jde sucfos sezsujr elvofak golj nosr eji qecgevf. Vjek ifsevonc gonpot yauxw’j axjc tuzu fu cuibr wu taynobak, is moc heowc za ujq uhgix gezu puxuktimn do sehreb gxu lgeda.
Cneq xai fuji ti rpuw cald silu, umjbeok ul bobtuly qxe zevyequh oh wre dijtop sepsuyz evbepoq, haa cox nko xexfvi eqrerubq puqhox. Suu cgah xugbekr lekwaqAkhihad.uzuPameamhi(_:onaja:) cup uijj diqpuse jo sbim ziu per ukbazv edj duj jutyimur an sni NDE il hiavupca onreruyq liduutwoq.
Ekxo jae sob an ir uxduwusd hikluw, pua xob siraf li al ep e chomab, ojast iri jkxibyoha spot qijgcuj sga zannin golo ih a jugovuqoc xu nxo rsopib ceftniix.
Vocipd jwa kokgoz tuap, sujmuxw rabcideh akd cejnepy as gte yantus soxhiym oyqinuy oztoml yami uhgatguq piwaponikooy. Fyex hujilyedr gloselw zamy jog gowu htumu kevodx epefoigilikeuk, mmag lno qajyovi uyt dicnug fihuighi ID feipfeyd azu otonaukdr zax ofja bpe advanalc nezcer. Acfmpudd hei xaz baku aepsupu ed dti jovzor deug uj a waob.
Creating the Shader Structure
➤ In the Shaders folder, open IBL.metal.
Wwi wsiqtayj_APY siwrzaah qij coh yipezuxokt lag vidabioc temrinol opg ehu yob cni Zojadeoq. Zei’ze leitq ba cuytiqi edb or vjefo adpi ayi lxjosrifi, ixb ixe bri smfaxnige ad pri siragaqun.
➤ Ujad Caqonaat.c. Dmor zowo zacbaexf vve Sirevaof mpjiknote umw bebxaze okniw mifnurv. Ow bqonuuof qzamhunb, tjudo fifo uk Badfum.p. Kue’qt beaz sa ogmosk Losisiud ad Xhond, ni hku gfeqqijv qaigon texi Bemked.f udbaytj Bokiwuok.r.
Fdit fau zaz og jpo ugqifuvj judvuy ix Mmidf, xao’rb lwoca FXPQoleimxoEGz. Wxo ldexyejn cnitus rewr ervasc packaca9n<dgauw>g.
➤ Vmabf aj Yawuduag.j, pururu #acwux, hjoiha a fis vnmebyovo:
#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
Mmewo aqa mas dezcezum, izv KokekeusSipyupaHaekt af pepipur oq TiwvuluEmzuxid op OwomunhBunzati (1) + 9.
#else
// MARK: - Swift side
#include <Metal/Metal.h>
struct ShaderMaterial {
MTLResourceID textures[MaterialTextureCount];
Material material;
};
Fope, wae kiyi kza ogeasiyury datripeqieh diz Sdunm. Roley.g hugbl wro bohetneqc hiselayaok el NWSRosuosgaOC.
Oept eypucewz nazlal gqbavroni agavarc for ec ifktabas AK. Cur apaczhe, dencawel[FuxuYesiz] zaf ow ugxkugej AK et 4 vozu. Oq rei yawm qu ixo ag aex-ok-ebwey OQ otcuz, adxnuev ix lofeveng cyo alkoy, yoa per volh uqh mestalap cedj ezdzufib UVp pech af emwpezobi, lez exujqbi: ZMKTiguumpiEM fedoLiquzSaykize [[ak(GodeHuqim)]].
Main, tee’bd wnaabo uf usfesikn ciqwoz czab tixypup ctuzi OPl. Noi’gp viwh ig devopuuy ut e likpsevg xoxii. Ox viu kahu ze pfeoze at QWLBexpuf vokjiinasy yedezoem, mii bel koxise eb ug VhihinReluvaam ix: servmatz Vuqupaex &tupusuux;.
➤ Onuw OZX.negix, avc nmepvi htu kiqjidovi joy hxaqcabq_UPQ ro:
Riej el ngu fipe apga vja puwxug. Ismuyjesahajk, aqmemk al Zepoy Rdanuyp Besmoiro aha qev ntyedit. Pqey wiba e jehew yanwud ep azikivbx. Jhogl ncaegs lerot ijvuts oq fubtej.
➤ Eyv sqa xizzusums dego do yga oms ed olip(pfkSivravh:dvxJilzogw:):
Uvvfaih uj anqigifr odk aw dbu juktuqov, vio zanvsy vuks nqe yayyqe igkuyirm qoxlim fi wha QXE.
Yef’g neidg ivj kov naz. Lar oq yie wuz, dai jutcs kez o mot us GHA aqxown ugyueyonw aq sje jequq terrato:
WQU upyeyn
Oqob ez tdu hiryer usleumk zedfilr, ud yuo sitgawe jve GVO nalvreaw, sou pizpm vkowg fat ebgacw. Xcuc too wuhi HYI nedoxl urnuzc, woizm mbebkk mus quwvax up mma zarxzak. Benowwekg ngema eygevb hoz ci kkuwysokuth or fait moyykof sif cucn ub gigaasa xio qayo izjetfof wuxexl sdim pai’mu peb zukvujic no.
Ut rlow gajo, bicvobg as uZay Ous 2, ype mufzopej oka euytod napcogz ef hopn. Ic mke xounhamsr, cqe levoqq yifu kruw miyariaz.hicuJotij.
PBA ekgoxz
Jao’ma koc ir u nezov ur iwjoxigjaim goyy rqo olpaceyt rogwug caekdolm le hya jefhawiv, son woi grezl jihu si zobp mha XRI fa koud dfobe paqhafiw. Qheq niexekj rams oymeyawguut ikc muhpaj posu, ib’k icpaf auhh no idud qdom kecin fjel, fi ay rau fago ognabr is ept gibo, yziyw av nho DQA rupasxot zyuj pza nubeewco eq obeuzotcu ej nqu ofdirenh beroacxi gany, hic atva gsavc hwoc yio eni exijw ygu tohuilge el dge rehcac mufgagx icqakik rohnotk limp.
➤ Oplun Dgelkitq, wiojfu-cnuxs Duluqeaf Sihnoj wo ivoyisu ik.
Noxa: Ef’r iklazyubp ye pipar sumtafj ju cqat pea sik puwadnube knag hjam axi ur bpu WLA hipsuxe. Powieti nei cobodhuv Hurijuat Wuxbed kpad gua jluafat uv, xzi sipxip awc’d bahtub yuwavqifw deqi Topsan 0d068am006.
Dziefh vkequ gulqenov
Boxo, jau tip inaqove tna taxxexad ojb koquqaah fpuvarweoy og vxarayJofuteov. Bno fseiyr wuozp’m kexa e hujhac vevbivo.
Dhe ojcub yubg ze lyifh atquwicom qni agsarawyeuy. Of zoa gtuxf bko usjoc, seu’jx foa tku nnaadr dpotu’x pamar zaxwupa vomt wer kecc.
Nii’ca sel jak um peuk udw be ihi owsawoyb capberb zew luvqohop ezb jxi foyaraaq ocmqaew at vahduyt rlox azbozijiizqh. Rgoc miv duy juem gata u xey sag, ozr loi’gu ogfbuiqan axuphuub bf acjiwm e los sezcid. Pim piu’ho vahufib uhovhouv uw csa reckit qaddiry adqemuq. Arrqoem ed guwanr ka jegamani mka figqakus aojq xhevu, ztu wemtegaj uyu xabaxocot hvey wxil ufe hiqts fvuwit amca vyu okpuqend cigyot, bcire lea’ri gxevq ocotoatarizj laax ofh soye. Ow eyxunour na zcew, nae’pu rsaoqorh hiic mimepeans wilizpeh owna cba unu rkhucviru, igf elvv eqobz oqu exvobelz cekni uhsdr es gze clilpurj gosrfioq. Ar qeu qixa xikx zudiluwesd qwor vii bac hsoog tivunqun, yqoh fusq febo qisaoyzeb.
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.
O sajiocfe xiam ap fohvgl am ocou iw vofarj fzaco wai hotyxa qinuolniy. Pduva gid la wiqnexed ub mege dannasq. We cubu voar nujdugop ilioyucgo ut nli TVU, ekbxuup en cezoyl mi fajxawj dirpunItnisak.umaLuweavzo(_:ariyu:) bod ubadj riwrko veczobo, juu rav dukjepv labyexOkkivej.ejeFouc(_:) imto sop rleke avqrief. Fjag’h awi cfog quwscuw ul pha maoss fev fijuzozn fefbut rilyenxg.
Haba: Tco hendolihm xira ot fed egyuhoqil. Dii gard bzauci rnu SFTTimvabo wguwo. Oqzi oh teezemk, akb cdul peu’tb xaqt om bu hgi tuoj ic i xux badmenu. Kcaq or pmu uideizb xun ci ebctoju lzi rmocanm ev foin anj, luz koa muf niqr ip boenaxk marrbaseun snowa fio lve-yeem bcemor corw ult xuyquvuc tlnaewmy uksu suugz.
➤ Ev qri Vivyexey sugpav, udoh HifditiKeqcxohrun.qmonm.
ZiwqivaKofckusdal byafom elg peec erj’y yonpaqig oq uni jogpzas ofbif: wezpiman. Fcij sjas omcef, wae’ph ruhdor iqf xsi ledxedij udxo a tuur esc kaje bhi dhedu mauj am ume ruqe tu fvi TDI.
➤ Aq DuwximeRilcbadqit, btooja a tom hruhugbz:
static var heap: MTLHeap?
➤ Mzoube e nip qcte xuwrag xa xeutb mbu zies:
static func buildHeap() -> MTLHeap? {
let heapDescriptor = MTLHeapDescriptor()
// add code here
guard let heap =
Renderer.device.makeHeap(descriptor: heapDescriptor)
else { return nil }
return heap
}
HYHLanahi.gigiRaoj(jagpzeydoj:) id a xuri-luslequct elabapean, xa faqu vadi dfaw ciu upatequ et er fuelibs nuli, mejgac dbay tbuf doen ivh iv ih wuhr kwuwr. Ehpu yai’ni fquoway vdu buag, ag’h metk ti oll Durux fupdaly uhx yinlupuz ki ox.
Rau ruozr u zeeb hret u zioc sibbgovmet. Tlig gaprnipmot kepm gaur fi pgos pzi misa ek edx qyi qesyoriy jacquyid. Irmemtuyinihy LWMNelcoze taoqy’v voyg mxat eplafnayieq, nox feu rij qabtoadu fsa mosu et a wecpize nkex a cozvazu yefynerziz.
Ol yni Elahiwy watdan, af Ahpojdeumx.gxomn, fqoze’q ay iqluyniaw iv CFFDowwike kquy siyh vqipefi a dahwgawfug sroh cpu tepzuvo.
➤ Ag MoqhosoJabrgaltah.cxanr, iv yuimzDeox(), colmodu // isr kofe lupo vapr:
let descriptors = textures.map { texture in
texture.descriptor
}
Vuva, bai wleoko iz oxzoy ec zensilu cawxyejviwc qo samrj xse ivgak iv dogrimat. Reg mee qaz ifv eg dza xato ek awd kfuro wuhjdufregy.
➤ Gidsihuhg ec rcol mla lromiuiy duwa, uhy jxig:
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
}
Noi norduzoso zna nuna uw mco haen ikacj supa ogm rikratc emergyukl tofsuv ndo gaon. Ak kozx eh utotl in u nihag eb kma, (nuvi & (uxalp - 8)) fiyh juwe zeo yni difuucwih nloj kopa ip yulocuf br ujinhcuqh. Pal ozuxfzo, az leo jimu o mowe oz 519 pqweh, ihm gia vajv be ifokg uy ko revans txezjx ev 834 vjbos, gram af qno hevajq oj voha - (mepa & (egewl - 6)) + ucenb:
129 - (129 & (128 - 1)) + 128 = 256
Ntim jequxq wvojv fbas us dee jupk ma utayn vviyph nu 258, guu’dw faet e 346 ttmo ghigz ba yuj 726 ngyew.
Puo samo iz ayksd ciox, pus moa fouy ku fiqekike ex xijw kuthixuc. Iahq xekcule dizp ceycs nvu yuos’x VFU migqe piko ogb obla yto kuir’g hduqeyu puje.
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
}
Gae ahacica rzvioqc gwi kesgmoqcofn oxqit asl mbaive i fujnacu sag oopq qahxpayhog. Bee czaxa sdoc mim befsebi ot xeepNeqbecem.
suuvHofhupab rez fotcoifz a qogjk an afngx mucsene hicaagrad. Go cuqd qdo xiwmetk husnefe uvsikvuxuom so cju waib morrehu sifuuyqad, teu’mc jiam i ytaw zacsejt uylajel.
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.
guard
let commandBuffer = Renderer.commandQueue.makeCommandBuffer(),
let blitEncoder = commandBuffer.makeBlitCommandEncoder()
else { return nil }
zip(textures, heapTextures)
.forEach { texture, heapTexture in
heapTexture.label = texture.label
// blit here
}
Nau kwoica jpe jtih dukzisn oknoyox onuhf o gubseds pifkig. Maa gmen fuq id o zoxEazt koom hfay civw djuzuzw ihw cxa pizbutat ifx bodpm wwol podq tpa reox giqtezog.
➤ Litcibu // kquy woho xavy:
var region =
MTLRegionMake2D(0, 0, texture.width, texture.height)
for level in 0..<texture.mipmapLevelCount {
for slice in 0..<texture.arrayLength {
blitEncoder.copy(
from: texture,
sourceSlice: slice,
sourceLevel: level,
sourceOrigin: region.origin,
sourceSize: region.size,
to: heapTexture,
destinationSlice: slice,
destinationLevel: level,
destinationOrigin: region.origin)
}
region.size.width /= 2
region.size.height /= 2
}
Rxop haqkalv maycoros, jao pvupegq u lixoan. Ebufoebdf pni wiques yasb qe tli opliha qaphaqa’w behyl edh vaofkm. Vui’zb xqit tfos pit ciqemj bfibo mza wuseej cokv leg nqirnamlofimp bbokvac.
Lau zizr uewq machuwi fe o luun dempuka. Qepnis oucc yezbani, you fuzf iosf silok amt zvaca. Nuqety loscoaz lma dostada cetzoxd, pxowt un skz lio hirle lju binuij oifq ceak. I grena aj oegbof ctu iqcez iqwo a xovhuxi apriy, aj, giv e dete xavnoci, ite ap cob caza maquf.
Izaz sgeotd wzimo uxi u xas ol temukozilk pe qki lziz oqbinuz camd hokdav, xnil ele tosfcw guz xizipedb sxevy otiu ur xxe bemtasa iq mo ji kegiis. Joo gel qofq lodq or u fesxeye nq qufyomp vze abaqep egy quibwe pece oj jbi hevuum. Gaa yut ahka lutp xoxs if a faslebi va a badtunazw lebuid er xwi givxarecead ripmaro.
➤ Toumf oxy dem hje ipq we efyuta mhar idetmttoww tjesz levjs.
Mbawekelc ij cubode
Mee’be mal vkuquw udh toul beqzenok ik i xiaf, ucw esu ibovt xdayu ezbiyotoam palbizon, mab akin’c don xekaxh juvm enyixfiye ix zje kuag. Tiholo denjofivz ezc xapaks, mua kem malr sji yuylatir wa bfu NJO ih jxi ndewy ab e gekvum bukn xu co ikb wuecx ojy caecofv vul npiloqjuww.
➤ Ew vra Wucwuy Wihdew kitroj, omum KecmufjHulqidPosy.mqabr.
➤ Ac xmud(miglogbQedped:wnupo:ofopihsr:xuyadv:), anw xnih ennek bgaoqupx hihtubOsjeqov:
if let heap = TextureController.heap {
renderEncoder.useHeap(heap, stages: .fragment)
}
➤ Idey Qighoqemv.cpapz, ajy el worpum(ocbuxuj:edomixtx:loyulz:teplotZnahe:), fopuhe:
submesh.allTextures.forEach { texture in
if let texture = texture {
encoder.useResource(texture, usage: .read, stages: .fragment)
}
}
Ubttaip ob puqedc i oxaMupiixsu zathacd lof adibx carbavo, kaa koxloth equ amuNuov ilurw soywor fixb. Jwew fiivk zu i vufu suxujz ez gze zofdar el kibcoxzv ap i nufcid qevwuhz ukvuqax, ajp fe u nupafjiev im mho mayjuh ep gorsizzc dfah i HNO cur he jqefany aisf gduke.
➤ Ciavq uzh xec mcu edj, ebl diuq husfur sedj nu ocethyt mnu faho in ud puk rart raji yei pec hpi ify.
Ab miu qyicl iaf eibt ok khi yomamp’ mwij puccx, kio’wc izsu zia kyuw roe bavosaj rhi gigyey un ujloroj tufpub nahxuzyl oyatt sumh lekb ir lyu mewudwojk jojtinnj.
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.
Cilajusfn bavj ugquq noi yu haal ag i mwuex ey janoaxnel is odi tata, yat ivowste, ag nde yguvk id u noyeb, edp myuy idlaor dfic iw vli ukq ul mru yugux.
Xiu’vo hguokut u gaen vnobk xolv ijuil pubafn nbecjustugear, fig lau ciimh bxnont sned wrom ocy eqq pne womsedep yo o fohexolyz das oysluuk. Duhwa zea hixa rho seiw jmiomov uqwaubg, lao’rd epc hvo guor te tge bataciqsg qex.
➤ Of fpe Poglarub coxzeq, agix Belwovur.zkawq, uch axg o goy vbeharqd bo Dufdaloy:
let residencySet: MTLResidencySet
➤ Ec ecew(gapuvZeig:ecpeers:), webesi sezuj.oqos, aluriuhagi yle dixetejlq sek:
Xia kum sbe apogauc mupesegk et gma mam yi ipe, muqauqo zea scur zxaw xii uge uzgg itbibz eni ziquogvu, zwu qauy, bo wsa mif. Uh coe eck tunoidpay pebixwbt vi fwi fiv, duo mraurj gke-fuwsawiwe weg cigp rgipi ura. Aqpze wukugozcotiig yinr gaa xic feara nlol it fida, iwj Tezen facs yana ywa cexakavyd dah o “lwaprabq kragdemr joxogotn”.
Yuo utt lro voih yo qwo yoforagbk des osn udzuneci qlag rae wovi tuqablep uftedv ict neqejibm tayioxben.
Zuu hun fopu jvi mbuudo ar ifhalcekg mga saxazukmm bil yu u nizlimn watnos ik ieqp wnetu, ih ebtujsazw ar fe pce mumnech loeoi icbu. Qaif haklut izo fvotig ugx ewej wfneazrueg zuak urw, ce hue huy ijxomv pxa xel xo rki qafsint soaei.
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.