Add BUILD_EXAMPLE_NAME option explain for README.md
[platform/core/uifw/dali-demo.git] / examples / deferred-shading / deferred-shading.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 #include <cstring>
18 #include <iostream>
19 #include <random>
20 #include "dali/dali.h"
21 #include "dali/public-api/actors/actor.h"
22 #include "dali/public-api/rendering/renderer.h"
23
24 #include "generated/deferred-shading-mainpass-frag.h"
25 #include "generated/deferred-shading-mainpass-vert.h"
26 #include "generated/deferred-shading-prepass-frag.h"
27 #include "generated/deferred-shading-prepass-vert.h"
28
29 using namespace Dali;
30
31 namespace
32 {
33 //=============================================================================
34 // Demonstrates deferred shading with multiple render targets (for color,
35 // position, and normal), a Phong lighting model and 32 point lights.
36 //
37 // Invoked with the --show-lights it will render a mesh at each light position.
38 //=============================================================================
39
40 #define QUOTE(x) DALI_COMPOSE_SHADER(x)
41
42 #define MAX_LIGHTS 32
43
44 #define DEFINE_MAX_LIGHTS "const int kMaxLights = " QUOTE(MAX_LIGHTS) ";"
45
46 //=============================================================================
47 // PRNG for floats.
48 struct FloatRand
49 {
50   std::random_device                    mDevice;
51   std::mt19937                          mMersenneTwister;
52   std::uniform_real_distribution<float> mDistribution;
53
54   FloatRand()
55   : mMersenneTwister(mDevice()),
56     mDistribution(0., 1.)
57   {
58   }
59
60   float operator()()
61   {
62     return mDistribution(mMersenneTwister);
63   }
64 };
65
66 //=============================================================================
67 float FastFloor(float x)
68 {
69   return static_cast<int>(x) - static_cast<int>(x < 0);
70 }
71
72 //=============================================================================
73 Vector3 FromHueSaturationLightness(Vector3 hsl)
74 {
75   Vector3 rgb;
76   if(hsl.y * hsl.y > 0.f)
77   {
78     if(hsl.x >= 360.f)
79     {
80       hsl.x -= 360.f;
81     }
82     hsl.x /= 60.f;
83
84     int   i  = FastFloor(hsl.x);
85     float ff = hsl.x - i;
86     float p  = hsl.z * (1.0 - hsl.y);
87     float q  = hsl.z * (1.0 - (hsl.y * ff));
88     float t  = hsl.z * (1.0 - (hsl.y * (1.f - ff)));
89
90     switch(i)
91     {
92       case 0:
93         rgb.r = hsl.z;
94         rgb.g = t;
95         rgb.b = p;
96         break;
97
98       case 1:
99         rgb.r = q;
100         rgb.g = hsl.z;
101         rgb.b = p;
102         break;
103
104       case 2:
105         rgb.r = p;
106         rgb.g = hsl.z;
107         rgb.b = t;
108         break;
109
110       case 3:
111         rgb.r = p;
112         rgb.g = q;
113         rgb.b = hsl.z;
114         break;
115
116       case 4:
117         rgb.r = t;
118         rgb.g = p;
119         rgb.b = hsl.z;
120         break;
121
122       case 5:
123       default:
124         rgb.r = hsl.z;
125         rgb.g = p;
126         rgb.b = q;
127         break;
128     }
129   }
130   else
131   {
132     rgb = Vector3::ONE * hsl.z;
133   }
134
135   return rgb;
136 }
137
138 //=============================================================================
139 Geometry CreateTexturedQuadGeometry(bool flipV)
140 {
141   // Create geometry -- unit square with whole of the texture mapped to it.
142   struct Vertex
143   {
144     Vector3 aPosition;
145     Vector2 aTexCoord;
146   };
147
148   Vertex vertexData[] = {
149     {Vector3(-.5f, .5f, .0f), Vector2(.0f, 1.0f)},
150     {Vector3(.5f, .5f, .0f), Vector2(1.0f, 1.0f)},
151     {Vector3(-.5f, -.5f, .0f), Vector2(.0f, .0f)},
152     {Vector3(.5f, -.5f, .0f), Vector2(1.0f, .0f)},
153   };
154
155   if(flipV)
156   {
157     std::swap(vertexData[0].aTexCoord, vertexData[2].aTexCoord);
158     std::swap(vertexData[1].aTexCoord, vertexData[3].aTexCoord);
159   }
160
161   VertexBuffer vertexBuffer = VertexBuffer::New(Property::Map()
162                                                   .Add("aPosition", Property::VECTOR3)
163                                                   .Add("aTexCoord", Property::VECTOR2));
164   vertexBuffer.SetData(vertexData, std::extent<decltype(vertexData)>::value);
165
166   Geometry geometry = Geometry::New();
167   geometry.AddVertexBuffer(vertexBuffer);
168   geometry.SetType(Geometry::TRIANGLE_STRIP);
169   return geometry;
170 }
171
172 //=============================================================================
173 Geometry CreateOctahedron(bool invertNormals)
174 {
175   Vector3 positions[] = {
176     Vector3{-1.f, 0.f, 0.f},
177     Vector3{1.f, 0.f, 0.f},
178     Vector3{0.f, -1.f, 0.f},
179     Vector3{0.f, 1.f, 0.f},
180     Vector3{0.f, 0.f, -1.f},
181     Vector3{0.f, 0.f, 1.f},
182   };
183
184   struct Vertex
185   {
186     Vector3 position{};
187     Vector3 normal{};
188   };
189   Vertex vertexData[] = {
190     {positions[0]},
191     {positions[3]},
192     {positions[5]},
193
194     {positions[5]},
195     {positions[3]},
196     {positions[1]},
197
198     {positions[1]},
199     {positions[3]},
200     {positions[4]},
201
202     {positions[4]},
203     {positions[3]},
204     {positions[0]},
205
206     {positions[0]},
207     {positions[5]},
208     {positions[2]},
209
210     {positions[5]},
211     {positions[1]},
212     {positions[2]},
213
214     {positions[1]},
215     {positions[4]},
216     {positions[2]},
217
218     {positions[4]},
219     {positions[0]},
220     {positions[2]},
221   };
222
223   // Calculate normals
224   for(uint32_t i = 0; i < std::extent<decltype(vertexData)>::value / 3; ++i)
225   {
226     uint32_t idx = i * 3;
227
228     Vector3 normal = (vertexData[idx + 2].position - vertexData[idx].position).Cross(vertexData[idx + 1].position - vertexData[idx].position);
229     normal.Normalize();
230     normal *= invertNormals * 2.f - 1.f;
231
232     vertexData[idx++].normal = normal;
233     vertexData[idx++].normal = normal;
234     vertexData[idx].normal   = normal;
235   }
236
237   // Configure property buffers and create geometry.
238   VertexBuffer vertexBuffer = VertexBuffer::New(Property::Map()
239                                                   .Add("aPosition", Property::VECTOR3)
240                                                   .Add("aNormal", Property::VECTOR3));
241   vertexBuffer.SetData(vertexData, std::extent<decltype(vertexData)>::value);
242
243   Geometry geometry = Geometry::New();
244   geometry.AddVertexBuffer(vertexBuffer);
245   geometry.SetType(Geometry::TRIANGLES);
246   return geometry;
247 }
248
249 //=============================================================================
250 enum RendererOptions
251 {
252   OPTION_NONE        = 0x0,
253   OPTION_BLEND       = 0x01,
254   OPTION_DEPTH_TEST  = 0x02,
255   OPTION_DEPTH_WRITE = 0x04
256 };
257
258 Renderer CreateRenderer(TextureSet textures, Geometry geometry, Shader shader, uint32_t options = OPTION_NONE)
259 {
260   Renderer renderer = Renderer::New(geometry, shader);
261   renderer.SetProperty(Renderer::Property::BLEND_MODE,
262                        (options & OPTION_BLEND) ? BlendMode::ON : BlendMode::OFF);
263   renderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE,
264                        (options & OPTION_DEPTH_TEST) ? DepthTestMode::ON : DepthTestMode::OFF);
265   renderer.SetProperty(Renderer::Property::DEPTH_WRITE_MODE,
266                        (options & OPTION_DEPTH_WRITE) ? DepthWriteMode::ON : DepthWriteMode::OFF);
267   renderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::BACK);
268
269   if(!textures)
270   {
271     textures = TextureSet::New();
272   }
273
274   renderer.SetTextures(textures);
275   return renderer;
276 }
277
278 //=============================================================================
279 void CenterActor(Actor actor)
280 {
281   actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
282   actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
283 }
284
285 //=============================================================================
286 void RegisterDepthProperties(float depth, float near, Handle& h)
287 {
288   h.RegisterProperty("uDepth_InvDepth_Near", Vector3(depth, 1.f / depth, near));
289 }
290
291 } // namespace
292
293 //=============================================================================
294 /// Create a String whose size can be evaluated at compile time
295 struct ConstantString
296 {
297   const char* const string;
298   const uint16_t    size;
299
300   template<uint16_t inputSize>
301   constexpr ConstantString(const char (&input)[inputSize])
302   : string(input),
303     size(inputSize)
304   {
305   }
306 };
307
308 constexpr ConstantString POSITION_STRING("position");
309 constexpr ConstantString RADIUS_STRING("radius");
310 constexpr ConstantString COLOR_STRING("color");
311 constexpr uint16_t       LIGHT_SOURCE_BUFFER_SIZE(128u);
312
313 //=============================================================================
314 class DeferredShadingExample : public ConnectionTracker
315 {
316 public:
317   struct Options
318   {
319     enum
320     {
321       NONE        = 0x0,
322       SHOW_LIGHTS = 0x1,
323     };
324   };
325
326   DeferredShadingExample(Application& app, uint32_t options = Options::NONE)
327   : mApp(app),
328     mOptions(options)
329   {
330     app.InitSignal().Connect(this, &DeferredShadingExample::Create);
331     app.TerminateSignal().Connect(this, &DeferredShadingExample::Destroy);
332   }
333
334 private:
335   void Create(Application& app)
336   {
337     // Grab window, configure layer
338     Window window    = app.GetWindow();
339     auto   rootLayer = window.GetRootLayer();
340     rootLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);
341
342     Vector2 windowSize = window.GetSize();
343
344     float unit = windowSize.y / 24.f;
345
346     // Get camera - we'll be re-using the same old camera in the two passes.
347     RenderTaskList tasks  = window.GetRenderTaskList();
348     CameraActor    camera = tasks.GetTask(0).GetCameraActor();
349
350     auto zCameraPos = camera.GetProperty(Actor::Property::POSITION_Z).Get<float>();
351     camera.SetFarClippingPlane(zCameraPos + windowSize.y * .5f);
352     camera.SetNearClippingPlane(zCameraPos - windowSize.y * .5f);
353
354     const float zNear = camera.GetNearClippingPlane();
355     const float zFar  = camera.GetFarClippingPlane();
356     const float depth = zFar - zNear;
357
358     // Create root of scene that shall be rendered off-screen.
359     auto sceneRoot = Actor::New();
360     CenterActor(sceneRoot);
361
362     mSceneRoot = sceneRoot;
363     window.Add(sceneRoot);
364
365     // Create an axis to spin our actors around.
366     auto axis = Actor::New();
367     CenterActor(axis);
368     sceneRoot.Add(axis);
369     mAxis = axis;
370
371     // Create an octahedral mesh for our main actors and to visualise the light sources.
372     Geometry mesh = CreateOctahedron(false);
373
374     // Create main actors
375     Shader     preShader        = Shader::New(SHADER_DEFERRED_SHADING_PREPASS_VERT, SHADER_DEFERRED_SHADING_PREPASS_FRAG);
376     TextureSet noTexturesThanks = TextureSet::New();
377     Renderer   meshRenderer     = CreateRenderer(noTexturesThanks, mesh, preShader, OPTION_DEPTH_TEST | OPTION_DEPTH_WRITE);
378     meshRenderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::BACK);
379     RegisterDepthProperties(depth, zNear, meshRenderer);
380     float c = 1.f;
381     for(auto v : {
382           Vector3{-c, -c, -c},
383           Vector3{c, -c, -c},
384           Vector3{-c, c, -c},
385           Vector3{c, c, -c},
386           Vector3{-c, -c, c},
387           Vector3{c, -c, c},
388           Vector3{-c, c, c},
389           Vector3{c, c, c},
390
391           Vector3{0.f, -c, -c},
392           Vector3{0.f, c, -c},
393           Vector3{0.f, -c, c},
394           Vector3{0.f, c, c},
395
396           Vector3{-c, 0.f, -c},
397           Vector3{c, 0.f, -c},
398           Vector3{-c, 0.f, c},
399           Vector3{c, 0.f, c},
400
401           Vector3{-c, -c, 0.f},
402           Vector3{c, -c, 0.f},
403           Vector3{-c, c, 0.f},
404           Vector3{c, c, 0.f},
405         })
406     {
407       Actor a = Actor::New();
408       CenterActor(a);
409
410       Vector3 position{v * unit * 5.f};
411       a.SetProperty(Actor::Property::POSITION, position);
412
413       float   scale = (c + ((v.x + v.y + v.z) + c * 3.f) * .5f) / (c * 4.f);
414       Vector3 size{Vector3::ONE * scale * unit * 2.f};
415       a.SetProperty(Actor::Property::SIZE, size);
416
417       a.SetProperty(Actor::Property::COLOR, Color::WHITE * .25f + (Color::RED * (v.x + c) / (c * 2.f) + Color::GREEN * (v.y + c) / (c * 2.f) + Color::BLUE * (v.z + c) / (c * 2.f)) * .015625f);
418       a.AddRenderer(meshRenderer);
419
420       axis.Add(a);
421     }
422
423     // Create off-screen textures, fbo and render task.
424     uint32_t width  = static_cast<uint32_t>(windowSize.x);
425     uint32_t height = static_cast<uint32_t>(windowSize.y);
426
427     Texture     rttNormal   = Texture::New(TextureType::TEXTURE_2D, Pixel::Format::RGB888, width, height);
428     Texture     rttPosition = Texture::New(TextureType::TEXTURE_2D, Pixel::Format::RGBA8888, width, height);
429     Texture     rttColor    = Texture::New(TextureType::TEXTURE_2D, Pixel::Format::RGB888, width, height);
430     FrameBuffer fbo         = FrameBuffer::New(width, height, FrameBuffer::Attachment::DEPTH);
431     fbo.AttachColorTexture(rttNormal);
432     fbo.AttachColorTexture(rttPosition);
433     fbo.AttachColorTexture(rttColor);
434
435     RenderTask sceneRender = tasks.CreateTask();
436     sceneRender.SetViewportSize(windowSize);
437     sceneRender.SetFrameBuffer(fbo);
438     sceneRender.SetCameraActor(camera);
439     sceneRender.SetSourceActor(sceneRoot);
440     sceneRender.SetInputEnabled(false);
441     sceneRender.SetCullMode(false);
442     sceneRender.SetClearEnabled(true);
443     sceneRender.SetClearColor(Color::BLACK);
444     sceneRender.SetExclusive(true);
445
446     mSceneRender = sceneRender;
447
448     // Create final image for deferred shading
449     auto finalImage = Actor::New();
450     CenterActor(finalImage);
451     finalImage.SetProperty(Actor::Property::SIZE, windowSize);
452
453     TextureSet finalImageTextures = TextureSet::New();
454     finalImageTextures.SetTexture(0, rttNormal);
455     finalImageTextures.SetTexture(1, rttPosition);
456     finalImageTextures.SetTexture(2, rttColor);
457
458     Sampler sampler = Sampler::New();
459     sampler.SetFilterMode(FilterMode::NEAREST, FilterMode::NEAREST);
460     finalImageTextures.SetSampler(0, sampler);
461     finalImageTextures.SetSampler(1, sampler);
462     finalImageTextures.SetSampler(2, sampler);
463
464     Shader   shdMain            = Shader::New(SHADER_DEFERRED_SHADING_MAINPASS_VERT, SHADER_DEFERRED_SHADING_MAINPASS_FRAG);
465     Geometry finalImageGeom     = CreateTexturedQuadGeometry(true);
466     Renderer finalImageRenderer = CreateRenderer(finalImageTextures, finalImageGeom, shdMain);
467     RegisterDepthProperties(depth, zNear, finalImageRenderer);
468
469     auto       propInvProjection  = finalImageRenderer.RegisterProperty("uInvProjection", Matrix::IDENTITY);
470     Constraint cnstrInvProjection = Constraint::New<Matrix>(finalImageRenderer, propInvProjection, [zCameraPos, zNear, depth](Matrix& output, const PropertyInputContainer& input) {
471       output = input[0]->GetMatrix();
472       DALI_ASSERT_ALWAYS(output.Invert() && "Failed to invert projection matrix.");
473     });
474     cnstrInvProjection.AddSource(Source(camera, CameraActor::Property::PROJECTION_MATRIX));
475     cnstrInvProjection.AddSource(Source(camera, CameraActor::Property::VIEW_MATRIX));
476     cnstrInvProjection.Apply();
477
478     finalImage.AddRenderer(finalImageRenderer);
479
480     mFinalImage = finalImage;
481     window.Add(finalImage);
482
483     // Create a node for our lights
484     auto lights = Actor::New();
485     CenterActor(lights);
486     sceneRoot.Add(lights);
487
488     // Create Lights
489     const bool showLights = mOptions & Options::SHOW_LIGHTS;
490     Renderer   lightRenderer;
491     if(showLights)
492     {
493       Geometry lightMesh = CreateOctahedron(true);
494       lightRenderer      = CreateRenderer(noTexturesThanks, lightMesh, preShader, OPTION_DEPTH_TEST | OPTION_DEPTH_WRITE);
495       lightRenderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::FRONT);
496     }
497
498     Vector3 lightPos{unit * 12.f, 0.f, 0.f};
499     float   theta    = M_PI * 2.f / MAX_LIGHTS;
500     float   cosTheta = std::cos(theta);
501     float   sinTheta = std::sin(theta);
502     for(int i = 0; i < MAX_LIGHTS; ++i)
503     {
504       Vector3 color = FromHueSaturationLightness(Vector3((360.f * i) / MAX_LIGHTS, .5f, 1.f));
505
506       Actor light = CreateLight(lightPos * (1 + (i % 8)) / 8.f, unit * 16.f, color, camera, finalImageRenderer);
507
508       float z  = (((i & 1) << 1) - 1) * unit * 8.f;
509       lightPos = Vector3(cosTheta * lightPos.x - sinTheta * lightPos.y, sinTheta * lightPos.x + cosTheta * lightPos.y, z);
510
511       if(showLights)
512       {
513         light.SetProperty(Actor::Property::SIZE, Vector3::ONE * unit / 8.f);
514         light.AddRenderer(lightRenderer);
515       }
516
517       lights.Add(light);
518     }
519
520     // Take them for a spin.
521     Animation animLights = Animation::New(40.f);
522     animLights.SetLooping(true);
523     animLights.AnimateBy(Property(lights, Actor::Property::ORIENTATION), Quaternion(Radian(M_PI * 2.f), Vector3::YAXIS));
524     animLights.Play();
525
526     // Event handling
527     window.KeyEventSignal().Connect(this, &DeferredShadingExample::OnKeyEvent);
528
529     mPanDetector = PanGestureDetector::New();
530     mPanDetector.DetectedSignal().Connect(this, &DeferredShadingExample::OnPan);
531     mPanDetector.Attach(window.GetRootLayer());
532   }
533
534   void Destroy(Application& app)
535   {
536     app.GetWindow().GetRenderTaskList().RemoveTask(mSceneRender);
537     mSceneRender.Reset();
538
539     UnparentAndReset(mSceneRoot);
540     UnparentAndReset(mFinalImage);
541   }
542
543   Actor CreateLight(Vector3 position, float radius, Vector3 color, CameraActor camera, Renderer renderer)
544   {
545     Actor light = Actor::New();
546     CenterActor(light);
547     light.SetProperty(Actor::Property::COLOR, Color::WHITE);
548     light.SetProperty(Actor::Property::POSITION, position);
549
550     auto iPropRadius     = light.RegisterProperty("radius", radius);
551     auto iPropLightColor = light.RegisterProperty("lightcolor", color);
552
553     // Create light source uniforms on lighting shader.
554     char  buffer[LIGHT_SOURCE_BUFFER_SIZE];
555     char* writep = buffer + snprintf(buffer, LIGHT_SOURCE_BUFFER_SIZE, "uLights[%d].", mNumLights);
556     ++mNumLights;
557
558     strncpy(writep, POSITION_STRING.string, POSITION_STRING.size);
559     auto oPropLightPos = renderer.RegisterProperty(buffer, position);
560
561     strncpy(writep, RADIUS_STRING.string, RADIUS_STRING.size);
562     auto oPropLightRadius = renderer.RegisterProperty(buffer, radius);
563
564     strncpy(writep, COLOR_STRING.string, COLOR_STRING.size);
565     auto oPropLightColor = renderer.RegisterProperty(buffer, color);
566
567     // Constrain the light position, radius and color to lighting shader uniforms.
568     // Convert light position to view space;
569     Constraint cLightPos = Constraint::New<Vector3>(renderer, oPropLightPos, [](Vector3& output, const PropertyInputContainer& input) {
570       Vector4 worldPos(input[0]->GetVector3());
571       worldPos.w = 1.f;
572
573       worldPos = input[1]->GetMatrix() * worldPos;
574       output   = Vector3(worldPos);
575     });
576     cLightPos.AddSource(Source(light, Actor::Property::WORLD_POSITION));
577     cLightPos.AddSource(Source(camera, CameraActor::Property::VIEW_MATRIX));
578     cLightPos.Apply();
579
580     Constraint cLightRadius = Constraint::New<float>(renderer, oPropLightRadius, EqualToConstraint());
581     cLightRadius.AddSource(Source(light, iPropRadius));
582     cLightRadius.Apply();
583
584     Constraint cLightColor = Constraint::New<Vector3>(renderer, oPropLightColor, EqualToConstraint());
585     cLightColor.AddSource(Source(light, iPropLightColor));
586     cLightColor.Apply();
587
588     return light;
589   }
590
591   void OnPan(Actor, PanGesture const& gesture)
592   {
593     Quaternion     q            = mAxis.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
594     const Vector2& displacement = gesture.GetScreenDisplacement();
595     Quaternion     qx(Radian(Degree(displacement.y) * -.5f), Vector3::XAXIS);
596     Quaternion     qy(Radian(Degree(displacement.x) * .5f), Vector3::YAXIS);
597     mAxis.SetProperty(Actor::Property::ORIENTATION, qy * qx * q);
598   }
599
600   void OnKeyEvent(const KeyEvent& event)
601   {
602     if(event.GetState() == KeyEvent::DOWN)
603     {
604       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
605       {
606         mApp.Quit();
607       }
608     }
609   }
610
611   Application& mApp;
612   uint32_t     mOptions;
613
614   Actor mSceneRoot;
615   Actor mAxis;
616
617   RenderTask mSceneRender;
618   Actor      mFinalImage;
619
620   int mNumLights = 0;
621
622   PanGestureDetector mPanDetector;
623 };
624
625 int DALI_EXPORT_API main(int argc, char** argv)
626 {
627   const bool showLights = [argc, argv]() {
628     auto endArgs = argv + argc;
629     return std::find_if(argv, endArgs, [](const char* arg) {
630              return strcmp(arg, "--show-lights") == 0;
631            }) != endArgs;
632   }();
633
634   Application            app = Application::New(&argc, &argv);
635   DeferredShadingExample example(app, (showLights ? DeferredShadingExample::Options::SHOW_LIGHTS : 0));
636   app.MainLoop();
637   return 0;
638 }