1 module shadow;
2 
3 import example;
4 
5 import gfx.core.rc;
6 import gfx.core.typecons;
7 import gfx.graal.buffer;
8 import gfx.graal.cmd;
9 import gfx.graal.format;
10 import gfx.graal.image;
11 import gfx.graal.pipeline;
12 import gfx.graal.renderpass;
13 import gfx.graal.sync;
14 import gfx.graal.types;
15 
16 import gfx.math;
17 
18 import std.math : PI;
19 import std.stdio;
20 import std.typecons;
21 
22 
23 final class ShadowExample : Example
24 {
25     // generic resources
26     Rc!Buffer vertBuf;
27     Rc!Buffer indBuf;
28 
29     Rc!Buffer meshUniformBuf;       // per mesh and per light
30     Rc!Buffer ligUniformBuf;        // per light
31 
32     Rc!DescriptorPool descPool;
33 
34     // shadow pass
35     Rc!Semaphore shadowFinishedSem;
36     Rc!RenderPass shadowRenderPass;
37     Rc!Pipeline shadowPipeline;
38     Rc!Image shadowTex;
39     Rc!Sampler shadowSampler;
40     Rc!Framebuffer shadowFramebuffer;
41     Rc!DescriptorSetLayout shadowDSLayout;
42     Rc!PipelineLayout shadowLayout;
43     DescriptorSet shadowDS;
44 
45     // mesh pass
46     Rc!RenderPass meshRenderPass;
47     Rc!Pipeline meshPipeline;
48     PerImage[] framebuffers;
49     Rc!ImageView meshShadowView;
50     Rc!DescriptorSetLayout meshDSLayout;
51     Rc!PipelineLayout meshLayout;
52     DescriptorSet meshDS;
53 
54     // scene
55     Mesh[] meshes;
56     Light[] lights;
57 
58     // constants
59     enum shadowSize = 2048;
60     enum maxLights = 5;
61 
62     final class PerImage : Disposable
63     {
64         ImageBase color;
65         Rc!ImageView colorView;
66         Rc!Image depth;
67         Rc!ImageView depthView;
68         Rc!Framebuffer meshFramebuffer;
69 
70         override void dispose() {
71             meshFramebuffer.unload();
72             colorView.unload();
73             depth.unload();
74             depthView.unload();
75         }
76     }
77 
78     // supporting structs
79 
80     static struct Vertex {
81         FVec3 position;
82         FVec3 normal;
83     }
84 
85     // uniform types
86     static struct ShadowVsLocals {  // meshDynamicBuf (per mesh and per light)
87         FMat4 proj;
88     }
89 
90     static struct MeshVsLocals {    // meshDynamicBuf (per mesh)
91         FMat4 mvp;
92         FMat4 model;
93     }
94 
95     static struct MeshFsMaterial {  // meshDynamicBuf (per mesh)
96         FVec4 color;
97         FVec4 padding;
98     }
99 
100     static struct LightBlk {        // within MeshFsLights
101         FVec4 position;
102         FVec4 color;
103         FMat4 proj;
104     }
105 
106     static struct MeshFsLights {     // ligUniformBuf (static, single instance)
107         int numLights;
108         int[3] padding;
109         LightBlk[maxLights] lights;
110     }
111 
112     // scene types
113     static struct Mesh {
114         uint vertOffset;
115         uint indOffset;
116         uint numVertices;
117         float pulse;
118         FVec4 color;
119         FMat4 model;
120     }
121 
122     static struct Light {
123         FVec4 position;
124         FVec4 color;
125         FMat4 view;
126         FMat4 proj;
127         CommandBuffer cmdBuf;
128         Rc!ImageView shadowPlane;
129         Rc!Framebuffer shadowFb;
130     }
131 
132     this(string[] args=[]) {
133         super("Shadow", args);
134     }
135 
136     override void dispose() {
137         device.waitIdle();
138 
139         vertBuf.unload();
140         indBuf.unload();
141         meshUniformBuf.unload();
142         ligUniformBuf.unload();
143 
144         descPool.unload();
145 
146         shadowFinishedSem.unload();
147         shadowRenderPass.unload();
148         shadowPipeline.unload();
149         shadowTex.unload();
150         shadowSampler.unload();
151         shadowFramebuffer.unload();
152         shadowDSLayout.unload();
153         shadowLayout.unload();
154 
155         meshRenderPass.unload();
156         meshPipeline.unload();
157         disposeArr(framebuffers);
158         meshShadowView.unload();
159         meshDSLayout.unload();
160         meshLayout.unload();
161 
162         reinitArr(lights);
163 
164         super.dispose();
165     }
166 
167     override void prepare() {
168         super.prepare();
169         prepareSceneAndResources();
170         prepareShadowRenderPass();
171         prepareMeshRenderPass();
172         prepareFramebuffers();
173         prepareDescriptors();
174         preparePipelines();
175     }
176 
177     void prepareSceneAndResources() {
178 
179         import std.exception : enforce;
180 
181         // setting up lights
182 
183         enum numLights = 3;
184 
185         shadowTex = device.createImage(
186             ImageInfo.d2Array(shadowSize, shadowSize, numLights)
187                 .withFormat(Format.d32_sFloat)
188                 .withUsage(ImageUsage.sampled | ImageUsage.depthStencilAttachment)
189         );
190         enforce(bindImageMemory(shadowTex));
191         meshShadowView = shadowTex.createView(
192             ImageType.d2Array,
193             ImageSubresourceRange(ImageAspect.depth, 0, 1, 0, numLights),
194             Swizzle.identity
195         );
196         {
197             auto b = autoCmdBuf().rc;
198             b.cmdBuf.pipelineBarrier(
199                 trans(PipelineStage.topOfPipe, PipelineStage.earlyFragmentTests), [], [
200                     ImageMemoryBarrier(
201                         trans(Access.none, Access.depthStencilAttachmentRead | Access.depthStencilAttachmentWrite),
202                         trans(ImageLayout.undefined, ImageLayout.depthStencilAttachmentOptimal),
203                         trans(queueFamilyIgnored, queueFamilyIgnored),
204                         shadowTex, ImageSubresourceRange(ImageAspect.depth, 0, 1, 0, numLights)
205                     )
206                 ]
207             );
208         }
209 
210         shadowSampler = device.createSampler(SamplerInfo(
211             Filter.linear, Filter.linear, Filter.nearest,
212             [WrapMode.repeat, WrapMode.repeat, WrapMode.repeat],
213             none!float, 0f, [0f, 0f], some(CompareOp.lessOrEqual)
214         ));
215 
216         auto ligCmdBufs = cmdPool.allocate(numLights);
217 
218         auto makeLight(uint layer, FVec3 pos, FVec4 color, float fov)
219         {
220             enum near = 5f;
221             enum far = 20f;
222 
223             Light l;
224             l.position = fvec(pos, 1);
225             l.color = color;
226             l.view = lookAt(pos, fvec(0, 0, 0), fvec(0, 0, 1));
227             l.proj = perspective(fov, 1f, near, far);
228             l.shadowPlane = shadowTex.createView(
229                 ImageType.d2,
230                 ImageSubresourceRange(
231                     ImageAspect.depth, 0, 1, layer, 1
232                 ),
233                 Swizzle.identity
234             );
235             l.cmdBuf = ligCmdBufs[layer];
236 
237             return l;
238         }
239         lights = [
240             makeLight(0, fvec(7, -5, 10), fvec(0.5, 0.7, 0.5, 1), 60),
241             makeLight(1, fvec(-5, 7, 10), fvec(0.7, 0.5, 0.5, 1), 45),
242             makeLight(2, fvec(10, 7, 5), fvec(0.5, 0.5, 0.7, 1), 90),
243         ];
244 
245         {
246             MeshFsLights mfl;
247             mfl.numLights = numLights;
248             foreach (ind, l; lights) {
249                 mfl.lights[ind] = LightBlk(
250                     l.position, l.color, transpose(l.proj*l.view)
251                 );
252             }
253             auto data = cast(void[])((&mfl)[0 .. 1]);
254             ligUniformBuf = createStaticBuffer(data, BufferUsage.uniform);
255         }
256 
257         // setup meshes
258 
259         // buffers layout:
260         // vertBuf: cube vertices | plane vertices
261         // indBuf:  cube indices  | plane indices
262 
263         import gfx.genmesh.cube : genCube;
264         import gfx.genmesh.algorithm : indexCollectMesh, triangulate, vertices;
265         import gfx.genmesh.poly : quad;
266         import std.algorithm : map;
267 
268         const cube = genCube()
269                 .map!(f => quad(
270                     Vertex( fvec(f[0].p), fvec(f[0].n) ),
271                     Vertex( fvec(f[1].p), fvec(f[1].n) ),
272                     Vertex( fvec(f[2].p), fvec(f[2].n) ),
273                     Vertex( fvec(f[3].p), fvec(f[3].n) ),
274                 ))
275                 .triangulate()
276                 .vertices()
277                 .indexCollectMesh();
278 
279         const planeVertices = [
280             Vertex(fvec(-7, -7,  0), fvec(0,  0,  1)),
281             Vertex(fvec( 7, -7,  0), fvec(0,  0,  1)),
282             Vertex(fvec( 7,  7,  0), fvec(0,  0,  1)),
283             Vertex(fvec(-7,  7,  0), fvec(0,  0,  1)),
284         ];
285 
286         const ushort[] planeIndices = [ 0,  1,  2,  0,  2,  3 ];
287 
288         const cubeVertBytes = cast(uint)(cube.vertices.length * Vertex.sizeof);
289         const cubeIndBytes = cast(uint)(cube.indices.length * ushort.sizeof);
290 
291         const cubeVertOffset = 0;
292         const cubeIndOffset = 0;
293         const cubeNumIndices = cast(uint)cube.indices.length;
294         const planeVertOffset = cubeVertBytes;
295         const planeIndOffset = cubeIndBytes;
296         const planeNumIndices = cast(uint)planeIndices.length;
297 
298         auto makeMesh(in uint vertOffset, in uint indOffset, in uint numVertices, in float rpm,
299                       in FMat4 model, in FVec4 color) {
300             return Mesh(vertOffset, indOffset, numVertices, rpm*2*PI/3600f, color, model);
301         }
302 
303         auto makeCube(in float rpm, in FVec3 pos, in float scale, in float angle, in FVec4 color) {
304             const r = rotation(angle*PI/180f, normalize(pos));
305             const t = translation(pos);
306             const s = gfx.math.scale(scale, scale, scale);
307             const model = t * s * r;
308             return makeMesh(cubeVertOffset, cubeIndOffset, cubeNumIndices, rpm, model, color);
309         }
310 
311         auto makePlane(in FMat4 model, in FVec4 color) {
312             return makeMesh(planeVertOffset, planeIndOffset, planeNumIndices, 0, model, color);
313         }
314 
315         meshes = [
316             makeCube(3, fvec(-2, -2, 2), 0.7, 10, fvec(0.8, 0.2, 0.2, 1)),
317             makeCube(7, fvec(2, -2, 2), 1.3, 50, fvec(0.2, 0.8, 0.2, 1)),
318             makeCube(10, fvec(-2, 2, 2), 1.1, 140, fvec(0.2, 0.2, 0.8, 1)),
319             makeCube(5, fvec(2, 2, 2), 0.9, 210, fvec(0.8, 0.8, 0.2, 1)),
320             makePlane(FMat4.identity, fvec(1, 1, 1, 1)),
321         ];
322 
323         {
324             auto verts = cube.vertices ~ planeVertices;
325             vertBuf = createStaticBuffer(verts, BufferUsage.vertex);
326         }
327         {
328             auto inds = cube.indices ~ planeIndices;
329             indBuf = createStaticBuffer(inds, BufferUsage.index);
330         }
331         meshUniformBuf = createDynamicBuffer(
332             ShadowVsLocals.sizeof * meshes.length * lights.length +
333             MeshVsLocals.sizeof * meshes.length +
334             MeshFsMaterial.sizeof * meshes.length,
335             BufferUsage.uniform
336         );
337     }
338 
339     void prepareShadowRenderPass() {
340         const attachments = [
341             AttachmentDescription(
342                 Format.d32_sFloat, 1,
343                 AttachmentOps(LoadOp.clear, StoreOp.store),
344                 AttachmentOps(LoadOp.dontCare, StoreOp.dontCare),
345                 trans(ImageLayout.depthStencilAttachmentOptimal, ImageLayout.depthStencilAttachmentOptimal),
346                 No.mayAlias
347             ),
348         ];
349         const subpasses = [
350             SubpassDescription(
351                 [], [], some(AttachmentRef(0, ImageLayout.depthStencilAttachmentOptimal)), []
352             ),
353         ];
354         shadowRenderPass = device.createRenderPass(attachments, subpasses, []);
355         shadowFinishedSem = device.createSemaphore();
356     }
357 
358     void prepareMeshRenderPass() {
359         const attachments = [
360             AttachmentDescription(swapchain.format, 1,
361                 AttachmentOps(LoadOp.clear, StoreOp.store),
362                 AttachmentOps(LoadOp.dontCare, StoreOp.dontCare),
363                 trans(ImageLayout.presentSrc, ImageLayout.presentSrc),
364                 No.mayAlias
365             ),
366             AttachmentDescription(findDepthFormat(), 1,
367                 AttachmentOps(LoadOp.clear, StoreOp.dontCare),
368                 AttachmentOps(LoadOp.dontCare, StoreOp.dontCare),
369                 trans(ImageLayout.undefined, ImageLayout.depthStencilAttachmentOptimal),
370                 No.mayAlias
371             )
372         ];
373         const subpasses = [
374             SubpassDescription(
375                 [], [ AttachmentRef(0, ImageLayout.colorAttachmentOptimal) ],
376                 some(AttachmentRef(1, ImageLayout.depthStencilAttachmentOptimal)),
377                 []
378             )
379         ];
380         meshRenderPass = device.createRenderPass(attachments, subpasses, []);
381     }
382 
383     void prepareFramebuffers() {
384         foreach (ref l; lights) {
385             l.shadowFb = device.createFramebuffer(
386                 shadowRenderPass, [ l.shadowPlane.obj ], shadowSize, shadowSize, 1
387             );
388         }
389 
390         auto b = autoCmdBuf().rc;
391 
392         foreach (img; scImages) {
393 
394             auto pi = new PerImage;
395             pi.color = img;
396             pi.colorView = img.createView(
397                 ImageType.d2, ImageSubresourceRange(ImageAspect.color), Swizzle.identity
398             );
399             pi.depth = createDepthImage(surfaceSize[0], surfaceSize[1]);
400             pi.depthView = pi.depth.createView(
401                 ImageType.d2, ImageSubresourceRange(ImageAspect.depth), Swizzle.identity
402             );
403             pi.meshFramebuffer = device.createFramebuffer(meshRenderPass, [
404                 pi.colorView.obj, pi.depthView.obj
405             ], surfaceSize[0], surfaceSize[1], 1);
406 
407             b.cmdBuf.pipelineBarrier(
408                 trans(PipelineStage.colorAttachmentOutput, PipelineStage.colorAttachmentOutput), [], [
409                     ImageMemoryBarrier(
410                         trans(Access.none, Access.colorAttachmentWrite),
411                         trans(ImageLayout.undefined, ImageLayout.presentSrc),
412                         trans(queueFamilyIgnored, queueFamilyIgnored),
413                         img, ImageSubresourceRange(ImageAspect.color)
414                     )
415                 ]
416             );
417 
418             b.cmdBuf.pipelineBarrier(
419                 trans(PipelineStage.topOfPipe, PipelineStage.earlyFragmentTests), [], [
420                     ImageMemoryBarrier(
421                         trans(Access.none, Access.depthStencilAttachmentRead | Access.depthStencilAttachmentWrite),
422                         trans(ImageLayout.undefined, ImageLayout.depthStencilAttachmentOptimal),
423                         trans(queueFamilyIgnored, queueFamilyIgnored),
424                         pi.depth, ImageSubresourceRange(ImageAspect.depth)
425                     )
426                 ]
427             );
428 
429             framebuffers ~= pi;
430         }
431     }
432 
433     void prepareDescriptors() {
434 
435         shadowDSLayout = device.createDescriptorSetLayout(
436             [
437                 PipelineLayoutBinding(0, DescriptorType.uniformBufferDynamic, 1, ShaderStage.vertex),
438             ]
439         );
440         shadowLayout = device.createPipelineLayout(
441             [ shadowDSLayout ], []
442         );
443 
444         meshDSLayout = device.createDescriptorSetLayout(
445             [
446                 PipelineLayoutBinding(0, DescriptorType.uniformBufferDynamic, 1, ShaderStage.vertex),
447                 PipelineLayoutBinding(1, DescriptorType.uniformBufferDynamic, 1, ShaderStage.fragment),
448                 PipelineLayoutBinding(2, DescriptorType.uniformBuffer, 1, ShaderStage.fragment),
449                 PipelineLayoutBinding(3, DescriptorType.combinedImageSampler, 1, ShaderStage.fragment),
450             ]
451         );
452         meshLayout = device.createPipelineLayout(
453             [ meshDSLayout ], []
454         );
455 
456         descPool = device.createDescriptorPool( 2,
457             [
458                 DescriptorPoolSize(DescriptorType.uniformBufferDynamic, 3),
459                 DescriptorPoolSize(DescriptorType.uniformBuffer, 1),
460                 DescriptorPoolSize(DescriptorType.combinedImageSampler, 1)
461             ]
462         );
463 
464         auto dss = descPool.allocate( [ shadowDSLayout, meshDSLayout ] );
465         shadowDS = dss[0];
466         meshDS = dss[1];
467 
468         const shadowVsLen = cast(uint)(ShadowVsLocals.sizeof * lights.length * meshes.length);
469         const meshVsLen = cast(uint)(MeshVsLocals.sizeof * meshes.length);
470         const ligFsLen = cast(uint)MeshFsLights.sizeof;
471 
472         import std.algorithm : map;
473         import std.array : array;
474 
475         auto writes = [
476             WriteDescriptorSet(shadowDS, 0, 0, new UniformBufferDynamicDescWrites(
477                 [ BufferRange(meshUniformBuf, 0, ShadowVsLocals.sizeof) ]
478             )),
479             WriteDescriptorSet(meshDS, 0, 0, new UniformBufferDynamicDescWrites(
480                 [ BufferRange(meshUniformBuf, shadowVsLen, MeshVsLocals.sizeof) ]
481             )),
482             WriteDescriptorSet(meshDS, 1, 0, new UniformBufferDynamicDescWrites(
483                 [ BufferRange(meshUniformBuf, shadowVsLen+meshVsLen, MeshFsMaterial.sizeof) ]
484             )),
485             WriteDescriptorSet(meshDS, 2, 0, new UniformBufferDescWrites(
486                 [ BufferRange(ligUniformBuf, 0, ligFsLen) ]
487             )),
488             WriteDescriptorSet(meshDS, 3, 0, new CombinedImageSamplerDescWrites(
489                 [ CombinedImageSampler(shadowSampler, meshShadowView, ImageLayout.depthStencilReadOnlyOptimal) ]
490             ))
491         ];
492         device.updateDescriptorSets(writes, []);
493     }
494 
495     PipelineInfo prepareShadowPipeline()
496     {
497         PipelineInfo info;
498         info.shaders.vertex = device.createShaderModule(
499             cast(immutable(uint)[])import("shadow.vert.spv"), "main"
500         );
501         info.shaders.fragment = device.createShaderModule(
502             cast(immutable(uint)[])import("shadow.frag.spv"), "main"
503         );
504         info.shaders.vertex.retain();
505         info.shaders.fragment.retain();
506 
507         info.inputBindings = [
508             VertexInputBinding(0, Vertex.sizeof, No.instanced)
509         ];
510         info.inputAttribs = [
511             VertexInputAttrib(0, 0, Format.rgb32_sFloat, 0),
512             VertexInputAttrib(1, 0, Format.rgb32_sFloat, Vertex.normal.offsetof),
513         ];
514         info.assembly = InputAssembly(Primitive.triangleList, No.primitiveRestart);
515         info.rasterizer = Rasterizer(
516             PolygonMode.fill, Cull.back, FrontFace.ccw, No.depthClamp,
517             some(DepthBias(1f, 0f, 2f)), 1f
518         );
519         info.viewports = [
520             ViewportConfig(
521                 Viewport(0, 0, cast(float)shadowSize, cast(float)shadowSize),
522                 Rect(0, 0, shadowSize, shadowSize)
523             )
524         ];
525         info.depthInfo = DepthInfo(
526             Yes.enabled, Yes.write, CompareOp.lessOrEqual, No.boundsTest, 0f, 1f
527         );
528         info.blendInfo = ColorBlendInfo(
529             none!LogicOp, [], [ 0f, 0f, 0f, 0f ]
530         );
531         info.layout = shadowLayout;
532         info.renderPass = shadowRenderPass;
533         info.subpassIndex = 0;
534 
535         return info;
536     }
537 
538     PipelineInfo prepareMeshPipeline()
539     {
540         PipelineInfo info;
541         info.shaders.vertex = device.createShaderModule(
542             cast(immutable(uint)[])import("mesh.vert.spv"), "main"
543         );
544         info.shaders.fragment = device.createShaderModule(
545             cast(immutable(uint)[])import("mesh.frag.spv"), "main"
546         );
547         info.shaders.vertex.retain();
548         info.shaders.fragment.retain();
549 
550         info.inputBindings = [
551             VertexInputBinding(0, Vertex.sizeof, No.instanced)
552         ];
553         info.inputAttribs = [
554             VertexInputAttrib(0, 0, Format.rgb32_sFloat, 0),
555             VertexInputAttrib(1, 0, Format.rgb32_sFloat, Vertex.normal.offsetof),
556         ];
557         info.assembly = InputAssembly(Primitive.triangleList, No.primitiveRestart);
558         info.rasterizer = Rasterizer(
559             PolygonMode.fill, Cull.back, FrontFace.ccw, No.depthClamp,
560             none!DepthBias, 1f
561         );
562         info.viewports = [
563             ViewportConfig(
564                 Viewport(0, 0, cast(float)surfaceSize[0], cast(float)surfaceSize[1]),
565                 Rect(0, 0, surfaceSize[0], surfaceSize[1])
566             )
567         ];
568         info.depthInfo = DepthInfo(
569             Yes.enabled, Yes.write, CompareOp.lessOrEqual, No.boundsTest, 0f, 1f
570         );
571         info.blendInfo = ColorBlendInfo(
572             none!LogicOp, [ ColorBlendAttachment.solid() ], [ 0f, 0f, 0f, 0f ]
573         );
574         info.layout = meshLayout;
575         info.renderPass = meshRenderPass;
576         info.subpassIndex = 0;
577 
578         return info;
579     }
580 
581     void preparePipelines() {
582         auto infos = [
583             prepareShadowPipeline(), prepareMeshPipeline()
584         ];
585         auto pls = device.createPipelines(infos);
586         shadowPipeline = pls[0];
587         meshPipeline = pls[1];
588 
589         foreach (ref i; infos) {
590             i.shaders.vertex.release();
591             i.shaders.fragment.release();
592         }
593     }
594 
595     void updateBuffers(in FMat4 viewProj) {
596 
597         const axis = fvec(0, 0, 1);
598         foreach (ref m; meshes) {
599             const r = rotation(m.pulse, axis);
600             m.model *= r;
601         }
602 
603         const shadowVsLen = cast(uint)(ShadowVsLocals.sizeof * lights.length * meshes.length);
604         const meshVsLen = cast(uint)(MeshVsLocals.sizeof * meshes.length);
605 
606         import gfx.graal.device : MappedMemorySet;
607 
608         auto mm = meshUniformBuf.boundMemory.map();
609 
610         {
611             auto v = mm.view!(ShadowVsLocals[])(0, lights.length*meshes.length);
612             foreach (il, ref l; lights) {
613                 foreach (im, ref m; meshes) {
614                     const mat = ShadowVsLocals(transpose(l.proj * l.view * m.model));
615                     v[il*meshes.length + im] = mat;
616                 }
617             }
618         }
619         {
620             auto v = mm.view!(MeshVsLocals[])(shadowVsLen, meshes.length);
621             foreach (im, ref m; meshes) {
622                 v[im] = MeshVsLocals(
623                     transpose(viewProj * m.model),
624                     transpose(m.model),
625                 );
626             }
627         }
628         {
629             auto v = mm.view!(MeshFsMaterial[])(shadowVsLen+meshVsLen, meshes.length);
630             foreach (im, ref m; meshes) {
631                 v[im] = MeshFsMaterial( m.color );
632             }
633         }
634         MappedMemorySet mms;
635         mm.addToSet(mms);
636         device.flushMappedMemory(mms);
637     }
638 
639     override void render()
640     {
641         import core.time : dur;
642         import gfx.graal.queue : PresentRequest, StageWait, Submission;
643         import std.algorithm : map;
644         import std.array : array;
645 
646         bool needReconstruction;
647         const imgInd = swapchain.acquireNextImage(dur!"seconds"(-1), imageAvailableSem, needReconstruction);
648         const cmdBufInd = nextCmdBuf();
649 
650         // we have cmdbufs for each light that must be used on every frame
651         // therefore, we must sync with the same fence on every frame
652         fences[0].wait();
653         fences[0].reset();
654 
655         recordCmds(cmdBufInd, imgInd);
656 
657         auto shadowSubmission = Submission(
658             [],
659             [ shadowFinishedSem.obj ],
660             lights.map!((ref Light l) { return l.cmdBuf; }).array
661         );
662         auto meshSubmission = Submission(
663             [
664                 StageWait(shadowFinishedSem, PipelineStage.fragmentShader),
665                 StageWait(imageAvailableSem, PipelineStage.transfer)
666             ],
667             [ renderingFinishSem.obj ], [ cmdBufs[cmdBufInd] ]
668         );
669 
670         graphicsQueue.submit(
671             [
672                 shadowSubmission,
673                 meshSubmission
674             ],
675             fences[0]
676         );
677 
678         presentQueue.present(
679             [ renderingFinishSem.obj ],
680             [ PresentRequest(swapchain, imgInd) ]
681         );
682 
683         // if (needReconstruction) {
684         //     prepareSwapchain(swapchain);
685         //     presentPool.reset();
686         // }
687     }
688 
689 
690     override void recordCmds(ulong cmdBufInd, ulong imgInd)
691     {
692         void recordLight(uint il, ref Light l) {
693             auto buf = l.cmdBuf;
694             buf.begin(No.persistent);
695 
696             buf.beginRenderPass(
697                 shadowRenderPass, l.shadowFb, Rect(0, 0, shadowSize, shadowSize),
698                 [ ClearValues.depthStencil(1f, 0) ]
699             );
700 
701             buf.bindPipeline(shadowPipeline);
702             foreach (c, m; meshes) {
703                 buf.bindIndexBuffer(indBuf, m.indOffset, IndexType.u16);
704                 buf.bindVertexBuffers(0, [ VertexBinding(vertBuf, m.vertOffset) ]);
705                 buf.bindDescriptorSets(
706                     PipelineBindPoint.graphics, shadowLayout, 0, [ shadowDS ],
707                     [
708                         (il*meshes.length + c) * ShadowVsLocals.sizeof
709                     ]
710                 );
711                 buf.drawIndexed(m.numVertices, 1, 0, 0, 0);
712             }
713 
714             buf.endRenderPass();
715 
716             buf.end();
717         }
718 
719         void recordMeshes() {
720             auto buf = cmdBufs[cmdBufInd];
721             auto fb = framebuffers[imgInd];
722 
723             buf.begin(No.persistent);
724             buf.beginRenderPass(
725                 meshRenderPass, fb.meshFramebuffer, Rect(0, 0, surfaceSize[0], surfaceSize[1]),
726                 [
727                     ClearValues.color(0.6f, 0.6f, 0.6f, hasAlpha ? 0.5f : 1f),
728                     ClearValues.depthStencil(1f, 0)
729                 ]
730             );
731 
732             buf.bindPipeline(meshPipeline);
733             foreach (c, m; meshes) {
734                 buf.bindIndexBuffer(indBuf, m.indOffset, IndexType.u16);
735                 buf.bindVertexBuffers(0, [ VertexBinding(vertBuf, m.vertOffset) ]);
736                 buf.bindDescriptorSets(
737                     PipelineBindPoint.graphics, meshLayout, 0, [ meshDS ],
738                     [
739                         c * MeshVsLocals.sizeof, c * MeshFsMaterial.sizeof
740                     ]
741                 );
742                 buf.drawIndexed(m.numVertices, 1, 0, 0, 0);
743             }
744 
745             buf.endRenderPass();
746             buf.end();
747         }
748 
749         foreach (uint il, ref l; lights) {
750             recordLight(il, l);
751         }
752         recordMeshes();
753     }
754 
755 }
756 
757 int main(string[] args) {
758 
759     try {
760         auto example = new ShadowExample(args);
761         example.prepare();
762         scope(exit) example.dispose();
763 
764         example.window.onMouseOn = (uint, uint) {
765             example.window.closeFlag = true;
766         };
767 
768         const winSize = example.surfaceSize;
769         const proj = perspective(45, winSize[0]/(cast(float)winSize[1]), 1f, 20f);
770         const viewProj = proj * lookAt(fvec(3, -10, 6), fvec(0, 0, 0), fvec(0, 0, 1));
771 
772         FPSProbe fpsProbe;
773         fpsProbe.start();
774 
775         enum reportFreq = 60;
776 
777         while (!example.window.closeFlag) {
778 
779             example.updateBuffers(viewProj);
780             example.render();
781             example.display.pollAndDispatch();
782 
783             fpsProbe.tick();
784             if ((fpsProbe.frameCount % reportFreq) == 0) {
785                 writeln("FPS: ", fpsProbe.fps);
786             }
787         }
788 
789         return 0;
790     }
791     catch(Exception ex) {
792         stderr.writeln("error occured: ", ex.msg);
793         return 1;
794     }
795 }