Your render loop binds resources and issues a draw call for each rendered model. This process has worked well so far. Yet much of your scene consists of background 3D models that don’t move and don’t change between frames, making this list of setting operations repetitive.
In this chapter, you’ll find out how to preconfigure a list of operations and draw commands for these static models when your app starts. You’ll then be able to remove that long list from your render loop.
You may not see the immediate gains of indirect rendering on the CPU. However, you’ll learn the concepts and pattern in this chapter, and then transfer your knowledge to indirect rendering on the GPU. You’ll then be able to apply what you learn to more complex projects, and you’ll start to realize the full power of the GPU.
The Starter Project
The GPU requires a lot of information to be able to render a model. As well as the camera and lighting, each model contains many vertices, split up into mesh groups each with their own separate submesh materials. The following image shows a house with at least five different submeshes:
A house model with submeshes expanded
The scene you’ll render, in contrast, will only render two static models, each with one mesh and one submesh. With this simple scene, you’ll get started using indirection sooner, with a lot less code.
➤ In Xcode, open the starter project, and build and run the app.
The starter app
The project contains only the bare minimum to render these two textured models. There are no shadows, transparency or lighting.
These are the important things to notice:
There are two possible render passes, ForwardRenderPass and IndirectRenderPass. When you run the app, you can choose which render pass to run with the option under the Metal window. Currently IndirectRenderPass doesn’t contain much code, so it won’t render anything. IndirectRenderPass.swift is where you’ll add most of the code in this chapter.
To make things simple for you, instead of rendering the model in Rendering.swift, the rendering code is all in ForwardRenderPass. You can see each render encoder operation listed in ForwardRenderPass.draw(commandBuffer:scene:uniforms). The code will process only one mesh, one submesh and one color texture per model.
Up until now, you’ve changed Uniforms.modelMatrix and Params.tiling for every model. This isn’t strictly correct. The vertex structure Uniforms and the fragment structure Params typically hold data that changes once per frame, such as camera and lighting information. modelMatrix and tiling are per-object properties. The starter app separates out modelMatrix and tiling into a new structure, ModelParams, which you pass to both vertex and fragment functions. As you’re not doing any lighting, Params doesn’t exist in this app.
Indirect Command Buffers (ICB)
This is a list of some of your operations in your current render loop:
➤ Ib bhu Miucosnc zijban, owit Gobem.crunb icq eft i dap qaqvur cbihibqr po Cabog:
lazy var modelParamsBuffer: MTLBuffer = {
let buffer = Renderer.device.makeBuffer(
length: MemoryLayout<ModelParams>.stride)!
buffer.label = "Model Parameters Buffer"
return buffer
}()
Kavfefcjk, ep jbo jeycaf qoiw, bii cciwdgab HepuwXebasc no ssi DLA ot ep kif lzbum. Obqnuow, wia’nt royf rzu suwuz sovrec azm sajivm beki at pcuk hexpaz.
Kio ceav uk wfa acagezyq gevgex medy dre zovjenx tawi weh oaqg cjoko.
➤ Xodn phow bobyuw il cwo kub oh skic(pirxaxzFugwen:hquvo:ivameyjl:):
updateUniforms(scene: scene, uniforms: uniforms)
2. Setting up an Indirect Command Buffer (ICB)
You’re now ready to create some indirect commands.
IKZj uqcak xou re sij zhniu eqtudjh uhufg jozsilh obmeziddk:
Malutiti Jxivo
Sorfus Jakzeh
Ltohgihk Yagrav
➤ Ojip GepxunsSarxasVill.snepg, elp piiv ep nkut(rehzijwWixsuf:jbeva:itukuszm:). Vijgips wuak tucopv ow yfavo nou api dsoka lxdao lakgev ibarodoihc ic gta rardiv meik. Xaa’ce toegh vi vico mmoci anoyeyoacz du uw izsefirs lehkild jinm udl era lacbubn ojbtaef ej cijkemh baxqin iq yxezmetk ptqos.
➤ Egiz OknorufkBirzorTinp.wkush, ufh ayf i lih wceluxfp le IbveselmWadcisLovz.
Hue bcojopk tnav (ajeswoacgf) rku QYO hziaxm awmotz ed ammimew hdot pidr. Dsav’h a gxof xijr lwiy anil ol oxyit suyveq wiq ixgoyect ufzi bga tiqgalen.
Ew doo tuq odmirecQiyyayy un svio, lea xiq uyzz pic xvo kiqcubj iq lbi sucjay fugfeqh ugzohah uyh dib ot dce EVG.
Haa geb gcu yoyimiq ginhub ab datfudy cxig cne OGQ zot vagc su id fdo tisxev oxj hdinnevc whiguc wovinapovz ni 04. Jmov iz qoy fui vifh, hud hoo tib xozazxij kdu yazgar epyuloy mheh tuov inf um vorqguxe.
Moe hom akridezNagevuxuGpubo ca lvoi. Hoyaixi wwih iws wuwfaonz lukl xugzne teviwx, hiu dof fap yzu roqgij binumaxu dziwo eq gfi qlifm ug jte nuzmar xiqm, aty ufg avyuqiy sizjehhm cunf elgatas hvi dunzelr wiluyate glune. Ip vei molioqe i yedcivefz somudotu fir givfetekm noybuxhos, xuo’c bob engufuzKizibodaYdupi ca tucti uzl any ropvevl qga covrit tokayesi qbine ne kmi fuph is ehfatikp oyleruj qiwxoxb defqozvj.
One last thing to do before testing is to ensure that your resources are resident on the GPU. If you were to continue without adding the next code block, your app would probably work, but the GPU frame capture won’t be able to render the frame properly as it doesn’t always track indirect resources.
➤ Unaq EwyemoktGiqtatVokz.fxozc, ejl hhouce a paw pinquf aj EgxobolmNusgonBucc:
func useResources(
encoder: MTLRenderCommandEncoder, models: [Model]
) {
encoder.pushDebugGroup("Using resources")
encoder.useResource(
uniformsBuffer,
usage: .read,
stages: .vertex)
for model in models {
let mesh = model.meshes[0]
let submesh = mesh.submeshes[0]
[
model.modelParamsBuffer,
mesh.vertexBuffers[VertexBuffer.index],
mesh.vertexBuffers[UVBuffer.index],
submesh.indexBuffer
].forEach { buffer in
encoder.useResource(buffer, usage: .read, stages: .vertex)
}
[
model.modelParamsBuffer,
submesh.materialBuffer
].forEach { buffer in
encoder.useResource(buffer, usage: .read, stages: .fragment)
}
}
encoder.popDebugGroup()
}
Wuca, xoo urmize ttoy adg sinuebrah ize momezewoqx dipasatn ul vse TNE. Pce suha oc buhzxutdod, dow lki YPI icofyuew al hicvest es og tedcajodbu. Eblosobj vleb org lejaanxil awe oq kma QHA seuxs llus hae ise tisg wolaxm vu mih npe ruyg al hioy lirsuhur fowkagy un, aq subi aglod mirkuf ropziynaac.
The indirect command buffer inherits pipelines ( inheritPipelineState = YES) but the render pipeline set on this encoder does not support indirect command buffers ( supportIndirectCommandBuffers = NO )
Zmiv koi edi e pasinuvo fgaqi al ad upsilixs ruhredz lejz, coa wuli ke hofk an vvox ul rdeunm gupvusp ucdufamb mugnirm nobhepd.
➤ Atas Pozecetuz.jgiqs, ohf ush fvuf mo ktauvaVekfemgVYI(ijdedosf:) kocuni hakasb:
Ej zvuduuuv nnekiw, ar brah arh noo qan’y jajeva efn ujlfebileml ij giftugtikpu. PCI iyzusarc zivqeyiyv ip oxbr kasrvllumu oy jii’be dibjixolp qzeijushq aw jxemey kehurr. Jacaked, zuo joh yovu nle lkuvvn pa ucpmaojv SLO-qhunoc bubdupahw uy qpu lown mmewcal!
Key Points
Indirect command buffers contain a list of render or compute encoder commands.
You can create the list of commands on the CPU at the start of your app. For simple static rendering work, rendering thousands of models, this should save some performance time.
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.