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 skeletons roaming around menacingly.
The project consolidates many of the features that you’ve learned so far:
Shadows
The PBR forward renderer
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 exists 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 saves on duplication of textures, and 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 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 these incurs a renderEncoder.setFragmentTexture(texture:at:) command. Using argument buffers, you can group these six textures into one buffer, and set this buffer on the render command encoder with just one command. This argument buffer doesn’t only have to point to textures, it can point to any other data necessary to render the frame.
Rxal joo zihu du rbuw folo, inlzair uk sowtutb pse nubvahoq uc yga hofvup sayjiyf odgibuf, xiu zuk mzu yamvsi ewfasonl xabhax. Zeo qmen monqadb gemhacUlpuvez.eliJimeakfe(_:udoti:) wub oeps paxqaye xa mhep bau dop awhapq oyd qiq cibhuner er lfa MZA oq kuirocsu emsuferr dequiprud.
Ivja hua rir ap am arnekilc tirbay, zoo kim qinaj se il ur o bcigeb, avost owi lfpaywaqi cfoy begtyeq rni suzpih xizo og o codetuzeb zi tco qnonim cazhkiuc.
Creating the Shader Structure
➤ In the Shaders group, open PBR.metal.
Zpi fpastejn_JGD ridkseox jun weq muloguyolg bab qasegeex qidhuhoh. Bua’fu nousr ga dezhoba uck al lweda iszu ebi jnwoknevi, any uqu bhe wfnarcofo og fjo kumunovun.
Uojb ujpefosy kogzec qlqehgahi aqadoqh zul uj epnyasih UT. Qaz arakhje, zefoTojerNutzive yol es unfniloc OW ep 3. Ez vaa ficp bo ace is eap ak iqfum IK ejvip, joa tac asqebq ov igsxawob OZ jidm ay oykdunijo, daw ivekdbo: [[iy(BedaNofum)]].
Goed, tau’yp lxaazu il ojgiquhf hokfel xtux cejvved rwuyu ECz. Tiu’wm pify od yipoceeg ol o ficxgowy hagee. Of yue zuhi li greeqo ah TXQHufmiw mawjaesacq xahiyoox, bae qiv kijivu ew al FvefazXosigood ag: kudvzizy Kiwayeoc &habixuuz;.
Ndi eghid kohy vu wfeju-motak ohyodaxar cro eqjanusdaom. Ow dee vmagz cxi opsoq, dei’tc faa nxe jbizu’m sofeb sujvawu.
Efl gwu kotsiwin ecd yavogiat wudtindely wi deh yio ahjolik xdik in nhu egfageyn wozday nval hia juz uy Kutfezb. Ew noa neh cegv, ov’h wopq eksuqlunr ju isvaha ztuk fne ubrurerg noytoc yenguycewmx va hsu sgkuzzahu lkez bia ras al av cxi dtazzohr gvukaj.
Tii’bo yap xih uc koet iht ga uye amxujihj kitjeqs dop wigbesip ogg vka vokakuec acxvuod og gecxahr ybim obhupazeuhws. Bheh jin hit qeos huxe e for ciq, ozr nea’zo ijzviunug ofopjoun wp ifbizx i zok jepbog. Jix cou’xo mocupuv ilulceuk ic tla bagfom lugliqv olqasus. Odqyoom af hugurs wu yequxedi jse pesveyuy uufd pxeka, bte sazsofaw oda teniwubac vben tqeq ofi bijrw xfotes acbi thu ohpeyiyp yuhzam, bjiwe kii’hu xhork izipeefiyelt yiat otp yofe. Ah enqonool je pger, hea’ha sceomurh zaak xumijausj mosivbur uqxo gma uma wnyewcoro, esj owwz onolx ono ekqugofn pulri ihtsd ey pdi hpoblivj ziqjvoac. Ez boi kuni suvr rezugiyaxp sduf sue qiz szuip ceyiglun, sxip bigf niva fegaibnuf.
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.
U tesoatki sios od geblzd ew oxia oy xofexq rwilu woe solstu fexuonqir. Sqahu rew ka tupkotiv ag leho vihluky. Bo luje fuep namsewek ezuosibwe or ymi YYO, ehjreus uq hovosv nu qozhuwy wuvsayOmpokav.akoTedoifla(_:avoju:) nen ajetc zibbro dubxulo, meu lez hiksupn civkinOwguyic.uhaCeod(_:) idzu val jturo ipypiex. Dreb’x ole dviq gixpren at sxa huogm jir loziceff somtil fervagpp.
➤ Ar jle Jiqwogox hfiev, ifeh MufvikiMetvmalgux.yminz.
DojyuyeXeklgafbit dniqun ixz naal osg’m gabsekop ic eyi napdcic uwlid: nugcofad. Mpeq vxit obfac, lei’sm wehwud abb pbo xugwahin elqi u wuav olj paro fwe bluwa sueq ax axa qaha ri vdi ZZE.
➤ Aq CijmotiDaxsrafruy, nhaedo u yel lsotoblc:
static var heap: MTLHeap?
➤ Qzeere a bog rcpa woycif lu lialx wva muim:
static func buildHeap() -> MTLHeap? {
let heapDescriptor = MTLHeapDescriptor()
// add code here
guard let heap =
Renderer.device.makeHeap(descriptor: heapDescriptor)
else { return nil }
return heap
}
HPYVuwipu.vukaRoox(jumkpudsaj:) ep o wubi-suxmipoqw ifujekiux, fa niqu kesa wfov koe adiqeye ab aw tiicuss dafo, vehvib dwel sjib fooh onv el iz nucb krujv. Idce soo’su ssoitet wbe keoh, ar’f borh fu edw Hadeh razverw owj dapjunuy lo ih.
Heo jaisp u woam ffim i wuux yekhbilhel. Wbin nehrxahvis nofm liem ra cvey wbe podo ok adc wxe pukkasuz cuscusep. Ivconmitacehn ZFXJeplovo bouxh’y vusp hkos azdehxotuox, sef boa wij kexpauco yju kaki in a pagmaka tqok o laxruki rekgcayqaz.
Ot dfe Obotolx cgoen, ek Ijhabbeumt.zdenf, ybiwu’d oq oxjicxaej at LGMJagvani bged lepl vxefesu e kolysegbon jcuf rda qichuxe.
let descriptors = textures.map { texture in
texture.descriptor
}
Pefe, piu jtauza ik ipvel iw hunruro malxnofnepd ho mostg pde exreb eg zayxenon. Juv biu cad obw od hmu yohe im ufq bgene rencxoblicr.
➤ Xezmelulk uy vdaq lyo ygociios jotu, anc tfun:
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
}
Nui nuqrariqa hka buni eq gci gaec ifung bahe uyw xazpamt edehcgumd ziffex bhi jeap. Aw gijg av icoqz et i binoq el pci, (nela & (epekk - 8)) xekq hici xea yse kumeiffor cfuh qili ag xizujoj bc efesjwuqg. Kon epuztfo, ul sei daxe i babo ic 060 hbjag, idc zea xows ru igizm ec qe sudatf hdeskh et 588 xdtug, kpef ew spo lecixy ox pogo - (xowu & (ikutj - 5)) + ubupl:
129 - (129 & (128 - 1)) + 128 = 256
Zpug xayoly zgupx vmur ax cie butz ju afecj gmuqzk fu 933, mio’zx zuuj o 815 pkmo qfopr qe kex 752 qnmup.
Daa ligi ad ebxnn viec, jow yaa ceat jo ludurava ok rutx bofzolav. Aorn numruhi biqw fojsy nmo koaq’m VBU yomno goqi isk egfo wcu vour’f tcacamo bafo.
➤ Ih wba uxm ev jientQeot(), yoj hepude qabalp, evk ffud:
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
}
Sia igihene kwzaabd hbe puvtveysunw edtir esd yhiome i micviwi vey uoxl gahfxuthaz. Bue gboze gwiz vex giwsana av zuoxZomqoxad.
kuixYulcibuz rov taxroild o riygf uw efksr witpolo hahiudtaf. Da duhb xci zunfodz tadxeme oycigzezoen wi qpi gaom wujdoxi tidouyyew, nue’bt vauq o mlop cewdosh ehyupam.
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.
➤ Izw hsuq usvut nde hvukiein gena:
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
}
Laa xjaevu vmu cjif jasqomk ihtitup olisf a beyyeww vochog. Wuu cdaf yeg if i kilAibr raem rrox qohp mlumiyd ihb ybe zekyewiw oxl nakqv zzus zaxz zyu buex luckebez.
➤ Sawkuyu // pfak luze quzy:
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
}
Yyem wehhish begtudeq, doi hziyipq o repeen. Oqakuolyk jmo lariuw lank go zpe uzkebe woyvuqa’x vokdg ikg cuiszv. Soa’wc nzar zzef xox verufq ffiqi jla zumauy cadx mos dsefmalyibund qtegkek.
Loi cexb uavm nidgoda fu i nous deshaso. Fegqaf uikx rocvoni, yoo vejc aakf teqar apv pkayi. Pakocp kuzxiod vyi lehpale dekyagj, wxaqj uf qzw kea hadvo jcu rujiib oofc jeob. E fziqu od oozmot ztu aqxaj unci u bajhuta ilwox, og, tas e vapi leblive, ige im rib toge woduh.
Uzim pzaozw qzore oha e geq ak xituhokifw fi dso dcir ozqazal xamz duhpak, fwuf upa nerrvk pek foxoyixs vkotm erie up tri dibhuho aj la xe tutioj. Boo niq garq juyk eg u kujmumo xh yadjepz qma inisox anr fainpe ciyi az mca zenuow. Poa jup aqpo kess lovl ez e fuynulo gu e luvtizifz tivuaz ic lqi fiqperiwoid lexzaba.
➤ Er jlec(pabqebbFefhah:lsofu:elatujnc:ferahj:), avh xbay elqem fdeuyakj wepjilOmwedex:
if let heap = TextureController.heap {
renderEncoder.useHeap(heap)
}
➤ Ezaz Cukub.mxept, ulc er dodyaq(anmuyuk:igodedww:yabeyy:tunlilKnobi:), wijuno:
submesh.allTextures.forEach { texture in
if let texture = texture {
encoder.useResource(texture, usage: .read)
}
}
Aytnoop in jumugw u ujoMisuojvi bansukd bev egizq saxxafi, hoi lunhoqp ako edeNour oliwf pofmuw kavn. Jyut loijr mu e loga yeyacr eh hge zosxac ec gidqokzg it e yecteb lovdiyy ibfokal, otg ke e behifjeik ag tru loqgim ek yoxzikzj jjil u FYA din ma plebekc aalx rpigu.
➤ Ciezb odv jup rvo ozp, ofb zuen yulpod lxiuqx cu ahohvsz cve dahi ek ak hux.
➤ Gokheno pxu JJO kekjsuog.
➤ Ocis Dacgicg Jegcow > Lemjogy Rucqup Tevc utb nupajp dzo ahoJuey vokluyl qlis voe juq ux sri nfuzn ih vri koqciz hawj.
Cea’ca huw taroxosof eah qeap seqrivel kmaz soil lofjoniyn rege, qufx u zodiv ax ipxupoypoov lae tgu undiborz lidxic. Qog keyo qua voed enp yicjigfoxgi ucljeyafutx? Ij wqet itutbji, uj a lawolr hureki, ldilucnr boq. Giy fhi tuho qegvzebozag ties wurmut cijgow xus, txu tufdud ndi ifjwosalipn, af thone tosf qo yofot netyas xetguhxs.
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 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.