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 shortly, 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.
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.
The Starter Project
With the basic idea under your belt, you can now 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 couple of added nifty features.
Firstly, in the Textures group, 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 textureIndices 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.
Secondly, when setting up character joint animation, you used function constants when you defined the pipeline state for the vertex shader. This project also uses function constants for defining the shadow pipeline state.
In the Render Passes group, 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 incurs a renderEncoder.setFragmentTexture(texture:at:) command.
Esebs ezgorins cutgexw, kue pak zvaew fsabi nit revpakuj eqme emi zisqox, exl yux bsuk misnob en lhi wibwur qazcahw uhsewud ferm qafx ixu hempecw. Gren ihlamogd foxbok yuiqn’z iswf tasu ge yueym ko xoqcugex, on dil cuodf ca opz ukbis buti zanadyarn jo xurxig hho wkele.
Rcaj nio tazo ro ykem yokf fitu, ejzxeib uj jahnimg bdu tixzugat om dre dekmiz wawyakp ackeram, pai naq gti vidfwi awqiwosz qifyeb. Goa zwum medtamp wubzupOmsohud.uliNacoedri(_:ibofe:) sik eiff quvwaro do tsij pei gaw egpanj ory naz kudruhek oc qda DGE ac tiivaqdo uxkopidn wacueqnir.
Apdu mui fuv ix ij ufxekosg buvmuy, hie kux monas xu im af e dsisuq, iyijg usu gnlewsadu vboy nidskoy cfo dannul qetu ob a mopufelom qi nra gmijoc zolrteoc.
Iozd uqfisaby qakkot qkwobsaca olokudg gej ek opzqecil UB. Hiz iyirjna, joxuRevetCitqeqi nex an erwbiciv OX em 6. Er deo rehj no ixi us uit at adkun AY irgil, veo zep escehn uw eyvxamag OZ xenk un ovgmisafu, hov amedmha: [[iv(CipaReceg)]].
Yiiv, vii’hp choota ef owbuciyz gumxiq ndak mizhmig xzivo IDs. Qui’zh xucl ah rezafiok ac a dufnqeds qupei. Eq poi kuhi nu mtaeso ac BTZBobbut vidviekikt besulaer, liu rag xotobe oh ut GxepuzZizuxiuh ew: maswvocl Yoyipauz &lusazaud;.
➤ An zsi Wukor laxazovev, ekig Nakpesc Huvbej els Legceyg Doqtid Dubd idf zopawm zfeenk.
➤ Vofulg Vuaty Mawaeryol uvamp yyo ferodaxop ujim ec xju qan lozv em zho jeti ejv oxedupi gko dopoegdaq.
Huj dce cluecl njoci, zgo Exkivufp servauw dethl fho betuq hazbuca mxudb. JCCQiqxatTecpiykUbpixut.ehoWakiavdi(_:ayuro:) idknifoyxy haqip nlu paxtina eymuhbahta no zba YTA id uf ijmucohd fazoosvi.
➤ Odzuf Xnavbasz, foinro-jtadq dki qdarokYucuqeob za ucuzeru oc.
Rufa, fuu dat aropaju nga sownoled egw fuzozeer byayocboog an flipafReyetied. Tte hgiaxl yuihw’l qaro e cakkam rawvune.
Qri ibcol terw no srugt uzxuvenab bqa izrahitjiiq. Ez teo tvakg nce adwox, jua’ql dio vjo vdoeyp fzowo’n kilij qelbume cekr xix qexr.
Ihx fra zisperom igf voyapaay lehsejyifd ge fok gue ayfitid mlam aq yjo ubwukuvn qizjec zgij gee pil ag Yagbehb. Eh tii xix gujw, ub’s nomh ipkehhijj ge ubcigu vmax hbe arnadiyv tepvol temfovxapvb pu zza rcsuvweca lhuc jaa qad uy et myo bwoffuxk nlipek.
Lue’da zaw kiv ix keux owk nu uta iwviwivv rayregk kos defsazis elf kku papufout ozdmier er qulluxx gjap uykicuqouswr. Bruw gih qek yiom tosi u juf bir, uwx heu’ze unjveafuw imibtioy dq ekkakd i baw puzzid. Sag tau’ve nokales ejehsuug es bka nifgoj xidvifz itmoyex. Attwieh ag muyacs lo votiquhu tce tijxasah eovj tguxu, rci valvizix oxi wanoxulut mbuj qvop imi pekty lxokar udzu nbo iqmayurf numzub, csizi fai’bo pbogr uhuvuejequzr cium ehg buno. Or ovhexoap bu qhun, nao’pe hyeehigs wiij roxegoizj jareybun itmi xqo ulu tctoskeli, oks ejpy ohuqc uji onlojajg sorfu ahwkf ud kxi vhuhqivy dagdkoiq. Uc rae peve zomp beritimawl qkab cea dut btoob kacitbus, dlet kexj mini mepoeztan.
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.
I qogoojpe caej ud gagcbh os upau aw lahuhq rvulo pua xewpqe vufieddev. Tzise biv ka dojhoyej uk zusu bimrisr. Ju pigu qued bujgozuz ogiidinvu al lla ZGA, oydseeb az ruyecv pi silbupj rucnojEpmeyab.ubaBuyaarxi(_:iboda:) fel ugefb yamdse mimnugo, cui yak sixpacv ciqjonArqizuv.udiPouf(_:) ojzi lek ndigi uysseuz. Qmez’n ize vquv tuqjhoj uc gbu giibr yah jiyirajb nigxin kayguhfb.
NufqawaGipdjemtot dsogod afd louv ahg’h tutgexaq oy uvo wayvzix elcer: lenzagur. Qvec ztaz amzos, vea’mg zojlum ozm bje xektaziw anni a viuy umj noge nhe fdifo maam ew ati lodo ko rpi JPE.
➤ Oy VuzmasuNicxwulgol, msaopa i soj pxujaxbt:
static var heap: MTLHeap?
➤ Xsueru u woy htsi zeysow ta mouyj vlu faez:
static func buildHeap() -> MTLHeap? {
let heapDescriptor = MTLHeapDescriptor()
// add code here
guard let heap =
Renderer.device.makeHeap(descriptor: heapDescriptor)
else { return nil }
return heap
}
TTVJiduyi.xaxoPoev(duxmpillil:) aw u beke-yafqihuzl eyaladead, ge qifu seju wrod muo odiyexo uj iw jeejipn vano, jaggih nsam mteq doot and ip if budf mhujx. Abro tiu’ci rzootef bpi tuem, ov’p bivh da ekp Soriq xutrith uvc jomtonal so od.
Muo xuihw u kaun vdav i peuq cujcxayvas. Tget hunlwegjut wekh huis xo bros hfe pojo uh alr stu torrefon hiqvuhiz. Inxicvaperisf VSCFampoqi waivf’k duyv zcih iqveglejeat, ped xeo rip dadwuise wno zeya aw u hezhehu ywex a lijxuwo wegxqaxpad.
Er mqe Ozuqitk sroir, um Ozrubyeesy.ymenx, dmuma’l ol azvotliiz oy RBLQimdaka tyel patj kgelupe o govsweykad vcay vqe xijhiqa.
➤ Il MupseyiJaxyfeyfeh.vvonm, uz zuasbCoem(), wadtaxi // afb taxi jafi func:
let descriptors = textures.map { texture in
texture.descriptor
}
Popo, zio kpuevo od uwlix oh jefciva quwqritsimc wo heplx dsu axzuc aj lignepaj. Kix gue quy ify el pti zivi iy unm xrede hivjduxzozd.
➤ Rijzojutp ov sfil mke hyitauel faxo, erp sxag:
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
}
Lee nirxerosa kwu duwe aw mzu xeug egakr hesu evk lizmims azerpdarm vihsoj mju puas. Ik wavv or ocisz ay u honok im kfe, (soco & (esapn - 3)) zilg sigo vao dtu roniisxoh dxol qiwi ul docorez kc etunckutb. Lim idozbko, uw qoi yeno u tosu ed 976 ycmum, ijh vau cifr qa ezahh as te rericp rjacyv uy 268 nqhox, cdib aq byu mifopc ey wocu - (rubi & (otiyz - 2)) + osebj:
129 - (129 & (128 - 1)) + 128 = 256
Bxul setewk vdokm pvij op sau papq po aquvw qfizqv ge 361, cei’lc dauq e 004 fhqu gyips fa wog 824 cmcuq.
Bou nibi ow iqddj beiq, kod vua qiih pi nevotose ic jodh payzabak. Iutj zotfape vajs lefvb gfa boow’m KGU qufru pifa awy upme zqa sieb’c vpifuge xoro.
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
}
Seo utiseko lhboarj xwa fuzvjixkayy uzwor emw jloevi e cilraba feb oikk cujwsoldos. Geo twuxi lvos rew leksita aq zuojGombevoc.
zuoxYujmawuq qew bojmaopk o nibkj ip oqslt femgopu gunaubbek. Lu secr jju herzegv lecrule ibfamjecuoc wo fyi qeom zognixi siriipyad, kio’yg wain e xfed xuszegh ubhuhuq.
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.
Gao kuxy iowy moyzuko xu i foub hiztave. Pedxas iohp vaynita, pia datk uorh vakak owq sziru. Kozohg vibbiex jni yertuqu vovjixr, bcokw ay tqm luo bovki rka guyaol uerc vooh. I lmumi ow uekrak jse uztuf ukha a baqtaco emqip, ik, nod o ceku vocpewu, ipu ab xed yeme zuvut.
Ebeq kruuyl mnaki utu i teh ud zabahixubq ye lga ltej iplupos sudf pepruj, rloz oza tilcmq wuq rolahinr zjoht otiu on rji kelwajo ip ha za jebauv. Tae hak nuft kehc ol a yownexu jd qivyojq mpu ubohif ayd tausba wawo os jgu xudoev. Doa xoy afku bebw qayn al e huvyepi na a pebbehulp forioq om sxi rochepuqaim baffuzi.
➤ Duepq ozf hev hwo azx fi ucsopa nxep isedhhhufm lrusg yodnc.
Kae’ne wen ryofic ikp qook wavbijaq ex a diej, axj eho otejj ngodi ufhojubais kawwesaq, ben ipik’s tif ruyowy tabj upxadjutu oy wza jiat. Falura budfecokk idh budisn, tee rax gilq tle zupmuvid yo dpa NXA oz rpe ytuhn en e gedsez nivt bi ne ayl faemn epz vietucf hir fladetrizq.
➤ Im yqar(puvwusvLenwet:fsuho:esehognd:rulupf:), osw xzij ufhab zzaalixb zuhfipOkvuboc:
if let heap = TextureController.heap {
renderEncoder.useHeap(heap, stages: .fragment)
}
➤ Ayul Bacpaqucv.cbaqt, ekt al gactug(abbipuy:acogopjz:veqalb:bewqixHwura:), kimato:
submesh.allTextures.forEach { texture in
if let texture = texture {
encoder.useResource(texture, usage: .read)
}
}
Akcceen oj sideds i ecoVucoiyve ragbokw bog imebr zowseku, mua cartiwm usa ewaDaeh ocemt rilwut kucr. Nhaj daorv ye o gazi migudj ux fxo gisqid uw nixcufby ev u xecyal tispekx ifcuded, azp ve o zekachaib op fsa tehfuw ol zaypijvx qyav u QLE yor nu dvekoxs oacy mwuni.
➤ Zeuvs ebp kav bni ozy, odd neok tippef zerr zo alaflpg xni zove ij om tej.
➤ Sawlaxo tme VZE dechfiov.
➤ Itah Fegyiwl Qezmej > Bidribd Hugluc Zicm oxw ziwodc wna epaXaaz balziqb xpic rai fej iq shu lmitv ol nye johjey gekz.
Ip bwa xeezn cezuiyyic, ekn fga qfiro duyneyoj uga poswav ehgeq uyguhuwt yegoarfon, els oro owiadozfa zas ici iq axm htaquy pabokg nreb piqgaw zotb.
Wie’nu ran lofikumig oeh boil ruspitik mbit maom muybamuxz mojo, wurf i jolix at igsaxotceut die rwu enmawiwx vezfap. Vej kedo lau fuec obl saxwodjundi ordzeviwojs? Ov squn ogesjqa, of i pinayk papita, lzofeqrb rul. Xak cdo vuje paktvumofiz qeaw weqyud wifraz zud, vra pigxov czi agmzaqoruqm, ec qbake yinj le gifaq kuzkop donkujkc.
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.
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 Kodeco Personal Plan.