<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>KDAB</title><description>Global software consultancy specialising in Qt, C++, Rust, Slint and Embedded Linux. Consulting, development, training and open-source tools since 1999.</description><link>https://www.kdab.com/</link><language>en-gb</language><item><title>Simplifying 3D Stereo Visualization – an Automated Approach</title><link>https://www.kdab.com/simplifying-3d-stereo-visualization-an-automated-approach/</link><guid isPermaLink="true">https://www.kdab.com/simplifying-3d-stereo-visualization-an-automated-approach/</guid><description>&lt;p data-block-key=&quot;kgi1l&quot;&gt;KDAB and Schneider Digital developed a system that automates stereo 3D setup by dynamically calculating focal distance, camera separation, pop-out, and field of view. A focus-area method inspired by digital cameras continuously adjusts depth, enabling a plug-and-play experience without manual tuning.&lt;/p&gt;</description><pubDate>Thu, 22 May 2025 06:51:05 GMT</pubDate><content:encoded>&lt;h1&gt;Simplifying 3D Stereo Visualization – an Automated Approach&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;exqil&quot;&gt;As early as the 1980s, the first stereo 3D visualizations appeared on computers using shutter glasses and anaglyph glasses. The theoretical foundations of stereoscopy are largely established and considered solved. Nevertheless, there is still room for improvement when it comes to usability.&lt;/p&gt;&lt;p data-block-key=&quot;dmgkr&quot;&gt;Together with &lt;a href=&quot;https://www.schneider-digital.com/de/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Schneider Digital&lt;/a&gt;, a leading manufacturer of 3D stereo display systems, KDAB tackled the issue of usability by developing a demonstrator. In a second phase, we applied the findings and, with support from Bullinger GmbH as a sponsor, created a 3D stereo prototype for QGIS, an open-source geospatial software.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Blog_Schneider_Timo_Screenshot&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_Schneider_Timo_Screenshot.original.png&quot; class=&quot;Blog_Schneider_Timo_Screenshot&quot; alt=&quot;Blog_Schneider_Timo_Screenshot&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;dxcky&quot;&gt;Screenshot: Schneider / KDAB 3D-Stereo-Demonstrator OpenGL / Vulkan&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;static-configurations-in-3d-visualization&quot; anchor=&quot;static-configurations-in-3d-visualization&quot; data-block-key=&quot;exqil&quot;&gt;&lt;b&gt;Static configurations in 3D visualization&lt;/b&gt;&lt;/h2&gt;&lt;p data-block-key=&quot;5qt4p&quot;&gt;When considering static configurations - such as the playback of a 3D movie in a cinema - all parameters are predefined. The screen size is approximately the same for all viewers. Virtual conditions such as the virtual camera distance, the focal distance to the object, and the “pop-out” effect are either fixed or implicitly embedded in the media. Viewers can simply put on their 3D glasses and enjoy the experience without any prior setup.&lt;/p&gt;&lt;h2 id=&quot;challenges-of-dynamic-real-time-3d-scenes&quot; anchor=&quot;challenges-of-dynamic-real-time-3d-scenes&quot; data-block-key=&quot;216ii&quot;&gt;&lt;b&gt;Challenges of dynamic real-time 3D scenes&lt;/b&gt;&lt;/h2&gt;&lt;p data-block-key=&quot;av2br&quot;&gt;The situation is different with dynamic, real-time 3D scenes. Display sizes vary, and the virtual scale of the scenes can differ significantly. This introduces several parameters that must be configured in advance to ensure a comfortable 3D stereo experience:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;7vljd&quot;&gt;&lt;b&gt;Camera separation&lt;/b&gt;: The virtual distance between the two cameras responsible for rendering the left and right eye perspectives.&lt;/li&gt;&lt;li data-block-key=&quot;5t7t3&quot;&gt;&lt;b&gt;Focal distance&lt;/b&gt;: The virtual distance from the camera to the object of focus. At this point, the two camera frustums intersect, and the images for both eyes align on the screen plane.&lt;/li&gt;&lt;li data-block-key=&quot;9u557&quot;&gt;&lt;b&gt;Pop-out&lt;/b&gt;: Determines how much a 3D object appears to protrude from the screen (positive values) or recede into it (negative values).&lt;/li&gt;&lt;li data-block-key=&quot;b7m9k&quot;&gt;&lt;b&gt;FOV (Field of View)&lt;/b&gt;: The vertical field of view of the virtual camera.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Blog_Schneider_Timo_3D-PluraView-Application-Terrasolid-Terrastereo-6&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_Schneider_Timo_3D-PluraView-Application-Te.original.jpg&quot; class=&quot;Blog_Schneider_Timo_3D-PluraView-Application-Terrasolid-Terrastereo-6&quot; alt=&quot;Blog_Schneider_Timo_3D-PluraView-Application-Terrasolid-Terrastereo-6&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;dxcky&quot;&gt;3D PluraView&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Blog_Schneider_Timo_3D-PluraView-Application-esri-ArcGIS-1&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_Schneider_Timo_3D-PluraView-Application-es.original.jpg&quot; class=&quot;Blog_Schneider_Timo_3D-PluraView-Application-esri-ArcGIS-1&quot; alt=&quot;Blog_Schneider_Timo_3D-PluraView-Application-esri-ArcGIS-1&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;dxcky&quot;&gt;3D PluraView&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;automating-parameter-configuration-for-user-comfort&quot; anchor=&quot;automating-parameter-configuration-for-user-comfort&quot; data-block-key=&quot;exqil&quot;&gt;&lt;b&gt;Automating parameter configuration for user comfort&lt;/b&gt;&lt;/h2&gt;&lt;p data-block-key=&quot;aa58t&quot;&gt;The goal is to define all these parameters automatically, based on virtual and physical conditions, so that the user ideally doesn’t need to adjust anything manually.&lt;/p&gt;&lt;p data-block-key=&quot;b2e29&quot;&gt;The field of view can be determined relatively easily: While it&apos;s generally a free parameter set according to the task (e.g., CAD, games, etc.) or personal preference, we can compute a good starting value from the real-world viewing angle, which depends on the display size and physical distance from the viewer.&lt;/p&gt;&lt;p data-block-key=&quot;2ihke&quot;&gt;Automatically determining the focal distance is more challenging. The trivial options are: 1) The user adjusts a slider to manually set the focal distance, or 2) the user clicks in the 3D scene to set a focal point. While the second method is relatively convenient, both require user interaction. We’ve developed a third approach inspired by digital cameras. Most allow users to define a &quot;focus area“, and the camera adjusts its optics to focus within this region. Similarly in our case, we cast several rays within a defined focus area into the 3D scene and calculate the optimal focal distance using median or average depth values. This focus field is adjustable in size and position, initially centered in the image, and typically works out of the box. The focal distance is then updated each frame, allowing continuous adaptation. Within the focus field, incorrect focus is effectively eliminated.&lt;/p&gt;&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Blog_Schneider_Timo_3D-PluraView-Case-Study-Hamburg-01&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_Schneider_Timo_3D-PluraView-Case-Study-Ham.original.jpg&quot; class=&quot;Blog_Schneider_Timo_3D-PluraView-Case-Study-Hamburg-01&quot; alt=&quot;Blog_Schneider_Timo_3D-PluraView-Case-Study-Hamburg-01&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;dxcky&quot;&gt;3D PluraView&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Blog_Schneider_Timo_3D-PluraView-Case-Study-Hamburg-07&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_Schneider_Timo_3D-PluraView-Case-Study-Ham.original_cY7c0zs.jpg&quot; class=&quot;Blog_Schneider_Timo_3D-PluraView-Case-Study-Hamburg-07&quot; alt=&quot;Blog_Schneider_Timo_3D-PluraView-Case-Study-Hamburg-07&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;dxcky&quot;&gt;3D PluraView&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;fine-tuning-the-3d-experience&quot; anchor=&quot;fine-tuning-the-3d-experience&quot; data-block-key=&quot;exqil&quot;&gt;&lt;b&gt;Fine-tuning the 3D experience&lt;/b&gt;&lt;/h2&gt;&lt;p data-block-key=&quot;ceb03&quot;&gt;Once the focal distance is determined, objects closer to the viewer appear in front of the screen (popping out), and those farther away appear inside it. Depending on taste or application, this impression can be fine-tuned by shifting the pop-out effect. Technically, this can be done by adjusting the focal distance but requiring the user to manually do this whenever focal distance changes would be inconvenient. Therefore, we introduced a dedicated pop-out parameter in our demonstrator, which indirectly adjusts the focal distance each frame “under the hood” without requiring any user input.&lt;/p&gt;&lt;h2 id=&quot;defining-camera-separation-for-optimal-viewing&quot; anchor=&quot;defining-camera-separation-for-optimal-viewing&quot; data-block-key=&quot;8dl7g&quot;&gt;&lt;b&gt;Defining camera separation for optimal viewing&lt;/b&gt;&lt;/h2&gt;&lt;p data-block-key=&quot;c2h4t&quot;&gt;The final piece is &lt;b&gt;camera separation&lt;/b&gt;, which may initially seem counterintuitive. Aren’t human eyes all roughly the same distance apart? Yes and no. 3D graphics coordinates are not bound to real-world units - a &quot;unit&quot; might represent a millimeter, a meter, or even a kilometer. So what value should be used for camera separation? A common solution, also used in our demonstrator, is to define a base value (e. g., 1/30) and multiply it by the focal distance to derive the camera separation. While this isn’t physically accurate - human eye distance doesn’t change (at least that would be disconcerting) - it works well in practice for stereo 3D rendering.&lt;/p&gt;&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Blog_Schneider_Timo_IMG_0492&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_Schneider_Timo_IMG_0492.original.png&quot; class=&quot;Blog_Schneider_Timo_IMG_0492&quot; alt=&quot;Blog_Schneider_Timo_IMG_0492&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;dxcky&quot;&gt;VR Wall&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Blog_Schneider_Timo_IMG_1371&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_Schneider_Timo_IMG_1371.original.png&quot; class=&quot;Blog_Schneider_Timo_IMG_1371&quot; alt=&quot;Blog_Schneider_Timo_IMG_1371&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;dxcky&quot;&gt;VR Wall&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;conclusion-an-immersive-3d-stereo-experience&quot; anchor=&quot;conclusion-an-immersive-3d-stereo-experience&quot; data-block-key=&quot;exqil&quot;&gt;&lt;b&gt;Conclusion: an immersive 3D stereo experience&lt;/b&gt;&lt;/h2&gt;&lt;p data-block-key=&quot;85mqs&quot;&gt;With these techniques, we’ve managed to define all four parameters in a way that allows users to immediately immerse themselves in a 3D stereo experience without needing to configure settings beforehand or during runtime.&lt;/p&gt;&lt;p data-block-key=&quot;bvggq&quot;&gt;The stereo demonstrator runs on both OpenGL and Vulkan. The source code for the OpenGL / Qt3D version is freely available at: &lt;a href=&quot;https://github.com/KDABLabs/stereo3ddemo&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/KDABLabs/stereo3ddemo&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/simplifying-3d-stereo-visualization-an-automated-approach/&quot;&gt;Simplifying 3D Stereo Visualization – an Automated Approach&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>3d</category><category>c++</category><category>desktop</category><category>open source</category><category>showcase</category><category>ux/ui</category></item><item><title>KDGpu 0.5.0 is here!</title><link>https://www.kdab.com/kdgpu-0-5-0-is-here/</link><guid isPermaLink="true">https://www.kdab.com/kdgpu-0-5-0-is-here/</guid><description>&lt;p&gt;Since we first announced it last year, our Vulkan wrapper KDGpu has been busy evolving to meet customer needs and our own. Our last post announced the public release of v0.1.0, and version 0.5.0 is available today. It&amp;#x27;s never been easier to interact with modern graphics technologies, enabling you to focus on the big picture […]&lt;/p&gt;</description><pubDate>Thu, 30 May 2024 06:30:29 GMT</pubDate><content:encoded>&lt;h1&gt;KDGpu 0.5.0 is here!&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;Since &lt;a href=&quot;https://www.kdab.com/kdgpu-v-0-1-0-is-released/&quot;&gt;we first announced it&lt;/a&gt; last year, our Vulkan wrapper KDGpu has been busy evolving to meet customer needs and our own. Our last post announced the public release of v0.1.0, and version 0.5.0 is available today. It&apos;s never been easier to interact with modern graphics technologies, enabling you to focus on the big picture instead of hassling with the intricacies and nuances of Vulkan.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Screenshot_19_124838.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Screenshot_19_124838.original.png&quot; class=&quot;Screenshot_19_124838.png&quot; alt=&quot;Screenshot_19_124838.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;The PBR example in the new KDGpu Examples repository.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;wider-device-support&quot; anchor=&quot;wider-device-support&quot;&gt;Wider device support&lt;/h2&gt;&lt;p&gt;KDGpu now supports a wider array of devices, such as older versions of Android. For some context, additional features in Vulkan are supported by extensions. If said features become part of the &quot;core&quot; specification, they are automatically included in Vulkan 1.2, 1.3 and so on. In the past, KDGpu required the device to fully support Vulkan 1.2, which limited what devices you could target. In newer KDGpu versions (&amp;gt;0.4.6) it will now run on certain 1.1 devices (like the Meta Quest) as long as the required extensions are supported.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;IMG_3932.jpg&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/IMG_3932.original.jpg&quot; class=&quot;IMG_3932.jpg&quot; alt=&quot;IMG_3932.jpg&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;A KDGpu example running natively on an Android device.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;We also added native examples for Android, which can be ran straight from &lt;a href=&quot;https://developer.android.com/studio&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Android Studio&lt;/a&gt;! There&apos;s also better iOS support alongside &lt;a href=&quot;https://github.com/KDAB/KDGpu/tree/main/examples/hello_triangle_apple&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;a native Apple example&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-20 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;76FC8DE4-425A-4E8B-AA24-A62E2F3B31EF_4_5005_c.jpeg&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/76FC8DE4-425A-4E8B-AA24-A62E2F3B31EF_4_5005_c.original.jpg&quot; class=&quot;76FC8DE4-425A-4E8B-AA24-A62E2F3B31EF_4_5005_c.jpeg&quot; alt=&quot;76FC8DE4-425A-4E8B-AA24-A62E2F3B31EF_4_5005_c.jpeg&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;the KDGpu Hello Triangle example running in the iOS simulator&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;external-memory-and-images-support&quot; anchor=&quot;external-memory-and-images-support&quot;&gt;External memory and images support&lt;/h2&gt;&lt;p&gt;When writing applications using KDGpu, you will inevitably have to interface with other APIs or libraries that don&apos;t support it or maybe not even Vulkan specifically. For example, if you generate an image using Vulkan graphics and then need to pass that to &lt;a href=&quot;https://developer.nvidia.com/cuda-zone&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;CUDA&lt;/a&gt; for further processing. Now with KDGpu it&apos;s possible to grab texture and buffer objects and get their external memory handles:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;const TextureOptions textureOptions = {
    .type = TextureType::TextureType2D,
    .format = Format::R8G8B8A8_SNORM,
    .extent = { 512, 512, 1 },
    .mipLevels = 1,
    .usage = TextureUsageFlagBits::SampledBit,
    .memoryUsage = MemoryUsage::GpuOnly,
    .externalMemoryHandleType = ExternalMemoryHandleTypeFlagBits::OpaqueFD,
};

