1 module deferred;
2 
3 import buffer;
4 import example;
5 import pipeline;
6 import scene;
7 
8 import gfx.core;
9 import gfx.graal;
10 import gfx.math;
11 import gfx.window;
12 
13 import std.datetime : Duration;
14 import std.stdio;
15 
16 class DeferredExample : Example
17 {
18     Rc!RenderPass deferredRenderPass;
19     Rc!RenderPass bloomRenderPass;
20     Rc!RenderPass blendRenderPass;
21 
22     Rc!DescriptorPool descriptorPool;
23 
24     Rc!DeferredBuffers buffers;
25     Rc!DeferredPipelines pipelines;
26 
27     DescriptorSet geomDescriptorSet;
28     DescriptorSet lightBufDescriptorSet;
29 
30     Rc!Sampler bloomSampler;
31 
32     Duration lastTimeElapsed;
33 
34     DeferredScene scene;
35     FMat4 viewProj;
36     FVec3 viewerPos;
37 
38     Timer bufUpdateTimer;
39     Timer recordTimer;
40 
41     this(string[] args) {
42         super("Deferred", args ~ "--no-gl3");
43     }
44 
45     override void dispose()
46     {
47         if (device)
48             device.waitIdle();
49         bloomSampler.unload();
50         deferredRenderPass.unload();
51         bloomRenderPass.unload();
52         blendRenderPass.unload();
53         descriptorPool.reset();
54         descriptorPool.unload();
55         pipelines.unload();
56         buffers.unload();
57 
58         super.dispose();
59     }
60 
61     override void prepare()
62     {
63         super.prepare();
64         const rad = scene.prepare();
65         buffers = new DeferredBuffers(this, scene.saucerCount);
66         pipelines = new DeferredPipelines(device, deferredRenderPass,
67                 bloomRenderPass, blendRenderPass);
68 
69         bloomSampler = device.createSampler(
70             SamplerInfo.bilinear().withWrapMode(WrapMode.clamp)
71         );
72 
73         prepareDescriptors();
74 
75         viewerPos = 1.2 * fvec(rad, rad, rad);
76         const view = lookAt(viewerPos, fvec(0, 0, 0), fvec(0, 0, 1));
77         const proj = perspective!float(this.ndc, 45, 4f/3f, 1f, rad * 3f);
78         viewProj = proj * view;
79     }
80 
81     override void prepareRenderPass()
82     {
83         // Deferred render pass
84         //  - subpass 1: geom
85         //      renders geometry into 4 images (position, normal, color, shininess)
86         //  - subpass 2: light
87         //      render lighted scene into HDR image
88         //      the brightest areas are extracted into the bloomBase image at the same time
89         prepareDeferredRenderPass();
90 
91         // Bloom render pass
92         //  - done successively with flipping attachments
93         //    in order to perform horizontal and vertical
94         //    blur ping pong passes
95         //     - the first pass blurs the bloomBase image horizontally into
96         //       the blurH image
97         //     - the second pass blurs the blurH image vertically into
98         //       the blurV image
99         //     - the third pass blurs the blurV image horizontally into
100         //       the blurH image
101         //     - and so on...
102         //    a descriptor set is prepared for each case
103         prepareBloomRenderPass();
104 
105         // Blend render pass
106         //  - blend the lighted
107         prepareBlendRenderPass();
108     }
109 
110     final void prepareDeferredRenderPass()
111     {
112         enum Attachment {
113             worldPos,
114             normal,
115             color,
116             depth,
117 
118             hdrScene,
119             bloomBase,
120 
121             count,
122         }
123         enum Subpass {
124             geom,
125             light,
126 
127             count,
128         }
129 
130         auto attachments = new AttachmentDescription[Attachment.count];
131         auto subpasses = new SubpassDescription[Subpass.count];
132 
133         attachments[Attachment.worldPos] = AttachmentDescription.color(
134             Format.rgba32_sFloat, AttachmentOps(LoadOp.clear, StoreOp.dontCare),
135             trans(ImageLayout.undefined, ImageLayout.colorAttachmentOptimal)
136         );
137         attachments[Attachment.normal] = AttachmentDescription.color(
138             Format.rgba16_sFloat, AttachmentOps(LoadOp.clear, StoreOp.dontCare),
139             trans(ImageLayout.undefined, ImageLayout.colorAttachmentOptimal)
140         );
141         attachments[Attachment.color] = AttachmentDescription.color(
142             Format.rgba8_uNorm, AttachmentOps(LoadOp.clear, StoreOp.dontCare),
143             trans(ImageLayout.undefined, ImageLayout.colorAttachmentOptimal)
144         );
145         attachments[Attachment.depth] = AttachmentDescription.depth(
146             Format.d16_uNorm, AttachmentOps(LoadOp.clear, StoreOp.dontCare),
147             trans(ImageLayout.undefined, ImageLayout.depthStencilAttachmentOptimal)
148         );
149         attachments[Attachment.hdrScene] = AttachmentDescription.color(
150             Format.rgba16_sFloat, AttachmentOps(LoadOp.clear, StoreOp.store),
151             trans(ImageLayout.undefined, ImageLayout.shaderReadOnlyOptimal)
152         );
153         attachments[Attachment.bloomBase] = AttachmentDescription.color(
154             Format.rgba16_sFloat, AttachmentOps(LoadOp.clear, StoreOp.store),
155             trans(ImageLayout.undefined, ImageLayout.shaderReadOnlyOptimal)
156         );
157 
158         subpasses[Subpass.geom] = SubpassDescription(
159             // inputs
160             [],
161             // outputs
162             [
163                 AttachmentRef(Attachment.worldPos, ImageLayout.colorAttachmentOptimal),
164                 AttachmentRef(Attachment.normal, ImageLayout.colorAttachmentOptimal),
165                 AttachmentRef(Attachment.color, ImageLayout.colorAttachmentOptimal),
166             ],
167             // depth
168             some(AttachmentRef(Attachment.depth, ImageLayout.depthStencilAttachmentOptimal))
169         );
170         subpasses[Subpass.light] = SubpassDescription(
171             // inputs
172             [
173                 AttachmentRef(Attachment.worldPos, ImageLayout.shaderReadOnlyOptimal),
174                 AttachmentRef(Attachment.normal, ImageLayout.shaderReadOnlyOptimal),
175                 AttachmentRef(Attachment.color, ImageLayout.shaderReadOnlyOptimal),
176             ],
177             // outputs
178             [
179                 AttachmentRef(Attachment.hdrScene, ImageLayout.colorAttachmentOptimal),
180                 AttachmentRef(Attachment.bloomBase, ImageLayout.colorAttachmentOptimal),
181             ],
182             // depth
183             none!AttachmentRef
184         );
185         const dependencies = [
186             SubpassDependency(
187                 trans(subpassExternal, Subpass.geom),
188                 trans(PipelineStage.bottomOfPipe, PipelineStage.vertexShader),
189                 trans(Access.memoryWrite, Access.uniformRead)
190             ),
191             SubpassDependency(
192                 trans!uint(Subpass.geom, Subpass.light), // from geometry to lighting pass
193                 trans(PipelineStage.colorAttachmentOutput, PipelineStage.fragmentShader),
194                 trans(Access.colorAttachmentWrite, Access.inputAttachmentRead)
195             ),
196             SubpassDependency(
197                 trans(Subpass.light, subpassExternal),
198                 trans(PipelineStage.colorAttachmentOutput, PipelineStage.fragmentShader),
199                 trans(Access.colorAttachmentWrite, Access.shaderRead)
200             ),
201         ];
202 
203         deferredRenderPass = device.createRenderPass(attachments, subpasses, dependencies);
204     }
205 
206     final void prepareBloomRenderPass()
207     {
208         enum Attachment {
209             blurOutput,
210 
211             count,
212         }
213         enum Subpass {
214             blur,
215 
216             count,
217         }
218 
219         auto attachments = new AttachmentDescription[Attachment.count];
220         auto subpasses = new SubpassDescription[Subpass.count];
221 
222         attachments[Attachment.blurOutput] = AttachmentDescription.color(
223             Format.rgba16_sFloat, AttachmentOps(LoadOp.clear, StoreOp.store),
224             trans(ImageLayout.undefined, ImageLayout.shaderReadOnlyOptimal)
225         );
226 
227         subpasses[Subpass.blur] = SubpassDescription(
228             // inputs
229             [],
230             // outputs
231             [
232                 AttachmentRef(Attachment.blurOutput, ImageLayout.colorAttachmentOptimal),
233             ],
234             // depth
235             none!AttachmentRef
236         );
237         const dependencies = [
238             SubpassDependency(
239                 trans(subpassExternal, Subpass.blur),
240                 trans(PipelineStage.bottomOfPipe, PipelineStage.fragmentShader),
241                 trans(Access.memoryRead, Access.shaderRead)
242             ),
243             SubpassDependency(
244                 trans(Subpass.blur, subpassExternal),
245                 trans(PipelineStage.colorAttachmentOutput, PipelineStage.topOfPipe),
246                 trans(Access.colorAttachmentWrite, Access.memoryRead)
247             ),
248         ];
249 
250         bloomRenderPass = device.createRenderPass(attachments, subpasses, dependencies);
251     }
252 
253     final void prepareBlendRenderPass()
254     {
255         enum Attachment {
256             hdrScene,
257             bloom,
258 
259             swcColor,
260 
261             count,
262         }
263         enum Subpass {
264             blend,
265 
266             count,
267         }
268 
269         auto attachments = new AttachmentDescription[Attachment.count];
270         auto subpasses = new SubpassDescription[Subpass.count];
271 
272         attachments[Attachment.hdrScene] = AttachmentDescription.color(
273             Format.rgba16_sFloat, AttachmentOps(LoadOp.load, StoreOp.dontCare),
274             trans(ImageLayout.shaderReadOnlyOptimal, ImageLayout.colorAttachmentOptimal)
275         );
276         attachments[Attachment.bloom] = AttachmentDescription.color(
277             Format.rgba16_sFloat, AttachmentOps(LoadOp.load, StoreOp.dontCare),
278             trans(ImageLayout.shaderReadOnlyOptimal, ImageLayout.colorAttachmentOptimal)
279         );
280         attachments[Attachment.swcColor] = AttachmentDescription.color(
281             swapchain.format, AttachmentOps(LoadOp.clear, StoreOp.store),
282             trans(ImageLayout.undefined, ImageLayout.presentSrc)
283         );
284 
285         subpasses[Subpass.blend] = SubpassDescription(
286             // inputs
287             [
288                 AttachmentRef(Attachment.hdrScene, ImageLayout.shaderReadOnlyOptimal),
289                 AttachmentRef(Attachment.bloom, ImageLayout.shaderReadOnlyOptimal),
290             ],
291             // outputs
292             [
293                 AttachmentRef(Attachment.swcColor, ImageLayout.colorAttachmentOptimal),
294             ],
295             // depth
296             none!AttachmentRef
297         );
298         const dependencies = [
299             SubpassDependency(
300                 trans(subpassExternal, Subpass.blend),
301                 trans(PipelineStage.bottomOfPipe, PipelineStage.colorAttachmentOutput),
302                 trans(Access.memoryRead, Access.colorAttachmentWrite)
303             ),
304             SubpassDependency(
305                 trans(Subpass.blend, subpassExternal),
306                 trans(PipelineStage.colorAttachmentOutput, PipelineStage.topOfPipe),
307                 trans(Access.colorAttachmentWrite, Access.memoryRead)
308             ),
309         ];
310 
311         blendRenderPass = device.createRenderPass(attachments, subpasses, dependencies);
312     }
313 
314     class DeferredFrameData : FrameData
315     {
316         struct FbImage
317         {
318             Image img;
319             ImageView view;
320 
321             this(Device device, DeferredExample ex, ImageInfo info) {
322                 img = retainObj(device.createImage(info));
323                 ex.bindImageMemory(img);
324                 const aspect = info.usage & ImageUsage.depthStencilAttachment ?
325                     ImageAspect.depth :
326                     ImageAspect.color;
327                 view = retainObj(img.createView(
328                     ImageType.d2, ImageSubresourceRange(aspect), Swizzle.identity
329                 ));
330             }
331 
332             this(this) {
333                 if (img) {
334                     retainObj(img);
335                     retainObj(view);
336                 }
337             }
338 
339             ~this() {
340                 if (img) {
341                     releaseObj(view);
342                     releaseObj(img);
343                 }
344             }
345 
346             ImageDescriptor attachmentDescriptor()
347             {
348                 return view.descriptor(ImageLayout.shaderReadOnlyOptimal);
349             }
350 
351             ImageSamplerDescriptor samplerDescriptor(Sampler sampler)
352             {
353                 return view.descriptorWithSampler(ImageLayout.shaderReadOnlyOptimal, sampler);
354             }
355         }
356         // G-buffer
357         FbImage worldPos;
358         FbImage normal;
359         FbImage color;
360 
361         /// depth buffer
362         FbImage depth;
363 
364         // HDR image the scene is rendered to
365         FbImage hdrScene;
366         FbImage bloomBase;
367 
368         // Framebuffer for deferred pass
369         Rc!Framebuffer deferredFramebuffer;
370 
371         // ping pong blur framebuffers for blooming
372         FbImage blurH;
373         FbImage blurV;
374         Rc!Framebuffer blurHFramebuffer;
375         Rc!Framebuffer blurVFramebuffer;
376 
377         // final blend framebuffer
378         Rc!Framebuffer blendFramebuffer;
379 
380         /// Command buffer
381         PrimaryCommandBuffer cmdBuf;
382 
383         /// Descriptors
384         Rc!DescriptorPool descriptorPool;
385         DescriptorSet lightAttachDescriptorSet;
386         DescriptorSet[] bloomDescriptorSets;
387         DescriptorSet blendDescriptorSet;
388 
389         this(ImageBase swcColor, CommandBuffer tempBuf)
390         {
391             import std.exception : enforce;
392 
393             super(swcColor);
394 
395             cmdBuf = cmdPool.allocatePrimary(1)[0];
396 
397             worldPos = FbImage(device, this.outer, ImageInfo.d2(size[0], size[1])
398                     .withFormat(Format.rgba32_sFloat)
399                     .withUsage(ImageUsage.colorAttachment | ImageUsage.inputAttachment)
400                 );
401             normal = FbImage(device, this.outer, ImageInfo.d2(size[0], size[1])
402                     .withFormat(Format.rgba16_sFloat)
403                     .withUsage(ImageUsage.colorAttachment | ImageUsage.inputAttachment)
404                 );
405             color = FbImage(device, this.outer, ImageInfo.d2(size[0], size[1])
406                     .withFormat(Format.rgba8_uNorm)
407                     .withUsage(ImageUsage.colorAttachment | ImageUsage.inputAttachment)
408                 );
409             depth = FbImage(device, this.outer, ImageInfo.d2(size[0], size[1])
410                     .withFormat(Format.d16_uNorm)
411                     .withUsage(ImageUsage.depthStencilAttachment)
412                 );
413             hdrScene = FbImage(device, this.outer, ImageInfo.d2(size[0], size[1])
414                     .withFormat(Format.rgba16_sFloat)
415                     .withUsage(ImageUsage.colorAttachment | ImageUsage.inputAttachment)
416                 );
417             bloomBase = FbImage(device, this.outer, ImageInfo.d2(size[0], size[1])
418                     .withFormat(Format.rgba16_sFloat)
419                     .withUsage(ImageUsage.colorAttachment | ImageUsage.sampled )
420                 );
421             blurH = FbImage(device, this.outer, ImageInfo.d2(size[0], size[1])
422                     .withFormat(Format.rgba16_sFloat)
423                     .withUsage(ImageUsage.colorAttachment | ImageUsage.sampled)
424                 );
425             blurV = FbImage(device, this.outer, ImageInfo.d2(size[0], size[1])
426                     .withFormat(Format.rgba16_sFloat)
427                     .withUsage(ImageUsage.colorAttachment | ImageUsage.inputAttachment | ImageUsage.sampled)
428                 );
429 
430             auto swcColorView = swcColor.createView(
431                 ImageType.d2, ImageSubresourceRange(ImageAspect.color), Swizzle.identity
432             ).rc;
433 
434             deferredFramebuffer = this.outer.device.createFramebuffer(
435                 this.outer.deferredRenderPass, [
436                     worldPos.view, normal.view, color.view,
437                     depth.view, hdrScene.view, bloomBase.view
438                 ], size[0], size[1], 1
439             );
440 
441             blurHFramebuffer = this.outer.device.createFramebuffer(
442                 this.outer.bloomRenderPass, [
443                     blurH.view,
444                 ], size[0], size[1], 1
445             );
446             blurVFramebuffer = this.outer.device.createFramebuffer(
447                 this.outer.bloomRenderPass, [
448                     blurV.view,
449                 ], size[0], size[1], 1
450             );
451 
452             blendFramebuffer = this.outer.device.createFramebuffer(
453                 this.outer.blendRenderPass, [
454                     hdrScene.view, blurV.view, swcColorView.obj,
455                 ], size[0], size[1], 1
456             );
457         }
458 
459         final void prepareDescriptors()
460         {
461             if (descriptorPool) return; // already initialized
462 
463             auto pipelines = this.outer.pipelines.obj;
464             auto bloomSampler = this.outer.bloomSampler.obj;
465 
466             const poolSizes = [
467                 DescriptorPoolSize(DescriptorType.inputAttachment, 5),
468                 DescriptorPoolSize(DescriptorType.combinedImageSampler, 3),
469             ];
470             descriptorPool = device.createDescriptorPool(5, poolSizes);
471             auto sets = descriptorPool.allocate([
472                 pipelines.light.descriptorLayouts[1],
473                 pipelines.bloom.descriptorLayouts[0],
474                 pipelines.bloom.descriptorLayouts[0],
475                 pipelines.bloom.descriptorLayouts[0],
476                 pipelines.blend.descriptorLayouts[0],
477             ]);
478             lightAttachDescriptorSet = sets[0];
479             bloomDescriptorSets = sets[1 .. 4];
480             blendDescriptorSet = sets[4];
481 
482             auto writes = [
483                 WriteDescriptorSet(lightAttachDescriptorSet, 0, 0, DescriptorWrite.make(
484                     DescriptorType.inputAttachment,
485                     [
486                         worldPos.attachmentDescriptor,
487                         normal.attachmentDescriptor,
488                         color.attachmentDescriptor,
489                     ]
490                 )),
491 
492                 WriteDescriptorSet(bloomDescriptorSets[0], 0, 0, DescriptorWrite.make(
493                     DescriptorType.combinedImageSampler,
494                     blurH.samplerDescriptor(bloomSampler),
495                 )),
496 
497                 WriteDescriptorSet(bloomDescriptorSets[1], 0, 0, DescriptorWrite.make(
498                     DescriptorType.combinedImageSampler,
499                     blurV.samplerDescriptor(bloomSampler),
500                 )),
501 
502                 WriteDescriptorSet(bloomDescriptorSets[2], 0, 0, DescriptorWrite.make(
503                     DescriptorType.combinedImageSampler,
504                     bloomBase.samplerDescriptor(bloomSampler),
505                 )),
506 
507                 WriteDescriptorSet(blendDescriptorSet, 0, 0, DescriptorWrite.make(
508                     DescriptorType.inputAttachment,
509                     [
510                         hdrScene.attachmentDescriptor,
511                         blurV.attachmentDescriptor,
512                     ]
513                 )),
514             ];
515             device.updateDescriptorSets(writes, []);
516         }
517 
518         override void dispose()
519         {
520             if (descriptorPool) descriptorPool.reset();
521             descriptorPool.unload();
522             blendFramebuffer.unload();
523             blurVFramebuffer.unload();
524             blurHFramebuffer.unload();
525             deferredFramebuffer.unload();
526             blurH = FbImage.init;
527             blurV = FbImage.init;
528             depth = FbImage.init;
529             bloomBase = FbImage.init;
530             hdrScene = FbImage.init;
531             color = FbImage.init;
532             normal = FbImage.init;
533             worldPos = FbImage.init;
534             cmdPool.free([ cast(CommandBuffer)cmdBuf ]);
535             super.dispose();
536         }
537     }
538 
539     override FrameData makeFrameData(ImageBase swcColor, CommandBuffer tempBuf)
540     {
541         return new DeferredFrameData(swcColor, tempBuf);
542     }
543 
544     final void prepareDescriptors()
545     {
546         const poolSizes = [
547             DescriptorPoolSize(DescriptorType.uniformBuffer, 2),
548             DescriptorPoolSize(DescriptorType.uniformBufferDynamic, 2),
549         ];
550 
551         descriptorPool = device.createDescriptorPool(7, poolSizes);
552         auto sets = descriptorPool.allocate([
553             pipelines.geom.descriptorLayouts[0],
554             pipelines.light.descriptorLayouts[0],
555         ]);
556         geomDescriptorSet = sets[0];
557         lightBufDescriptorSet = sets[1];
558 
559         auto writes = [
560             WriteDescriptorSet(geomDescriptorSet, 0, 0, DescriptorWrite.make(
561                 DescriptorType.uniformBuffer,
562                 buffers.geomFrameUbo.descriptor(),
563             )),
564             WriteDescriptorSet(geomDescriptorSet, 1, 0, DescriptorWrite.make(
565                 DescriptorType.uniformBufferDynamic,
566                 buffers.geomModelUbo.descriptor(0, 1),
567             )),
568             WriteDescriptorSet(lightBufDescriptorSet, 0, 0, DescriptorWrite.make(
569                 DescriptorType.uniformBuffer,
570                 buffers.lightFrameUbo.descriptor(),
571             )),
572             WriteDescriptorSet(lightBufDescriptorSet, 1, 0, DescriptorWrite.make(
573                 DescriptorType.uniformBufferDynamic,
574                 buffers.lightModelUbo.descriptor(0, 1),
575             )),
576         ];
577         device.updateDescriptorSets(writes, []);
578     }
579 
580     void updateSceneAndBuffers(in float dt)
581     {
582         import std.math : sqrt;
583 
584         auto ft = bufUpdateTimer.frame();
585 
586         scene.mov.rotate(dt);
587         const M1 = scene.mov.transform();
588 
589         foreach (ref ss; scene.subStructs) {
590             ss.mov.rotate(dt);
591             const M2 = M1 * ss.mov.transform();
592 
593             foreach (ref s; ss.saucers) {
594                 s.anim(dt);
595                 const M3 = M2 * s.mov.transform();
596 
597                 foreach (bi; 0 .. 3) {
598                     buffers.geomModelUbo.data[s.saucerIdx].data[bi] = GeomModelData(
599                         (M3 * s.bodies[bi].transform).transpose(),
600                         fvec(
601                             s.bodies[bi].color,
602                             s.bodies[bi].shininess,
603                         ),
604                     );
605                 }
606 
607                 const lightPosMat = M3 * translation(s.lightPos);
608                 // set the light sphere volume as a function of brightness on the edge
609                 // brightness = luminosity / (distance ^ 2 + 1)
610                 enum edgeBrightness = 0.02;
611                 const lightRadius = sqrt(s.lightLuminosity / edgeBrightness - 1.0);
612                 buffers.lightModelUbo.data[s.saucerIdx] = LightModelUbo(
613                     transpose(viewProj * lightPosMat * scale(FVec3(lightRadius))),
614                     lightPosMat * fvec(0, 0, 0, 1),
615                     fvec(
616                         s.lightAnim.color,
617                         s.lightLuminosity,
618                     ),
619                 );
620             }
621         }
622 
623         buffers.geomFrameUbo.data[0].viewProj = viewProj.transpose();
624         buffers.lightFrameUbo.data[0].viewerPos = fvec(viewerPos, 1.0);
625 
626         buffers.flush(device.obj);
627     }
628 
629     override Submission[] recordCmds(FrameData frameData)
630     {
631         import std.datetime : dur;
632 
633         auto dfd = cast(DeferredFrameData)frameData;
634         dfd.prepareDescriptors();
635 
636         // update scene
637         const time = timeElapsed();
638         if (time > lastTimeElapsed) {
639             const dt = (time - lastTimeElapsed).total!"hnsecs" / 10_000_000.0;
640             updateSceneAndBuffers(dt);
641             lastTimeElapsed = time;
642         }
643 
644         auto ft = recordTimer.frame();
645 
646         PrimaryCommandBuffer buf = dfd.cmdBuf;
647 
648         buf.begin(CommandBufferUsage.oneTimeSubmit);
649 
650             buf.setViewport(0, [ Viewport(0f, 0f, cast(float)surfaceSize[0], cast(float)surfaceSize[1]) ]);
651             buf.setScissor(0, [ Rect(0, 0, surfaceSize[0], surfaceSize[1]) ]);
652 
653             deferredPass(buf, dfd);
654 
655             bloomPasses(buf, dfd);
656 
657             blendPass(buf, dfd);
658 
659         buf.end();
660 
661         return simpleSubmission([ buf ]);
662     }
663 
664     void deferredPass(PrimaryCommandBuffer buf, DeferredFrameData dfd)
665     {
666         const(ClearValues[6]) cvs = [
667             // clearing all attachments in the framebuffer
668             ClearValues(ClearColorValues( 0f, 0f, 0f, 0f )), // world-pos
669             ClearValues(ClearColorValues( 0f, 0f, 0f, 0f )), // normal
670             ClearValues(ClearColorValues( 0f, 0f, 0f, 0f )), // color
671             ClearValues(ClearDepthStencilValues( 1f, 0 )), // depth
672             ClearValues(ClearColorValues( 0f, 0f, 0f, 0f )), // hdrScene
673             ClearValues(ClearColorValues( 0f, 0f, 0f, 1f )), // bloom base
674         ];
675 
676         buf.beginRenderPass(
677             deferredRenderPass,
678             dfd.deferredFramebuffer,
679             Rect(0, 0, surfaceSize[0], surfaceSize[1]),
680             cvs[],
681         );
682 
683             // geometry pass
684 
685             buf.bindPipeline(pipelines.geom.pipeline);
686             buffers.hiResSphere.bindIndex(buf);
687             buf.bindVertexBuffers(0, [ buffers.hiResSphere.vertexBinding() ]);
688 
689             foreach (ref ss; scene.subStructs) {
690                 foreach (ref s; ss.saucers) {
691                     buf.bindDescriptorSets(
692                         PipelineBindPoint.graphics,
693                         pipelines.geom.layout, 0, [ geomDescriptorSet ],
694                         [ s.saucerIdx * GeomModelUbo.sizeof ]
695                     );
696                     // only draw the bulbs that are off
697                     const numInstances = s.lightAnim.on ? 2 : 3;
698                     buf.drawIndexed(
699                         cast(uint)buffers.hiResSphere.indicesCount, numInstances, 0, 0, 0
700                     );
701                 }
702             }
703 
704             // now draw the "on" bulbs with inverted normals as the light is from within
705             buffers.invertedSphere.bindIndex(buf);
706             buf.bindVertexBuffers(0, [ buffers.invertedSphere.vertexBinding() ]);
707 
708             foreach (ref ss; scene.subStructs) {
709                 foreach (ref s; ss.saucers) {
710                     if (!s.lightAnim.on) continue;
711 
712                     buf.bindDescriptorSets(
713                         PipelineBindPoint.graphics,
714                         pipelines.geom.layout, 0, [ geomDescriptorSet ],
715                         [ s.saucerIdx * GeomModelUbo.sizeof ]
716                     );
717                     buf.drawIndexed(
718                         cast(uint)buffers.hiResSphere.indicesCount, 1, 0, 0, 2
719                     );
720                 }
721             }
722 
723         buf.nextSubpass();
724 
725             // light pass
726 
727             buf.bindPipeline(pipelines.light.pipeline);
728             buffers.loResSphere.bindIndex(buf);
729             buf.bindVertexBuffers(0, [ buffers.loResSphere.vertexBinding() ]);
730             buf.bindDescriptorSets(
731                 PipelineBindPoint.graphics,
732                 pipelines.light.layout, 1, [ dfd.lightAttachDescriptorSet ], []
733             );
734 
735             foreach (ref ss; scene.subStructs) {
736                 foreach (ref s; ss.saucers) {
737 
738                     if (!s.lightAnim.on) continue;
739 
740                     buf.bindDescriptorSets(
741                         PipelineBindPoint.graphics,
742                         pipelines.light.layout, 0, [ lightBufDescriptorSet ],
743                         [ s.saucerIdx * LightModelUbo.sizeof ]
744                     );
745                     buf.drawIndexed(
746                         cast(uint)buffers.loResSphere.indicesCount, 1, 0, 0, 0
747                     );
748                 }
749             }
750 
751         buf.endRenderPass();
752 
753     }
754 
755     void bloomPasses(PrimaryCommandBuffer buf, DeferredFrameData dfd)
756     {
757         buf.bindPipeline(pipelines.bloom.pipeline);
758         buffers.square.bindIndex(buf);
759         buf.bindVertexBuffers(0, [ buffers.square.vertexBinding() ]);
760 
761         const cv = ClearValues(ClearColorValues( 0f, 0f, 0f, 1f ));
762 
763         enum numBlurPasses = 4;
764 
765         foreach(i; 0 .. numBlurPasses) {
766             foreach(v; 0 .. 2) {
767                 const h = 1 - v;
768                 // input descriptor sets:
769                 //  0: blurH, 1: blurV, 2: bloomBase (result of previous pass)
770                 auto dsInd = (i+v == 0) ? 2 : h;
771                 // output
772                 auto fb = v == 0 ? dfd.blurHFramebuffer.obj : dfd.blurVFramebuffer.obj;
773 
774                 buf.bindDescriptorSets(
775                     PipelineBindPoint.graphics,
776                     pipelines.bloom.layout, 0, dfd.bloomDescriptorSets[dsInd .. dsInd+1],
777                     []
778                 );
779 
780                 buf.beginRenderPass(bloomRenderPass, fb,
781                         Rect(0, 0, surfaceSize[0], surfaceSize[1]),
782                         (&cv)[0 .. 1]);
783 
784                     buf.pushConstants(pipelines.bloom.layout, ShaderStage.fragment,
785                         0, int.sizeof, &h
786                     );
787 
788                     buf.drawIndexed(buffers.square.indicesCount, 1, 0, 0, 0);
789 
790                 buf.endRenderPass();
791             }
792         }
793     }
794 
795 
796     void blendPass(PrimaryCommandBuffer buf, DeferredFrameData dfd)
797     {
798         buf.bindPipeline(pipelines.blend.pipeline);
799         // square mesh already bound in bloom pass
800         buf.bindDescriptorSets(
801             PipelineBindPoint.graphics,
802             pipelines.blend.layout, 0, [ dfd.blendDescriptorSet ],
803             []
804         );
805 
806         const cvs = [
807             ClearValues(ClearColorValues( 0f, 0f, 0f, 1f )),
808             ClearValues(ClearColorValues( 0f, 0f, 0f, 1f )),
809             ClearValues(ClearColorValues( 0f, 0f, 0f, 1f )),
810         ];
811 
812         buf.beginRenderPass(blendRenderPass, dfd.blendFramebuffer.obj,
813                 Rect(0, 0, surfaceSize[0], surfaceSize[1]), cvs);
814 
815             buf.drawIndexed(buffers.square.indicesCount, 1, 0, 0, 0);
816 
817         buf.endRenderPass();
818     }
819 }
820 
821 int main(string[] args)
822 {
823     try {
824         auto example = new DeferredExample(args);
825         example.prepare();
826         scope(exit) example.dispose();
827 
828         example.window.onKeyOn = (KeyEvent ev)
829         {
830             if (ev.sym == KeySym.escape) {
831                 example.window.closeFlag = true;
832             }
833         };
834 
835         while (!example.window.closeFlag) {
836             example.render();
837             example.frameTick();
838 
839             enum timerReportFreq = 300;
840             if (example.recordTimer.framecount % timerReportFreq == 0) {
841                 gfxExLog.infof("buf update  = %s µs", example.bufUpdateTimer.avgDur.total!"usecs");
842                 gfxExLog.infof("record cmds = %s µs", example.recordTimer.avgDur.total!"usecs");
843             }
844 
845             example.display.pollAndDispatch();
846         }
847         return 0;
848     }
849     catch(Exception ex)
850     {
851         import std.stdio : stderr;
852 
853         stderr.writeln("error occured: ", ex.msg);
854         return 1;
855     }
856 }