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