Texture t = device.createTexture(textureOptions);
const MemoryHandle externalHandleOrFD = t.externalMemoryHandle();
&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;Additionally, we have added methods to adopt existing VkImages as native KDGpu objects to better support libraries like OpenXR.&lt;/p&gt;&lt;h2 id=&quot;easy-fast-xr&quot; anchor=&quot;easy-fast-xr&quot;&gt;Easy &amp;amp; fast XR&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://www.khronos.org/OpenXR/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;OpenXR&lt;/a&gt; is the leading API used for writing cross-platform VR/AR experiences. Like Vulkan, code directly using OpenXR tends to be verbose and requires a lot of setup. To alleviate this, KDGpu now includes an optional library called KDXr. It wraps OpenXR, and it even easily integrates into KDGpu. It takes care of initialization, has the C++ classes you expect and can make it painless to integrate XR functionality into your application including support for XR compositor layers, head tracking, input handling and haptic feedback.&lt;/p&gt;&lt;p&gt;For example, to set up a projection view you subclass the ProjectionLayer type:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;class ProjectionLayer : public XrProjectionLayer
{
public:&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;And implement the required methods like renderView() to start rendering into each eye:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;void ProjectionLayer::renderView()
{
    m_fence.wait();
    m_fence.reset();

    // Update the scene data once per frame
    if (m_currentViewIndex == 0) {
        updateTransformUbo();
    }

    // Update the per-view camera matrices
    updateViewUbo();

    auto commandRecorder = m_device-&amp;gt;createCommandRecorder();

    // Set up the render pass using the current color and depth texture views
    m_opaquePassOptions.colorAttachments[0].view = m_colorSwapchains[m_currentViewIndex].textureViews[m_currentColorImageIndex];
    m_opaquePassOptions.depthStencilAttachment.view = m_depthSwapchains[m_currentViewIndex].textureViews[m_currentDepthImageIndex];
    auto opaquePass = commandRecorder.beginRenderPass(m_opaquePassOptions);

   // Do the rest of your rendering commands to this pass...&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;And add this layer to the compositor, in our examples this is abstracted away for you:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;// Create a projection layer to render the 3D scene
const XrProjectionLayerOptions projectionLayerOptions = {
    .device = &amp;amp;m_device,
    .queue = &amp;amp;m_queue,
    .session = &amp;amp;m_session,
    .colorSwapchainFormat = m_colorSwapchainFormat,
    .depthSwapchainFormat = m_depthSwapchainFormat,
    .samples = m_samples.get()
};
m_projectionLayer = createCompositorLayer&amp;lt;ProjectionLayer&amp;gt;(projectionLayerOptions);
m_projectionLayer-&amp;gt;setReferenceSpace(m_referenceSpace);&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;You can &lt;a href=&quot;https://github.com/KDAB/KDGpu/tree/main/examples/hello_xr&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;view the complete example here&lt;/a&gt;. In this new release, we&apos;re continuing to work on multiview support! KDXr supports multiview out of the box (see the example layer code) and you can check out the &lt;a href=&quot;https://github.com/KDAB/KDGpu/tree/main/examples/multiview&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;multiview example&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;more-in-depth-examples-are-now-available&quot; anchor=&quot;more-in-depth-examples-are-now-available&quot;&gt;More in-depth examples are now available&lt;/h2&gt;&lt;p&gt;The examples sitting in our main repository are no more than small tests, which don&apos;t show the true benefits of using KDGpu in large graphical applications. So, in addition to our previous examples, we now have &lt;a href=&quot;https://github.com/KDAB/kdgpu-examples&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;a dedicated KDGpu Examples repository&lt;/a&gt;!&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-75 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Screenshot_19_012418.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Screenshot_19_012418.original.png&quot; class=&quot;Screenshot_19_012418.png&quot; alt=&quot;Screenshot_19_012418.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p&gt;Screenshot from our N-Body Compute example.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;and-more&quot; anchor=&quot;and-more&quot;&gt;And more!&lt;/h2&gt;&lt;p&gt;There are also small improvements such as being able to request custom extensions and ignore specific validation layer warnings. Check out the &lt;a href=&quot;https://github.com/KDAB/KDGpu/blob/main/ChangeLog&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;changelog on GitHub&lt;/a&gt; for a full list of what&apos;s been changed.&lt;/p&gt;&lt;p&gt;Let us know what you think about the improvements we&apos;ve made, and what could be useful for you in the future!&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/kdgpu-0-5-0-is-here/&quot;&gt;KDGpu 0.5.0 is here!&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>android</category><category>3d</category><category>c++</category><category>tools</category></item><item><title>Projection Matrices with Vulkan - Part 2</title><link>https://www.kdab.com/projection-matrices-with-vulkan-part-2/</link><guid isPermaLink="true">https://www.kdab.com/projection-matrices-with-vulkan-part-2/</guid><description>&lt;p data-block-key=&quot;3e5n5&quot;&gt;Recap Recall that in Part 1 we discussed the differences between OpenGL and Vulkan when it comes to the fixed function parts of the graphics pipeline. We looked at how OpenGL&amp;#x27;s use of a left-handed set of coordinate axes for clip-space meant that projection matrices for OpenGL also incorporate a z-axis flip to switch from […]&lt;/p&gt;</description><pubDate>Thu, 21 Dec 2023 09:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Projection Matrices with Vulkan - Part 2&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;recap&quot; anchor=&quot;recap&quot; data-block-key=&quot;kz97f&quot;&gt;Recap&lt;/h2&gt;&lt;p data-block-key=&quot;9ixj4&quot;&gt;Recall that in &lt;a href=&quot;https://www.kdab.com/projection-matrices-with-vulkan-part-1/&quot;&gt;Part 1&lt;/a&gt; we discussed the differences between OpenGL and Vulkan when it comes to the fixed function parts of the graphics pipeline. We looked at how OpenGL&amp;#x27;s use of a left-handed set of coordinate axes for clip-space meant that projection matrices for OpenGL also incorporate a z-axis flip to switch from a right-handed eye space to a left-handed clip space.&lt;/p&gt;&lt;p data-block-key=&quot;tined&quot;&gt;We then went on to explain how we can apply a post-view correction matrix that performs a rotation of 180 degrees about the eye-space x-axis which will reorient the eye space axes such that they are aligned with the Vulkan clip space axes.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;view-to-clip-correction.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/view-to-clip-correction.original.png&quot; class=&quot;view-to-clip-correction.png&quot; alt=&quot;view-to-clip-correction.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-end&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;ayx6b&quot;&gt;Rotating the eye space coordinate axes to align them with the Vulkan clip space axes as a step prior to applying the projection matrix.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;5xpz9&quot;&gt;In this article we shall derive a perspective projection matrix, that transforms a vertex from the rotated eye space into the Vulkan clip space. Thanks to the fact that we have already taken care of aligning the source and destination space axes, all we have to care about is the projection itself. There is no need to introduce any axis inversions or other sleights of hand. We hope that this article when coupled with Part 1 will give you a full understanding of your transformations and allow you to make modifications without adding special cases. Let&amp;#x27;s get cracking!&lt;/p&gt;&lt;h2 id=&quot;defining-the-problem&quot; anchor=&quot;defining-the-problem&quot; data-block-key=&quot;00mg6&quot;&gt;Defining the Problem&lt;/h2&gt;&lt;p data-block-key=&quot;9d2iq&quot;&gt;We will look at deriving the perspective projection matrix for a view volume, defined by 6 planes forming a frustum (rectangular truncated pyramid). Let&amp;#x27;s assume that the camera is located at the origin&lt;/p&gt;&lt;div data-katex-embed=&quot;O&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;eaa0h&quot;&gt;in our &amp;quot;rotated eye space&amp;quot; and looking along the &lt;b&gt;positive&lt;/b&gt; z-axis. From here on in we will just refer to this &amp;quot;rotated eye space&amp;quot; as &amp;quot;eye space&amp;quot; for brevity and we will use the subscript &amp;quot;eye&amp;quot; for quantities in this space.&lt;/p&gt;&lt;p data-block-key=&quot;30hep&quot;&gt;The following two diagrams show the view volume from top-down and side-elevation views. You may want to middle-click on them to get the full images in separate browser tabs so that you can refer back to them.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;perspective-projection-asymmetric-top-down.width-1200&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/perspective-projection-asymmetric-top-down.widt.original.png&quot; class=&quot;perspective-projection-asymmetric-top-down.width-1200&quot; alt=&quot;perspective-projection-asymmetric-top-down.width-1200&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-end&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;perspective-projection-asymmetric-side-ele.max-3000x3000&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/perspective-projection-asymmetric-side-ele.max-.original.png&quot; class=&quot;perspective-projection-asymmetric-side-ele.max-3000x3000&quot; alt=&quot;perspective-projection-asymmetric-side-ele.max-3000x3000&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-end&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;4kmt3&quot;&gt;The planes forming the frustum are defined by:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;ch4fr&quot;&gt;&lt;b&gt;Near plane&lt;/b&gt; is defined by&lt;/li&gt;&lt;/ul&gt;&lt;div data-katex-embed=&quot;z_{eye} = n&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;dj74g&quot;&gt;. This is the plane that we will project the vertices on to. Think of it as the window on to the virtual world through which we will look.&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;3exux&quot;&gt;&lt;b&gt;Far plane&lt;/b&gt; is defined by&lt;/li&gt;&lt;/ul&gt;&lt;div data-katex-embed=&quot;z_{eye} = f&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;cj83c&quot;&gt;. This defines the maximum distance to which we can see. Anything beyond this will be clipped to the far plane.&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;n18k9&quot;&gt;&lt;b&gt;Left and right planes&lt;/b&gt; are defined by specifying the x-coordinate of the near plane&lt;/li&gt;&lt;/ul&gt;&lt;div data-katex-embed=&quot;x_{eye} = l&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;2jn77&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;x_{eye} = r&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7phcf&quot;&gt;, then projecting those back to the origin&lt;/p&gt;&lt;div data-katex-embed=&quot;O&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;drmgr&quot;&gt;. Note that&lt;/p&gt;&lt;div data-katex-embed=&quot;r &amp;gt; l&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;51a29&quot;&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;nnjo5&quot;&gt;&lt;b&gt;Top and bottom planes&lt;/b&gt; are defined by specifying the y-coordinate of the near plane&lt;/li&gt;&lt;/ul&gt;&lt;div data-katex-embed=&quot;y_{eye} = t&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;836kn&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;y_{eye} = b&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;4sgu6&quot;&gt;, then projecting those back to the origin&lt;/p&gt;&lt;div data-katex-embed=&quot;O&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;alqmk&quot;&gt;. Note that&lt;/p&gt;&lt;div data-katex-embed=&quot;b&amp;gt;t&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;3gacr&quot;&gt;which is in the opposite sense that you may be used to. This is because we rotated our eye space coordinate system so that y increases downwards.&lt;/p&gt;&lt;p data-block-key=&quot;91g0c&quot;&gt;Within the view volume, we define a point&lt;/p&gt;&lt;div data-katex-embed=&quot;\bm{p}_{eye} = (x_e, y_e, z_e)^T&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;ff0i6&quot;&gt;representing a vertex that we wish to transform into clip space. If we trace a ray back from&lt;/p&gt;&lt;div data-katex-embed=&quot;\bm{p}_{eye}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;lmbq&quot;&gt;to the origin, then we label the point where the ray crosses the near plane as&lt;/p&gt;&lt;div data-katex-embed=&quot;\bm{p}_{proj} = (x_p, y_p, z_p)^T&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7g39h&quot;&gt;. Note that&lt;/p&gt;&lt;div data-katex-embed=&quot;\bm{p}_{proj}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;dp0jk&quot;&gt;is still in eye space coordinates.&lt;/p&gt;&lt;p data-block-key=&quot;m4otu&quot;&gt;We know that clip space uses 4 dimensional homogeneous coordinates. We shall call the resulting point in clip-space&lt;/p&gt;&lt;div data-katex-embed=&quot;\bm{p}_{clip} = (x_c, y_c, z_c, w_c)^T&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7dta4&quot;&gt;. Our job then is to find a 4x4 projection matrix,&lt;/p&gt;&lt;div data-katex-embed=&quot;P&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;b9b52&quot;&gt;such that:&lt;/p&gt;&lt;p data-block-key=&quot;dw727&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;78ud7&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot;\bm{p}_{clip} = P \bm{p}_{eye}
\qquad \rm{or} \qquad
\begin{pmatrix}
x_c \\ y_c \\ z_c \\ w_c
\end{pmatrix}
=
P \begin{pmatrix}
x_e \\ y_e \\ z_e \\ 1
\end{pmatrix}
\qquad (\dagger)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;5tj9r&quot;&gt;&lt;/p&gt;&lt;h2 id=&quot;deriving-the-perspective-projection-matrix&quot; anchor=&quot;deriving-the-perspective-projection-matrix&quot; data-block-key=&quot;j52mu&quot;&gt;Deriving the Perspective Projection Matrix&lt;/h2&gt;&lt;p data-block-key=&quot;qsus2&quot;&gt;Clip space is an intermediate coordinate system used by Vulkan and the other graphics APIs to perform clipping of geometry. Once that is complete, the clip space homogenous coordinates are projected back to Cartesian space by dividing all components by the 4th component,&lt;/p&gt;&lt;div data-katex-embed=&quot;w_c&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;6k0b7&quot;&gt;. In order to allow for perspective-correct interpolation of per-vertex attributes to happen, the 4th component must be equal to the eye space depth or&lt;/p&gt;&lt;div data-katex-embed=&quot;w_c = z_e&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;c8t5a&quot;&gt;. This normalisation process then yields the vertex position in normalised device coordinates (NDC) as:&lt;/p&gt;&lt;p data-block-key=&quot;c1gje&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;4tmv9&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \bm{p}_{ndc} = \begin{pmatrix}
 x_n \\ y_n \\ z_n
 \end{pmatrix} = \begin{pmatrix}
 x_c / z_e \\
 y_c / z_e \\
 z_c / z_e \\
 \end{pmatrix}
 \qquad (\ast)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7s938&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;1ausg&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;tbfpe&quot;&gt;Since we always want&lt;/p&gt;&lt;div data-katex-embed=&quot;w_c = z_e&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7slkk&quot;&gt;, this means that the final row of&lt;/p&gt;&lt;div data-katex-embed=&quot;P&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;67c9s&quot;&gt;will be&lt;/p&gt;&lt;div data-katex-embed=&quot;(0, 0, 1, 0)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;48iun&quot;&gt;. Notice that because our z-axis is aligned with the clip-space z-axis there is no negation required here.&lt;/p&gt;&lt;p data-block-key=&quot;ygt0d&quot;&gt;So, at this stage we know that the projection matrix looks like this:&lt;/p&gt;&lt;p data-block-key=&quot;zlj8j&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5tt05&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;mf9u&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; P = \begin{pmatrix}
 \cdot &amp;amp; \cdot &amp;amp; \cdot &amp;amp; \cdot \\
 \cdot &amp;amp; \cdot &amp;amp; \cdot &amp;amp; \cdot \\
 \cdot &amp;amp; \cdot &amp;amp; \cdot &amp;amp; \cdot \\
 0 &amp;amp; 0 &amp;amp; 1 &amp;amp; 0 \\
 \end{pmatrix}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;78tua&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;a4ov1&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;3m69s&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;okkiy&quot;&gt;Let&amp;#x27;s carry on and fill in the blanks.&lt;/p&gt;&lt;h3 id=&quot;projection-of-the-x-coordinate&quot; anchor=&quot;projection-of-the-x-coordinate&quot; data-block-key=&quot;c6p22&quot;&gt;Projection of the x-coordinate&lt;/h3&gt;&lt;p data-block-key=&quot;z1b7b&quot;&gt;Looking back at Figure 1, we can see by the properties of similar triangles that:&lt;/p&gt;&lt;p data-block-key=&quot;xiut0&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;6uo26&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \frac{x_p}{z_p} = \frac{x_e}{z_e} \implies \frac{x_p}{n} = \frac{x_e}{z_e}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;6daa6&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;37pge&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;el07o&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;quodp&quot;&gt;since on the near plane&lt;/p&gt;&lt;div data-katex-embed=&quot;z_p = n&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;e0p6r&quot;&gt;. Rearranging this very slightly we get:&lt;/p&gt;&lt;p data-block-key=&quot;3lv0q&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;2jq7l&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; x_p = \frac{n x_e}{z_e} \qquad (i)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;e2o2q&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;dsr12&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;egcnu&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;gddy7&quot;&gt;Let us now consider how the projected vertex positions map through to normalised device coordinates. In Vulkan&amp;#x27;s NDC, the view volume becomes a cuboid where&lt;/p&gt;&lt;div data-katex-embed=&quot;-1 \leq x_n \leq 1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;6id4g&quot;&gt;,&lt;/p&gt;&lt;div data-katex-embed=&quot;-1 \leq y_n \leq 1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;2ee4l&quot;&gt;, and&lt;/p&gt;&lt;div data-katex-embed=&quot;0 \leq z_n \leq 1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;5rlvi&quot;&gt;. We want the x component of&lt;/p&gt;&lt;div data-katex-embed=&quot;\bm{p}_{ndc}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;1vgbt&quot;&gt;to vary linearly with the x component of the projected point,&lt;/p&gt;&lt;div data-katex-embed=&quot;x_p&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;fmhrn&quot;&gt;. If it was not a linear relationship then objects would appear to be distorted across the screen or to move with apparently varying velocities.&lt;/p&gt;&lt;p data-block-key=&quot;xdy9h&quot;&gt;We know that the extremities of the view volume in the x direction are defined by&lt;/p&gt;&lt;div data-katex-embed=&quot;x_p = l&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;3rchp&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;x_p = r&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7q509&quot;&gt;. These map to -1 and +1 in normalised device coordinates respectively. We can therefore say that at&lt;/p&gt;&lt;div data-katex-embed=&quot;x_p = l, x_n = -1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;2m1t1&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;x_p = r, x_n = 1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;e1dd4&quot;&gt;. Using this information we can plot the following graph for&lt;/p&gt;&lt;div data-katex-embed=&quot;x_n = m x_p + c&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;2m3th&quot;&gt;:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;ndc-x-1.max-3000x3000&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/ndc-x-1.max-3000x3000.original.png&quot; class=&quot;ndc-x-1.max-3000x3000&quot; alt=&quot;ndc-x-1.max-3000x3000&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-end&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;67auh&quot;&gt;That&amp;#x27;s right, more of your high school maths is going to be used to find the gradient and intercept of this equation!&lt;/p&gt;&lt;p data-block-key=&quot;ynobm&quot;&gt;The gradient,&lt;/p&gt;&lt;div data-katex-embed=&quot;m&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;2r3nc&quot;&gt;is given by:&lt;/p&gt;&lt;p data-block-key=&quot;crc0b&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;fmtn1&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; m = \frac{\Delta y}{\Delta x} = \frac{1 - (-1)}{r - l} = \frac{2}{r - l}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;dt8l2&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;bf3b5&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;6rh0g&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;lyfzh&quot;&gt;Substituting the gradient back in we get a simple equation to solve to find the intercept,&lt;/p&gt;&lt;div data-katex-embed=&quot;c&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;6ft4f&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;4jojk&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;am8bo&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; x_n = \frac{2 x_p}{r - l} + c&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;9dd3g&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;a77o4&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;eci1c&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;9csu1&quot;&gt;substituting in&lt;/p&gt;&lt;div data-katex-embed=&quot;x_n = 1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;35f8n&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;x_p = r&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;8acih&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;v7enu&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;2dr3a&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; 1 = \frac{2 r}{r - l} + c \implies c = 1 - \frac{2 r}{r - l} \implies c = \frac{r - l - 2r}{r - l} \implies c = - \frac{r + l}{r - l}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;ej50h&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;ecaqh&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;22bq3&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5wimr&quot;&gt;We then get the following expression for&lt;/p&gt;&lt;div data-katex-embed=&quot;x_n&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;b6pg1&quot;&gt;as a function of&lt;/p&gt;&lt;div data-katex-embed=&quot;x_p&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;fn50&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;tkx61&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;u41t&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; x_n = \frac{2 x_p}{r - l} - \frac{r + l}{r - l} \qquad (ii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;4mhi8&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;2481j&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;6rrlt&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;qngu5&quot;&gt;Substituting in for&lt;/p&gt;&lt;div data-katex-embed=&quot;x_p&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;eaas6&quot;&gt;from equation&lt;/p&gt;&lt;div data-katex-embed=&quot;(i)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;69pad&quot;&gt;into equation&lt;/p&gt;&lt;div data-katex-embed=&quot;(ii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;5du5v&quot;&gt;and factorising gives:&lt;/p&gt;&lt;p data-block-key=&quot;afkuu&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;4c9kb&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; x_n = \frac{2 n x_e}{(r - l) z_e} - \frac{r + l}{r - l}

&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;a0b2a&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies x_n = \frac{2 n x_e}{(r - l) z_e} - \frac{r + l}{r - l} \frac{z_e}{z_e}
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7k063&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies x_n = \frac{1}{z_e} \left( \left( \frac{2n}{r - l} \right) x_e - \frac{r + l}{r - l} z_e \right)
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;81cn9&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies x_n z_e = \left( \frac{2n}{r - l} \right) x_e - \frac{r + l}{r - l} z_e&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;ri5k&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;8a8pt&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;tr5lv&quot;&gt;Recall from the first component of&lt;/p&gt;&lt;div data-katex-embed=&quot;(\ast)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;9vocp&quot;&gt;that&lt;/p&gt;&lt;div data-katex-embed=&quot;x_n z_e = x_c&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;itt0&quot;&gt;. Substituting this in for the left-hand side of the previous equation gives:&lt;/p&gt;&lt;p data-block-key=&quot;bbo90&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;1d5dw&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; x_c = \left( \frac{2n}{r - l} \right) x_e - \frac{r + l}{r - l} z_e&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;e90lq&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;35a2b&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5kt4a&quot;&gt;which is now directly comparable to the equation for the 1st component of&lt;/p&gt;&lt;div data-katex-embed=&quot;(\dagger)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;6saef&quot;&gt;and comparing coefficients allows us to immediately read off the first row of the projection matrix as&lt;/p&gt;&lt;div data-katex-embed=&quot;(\frac{2n}{r - l}, 0, -\frac{r + l}{r - l}, 0)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;bgm78&quot;&gt;. This also makes intuitive sense looking back at Figure 1 as the x component of the clip space point should only depend upon the x and z components of the eye space position (the eye space y component does not affect it).&lt;/p&gt;&lt;p data-block-key=&quot;xcavu&quot;&gt;As it stands here, the projection matrix looks like this:&lt;/p&gt;&lt;p data-block-key=&quot;8uld9&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;1k1nr&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; P = \begin{pmatrix}
 \frac{2n}{r - l} &amp;amp; 0 &amp;amp; -\frac{r + l}{r - l} &amp;amp; 0 \\
 \cdot &amp;amp; \cdot &amp;amp; \cdot &amp;amp; \cdot \\
 \cdot &amp;amp; \cdot &amp;amp; \cdot &amp;amp; \cdot \\
 0 &amp;amp; 0 &amp;amp; 1 &amp;amp; 0 \\
 \end{pmatrix}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;70bn0&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;bri4c&quot;&gt;&lt;/p&gt;&lt;h3 id=&quot;projection-of-the-y-coordinate&quot; anchor=&quot;projection-of-the-y-coordinate&quot; data-block-key=&quot;jp7fu&quot;&gt;Projection of the y-coordinate&lt;/h3&gt;&lt;p data-block-key=&quot;kodgv&quot;&gt;The good news, is that the analysis in the y direction is exactly analogous to what we just did for the x direction. Without further ado, from Figure 2 and by the properties of similar triangles and since on the near plane&lt;/p&gt;&lt;div data-katex-embed=&quot;z_p = n&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;s3o9&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;5xw5b&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;em4fv&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \frac{y_p}{z_p} = \frac{y_e}{z_e} \implies \frac{y_p}{n} = \frac{y_e}{z_e}.&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;fj9ac&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5duuj&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;7473j&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;82nav&quot;&gt;Which then gives:&lt;/p&gt;&lt;p data-block-key=&quot;mbg6z&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;7i3n4&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; y_p = \frac{n y_e}{z_e} \qquad (iii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;42req&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5a9b0&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;8djot&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;72738&quot;&gt;We know that the extremities of the view volume in the y direction are defined by&lt;/p&gt;&lt;div data-katex-embed=&quot;y_p = t&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;bm60n&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;y_p = b&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;6uc9g&quot;&gt;. These map to -1 and +1 in normalised device coordinates respectively. We can therefore say that at&lt;/p&gt;&lt;div data-katex-embed=&quot;y_p = t, y_n = -1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;e85me&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;y_p = b, y_n = 1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;329qa&quot;&gt;. Using this information we can plot the following graph for&lt;/p&gt;&lt;div data-katex-embed=&quot;y_n = m y_p + c&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;d8441&quot;&gt;.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;ndc-y.max-3000x3000&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/ndc-y.max-3000x3000.original.png&quot; class=&quot;ndc-y.max-3000x3000&quot; alt=&quot;ndc-y.max-3000x3000&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-end&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;y35hs&quot;&gt;As before, we have a linear equation to find the gradient and intercept of. The gradient,&lt;/p&gt;&lt;div data-katex-embed=&quot;m&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;94ih1&quot;&gt;is given by:&lt;/p&gt;&lt;p data-block-key=&quot;7e4cm&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;deiv1&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; m = \frac{\Delta y}{\Delta x} = \frac{1 - (-1)}{b - t} = \frac{2}{b - t}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;bte08&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;cl34u&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;9259h&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;dfjxt&quot;&gt;Substituting the gradient back in we get a simple equation to solve to find the intercept,&lt;/p&gt;&lt;div data-katex-embed=&quot;c&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;2al3g&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;93mpk&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;cvrll&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; y_n = \frac{2 y_p}{b - t} + c&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7oabl&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;1ell4&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;bhv1p&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;6fb65&quot;&gt;substituting in&lt;/p&gt;&lt;div data-katex-embed=&quot;y_n = 1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;3l6ir&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;y_p = b&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;5vlr3&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;bwoe8&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;99doo&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; 1 = \frac{2 b}{b - t} + c \implies c = 1 - \frac{2 b}{b - t} \implies c = \frac{b - t - 2b}{b - t} \implies c = - \frac{b + t}{b - t}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;8qeo&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;drtep&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;cpe31&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;jjv51&quot;&gt;We then get the following expression for&lt;/p&gt;&lt;div data-katex-embed=&quot;y_n&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;ensm4&quot;&gt;as a function of&lt;/p&gt;&lt;div data-katex-embed=&quot;y_p&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;b69dl&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;ghaoa&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;96gsb&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; y_n = \frac{2 y_p}{b - t} - \frac{b + t}{b - t} \qquad (iv)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;brd9h&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;fepij&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;ka5l&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;dc2em&quot;&gt;Substituting in for&lt;/p&gt;&lt;div data-katex-embed=&quot;y_p&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;870a3&quot;&gt;from equation&lt;/p&gt;&lt;div data-katex-embed=&quot;(iii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;dd17o&quot;&gt;into equation&lt;/p&gt;&lt;div data-katex-embed=&quot;(iv)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;t6nk&quot;&gt;and factorising gives:&lt;/p&gt;&lt;p data-block-key=&quot;j5ogz&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;feg7q&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; y_n = \frac{2 n y_e}{(b - t) z_e} - \frac{b + t}{b - t}
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;droof&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies y_n = \frac{2 n y_e}{(b - t) z_e} - \frac{b + t}{b - t} \frac{z_e}{z_e}
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;bh988&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies y_n = \frac{1}{z_e} \left( \left( \frac{2n}{b - t} \right) y_e - \frac{b + t}{b - t} z_e \right)
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7s5u6&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies y_n z_e = \left( \frac{2n}{b - t} \right) y_e - \frac{b + t}{b - t} z_e&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;819r1&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;30sfv&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;dth7a&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;uv2cm&quot;&gt;Recall from the second component of&lt;/p&gt;&lt;div data-katex-embed=&quot;(\ast)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;f9i62&quot;&gt;that&lt;/p&gt;&lt;div data-katex-embed=&quot;y_n z_e = y_c&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;d3i55&quot;&gt;. Substituting this in for the left-hand side of the previous equation gives:&lt;/p&gt;&lt;p data-block-key=&quot;tbe9m&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;7milq&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;bah1l&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; y_c = \left( \frac{2n}{b - t} \right) y_e - \frac{b + t}{b - t} z_e&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;3c0h7&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;1v3n1&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;e740f&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;yvsjn&quot;&gt;This time, comparing to the second component of&lt;/p&gt;&lt;div data-katex-embed=&quot;(\dagger)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;8ms00&quot;&gt;we can read off the coefficients for the second row of the projection matrix as&lt;/p&gt;&lt;div data-katex-embed=&quot;(0, \frac{2n}{b - t}, -\frac{b + t}{b - t}, 0)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;3c5s0&quot;&gt;. Once again a quick intuitive check against Figure 2 matches what we have found. The projected and clip space y coordinates do not depend upon the x component of the eye space position.&lt;/p&gt;&lt;p data-block-key=&quot;dewk5&quot;&gt;At the three quarters stage, the projection matrix is now:&lt;/p&gt;&lt;p data-block-key=&quot;oaokw&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;7auhv&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; P = \begin{pmatrix}
 \frac{2n}{r - l} &amp;amp; 0 &amp;amp; -\frac{r + l}{r - l} &amp;amp; 0 \\
 0 &amp;amp; \frac{2n}{b-t} &amp;amp; -\frac{b + t}{b - t} &amp;amp; 0 \\
 \cdot &amp;amp; \cdot &amp;amp; \cdot &amp;amp; \cdot \\
 0 &amp;amp; 0 &amp;amp; 1 &amp;amp; 0 \\
 \end{pmatrix}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;78fr1&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;dir3g&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;cfcmu&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;p2aol&quot;&gt;We are almost there now. We have just the z-axis mapping left to deal with.&lt;/p&gt;&lt;h3 id=&quot;mapping-the-z-coordinate&quot; anchor=&quot;mapping-the-z-coordinate&quot; data-block-key=&quot;w2xlj&quot;&gt;Mapping the z-coordinate&lt;/h3&gt;&lt;p data-block-key=&quot;knndh&quot;&gt;The analysis of the z-axis is a little different to that of the x and y dimensions. For Vulkan, we wish to map eye space depths such that:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;35wjn&quot;&gt;the near plane,&lt;/li&gt;&lt;/ul&gt;&lt;div data-katex-embed=&quot;z_e = n&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;eglb7&quot;&gt;, maps to&lt;/p&gt;&lt;div data-katex-embed=&quot;z_n = 0&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;9g3a4&quot;&gt;and&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;600gx&quot;&gt;the far plane,&lt;/li&gt;&lt;/ul&gt;&lt;div data-katex-embed=&quot;z_e = f&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;d2364&quot;&gt;, maps to&lt;/p&gt;&lt;div data-katex-embed=&quot;z_n = 1&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;9ge8n&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;f2vxw&quot;&gt;The z components of the projected point and the normalised device coordinates point should not depend upon the x and y components. This means that for the 3rd row of the projection matrix the first two elements will be 0. The remaining two elements we will denote by&lt;/p&gt;&lt;div data-katex-embed=&quot;A&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;1laoc&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;B&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;d68bs&quot;&gt;respectively:&lt;/p&gt;&lt;p data-block-key=&quot;536hi&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;fm4d9&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; P = \begin{pmatrix}
 \frac{2n}{r - l} &amp;amp; 0 &amp;amp; -\frac{r + l}{r - l} &amp;amp; 0 \\
 0 &amp;amp; \frac{2n}{b-t} &amp;amp; -\frac{b + t}{b - t} &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; A &amp;amp; B \\
 0 &amp;amp; 0 &amp;amp; 1 &amp;amp; 0 \\
 \end{pmatrix}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;7o93n&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;us3d&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;12ac2&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;ka22o&quot;&gt;Combining this with the 3rd row of&lt;/p&gt;&lt;div data-katex-embed=&quot;(\dagger)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;cstdl&quot;&gt;we see that:&lt;/p&gt;&lt;p data-block-key=&quot;u2x8k&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;bnvdo&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; z_c = A z_e + B&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;52co5&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;9iiut&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;4olmm&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;pap1a&quot;&gt;Now if we divide both sides by&lt;/p&gt;&lt;div data-katex-embed=&quot;z_e&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;brtgg&quot;&gt;and recalling that from&lt;/p&gt;&lt;div data-katex-embed=&quot;(\ast)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;39jn&quot;&gt;that&lt;/p&gt;&lt;div data-katex-embed=&quot;z_n = z_c / z_e&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;46i4b&quot;&gt;we can write:&lt;/p&gt;&lt;p data-block-key=&quot;i5yht&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;ci2nr&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; z_n = A + \frac{B}{z_e}. \qquad (v)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;2u2td&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;ehmb9&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;fic0r&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;a91ms&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;c672o&quot;&gt;Substituting in our boundary conditions (shown in the bullet points above) into equation&lt;/p&gt;&lt;div data-katex-embed=&quot;(v)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;cfa34&quot;&gt;we get a pair of simultaneous equations for&lt;/p&gt;&lt;div data-katex-embed=&quot;A&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;fgbeq&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;B&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;4s7ih&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;tnojf&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;a08f0&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; A + \frac{B}{n} = 0 \qquad (vi)
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;93brj&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; A + \frac{B}{f} = 1 \qquad (vii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;4qgei&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;9cfmj&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5qa5a&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;4b67r&quot;&gt;We can subtract equation&lt;/p&gt;&lt;div data-katex-embed=&quot;(vi)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;asjsh&quot;&gt;from equation&lt;/p&gt;&lt;div data-katex-embed=&quot;(vii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;38rru&quot;&gt;to eliminate&lt;/p&gt;&lt;div data-katex-embed=&quot;A&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;5m1rp&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;gvweo&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;6l029&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \frac{B}{f} - \frac{B}{n} = 1 \implies \frac{Bn - Bf}{nf} = 1 \implies \frac{B(n - f)}{nf} = 1 \implies B = \frac{nf}{n - f}
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;cqo9k&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies B = - \frac{nf}{f - n} \qquad (viii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;9a081&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;bv14t&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;3cnoi&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;y6gyv&quot;&gt;Now to find&lt;/p&gt;&lt;div data-katex-embed=&quot;A&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;f4uvp&quot;&gt;we can substitute&lt;/p&gt;&lt;div data-katex-embed=&quot;(viii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;9b3dn&quot;&gt;back into&lt;/p&gt;&lt;div data-katex-embed=&quot;(vii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;usst&quot;&gt;:&lt;/p&gt;&lt;p data-block-key=&quot;d204x&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;d8t3t&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; A - \frac{n f}{f(f - n)} = 1
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;5q9v7&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies A - \frac{n}{f - n} = 1
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;f0ilf&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies A = 1 + \frac{n}{f - n}
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;c3bo3&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies A = \frac{f - n + n}{f - n}
&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;3ns5v&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies A = \frac{f}{f - n} \qquad (ix)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;6jldm&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5vol8&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;fqoi1&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;qxlay&quot;&gt;Substituting equations&lt;/p&gt;&lt;div data-katex-embed=&quot;(viii)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;31m7&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;(ix)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;d5umj&quot;&gt;back into the projection matrix, we finally arrive at the result for a perspective projection matrix useable with Vulkan in conjunction with the post-view rotation matrix from Part 1:&lt;/p&gt;&lt;p data-block-key=&quot;85m27&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;56vg1&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; P = \begin{pmatrix}
 \frac{2n}{r - l} &amp;amp; 0 &amp;amp; -\frac{r + l}{r - l} &amp;amp; 0 \\
 0 &amp;amp; \frac{2n}{b-t} &amp;amp; -\frac{b + t}{b - t} &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; \frac{f}{f - n} &amp;amp; - \frac{n f}{f - n} \\
 0 &amp;amp; 0 &amp;amp; 1 &amp;amp; 0 \\
 \end{pmatrix} \qquad (x)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;al9ne&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;mr4h&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;flq09&quot;&gt;&lt;/p&gt;&lt;h2 id=&quot;using-the-projection-matrix-in-practice&quot; anchor=&quot;using-the-projection-matrix-in-practice&quot; data-block-key=&quot;vjrnl&quot;&gt;Using the Projection Matrix in Practice&lt;/h2&gt;&lt;p data-block-key=&quot;p9fs0&quot;&gt;Recall that equation&lt;/p&gt;&lt;div data-katex-embed=&quot;(x)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;8fs01&quot;&gt;is the matrix to perform the projection operation from the &lt;b&gt;rotated eye space&lt;/b&gt; coordinates to the right-handed &lt;b&gt;clip space&lt;/b&gt; coordinates used by Vulkan. What does this mean? Well, it means that we should include the post-view correction matrix into our calculations when transforming vertices. Given a vertex position in model space,&lt;/p&gt;&lt;div data-katex-embed=&quot;\bm{p}_{model}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;dj0a6&quot;&gt;, we can transform it into clip space by the following:&lt;/p&gt;&lt;p data-block-key=&quot;1dvje&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;1n179&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \bm{p}_{clip} = P X V M \bm{p}_{model}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;9s8e7&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;fvn1n&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;4s5m0&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;9qwpu&quot;&gt;As we saw in Part 1, the post-view correction matrix is just a constant that performs the 180 degree rotation about the x-axis, we can combine this into our calculation of the projection matrix,&lt;/p&gt;&lt;div data-katex-embed=&quot;P&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;720is&quot;&gt;. This is analogous to how the OpenGL projection matrix typically includes the z-axis flip to change from a right-handed to left-handed coordinate system. Combining the post-view rotation and Vulkan projection matrix gives:&lt;/p&gt;&lt;p data-block-key=&quot;6g2ru&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;a8101&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; Q = P X&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;e125h&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies Q =
 \begin{pmatrix}
 \frac{2n}{r - l} &amp;amp; 0 &amp;amp; -\frac{r + l}{r - l} &amp;amp; 0 \\
 0 &amp;amp; \frac{2n}{b-t} &amp;amp; -\frac{b + t}{b - t} &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; \frac{f}{f - n} &amp;amp; - \frac{n f}{f - n} \\
 0 &amp;amp; 0 &amp;amp; 1 &amp;amp; 0 \\
 \end{pmatrix}
 \begin{pmatrix}
 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
 0 &amp;amp; -1 &amp;amp; 0 &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; -1 &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 1 \\
 \end{pmatrix} \\&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;5fvgb&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; \implies Q =
 \begin{pmatrix}
 \frac{2n}{r - l} &amp;amp; 0 &amp;amp; \frac{r + l}{r - l} &amp;amp; 0 \\
 0 &amp;amp; -\frac{2n}{b-t} &amp;amp; \frac{b + t}{b - t} &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; -\frac{f}{f - n} &amp;amp; - \frac{n f}{f - n} \\
 0 &amp;amp; 0 &amp;amp; -1 &amp;amp; 0 \\
 \end{pmatrix} \qquad (xi) \\&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;9lv1e&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;2eq5a&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;pq326&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;bfv84&quot;&gt;&lt;b&gt;Edit: Fixed the signs of the 1st and 2nd rows of the 3rd column in the matrix above (copy and paste error). Thanks to FourierTransform and Ziflin in the comments for pointing this out!&lt;/b&gt;&lt;/p&gt;&lt;p data-block-key=&quot;37a7e&quot;&gt;Before you rush off and implement equation&lt;/p&gt;&lt;div data-katex-embed=&quot;(xi)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;dhu90&quot;&gt;in your favourite editor and language, there is one final piece of subtlety to consider! Recall that when we began deriving the perspective projection matrix, we set things up so that our source coordinate system was the rotated eye space so that its axes were already aligned with the clip space destination coordinate system. Refer back to Figures 1 and 2 and note the orientation of the axes. In particular that the y axis increases in a downward direction.&lt;/p&gt;&lt;p data-block-key=&quot;j8ajd&quot;&gt;The thing to keep in mind is that the parameters used in&lt;/p&gt;&lt;div data-katex-embed=&quot;(xi)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;c77fr&quot;&gt;are actually specified in the rotated eye space coordinate system. This has implications:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;t62sn&quot;&gt;&lt;b&gt;x axis:&lt;/b&gt; Nothing to change here. Since we rotate about the x-axis to get from eye space to rotated eye space, the x component of any position does not change.&lt;/li&gt;&lt;li data-block-key=&quot;1nc2t&quot;&gt;&lt;b&gt;y axis:&lt;/b&gt; The 180 degree rotation about the x axis will affect the y components of any positions. The following diagram shows a blue view volume in the non-rotated eye space - the z-axis increases to the left and the near plane is positioned on the negative z side. The view volume is in the upper right quadrant and in this case both the top and bottom values for the near plane are positive. In the lower left quadrant, in green, we also show the rotated view volume. Notice that the 180 degree rotation causes the &lt;b&gt;signs of the&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div data-katex-embed=&quot;t&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;degva&quot;&gt;&lt;b&gt;and&lt;/b&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot;b&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;5qsir&quot;&gt;&lt;b&gt;parameters to be negated&lt;/b&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;83vid&quot;&gt;&lt;b&gt;z axis:&lt;/b&gt; Technically, the 180 degree rotation would also negate the z components of any positions. However, developers are already used to specifying the near and far plane parameters,&lt;/li&gt;&lt;/ul&gt;&lt;div data-katex-embed=&quot;n&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;fuon0&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;f&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;18iug&quot;&gt;, as distances from the&lt;/p&gt;&lt;div data-katex-embed=&quot;z_e = 0&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;62fdi&quot;&gt;plane. This is exactly what happens when creating an OpenGL projection matrix for example. Since we already specified&lt;/p&gt;&lt;div data-katex-embed=&quot;n&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;cir2l&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;f&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;odd9&quot;&gt;as positive values in the rotated eye space, we can just treat the inputs to any function that we write as positive distances for the near and far plane and stay in keeping with what developers are used to.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;rotated-eye-space-view-volume.max-3000x3000&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/rotated-eye-space-view-volume.max-3000x3000.original.png&quot; class=&quot;rotated-eye-space-view-volume.max-3000x3000&quot; alt=&quot;rotated-eye-space-view-volume.max-3000x3000&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-end&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;niv9j&quot;&gt;Putting this together, we can create a function to produce a Vulkan projection matrix and optionally have it incorporate the post-view correction rotation matrix. All we have to remember is that if we are opting in to include the post-view correction, then the top and bottom parameters are treated as being specified in the non-rotated eye space. If we do not opt in, then they are specified in rotated eye space.&lt;/p&gt;&lt;p data-block-key=&quot;tx34v&quot;&gt;In practise, this works well because often you want to minimise the amount of floating point arithmetic going on per frame so opting in allows the developer to specify top and bottom in the usual eye space coordinates which is closer to the chosen world space system (often y-up too), than the rotated eye space.&lt;/p&gt;&lt;p data-block-key=&quot;mlwns&quot;&gt;Using the popular glm library, we can declare a function as:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;enum class ApplyPostViewCorrection : uint8_t {
    No,
    Yes
};

struct AsymmetricPerspectiveOptions {
    float left{ -1.0f };
    float right{ 1.0f };
    float bottom{ -1.0f };
    float top{ 1.0f };
    float nearPlane{ 0.1f };
    float farPlane{ 100.0f };
    ApplyPostViewCorrection applyPostViewCorrection{ ApplyPostViewCorrection::Yes };
};

glm::mat4 perspective(const AsymmetricPerspectiveOptions &amp;amp;options);&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;ne9qx&quot;&gt;The implementation turns out to be very easy once we know equations&lt;/p&gt;&lt;div data-katex-embed=&quot;(x)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;8bt83&quot;&gt;and&lt;/p&gt;&lt;div data-katex-embed=&quot;(xi)&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;51q0k&quot;&gt;:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;glm::mat4 perspective(const AsymmetricPerspectiveOptions &amp;amp;options)
{
    const auto twoNear = 2.0f * options.nearPlane;
    const auto rightMinusLeft = options.right - options.left;
    const auto farMinusNear = options.farPlane - options.nearPlane;

    if (options.applyPostViewCorrection == ApplyPostViewCorrection::No) {
        const auto bottomMinusTop = options.bottom - options.top;

        const glm::mat4 m = {
            twoNear / rightMinusLeft,
            0.0f,
            0.0f,
            0.0f,

            0.0f,
            twoNear / bottomMinusTop,
            0.0f,
            0.0f,

            -(options.right + options.left) / rightMinusLeft,
            -(options.bottom + options.top) / bottomMinusTop,
            options.farPlane / farMinusNear,
            1.0f,

            0.0f,
            0.0f,
            -options.nearPlane * options.farPlane / farMinusNear,
            0.0f
        };

        return m;
    } else {
        // If we are applying the post view correction, we need to negate the signs of the
        // top and bottom planes to take into account the fact that the post view correction
        // rotate them 180 degrees around the x axis.
        //
        // This has the effect of treating the top and bottom planes as if they were specified
        // in the non-rotated eye space coordinate system.
        //
        // We do not need to flip the signs of the near and far planes as these are always
        // treated as positive distances from the camera.
        const auto bottom = -options.bottom;
        const auto top = -options.top;
        const auto bottomMinusTop = bottom - top;

        // In addition to negating the top and bottom planes, we also need to post-multiply
        // the projection matrix by the post view correction matrix. This amounts to negating
        // the y and z axes of the projection matrix.
        const glm::mat4 m = {
            twoNear / rightMinusLeft,
            0.0f,
            0.0f,
            0.0f,

            0.0f,
            -twoNear / (bottomMinusTop),
            0.0f,
            0.0f,

            (options.right + options.left) / rightMinusLeft,
            (bottom + top) / bottomMinusTop,
            -options.farPlane / farMinusNear,
            -1.0f,

            0.0f,
            0.0f,
            -options.nearPlane * options.farPlane / farMinusNear,
            0.0f
        };

        return m;
    }
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;summary&quot; anchor=&quot;summary&quot; data-block-key=&quot;bjzrs&quot;&gt;Summary&lt;/h2&gt;&lt;p data-block-key=&quot;124px&quot;&gt;In this article we have shown how to build a perspective projection matrix to transform vertices from rotated eye space to clip space all from first principles. The requirement for perspective correct interpolation and the perspective divide yielded the 4th row of the the projection matrix. We have then shown how we can construct a linear relationship between the x or y components of the eye space projected point on the near plane to the normalised device coordinate point, and from there back to clip space. We then showed how to map the eye space depth component onto the normalised device coordinate depth. Finally we have given some practical tips about combining the projection matrix with the post-view rotation matrix.&lt;/p&gt;&lt;p data-block-key=&quot;ecl4z&quot;&gt;We hope that this has removed some of the mystery surrounding the perspective projection matrix and how using an OpenGL projection matrix can cause your rendered results to be upside down. Armed with this knowledge you will have no need for the various hacks mentioned earlier.&lt;/p&gt;&lt;p data-block-key=&quot;vph80&quot;&gt;In the next article, we will take a look at some more variations on the projection matrix and some more tips for using it in applications. Thank you for reading!&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/projection-matrices-with-vulkan-part-2/&quot;&gt;Projection Matrices with Vulkan - Part 2&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sean Harmer</dc:creator><category>3d</category><category>performance</category></item><item><title>Projection Matrices with Vulkan - Part 1</title><link>https://www.kdab.com/projection-matrices-with-vulkan-part-1/</link><guid isPermaLink="true">https://www.kdab.com/projection-matrices-with-vulkan-part-1/</guid><description>&lt;p data-block-key=&quot;7w8ak&quot;&gt;Introduction When someone with an OpenGL background begins using Vulkan, one of the very common outcomes - beyond the initial one of &amp;quot;OMG how much code does it take to draw a triangle?&amp;quot; - is that the resulting image is upside down. Searching the web for this will give many hits on discussions about coordinate […]&lt;/p&gt;</description><pubDate>Thu, 23 Nov 2023 08:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Projection Matrices with Vulkan - Part 1&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;introduction&quot; anchor=&quot;introduction&quot; data-block-key=&quot;t1jdp&quot;&gt;Introduction&lt;/h2&gt;&lt;p data-block-key=&quot;az8bt&quot;&gt;When someone with an OpenGL background begins using Vulkan, one of the very common outcomes - beyond the initial one of &amp;quot;OMG how much code does it take to draw a triangle?&amp;quot; - is that the resulting image is upside down.&lt;/p&gt;&lt;p data-block-key=&quot;r1u59&quot;&gt;Searching the web for this will give many hits on discussions about coordinate systems being flipped with suggested solutions being to do things like:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;zw5a8&quot;&gt;Invert all of your &lt;code&gt;gl_Position.y&lt;/code&gt; coordinates in all of your vertex shaders.&lt;/li&gt;&lt;li data-block-key=&quot;mhed9&quot;&gt;Provide a negative height viewport to flip the viewport transformation applied by Vulkan.&lt;/li&gt;&lt;li data-block-key=&quot;g24jp&quot;&gt;Perform some magic incantation on your transformation matrices such as negating the y-axis.&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;93v3c&quot;&gt;All of these approaches have downsides such as needing to touch all of your vertex shaders; using hardware where negative viewport heights are supported; not really understanding the implications of randomly flipping an axis in a transformation matrix; having to invert your geometry winding order.&lt;/p&gt;&lt;p data-block-key=&quot;kh1f2&quot;&gt;This post will aim to explain what is different between OpenGL and Vulkan transformations and how we can adapt our code to get the desired results with the bonus of actually understanding what is going on. This final point is crucial when it comes time to make changes later so that you don&amp;#x27;t end up in the common situation of randomly flipping axes until you get what you want but which probably breaks something else.&lt;/p&gt;&lt;h2 id=&quot;left-vs-right-handed-coordinate-systems&quot; anchor=&quot;left-vs-right-handed-coordinate-systems&quot; data-block-key=&quot;vxh9y&quot;&gt;Left- vs Right-handed Coordinate Systems&lt;/h2&gt;&lt;p data-block-key=&quot;ablgq&quot;&gt;As a quick aside, it is important in what follows to know if we are dealing with a left-handed or right-handed coordinate system at any given time. First of all what does it even mean for a coordinate system to be left-handed or right-handed?&lt;/p&gt;&lt;p data-block-key=&quot;4k4rt&quot;&gt;Well, it&amp;#x27;s just a way of defining the relative orientations of the coordinate axes. In the following pictures we can use our thumb, first finger, and middle finger to represent the x, y, and z axes (or basis vectors if you prefer).&lt;/p&gt;&lt;p data-block-key=&quot;8mbc3&quot;&gt;In a right-handed coordinate system we use those digits on our right hand so that the x-axis points to the right say, the y-axis points up, leaving the z-axis (middle finger) to point towards us.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-33 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;right-handed.jpg&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/right-handed.original.jpg&quot; class=&quot;right-handed.jpg&quot; alt=&quot;right-handed.jpg&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;quy4v&quot;&gt;Conversely, in a left-handed coordinate system we can still have the x-axis pointing to the right and the y-axis pointing up, but this time the z-axis increases away from us.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-33 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;left-handed.jpg&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/left-handed.original.jpg&quot; class=&quot;left-handed.jpg&quot; alt=&quot;left-handed.jpg&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;lwejd&quot;&gt;Converting from a right-handed coordinate system to a left-handed coordinate system or vice versa can be achieved by simply flipping the sign of a single axis (or any odd number of axes).&lt;/p&gt;&lt;p data-block-key=&quot;kg1kv&quot;&gt;As we shall see, different graphics APIs use left- or right-handed coordinate systems at various stages of processing. This stuff can be a major source of confusion for graphics developers if they do not keep track of coordinate systems and often results in &amp;quot;oh hey, it works if I flip the sign of this column but I have no idea why&amp;quot;.&lt;/p&gt;&lt;h2 id=&quot;common-coordinate-systems-in-3d-graphics&quot; anchor=&quot;common-coordinate-systems-in-3d-graphics&quot; data-block-key=&quot;4owqo&quot;&gt;Common Coordinate Systems in 3D Graphics&lt;/h2&gt;&lt;p data-block-key=&quot;dwah6&quot;&gt;Let&amp;#x27;s take a quick tour of the coordinate systems used in 3D graphics at various stages of the (extended) pipeline. We will begin with OpenGL and then go on to discuss Vulkan and its differences. Note that the uses of the coordinate systems are the same in both systems, but as we shall see, there are some small but important changes between the two APIs. It is these differences that we need to be aware of in order to make our applications behave the way we want them to.&lt;/p&gt;&lt;p data-block-key=&quot;nutcm&quot;&gt;Here is a quick summary of the coordinate systems, what they are used for and where they occur.&lt;/p&gt;&lt;h3 id=&quot;model-space-or-object-space&quot; anchor=&quot;model-space-or-object-space&quot; data-block-key=&quot;fki22&quot;&gt;Model Space or Object Space&lt;/h3&gt;&lt;p data-block-key=&quot;f81ej&quot;&gt;This is any coordinate system that a 3D artist chooses to use when creating a particular asset. If modelling a chair, then they may use units of cm perhaps. If modelling a mountain range, a more suitable choice of unit may be km. Different tools also have different conventions for the orientation of axes. Blender for example, uses a z-up convention whereas as we shall see later, many real-time 3D applications choose to use y-up as their chosen orientation. Ultimately, it does not matter just so long as we know which conventions are used. Objects in model space are also often located close to the origin for convenience when being modelled and for when we later wish to position them.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;blender-rh-zup.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/blender-rh-zup.original.png&quot; class=&quot;blender-rh-zup.png&quot; alt=&quot;blender-rh-zup.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;yzfq5&quot;&gt;Model space is often right-handed but it is usually decided by the tool author or generative code author.&lt;/p&gt;&lt;h3 id=&quot;world-space&quot; anchor=&quot;world-space&quot; data-block-key=&quot;1ma0e&quot;&gt;World Space&lt;/h3&gt;&lt;p data-block-key=&quot;b5kve&quot;&gt;World space is what we are most familiar with and is typically what you create using game engine editors. World space is a coordinate system where everything is brought into consistent units whether the units we choose are microns, centimeters, meters, kilometers etc. How we define world space in our applications is up to us. It may well differ depending upon what it is we are trying to simulate. Cellular microscopy applications probably make more sense using suitable units such as microns or perhaps even nanometers. Whereas a space simulation is probably better off using kilometers or maybe something even larger – whatever allows you to make best use of the limited precision of floating point numbers.&lt;/p&gt;&lt;p data-block-key=&quot;us0r&quot;&gt;World space is also where we would rotate objects coming from various definitions of model space so that they make sense in the larger scene. For example, if a chair was modeled with the z-up convention and it wasn&amp;#x27;t rotated when it was exported, then when we place it into world space we would also apply the rotation here, so that it looks correct in a y-up convention.&lt;/p&gt;&lt;p data-block-key=&quot;rtkdv&quot;&gt;To create a consistent scene, we scale, rotate and translate our various 3D assets so that they are positioned relative to each other as we wish. The way we do this is to pre-multiply the vertex positions of the 3D assets by a &amp;quot;Model Matrix&amp;quot; for that asset. The Model Matrix, or just&lt;/p&gt;&lt;div data-katex-embed=&quot;M&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;50he6&quot;&gt;for short, is a 4x4 matrix that encodes the scaling, rotation and translation operations needed to correctly position the asset.&lt;/p&gt;&lt;p data-block-key=&quot;eof2d&quot;&gt;World space is often right-handed but it is up to the application developer to decide.&lt;/p&gt;&lt;h3 id=&quot;camera-or-view-or-eye-space&quot; anchor=&quot;camera-or-view-or-eye-space&quot; data-block-key=&quot;1j2ko&quot;&gt;Camera or View or Eye Space&lt;/h3&gt;&lt;p data-block-key=&quot;65tr3&quot;&gt;This next space goes by various names in the literature and online such as eye space, camera space or view space. Ultimately they all mean the same thing which is that the objects in our 3D world are transformed to be relative to our virtual camera. Wait, our what?&lt;/p&gt;&lt;p data-block-key=&quot;vglly&quot;&gt;Well, in order to be able to visualize our virtual 3D worlds on a display device, we must choose a position and orientation from which to view it. This is typically achieved by placing a virtual camera into the world. Yes, the camera entity is also positioned in world space by way of a transformation just like the assets mentioned above. View space is often defined to be a right-handed coordinate system where:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;t3frq&quot;&gt;the x-axis points to the right;&lt;/li&gt;&lt;li data-block-key=&quot;k612o&quot;&gt;the y-axis points upwards;&lt;/li&gt;&lt;li data-block-key=&quot;n4u3j&quot;&gt;and the z-axis is such that we are looking down the negative z-axis.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;coordinate-systems-viewspace.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/coordinate-systems-viewspace.original.png&quot; class=&quot;coordinate-systems-viewspace.png&quot; alt=&quot;coordinate-systems-viewspace.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;x0cpk&quot;&gt;Typically a camera is only rotated and translated to place it into world space and so the units of measurement are still whatever you decided upon for World space. Therefore, the transformation to get our 3D entities from world space and into view space consists only of a translation and rotation. The matrix for transforming from World space to View space is typically called the &amp;quot;View Matrix&amp;quot; or just&lt;/p&gt;&lt;div data-katex-embed=&quot;V&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;ft1t7&quot;&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;bj4eh&quot;&gt;View space is often right-handed but it is up to the developer to decide.&lt;/p&gt;&lt;h3 id=&quot;clip-space&quot; anchor=&quot;clip-space&quot; data-block-key=&quot;4knm6&quot;&gt;Clip Space&lt;/h3&gt;&lt;p data-block-key=&quot;dhd8h&quot;&gt;In addition to a position and orientation, our virtual camera also needs to provide some additional information that helps us convert from a purely mathematical model of our 3D world to how it should appear on screen. We need a way to map points in View space onto specific pixel coordinates on the display.&lt;/p&gt;&lt;p data-block-key=&quot;ke7vc&quot;&gt;The first step towards this is the conversion from View space to &amp;quot;Clip Space&amp;quot; which is achieved by multiplying the View space positions by a so-called &amp;quot;Projection Matrix&amp;quot; (abbreviated to&lt;/p&gt;&lt;div data-katex-embed=&quot;P&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;2civ2&quot;&gt;).&lt;/p&gt;&lt;p data-block-key=&quot;2avzo&quot;&gt;There are various ways to calculate a projection matrix,&lt;/p&gt;&lt;div data-katex-embed=&quot;P&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;13p19&quot;&gt;, depending upon if you wish to use an orthographic projection or a perspective projection.&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;3bmix&quot;&gt;&lt;b&gt;Orthographic projection:&lt;/b&gt; Often used in CAD applications as parallel lines in the world remain parallel on screen. Angles are preserved. The view volume (portion of scene that will appear on screen) is a cuboid.&lt;/li&gt;&lt;li data-block-key=&quot;2l5dp&quot;&gt;&lt;b&gt;Perspective projection:&lt;/b&gt; Often used in games and other applications as this mimics the way our eyes work. Distant objects appear smaller. Angles are not preserved. The view volume for a perspective projection is a frustum (truncated rectangular pyramid).&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;ht4df&quot;&gt;Ultimately, the projection matrix transforms the view volume into a cuboid in clip space with a characteristic size of w. Thanks to the way that perspective projection matrices are constructed, the w component is equal to the z-depth in eye space of the point being transformed. This is so that we can later use this to perform the perspective divide operation and get perspective-correct interpolation of our geometry&amp;#x27;s attributes (see below).&lt;/p&gt;&lt;p data-block-key=&quot;z6n1x&quot;&gt;Don&amp;#x27;t worry too much about the details of this. Conceptually it squashes things around so that anything that was inside the view volume (cube or frustum) into a cuboidal volume. The exact details of this depends upon which graphics API you are using (see even further below).&lt;/p&gt;&lt;h3 id=&quot;normalised-device-coordinates&quot; anchor=&quot;normalised-device-coordinates&quot; data-block-key=&quot;8igl7&quot;&gt;Normalised Device Coordinates&lt;/h3&gt;&lt;p data-block-key=&quot;3ohvh&quot;&gt;The next step along our path to getting something to appear on screen involves the use of NDC space or Normalized Device Coordinates. This step is easy though. All we do to get from Clip space to NDC space is to divide the x, y, and z components of each vertex by the 4th w component (and then discarding the 4th component which is now guaranteed to be exactly 1). A process known as homogenization or perspective divide.&lt;/p&gt;&lt;p data-block-key=&quot;v2lg7&quot;&gt;Why even do this? Well as the name suggests, clip space is used by the fixed function parts of the GPU to clip geometry so that it only has to rasterize parts that will actually be visible on the display. Any coordinate that has a magnitude exceeding the value w will be clipped.&lt;/p&gt;&lt;p data-block-key=&quot;edlll&quot;&gt;It is this step that &amp;quot;bakes in&amp;quot; the perspective effect if using a perspective transformation.&lt;/p&gt;&lt;p data-block-key=&quot;tu5p8&quot;&gt;The end result is that our visible part of the scene is now contained within a cuboid with characteristic length of 1. Again, see below for the differences between graphics APIs.&lt;/p&gt;&lt;p data-block-key=&quot;s425i&quot;&gt;NDC space is a nice simple, normalized coordinate system to reason about. We&amp;#x27;re now just a small step away from getting our 3D world to appear at the correct set of pixels on the display.&lt;/p&gt;&lt;h3 id=&quot;framebuffer-or-window-space&quot; anchor=&quot;framebuffer-or-window-space&quot; data-block-key=&quot;bo4k6&quot;&gt;Framebuffer or Window Space&lt;/h3&gt;&lt;p data-block-key=&quot;81np6&quot;&gt;The final step of the process is to convert from NDC to Window Space or Framebuffer Space or Viewport Space. Again more names for the same thing. It’s basically the pixel coordinates in your application window.&lt;/p&gt;&lt;p data-block-key=&quot;r0ype&quot;&gt;The conversion from NDC to Framebuffer space is controlled by the viewport transformation that you can configure in your graphics API of choice. This transformation is just a bias (offset) and scaling operation. This makes intuitive sense when you think we are converting from the normalized coordinates in NDC space to pixels. The levels of scale and bias are controlled by which portion of the window you wish to display to. Specifically it&amp;#x27;s offset and dimensions. The details of how to set the viewport transformation vary between graphics APIs.&lt;/p&gt;&lt;h2 id=&quot;coordinate-systems-in-practice&quot; anchor=&quot;coordinate-systems-in-practice&quot; data-block-key=&quot;kcyy5&quot;&gt;Coordinate Systems in Practice&lt;/h2&gt;&lt;p data-block-key=&quot;7nuxc&quot;&gt;The above descriptions sound very scary and intimidating but in practice they are not so bad once we understand what is going on. Spending a little time to understand the sequence of operations is very worth while and is infinitely better than randomly changing the signs of various elements to make something work in your one particular case. It&amp;#x27;s only a matter of time until your random tweak will break something else in the future.&lt;/p&gt;&lt;p data-block-key=&quot;hpt3k&quot;&gt;Take a look at the following diagram that summarizes the path that data takes through the graphics pipeline and the transformations/operations at each stage:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;pipeline-coords.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/pipeline-coords.original.png&quot; class=&quot;pipeline-coords.png&quot; alt=&quot;pipeline-coords.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;3po4q&quot;&gt;A few things to note:&lt;/p&gt;&lt;ol&gt;&lt;li data-block-key=&quot;rlhxx&quot;&gt;The transformations from Model Space to Clip Space are performed in the programmable Vertex Shader stage of the graphics pipeline.&lt;/li&gt;&lt;li data-block-key=&quot;fh5qu&quot;&gt;Rather than doing 3 distinct matrix multiplications for every vertex, we often combine the&lt;/li&gt;&lt;/ol&gt;&lt;div data-katex-embed=&quot;M&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;vsdc&quot;&gt;,&lt;/p&gt;&lt;div data-katex-embed=&quot;V&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;9cvoh&quot;&gt;, and&lt;/p&gt;&lt;div data-katex-embed=&quot;P&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;5ke4f&quot;&gt;matrices into a single matrix on the CPU and pass the result into the vertex shader. This allows a vertex to be transformed all the way to Clip Space with a single matrix multiplication.&lt;/p&gt;&lt;ol&gt;&lt;li data-block-key=&quot;k62m4&quot;&gt;The clipping and perspective divide operations are fixed function (hardwired in silicon) operations. Each graphics API specifies the coordinate systems in which these happen.&lt;/li&gt;&lt;li data-block-key=&quot;znjul&quot;&gt;The scale and bias transformation to go from NDC to Framebuffer Space is fixed function too but is controlled via API calls such as &lt;code&gt;glViewport()&lt;/code&gt; or &lt;code&gt;vkCmdSetViewport()&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p data-block-key=&quot;flzz0&quot;&gt;The upshot of all of this is that we need to create the Model, View and Projection matrices to get our vertex data correctly into Clip Space. How we do this differs subtly between the different graphics APIs such as OpenGL vs Vulkan as we shall see now. These differences are what often lead to some issues when migrating from OpenGL to Vulkan. Especially when using some helper libraries that were coded up with the expectation of only being used with OpenGL.&lt;/p&gt;&lt;p data-block-key=&quot;qf1x6&quot;&gt;As stated above, the Model, World and View spaces are defined by the content creation tools (Model Space) or by us as application/library developers (World and View spaces). It is only when we get to clip space that we have to be concerned about what the graphics API we are using expects to receive.&lt;/p&gt;&lt;h3 id=&quot;opengl-coordinate-systems&quot; anchor=&quot;opengl-coordinate-systems&quot; data-block-key=&quot;6edu7&quot;&gt;OpenGL Coordinate Systems&lt;/h3&gt;&lt;p data-block-key=&quot;g47lk&quot;&gt;With OpenGL, the fixed function parts of the pipeline all use &lt;b&gt;left-handed&lt;/b&gt; coordinate systems as shown here:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;opengl-fixed-function-coords.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/opengl-fixed-function-coords.original.png&quot; class=&quot;opengl-fixed-function-coords.png&quot; alt=&quot;opengl-fixed-function-coords.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gb571&quot;&gt;If we stick with the common conventions of using a right-handed set of coordinate systems for Model Space, World Space and View Space, then the transformation from View Space to Clip Space must also flip the handedness of the coordinate system somehow.&lt;/p&gt;&lt;p data-block-key=&quot;u6l48&quot;&gt;Recall that to go from View Space to Clip Space, we multiply our View Space vertex by the projection matrix&lt;/p&gt;&lt;div data-katex-embed=&quot;P&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;eunj9&quot;&gt;. Usually we would use some library to create a projection matrix for us such as glm or even glFrustum, if you are still using OpenGL 1.x!&lt;/p&gt;&lt;p data-block-key=&quot;lbr2x&quot;&gt;There are various ways to parameterize a perspective projection matrix but to keep it simple let&amp;#x27;s stick with the left (&lt;/p&gt;&lt;div data-katex-embed=&quot;l&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;aoumr&quot;&gt;), right (&lt;/p&gt;&lt;div data-katex-embed=&quot;r&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;e172e&quot;&gt;), top (&lt;/p&gt;&lt;div data-katex-embed=&quot;t&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;br15f&quot;&gt;), bottom (&lt;/p&gt;&lt;div data-katex-embed=&quot;b&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;ffiqh&quot;&gt;), near (&lt;/p&gt;&lt;div data-katex-embed=&quot;n&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;bsnt6&quot;&gt;) and far (&lt;/p&gt;&lt;div data-katex-embed=&quot;f&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;dc4d2&quot;&gt;) parameterisation as per the glFrustum specification. This assumes the virtual camera (or eye) is at the origin and that the near and far values are the distances to the near and far clip planes along the negative z-axis. The near plane is the plane to which our scene will be projected. The left, right, top and bottom values specify the positions on the near plane used to define the clip planes that form the view volume - a frustum in the case of a perspective transform.&lt;/p&gt;&lt;p data-block-key=&quot;ov9u6&quot;&gt;With this parameterisation, the projection matrix for OpenGL looks like this:&lt;/p&gt;&lt;p data-block-key=&quot;css4g&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5amc7&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; P = \begin{pmatrix}
 \tfrac{2n}{r-1} &amp;amp; 0 &amp;amp; \tfrac{r+1}{r-1} &amp;amp; 0 \\
 0 &amp;amp; \tfrac{2n}{t-b} &amp;amp; \tfrac{t+b}{t-b} &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; -\tfrac{f+n}{f-n} &amp;amp; -\tfrac{2fn}{f-n} \\
 0 &amp;amp; 0 &amp;amp; -1 &amp;amp; 0
 \end{pmatrix}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;2r5tt&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;21h9b&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5pf9c&quot;&gt;&lt;b&gt;Do not blindly use this as your projection matrix! It is specifically for OpenGL!&lt;/b&gt;&lt;/p&gt;&lt;p data-block-key=&quot;lmvgo&quot;&gt;OK, that looks reasonable and matches various texts on OpenGL programming. It works perfectly well for OpenGL because it not only performs the perspective projection transform but it also bakes in the flip from right-handed coordinates to left-handed coordinates. This last little fact seems to be something that many texts gloss over and so goes unnoticed by many graphics developers. So where does this happen? That pesky little -1 in the 3rd column of the 4th row is what does it. This has the effect of flipping the z-axis and using -z as the w component causing the change in handedness.&lt;/p&gt;&lt;p data-block-key=&quot;ldp2u&quot;&gt;If we blindly then use the same matrix to calculate a perspective projection for use with Vulkan that does not need the handedness flip, then we end up in trouble. This is typically followed by google searches leading to one of the many hacks to provide a &amp;quot;fix&amp;quot;.&lt;/p&gt;&lt;p data-block-key=&quot;93t9e&quot;&gt;Instead, let&amp;#x27;s use our understanding of the problem domain to now come up with a proper correction for use with Vulkan.&lt;/p&gt;&lt;h3 id=&quot;vulkan-coordinate-systems&quot; anchor=&quot;vulkan-coordinate-systems&quot; data-block-key=&quot;ixxb0&quot;&gt;Vulkan Coordinate Systems&lt;/h3&gt;&lt;p data-block-key=&quot;gzcs7&quot;&gt;Conversely to OpenGL, the fixed function coordinate systems used in Vulkan remain as &lt;b&gt;right-handed&lt;/b&gt; in keeping with the earlier coordinate systems as shown here:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;vulkan-fixed-function-coords.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/vulkan-fixed-function-coords.original.png&quot; class=&quot;vulkan-fixed-function-coords.png&quot; alt=&quot;vulkan-fixed-function-coords.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;qn2mi&quot;&gt;Notice that even though z-increases into the distance and y is increasing down, it is still in fact a right-handed coordinate system. You can convince yourself of this with some flexible rotations of your right hand similar to the photographs above.&lt;/p&gt;&lt;p data-block-key=&quot;l4ajm&quot;&gt;Let&amp;#x27;s think about what we need conceptually without getting bogged down in the math - for now at least, we will save that for next time. With the OpenGL perspective projection matrix we have something that takes care of the transformation of the view frustum into a cube in clip space. The problem we have when using it with Vulkan is the flip in the handedness of the coordinate system thanks to that -1 we mentioned in the previous section. Setting that perspective component to 1 instead of -1 prevents the flip in handedness - there&amp;#x27;s a bit more to it as we will see in part 2 but that takes care of the change in handedness.&lt;/p&gt;&lt;p data-block-key=&quot;2n5z0&quot;&gt;We still need to reorient our coordinate axes from View Space (x-right, y-up, looking down the negative z-axis) to Vulkan&amp;#x27;s Clip Space (x-right, y-down, looking down the positive z-axis). Since the start and end coordinate systems are both right-handed, this does not involve an axis flip as in the OpenGL case. Instead, all we need to do is to perform a rotation of 180 degrees about the x-axis. This gives us exactly the change in orientation that we need.&lt;/p&gt;&lt;p data-block-key=&quot;j2kak&quot;&gt;This means that before we see how to construct a projection matrix, we should reorient our coordinate axes to already be aligned with the desired clip space orientation. To do this, we inject a 180 degree rotation of the eye space coordinate around the x-axis before we later apply the actual projection. This rotation is shown here:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;view-to-clip-correction.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/view-to-clip-correction.original.png&quot; class=&quot;view-to-clip-correction.png&quot; alt=&quot;view-to-clip-correction.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;a1iu0&quot;&gt;Recall from high school maths, that a rotation matrix about the x-axis basis vector of&lt;/p&gt;&lt;div data-katex-embed=&quot;180\degree&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;52n6a&quot;&gt;(&lt;/p&gt;&lt;div data-katex-embed=&quot;\pi&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;bihnf&quot;&gt;radians) is easily constructed as:&lt;/p&gt;&lt;p data-block-key=&quot;5qf17&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;99peo&quot;&gt;&lt;/p&gt;&lt;div data-katex-embed=&quot; X =
 \begin{pmatrix}
 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
 0 &amp;amp; \cos{\pi} &amp;amp; \sin{\pi} &amp;amp; 0 \\
 0 &amp;amp; -\sin{\pi} &amp;amp; \cos{\pi} &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 1 \\
 \end{pmatrix}
 =
 \begin{pmatrix}
 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
 0 &amp;amp; -1 &amp;amp; 0 &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; -1 &amp;amp; 0 \\
 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 1 \\
 \end{pmatrix}&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;cniid&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;9a2vq&quot;&gt;&lt;/p&gt;&lt;p data-block-key=&quot;k8qzb&quot;&gt;This also makes sense intuitively as the y and z components of the matrix will both be negated by the -1 elements. Note that we have two &amp;quot;axis flips&amp;quot;, so it still maintains the right-handedness of the coordinate system as desired.&lt;/p&gt;&lt;p data-block-key=&quot;1tuin&quot;&gt;So, in the end all we need to do is to include this &amp;quot;correction matrix&amp;quot;, X, into our usual chain of matrices when calculating the combined model-view-projection matrix that gets passed to the vertex shader. With the correction included, our combined matrix is calculated as&lt;/p&gt;&lt;div data-katex-embed=&quot;A = (PX)VM = PXVM&quot;&gt;&lt;/div&gt;&lt;p data-block-key=&quot;f6a11&quot;&gt;. That means the transforms applied in order (right to left) are:&lt;/p&gt;&lt;ol&gt;&lt;li data-block-key=&quot;rgvaf&quot;&gt;Model to World&lt;/li&gt;&lt;li data-block-key=&quot;qpnbu&quot;&gt;World to Eye/View&lt;/li&gt;&lt;li data-block-key=&quot;fjgdn&quot;&gt;Eye/View to &lt;b&gt;Rotated Eye/View&lt;/b&gt;&lt;/li&gt;&lt;li data-block-key=&quot;9ealq&quot;&gt;Rotated Eye/View to Clip&lt;/li&gt;&lt;/ol&gt;&lt;p data-block-key=&quot;p841u&quot;&gt;With the above in place, we can transform vertices all the way from Model Space through to Vulkan&amp;#x27;s Clip Space and beyond. All that remains for us next time, is to see how to actually construct the perspective projection matrix. However, we are now in a good position (and orientation) to derive the perspective projection matrix as our source (rotated eye space) and destination (clip space) coordinate systems are now aligned. All we have to worry about is the actual projection of vertices onto the near plane.&lt;/p&gt;&lt;p data-block-key=&quot;o6772&quot;&gt;Once we complete this next step, we will be able to avoid any of the ugly hacks mentioned at the start of this article and we will have a full understanding of how our vertices are transformed all the way from Blender through to appearing on our screens. Thanks for reading!&lt;/p&gt;&lt;p data-block-key=&quot;lgn05&quot;&gt;Part 2 is available &lt;a href=&quot;https://www.kdab.com/projection-matrices-with-vulkan-part-2/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/projection-matrices-with-vulkan-part-1/&quot;&gt;Projection Matrices with Vulkan - Part 1&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sean Harmer</dc:creator><category>3d</category><category>performance</category></item><item><title>Optimizing and Sharing Shader Structures</title><link>https://www.kdab.com/optimizing-and-sharing-shader-structures/</link><guid isPermaLink="true">https://www.kdab.com/optimizing-and-sharing-shader-structures/</guid><description>&lt;p data-block-key=&quot;u9r2r&quot;&gt;When writing large graphics applications in Vulkan or OpenGL, there&amp;#x27;s many data structures that need to be passed from the CPU to the GPU and vice versa. There are subtle differences in alignment, padding and so on between C++ and GLSL to keep track of as well. I&amp;#x27;m going to cover a tool I wrote […]&lt;/p&gt;</description><pubDate>Thu, 24 Aug 2023 07:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Optimizing and Sharing Shader Structures&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;caknr&quot;&gt;When writing large graphics applications in Vulkan or OpenGL, there&amp;#x27;s many data structures that need to be passed from the CPU to the GPU and vice versa. There are subtle differences in alignment, padding and so on between C++ and GLSL to keep track of as well. I&amp;#x27;m going to cover a tool I wrote that generates safe and optimal code. This helps not only the GPU but the programmer writing shaders too. Here&amp;#x27;s a rundown of the problems I&amp;#x27;m trying to solve and how you can implement a similar system in your own programs.&lt;/p&gt;&lt;p data-block-key=&quot;z0eka&quot;&gt;This tool specifically targets and references Vulkan rules, but similar rules exist in OpenGL.&lt;/p&gt;&lt;h2 id=&quot;reasoning&quot; anchor=&quot;reasoning&quot; data-block-key=&quot;fq3el&quot;&gt;Reasoning&lt;/h2&gt;&lt;p data-block-key=&quot;7mml6&quot;&gt;Here&amp;#x27;s an example of real code, exposing options to a post-processing stage.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-glsl  line-numbers &quot;&gt;layout(push_constant) uniform PushConstant {
    vec4 viewport;
    vec4 options;
    vec4 transform_ops;
    vec4 ao_options;
    vec4 ao_options2;
    vec4 proj_info;
    mat4 cameraProj;
    mat4 invProj;
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;chdte&quot;&gt;Even for the person who wrote this code, it&amp;#x27;s hard to tell what each option does from a glance. This is a great way to create bugs, since it&amp;#x27;s extremely easy to mix up accessors like &lt;i&gt;ao_options.x&lt;/i&gt; and &lt;i&gt;ao_options.y&lt;/i&gt;. Ideally, we want these options to be separated but there&amp;#x27;s a reason why they&amp;#x27;re packed in the first place.&lt;/p&gt;&lt;h3 id=&quot;alignment-rules&quot; anchor=&quot;alignment-rules&quot; data-block-key=&quot;xfryy&quot;&gt;Alignment rules&lt;/h3&gt;&lt;p data-block-key=&quot;dhkoj&quot;&gt;Say you&amp;#x27;re beginning to explore &lt;a href=&quot;https://en.wikipedia.org/wiki/Phong_shading&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Phong shading&lt;/a&gt;, and you want to expose a position and a color property so you can change them while the program is running. In a 3D environment, there are three axes (X, Y and Z) so naturally it must be a vec3. Light color also makes sense to be a vec3. When emitted from a light, it&amp;#x27;s color can&amp;#x27;t really be &amp;quot;transparent&amp;quot; so  we don&amp;#x27;t need the alpha channel. The GLSL code so far looks like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-glsl  line-numbers &quot;&gt;#version 430

out vec4 finalColor;

layout(binding = 0) buffer block {
    vec3 position;
    vec3 color;
} light;

void main() {
    const vec3 dummy = vec3(1) - light.position;
    finalColor = vec4(vec3(1.0, 1.0, 1.0) * light.color, 1.0);
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;t5m8f&quot;&gt;(There&amp;#x27;s no Phong formula here, we want to make sure the GLSL compiler doesn&amp;#x27;t optimize anything out.)&lt;/p&gt;&lt;p data-block-key=&quot;43uqn&quot;&gt;When writing the structure on the C++ side, you might write something like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;struct Light {
    glm::vec3 position;
    glm::vec3 color;
} light;

light.position = {1, 5, 0};
light.color = {3, 2, -1};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;0ass3&quot;&gt;For this example I used the &lt;a href=&quot;https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/debug_printf.md&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;debug printf&lt;/a&gt; system, which is part of the &lt;a href=&quot;https://www.lunarg.com/vulkan-sdk/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Vulkan SDK&lt;/a&gt; so we can confirm the exact values. The output is as follows:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;Position = (1.000000, 5.000000, 0.000000)
Color = (2.000000, -1.000000, 0.000000)&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;5twoj&quot;&gt;As you can see, the first value of color is getting chopped off when reading it in the shader. The usual solution to the problem is to use a vec4 instead:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;struct Light {
    glm::vec4 position;
    glm::vec4 color;
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;ktqtd&quot;&gt;And to confirm, this does indeed fix the issue:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;Position = (1.000000, 5.000000, 0.000000)
Color = (3.000000, 2.000000, -1.000000)&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;g7kqv&quot;&gt;But why does it work when we change to it a vec4? &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#interfaces-resources-layout&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;This section&lt;/a&gt; from the Vulkan specification spells it out for us:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;sqswz&quot;&gt;The base alignment of the type of an OpTypeStruct member is defined recursively as follows:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;7shm7&quot;&gt;A scalar has a base alignment equal to its scalar alignment.&lt;/li&gt;&lt;li data-block-key=&quot;40kal&quot;&gt;A two-component vector has a base alignment equal to twice its scalar alignment.&lt;/li&gt;&lt;li data-block-key=&quot;9ei87&quot;&gt;&lt;b&gt;A three- or four-component vector has a base alignment equal to four times its scalar alignment.&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gi1wj&quot;&gt;The third bullet point hits it right on the head, vec4 and vec3 have the &lt;i&gt;same alignment!&lt;/i&gt; An alternative solution could be to use alignas:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;struct Light {
    glm::vec3 color;
    alignas(16) glm::vec3 position;
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;ymjgi&quot;&gt;There&amp;#x27;s a bunch of more nitty and dirty alignment issues that stem from differences between C++ and GLSL, and this is one of those cases. In my opinion, this shouldn&amp;#x27;t be nessecary for the programmer to handle themselves.&lt;/p&gt;&lt;h3 id=&quot;passing-booleans&quot; anchor=&quot;passing-booleans&quot; data-block-key=&quot;ti9lz&quot;&gt;Passing booleans&lt;/h3&gt;&lt;p data-block-key=&quot;fvue3&quot;&gt;Another example of esoteric shader rules is when you try passing booleans. Take a look at this C++ structure, which seems okay at first glance:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;struct TestBuffer {
    bool a = false;
    bool b = true;
    bool c = false;
    bool d = true;
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;bvkqs&quot;&gt;And this is how it&amp;#x27;s defined in GLSL:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-glsl  line-numbers &quot;&gt;layout(binding = 0) buffer readonly TestBuffer {
    bool a, b, c, d;
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;qo0il&quot;&gt;When sent to the shader, the values of the structure end up like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;a = 1, b = 0, c = 0, d = 0&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;yctyu&quot;&gt;This is because &lt;a href=&quot;https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpTypeBool&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;because SPIR-V doesn&amp;#x27;t seem to define a physical size for bool&lt;/a&gt;, so  it could be represented as anything (like an unsigned integer). In this case, you actually want to define them as integer:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;layout(binding = 0) buffer readonly TestBuffer {
    int a, b, c, d;
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;68bl2&quot;&gt;This is a little disappointing, because the semantic meaning of a boolean option is lost when you declare them as integers. You can also pack a lot of booleans into the space of one 32-bit integer, which could be a possible space-saving optimization in the future.&lt;/p&gt;&lt;h3 id=&quot;sharing-structures&quot; anchor=&quot;sharing-structures&quot; data-block-key=&quot;2zpq0&quot;&gt;Sharing structures&lt;/h3&gt;&lt;p data-block-key=&quot;5qqaq&quot;&gt;The last problem is keeping the structures in sync. There&amp;#x27;s usually one instance of the structure written in C++ and many copies in GLSL shaders. This is problematic because member order could change, so parts of the structure itself could be undefined and can easily escape notice. Having &lt;i&gt;one&lt;/i&gt; definition for all shaders and C++ would be a huge improvement!&lt;/p&gt;&lt;h2 id=&quot;struct-compiler&quot; anchor=&quot;struct-compiler&quot; data-block-key=&quot;9343j&quot;&gt;Struct compiler&lt;/h2&gt;&lt;p data-block-key=&quot;8wbzg&quot;&gt;What I ended up with is a new pre-processing step, which I called the &amp;quot;struct compiler&amp;quot;. I tried searching on the Internet to see if someone has already made a tool like this, but couldn&amp;#x27;t find much - maybe shader reflection is more popular. I did learn a lot from making this tool anyway. It&amp;#x27;s main goals are:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;li67o&quot;&gt;Define the shader structures in one, centralized file.&lt;/li&gt;&lt;li data-block-key=&quot;ydn3g&quot;&gt;Structures should be able to be written on a higher-level, allowing us to decouple the actual member order, alignment and packing from the logic. This enables the compiler to optimize the structure in the future, maybe beyond what we can reasonably hand-write.&lt;/li&gt;&lt;li data-block-key=&quot;yplzx&quot;&gt;The structure is usable in GLSL and C++.&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;8fzl0&quot;&gt;First you write a &lt;i&gt;.struct&lt;/i&gt; file, describing the required members and their types. Here&amp;#x27;s the same post-processing structure showcased in the beginning, but now written in the compiler&amp;#x27;s custom syntax:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-glsl  line-numbers &quot;&gt;primary PostPushConstant {
    viewport: vec4
    camera_proj: mat4
    inv_proj: mat4
    inv_view: mat4

    enable_aa: bool
    enable_dof: bool

    exposure: float
    display_color_space: int
    tonemapping: int

    ao_radius: float
    ao_r2: float
    ao_rneginvr2: float
    ao_rdotvbias: float
    ao_intensity: float
    ao_bias: float
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;esjph&quot;&gt;This looks much better, doesn&amp;#x27;t it? Even without knowing anything else about the actual shader, you can guess which options do what with some accuracy. Here&amp;#x27;s what it might look like, compiled to C++:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;struct PostPushConstant {
    glm::mat4 camera_proj;
    glm::mat4 inv_proj;
    glm::mat4 inv_view;
    glm::vec4 viewport;
    glm::ivec4 enable_aa_enable_dof_display_color_space_tonemapping_;
    glm::vec4 exposure_ao_radius_ao_r2_ao_rneginvr2_;
    glm::vec4 ao_rdotvbias_ao_intensity_ao_bias_;
    ...
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;2eqrp&quot;&gt;(Setters like &lt;i&gt;set_exposure()&lt;/i&gt; and set_exposure() are used instead of accessing the glm::vec4 manually.)&lt;/p&gt;&lt;p data-block-key=&quot;7zpke&quot;&gt;I hook the generation step in my buildsystem to automatically run, so all you need to do is include the auto-generated header. To use the structure in GLSL, I created a new directive that inserts the GLSL version of the structure given by the struct compiler. The same system that generates the C++ headers also generates GLSL which inserts where this directive is found:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;#use_struct(push_constant, post, post_push_constant)&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;v7tcj&quot;&gt;(The syntax could use some work, but the first argument is the usage, and the second argument is the name of the struct. The third argument is a unique name for the instance.)&lt;/p&gt;&lt;p data-block-key=&quot;4ihu7&quot;&gt;Since the member order and names are undefined, you must access the members by a setter/getter in GLSL and C++. I think this is a worthwhile trade-off for readable code.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;vec3 ao_result = pow(ao, ao_intensity())&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;njhpa&quot;&gt;This tool runs as a pre-processing step offline, before shader compilation begins. The tool&amp;#x27;s source code is &lt;a href=&quot;https://git.sr.ht/~redstrate/structcompiler&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;available here&lt;/a&gt;, which is taken from one of my personal projects. It&amp;#x27;s quickly written and I don&amp;#x27;t recommend using it directly, but I&amp;#x27;m confident that this idea is worth pursuing.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/optimizing-and-sharing-shader-structures/&quot;&gt;Optimizing and Sharing Shader Structures&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>3d</category><category>c++</category><category>performance</category></item><item><title>KDGpu v.0.1.0 is released</title><link>https://www.kdab.com/kdgpu-v-0-1-0-is-released/</link><guid isPermaLink="true">https://www.kdab.com/kdgpu-v-0-1-0-is-released/</guid><description>&lt;p data-block-key=&quot;3jpi9&quot;&gt;We&amp;#x27;re pleased to announce we&amp;#x27;ve added a new library, KDGpu, to the arsenal of tools we invent to make our lives easier - and then share with you on KDAB&amp;#x27;s GitHub. Who is this for? If you want to become more productive with Vulkan or learn the concepts of modern explicit graphics APIs, then KDGpu […]&lt;/p&gt;</description><pubDate>Thu, 10 Aug 2023 06:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;KDGpu v.0.1.0 is released&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;wuakf&quot;&gt;We&amp;#x27;re pleased to announce we&amp;#x27;ve added a new library, KDGpu, to the arsenal of tools we invent to make our lives easier - and then share with you on KDAB&amp;#x27;s &lt;a href=&quot;https://github.com/KDAB/KDGpu&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;who-is-this-for&quot; anchor=&quot;who-is-this-for&quot; data-block-key=&quot;bfqr3&quot;&gt;Who is this for?&lt;/h2&gt;&lt;p data-block-key=&quot;cgo09&quot;&gt;If you want to become more productive with Vulkan or learn the concepts of modern explicit graphics APIs, then KDGpu is the library for you!&lt;/p&gt;&lt;p data-block-key=&quot;itjam&quot;&gt;KDGpu is a thin wrapper around Vulkan whose purpose it is to make modern graphics APIs more accessible and easier to learn. It cuts through the verbose syntax, makes managing object lifetimes much simpler and allows you to get your project working without having to be bogged down in intricacies involved in tasks such as synchronization or memory handling. The sensible option defaults in KDGpu mean that you can focus on solving the problem at hand. Furthermore, KDGpu exposes almost all of the power of raw Vulkan and is very well suited to teaching modern graphics APIs and their concepts.&lt;/p&gt;&lt;p data-block-key=&quot;rsr8k&quot;&gt;KDGpu enables you to make examples like this easily and with great readability:&lt;/p&gt;&lt;/div&gt;







&lt;div class=&quot;cookieconsent-optin-marketing overlay-embed-block&quot;&gt;
    &lt;div style=&quot;padding-bottom: 56.49999999999999%;&quot; class=&quot;responsive-object&quot;&gt;
    &lt;iframe width=&quot;200&quot; height=&quot;113&quot; src=&quot;https://www.youtube.com/embed/KoG3nhyEtcg?feature=oembed&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen title=&quot;KDGpu Compute Particles&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;style&gt;
.overlay-embed-block .responsive-object {
    position: relative;
}

.overlay-embed-block .responsive-object iframe,
.overlay-embed-block .responsive-object object,
.overlay-embed-block .responsive-object embed {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}
&lt;/style&gt;



&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;what-got-kdab-started-on-this&quot; anchor=&quot;what-got-kdab-started-on-this&quot; data-block-key=&quot;uakqy&quot;&gt;What got KDAB started on this?&lt;/h2&gt;&lt;p data-block-key=&quot;s708m&quot;&gt;As you are likely aware, KDAB offers training in 3D. So far, we have used OpenGL as the vehicle for these training courses and for years it has performed very well, both for the courses and the industry.&lt;/p&gt;&lt;p data-block-key=&quot;ih9i2&quot;&gt;However, with the modern explicit graphics APIs that have come along (Vulkan, Metal, D3D12 and WebGPU), OpenGL has been left to languish a bit. On MacOS, Apple has frozen OpenGL at version 4.1 which is annoying as 4.2 brought all sorts of cool things to the party.&lt;/p&gt;&lt;p data-block-key=&quot;aqu74&quot;&gt;Replacing OpenGL in our training courses with Vulkan wasn&amp;#x27;t really an option due to the extreme complexity in this, the most verbose API available. We needed something like Vulkan but not Vulkan :-)&lt;/p&gt;&lt;p data-block-key=&quot;1g4zt&quot;&gt;&lt;b&gt;Say hello to KDGpu!&lt;/b&gt;&lt;/p&gt;&lt;h2 id=&quot;what-does-kdgpu-provide&quot; anchor=&quot;what-does-kdgpu-provide&quot; data-block-key=&quot;w8pv2&quot;&gt;What does KDGpu Provide?&lt;/h2&gt;&lt;p data-block-key=&quot;p6915&quot;&gt;Although we began work on KDGpu as a tool to aid teaching, it has grown beyond that into a library that is suitable for use in production projects and we are using it as such ourselves. We will keep adding to the library as we find new features that we need but we will keep KDGpu itself as lightweight as possible. It is also possible that in the future we may add other backend beyond Vulkan.&lt;/p&gt;&lt;p data-block-key=&quot;ruk7c&quot;&gt;For now though, what exactly is in KDGpu the repository?&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;023h2&quot;&gt;The KDGpu library. A collection of classes and options structs that make it easy, concise and clear to work with Vulkan.&lt;/li&gt;&lt;li data-block-key=&quot;e7qed&quot;&gt;An example framework called KDGpuExample that provides integration with &lt;a href=&quot;https://github.com/KDAB/KDUtils&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;KDGui&lt;/a&gt; to make it easy to experiment by providing a cross-platform windowing and event loop implementation.&lt;/li&gt;&lt;li data-block-key=&quot;zzm6c&quot;&gt;A set of illustrative examples showing how to use KDGpu for common rendering tasks from the typical hello_triangle through to rendering involving multiple rendering and compute passes.&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;oelpg&quot;&gt;KDGpu is independent of any particular windowing system. You can use it purely with platform native APIs or you can checkout the included KDGpuExample library which makes it trivial to use KDGpu with KDGui::Window and friends. Check out the handy &lt;a href=&quot;https://github.com/KDAB/KDBindings&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;KDBindings&lt;/a&gt; repository too for some more syntactic sugar.&lt;/p&gt;&lt;p data-block-key=&quot;da0sy&quot;&gt;The following images were all created with examples written with KDGpu. We will publish some more in depth blogs on these and other examples in the future to show just how easy it is to reason about graphics issues without getting lost in hundreds of lines of code.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;model_with_textures.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/model_with_textures.original.png&quot; class=&quot;model_with_textures.png&quot; alt=&quot;model_with_textures.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-start&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;cjc0q&quot;&gt;Using KDGpu to render a textured glTF 2 model. KDGpu makes it easy to manage resources for shaders. The glTF parsing was performed using the excellent tinygltf library.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;model_without_textures.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/model_without_textures.original.png&quot; class=&quot;model_without_textures.png&quot; alt=&quot;model_without_textures.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-start&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;e1d9y&quot;&gt;Instanced rendering of the glTF 2 buggy model. Each sub-mesh is drawn using instancing to reduce the number of draw calls and binding operations.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;kdgpu_msdf_text_02.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/kdgpu_msdf_text_02.original.png&quot; class=&quot;kdgpu_msdf_text_02.png&quot; alt=&quot;kdgpu_msdf_text_02.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-start&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;4ghzx&quot;&gt;Example showing various styles of text effect using multi-channel signed distance fields.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gbb36&quot;&gt;For now, you can get started looking at the examples in the repository and looking at the &lt;a href=&quot;https://docs.kdab.com/kdgpu/unstable/index.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;gimqg&quot;&gt;The KDGpuExample helper library is a great help to those starting out. It takes care of creating a device; performing frame-to-frame synchronisation (options for loops with and without the use of vkDeviceWaitIdle); managing the depth buffer; easily enabling of MSAA etc. When you want to take the leap and manage these things yourself or integrate KDGpu into your own engine, then it is very easy to do so.&lt;/p&gt;&lt;h2 id=&quot;show-me-some-code&quot; anchor=&quot;show-me-some-code&quot; data-block-key=&quot;27qjj&quot;&gt;Show me some code!&lt;/h2&gt;&lt;p data-block-key=&quot;um1iu&quot;&gt;We won&amp;#x27;t go into all of the details in this announcement blog, but it would be remiss of us not to give you a taster of what KDGpu code looks like in practise.&lt;/p&gt;&lt;p data-block-key=&quot;rof2x&quot;&gt;The core of a graphics application is its render function which gets called each and every frame to record the command buffers for the GPU to process. In KDGpu, the process of recording a command buffer to bind a graphics pipeline, set some state and actually draw something would look like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;void HelloTriangle::render()
{
    // m_device is a KDGpu::Device
    auto commandRecorder = m_device.createCommandRecorder();

    // m_opaquePassOptions is a KDGpu::RenderPassCommandRecorderOptions struct that we use to specify the render pass setup
    m_opaquePassOptions.colorAttachments[0].view = m_swapchainViews.at(m_currentSwapchainImageIndex);
    auto opaquePass = commandRecorder.beginRenderPass(m_opaquePassOptions);

    // Get ready to draw
    opaquePass.setPipeline(m_pipeline);
    opaquePass.setVertexBuffer(0, m_buffer);
    opaquePass.setIndexBuffer(m_indexBuffer);
    opaquePass.setBindGroup(0, m_transformBindGroup);

    // Record the draw command
    const DrawIndexedCommand drawCmd = { .indexCount = 3 };
    opaquePass.drawIndexed(drawCmd);

    // End the render pass and finish the command recording
    opaquePass.end();
    m_commandBuffer = commandRecorder.finish();

    // Submit command buffer
    ...
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;h06lu&quot;&gt;In the above code, we are referring to a few other objects of types such as KDGpu::Buffer (m_buffer and m_indexBuffer), KDGpu::BindGroup (m_transformBindGroup) and KDGpu::GraphicsPipeline (m_pipeline). The functions that consume these objects (and many others) are actually passing around handles to the objects rather than the objects themselves.&lt;/p&gt;&lt;p data-block-key=&quot;ak5ob&quot;&gt;The objects themselves are owning, move-only, strong references to the underlying Vulkan objects whereas the templated Handle type is a weak-reference that is very cheap to pass around. The handles are just generational indices into some object pools managed by KDGpu.&lt;/p&gt;&lt;p data-block-key=&quot;vpg3q&quot;&gt;To keep a GPU resource alive, simply keep the corresponding C++ object alive. To pass it to a consuming function, each class has a convenience conversion to handle operator implemented. This really simplifies resource management with natural C++ semantics.&lt;/p&gt;&lt;p data-block-key=&quot;ncytu&quot;&gt;Just having local named objects makes the explicit graphics APIs much easier to reason about than OpenGL&amp;#x27;s massive global state machine approach. Using KDGpu to go even further in terms of reducing code verbosity and using it&amp;#x27;s sane defaults approach makes graphics programming a delight and accessible to mere mortals.&lt;/p&gt;&lt;p data-block-key=&quot;it2vo&quot;&gt;Of course, rendering is not very exciting without some GPU-based resources such as textures and buffers. KDGpu makes creating such resources very easy and intuitive.&lt;/p&gt;&lt;p data-block-key=&quot;mme13&quot;&gt;We begin with a quick look at the KDGpu::BufferOptions struct:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;struct BufferOptions {
    DeviceSize size;
    BufferUsageFlags usage;
    MemoryUsage memoryUsage;
    SharingMode sharingMode{ SharingMode::Exclusive };
    std::vector queueTypeIndices{};
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;p9ncu&quot;&gt;Of note is the sensible default value for the sharing mode member. The option is there if we need to set it but in the common case we can just run with the default value. The code to create a buffer object to hold some index data is then pretty much as you would hope:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;std::array&amp;lt;uint32_t, 3&amp;gt; indexData = { 0, 1, 2 };
const DeviceSize dataByteSize = indexData.size() * sizeof(uint32_t);
const BufferOptions bufferOptions = {
    .size = dataByteSize,
    .usage = BufferUsageFlagBits::IndexBufferBit | BufferUsageFlagBits::TransferDstBit,
    .memoryUsage = MemoryUsage::GpuOnly
};
m_indexBuffer = m_device.createBuffer(bufferOptions);&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;e7umr&quot;&gt;The memory usage flags indicate that we wish the buffer to be resident in GPU memory and the usage flags indicate that we will use it as a source of index data for some geometry and that the buffer should be capable of being the target of a copy operation (we need to get the data in there somehow).&lt;/p&gt;&lt;p data-block-key=&quot;8euob&quot;&gt;This pattern of using options structs and initializing them with C++20 designated initializers permeates through the API. It makes it easily discoverable, extensible and trivial to queue up for deferred invocations.&lt;/p&gt;&lt;p data-block-key=&quot;k7nld&quot;&gt;Uploading data to the above buffer is just as simple:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;const BufferUploadOptions uploadOptions = {
    .destinationBuffer = m_indexBuffer,
    .dstStages = PipelineStageFlagBit::VertexAttributeInputBit,
    .dstMask = AccessFlagBit::IndexReadBit,
    .data = indexData.data(),
    .byteSize = dataByteSize
};
m_queue.uploadBufferData(uploadOptions);&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;lwkyf&quot;&gt;Here we are using the uploadBufferData function that is a member function on KDGpu::Queue to perform a synchronous upload of the data. If you instead wish to perform an asynchronous upload of the data to keep the CPU doing useful work, then the KDGpuExample library has helpers for this too. There is some interplay with the concept of a frame which the KDGpu::Queue on its own does not know about. Feel free to take a look at the code for the details, it is all very simple and clearly written.&lt;/p&gt;&lt;h2 id=&quot;find-out-more-and-get-up-and-running-with-kdgpu&quot; anchor=&quot;find-out-more-and-get-up-and-running-with-kdgpu&quot; data-block-key=&quot;6n35l&quot;&gt;Find out more and get up and running with KDGpu&lt;/h2&gt;&lt;p data-block-key=&quot;rc2eu&quot;&gt;In this blog post we have introduced KDAB&amp;#x27;s new library, KDGpu, seen some of the advantages it brings, and had a cursory look at a flavour of the API in use.&lt;/p&gt;&lt;p data-block-key=&quot;jovmc&quot;&gt;If this has whetted your appetite for GPU programming, look out for our follow-up posts which will deal with glTF rendering and more in much more detail. We think that you will like KDGpu. We have certainly had fun writing it and then using it on projects. For now, please give &lt;a href=&quot;https://github.com/KDAB/KDGpu&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;KDGpu a try&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/kdgpu-v-0-1-0-is-released/&quot;&gt;KDGpu v.0.1.0 is released&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sean Harmer</dc:creator><category>3d</category><category>tools</category></item><item><title>Synchronization in Vulkan</title><link>https://www.kdab.com/synchronization-in-vulkan/</link><guid isPermaLink="true">https://www.kdab.com/synchronization-in-vulkan/</guid><description>&lt;p data-block-key=&quot;7nh6y&quot;&gt;An important part of working with Vulkan and other modern explicit rendering APIs is the synchronization of GPU/GPU and CPU/GPU workloads. In this article we will learn about what Vulkan needs us to synchronize and how to achieve it. We will talk about two high-level parts of the synchronization domain that we, as application and […]&lt;/p&gt;</description><pubDate>Thu, 22 Jun 2023 07:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Synchronization in Vulkan&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;a2sh1&quot;&gt;An important part of working with Vulkan and other modern explicit rendering APIs is the synchronization of GPU/GPU and CPU/GPU workloads. In this article we will learn about what Vulkan needs us to synchronize and how to achieve it. We will talk about two high-level parts of the synchronization domain that we, as application and library developers, are responsible for:&lt;/p&gt;&lt;ol&gt;&lt;li data-block-key=&quot;bvb6w&quot;&gt;GPU↔GPU synchronization to ensure that certain GPU operations do not occur out of order,&lt;/li&gt;&lt;li data-block-key=&quot;59ncw&quot;&gt;CPU↔GPU synchronization to ensure that we maintain a certain level of latency and resource usage in our applications.&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;gpugpu-synchronization&quot; anchor=&quot;gpugpu-synchronization&quot; data-block-key=&quot;j23xu&quot;&gt;GPU↔GPU Synchronization&lt;/h2&gt;&lt;p data-block-key=&quot;rmnki&quot;&gt;Whereas in OpenGL we could simply render to the GL_BACK buffer of the default framebuffer and then tell the system to swap the back and front buffers, with Vulkan we have to get more involved. Vulkan exposes the concept of a swapchain of images. This is essentially a collection of textures (VkImages) that are owned and managed by the swapchain and the window system integration (WSI). A typical frame in Vulkan looks something like this:&lt;/p&gt;&lt;ol&gt;&lt;li data-block-key=&quot;15m9e&quot;&gt;Acquire the index of the swapchain image to which we should render.&lt;/li&gt;&lt;li data-block-key=&quot;lxjdb&quot;&gt;Record one or more command buffers that ultimately output to the swapchain image from step 1.&lt;/li&gt;&lt;li data-block-key=&quot;hbzco&quot;&gt;Submit the command buffers from step 2 to a GPU queue for processing.&lt;/li&gt;&lt;li data-block-key=&quot;td98l&quot;&gt;Instruct the GPU presentation engine to display the final rendered swapchain image from step 3.&lt;/li&gt;&lt;li data-block-key=&quot;v5zrg&quot;&gt;Go back to step 1 and start over for the next frame.&lt;/li&gt;&lt;/ol&gt;&lt;p data-block-key=&quot;o2ada&quot;&gt;This may look innocuous at first glance but let&amp;#x27;s delve deeper.&lt;/p&gt;&lt;h3 id=&quot;a-day-at-the-races&quot; anchor=&quot;a-day-at-the-races&quot; data-block-key=&quot;ir5fl&quot;&gt;A day at the races&lt;/h3&gt;&lt;p data-block-key=&quot;girc5&quot;&gt;In step 1 we are asking the WSI to tell us the index of the next available swapchain image that we may render into. Now, just because this function tells us (and the CPU) that, for example, image index 1 is the image we should use as our render target, it does not mean that the GPU is actually ready to write to this image right now.&lt;/p&gt;&lt;p data-block-key=&quot;nupfh&quot;&gt;It is important to note that we are operating on two distinct timelines. There is the CPU timeline that we are familiar with when writing applications. Then there is also the GPU timeline on which the GPU processes the work that we give to it (from the CPU timeline).&lt;/p&gt;&lt;p data-block-key=&quot;gsq63&quot;&gt;In the case of acquiring a swapchain image index, we are actually asking the GPU to look into the future a little bit and tell us which image index &lt;b&gt;will become&lt;/b&gt; the next image to become ready for writing. However, when we call the function to acquire this image index, the GPU presentation engine may well still be reading from the image in question in order to display its contents from an earlier frame.&lt;/p&gt;&lt;p data-block-key=&quot;vf7ye&quot;&gt;Many people coming new to Vulkan (myself included) make the mistake of thinking that acquiring the swapchain image index means the image is ready to go right now. It&amp;#x27;s not!&lt;/p&gt;&lt;p data-block-key=&quot;5dm71&quot;&gt;In step 2, we are entirely operating on the CPU timeline and we can safely record command buffers without fear of trampling over anything happening on the GPU.&lt;/p&gt;&lt;p data-block-key=&quot;fca6n&quot;&gt;The same is true in step 3. We can happily submit the command buffers which will render to our swapchain image. However, this does then trigger the problem. If the GPU presentation engine is still busy reading from the swapchain image when suddenly along comes a bundle of work that tells the GPU to render into that same image we have a potential problem. GPUs are thirsty beasts and are massively parallel machines that like to do as much as possible concurrently. Without some form of synchronization, it is clear to see that, if the GPU begins processing the command buffers, it could easily lead to a situation where the presentation engine could be reading data at the same time it is being written to by another GPU thread. Say hello to our old friend undefined behaviour!&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;01-overlapping-gpu-workloads.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/01-overlapping-gpu-workloads.original.png&quot; class=&quot;01-overlapping-gpu-workloads.png&quot; alt=&quot;01-overlapping-gpu-workloads.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;otz4n&quot;&gt;It is now clear that we need some mechanism to instruct the GPU to not process these command buffers until the GPU presentation engine is done reading from the swapchain image we are rendering to.&lt;/p&gt;&lt;p data-block-key=&quot;szima&quot;&gt;The solution for synchronising blocks of GPU work in Vulkan is a &lt;b&gt;semaphore&lt;/b&gt; (VkSemaphore).&lt;/p&gt;&lt;p data-block-key=&quot;a6ch8&quot;&gt;The way it works is that in our application&amp;#x27;s initialisation code, we create a semaphore for the purposes of forcing the command buffer processing to begin only once the GPU presentation engine tells us it is done reading from the swapchain image it told us to use.&lt;/p&gt;&lt;p data-block-key=&quot;zdctk&quot;&gt;With this semaphore in hand, we can tell the GPU to switch it to a &amp;quot;signalled&amp;quot; state when the presentation engine is done reading from the image. The other half of the problem is solved when we submit the render command buffers to the GPU by handing the same semaphore to the call to vkQueueSubmit().&lt;/p&gt;&lt;p data-block-key=&quot;6agru&quot;&gt;We now have this kind of setup:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;lovzq&quot;&gt;At initialisation, create a semaphore (&lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCreateSemaphore.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkCreateSemaphore&lt;/a&gt;) in the unsignalled state.&lt;/li&gt;&lt;li data-block-key=&quot;ipc3l&quot;&gt;Pass the above semaphore to &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkAcquireNextImageKHR.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkAcquireNextImageKHR&lt;/a&gt; as the semaphore argument so that it is signalled when the image is ready for writing.&lt;/li&gt;&lt;li data-block-key=&quot;ua0eg&quot;&gt;Pass the above semaphore to &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkQueueSubmit.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkQueueSubmit&lt;/a&gt; (as one of the pWaitSemaphore arguments of the VkSubmitInfo struct) so that this set of command buffers is deferred until the semaphore is signalled.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;02-ordering-present-then-render.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/02-ordering-present-then-render.original.png&quot; class=&quot;02-ordering-present-then-render.png&quot; alt=&quot;02-ordering-present-then-render.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;sa0p9&quot;&gt;Phew, we&amp;#x27;re all done right? Nope, sadly not. Read on to see what else can go wrong and how to solve it.&lt;/p&gt;&lt;h3 id=&quot;im-not-ready-to-show-you-my-painting&quot; anchor=&quot;im-not-ready-to-show-you-my-painting&quot; data-block-key=&quot;m4ipi&quot;&gt;I&amp;#x27;m not ready to show you my painting&lt;/h3&gt;&lt;p data-block-key=&quot;9wk18&quot;&gt;We have solved the race condition on the GPU of preventing the start of the rendering from clobbering the swapchain image whilst the presentation engine may still be reading from it. However, there is currently nothing to prevent the request to begin the presentation of the swapchain image whilst the rendering is still going on!&lt;/p&gt;&lt;p data-block-key=&quot;bh2c9&quot;&gt;That is, we have solved the potential race between steps 1 and 3, but there is another race between steps 3 and 4. Luckily the problem is at heart exactly the same. We need to stop some incoming GPU work (the present request in step 4) from stepping on the toes of the already ongoing rendering work from step 3. That is, we need another application of GPU↔GPU synchronization which we know we can do with a semaphore.&lt;/p&gt;&lt;p data-block-key=&quot;cdoxr&quot;&gt;To solve this race condition we use the following approach:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;waonv&quot;&gt;At initialisation, create another unsignalled semaphore.&lt;/li&gt;&lt;li data-block-key=&quot;kyyds&quot;&gt;In step 3 when we submit the command buffers for rendering, we pass in the semaphore to vkQueueSubmit as one of the pSignalSemaphores arguments.&lt;/li&gt;&lt;li data-block-key=&quot;fsjx7&quot;&gt;In step 4 we then pass this same semaphore to the call to &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkQueuePresentKHR.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkQueuePresentKHR&lt;/a&gt; as one of the pWaitSemaphores arguments.&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;f756j&quot;&gt;This works in a completely analogous way to the first problem that we solved. When we submit the render command buffers for processing, this second semaphore is unsignalled. When the command buffers finish execution, the GPU will transition the semaphore to the signalled state. The call to vkQueuePresentKHR has been configured to ensure the presentation engine waits for this condition to be true before beginning whatever work it needs to do to get that image on to our screen.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;03-ordering-render-then-present.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/03-ordering-render-then-present.original.png&quot; class=&quot;03-ordering-render-then-present.png&quot; alt=&quot;03-ordering-render-then-present.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;3kj57&quot;&gt;With the above two race conditions brought under control, we can now safely loop around the sequence of steps 1-4 as many times as we like.&lt;/p&gt;&lt;p data-block-key=&quot;5kfnm&quot;&gt;Well, almost. There is a slight subtlety in that the swapchain has N frames (typically 3 or so) but so far we have only created a single semaphore for the presentation→render ordering, and a second single semaphore for the render→presentation ordering. Usually however, we do not want to render and present a single image and then wait around for the presentation to be done before starting over, as that is a big waste of cycles on both the CPU and GPU sides.&lt;/p&gt;&lt;p data-block-key=&quot;7i6i1&quot;&gt;As a side note, many Vulkan examples in tutorials do this by introducing a call to &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkDeviceWaitIdle.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkDeviceWaitIdle&lt;/a&gt; or &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkQueueWaitIdle.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkQueueWaitIdle&lt;/a&gt; somewhere in their main loop. This is fine for learning Vulkan and its concepts but to get full performance we want to go further into allowing the CPU and the GPU to work concurrently.&lt;/p&gt;&lt;p data-block-key=&quot;zj9mi&quot;&gt;One thing that we can do is to create enough semaphores such that we have one each for every frame that we wish to have &amp;quot;in flight&amp;quot; at any time and for each of the 2 required synchronization points. We can then use the i&amp;#x27;th pair of semaphores for the i&amp;#x27;th in-flight frame and when we get to the N&amp;#x27;th in-flight frame we loop back to the 0&amp;#x27;th pair of semaphores in a round robin fashion.&lt;/p&gt;&lt;p data-block-key=&quot;07wc7&quot;&gt;This then allows us to get potentially N frames ahead of the GPU on the CPU timeline. This, unfortunately, opens up our next can of worms.&lt;/p&gt;&lt;h2 id=&quot;cpugpu-synchronization&quot; anchor=&quot;cpugpu-synchronization&quot; data-block-key=&quot;vl93a&quot;&gt;CPU↔GPU Synchronization&lt;/h2&gt;&lt;p data-block-key=&quot;e15cz&quot;&gt;So far we have shown that using semaphores when enqueuing work for the GPU allows us to correctly order the work done on the GPU timeline. We have briefly mentioned that this does nothing to keep the CPU in sync with the GPU. As it stands right now the CPU is free to schedule as much work in advance as we like (assuming sufficient available resources). This has a couple of issues though:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;9zuo0&quot;&gt;The more frames of work in advance the CPU schedules work for the GPU, the more resources we need to hold command buffers, semaphores etc. - not to mention the GPU resources to which the command buffers refer, such as buffers and textures. These GPU resources all have to be kept alive as long as any command buffers are referencing them.&lt;/li&gt;&lt;li data-block-key=&quot;suum0&quot;&gt;The second issue is that the further the CPU gets ahead of the GPU the further our simulation state gets ahead of what we see. That means, the more frames ahead we allow the CPU to get, the higher is our latency. Some latency can be good in that if we have a frame or two queued up already, a frame that then takes a bit longer to prepare can be absorbed unnoticed. However, too much latency and our application feels sluggish and unnatural to use as it takes too long for our input to be responded to and for us to see the results of that on screen.&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;q906r&quot;&gt;It is therefore essential to have a good handle on our system&amp;#x27;s latency which in this case means the number of frames we allow to be &amp;quot;in flight&amp;quot; at any one time. That is, the number of frames worth of command buffers that have been submitted to the GPU queues and are being recorded at the current time. A common choice here is to allow 2-3 frames to be in flight at once. Bear in mind that this also depends upon other factors such as your display&amp;#x27;s refresh rate. If you are running on a high refresh rate display at say 240Hz, then each frame is only around for 1/4 of the time of a &amp;quot;standard&amp;quot; 60Hz display. If this is the case, you may wish to increase the number of frames in flight to compensate.&lt;/p&gt;&lt;p data-block-key=&quot;w88pa&quot;&gt;Let&amp;#x27;s parameterise the max number of frames that the CPU can get ahead as MAX_FRAMES_IN_FLIGHT. From our discussions in the previous sections we know that if we can keep the CPU from getting ahead by only MAX_FRAMES_IN_FLIGHT frames, then we will only need MAX_FRAMES_IN_FLIGHT semaphores for each use of a semaphore within a frame.&lt;/p&gt;&lt;p data-block-key=&quot;b2kow&quot;&gt;So now the question is how do we stop the CPU from racing ahead of the GPU? Specifically we need a way to make the CPU timeline wait until the GPU timeline indicates that it is done with processing a frame. In Vulkan, the answer to this is a fence (&lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkFence.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;VkFence&lt;/a&gt;). Conceptually this is how we can structure a frame with fences to get the desired result (ignoring the use of semaphores for GPU↔GPU synchronization):&lt;/p&gt;&lt;ol&gt;&lt;li data-block-key=&quot;vz2kz&quot;&gt;In the application initialisation, create MAX_FRAMES_IN_FLIGHT fence objects in the signalled state.&lt;/li&gt;&lt;li data-block-key=&quot;hnus0&quot;&gt;Force the CPU timeline to wait until the fence for this frame becomes signalled or continue immediately if it is the first frame and the fence is already signalled (&lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkWaitForFences.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkWaitForFences&lt;/a&gt;).&lt;/li&gt;&lt;li data-block-key=&quot;dnxmk&quot;&gt;Reset the fence to the unsignalled state so that we can wait for it again in the future (&lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkResetFences.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkResetFences&lt;/a&gt;).&lt;/li&gt;&lt;li data-block-key=&quot;eqadz&quot;&gt;Acquire the swapchain image index (as before).&lt;/li&gt;&lt;li data-block-key=&quot;umx0y&quot;&gt;Record and submit the command buffers to perform the rendering for this frame. When it is time to submit the command buffers to the GPU queue, we can pass in the fence for this frame as the final argument to &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkQueueSubmit.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkQueueSubmit&lt;/a&gt;. Just as with a semaphore, when the GPU queue finishes processing this command buffer submission, it will transition the fence to the signalled state.&lt;/li&gt;&lt;li data-block-key=&quot;ru41f&quot;&gt;Issue a GPU command to present the completed swapchain image (as before).&lt;/li&gt;&lt;li data-block-key=&quot;8mt18&quot;&gt;Go to step 2 and use the next fence and (set of semaphores).&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;04-synchronising-cpu-and-gpu.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/04-synchronising-cpu-and-gpu.original.png&quot; class=&quot;04-synchronising-cpu-and-gpu.png&quot; alt=&quot;04-synchronising-cpu-and-gpu.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;iyr8e&quot;&gt;With this approach, the CPU timeline can only get at most MAX_FRAMES_IN_FLIGHT ahead of the GPU before the call to vkWaitForFences in step 2 forces it to wait for the corresponding fence to become signalled by the GPU. This is when it completes command buffer submission that went along with this fence.&lt;/p&gt;&lt;p data-block-key=&quot;532u5&quot;&gt;Making use of both fences and semaphores allows us to nicely keep both the CPU and the GPU timelines making progress without races (between rendering and presentation) and without the CPU running away from us. These two synchronization primitives, fences and semaphores, solve similar but different problems:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;ul&gt;&lt;li data-block-key=&quot;pelu5&quot;&gt;A &lt;b&gt;VkFence&lt;/b&gt; is a synchronization primitive to allow the keeping of the GPU and CPU timelines in pace.&lt;/li&gt;&lt;li data-block-key=&quot;43nrq&quot;&gt;A &lt;b&gt;VkSemaphore&lt;/b&gt; is a synchronization primitive to ensure ordering of GPU tasks.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;f3vkv&quot;&gt;It is also worth noting that a VkFence can also be queried as to its state from the CPU timeline rather than having to block until it becomes signalled (&lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetFenceStatus.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkGetFenceStatus&lt;/a&gt;). This allows your application to peek and see if a fence is signalled or not. If it is not yet signalled, your application may be able to make more use of the available time to go do something more productive than just blocking like with vkWaitFences. It all depends upon the design of your application.&lt;/p&gt;&lt;h2 id=&quot;other-considerations&quot; anchor=&quot;other-considerations&quot; data-block-key=&quot;6e2l1&quot;&gt;Other Considerations&lt;/h2&gt;&lt;h3 id=&quot;presentation-mode&quot; anchor=&quot;presentation-mode&quot; data-block-key=&quot;0t25f&quot;&gt;Presentation Mode&lt;/h3&gt;&lt;p data-block-key=&quot;utals&quot;&gt;We have seen above how we can utilise fences and semaphores to make our Vulkan applications well-behaved. It is also worth mentioning that, as an application author, you should also consider your choice of swapchain presentation mode. This is because this can heavily impact on how your application behaves and how many CPU/GPU cycles it uses. With OpenGL we would typically setup to have either:&lt;/p&gt;&lt;p data-block-key=&quot;2sud9&quot;&gt;VSync enabled rendering for tear-free display OR&lt;br/&gt; VSync disabled rendering and go as fast as you can but probably see some image tearing.&lt;/p&gt;&lt;p data-block-key=&quot;i6w41&quot;&gt;With Vulkan we can still get these configurations but there are also others that offer variations. As an example, &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPresentModeKHR.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;VK_PRESENT_MODE_MAILBOX_KHR&lt;/a&gt; allows us to have tear-free display of the currently presented image (it is vsync enabled), but we can have our application also render as fast as possible. Very briefly, the way this works is that when the presentation engine is displaying swapchain image 0, our calls to vkAcquireNextImageKHR will only return the other swapchain image indices. When we subsequently tell the GPU to present those images it will happily take the image and overwrite your previous presentation submission. When the next vertical blank occurs, the presentation engine will actually show the most up to date submitted swapchain image.&lt;/p&gt;&lt;p data-block-key=&quot;cy6ki&quot;&gt;In this manner we can render to e.g. images 1 and 2 as many times as we like so that when the presentation engine moves along, it has the most up to date representation of our application&amp;#x27;s state possible.&lt;/p&gt;&lt;p data-block-key=&quot;f9s4n&quot;&gt;Depending upon which swapchain presentation mode you request, your application could be locked to the VSync frequency or not, which in turn can lead to large differences in how much of your available CPU and GPU resources are consumed. Are they out for a leisurely stroll (VSync enabled) or sprinting (VSync disabled or mailbox mode)?&lt;/p&gt;&lt;h3 id=&quot;multiple-windows-and-swapchains&quot; anchor=&quot;multiple-windows-and-swapchains&quot; data-block-key=&quot;u3aa6&quot;&gt;Multiple Windows and Swapchains&lt;/h3&gt;&lt;p data-block-key=&quot;puaes&quot;&gt;All of the above examples have assumed we are working with a single window surface and a single swapchain. This is the common case for games but in desktop and embedded applications we may well have multiple windows or multiple screens or even multiple adapters. Vulkan, unlike OpenGL, is pretty flexible when it comes to threading. With some care, we can happily record the command buffers for different windows (swapchains) on different CPU threads. For swapchains sharing a common Vulkan device, we can even request them all to be presented in one function call rather than having to call the equivalent of swapbuffers on each of them sequentially. Once again, Vulkan and the WSI gives you the tools, it&amp;#x27;s up to you how you utilise them.&lt;/p&gt;&lt;h3 id=&quot;timeline-semaphores&quot; anchor=&quot;timeline-semaphores&quot; data-block-key=&quot;pe26d&quot;&gt;Timeline Semaphores&lt;/h3&gt;&lt;p data-block-key=&quot;lb3rf&quot;&gt;A more recent addition to Vulkan, known as timeline semaphores, allows applications to use this synchronization primitive to work like a combination of a traditional semaphore and a fence. A timeline semaphore can be used just like a traditional (binary) semaphore to order packets of GPU work correctly, but they may also be waited upon by the CPU timeline (&lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkWaitSemaphores.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkWaitSemaphores&lt;/a&gt;).  The CPU may also signal a timeline semaphore via a call to &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkSignalSemaphore.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;vkSignalSemaphore&lt;/a&gt;. If found to be supported by your Vulkan version and driver, you can use timeline semaphores to simplify your synchronization mechanisms.&lt;/p&gt;&lt;h3 id=&quot;pipeline-and-memory-barriers&quot; anchor=&quot;pipeline-and-memory-barriers&quot; data-block-key=&quot;mge4y&quot;&gt;Pipeline and Memory Barriers&lt;/h3&gt;&lt;p data-block-key=&quot;l8eq2&quot;&gt;This article has only concerned itself with the high-level or coarse synchronization requirements. Depending upon what you are doing inside of your command buffers you will also likely need to take care of synchronising access to various other resources. These include textures and buffers to ensure that different phases of your rendering are not trampling over each other. This is a large topic in itself and is covered in extreme detail by &lt;a href=&quot;https://www.khronos.org/blog/understanding-vulkan-synchronization&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;this article&lt;/a&gt; and the accompanying examples.&lt;/p&gt;&lt;h3 id=&quot;its-up-to-you&quot; anchor=&quot;its-up-to-you&quot; data-block-key=&quot;lu715&quot;&gt;It&amp;#x27;s up to you!&lt;/h3&gt;&lt;p data-block-key=&quot;93gj0&quot;&gt;A lot of what the OpenGL driver used to manage for us, is now firmly on the plate of application and library developers who wish to make use of Vulkan or other explicit modern graphics APIs. Vulkan provides us with a plethora of tools but it is up to us to decide how to make best use of them and how to map them onto the requirements of our applications. I hope that this article has helped explain some of the considerations of synchronization that you need to keep in mind when you decide to take the next step from the tutorial examples and remove that magic call to vkDeviceWaitIdle.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/synchronization-in-vulkan/&quot;&gt;Synchronization in Vulkan&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sean Harmer</dc:creator><category>3d</category></item><item><title>Shader Variants</title><link>https://www.kdab.com/shader-variants/</link><guid isPermaLink="true">https://www.kdab.com/shader-variants/</guid><description>&lt;p data-block-key=&quot;qn86o&quot;&gt;Background of Shaders One particular facet of modern graphics development that is often a pain - even for AAA games -- is shader variants! If you have bought an AAA game in recent years and wondered what the heck it is doing when it says it is compiling shaders for a long time (up to […]&lt;/p&gt;</description><pubDate>Thu, 27 Apr 2023 08:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Shader Variants&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;background-of-shaders&quot; anchor=&quot;background-of-shaders&quot; data-block-key=&quot;8e6xm&quot;&gt;Background of Shaders&lt;/h2&gt;&lt;p data-block-key=&quot;xhwum&quot;&gt;One particular facet of modern graphics development that is often a pain - even for AAA games -- is shader variants!&lt;/p&gt;&lt;p data-block-key=&quot;8xng9&quot;&gt;If you have bought an AAA game in recent years and wondered what the heck it is doing when it says it is compiling shaders for a long time (up to an hour or more for some recent PC titles on slower machines!), then this blog will explain it a little.&lt;/p&gt;&lt;p data-block-key=&quot;bax3m&quot;&gt;Modern graphics APIs (Vulkan, D3D12, Metal) like to know about everything that has to do with GPU state, up front. A large chunk of the GPU state is provided by so-called shader programs. These shader programs fill in various gaps in the graphics pipeline that used to be provided by fixed-function hardware back in the days of OpenGL 1.x.&lt;/p&gt;&lt;p data-block-key=&quot;jevk2&quot;&gt;As OpenGL (and DirectX) evolved, people wanted to do a wider range of things when processing vertices into colorful pixels on-screen. So, over time, the fixed function silicon on GPUs has gradually been replaced by more and more general purpose processors. As with CPUs, we now need to tell these processors what to do by writing small (sometimes larger), specialized programs called shader programs.&lt;/p&gt;&lt;p data-block-key=&quot;kncoo&quot;&gt;In OpenGL, we would write our shaders in the high-level GLSL language and feed that to the OpenGL driver as a string at runtime. The OpenGL driver would then compile the GLSL to GPU machine code and we could then throw big piles of vertices and other resources like textures at it and marvel at the results -- or, more likely, swear a bit and wonder why we are staring at a black window yet again.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;blackscreen.jpg&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/blackscreen.original.jpg&quot; class=&quot;blackscreen.jpg&quot; alt=&quot;blackscreen.jpg&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;428dn&quot;&gt;The necessity of including a complete compiler in the graphics driver was a huge burden for each of the GPU vendors, resulting in a great deal of overhead for them. It also led to some strange problems for developers when running code on a new platform with a different GLSL compiler in the driver and hitting new and different bugs or shortcomings.&lt;/p&gt;&lt;p data-block-key=&quot;q6py9&quot;&gt;With the advent of modern graphics APIs, there has been a move toward consuming shader code in the form of a bytecode intermediate representation, such as &lt;a href=&quot;https://www.khronos.org/spir/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;SPIR-V&lt;/a&gt;. SPIR-V is still not the final form of executable code required by the GPU silicon but it is much closer to it than GLSL and means the Vulkan drivers no longer need the entire compiler front-end.&lt;/p&gt;&lt;p data-block-key=&quot;v9c1k&quot;&gt;Tooling, such as &lt;a href=&quot;https://developer.nvidia.com/nsight-graphics&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;nSight&lt;/a&gt; and &lt;a href=&quot;https://renderdoc.org/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;RenderDoc&lt;/a&gt;, are able to decompile the SPIR-V shader code back to GLSL (or HLSL) to make it easier for you to debug your applications.&lt;/p&gt;&lt;p data-block-key=&quot;8356j&quot;&gt;The conversion from GLSL (or any other suitable language) to SPIR-V can still happen at runtime if that&amp;#x27;s what you need -- for example, in dynamic editor tools. However, for constrained applications, we can now compile the GLSL to SPIR-V up front at build time.&lt;/p&gt;&lt;p data-block-key=&quot;ghx33&quot;&gt;That&amp;#x27;s nice! We can simply add a few targets to our CMakeLists.txt and go home, right? Well, not quite.&lt;/p&gt;&lt;h2 id=&quot;the-need-for-shader-variants&quot; anchor=&quot;the-need-for-shader-variants&quot; data-block-key=&quot;w10tw&quot;&gt;The Need for Shader Variants&lt;/h2&gt;&lt;p data-block-key=&quot;faakg&quot;&gt;You see, shader developers are just as lazy as any other kinds of developers and like to reduce the amount of copy/paste coding that we have to do. So, we add optional features to our shaders that can be compiled in or out by way of pre-processor #defines, just as with C/C++.&lt;/p&gt;&lt;p data-block-key=&quot;6manf&quot;&gt;Why is this even needed, though? Well, we don&amp;#x27;t always have full control over the data that our application will be fed. Imagine a generic &lt;a href=&quot;https://www.khronos.org/gltf/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;glTF&lt;/a&gt; file viewer application. Some models that get loaded will use textures for the materials and include texture coordinates in the model&amp;#x27;s vertex data. Other models may just use vertex colors, completely leaving out texture coordinates.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;model_without_textures.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/model_without_textures.original.png&quot; class=&quot;model_without_textures.png&quot; alt=&quot;model_without_textures.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;model_with_textures.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/model_with_textures.original.png&quot; class=&quot;model_with_textures.png&quot; alt=&quot;model_with_textures.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;4cfe3&quot;&gt;To handle this, our vertex shader&amp;#x27;s prologue may look something like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-clike  line-numbers &quot;&gt;layout(location = 0) in vec3 vertexPosition;
layout(location = 1) in vec3 vertexNormal;
#ifdef TEXCOORD_0_ENABLED
layout(location = 2) in vec2 vertexTexCoord;
#endif

layout(location = 0) out vec3 normal;
#ifdef TEXCOORD_0_ENABLED
layout(location = 1) out vec2 texCoord;
#endif&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;mtxk9&quot;&gt;Then, in the main() function, we would have:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-clike  line-numbers &quot;&gt;void main()
{
#ifdef TEXCOORD_0_ENABLED
    texCoord = vertexTexCoord;
#endif
    normal = normalize((camera.view * entity.model[gl_InstanceIndex] * vec4(vertexNormal, 0.0)).xyz);
    gl_Position = camera.projection * camera.view * entity.model[gl_InstanceIndex] * vec4(vertexPosition, 1.0);
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;xx9q6&quot;&gt;The fragment shader would have similar changes to handle the cases with and without texture coordinates.&lt;/p&gt;&lt;p data-block-key=&quot;2g84q&quot;&gt;Super, so we have one set of shader source files that can handle both models with textures and models without textures. How do we compile the shaders to get these shader variants?&lt;/p&gt;&lt;p data-block-key=&quot;wal0b&quot;&gt;Just as with C/C++ we have a compiler toolchain and, similarly, we invoke the compiler with the various -D options as needed, e.g.:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-bash  line-numbers &quot;&gt;glslangValidator -o material-with-uvs.vert.spirv -DTEXCOORD_0_ENABLED material.vert    # With texture coords
glslangValidator -o material-without-uvs.vert.spirv material.vert                      # Without texture coords&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;yplbi&quot;&gt;Then, within our application, we can load the glTF model, inspect its data to see whether it uses textures, and then load the appropriate SPIR-V compiled shader.&lt;/p&gt;&lt;p data-block-key=&quot;xqogm&quot;&gt;Hooray! The job is done and we can go home now, right? Well, actually, no -- the project manager just called to say we also need to handle models that include the alpha cut-off feature and models that don&amp;#x27;t include it.&lt;/p&gt;&lt;p data-block-key=&quot;m1p9t&quot;&gt;Alpha cut-off is a feature of glTF files by which any pixels determined to have an alpha value less than some specified threshold simply get discarded. This is often used to cut away the transparent parts of quads used to render leaves of plants.&lt;/p&gt;&lt;p data-block-key=&quot;xsdmh&quot;&gt;Ok then -- let&amp;#x27;s simply repeat a process similar to that which we did for handling the presence, or absence, of texture coordinates.&lt;/p&gt;&lt;p data-block-key=&quot;f8fr2&quot;&gt;The fragment shader implementation of alpha cut-off is trivial:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-clike  line-numbers &quot;&gt;void main()
{
    vec4 baseColor = ...;
#ifdef ALPHA_CUTOFF_ENABLED
    if (baseColor.a &amp;lt; material.alphaCutoff)
        discard;
#endif
    ...
    fragColor = baseColor;
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;c3exw&quot;&gt;We can then add suitable CMake targets to compile with and without this option.&lt;/p&gt;&lt;p data-block-key=&quot;wt6o8&quot;&gt;Of course, there&amp;#x27;s a catch. We have a combinatorial explosion of feature combinations. This only gets worse when we add the next optional feature or optional features that have various settings we wish to set at compile time, such as the number of taps used when sampling from a texture to perform a Gaussian blur.&lt;/p&gt;&lt;p data-block-key=&quot;rx6p5&quot;&gt;Clearly, we do not want to have to add several thousand combinations of features as CMake targets by hand! So, what can we do?&lt;/p&gt;&lt;h2 id=&quot;exploring-the-problem&quot; anchor=&quot;exploring-the-problem&quot; data-block-key=&quot;lifj4&quot;&gt;Exploring the Problem&lt;/h2&gt;&lt;p data-block-key=&quot;p2s9y&quot;&gt;Let&amp;#x27;s consider the above combination of the texture coordinates and alpha cut-off features. Our table of features and compiler flags looks like this:&lt;/p&gt;&lt;/div&gt;


&lt;table&gt;
    
    
        &lt;thead&gt;
            &lt;tr&gt;
                
                    
                        
                        
                            &lt;th scope=&quot;col&quot;  &gt;
                                
                            &lt;/th&gt;
                        
                    
                
                    
                        
                        
                            &lt;th scope=&quot;col&quot;  &gt;
                                
                                    
                                        Tex Coord Off
                                    
                                
                            &lt;/th&gt;
                        
                    
                
                    
                        
                        
                            &lt;th scope=&quot;col&quot;  &gt;
                                
                                    
                                        Tex Coord On
                                    
                                
                            &lt;/th&gt;
                        
                    
                
            &lt;/tr&gt;
        &lt;/thead&gt;
    
    &lt;tbody&gt;
        
            
                &lt;tr&gt;
                    
                        
                            
                            
                                
                                    &lt;th scope=&quot;row&quot;  &gt;
                                        
                                            
                                                Alpha Cut-off Off
                                            
                                        
                                    &lt;/th&gt;
                                
                            
                        
                    
                        
                            
                            
                                
                                    &lt;td  &gt;
                                        
                                    &lt;/td&gt;
                                
                            
                        
                    
                        
                            
                            
                                
                                    &lt;td  &gt;
                                        
                                            
                                                -DTEXCOORD_0_ENABLED
                                            
                                        
                                    &lt;/td&gt;
                                
                            
                        
                    
                &lt;/tr&gt;
            
        
            
                &lt;tr&gt;
                    
                        
                            
                            
                                
                                    &lt;th scope=&quot;row&quot;  &gt;
                                        
                                            
                                                Alpha Cut-off On
                                            
                                        
                                    &lt;/th&gt;
                                
                            
                        
                    
                        
                            
                            
                                
                                    &lt;td  &gt;
                                        
                                            
                                                -DALPHA_CUTOFF_ENABLED
                                            
                                        
                                    &lt;/td&gt;
                                
                            
                        
                    
                        
                            
                            
                                
                                    &lt;td  &gt;
                                        
                                            
                                                -DTEXCOORD_0_ENABLED -DALPHA_CUTOFF_ENABLED
                                            
                                        
                                    &lt;/td&gt;
                                
                            
                        
                    
                &lt;/tr&gt;
            
        
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8c5qf&quot;&gt;Adding another option would add another dimension to this table. The above mentioned option of blur filter taps with, say, 3, 5, 7, or 9 taps would add a 3rd dimension to the table and increase the number of options by another factor of 4, for a total of 16 possible configurations of this one shader program.&lt;/p&gt;&lt;p data-block-key=&quot;7kl25&quot;&gt;Adding just a handful of features, we can see that it would be all too easy to end up with thousands of combinations of compiled shaders from the single set of GLSL files!&lt;/p&gt;&lt;p data-block-key=&quot;h2y66&quot;&gt;How can we solve this in a nice and extensible way?&lt;/p&gt;&lt;p data-block-key=&quot;pjqsf&quot;&gt;It is easy enough to have nested loops to iterate over the available options for each of the specified axes of variations. But what if we don&amp;#x27;t know all of the axes of variation up front? What if they vary from shader to shader? Not all shaders will care about alpha cut-off or blur filter taps, for example.&lt;/p&gt;&lt;p data-block-key=&quot;xrs61&quot;&gt;We can&amp;#x27;t simply hard-wire a set number of nested loops to iterate over the combinations in our CMake files. We need something a bit more flexible and smarter.&lt;/p&gt;&lt;p data-block-key=&quot;y9a1o&quot;&gt;Let&amp;#x27;s think about the problem in a slightly different way.&lt;/p&gt;&lt;p data-block-key=&quot;0tusx&quot;&gt;To start with, let&amp;#x27;s represent a given configuration of our option space by a vector of length N, where N is the number of options. For now, let&amp;#x27;s set this to 3, for our options we have discussed:&lt;/p&gt;&lt;ol&gt;&lt;li data-block-key=&quot;e9qr5&quot;&gt;Texture Coordinates (Off or On)&lt;/li&gt;&lt;li data-block-key=&quot;6s4vl&quot;&gt;Alpha Cut-off (Off or On)&lt;/li&gt;&lt;li data-block-key=&quot;oezs7&quot;&gt;Blur filter taps (3, 5, 7, or 9)&lt;/li&gt;&lt;/ol&gt;&lt;p data-block-key=&quot;xkhok&quot;&gt;That is, we will have a vector like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-clike &quot;&gt;[TexCoords Off, Alpha Cut-off Off, blur taps = 3]&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;o4voe&quot;&gt;To save some typing, let&amp;#x27;s now replace the wordy description of each element with a number representing the index of the option for that axis of variation:&lt;/p&gt;&lt;ol&gt;&lt;li data-block-key=&quot;hvqvm&quot;&gt;Texture Coordinates: (0 = Off, 1 = On)&lt;/li&gt;&lt;li data-block-key=&quot;skg1x&quot;&gt;Alpha Cut-off: (0 = Off, 1 = On)&lt;/li&gt;&lt;li data-block-key=&quot;1jegy&quot;&gt;Blur filter taps: (0 = 3 taps, 1 = 5 taps, 2 = 7 taps, 3 = 9 taps)&lt;/li&gt;&lt;/ol&gt;&lt;p data-block-key=&quot;j2720&quot;&gt;With this scheme in place, our above option set will be:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain &quot;&gt;[0, 0, 0]&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;k618i&quot;&gt;And the vector representing texture coordinates on, no alpha cut-off, and 7 blur filter taps option will be:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain &quot;&gt;[1, 0, 2]&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;vtgyr&quot;&gt;How does this help us? Well, it allows us to succinctly represent any combination of options; but it&amp;#x27;s even better than that. We can now easily go through the list of all possible combinations in a logical order. We begin by incrementing the final element of the vector over all possible values. Then we increment the previous element and repeat, like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;[0, 0, 0]
[0, 0, 1]
[0, 0, 2]
[0, 0, 3]
[0, 1, 0]
[0, 1, 1]
[0, 1, 2]
[0, 1, 3]
[1, 0, 0]
[1, 0, 1]
[1, 0, 2]
[1, 0, 3]
[1, 1, 0]
[1, 1, 1]
[1, 1, 2]
[1, 1, 3]&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;b2vzu&quot;&gt;Note that the total number of option combinations is just the product of the number of options in each dimension or axis of variation, e.g. 2x2x4 = 16 in this example.&lt;/p&gt;&lt;p data-block-key=&quot;5zr9k&quot;&gt;The above sequence is exactly what we would get if we had 3 nested for-loops to iterate over the options at each level. How does this help us?&lt;/p&gt;&lt;p data-block-key=&quot;kk2mu&quot;&gt;Well, looking at the above sequence of options vectors, you may well notice the similarity to plain old counting of numbers. For each &amp;quot;decimal place&amp;quot; (element in the vector), starting with the final or least significant digit, we go up through each of the available values. Then, we increment the next least significant digit and repeat.&lt;/p&gt;&lt;p data-block-key=&quot;usx3w&quot;&gt;The only difference to how we are used to counting in decimal (base 10), binary, octal, or hexadecimal is that the base of each digit is potentially different. The base for each digit is simply the number of options available for that axis of variation (e.g. the texture coordinates can only be on or off (base = 2)). It&amp;#x27;s the same for the alpha cut-off. The blur taps option has a base of 4 (4 possible options).&lt;/p&gt;&lt;p data-block-key=&quot;fiwap&quot;&gt;We know how many combinations we need in total and we know that each combination can be represented by a vector that acts like a variable-base number. Therefore, if we can find a way to convert from a decimal number to the corresponding combination vector, we are in a good situation, as we will have converted a recursive approach (nested for-loops) into a flat linear approach. All we would need would be something like this pseudo-code:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-clike  line-numbers &quot;&gt;for i = 0 to combination_count
   option_vector = calculate_option_vector(i)
   output_compiler_options(option_vector)
next i&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;4ewbm&quot;&gt;So how do we do this?&lt;/p&gt;&lt;h2 id=&quot;a-solution&quot; anchor=&quot;a-solution&quot; data-block-key=&quot;4sd1q&quot;&gt;A Solution&lt;/h2&gt;&lt;p data-block-key=&quot;hh4v0&quot;&gt;To convert a decimal number into a different base system is fairly easy. The process is described well at &lt;a href=&quot;https://www.tutorialspoint.com/computer_logical_organization/number_system_conversion.htm&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;https://www.tutorialspoint.com/computer_logical_organization/number_system_conversion.htm&lt;/a&gt;, where they give an example of converting from decimal to binary.&lt;/p&gt;&lt;p data-block-key=&quot;lvujx&quot;&gt;All we have to do, in our case, is use a base that differs for each digit of our combination vector. However, before we show this, we need a way to specify the options for each shader that we wish to consider. We have done this by way of a simple JSON file, for now. Here is an example showing our above case for these options as applied to the fragment shader, but only the texture coordinates and alpha cut-off for the vertex shader. This is just an example for illustration. In reality, the vertex shader has nothing to do with alpha cut-off and our simple shaders do not do anything with the blur tap option at all:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-json  line-numbers &quot;&gt;{
    &amp;quot;options&amp;quot;: [
        {
            &amp;quot;name&amp;quot;: &amp;quot;hasTexCoords&amp;quot;,
            &amp;quot;define&amp;quot;: &amp;quot;TEXCOORD_0_ENABLED&amp;quot;
        },
        {
            &amp;quot;name&amp;quot;: &amp;quot;enableAlphaCutoff&amp;quot;,
            &amp;quot;define&amp;quot;: &amp;quot;ALPHA_CUTOFF_ENABLED&amp;quot;
        },
        {
            &amp;quot;name&amp;quot;: &amp;quot;taps&amp;quot;,
            &amp;quot;define&amp;quot;: &amp;quot;BLUR_TAPS&amp;quot;,
            &amp;quot;values&amp;quot;: [3, 5, 7, 9]
        }
    ],
    &amp;quot;shaders&amp;quot;: [
        {
            &amp;quot;filename&amp;quot;: &amp;quot;materials.vert&amp;quot;,
            &amp;quot;options&amp;quot;: [0, 1]
        },
        {
            &amp;quot;filename&amp;quot;: &amp;quot;materials.frag&amp;quot;,
            &amp;quot;options&amp;quot;: [0, 1, 2]
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8phex&quot;&gt;The default in our system, if no explicit options are provided in the JSON file, is defined (on) or not defined (off).&lt;/p&gt;&lt;p data-block-key=&quot;s4lrf&quot;&gt;Each input shader file section then specifies which of the options it cares about. So, in this example, the fragment shader considers all 3 options and will have 16 variants compiled.&lt;/p&gt;&lt;p data-block-key=&quot;g862s&quot;&gt;In order to generate the possible build combinations, we have written a small Ruby script to implement the necessary logic. Why Ruby? Because I couldn&amp;#x27;t face trying to do the necessary math in CMake&amp;#x27;s scripting language and Ruby is lovely!&lt;/p&gt;&lt;p data-block-key=&quot;3el1s&quot;&gt;The core of the script that implements the decimal to a variable-base number (combination vector) is pretty simple:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-ruby  line-numbers &quot;&gt;def calculate_digits(bases, index)
  digits = Array.new(bases.size, 0)
  base_index = digits.size - 1
  current_value = index
  while current_value != 0
    quotient, remainder = current_value.divmod(bases[base_index])
    digits[base_index] = remainder
    current_value = quotient
    base_index -= 1
  end
  return digits
end&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gcpnt&quot;&gt;In the above code, the &lt;code&gt;bases &lt;/code&gt;argument is a vector representing the base of each digit in the final combination vector. Here, bases = [2, 2, 4]. We then loop over the decimal number, performing the &lt;code&gt;divmod&lt;/code&gt; operation at each step to find the value of each digit in our combination vector. When we have reduced the input decimal number to 0, we are done. This is exactly analogous to the decimal to binary conversion linked above but for variable base at each digit.&lt;/p&gt;&lt;p data-block-key=&quot;znwvc&quot;&gt;With the resulting combination vector in hand, it is simple for us to then look up the corresponding compiler -D option for that selection and output that into a JSON string. Here is an example of the output of running the ruby script against the above configuration file:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-json  line-numbers &quot;&gt;{
  &amp;quot;variants&amp;quot;: [
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.vert&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials.vert.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.vert&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DALPHA_CUTOFF_ENABLED&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_alpha_cutoff_enabled.vert.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.vert&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled.vert.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.vert&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED -DALPHA_CUTOFF_ENABLED&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled_alpha_cutoff_enabled.vert.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DBLUR_TAPS=3&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_blur_taps_3.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DBLUR_TAPS=5&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_blur_taps_5.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DBLUR_TAPS=7&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_blur_taps_7.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DBLUR_TAPS=9&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_blur_taps_9.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DALPHA_CUTOFF_ENABLED -DBLUR_TAPS=3&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_alpha_cutoff_enabled_blur_taps_3.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DALPHA_CUTOFF_ENABLED -DBLUR_TAPS=5&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_alpha_cutoff_enabled_blur_taps_5.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DALPHA_CUTOFF_ENABLED -DBLUR_TAPS=7&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_alpha_cutoff_enabled_blur_taps_7.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DALPHA_CUTOFF_ENABLED -DBLUR_TAPS=9&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_alpha_cutoff_enabled_blur_taps_9.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED -DBLUR_TAPS=3&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled_blur_taps_3.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED -DBLUR_TAPS=5&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled_blur_taps_5.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED -DBLUR_TAPS=7&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled_blur_taps_7.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED -DBLUR_TAPS=9&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled_blur_taps_9.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED -DALPHA_CUTOFF_ENABLED -DBLUR_TAPS=3&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled_alpha_cutoff_enabled_blur_taps_3.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED -DALPHA_CUTOFF_ENABLED -DBLUR_TAPS=5&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled_alpha_cutoff_enabled_blur_taps_5.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED -DALPHA_CUTOFF_ENABLED -DBLUR_TAPS=7&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled_alpha_cutoff_enabled_blur_taps_7.frag.spv&amp;quot;
    },
    {
      &amp;quot;input&amp;quot;: &amp;quot;materials.frag&amp;quot;,
      &amp;quot;defines&amp;quot;: &amp;quot;-DTEXCOORD_0_ENABLED -DALPHA_CUTOFF_ENABLED -DBLUR_TAPS=9&amp;quot;,
      &amp;quot;output&amp;quot;: &amp;quot;materials_texcoord_0_enabled_alpha_cutoff_enabled_blur_taps_9.frag.spv&amp;quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;ku2h9&quot;&gt;If you are interested, this is the full script:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-ruby  line-numbers &quot;&gt;require &amp;#x27;json&amp;#x27;
require &amp;#x27;pp&amp;#x27;

def expand_options(data)
  # Expand the options so that if no explicit options are specified we default
  # to options where the #define symbole is defined or not
  data[:options].each do |option|
    if !option.has_key?(:values)
      option[:values] = [:nil, :defined]
    end
    option[:count] = option[:values].size
  end
end

def extract_options(data, shader)
  shader_options = Hash.new
  shader_options[:options] = Array.new
  shader[:options].each do |option_index|
    shader_options[:options].push data[:options][option_index]
  end
  # STDERR.puts &amp;quot;Options for shader:&amp;quot;
  # STDERR.puts shader_options
  return shader_options
end

def find_bases(data)
  bases = Array.new(data[:options].size)
  (0..(data[:options].size - 1)).each do |index|
    bases[index] = data[:options][index][:count]
  end
  return bases
end

def calculate_steps(bases)
  step_count = bases[0]
  (1..(bases.size - 1)).each do |index|
    step_count *= bases[index]
  end
  return step_count
end

# Calculate the number for &amp;quot;index&amp;quot; in our variable-bases counting system
def calculate_digits(bases, index)
  digits = Array.new(bases.size, 0)
  base_index = digits.size - 1
  current_value = index
  while current_value != 0
    quotient, remainder = current_value.divmod(bases[base_index])
    digits[base_index] = remainder
    current_value = quotient
    base_index -= 1
  end
  return digits
end

def build_options_string(data, selected_options)
  str = &amp;quot;&amp;quot;
  selected_options.each_with_index do |selected_option, index|
    # Don&amp;#x27;t add anything if option is disabled
    next if selected_option == :nil

    # If we have the special :defined option, then we add a -D option
    if selected_option == :defined
      str += &amp;quot; -D#{data[:options][index][:define]}&amp;quot;
    else
      str += &amp;quot; -D#{data[:options][index][:define]}=#{selected_option}&amp;quot;
    end
  end
  return str.strip
end

def build_filename(shader, data, selected_options)
  str = File.basename(shader[:filename], File.extname(shader[:filename]))
  selected_options.each_with_index do |selected_option, index|
    # Don&amp;#x27;t add anything if option is disabled
    next if selected_option == :nil

    # If we have the special :defined option, then we add a section for that option
    if selected_option == :defined
      str += &amp;quot;_#{data[:options][index][:define].downcase}&amp;quot;
    else
      str += &amp;quot;_#{data[:options][index][:define].downcase}_#{selected_option.to_s}&amp;quot;
    end
  end
  str += File.extname(shader[:filename]) + &amp;quot;.spv&amp;quot;
  return str
end

# Load the configuration data and expand default options
if ARGV.size != 1
  puts &amp;quot;No filename specified.&amp;quot;
  puts &amp;quot;  Usage: generate_shader_variants.rb &amp;quot;
  exit(1)
end

variants_filename = ARGV[0]
file = File.read(variants_filename)
data = JSON.parse(file, { symbolize_names: true })
expand_options(data)

# Prepare a hash to output as json at the end
output_data = Hash.new
output_data[:variants] = Array.new

data[:shaders].each do |shader|
  # STDERR.puts &amp;quot;Processing #{shader[:filename]}&amp;quot;

  # Copy over the options referenced by this shader to a local hash that we can operate on
  shader_options = extract_options(data, shader)

  # Create a &amp;quot;digits&amp;quot; array we can use for counting. Each element (digit) in the array
  # will correspond to an option in the loaded data configuration. The values each
  # digit can take are those specified in the &amp;quot;values&amp;quot; array for that option.
  #
  # The number of steps we need to take to count from &amp;quot;0&amp;quot; to the maximum value is the
  # product of the number of options for each &amp;quot;digit&amp;quot; (option).
  bases = find_bases(shader_options)
  # STDERR.puts &amp;quot;Bases = #{bases}&amp;quot;
  step_count = calculate_steps(bases)
  # STDERR.puts &amp;quot;There are #{step_count} combinations of options&amp;quot;

  # Count up through out range of options
  (0..(step_count - 1)).each do |index|
    digits = calculate_digits(bases, index)

    selected_options = Array.new(bases.size)
    (0..(bases.size - 1)).each do |digit_index|
      settings = data[:options][digit_index]
      setting_index = digits[digit_index]
      selected_options[digit_index] = settings[:values][setting_index]
    end

    # Construct the options to pass to glslangValidator
    defines = build_options_string(shader_options, selected_options)
    output_filename = build_filename(shader, shader_options, selected_options)

    # STDERR.puts &amp;quot;  Step #{index}: #{digits}, selected_options = #{selected_options}, defines = #{defines}, output_filename = #{output_filename}&amp;quot;

    variant = { input: shader[:filename], defines: defines, output: output_filename }
    output_data[:variants].push variant
  end

  # STDERR.puts &amp;quot;&amp;quot;
end

puts output_data.to_json&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;integrating-into-the-build-system&quot; anchor=&quot;integrating-into-the-build-system&quot; data-block-key=&quot;z5aub&quot;&gt;Integrating into the Build System&lt;/h2&gt;&lt;p data-block-key=&quot;iiwzc&quot;&gt;CMake is now able to &lt;a href=&quot;https://cmake.org/cmake/help/latest/command/string.html#json&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;read and parse JSON documents&lt;/a&gt; -- a fact that I didn&amp;#x27;t know at first. This means that we can quite conveniently ask our build system to execute our Ruby script as an external process at configure time, capture the JSON output as shown above, iterate over the generated combinations, and add a build target for each one.&lt;/p&gt;&lt;p data-block-key=&quot;9xmgr&quot;&gt;The cut-down code for doing this is:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cmake  line-numbers &quot;&gt;function(CompileShaderVariants target variants_filename)
    # Run the helper script to generate json data for all configured shader variants
    execute_process(
        COMMAND ruby ${CMAKE_SOURCE_DIR}/generate_shader_variants.rb ${variants_filename}
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        OUTPUT_VARIABLE SHADER_VARIANTS
        RESULT_VARIABLE SHADER_VARIANT_RESULT
    )

    if(NOT SHADER_VARIANT_RESULT EQUAL &amp;quot;0&amp;quot;)
        message(NOTICE ${SHADER_VARIANT_RESULT})
        message(FATAL_ERROR &amp;quot;Failed to generate shader variant build targets for &amp;quot; ${variants_filename})
    endif()

    string(JSON VARIANT_COUNT LENGTH ${SHADER_VARIANTS} variants)
    message(NOTICE &amp;quot;Generating &amp;quot; ${VARIANT_COUNT} &amp;quot; shader variants from &amp;quot; ${variants_filename})

    # Adjust count as loop index goes from 0 to N
    MATH(EXPR VARIANT_COUNT &amp;quot;${VARIANT_COUNT} - 1&amp;quot;)

    foreach(VARIANT_INDEX RANGE ${VARIANT_COUNT})
        string(JSON CURRENT_INTPUT_FILENAME GET ${SHADER_VARIANTS} variants ${VARIANT_INDEX} input)
        string(JSON CURRENT_OUTPUT_FILENAME GET ${SHADER_VARIANTS} variants ${VARIANT_INDEX} output)
        string(JSON CURRENT_DEFINES GET ${SHADER_VARIANTS} variants ${VARIANT_INDEX} defines)

        set(SHADER_TARGET_NAME &amp;quot;${target}_${CURRENT_OUTPUT_FILENAME}&amp;quot;)
        CompileShader(${SHADER_TARGET_NAME} ${CURRENT_INTPUT_FILENAME} ${CURRENT_OUTPUT_FILENAME} ${CURRENT_DEFINES})
    endforeach(VARIANT_INDEX RANGE ${VARIANT_COUNT})
endfunction()&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;pdirf&quot;&gt;Here, CompileShader() call is another helper function that just invokes the glslangValidator GLSL-&amp;gt;SPIR-V compiler with the specified options.&lt;/p&gt;&lt;p data-block-key=&quot;68pjc&quot;&gt;This nicely takes care of generating all of the required shader variants that will be compiled with correct dependencies on the source GLSL files. To ensure that the targets get updated if the input JSON configuration file changes, we can add the following snippet to the above function:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cmake  line-numbers &quot;&gt;# Re-run cmake configure step if the variants file changes
set_property(
    DIRECTORY
    APPEND
    PROPERTY CMAKE_CONFIGURE_DEPENDS ${variants_filename}
)&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8bwrw&quot;&gt;Now, if we edit the JSON configuration file that contains the options, CMake will automatically re-run and generate the targets.&lt;/p&gt;&lt;p data-block-key=&quot;d0xj1&quot;&gt;On the C++ runtime side of things, we have some logic to construct the appropriate shader file name for the compiled SPIR-V shader matching the options needed by whatever model we are rendering.&lt;/p&gt;&lt;p data-block-key=&quot;cd3op&quot;&gt;In the future, we may make this part more reusable by making it read in the same JSON configuration file used to create the shader variants.&lt;/p&gt;&lt;h2 id=&quot;wrapping-up&quot; anchor=&quot;wrapping-up&quot; data-block-key=&quot;ngqid&quot;&gt;Wrapping Up&lt;/h2&gt;&lt;p data-block-key=&quot;t13ue&quot;&gt;So, going back to where we started: how does all of this tie into your PC&amp;#x27;s spending an hour compiling shaders when we have shown here how to compile them at application build time?&lt;/p&gt;&lt;p data-block-key=&quot;gg4u6&quot;&gt;It all goes back to SPIR-V&amp;#x27;s just being a bytecode intermediate representation. Before the GPU can execute these shaders, it needs to do a final compilation step to convert the SPIR-V to actual machine code. In a modern graphics API, this is done when we create a so-called &amp;quot;graphics pipeline.&amp;quot; At this point, we have to specify pretty much all GPU state, which then gets baked into a binary blob along with the shader code by the driver. This binary blob is both GPU-vendor and driver-version specific. So, it cannot be built at application build time but, rather, has to be done on the actual machine on which it will execute.&lt;/p&gt;&lt;p data-block-key=&quot;jezbh&quot;&gt;The first time you run such a game or other application, it will often loop through all of the shader variants and compile a graphics pipeline for each one. These then get cached to disk for use on subsequent runs. If you change your GPU or (more likely) the driver version, then this cache might get invalidated and you&amp;#x27;d have to sit through this process once again.&lt;/p&gt;&lt;p data-block-key=&quot;oyz55&quot;&gt;For systems with known hardware and drivers, this whole process can be performed as part of the build step. This is why consoles such as the PlayStation 5 do not have to do this lengthy shader compiling step, while we wait there and watch.&lt;/p&gt;&lt;p data-block-key=&quot;6gl78&quot;&gt;There is some work going on in Khronos at present, in the shape of &lt;a href=&quot;https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_shader_object.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;VK_ext_shader_object&lt;/a&gt;, to try to get back to a more dynamic-shader friendly way of doing things, in which the driver takes care of much of this compiling and caching for us. As with all things in computer science though, it will be a trade-off.&lt;/p&gt;&lt;p data-block-key=&quot;3tzcj&quot;&gt;Thank you for reading about what turned out to be a nice little excursion of simplifying a problem by changing it from recursive to linear and learning about converting between numbers of different bases.&lt;/p&gt;&lt;p data-block-key=&quot;p4xwr&quot;&gt;If you would like to learn more about modern 3D graphics or get some help on your own projects, then please get in touch.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/shader-variants/&quot;&gt;Shader Variants&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sean Harmer</dc:creator><category>3d</category><category>c++</category><category>performance</category></item><item><title>FMA Woes</title><link>https://www.kdab.com/fma-woes/</link><guid isPermaLink="true">https://www.kdab.com/fma-woes/</guid><description>&lt;p data-block-key=&quot;322yh&quot;&gt;Given a strictly positive integer i, this code will calculate i+1 &amp;quot;equally spaced&amp;quot; values between 1 and 0: If you&amp;#x27;re looking for a trap, this does actually work for any i &amp;gt; 0. One can verify it experimentally; run the code with i from 1 to INT_MAX. For simplicity, just consider the case j = […]&lt;/p&gt;</description><pubDate>Fri, 24 Feb 2023 07:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;FMA Woes&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;fvwin&quot;&gt;Given a strictly positive integer &lt;code&gt;i&lt;/code&gt;, this code will calculate i+1 &amp;quot;equally spaced&amp;quot; values between 1 and 0:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;const double scale = 1.0 / i;

for (int j = 0; j &amp;lt;= i; ++j) {
    const double r = 1.0 - j * scale;
    assert(r &amp;gt;= 0);
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;xkyyi&quot;&gt;If you&amp;#x27;re looking for a trap, this does actually work for any i &amp;gt; 0. One can verify it experimentally; run the code with &lt;code&gt;i&lt;/code&gt; from 1 to &lt;code&gt;INT_MAX&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;08lb2&quot;&gt;For simplicity, just consider the case &lt;code&gt;j = i&lt;/code&gt; (the maximum for &lt;code&gt;j&lt;/code&gt;, in the last loop of iteration above):&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;const double scale = 1.0 / i;
const double r = 1.0 - i * scale;
assert(r &amp;gt;= 0);&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;p090e&quot;&gt;You can see it running &lt;a href=&quot;https://gcc.godbolt.org/z/zY7o57xYf&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;here on Compiler Explorer&lt;/a&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;qgfed&quot;&gt;Some time later, you upgrade your compiler and the code doesn&amp;#x27;t work any more.&lt;/p&gt;&lt;p data-block-key=&quot;ry9cs&quot;&gt;Another example: given two double numbers &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; such that &lt;code&gt;abs(a) &amp;gt;= abs(b)&lt;/code&gt;, then this code,&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;const double result = std::sqrt(a*a - b*b);&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;kxsmz&quot;&gt;will work; it will never pass a negative argument to &lt;code&gt;sqrt&lt;/code&gt; until you upgrade your compiler. Then, it will start failing...&lt;/p&gt;&lt;h2 id=&quot;what-did-the-compiler-do-to-you&quot; anchor=&quot;what-did-the-compiler-do-to-you&quot; data-block-key=&quot;21eu8&quot;&gt;What Did the Compiler Do to You?&lt;/h2&gt;&lt;p data-block-key=&quot;32qeu&quot;&gt;In an least one interesting case (&lt;a href=&quot;https://releases.llvm.org/14.0.0/tools/clang/docs/ReleaseNotes.html#floating-point-support-in-clang&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Clang 14, now shipped with the last XCode on Apple&lt;/a&gt;), started recognizing floating-point expressions, such as,&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;x * y + z&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;s1cy6&quot;&gt;and started to automatically turn them into &lt;a href=&quot;https://en.wikipedia.org/wiki/Multiply%E2%80%93accumulate_operation&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;fused multiply-add (FMA) instructions&lt;/a&gt;:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;yxh8n&quot;&gt;The MAC operation modifies an accumulator&lt;/p&gt;&lt;p data-block-key=&quot;9skp9&quot;&gt;&lt;b&gt;a: a ← a + ( b × c )&lt;/b&gt;&lt;/p&gt;&lt;p data-block-key=&quot;5f9qa&quot;&gt;When done with floating point numbers, it might be performed with two roundings (typical in many DSPs), or with a single rounding. When performed with a single rounding, it is called a &lt;b&gt;fused multiply–add (FMA) or fused multiply–accumulate (FMAC)&lt;/b&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;5798h&quot;&gt;An FMA instruction carries the two operations in one step, and does them in &amp;quot;infinite precision&amp;quot;. Notably, an FMA does only one rounding at the end instead of the sequence expressed by the source code, which is: 1) multiplying, 2) rounding the result, 3) adding, 4) rounding the result. So, there are &lt;i&gt;two&lt;/i&gt; steps of rounding.&lt;/p&gt;&lt;p data-block-key=&quot;kvbxy&quot;&gt;So not only is it faster, but it&amp;#x27;s also more accurate.&lt;/p&gt;&lt;p data-block-key=&quot;irriz&quot;&gt;&lt;b&gt;However,&lt;/b&gt; one can easily encounter cases (like the two cases illustrated above) in which doing operations &lt;i&gt;without&lt;/i&gt; the intermediate rounding step will give you trouble.&lt;/p&gt;&lt;p data-block-key=&quot;ih3zs&quot;&gt;Let&amp;#x27;s look again at the first example:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;const double scale = 1.0 / i;
const double r = 1.0 - i * scale;
assert(r &amp;gt;= 0);&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;v3bd1&quot;&gt;If &lt;code&gt;i = 5&lt;/code&gt;, then &lt;code&gt;scale&lt;/code&gt; is&lt;br/&gt; &lt;code&gt;0.200000000000000011102230246251565404236316680908203125&lt;/code&gt;, and &lt;code&gt;r&lt;/code&gt; is&lt;br/&gt; negative (about &lt;code&gt;-5.55e-17&lt;/code&gt;) when using a FMA. The point is that &lt;code&gt;i * scale&lt;/code&gt; &lt;b&gt;did not&lt;/b&gt; get rounded in an intermediate.&lt;/p&gt;&lt;p data-block-key=&quot;81ycr&quot;&gt;In the second example,&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;const double result = std::sqrt(a*a - b*b);&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8uj9e&quot;&gt;the argument to &lt;code&gt;sqrt(a*a - b*b)&lt;/code&gt; can be turned into &lt;code&gt;FMA(a, a, -b*b)&lt;/code&gt;. If &lt;code&gt;a == b&lt;/code&gt;. Then, this expression is equivalent to &lt;code&gt;FMA(a, a, -a*a)&lt;/code&gt;. The problem is that, if &lt;code&gt;a*a&lt;/code&gt; done in &amp;quot;infinite precision&amp;quot; is strictly less than the rounded product of &lt;code&gt;a*a&lt;/code&gt;, then the result will again be a negative number (not 0!) passed into &lt;code&gt;sqrt&lt;/code&gt;. This is very easy to obtain (&lt;a href=&quot;https://gcc.godbolt.org/z/M1WM3s7G8&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;example on CE&lt;/a&gt;)!&lt;/p&gt;&lt;h2 id=&quot;rounding-in-c&quot; anchor=&quot;rounding-in-c&quot; data-block-key=&quot;8uvtb&quot;&gt;Rounding in C++&lt;/h2&gt;&lt;p data-block-key=&quot;nkm0m&quot;&gt;For me, the interesting question is, &amp;quot;Is the compiler allowed to do these manipulations, since they affect the rounding as expressed by the source code?&amp;quot;&lt;/p&gt;&lt;p data-block-key=&quot;7rpaa&quot;&gt;Within the context of &lt;b&gt;one&lt;/b&gt; expression, compilers can use as much precision as they want. This is allowed by:&lt;br/&gt; &lt;a href=&quot;https://eel.is/c++draft/expr.pre#6&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;[expr.pre/6]&lt;/a&gt; and similar paragraphs:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;z0a4z&quot;&gt;The values of the floating-point operands and the results of floating-point expressions may be represented in greater precision and range than that required by the type; the types are not changed thereby.&lt;/p&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;94dkv&quot;&gt;with a footnote that says:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;vc4ab&quot;&gt;The cast and assignment operators must still perform their specific conversions as described in [expr.type.conv], [expr.cast], [expr.static.cast] and [expr.ass].&lt;/p&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gpx02&quot;&gt;Now suppose that one turns&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;const double scale = 1.0 / i;
const double r = 1.0 - i * scale;&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;42o23&quot;&gt;into separate expressions and statements:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;const double scale = 1.0 / i;
const double tmp = i * scale;
const double r = 1.0 - tmp;&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;chs77&quot;&gt;Here, &lt;i&gt;in theory&lt;/i&gt;, the source code mandates that &lt;code&gt;tmp&lt;/code&gt; is rounded; so a compiler cannot do a FMA when calculating &lt;code&gt;r&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;0qw30&quot;&gt;&lt;b&gt;In practice, compilers violate the standard and apply FMA. :-)&lt;/b&gt;&lt;/p&gt;&lt;p data-block-key=&quot;tk2fp&quot;&gt;In literature, these substitutions are called &amp;quot;floating point contractions.&amp;quot; Let&amp;#x27;s read what &lt;a href=&quot;https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;the GCC manual&lt;/a&gt; has to say about them:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;zh9tk&quot;&gt;By default, -fexcess-precision=fast is in effect; this means that operations may be carried out in a wider precision than the types specified in the source if that would result in faster code, and it is unpredictable when rounding to the types specified in the source code takes place.&lt;/p&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8d90c&quot;&gt;(Emph. mine)&lt;/p&gt;&lt;p data-block-key=&quot;6hqkh&quot;&gt;Hence, you can turn these optimizations off by compiling under &lt;code&gt;-std=c++XX&lt;/code&gt;, not &lt;code&gt;-std=gnu++XX&lt;/code&gt; (the default). If you try to use &lt;code&gt;-fexcess-precision=standard&lt;/code&gt;, then GCC lets you know that:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;cc1plus: sorry, unimplemented: &amp;#x27;-fexcess-precision=standard&amp;#x27; for C++&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;the-origin&quot; anchor=&quot;the-origin&quot; data-block-key=&quot;7c6lq&quot;&gt;The Origin&lt;/h2&gt;&lt;p data-block-key=&quot;4yp9j&quot;&gt;Where does all this nonsense come from? The first testcase is actually out of &lt;a href=&quot;https://codebrowser.dev/qt5/qtdeclarative/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp.html#3208&amp;lt;br /&amp;gt;
&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Qt Quick rendering code&lt;/a&gt;. It has been lingering around for a decade!&lt;/p&gt;&lt;p data-block-key=&quot;g7yew&quot;&gt;Qt Quick wants to give a unique Z value to each element of the scene. These Z values are then going to be used by the underlying graphics stack (GL, Vulkan, Metal) as the depth of the element. This allows Qt Quick to render a scene using the ordinary depth testing that a GPU provides.&lt;/p&gt;&lt;p data-block-key=&quot;sevwb&quot;&gt;The Z values themselves have no intrinsic meaning, as long as they establish an order. That&amp;#x27;s why they&amp;#x27;re simply picked to be equidistant in a given range (simplest strategy that maximizes the available resolution).&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;screenshot-1.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/screenshot-1.original.png&quot; class=&quot;screenshot-1.png&quot; alt=&quot;screenshot-1.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;i4p1b&quot;&gt;Now, the underlying 3D APIs want a depth coordinate precisely in [0.0, 1.0]. So that&amp;#x27;s picked as range and then inverted (going from 1.0 to 0.0) because, for various reasons, Qt Quick wants to render back-to-front (smaller depth means &amp;quot;closer to the camera&amp;quot;, i.e. on top in the Qt Quick scene.).&lt;/p&gt;&lt;p data-block-key=&quot;8jjrn&quot;&gt;When the bug above gets triggered &lt;b&gt;the topmost element of the scene doesn&amp;#x27;t get rendered at all&lt;/b&gt;. That is because its calculated Z value is negative; instead of being the &amp;quot;closest to the camera&amp;quot; (it&amp;#x27;s the topmost element in the scene), the 3D API will think the object ended up being &lt;i&gt;behind the camera&lt;/i&gt; and will cull it away.&lt;/p&gt;&lt;p data-block-key=&quot;4mf65&quot;&gt;So why didn&amp;#x27;t anyone notice so far in the last 10 years? On one hand, it&amp;#x27;s because no one seems to compile Qt with aggressive compiler optimizations enabled. For instance, on X86-64 one needs to opt-in to FMA instructions; on GCC, you need to pass &lt;code&gt;-march=haswell&lt;/code&gt; or higher. On ARM(64), this manifests more &amp;quot;out of the box&amp;quot; since ARM7/8 have FMA instructions.&lt;/p&gt;&lt;p data-block-key=&quot;94gvi&quot;&gt;On the other hand, because &lt;b&gt;by accident&lt;/b&gt; everything works fine on OpenGL. Unlike other 3D graphics APIs, on OpenGL the depth range in &lt;i&gt;Normalized Device Coordinates&lt;/i&gt; is from -1 to +1, and not 0 to +1. So even a (slightly) negative value for the topmost element is fine. If one peeks at an OpenGL call trace (using apitrace or similar tools), one can clearly see the negative Z being set.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-75 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;gl_projectionmatrix01.png&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/gl_projectionmatrix01.original.png&quot; class=&quot;gl_projectionmatrix01.png&quot; alt=&quot;gl_projectionmatrix01.png&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;sgoh1&quot;&gt;Only on a relatively more recent combination of components does the bug manifest itself, for instance, on Mac:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;0yfsr&quot;&gt;Qt 6 ⇒ Metal as graphics API (through RHI)&lt;/li&gt;&lt;li data-block-key=&quot;vap69&quot;&gt;ARM8 ⇒ architecture with FMA&lt;/li&gt;&lt;li data-block-key=&quot;s00m1&quot;&gt;Clang 14 in the latest XCode ⇒ enables FP contractions by default&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;3fwb1&quot;&gt;Windows and Direct3D (again through RHI) are also, in theory, affected, but MSVC does not generate FMA instructions &lt;i&gt;at all&lt;/i&gt;. On Linux, including embedded Linux (e.g. running on ARM), most people still use OpenGL and not Vulkan. Therefore, although GCC has floating-point contractions enabled by default, the bug doesn&amp;#x27;t manifest itself.&lt;/p&gt;&lt;p data-block-key=&quot;xfpil&quot;&gt;Definitely an &lt;a href=&quot;https://bugreports.qt.io/browse/QTBUG-109739&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;interesting one&lt;/a&gt; to research; many kudos to the original reporter. The &lt;a href=&quot;https://codereview.qt-project.org/c/qt/qtdeclarative/+/451209&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;proposed fix&lt;/a&gt; was simply to clamp the values to the wanted range. I&amp;#x27;m not sure if one can find a numerical solution that works in all cases.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/fma-woes/&quot;&gt;FMA Woes&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>3d</category><category>c++</category><category>performance</category><category>qml</category><category>qt</category></item><item><title>The Top 100 QML Resources for Developers</title><link>https://www.kdab.com/top-100-qml-resources-kdab/</link><guid isPermaLink="true">https://www.kdab.com/top-100-qml-resources-kdab/</guid><description>&lt;p data-block-key=&quot;ol7mn&quot;&gt;If you’re a reader of this blog, you probably know that we have a huge amount of quality material on QML and Qt Quick, among other topics. In fact, there is so much material that it can be hard to find what you need. If that sounds familiar, you’ll want to bookmark this page! This […]&lt;/p&gt;</description><pubDate>Mon, 19 Dec 2022 10:03:00 GMT</pubDate><content:encoded>&lt;h1&gt;The Top 100 QML Resources for Developers&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;4fa8x&quot;&gt;If you’re a reader of this blog, you probably know that we have a huge amount of quality material on QML and Qt Quick, among other topics. In fact, there is so much material that it can be hard to find what you need.&lt;/p&gt;&lt;p data-block-key=&quot;4d7wk&quot;&gt;If that sounds familiar, you’ll want to bookmark this page! This blog captures a snapshot of the top 100 resources we offer on QML and Qt Quick. This mix of blogs, instructional videos, and other resources has been organized into simple, easy-to-understand categories with simple descriptions added when necessary.&lt;/p&gt;&lt;p data-block-key=&quot;hq2i8&quot;&gt;If you’re just getting started with Qt, you’ll want to begin with our training class. And if there’s a topic here you can’t find, you may also want to try using our &lt;a href=&quot;https://www.kdab.com/resources/&quot;&gt;search&lt;/a&gt; or visit our &lt;a href=&quot;https://www.youtube.com/c/KDABtv&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;YouTube channel&lt;/a&gt; for even more content.&lt;/p&gt;&lt;h2 id=&quot;qml-tutorials-for-beginners&quot; anchor=&quot;qml-tutorials-for-beginners&quot; data-block-key=&quot;3f2l7&quot;&gt;&lt;b&gt;QML Tutorials for Beginners&lt;/b&gt;&lt;/h2&gt;&lt;h3 id=&quot;introduction-to-qtqml-full-kdab-training-class&quot; anchor=&quot;introduction-to-qtqml-full-kdab-training-class&quot; data-block-key=&quot;wcn99&quot;&gt;Introduction to Qt/QML - Full KDAB Training Class&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;0jyd3&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL6CJYn40gN6hUXoYdyn4DHUTXfcVDJcfB&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Module 1: Introduction to Qt Quick&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;4o4zy&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL6CJYn40gN6hAypDlt2EHnPj8mtl7QueU&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Module 2: User Interfaces basics&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;81wqu&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL6CJYn40gN6hMnhNCOIAx7jk0ZwRLW_Fa&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Module 3: User Interaction&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;5zo95&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL6CJYn40gN6hfhS5o4X45cfPw7acPX6uJ&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Module 4: Components and dynamic loading&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;3zy7v&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL6CJYn40gN6gx7N631Az7VjyxMOyyCpti&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Module 5: Animations and States&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;wbee3&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL6CJYn40gN6h_12NR620wUHvPZJ6isI3P&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Module 6: Presenting Data&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;k19wk&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL6CJYn40gN6gdhv9f3F3rrRC8X52pKI1J&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Module 7: The C++ machine room&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;5imuf&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL6CJYn40gN6h3usMQY3BSZJs08isz3jqa&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Module 8: Integrating QML with C++&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;dmkxk&quot;&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL6CJYn40gN6hY4Lef4tqtjWLPO361tx9v&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Module 9: Model/View from the C++ level&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;how-to-tutorials&quot; anchor=&quot;how-to-tutorials&quot; data-block-key=&quot;at9zn&quot;&gt;How-To Tutorials&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;hb6mv&quot;&gt;&lt;a href=&quot;https://www.kdab.com/3d-block-building-game/&quot;&gt;A 3D Block Building Game in QML&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;tmwny&quot;&gt;&lt;a href=&quot;https://www.kdab.com/qt-and-the-unu-dashboard/&quot;&gt;Qt and the unu dashboard&lt;/a&gt; &lt;i&gt;– using Redis&lt;/i&gt; &lt;i&gt;and pub/sub&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;get-the-most-out-of-qt-creator&quot; anchor=&quot;get-the-most-out-of-qt-creator&quot; data-block-key=&quot;2y0n5&quot;&gt;&lt;b&gt;Get the most out of Qt Creator&lt;/b&gt;&lt;/h2&gt;&lt;h3 id=&quot;maximizing-your-ide-efficiency&quot; anchor=&quot;maximizing-your-ide-efficiency&quot; data-block-key=&quot;2ns3z&quot;&gt;Maximizing your IDE efficiency&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;ka7cz&quot;&gt;&lt;a href=&quot;https://www.kdab.com/software-technologies/qt/qt-creator-reference-card/&quot;&gt;Qt Creator cheat-sheet&lt;/a&gt; – double-sided page of the best keyboard shortcuts&lt;/li&gt;&lt;li data-block-key=&quot;pute4&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=qmDU5xiP2x4&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=2&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Sessions in Qt Creator&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;jusz0&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=3Dg5u1Mrj0I&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=4&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Spell Checking in Qt Creator&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;lx6zp&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7Zn6r9HYy6Y&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=8&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Top 7 Shortcuts in Qt Creator&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;rmwl6&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=mgBBT7aUNco&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=22&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Writing Qt Creator Debugging Helpers&lt;/a&gt; &lt;i&gt;–&lt;/i&gt; &lt;i&gt;straightforward variable examination&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;kobxh&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=bdHcwZsxiRo&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=23&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Grepping in Qt Creator&lt;/a&gt; &lt;i&gt;–&lt;/i&gt; &lt;i&gt;getting the most out of&lt;/i&gt; &lt;i&gt;search&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;8lhcp&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=OXAWLzx4np8&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=24&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Qt Creator Refactoring Part 1&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;q0umq&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=XT53pEAOKbs&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=25&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Qt Creator Refactoring Part 2&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;bze0o&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=8JfVpJg95Po&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=26&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Using Bookmarks in Qt Creator&lt;/a&gt; &lt;i&gt;– moving through large code bases&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;k3ccx&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=2b4_R46OYeM&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=56&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Why doesn&amp;#x27;t my Qt Creator find my files anymore&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;s5njd&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Of-SfIzNARU&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=49&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Mass Text Editing in Qt Creator Using Macros, Block Commands, and more&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;2njbu&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Cx_m-qVnEjo&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=53&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Save Re-compile Time - Include moc Files in Source Files&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;customizing-the-ide&quot; anchor=&quot;customizing-the-ide&quot; data-block-key=&quot;h69l1&quot;&gt;Customizing the IDE&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;ladzl&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=iBbWbJo4Xpw&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=7&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Changing the Font to Jetbrains Mono in Qt Creator&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;ung16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=rX2jXRU8Qho&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=10&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Document Templates in Qt Creator - Part 1&lt;/a&gt; – &lt;i&gt;customizing new files&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;99409&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ZpdFzro9KGI&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=11&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Document Templates in Qt Creator - Part 2&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;jpa4i&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=KvB6S3RAc9k&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=12&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Document Templates in Qt Creator - Part 3&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;d6ywb&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=m6VOl1a80EM&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=13&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Document Templates in Qt Creator - Part 4&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;c03op&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5ELbrOdRM2c&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=16&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Adding CPPreference to Qt Creator&lt;/a&gt; &lt;i&gt;– extending the help system&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;qml-development-best-practices&quot; anchor=&quot;qml-development-best-practices&quot; data-block-key=&quot;0cej4&quot;&gt;&lt;b&gt;QML Development Best practices&lt;/b&gt;&lt;/h2&gt;&lt;h3 id=&quot;development-patterns&quot; anchor=&quot;development-patterns&quot; data-block-key=&quot;ybka0&quot;&gt;Development Patterns&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;ileth&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=q6eOEz_UfTI&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=5&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Communicating between a View/Delegate and a Model&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;8obgi&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=fPy0nuGWTZk&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=45&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;A Complete Proxy Model Implementation - Part 1&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;tvuo6&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7wObo3LVcWA&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=46&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;A Complete Proxy Model Implementation - Part 2&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;mz1pi&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=EGhHlPDeyvg&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=38&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;No More Booleans!&lt;/a&gt; &lt;i&gt;–&lt;/i&gt; &lt;i&gt;when&lt;/i&gt; _enums &lt;i&gt;and other&lt;/i&gt; &lt;i&gt;means are better&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;89rzn&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=vZAvFqv5ZWs&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=39&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Subclassing isn&amp;#x27;t always the solution!&lt;/a&gt; &lt;i&gt;– objects in C++ when they make sense&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;rzz73&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=brINmY_4X60&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=34&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Checking Your QModelIndex(es)&lt;/a&gt; – &lt;i&gt;defensive programming&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;az22j&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=FQki8cWefJ4&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=32&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Lazy Value&lt;/a&gt; – &lt;i&gt;the how and why of delayed computation&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;oq46o&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=V67MauRWDwI&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=19&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Enum Class and Model/View&lt;/a&gt; – &lt;i&gt;static casting that’s safer and less typing&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;development-workflow&quot; anchor=&quot;development-workflow&quot; data-block-key=&quot;ay2nd&quot;&gt;Development Workflow&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;sl94v&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=EKOPlEP4YOY&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=33&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;My Git Workflow in the Shell and from Qt Creator&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;rlw9d&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=1yITO6dTqsU&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=30&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Backing up Source Files Every 10 Minutes on Linux&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;wu3wb&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=e63NlKb5QdA&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=15&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Compile Just This File&lt;/a&gt; &lt;i&gt;– get it right before building the world&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;a20y7&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=JNcXUFsBq1Y&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=44&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Git Switch and Restore&lt;/a&gt; &lt;i&gt;– recovering repository disasters&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;uoshr&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Cz36YveDI2E&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=3&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Using Clang-Format to Ensure Style Guidelines&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;testing&quot; anchor=&quot;testing&quot; data-block-key=&quot;y4tzn&quot;&gt;Testing&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;3c1eo&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=N4pvvCToogM&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=36&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Unit Testing from Qt Creator&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;vb87a&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=4WY2veS28Kc&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=57&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Checking for Regression via Screenshots&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;74lc4&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Tm1nNyM6Upk&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=27&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Testing QAbstractItemModels on the Fly&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;improved-graphics-with-qml&quot; anchor=&quot;improved-graphics-with-qml&quot; data-block-key=&quot;lzsq4&quot;&gt;&lt;b&gt;Improved Graphics with QML&lt;/b&gt;&lt;/h2&gt;&lt;h3 id=&quot;graphics-sizing-and-scaling&quot; anchor=&quot;graphics-sizing-and-scaling&quot; data-block-key=&quot;cnf53&quot;&gt;Graphics Sizing and Scaling&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;34ldp&quot;&gt;&lt;a href=&quot;https://www.kdab.com/borderimage-scaling-scalable-uis-2-2/&quot;&gt;BorderImage is for Scaling!&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;2c0il&quot;&gt;&lt;a href=&quot;https://www.kdab.com/scalable-uis-qml/&quot;&gt;Scalable UIs In QML&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;2693k&quot;&gt;&lt;a href=&quot;https://www.kdab.com/pixels-trust-scalable-uis-qml-part-2/&quot;&gt;In Pixels we trust&lt;/a&gt; &lt;i&gt;– scalable UIs in QML, part 2&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;qml-and-3d&quot; anchor=&quot;qml-and-3d&quot; data-block-key=&quot;pqb8n&quot;&gt;QML and 3D&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;30flu&quot;&gt;&lt;a href=&quot;https://www.kdab.com/integrating-opengl-with-qt-quick-2-applications-part-1/&quot;&gt;How to integrate OpenGL code with Qt Quick 2 applications (part 1)&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;jcmjl&quot;&gt;&lt;a href=&quot;https://www.kdab.com/integrate-opengl-code-qt-quick-2-applications-part-2/&quot;&gt;How to integrate OpenGL code with Qt Quick 2 applications (part 2)&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;n9e03&quot;&gt;&lt;a href=&quot;https://www.kdab.com/integrating-opengl-with-qt-quick-2-applications/&quot;&gt;Integrating OpenGL with Qt Quick 2 applications&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;7ki0h&quot;&gt;&lt;a href=&quot;https://www.kdab.com/integrating-qtquick-2-3d-renderers/&quot;&gt;Integrating QtQuick 2 with 3D renderers&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;qml-components&quot; anchor=&quot;qml-components&quot; data-block-key=&quot;1s43f&quot;&gt;QML Components&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;u7rr8&quot;&gt;&lt;a href=&quot;https://www.kdab.com/qml-component-design/&quot;&gt;QML Component Design&lt;/a&gt; &lt;i&gt;– creating unbreakable bindings&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;7zpny&quot;&gt;&lt;a href=&quot;https://www.kdab.com/declarative-widgets/&quot;&gt;Declarative Widgets&lt;/a&gt; &lt;i&gt;– adding Qt Widgets to QML&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;vijcj&quot;&gt;&lt;a href=&quot;https://www.kdab.com/efficient-custom-shapes-in-qt-quick/&quot;&gt;Efficient custom shapes in Qt Quick&lt;/a&gt; – &lt;i&gt;the perfect mix of triangles and shaders&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;sud8e&quot;&gt;&lt;a href=&quot;https://www.kdab.com/qtquick-shaders/&quot;&gt;Efficient Custom Shapes in QtQuick : Shaders&lt;/a&gt; – &lt;i&gt;coding the fragment shader&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;gaf6y&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=DFyjLYcxwU4&amp;amp;list=PL6CJYn40gN6gK8l5VXdt7WNRPmhbt0VoQ&amp;amp;index=12&amp;amp;t=235s&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;QtDD - Breathing new QML life into a QWidget-based app from the 2000s&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;qcetm&quot;&gt;&lt;a href=&quot;https://www.kdab.com/qtwidgets-qtquick-controls-comparison/&quot;&gt;QtWidgets and QtQuick Controls – A Comparison&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;kbbxf&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=9-8NkKBItCo&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=50&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Migrating to Qt6 - QVariant&lt;/a&gt; – &lt;i&gt;solving&lt;/i&gt; &lt;i&gt;combobox&lt;/i&gt; &lt;i&gt;problems with&lt;/i&gt; &lt;i&gt;QVariant&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;m3xvi&quot;&gt;&lt;a href=&quot;https://www.kdab.com/writing-custom-qt-quick-components-using-opengl/&quot;&gt;Writing custom Qt Quick components using OpenGL&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;qml-in-depth-for-advanced-developers&quot; anchor=&quot;qml-in-depth-for-advanced-developers&quot; data-block-key=&quot;e6j5k&quot;&gt;&lt;b&gt;QML in-depth for advanced developers&lt;/b&gt;&lt;/h2&gt;&lt;h3 id=&quot;special-problems&quot; anchor=&quot;special-problems&quot; data-block-key=&quot;8t3qx&quot;&gt;Special Problems&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;9q35k&quot;&gt;&lt;a href=&quot;https://www.kdab.com/fun-with-paths-urls-in-qml/&quot;&gt;Fun with Paths and URLs in QML&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;2rx90&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Dy4EfRa7NjQ&amp;amp;list=PL6CJYn40gN6gK8l5VXdt7WNRPmhbt0VoQ&amp;amp;index=3&amp;amp;t=2s&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;QtDD - Insights From Building A Desktop Productivity App Using QML&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;l5afg&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=-CM45D0QBfc&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=43&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Understanding qAsConst / std::as_const&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;tiu0b&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=GUdQ9u34HQI&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=48&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;The C++ Explicit Keyword and Qt&lt;/a&gt; – &lt;i&gt;understanding why and when it’s used&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;mobs6&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=zrz_AXpgkrE&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=40&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Using strong_typedef with Qt for Improved Safety&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;9sqiy&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=caNufuHsUjs&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=9&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Avoiding QVariant::fromValue around your Own Types&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;strings&quot; anchor=&quot;strings&quot; data-block-key=&quot;uekpu&quot;&gt;Strings&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;jhcvq&quot;&gt;&lt;a href=&quot;https://www.kdab.com/handling-a-lot-of-text-in-qml/&quot;&gt;Handling a Lot of Text in QML&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;zi8b2&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=GlP0JHUUP8A&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=51&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Which String Class in Qt Should I Use?&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;0d0ld&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=tLFYa97Zds4&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=52&amp;amp;t=323s&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;QStringBuilder&lt;/a&gt; &lt;i&gt;– what it is and how to use it&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;av9bh&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=hWh_bA0-_KM&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=14&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Converting Enums to and from Strings&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;specific-environments&quot; anchor=&quot;specific-environments&quot; data-block-key=&quot;9wmgr&quot;&gt;Specific Environments&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;mqyd6&quot;&gt;&lt;a href=&quot;https://www.kdab.com/maps-and-navigation/&quot;&gt;Automotive Grade Maps and Navigation for Everyone&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;57j98&quot;&gt;&lt;a href=&quot;https://www.kdab.com/embedding-qml-why-where-and-how/&quot;&gt;Embedding QML: Why, Where, and How&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;6no77&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=fbN8bAxqtcM&amp;amp;list=PL6CJYn40gN6gK8l5VXdt7WNRPmhbt0VoQ&amp;amp;index=7&amp;amp;t=659s&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;QtDD - Using QtQuick Designer for Desktop Applications&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;pwm3m&quot;&gt;&lt;a href=&quot;https://www.kdab.com/qt-android-create-zero-copy-android-surfacetexture-qml-item/&quot;&gt;Qt on Android: How to create a zero-copy Android SurfaceTexture QML item&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;k2yvs&quot;&gt;&lt;a href=&quot;https://www.kdab.com/running-qtquick-applications-web/&quot;&gt;Running QtQuick Applications on the Web&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;qml-internals&quot; anchor=&quot;qml-internals&quot; data-block-key=&quot;apmz4&quot;&gt;QML Internals&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;juhyh&quot;&gt;&lt;a href=&quot;https://www.kdab.com/qml-engine-internals-part-1-qml-file-loading/&quot;&gt;QML Engine Internals, Part 1: QML File Loading&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;1vwjp&quot;&gt;&lt;a href=&quot;https://www.kdab.com/qml-engine-internals-part-2-bindings/&quot;&gt;QML Engine Internals, Part 2: Bindings&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;qfiby&quot;&gt;&lt;a href=&quot;https://www.kdab.com/qml-engine-internals-part-3-binding-types/&quot;&gt;QML Engine Internals, Part 3: Binding Types&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;tdjc3&quot;&gt;&lt;a href=&quot;https://www.kdab.com/qml-engine-internals-part-4-custom-parsers/&quot;&gt;QML Engine Internals, Part 4: Custom Parsers&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;j3fxk&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=-5l2ozI_6v0&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=35&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Building Qt&lt;/a&gt; &lt;i&gt;– how to build&lt;/i&gt; &lt;i&gt;Qt from source&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;b6cge&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=N89s0c8qRxc&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=55&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Reading the Qt Source Code&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;indispensable-tools-for-your-qml-projects&quot; anchor=&quot;indispensable-tools-for-your-qml-projects&quot; data-block-key=&quot;1oyjv&quot;&gt;&lt;b&gt;Indispensable Tools for Your QML Projects&lt;/b&gt;&lt;/h2&gt;&lt;h3 id=&quot;debugging&quot; anchor=&quot;debugging&quot; data-block-key=&quot;lyvkh&quot;&gt;Debugging&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;xkkh3&quot;&gt;&lt;a href=&quot;https://www.kdab.com/full-stack-tracing-part-1/&quot;&gt;Full Stack Tracing Part 1&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;2msno&quot;&gt;&lt;a href=&quot;https://www.kdab.com/full-stack-tracing-part-2/&quot;&gt;Full Stack Tracing Part 2&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;zu9es&quot;&gt;&lt;a href=&quot;https://www.kdab.com/full-stack-tracing-part-3/&quot;&gt;Full Stack Tracing, Part 3&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;yc6i7&quot;&gt;&lt;a href=&quot;https://www.kdab.com/fixing-bugs-via-lateral-thinking/&quot;&gt;Fixing bugs via lateral thinking&lt;/a&gt; &lt;i&gt;– finding&lt;/i&gt; &lt;i&gt;really hard&lt;/i&gt; &lt;i&gt;bugs&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;4vt15&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=0uYsZEAQiLM&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=20&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Reverse Debugging Using rr&lt;/a&gt; – &lt;i&gt;finding bug root causes&lt;/i&gt; &lt;i&gt;faster&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;mos0e&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=2e2MGZKSvBY&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=21&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Speeding up the Start-up of GDB&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;ogsje&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=f12lUvbdj2U&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=31&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;qDebug - Power User&lt;/a&gt; – &lt;i&gt;making the best of simple console output&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;profiling&quot; anchor=&quot;profiling&quot; data-block-key=&quot;pb8c8&quot;&gt;Profiling&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;yftkq&quot;&gt;&lt;a href=&quot;https://www.kdab.com/new-qt-5-10-diagnostics-breaking-qml-bindings/&quot;&gt;New in Qt 5.10: Diagnostics when breaking QML bindings&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;4e79b&quot;&gt;&lt;a href=&quot;https://www.kdab.com/analyzing-performance-qtquick-applications/&quot;&gt;Analyzing Performance of QtQuick Applications&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;cpk1g&quot;&gt;&lt;a href=&quot;https://www.kdab.com/profiling-qtquick-performance/&quot;&gt;Profiling QtQuick HMI Performance on Embedded Linux&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;usud5&quot;&gt;&lt;a href=&quot;https://www.kdab.com/profile-qtquick-applications-mx-6-vanalyzer/&quot;&gt;How to Profile QtQuick applications on Freescale i.MX 6 with vAnalyzer&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;ntrnc&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=cKedzwAWBC0&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=17&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Compiling (Part 1) - Speeding Up Compilation Using CCache, Ninja and Clang + Integration in Qt Creator&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;31rlv&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=a6PdfwVE9hA&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=18&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Compiling (Part 2) - Speeding Up Compilation Using PCH Support in CMake&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;tools&quot; anchor=&quot;tools&quot; data-block-key=&quot;pd4kj&quot;&gt;Tools&lt;/h3&gt;&lt;ul&gt;&lt;li data-block-key=&quot;8052w&quot;&gt;&lt;a href=&quot;https://www.kdab.com/using-vsc-for-qt-apps-part-3/&quot;&gt;VS Code for Qt Applications – Part 3&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;l4ayn&quot;&gt;&lt;a href=&quot;https://www.kdab.com/kdab-contributions-qt-5-4-qmllint/&quot;&gt;KDAB contributions to Qt 5.4: qmllint&lt;/a&gt; &lt;i&gt;–&lt;/i&gt; &lt;i&gt;identifying&lt;/i&gt; &lt;i&gt;QML errors&lt;/i&gt;&lt;/li&gt;&lt;li data-block-key=&quot;mn9bz&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=KkJfkrIO29Y&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=6&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Visualizing the Model Stack in GammaRay&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;5nfmz&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=gO3KCzdmcrQ&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=28&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Running clang-tidy and clazy from Qt Creator&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;si6je&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=c0ie0xww7SA&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=29&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Running clazy and clang-tidy from the Command Line&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;wug4v&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=akGr97hf3J8&amp;amp;list=PL6CJYn40gN6jWHP5krsQrVGyYtKh3A3be&amp;amp;index=54&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Improving My clang-tidy Checks&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/top-100-qml-resources-kdab/&quot;&gt;The Top 100 QML Resources for Developers&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Editor Team</dc:creator><category>3d</category><category>performance</category><category>qml</category><category>qt</category></item></channel></rss>