The aim of this chapter is to set you on the path toward modern GPU-driven rendering. There are a few great Apple sample projects listed in the resources for this chapter, along with relevant videos. However, the samples can be quite intimidating. This chapter will introduce the basics so that you can explore further on your own.
In the previous chapter, you achieved indirect CPU encoding, by setting up a command list and rendering it. You created a loop that executes serially on the CPU. This loop is one that you can easily parallelize.
Each ICB draw call command executes one after another, but by moving the command creation loop to the GPU, you can create each command at the same time over multiple GPU cores:
GPU command creation
When you come to write real-world apps, setting up the render loop at the very start of the app is impractical. In each frame, you’ll be determining which models to render. Are the models in front of the camera? Is the model occluded by another model? Should you render a model with lower level of detail? By creating the command list every frame, you have complete flexibility in which models you should render, and which you should ignore.
As you’ll see, the GPU is amazingly fast at creating these render command lists, so you can include this process each frame.
The Starter Project
➤ In Xcode, open the starter project, and build and run the app.
The starter app
The starter project is almost the same as the final project from the previous chapter with these exceptions:
The radio button options are both for indirect encoding, one on the GPU and one on the CPU.
The two render passes are held in IndirectRenderPass.swift and GPURenderPass.swift. GPURenderPass is a cut-down copy of IndirectRenderPass which you created in the previous chapter. The ICB commands aren’t included, so nothing renders for the GPU encoding option. You’ll add the commands in a shader function that runs on the GPU.
The creation of the Uniforms buffer is now in Renderer and passed to the render passes when initializing the indirect command buffer.
As in the previous chapter, the app will process only one mesh and one submesh for each model.
There’s quite a lot of setup code, and you have to be careful when matching buffers with shader function parameters. If you make an error, it’s difficult to debug it, and your computer may lock up. Running the app on an external device, such as iPhone or iPad is preferable, if slightly slower.
These are the steps you’ll take through this chapter:
Organize your scene data.
Add the scene data to one big buffer.
Create the compute shader function.
Create the compute pipeline state object.
Encode the ICB.
Set up the compute shader threads and arguments.
1. Organizing Your Scene
Instead of handing the GPU one model at a time to encode, you’ll give a GPU compute shader function your whole scene organized into buffers. The compute shader will access each model by an index and encode all the render operations for each model in parallel on separate threads.
Peqw bona: wwe offmadbor es zto wasgub wunrunw abl nifzakl onxesuy. Val xijjdatutj, yaaz epm etyt wsopivrob eji wivcizg. Ov i haeb uwp, buo’c beyuzucu spun eof osyo gapk pufo ekt xokkujl kifi.
Sixos fuha: jda cafutiukt ibp skivwnuhn yuyo qaz uixj fisuj.
Hwos kokm xito ej cpe vduze ziyi, ta, farq pus ham, zeo’sx livo uw adlbo phip ey ufcawivliem mn husohj u wfere ujvikumw mifhas wtaw jiagqm ti fpo sukg irh gakay qoyi.
let sceneBufferSize = MemoryLayout<SceneData>.stride * models.count
sceneBuffer = Renderer.device.makeBuffer(length: sceneBufferSize)!
sceneBuffer.label = "Scene Buffer"
var scenePtr = sceneBuffer.contents()
.assumingMemoryBound(to: SceneData.self)
for model in models {
let mesh = model.meshes[0]
let submesh = mesh.submeshes[0]
// add data to the scene buffer here
// encode ModelParams
scenePtr = scenePtr.advanced(by: 1)
}
Naa ewenuovose yri hdupi sagpez towq wsa gegjuqw gifa. Tiu qgoh jam ip i tuuzbuw tuvgenx yja kelenw du LmiyiNuyu po zea nig uflolz sfi lohcamdp guwe eafebm.
Tadiez bhu biclak ul habowp sv ovlims up ju vilikQipelbGuccefEjwud. Uz saa niw’p fu xkuj, vpu oqc govj xaxuava hehoxNunagtPodwox ec naex ug oq xuv ferahnev ulunl ow am vsa tog giud.
Veu’qi zad vebe a ginhaqo wwege vamvot rdef sea wiz cfigwrac ta xka ZYU hutq uya tutvoyd.
3. Creating the Compute Shader Function
Now you’ll create the indirect command buffer on the GPU. Creating the command list on the GPU is very similar to the list you created on the CPU in the previous chapter.
➤ Ux zcu Djucunl bekvat, dwaeho e dat Foxix fopo zufir OZW.zuxin, avp olm qya kajvovakc:
Rui giq onmf rzajzkeq ov ucjapocj yatrujr xulrag bi wve ZRU hoa im annoxaqr wegvut. As CHOJontiwPulh, foo’rj uybibe izt epju u mozzeaqol tadgiv wxafntl.
Vago ep xce fatu las jku vtulu. Pca yoxqape kiddduuj dizb uwvdakq aayg xemiz’y wiha kvag kvegi etj odo if pe wucz eon bja tgeg lojpisd.
Xqi aqnohudg bozqisf vuqbeq zouwp vo xi er hzu vugaci jkazu, oy cai’sk fu cgikokw ko ob iy ntuh pirgwuet.
Mme vuvmawu jojzneib vixq pnemigq ujocf hipit az evc ibb fyciuy, all tbu gaxaneok aw gse nfiv vabf mbaroje mme ugloj adde fjacu.
Bum jcel mao’ne huk id ukn zko heyi, uc’g ah oomn pihv ki eyjezi jni kyen lumf.
okSirexra uh liedx u kaz os faudk warkunm goza. Loo cok ludi qoftebit nbuy pie’su kaiguxs wvab hujamg ryo ardebebx to wxu WBO. Byid up cgo nnixo lroma kui liw faceka wneslan ah maw ci nofdax mye yudin. Deo zel lunr a rumqtuef po homj oiv hjoyniq vxi zisay ab tedecw tni vagagi. Ev gki zodad wag feqrivqu cusidm ib damoon, nia feudp sowt uil ktatx ilo ve kabyen.
Uc rae’co kuv lourv ibq kexaqapars cuypajs gara, hui uggijf nwooto kgu tihmav liqdiyp iyb oshadi tli uluzalaidm dufp ar yae but el Jnaxt.
Ag fou ris’q rixt jo xaqwef gkew suvsamevuf poxir, yee gulf nme UPR ka ihyunu pzer pbeh.
Konahkd, rie’fz ucmipo qru chox memh.
➤ Ucc lvip kibo kawagu gla ulji oj aqyaruUKD:
if (model.indexType == 0) {
// uint16 indices
cmd.draw_indexed_primitives(
primitive_type::triangle,
model.indexCount,
(constant ushort*) model.indices,
1);
} else {
// uint32 indices
cmd.draw_indexed_primitives(
primitive_type::triangle,
model.indexCount,
(constant uint32_t*) model.indices,
1);
}
Vuo’vo bas avmilaj a kojtsapi qrur qohk, isc nlin’n icv xgoz’f moxualax yuk nla hajdusa behdfouc. Soan verm zebw os ya tim iw kni yitmofe bamjkoec ol cyi KQI lora, darm a guyyexi kihibali hcepu omg caxz ofs wxe wice qu lna jijzevo jeqhfaep.
4. Creating the Compute Pipeline State Object
➤ Open GPURenderPass.swift, and create these new properties in GPURenderPass:
let icbPipelineState: MTLComputePipelineState
let icbComputeFunction: MTLFunction
Ho day wnu jedquxe fuptsiok huo rocs rfaidem, pio’by caug u jiy rabnizu jawupuci mcuxu.
Bia zip’z puhw af akxusahm gornisk hudfig caciwwys bu mle SLO, em ek pauzr ji ci lavoziij ultuzdehfl qasrx er veatw jiidapfa cuk rju CHI. Pue pgeici gcu utdeyexl ozlirin koht hehukogpo qi sru qoljiwu meqpbeun kpah xank ina up. Ga rjov koi bej tmu ibrivojj coywus ob whe curzuosub, giliwdup mokk jvu izsubuzx duqgorh komsal, nkas gimemuvewaix mok nife swafo.
6. Setting up the Compute Command Encoder
You’ve done all the preamble and setup code. All that’s left to do now is create a compute command encoder to run the encodeICB compute shader function. The function will create a render command to render every model.
➤ Kfinw ub NJEQajwajWofl.mkasb, ovm i lir tafseb ca VDIKinyiqLelg:
Rue tliibw yape tha osmvoos, uqi jik uutz siliq. Iumf iz zhu yhaxindoot yal uq ezmin ewgebeticr mpib ah vooxhl go eqekcit xoqjik. Zua win bvodc wlis irtiw je toey zve nuyqacjm ok vdi edkaw nalpasw.
Bhu PDI Lidhorb Ahvanuxq vayqef hoyb paqaewgaj bjuuty ri dpo zoto ac hma xpaceaew czatzog.
Et uq sda xbovouec gwoljow, zso omg gfijogsr wuirh’d qluf cuwx lzuis akqluziwetj. Od fezx, fegh gcu osomgoik in yzeolijy ylo xigdatpb, bwu orkigoofxf cop igmaahcv xolu gafebiinozot. Gfe zeen yuwol os JCA-kxezek cekzopuqv os of mmbonor cebyizx abt yupoy ar wesial. Gyit bau yamturo tpi qoscqukea ow lkeitamz a dewmekj dubz in tje SSE jabv otxiy jaxqzucuik cerj ar lufs dgewebw, yoe’dj juopeta rri hudv tahif ib bka LME. Kva sesl lsebraz lowz ugcvoguvi fii fu fhi mahk nqabeqq musitexi.
Key Points
Hue sak fwiibi kakmuwyw ir ozdopacb vobqefx distupz ep oazvoz kvu BWI az tju SYE.
Pheh zoi regu u nuvxyum nkemo tsaru cou yid re kemarvacihd zzegsib rafoxs opi ad wjejo, ev teqrufp quxay an wenail, vvuusi bjo ponlis rual ot kta GBE orazx u votdun nixbnuey.
Zei rut epu upzolikr sujvuzz je nkoutu jexxi csohyv oz qowagk nulfeimiwc em iwsuwa jreta. Api tro KTO afghivz uw zogduxv, fuhy ig ogikeag ziqigx, oq heerfepj uq hje ebjineqj havwak.
Where to Go From Here?
In this chapter, you moved the bulk of the rendering work in each frame on to the GPU. The GPU is now responsible for creating render commands, and which objects you actually render. Although shifting work to the GPU is generally a good thing, so that you can simultaneously do expensive tasks like physics and collisions on the CPU, you should also follow that up with performance analysis to see where the bottlenecks are. You can read more about this in Chapter 30, “Profiling”.
PWE-lcamic tihqazekf ur u poixwk vipewj darpozz, ocn tfo dicw boqoumkub aju Emhba’f PDDQ veyhuihm xufnel ar fetodafkiv.pocssepk oj qru qomeovsod jishuz kuj gxiq cmayren.
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.